-
Notifications
You must be signed in to change notification settings - Fork 250
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ported Tomas Akenine-Moller's triangle-AABB implementation to javascript #115
Changes from 1 commit
9d61bea
9e4fd04
1f61442
c3da1de
45235dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
'use strict'; | ||
|
||
/** | ||
* Function and helpers for computing the intersection between a static triangle and a static AABB. | ||
* Adapted from Tomas Akenine-Möller's public domain implementation: http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/ | ||
*/ | ||
|
||
var Cesium = require('cesium'); | ||
var Cartesian3 = Cesium.Cartesian3; | ||
|
||
module.exports = triBoxOverlap; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't use abbreviations in public APIs so make this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually |
||
|
||
var vMin = new Cartesian3(); | ||
var vMax = new Cartesian3(); | ||
// Perform a fast plane/box overlap test using the separating axis theorem. | ||
// Basically, this checks the plane normal against the two vertices whose normals which are most closely aligned with it. | ||
function planeBoxOverlap(normal, vertex, maxBox) { | ||
// Pick the most closely aligned vertices, vMin and vMax | ||
var v = vertex.x; | ||
var sign = (normal.x > 0.0) ? -1.0 : 1.0; | ||
vMin.x = sign * maxBox.x - v; | ||
vMax.x = (-sign) * maxBox.x - v; | ||
|
||
v = vertex.y; | ||
sign = (normal.y > 0.0) ? -1.0 : 1.0; | ||
vMin.y = sign * maxBox.y - v; | ||
vMax.y = (-sign) * maxBox.y - v; | ||
|
||
v = vertex.z; | ||
sign = (normal.z > 0.0) ? -1.0 : 1.0; | ||
vMin.z = sign * maxBox.z - v; | ||
vMax.z = (-sign) * maxBox.z - v; | ||
|
||
// Project each vertex as a vector from the origin onto the plane normal | ||
if (Cartesian3.dot(normal, vMin) > 0.0) { | ||
return false; | ||
} | ||
else if (Cartesian3.dot(normal, vMax) >= 0.0) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
////// X-tests ////// | ||
|
||
function axisTestX01(a, b, fa, fb, boxHalfSize, v0, v2) { | ||
var p0 = a * v0.y - b * v0.z; | ||
var p2 = a * v2.y - b * v2.z; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whitespace. |
||
var min = p2; | ||
var max = p0; | ||
if (p0 < p2) { | ||
min = p0; | ||
max = p2; | ||
} | ||
var rad = fa * boxHalfSize.y + fb * boxHalfSize.z; | ||
return (min > rad || max < -rad); | ||
} | ||
|
||
function axisTestX2(a, b, fa, fb, boxHalfSize, v0, v1) { | ||
var p0 = a * v0.y - b * v0.z; | ||
var p1 = a * v1.y - b * v1.z; | ||
var min = p1; | ||
var max = p0; | ||
if (p0 < p1) { | ||
min = p0; | ||
max = p1; | ||
} | ||
var rad = fa * boxHalfSize.y + fb * boxHalfSize.z; | ||
return (min > rad || max < -rad); | ||
} | ||
|
||
////// Y-tests ////// | ||
|
||
function axisTestY02(a, b, fa, fb, boxHalfSize, v0, v2) { | ||
var p0 = -a * v0.x + b * v0.z; | ||
var p2 = -a * v2.x + b * v2.z; | ||
var min = p2; | ||
var max = p0; | ||
if (p0 < p2) { | ||
min = p0; | ||
max = p2; | ||
} | ||
var rad = fa * boxHalfSize.x + fb * boxHalfSize.z; | ||
return (min > rad || max < -rad); | ||
} | ||
|
||
function axisTestY1(a, b, fa, fb, boxHalfSize, v0, v1) { | ||
var p0 = -a * v0.x + b * v0.z; | ||
var p1 = -a * v1.x + b * v1.z; | ||
var min = p1; | ||
var max = p0; | ||
if (p0 < p1) { | ||
min = p0; | ||
max = p1; | ||
} | ||
var rad = fa * boxHalfSize.x + fb * boxHalfSize.z; | ||
return (min > rad || max < -rad); | ||
} | ||
|
||
////// Z-tests ////// | ||
|
||
function axisTestZ12(a, b, fa, fb, boxHalfSize, v1, v2) { | ||
var p1 = a * v1.x - b * v1.y; | ||
var p2 = a * v2.x - b * v2.y; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same comment. |
||
var min = p1; | ||
var max = p2; | ||
if (p2 < p1) { | ||
min = p2; | ||
max = p1; | ||
} | ||
var rad = fa * boxHalfSize.x + fb * boxHalfSize.y; | ||
return (min > rad || max < -rad); | ||
} | ||
|
||
function axisTestZ0(a, b, fa, fb, boxHalfSize, v0, v1) { | ||
var p0 = a * v0.x - b * v0.y; | ||
var p1 = a * v1.x - b * v1.y; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same. |
||
var min = p1; | ||
var max = p0; | ||
if (p0 < p1) { | ||
min = p0; | ||
max = p1; | ||
} | ||
var rad = fa * boxHalfSize.x + fb * boxHalfSize.y; | ||
return (min > rad || max < -rad); | ||
} | ||
|
||
var triangleNormal = new Cartesian3(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here and below, use |
||
|
||
var edges = []; | ||
edges.push(new Cartesian3()); | ||
edges.push(new Cartesian3()); | ||
edges.push(new Cartesian3()); | ||
|
||
var v0 = new Cartesian3(); | ||
var v1 = new Cartesian3(); | ||
var v2 = new Cartesian3(); | ||
|
||
function triBoxOverlap(boxCenter, boxHalfSize, triangle) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put this and the scratch variables at the top of the file and the helper function below. This will read better. It relies on "hoisting" the helper functions, but that is OK. Never rely on hoisting variables though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make this efficiently use Cesium's AxisAlignedBoundingBox? |
||
// Use the separating axis theorem to test overlap between triangle and box. | ||
// Need to test for overlap in these directions: | ||
// 1) the {x,y,z} directions (testing the AABB of the triangle against the box handles these) | ||
// 2) normal of the triangle | ||
// 3) cross product of edge from triangle with {x, y, z} direction. this is 3x3 = 9 tests. | ||
|
||
// Translate the triangle and box so the box is centered at the origin. | ||
Cartesian3.subtract(triangle[0], boxCenter, v0); | ||
Cartesian3.subtract(triangle[1], boxCenter, v1); | ||
Cartesian3.subtract(triangle[2], boxCenter, v2); | ||
|
||
// Compute triangle edges | ||
Cartesian3.subtract(v1, v0, edges[0]); | ||
Cartesian3.subtract(v2, v1, edges[1]); | ||
Cartesian3.subtract(v0, v2, edges[2]); | ||
|
||
// Bullet 3: Perform edge checks first for early return. Check if each is a separating axis. | ||
var fex = Math.abs(edges[0].x); | ||
var fey = Math.abs(edges[0].y); | ||
var fez = Math.abs(edges[0].z); | ||
if (axisTestX01(edges[0].z, edges[0].y, fez, fey, boxHalfSize, v0, v2)) { | ||
return false; | ||
} else if (axisTestY02(edges[0].z, edges[0].x, fez, fex, boxHalfSize, v0, v2)) { | ||
return false; | ||
} else if (axisTestZ12(edges[0].y, edges[0].x, fey, fex, boxHalfSize, v1, v2)) { | ||
return false; | ||
} | ||
|
||
fex = Math.abs(edges[1].x); | ||
fey = Math.abs(edges[1].y); | ||
fez = Math.abs(edges[1].z); | ||
if (axisTestX01(edges[1].z, edges[1].y, fez, fey, boxHalfSize, v0, v2)) { | ||
return false; | ||
} else if (axisTestY02(edges[1].z, edges[1].x, fez, fex, boxHalfSize, v0, v2)) { | ||
return false; | ||
} else if (axisTestZ0(edges[1].y, edges[1].x, fey, fex, boxHalfSize, v0, v1)) { | ||
return false; | ||
} | ||
|
||
fex = Math.abs(edges[2].x); | ||
fey = Math.abs(edges[2].y); | ||
fez = Math.abs(edges[2].z); | ||
if (axisTestX2(edges[2].z, edges[2].y, fez, fey, boxHalfSize, v0, v1)) { | ||
return false; | ||
} else if (axisTestY1(edges[2].z, edges[2].x, fez, fex, boxHalfSize, v0, v1)) { | ||
return false; | ||
} else if (axisTestZ12(edges[2].y, edges[2].x, fey, fex, boxHalfSize, v1, v2)) { | ||
return false; | ||
} | ||
|
||
// Bullet 1: Perform an AABB test between the triangle's minimum AABB and the box | ||
var triangleAABBmin = Math.min(v0.x, v1.x, v2.x); | ||
var triangleAABBmax = Math.max(v0.x, v1.x, v2.x); | ||
if (triangleAABBmin > boxHalfSize.x || triangleAABBmax < -boxHalfSize.x) { | ||
return false; | ||
} | ||
|
||
triangleAABBmin = Math.min(v0.y, v1.y, v2.y); | ||
triangleAABBmax = Math.max(v0.y, v1.y, v2.y); | ||
if (triangleAABBmin > boxHalfSize.y || triangleAABBmax < -boxHalfSize.y) { | ||
return false; | ||
} | ||
|
||
triangleAABBmin = Math.min(v0.z, v1.z, v2.z); | ||
triangleAABBmax = Math.max(v0.z, v1.z, v2.z); | ||
if (triangleAABBmin > boxHalfSize.z || triangleAABBmax < -boxHalfSize.z) { | ||
return false; | ||
} | ||
|
||
// Bullet 2: Test if the box intersects the plane of the triangle. | ||
triangleNormal = Cartesian3.cross(edges[0], edges[1], triangleNormal); | ||
return planeBoxOverlap(triangleNormal, v0, boxHalfSize); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
'use strict'; | ||
var Cesium = require('cesium'); | ||
var Cartesian3 = Cesium.Cartesian3; | ||
var triBoxOverlap = require('../../lib/triBoxOverlap'); | ||
|
||
describe('triBoxOverlap', function() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these my original tests? How is test coverage? It is important that we test all the case and combinations since we are going to build on this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests are new, and they don't cover every possible return path although they cover all the helper functions. Do you have comprehensive tests? Otherwise I could write them, but figuring out the geometry for each case could take some time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure that code coverage is 100% even if the cyclomatic complexity is not. Also test representative, but perhaps not exhaustive cases, e.g.,
We want to be very confident in this code so when we build on it, we don't have to chase down bugs in a more complex system. |
||
var triangle = [ | ||
new Cartesian3(0.0, 0.0, 0.0), | ||
new Cartesian3(1.0, 0.0, 0.0), | ||
new Cartesian3(1.0, 1.0, 0.0) | ||
]; | ||
|
||
var boxCenter = new Cartesian3(); | ||
var halfSize = new Cartesian3(); | ||
|
||
it('correctly detects no intersection for a completely separate triangle and box', function() { | ||
boxCenter.x = 2.0; | ||
boxCenter.y = 2.0; | ||
boxCenter.z = 2.0; | ||
halfSize.x = 0.5; | ||
halfSize.y = 0.5; | ||
halfSize.z = 0.5; | ||
|
||
expect(triBoxOverlap(boxCenter, halfSize, triangle)).toEqual(false); | ||
}); | ||
|
||
it('correctly detects an intersection when the triangle is entirely inside the box', function() { | ||
boxCenter.x = 0.5; | ||
boxCenter.y = 0.5; | ||
boxCenter.z = 0.0; | ||
halfSize.x = 1.0; | ||
halfSize.y = 1.0; | ||
halfSize.z = 1.0; | ||
|
||
expect(triBoxOverlap(boxCenter, halfSize, triangle)).toEqual(true); | ||
}); | ||
|
||
it('correctly detects an intersection when the box is entirely in the triangle plane', function() { | ||
boxCenter.x = 0.5; | ||
boxCenter.y = 0.5; | ||
boxCenter.z = 0.0; | ||
halfSize.x = 0.1; | ||
halfSize.y = 0.1; | ||
halfSize.z = 0.1; | ||
|
||
expect(triBoxOverlap(boxCenter, halfSize, triangle)).toEqual(true); | ||
}); | ||
|
||
it('correctly detects an intersection when a triangle vertex is in the box', function() { | ||
boxCenter.x = 1.0; | ||
boxCenter.y = 1.0; | ||
boxCenter.z = 0.0; | ||
halfSize.x = 0.5; | ||
halfSize.y = 0.5; | ||
halfSize.z = 0.5; | ||
|
||
expect(triBoxOverlap(boxCenter, halfSize, triangle)).toEqual(true); | ||
}); | ||
|
||
it('correctly detects an intersection when the intersection is on an edge of the triangle', function() { | ||
boxCenter.x = 0.0; | ||
boxCenter.y = 1.0; | ||
boxCenter.z = 0.0; | ||
halfSize.x = 1.1; | ||
halfSize.y = 1.1; | ||
halfSize.z = 1.1; | ||
|
||
expect(triBoxOverlap(boxCenter, halfSize, triangle)).toEqual(true); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still add this to LICENSE.md as public domain so all third-party code is referenced from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like the third party code section is pretty out of date, should I open an issue for that or just update it here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updating it as part of this PR would be fine and appreciated.