// 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 < 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 = [] for (var i = 0; i < length(_colliders); i++) { for (var j = i + 1; j < length(_colliders); 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 < length(_colliders); 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 (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(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 (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 (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 (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 }