238 lines
5.5 KiB
Plaintext
238 lines
5.5 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) {
|
|
opts = opts || {}
|
|
var c = {
|
|
id: _collider_id++,
|
|
type: "sphere",
|
|
transform: transform,
|
|
radius: radius,
|
|
layer_mask: opts.layer_mask || 1,
|
|
user: opts.user
|
|
}
|
|
_colliders.push(c)
|
|
return c
|
|
}
|
|
|
|
function add_box(transform, sx, sy, sz, opts) {
|
|
opts = opts || {}
|
|
var c = {
|
|
id: _collider_id++,
|
|
type: "box",
|
|
transform: transform,
|
|
sx: sx, sy: sy, sz: sz,
|
|
layer_mask: opts.layer_mask || 1,
|
|
user: opts.user
|
|
}
|
|
_colliders.push(c)
|
|
return c
|
|
}
|
|
|
|
function remove(collider) {
|
|
for (var i = 0; i < _colliders.length; 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 = []
|
|
for (var i = 0; i < _colliders.length; i++) {
|
|
for (var j = i + 1; j < _colliders.length; j++) {
|
|
var a = _colliders[i]
|
|
var 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.push({a: a, b: b})
|
|
}
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
function raycast(ox, oy, oz, dx, dy, dz, opts) {
|
|
opts = opts || {}
|
|
var max_dist = opts.max_dist || 1000000
|
|
var layer_mask = opts.layer_mask || 0xFFFFFFFF
|
|
|
|
// Normalize direction
|
|
var len = math.sqrt(dx*dx + dy*dy + dz*dz)
|
|
if (len < 0.0001) return null
|
|
dx /= len
|
|
dy /= len
|
|
dz /= len
|
|
|
|
var closest = null
|
|
var closest_dist = max_dist
|
|
|
|
for (var i = 0; i < _colliders.length; i++) {
|
|
var c = _colliders[i]
|
|
if (!(c.layer_mask & layer_mask)) continue
|
|
|
|
var hit = null
|
|
if (c.type == "sphere") {
|
|
hit = _ray_sphere(ox, oy, oz, dx, dy, dz, c)
|
|
} else if (c.type == "box") {
|
|
hit = _ray_box(ox, oy, oz, dx, dy, dz, 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 (transform.length == 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 || number.max(a.sx || 0, a.sy || 0, a.sz || 0)
|
|
var rb = b.radius || number.max(b.sx || 0, b.sy || 0, b.sz || 0)
|
|
return dist < ra + rb
|
|
}
|
|
|
|
function _ray_sphere(ox, oy, oz, dx, dy, dz, sphere) {
|
|
var pos = _get_position(sphere.transform)
|
|
var r = sphere.radius
|
|
|
|
// 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
|
|
if (tca < 0) return null
|
|
|
|
var d2 = lx*lx + ly*ly + lz*lz - tca*tca
|
|
var r2 = r*r
|
|
if (d2 > r2) return null
|
|
|
|
var thc = math.sqrt(r2 - d2)
|
|
var t = tca - thc
|
|
if (t < 0) t = tca + thc
|
|
if (t < 0) return null
|
|
|
|
var hx = ox + dx*t
|
|
var hy = oy + dy*t
|
|
var hz = oz + dz*t
|
|
|
|
// Normal at hit point
|
|
var nx = (hx - pos.x) / r
|
|
var ny = (hy - pos.y) / r
|
|
var 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(ox, oy, oz, dx, dy, dz, box) {
|
|
var pos = _get_position(box.transform)
|
|
var hx = (box.sx || 1) / 2
|
|
var hy = (box.sy || 1) / 2
|
|
var hz = (box.sz || 1) / 2
|
|
|
|
var minx = pos.x - hx, maxx = pos.x + hx
|
|
var miny = pos.y - hy, maxy = pos.y + hy
|
|
var minz = pos.z - hz, maxz = pos.z + hz
|
|
|
|
var tmin = -1000000, tmax = 1000000
|
|
var nx = 0, ny = 0, nz = 0
|
|
|
|
// X slab
|
|
if (number.abs(dx) > 0.0001) {
|
|
var t1 = (minx - ox) / dx
|
|
var t2 = (maxx - ox) / dx
|
|
if (t1 > t2) { var 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 (number.abs(dy) > 0.0001) {
|
|
var t1 = (miny - oy) / dy
|
|
var t2 = (maxy - oy) / dy
|
|
if (t1 > t2) { var 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 (number.abs(dz) > 0.0001) {
|
|
var t1 = (minz - oz) / dz
|
|
var t2 = (maxz - oz) / dz
|
|
if (t1 > t2) { var 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
|
|
|
|
var 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
|
|
}
|