Files
retro3d/collision.cm
2026-02-19 03:46:58 -06:00

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
}