// 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 }