/**
* @namespace Intersection
* @memberof SQR
*
* @description Utility to perform intersection tests of rays against colliders. It is a static singleton, no need to instantiate. Just use: SQR.Intersection.rayTest(...)
*/
SQR.Intersection = {};
/**
* Performs an intersection test of a ray against a transform that has a collider. J3D supports three types of colliders: sphere, box and mesh. This method will check what type of collider the transform has and call the apropriate method to make the intersection tests.
*
* WARNING. For meshes that have lots of polys, the ray/mesh intersection test can be very slow! It's better to wrap it into a box or sphere collider or have a simpler version of the mesh to make intersection tests against.
*
* @param {SQR.Ray} r an instance of J3D.Ray
* @param {SQR.Transform} t a transform to test against.
*
* @returns True if ray intersects with the collider, false otherwise. If the transform doesn't have a collider the method returns false.
*/
SQR.Intersection.rayTest = function(r, t) {
if (!t.collider) {
console.log("Intersection test failed. " + t.name + " has no collider.");
return false;
}
if (!SQR.Intersection.__tv1) {
SQR.Intersection.__tv1 = new SQR.V3();
SQR.Intersection.__tv2 = new SQR.V3();
SQR.Intersection.__tv3 = new SQR.V3();
SQR.Intersection.__tv4 = new SQR.V3();
SQR.Intersection.__tv5 = new SQR.V3();
SQR.Intersection.__tv6 = new SQR.V3();
SQR.Intersection.__tv7 = new SQR.V3();
}
switch (t.collider.type) {
case SQR.Collider.SPHERE:
return SQR.Intersection.raySphere(r, t);
case SQR.Collider.BOX:
return SQR.Intersection.rayBox(r, t);
case SQR.Collider.MESH:
return SQR.Intersection.rayMesh(r, t);
default:
console.log(t.collider);
throw "> SQR.Intersection > unknown collider type on " + t.name;
return false;
}
}
/**
* Performs an intersection test of a ray against a transform that has a mesh collider.
*
* WARNING. For meshes that have lots of polys, the ray/mesh intersection test can be very slow! It's better to wrap it into a box or sphere collider or have a simpler version of the mesh to make intersection tests against.
*
* @param {J3D.Ray} r an instance of J3D.Ray
* @param {J3D.Transform} t a transform to test against.
*
* @returns True if ray intersects with the collider, false otherwise. If the transform doesn't have a geometry the method returns false.
*/
SQR.Intersection.rayMesh = function(r, t) {
if (t.collider.box) {
if (!SQR.Intersection.rayBox(r, t)) {
return false;
}
} else {
r.makeLocal(t);
}
var v = t.buffer.getDataArray();
var d = t.buffer.getIndexArray();
var op = t.buffer.attributes['aPosition'].offset;
var c = false;
var p0 = SQR.Intersection.__tv1;
var p1 = SQR.Intersection.__tv2;
var p2 = SQR.Intersection.__tv3;
for (var i = 0; i < d.length; i += 3) {
var i0 = d[i + 0] * t.buffer.strideSize + op;
var i1 = d[i + 1] * t.buffer.strideSize + op;
var i2 = d[i + 2] * t.buffer.strideSize + op;
p0.set(v[ i0 ], v[ i0 + 1 ], v[ i0 + 2 ]);
p1.set(v[ i1 ], v[ i1 + 1 ], v[ i1 + 2 ]);
p2.set(v[ i2 ], v[ i2 + 1 ], v[ i2 + 2 ]);
c = c || SQR.Intersection.rayTriangle(r, p0, p1, p2);
if (c) break;
}
return c;
}
/**
* Performs an intersection test of a ray against a single triangle.
* You should not have to call this method directly, unless you have specific needs, like ex. doing intersection tests for particles.
*/
SQR.Intersection.rayTriangle = function(r, p0, p1, p2, n) {
var p = SQR.Intersection.__tv4;
var n = SQR.Intersection.__tv5;
var t1 = SQR.Intersection.__tv6;
var t2 = SQR.Intersection.__tv7;
t1.sub(p0, p1);
t2.sub(p2, p0);
n.cross(t1, t2).norm();
var dot = SQR.V3.dot(n, r.localDirection);
if (!( dot < 0 )) {
return false;
}
var d = SQR.V3.dot(n, p0);
var t = d - SQR.V3.dot(n, r.localOrigin);
if (!( t <= 0 )) {
return false;
}
t = t / dot;
p.copyFrom(r.localDirection);
p = p.norm().mul(t);
p.add(r.localOrigin);
var u0, u1, u2, v0, v1, v2;
if (Math.abs(n.x) > Math.abs(n.y)) {
if (Math.abs(n.x) > Math.abs(n.z)) {
u0 = p.y - p0.y;
u1 = p1.y - p0.y;
u2 = p2.y - p0.y;
v0 = p.z - p0.z;
v1 = p1.z - p0.z;
v2 = p2.z - p0.z;
} else {
u0 = p.x - p0.x;
u1 = p1.x - p0.x;
u2 = p2.x - p0.x;
v0 = p.y - p0.y;
v1 = p1.y - p0.y;
v2 = p2.y - p0.y;
}
} else {
if (Math.abs(n.y) > Math.abs(n.z)) {
u0 = p.x - p0.x;
u1 = p1.x - p0.x;
u2 = p2.x - p0.x;
v0 = p.z - p0.z;
v1 = p1.z - p0.z;
v2 = p2.z - p0.z;
} else {
u0 = p.x - p0.x;
u1 = p1.x - p0.x;
u2 = p2.x - p0.x;
v0 = p.y - p0.y;
v1 = p1.y - p0.y;
v2 = p2.y - p0.y;
}
}
var temp = u1 * v2 - v1 * u2;
if (!(temp != 0)) {
return false;
}
temp = 1 / temp;
var alpha = ( u0 * v2 - v0 * u2 ) * temp;
if (!(alpha >= 0)) {
return false;
}
var beta = ( u1 * v0 - v1 * u0 ) * temp;
if (!(beta >= 0)) {
return false;
}
var gamma = 1 - alpha - beta;
if (!(gamma >= 0)) {
return false;
}
return t;
}
/**
* Performs an intersection test of a ray against a transform that has a sphere collider.
*
* @param {J3D.Ray} r an instance of J3D.Ray
* @param {J3D.Transform} t a transform to test against.
*
* @returns True if ray intersects with the collider, false otherwise. If the transform doesn't have a sphere collider the method returns false.
*/
SQR.Intersection.raySphere = function(r, t) {
var e = SQR.Intersection.__tv1;
var radius = t.collider.radius;
var radiusSq = radius * radius;
r.makeLocal(t);
e.sub(t.collider.center, r.localOrigin);
if (e.lengthSq < radiusSq) return false;
var a = SQR.V3.dot(e, r.localDirection);
if (a <= 0) return false;
var t = radiusSq - ( e.magsq() - a * a );
if (t >= 0) return Math.abs(a) - Math.sqrt(t);
return false;
};
/**
* Performs an intersection test of a ray against a transform that has a box collider.
*
* @param {J3D.Ray} r an instance of J3D.Ray
* @param {J3D.Transform} t a transform to test against.
*
* @returns True if ray intersects with the collider, false otherwise. If the transform doesn't have a box collider the method returns false.
*/
SQR.Intersection.rayBox = function(r, t) {
var b = t.collider.box;
r.makeLocal(t);
var xt = 0, yt = 0, zt = 0;
var xn = 0, yn = 0, zn = 0;
var ins = true;
if (r.localOrigin.x < b.minX) {
xt = b.minX - r.localOrigin.x;
//if(xt > r.localDirection.x) return return Number.MAX_VALUE;
xt /= r.localDirection.x;
ins = false;
xn = -1;
} else if (r.localOrigin.x > b.maxX) {
xt = b.maxX - r.localOrigin.x;
//if(xt < r.localDirection.x) return return Number.MAX_VALUE;
xt /= r.localDirection.x;
ins = false;
xn = 1;
}
if (r.localOrigin.y < b.minY) {
yt = b.minY - r.localOrigin.y;
//if(yt > r.localDirection.y) return return Number.MAX_VALUE;
yt /= r.localDirection.y;
ins = false;
yn = -1;
} else if (r.localOrigin.y > b.maxY) {
yt = b.maxY - r.localOrigin.y;
//if(yt < r.localDirection.y) return return Number.MAX_VALUE;
yt /= r.localDirection.y;
ins = false;
yn = 1;
}
if (r.localOrigin.z < b.minZ) {
zt = b.minZ - r.localOrigin.z;
//if(zt > r.direction.z) return return Number.MAX_VALUE;
zt /= r.localDirection.z;
ins = false;
zn = -1;
} else if (r.localOrigin.z > b.maxZ) {
zt = b.maxZ - r.localOrigin.z;
//if(zt < r.direction.z) return return Number.MAX_VALUE;
zt /= r.localDirection.z;
ins = false;
zn = 1;
}
if (ins) return -1;
var which = 0;
var td = xt;
if (yt > td) {
which = 1;
td = yt;
}
if (zt > td) {
which = 2;
td = zt;
}
switch (which) {
case 0:
var y = r.localOrigin.y + r.localDirection.y * td;
if (y < b.minY || y > b.maxY) return false;
var z = r.localOrigin.z + r.localDirection.z * td;
if (z < b.minZ || z > b.maxZ) return false;
//ab.normal = v3(xn, 0, 0);
break;
case 1:
var x = r.localOrigin.x + r.localDirection.x * td;
if (x < b.minX || x > b.maxX) return false;
var z = r.localOrigin.z + r.localDirection.z * td;
if (z < b.minZ || z > b.maxZ) return false;
//ab.normal = v3(0, yn, 0);
break;
case 2:
var x = r.localOrigin.x + r.localDirection.x * td;
if (x < b.minX || x > b.maxX) return false;
var y = r.localOrigin.y + r.localDirection.y * td;
if (y < b.minY || y > b.maxY) return false;
//ab.normal = new v3(0, 0, zn);
break;
}
return td;
}