Skip to content

Commit

Permalink
Added intersection and proximity methods to Triangle class. Fixed exp…
Browse files Browse the repository at this point in the history
…ort typo.
  • Loading branch information
kbirk committed Jan 25, 2016
1 parent 807e26a commit d535eea
Show file tree
Hide file tree
Showing 9 changed files with 377 additions and 100 deletions.
4 changes: 3 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
"require" : false,
"module" : false,
"describe" : false,
"it" : false
"it" : false,
"beforeEach": false,
"afterEach" : false
}
}
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alfador",
"version": "0.4.8",
"version": "0.4.9",
"homepage": "https://github.com/kbirk/alfador",
"description": "A fast 3D math library for JavaScript",
"author": "Kevin Birk <birk.kevin@gmail.com>",
Expand Down
151 changes: 63 additions & 88 deletions build/alfador.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/alfador.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alfador",
"version": "0.4.8",
"version": "0.4.9",
"description": "A fast 3D math library for JavaScript",
"author": "Kevin Birk <birk.kevin@gmail.com>",
"main": "./src/exports.js",
Expand Down
3 changes: 1 addition & 2 deletions src/Mat33.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

"use strict";

var Vec3 = require( './Vec3' ),
Vec4 = require( './Vec4' );
var Vec3 = require( './Vec3' );

/**
* Instantiates a Mat33 object.
Expand Down
127 changes: 127 additions & 0 deletions src/Triangle.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@

var Vec3 = require('./Vec3');

function closestPointOnEdge( a, b, point ) {
var ab = b.sub( a );
// project c onto ab, computing parameterized position d(t) = a + t*(b * a)
var t = new Vec3( point ).sub( a ).dot( ab ) / ab.dot( ab );
// If outside segment, clamp t (and therefore d) to the closest endpoint
if ( t < 0 ) {
t = 0;
}
if ( t > 1 ) {
t = 1;
}
// compute projected position from the clamped t
return a.add( ab.mult( t ) );
}

/**
* Instantiates a Triangle object.
* @class Triangle
Expand Down Expand Up @@ -71,6 +86,118 @@
return ab.cross( ac ).normalize();
};

/**
* Returns the area of the triangle.
* @memberof Triangle
*
* @returns {number} The area of the triangle.
*/
Triangle.prototype.area = function() {
var ab = this.b.sub( this.a ),
ac = this.c.sub( this.a );
return ab.cross( ac ).length() / 2.0;
};

/**
* Returns true if the point is inside the triangle. The point must be
* coplanar.
* @memberof Triangle
*
* @param {Vec3|Array} point - The point to test.
*
* @returns {boolean} Whether or not the point is inside the triangle.
*/
Triangle.prototype.isInside = function( point ) {
var p = new Vec3( point );
// Translate point and triangle so that point lies at origin
var a = this.a.sub( p );
var b = this.b.sub( p );
var c = this.c.sub( p );
// Compute normal vectors for triangles pab and pbc
var u = b.cross( c );
var v = c.cross( a );
// Make sure they are both pointing in the same direction
if (u.dot( v ) < 0.0 ) {
return false;
}
// Compute normal vector for triangle pca
var w = a.cross( b );
// Make sure it points in the same direction as the first two
if ( u.dot( w ) < 0.0 ) {
return false;
}
// Otherwise P must be in (or on) the triangle
return true;
};

/**
* Intersect the triangle and return intersection information.
* @memberof Triangle
*
* @param {Vec3|Array} origin - The origin of the intersection ray
* @param {Vec3|Array} direction - The direction of the intersection ray.
* @param {boolean} ignoreBehind - Whether or not to ignore intersections behind the origin of the ray.
* @param {boolean} ignoreBackface - Whether or not to ignore the backface of the triangle.
*
* @returns {Object|boolean} The intersection information, or false if there is no intersection.
*/
Triangle.prototype.intersect = function( origin, direction, ignoreBehind, ignoreBackface ) {
// Compute ray/plane intersection
var o = new Vec3( origin );
var d = new Vec3( direction );
var normal = this.normal();
var dn = d.dot( normal );
if ( dn === 0 ) {
// ray is parallel to plane
// TODO: check if ray is co-linear and intersects?
return false;
}
var t = this.a.sub( o ).dot( normal ) / dn;
if ( ignoreBehind && ( t < 0 ) ) {
// plane is behind ray
return false;
}
if ( ignoreBackface ) {
// ignore triangles facing away from ray
if ( ( t > 0 && dn > 0 ) || ( t < 0 && dn < 0 ) ) {
// triangle is facing opposite the direction of intersection
return false;
}
}
var position = o.add( d.mult( t ) );
// check if point is inside the triangle
if ( !this.isInside( position ) ) {
return false;
}
return {
position: position,
normal: normal,
t: t
};
};

/**
* Returns the closest point on the triangle to the specified point.
* @memberof Triangle
*
* @param {Vec3|Array} point - The point to test.
*
* @returns {Vec3} The closest point on the edge.
*/
Triangle.prototype.closestPoint = function( point ) {
var e0 = closestPointOnEdge( this.a, this.b, point );
var e1 = closestPointOnEdge( this.b, this.c, point );
var e2 = closestPointOnEdge( this.c, this.a, point );
var d0 = ( e0.sub( point ) ).lengthSquared();
var d1 = ( e1.sub( point ) ).lengthSquared();
var d2 = ( e2.sub( point ) ).lengthSquared();
if ( d0 < d1 ) {
return ( d0 < d2 ) ? e0 : e2;
} else {
return ( d1 < d2 ) ? e1 : e2;
}
};

/**
* Returns a random Triangle of unit length.
* @memberof Triangle
Expand Down
2 changes: 1 addition & 1 deletion src/exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
Mat44: require('./Mat44'),
Vec2: require('./Vec2'),
Vec3: require('./Vec3'),
Vec4: require('./Vec3'),
Vec4: require('./Vec4'),
Quaternion: require('./Quaternion'),
Transform: require('./Transform'),
Triangle: require('./Triangle')
Expand Down
184 changes: 179 additions & 5 deletions test/TriangleTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,36 @@ var EPSILON = 0.00001,
Vec3 = require( '../src/Vec3' ),
Triangle = require( '../src/Triangle' );

var _log, _warn, _error;

function muteConsole() {
_log = console.log;
_warn = console.warn;
_error = console.error;
console.log = function() {};
console.warn = function() {};
console.error = function() {};
}

function unmuteConsole() {
console.log = _log;
console.warn = _warn;
console.error = _error;
_log = null;
_warn= null;
_error = null;
}

describe('Triangle', function() {

beforeEach( function() {
muteConsole();
});

afterEach( function() {
unmuteConsole();
});

describe('#equals()', function() {
it('should return false if any components do not match', function() {
var a = new Vec3.random(),
Expand Down Expand Up @@ -96,7 +124,157 @@ describe('Triangle', function() {
normal = ab.cross( ac ).normalize();
assert( normal.equals( t.normal() ) );
});
it('should cache the normal if the triangle positions are unchanged', function() {
});

describe('#area()', function() {
it('should return the area of the triangle', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
assert( t.area() === 2.0 );
});
});

describe('#isInside()', function() {
it('should return bool if point is contained in the triangle', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var p0 = [ 1.0, 0.8, 0.0 ];
var p1 = [ 3.0, 0.0, 0.0 ];
var p2 = [ 3.0, 3.0, 0.0 ];
assert( t.isInside( p0 ) );
assert( !t.isInside( p1 ) );
assert( !t.isInside( p2 ) );
});
it('should only return true if the point is co-planar with the triangle', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var p = [ 1.0, 0.8, 10.0 ];
assert( !t.isInside( p ) );
});
it('should return true if p === a', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
assert( t.isInside([ 0, 0, 0 ]) );
})
;it('should return true if p === b', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
assert( t.isInside([ 2, 0, 0 ]) );
});
it('should return true if p === c', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
assert( t.isInside([ 2, 2, 0 ]) );
});
});

describe('#intersect()', function() {
it('should return false if no intersection takes place', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var o = [ 0, 0, 10 ];
var d = [ 3, 3, -1 ];
assert( t.intersect( o, d, true, true ) === false );
});
it('should return an intersection object if one takes place', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var o = [ 1, 0.5, 10 ];
var d = [ 0, 0, -1 ];
assert( t.intersect( o, d, true, true ) !== false );
});
it('should provide interface for ignoring behind the point of origin', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var o = [ 1, 0.5, 10 ];
var d = [ 0, 0, 1 ];
// ignore
assert( t.intersect( o, d, true, true ) === false );
assert( t.intersect( o, d, true, false ) === false );
// don't ignore
assert( t.intersect( o, d, false, true ) !== false );
assert( t.intersect( o, d, false, false ) !== false );
});
it('should provide interface for ignoring triangles facing opposite the direction of intersection', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var o = [ 1, 0.5, -10 ];
var d = [ 0, 0, 1 ];
// ignore
assert( t.intersect( o, d, true, true ) === false );
assert( t.intersect( o, d, false, true ) === false );
// don't ignore
assert( t.intersect( o, d, true, false ) !== false );
assert( t.intersect( o, d, false, false ) !== false );
});
it('should always return false to directions perpendicular to the normal', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
var o = [ 0, 0, 0 ];
var d = [ 3, 3, 0 ];
assert( t.intersect( o, d, true, true ) === false );
assert( t.intersect( o, d, true, false ) === false );
assert( t.intersect( o, d, false, true ) === false );
assert( t.intersect( o, d, false, false ) === false );
});
});

describe('#closestPoint()', function() {
it('should return the closest point on the triangle to the provided', function() {
var t = new Triangle(
[ 0, 0, 0 ],
[ 2, 0, 0 ],
[ 2, 2, 0 ]
);
assert( t.closestPoint([ 0, 0, 0 ]).equals([ 0, 0, 0 ]) );
assert( t.closestPoint([ 3, 0, 0 ]).equals([ 2, 0, 0 ]) );
assert( t.closestPoint([ 3, 3, 0 ]).equals([ 2, 2, 0 ]) );
t = Triangle.random();
var ab = t.b.sub( t.a );
var bc = t.c.sub( t.b );
var ca = t.a.sub( t.c );
var abm = t.a.add( ab.normalize().mult( ab.length() / 2 ) );
var bcm = t.b.add( bc.normalize().mult( bc.length() / 2 ) );
var cam = t.c.add( ca.normalize().mult( ca.length() / 2 ) );
var abp = ab.cross( t.normal() ).normalize();
var bcp = bc.cross( t.normal() ).normalize();
var cap = ca.cross( t.normal() ).normalize();
assert( t.closestPoint( abm.add( abp ) ).equals( abm, EPSILON ) );
assert( t.closestPoint( bcm.add( bcp ) ).equals( bcm, EPSILON ) );
assert( t.closestPoint( cam.add( cap ) ).equals( cam, EPSILON ) );
});
});

Expand All @@ -113,8 +291,6 @@ describe('Triangle', function() {
t = new Triangle([ a, b, c ]);
assert( Math.abs( radius - t.radius() ) < EPSILON );
});
it('should cache the radius if the triangle positions are unchanged', function() {
});
});

describe('#centroid()', function() {
Expand All @@ -126,8 +302,6 @@ describe('Triangle', function() {
centroid = a.add( b ).add( c ).div( 3 );
assert( centroid.equals( t.centroid() ) );
});
it('should cache the centroid if the triangle positions are unchanged', function() {
});
});

describe('#toString', function() {
Expand Down

0 comments on commit d535eea

Please sign in to comment.