fury-js
Version:
WebGL Game Engine
147 lines (122 loc) • 5.52 kB
JavaScript
const vec3 = require('./maths').vec3;
module.exports = (function() {
let exports = {};
let prototype = {
recalculateMinMax: function() {
vec3.subtract(this.min, this.center, this.extents);
vec3.add(this.max, this.center, this.extents);
},
recalculateExtents: function() {
vec3.subtract(this.size, this.max, this.min);
// If we had a vec3.zero vector could use scale and add
this.extents[0] = 0.5 * this.size[0];
this.extents[1] = 0.5 * this.size[1];
this.extents[2] = 0.5 * this.size[2];
vec3.add(this.center, this.min, this.extents);
}
};
exports.contains = function(point, box) {
return point[0] >= box.min[0] && point[0] <= box.max[0]
&& point[1] >= box.min[1] && point[1] <= box.max[1]
&& point[2] >= box.min[2] && point[2] <= box.max[2];
};
// TODO: Adds Touches methods which use <= and >=
// Note - ray casts should probably return true for touches
exports.intersect = function(a, b) {
return (a.min[0] < b.max[0] && a.max[0] > b.min[0])
&& (a.min[1] < b.max[1] && a.max[1] > b.min[1])
&& (a.min[2] < b.max[2] && a.max[2] > b.min[2]);
};
// Return true if box b and box a overlap on provided axis
exports.intersectsAxis = function(a, b, axis) {
return a.min[axis] < b.max[axis] && a.max[axis] > b.min[axis];
};
// Returns true if box b offset by provided displacement would intersect box a on provided axis
exports.intersectsAxisOffset = function(a, b, axis, displacement) {
return a.min[axis] < b.max[axis] + displacement && a.max[axis] > b.min[axis] + displacement;
};
// Enters functions return true if box b did not intersect box a on specified axis
// before displacement but would afterwards. Calculating the point of entry could be useful.
// If it's always needed we could return the distance and use > 0 check for does enter
exports.entersAxis = function(a, b, axis, displacement) {
return !(a.min[axis] < b.max[axis] && a.max[axis] > b.min[axis])
&& (a.min[axis] < b.max[axis] + displacement && a.max[axis] > b.min[axis] + displacement);
};
// Entered is the same as enters but it assumes you've already moved the box
exports.enteredAxis = function(a, b, axis, displacement) {
return !(a.min[axis] < b.max[axis] - displacement && a.max[axis] > b.min[axis] - displacement)
&& (a.min[axis] < b.max[axis] && a.max[axis] > b.min[axis]);
};
exports.rayCast = function(out, origin, direction, box) {
// Using 0 to imply no intersection so we can return distance (if normalized)
// Wouldn't work if we included origin touching as impact
// Check we aren't in the box - note also includes touching
if (exports.contains(origin, box)) {
return 0;
}
// AABB: is box center in at least one direction from origin?
for (let i = 0; i < 3; i++) {
if (Math.sign(box.center[i] - origin[i]) == Math.sign(direction[i])
&& !(origin[i] >= box.min[i] && origin[i] <= box.max[i])) { // and NOT INSIDE the box on this axis
let axis = i;
// Move along that axis to find the intersection point on this axis
let ip = box.center[axis] - Math.sign(direction[axis]) * box.extents[axis];
let s = ip - origin[axis]; // distance to intersection
let k = s / direction[axis]; // how many dir vectors to get to ip
// ^^ may need to do abs on these but I think they cancel
// calculate the intersection point
vec3.scaleAndAdd(out, origin, direction, k);
let isHit = (axis == 0 || (out[0] >= box.min[0] && out[0] <= box.max[0]))
&& (axis == 1 || (out[1] >= box.min[1] && out[1] <= box.max[1]))
&& (axis == 2 || (out[2] >= box.min[2] && out[2] <= box.max[2]));
if (isHit) {
return k;
}
// If it doesn't collide on this face, maybe it collides on another, keep going!
}
}
return 0;
}
exports.intersectSphere = function(sphere, box) {
// closest point on box to sphere center
let x = Math.max(box.min[0], Math.min(sphere.center[0], box.max[0]));
let y = Math.max(box.min[1], Math.min(sphere.center[1], box.max[1]));
let z = Math.max(box.min[2], Math.min(sphere.center[2], box.max[2]));
let sqrDistance = (x - sphere.center[0]) * (x - sphere.center[0]) +
(y - sphere.center[1]) * (y - sphere.center[1]) +
(z - sphere.center[2]) * (z - sphere.center[2]);
return sqrDistance < sphere.radius * sphere.radius;
};
exports.create = function({ center, size, extents, min, max }) {
// Note - you are expected to recalculate min/max when position or extents change
// or alternatively if you change min/max you can recalculate extents/size/center
let aabb = Object.create(prototype);
if (center || size || extents) {
if (center) {
aabb.center = center;
} else {
aabb.center = vec3.create();
}
if (size) {
aabb.size = size;
aabb.extents = vec3.fromValues(0.5 * aabb.size[0], 0.5 * aabb.size[1], 0.5 * aabb.size[2])
} else if (extents) {
aabb.extents = extents;
aabb.size = vec3.fromValues(2 * aabb.extents[0], 2 * aabb.extents[1], 2 * aabb.extents[2]);
}
aabb.min = vec3.create();
aabb.max = vec3.create();
aabb.recalculateMinMax();
} else {
// Could check min < max on all axes to make this easier to use
aabb.min = min;
aabb.max = max;
aabb.center = vec3.create();
aabb.size = vec3.create();
aabb.extents = vec3.create();
aabb.recalculateExtents();
}
return aabb;
};
return exports;
})();