291 lines
6.1 KiB
Plaintext
291 lines
6.1 KiB
Plaintext
// Collision module for lance3d
|
|
|
|
var math = use('math/radians')
|
|
|
|
// Private collider storage
|
|
var _colliders = []
|
|
var _collider_id = 0
|
|
|
|
function clear() {
|
|
_colliders = []
|
|
}
|
|
|
|
function add_sphere(transform, radius, opts) {
|
|
var _opts = opts || {}
|
|
var c = {
|
|
id: _collider_id++,
|
|
type: "sphere",
|
|
transform: transform,
|
|
radius: radius,
|
|
layer_mask: _opts.layer_mask || 1,
|
|
user: _opts.user
|
|
}
|
|
_colliders[] = c
|
|
return c
|
|
}
|
|
|
|
function add_box(transform, size, opts) {
|
|
var _opts = opts || {}
|
|
var c = {
|
|
id: _collider_id++,
|
|
type: "box",
|
|
transform: transform,
|
|
sx: size.x, sy: size.y, sz: size.z,
|
|
layer_mask: _opts.layer_mask || 1,
|
|
user: _opts.user
|
|
}
|
|
_colliders[] = c
|
|
return c
|
|
}
|
|
|
|
function remove(collider) {
|
|
var i = null
|
|
for (i = 0; i < length(_colliders); i++) {
|
|
if (_colliders[i].id == collider.id) {
|
|
_colliders.splice(i, 1)
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
function overlaps(layer_mask_a, layer_mask_b) {
|
|
var results = []
|
|
var i = null
|
|
var j = null
|
|
var a = null
|
|
var b = null
|
|
for (i = 0; i < length(_colliders); i++) {
|
|
for (j = i + 1; j < length(_colliders); j++) {
|
|
a = _colliders[i]
|
|
b = _colliders[j]
|
|
|
|
if (layer_mask_a != null && !(a.layer_mask & layer_mask_a)) continue
|
|
if (layer_mask_b != null && !(b.layer_mask & layer_mask_b)) continue
|
|
|
|
if (_check_collision(a, b)) {
|
|
results[] = {a: a, b: b}
|
|
}
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
function raycast(origin, direction, opts) {
|
|
var _opts = opts || {}
|
|
var max_dist = _opts.max_dist || 1000000
|
|
var layer_mask = _opts.layer_mask || 0xFFFFFFFF
|
|
var ox = origin.x
|
|
var oy = origin.y
|
|
var oz = origin.z
|
|
var dx = direction.x
|
|
var dy = direction.y
|
|
var dz = direction.z
|
|
var closest = null
|
|
var closest_dist = null
|
|
var i = null
|
|
var c = null
|
|
var hit = null
|
|
var len = null
|
|
var org = null
|
|
var dir = null
|
|
|
|
// Normalize direction
|
|
len = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
if (len < 0.0001) return null
|
|
dx /= len
|
|
dy /= len
|
|
dz /= len
|
|
|
|
org = {x: ox, y: oy, z: oz}
|
|
dir = {x: dx, y: dy, z: dz}
|
|
|
|
closest_dist = max_dist
|
|
|
|
for (i = 0; i < length(_colliders); i++) {
|
|
c = _colliders[i]
|
|
if (!(c.layer_mask & layer_mask)) continue
|
|
|
|
hit = null
|
|
if (c.type == "sphere") {
|
|
hit = _ray_sphere(org, dir, c)
|
|
} else if (c.type == "box") {
|
|
hit = _ray_box(org, dir, c)
|
|
}
|
|
|
|
if (hit && hit.distance < closest_dist) {
|
|
closest = hit
|
|
closest_dist = hit.distance
|
|
}
|
|
}
|
|
|
|
return closest
|
|
}
|
|
|
|
function _get_position(transform) {
|
|
if (!transform) return {x: 0, y: 0, z: 0}
|
|
// If transform is a matrix (array of 16), extract translation
|
|
if (length(transform) == 16) {
|
|
return {x: transform[12], y: transform[13], z: transform[14]}
|
|
}
|
|
// If transform is an object with x,y,z
|
|
if (transform.x != null) {
|
|
return {x: transform.x, y: transform.y, z: transform.z}
|
|
}
|
|
return {x: 0, y: 0, z: 0}
|
|
}
|
|
|
|
function _check_collision(a, b) {
|
|
var pa = _get_position(a.transform)
|
|
var pb = _get_position(b.transform)
|
|
|
|
var dx = pb.x - pa.x
|
|
var dy = pb.y - pa.y
|
|
var dz = pb.z - pa.z
|
|
var dist = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
|
|
// Simple sphere-sphere approximation
|
|
var ra = a.radius || max(a.sx || 0, a.sy || 0, a.sz || 0)
|
|
var rb = b.radius || max(b.sx || 0, b.sy || 0, b.sz || 0)
|
|
return dist < ra + rb
|
|
}
|
|
|
|
function _ray_sphere(origin, dir, sphere) {
|
|
var pos = _get_position(sphere.transform)
|
|
var r = sphere.radius
|
|
var ox = origin.x
|
|
var oy = origin.y
|
|
var oz = origin.z
|
|
var dx = dir.x
|
|
var dy = dir.y
|
|
var dz = dir.z
|
|
|
|
// Vector from ray origin to sphere center
|
|
var lx = pos.x - ox
|
|
var ly = pos.y - oy
|
|
var lz = pos.z - oz
|
|
|
|
// Project onto ray direction
|
|
var tca = lx*dx + ly*dy + lz*dz
|
|
var d2 = null
|
|
var r2 = null
|
|
var thc = null
|
|
var t = null
|
|
var hx = null
|
|
var hy = null
|
|
var hz = null
|
|
var nx = null
|
|
var ny = null
|
|
var nz = null
|
|
if (tca < 0) return null
|
|
|
|
d2 = lx*lx + ly*ly + lz*lz - tca*tca
|
|
r2 = r*r
|
|
if (d2 > r2) return null
|
|
|
|
thc = math.sqrt(r2 - d2)
|
|
t = tca - thc
|
|
if (t < 0) t = tca + thc
|
|
if (t < 0) return null
|
|
|
|
hx = ox + dx*t
|
|
hy = oy + dy*t
|
|
hz = oz + dz*t
|
|
|
|
// Normal at hit point
|
|
nx = (hx - pos.x) / r
|
|
ny = (hy - pos.y) / r
|
|
nz = (hz - pos.z) / r
|
|
|
|
return {
|
|
x: hx, y: hy, z: hz,
|
|
nx: nx, ny: ny, nz: nz,
|
|
distance: t,
|
|
collider: sphere
|
|
}
|
|
}
|
|
|
|
function _ray_box(origin, dir, box) {
|
|
var pos = _get_position(box.transform)
|
|
var ox = origin.x
|
|
var oy = origin.y
|
|
var oz = origin.z
|
|
var dx = dir.x
|
|
var dy = dir.y
|
|
var dz = dir.z
|
|
var hx = (box.sx || 1) / 2
|
|
var hy = (box.sy || 1) / 2
|
|
var hz = (box.sz || 1) / 2
|
|
|
|
var minx = pos.x - hx
|
|
var maxx = pos.x + hx
|
|
var miny = pos.y - hy
|
|
var maxy = pos.y + hy
|
|
var minz = pos.z - hz
|
|
var maxz = pos.z + hz
|
|
|
|
var tmin = -1000000
|
|
var tmax = 1000000
|
|
var nx = 0
|
|
var ny = 0
|
|
var nz = 0
|
|
var t1 = null
|
|
var t2 = null
|
|
var tmp = null
|
|
var t = null
|
|
|
|
// X slab
|
|
if (abs(dx) > 0.0001) {
|
|
t1 = (minx - ox) / dx
|
|
t2 = (maxx - ox) / dx
|
|
if (t1 > t2) { tmp = t1; t1 = t2; t2 = tmp }
|
|
if (t1 > tmin) { tmin = t1; nx = dx > 0 ? -1 : 1; ny = 0; nz = 0 }
|
|
if (t2 < tmax) tmax = t2
|
|
} else if (ox < minx || ox > maxx) {
|
|
return null
|
|
}
|
|
|
|
// Y slab
|
|
if (abs(dy) > 0.0001) {
|
|
t1 = (miny - oy) / dy
|
|
t2 = (maxy - oy) / dy
|
|
if (t1 > t2) { tmp = t1; t1 = t2; t2 = tmp }
|
|
if (t1 > tmin) { tmin = t1; nx = 0; ny = dy > 0 ? -1 : 1; nz = 0 }
|
|
if (t2 < tmax) tmax = t2
|
|
} else if (oy < miny || oy > maxy) {
|
|
return null
|
|
}
|
|
|
|
// Z slab
|
|
if (abs(dz) > 0.0001) {
|
|
t1 = (minz - oz) / dz
|
|
t2 = (maxz - oz) / dz
|
|
if (t1 > t2) { tmp = t1; t1 = t2; t2 = tmp }
|
|
if (t1 > tmin) { tmin = t1; nx = 0; ny = 0; nz = dz > 0 ? -1 : 1 }
|
|
if (t2 < tmax) tmax = t2
|
|
} else if (oz < minz || oz > maxz) {
|
|
return null
|
|
}
|
|
|
|
if (tmin > tmax || tmax < 0) return null
|
|
|
|
t = tmin > 0 ? tmin : tmax
|
|
if (t < 0) return null
|
|
|
|
return {
|
|
x: ox + dx*t, y: oy + dy*t, z: oz + dz*t,
|
|
nx: nx, ny: ny, nz: nz,
|
|
distance: t,
|
|
collider: box
|
|
}
|
|
}
|
|
|
|
return {
|
|
clear: clear,
|
|
add_sphere: add_sphere,
|
|
add_box: add_box,
|
|
remove: remove,
|
|
overlaps: overlaps,
|
|
raycast: raycast
|
|
}
|