- /**
- * Provides the classes and algorithms for running GJK+EPA based collision detection
- *
- * @class GjkEpa2
- * @static
- */
- Goblin.GjkEpa2 = {
- max_iterations: 20,
- epa_condition: 0.001,
-
- /**
- * Holds a point on the edge of a Minkowski difference along with that point's witnesses and the direction used to find the point
- *
- * @class SupportPoint
- * @param witness_a {vec3} Point in first object used to find the supporting point
- * @param witness_b {vec3} Point in the second object ued to find th supporting point
- * @param point {vec3} The support point on the edge of the Minkowski difference
- * @constructor
- */
- SupportPoint: function( witness_a, witness_b, point ) {
- this.witness_a = witness_a;
- this.witness_b = witness_b;
- this.point = point;
- },
-
- /**
- * Finds the extant point on the edge of the Minkowski difference for `object_a` - `object_b` in `direction`
- *
- * @method findSupportPoint
- * @param object_a {Goblin.RigidBody} First object in the search
- * @param object_b {Goblin.RigidBody} Second object in the search
- * @param direction {vec3} Direction to find the extant point in
- * @param gjk_point {Goblin.GjkEpa.SupportPoint} `SupportPoint` class to store the resulting point & witnesses in
- */
- findSupportPoint: (function(){
- var temp = new Goblin.Vector3();
- return function( object_a, object_b, direction, support_point ) {
- // Find witnesses from the objects
- object_a.findSupportPoint( direction, support_point.witness_a );
- temp.scaleVector( direction, -1 );
- object_b.findSupportPoint( temp, support_point.witness_b );
-
- // Find the CSO support point
- support_point.point.subtractVectors( support_point.witness_a, support_point.witness_b );
- };
- })(),
-
- /**
- * Perform GJK algorithm against two objects. Returns a ContactDetails object if there is a collision, else null
- *
- * @method GJK
- * @param object_a {Goblin.RigidBody}
- * @param object_b {Goblin.RigidBody}
- * @return {Goblin.ContactDetails|Boolean} Returns `null` if no collision, else a `ContactDetails` object
- */
- GJK: (function(){
- return function( object_a, object_b ) {
- var simplex = new Goblin.GjkEpa2.Simplex( object_a, object_b ),
- last_point;
-
- while ( ( last_point = simplex.addPoint() ) ){}
-
- // If last_point is false then there is no collision
- if ( last_point === false ) {
- Goblin.GjkEpa2.freeSimplex( simplex );
- return null;
- }
-
- return simplex;
- };
- })(),
-
- freeSimplex: function( simplex ) {
- // Free the support points used by this simplex
- for ( var i = 0, points_length = simplex.points.length; i < points_length; i++ ) {
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', simplex.points[i] );
- }
- },
-
- freePolyhedron: function( polyhedron ) {
- // Free the support points used by the polyhedron (includes the points from the simplex used to create the polyhedron
- var pool = Goblin.ObjectPool.pools['GJK2SupportPoint'];
-
- for ( var i = 0, faces_length = polyhedron.faces.length; i < faces_length; i++ ) {
- // The indexOf checking is required because vertices are shared between faces
- if ( pool.indexOf( polyhedron.faces[i].a ) === -1 ) {
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', polyhedron.faces[i].a );
- }
- if ( pool.indexOf( polyhedron.faces[i].b ) === -1 ) {
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', polyhedron.faces[i].b );
- }
- if ( pool.indexOf( polyhedron.faces[i].c ) === -1 ) {
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', polyhedron.faces[i].c );
- }
- }
- },
-
- /**
- * Performs the Expanding Polytope Algorithm a GJK simplex
- *
- * @method EPA
- * @param simplex {Goblin.GjkEpa2.Simplex} Simplex generated by the GJK algorithm
- * @return {Goblin.ContactDetails}
- */
- EPA: (function(){
- return function( simplex ) {
- // Time to convert the simplex to real faces
- // @TODO this should be a priority queue where the position in the queue is ordered by distance from face to origin
- var polyhedron = new Goblin.GjkEpa2.Polyhedron( simplex );
-
- var i = 0;
-
- // Expand the polyhedron until it doesn't expand any more
- while ( ++i ) {
- polyhedron.findFaceClosestToOrigin();
-
- // Find a new support point in the direction of the closest point
- if ( polyhedron.closest_face_distance < Goblin.EPSILON ) {
- _tmp_vec3_1.copy( polyhedron.faces[polyhedron.closest_face].normal );
- } else {
- _tmp_vec3_1.copy( polyhedron.closest_point );
- }
-
- var support_point = Goblin.ObjectPool.getObject( 'GJK2SupportPoint' );
- Goblin.GjkEpa2.findSupportPoint( simplex.object_a, simplex.object_b, _tmp_vec3_1, support_point );
-
- // Check for terminating condition
- _tmp_vec3_1.subtractVectors( support_point.point, polyhedron.closest_point );
- var gap = _tmp_vec3_1.lengthSquared();
-
- if ( i === Goblin.GjkEpa2.max_iterations || ( gap < Goblin.GjkEpa2.epa_condition && polyhedron.closest_face_distance > Goblin.EPSILON ) ) {
-
- // Get a ContactDetails object and fill out its details
- var contact = Goblin.ObjectPool.getObject( 'ContactDetails' );
- contact.object_a = simplex.object_a;
- contact.object_b = simplex.object_b;
-
- contact.contact_normal.normalizeVector( polyhedron.closest_point );
- if ( contact.contact_normal.lengthSquared() === 0 ) {
- contact.contact_normal.subtractVectors( contact.object_b.position, contact.object_a.position );
- }
- contact.contact_normal.normalize();
-
- var barycentric = new Goblin.Vector3();
- Goblin.GeometryMethods.findBarycentricCoordinates( polyhedron.closest_point, polyhedron.faces[polyhedron.closest_face].a.point, polyhedron.faces[polyhedron.closest_face].b.point, polyhedron.faces[polyhedron.closest_face].c.point, barycentric );
-
- if ( isNaN( barycentric.x ) ) {
- // @TODO: Avoid this degenerate case
- //console.log( 'Point not in triangle' );
- Goblin.GjkEpa2.freePolyhedron( polyhedron );
- return null;
- }
-
- var confirm = {
- a: new Goblin.Vector3(),
- b: new Goblin.Vector3(),
- c: new Goblin.Vector3()
- };
-
- // Contact coordinates of object a
- confirm.a.scaleVector( polyhedron.faces[polyhedron.closest_face].a.witness_a, barycentric.x );
- confirm.b.scaleVector( polyhedron.faces[polyhedron.closest_face].b.witness_a, barycentric.y );
- confirm.c.scaleVector( polyhedron.faces[polyhedron.closest_face].c.witness_a, barycentric.z );
- contact.contact_point_in_a.addVectors( confirm.a, confirm.b );
- contact.contact_point_in_a.add( confirm.c );
-
- // Contact coordinates of object b
- confirm.a.scaleVector( polyhedron.faces[polyhedron.closest_face].a.witness_b, barycentric.x );
- confirm.b.scaleVector( polyhedron.faces[polyhedron.closest_face].b.witness_b, barycentric.y );
- confirm.c.scaleVector( polyhedron.faces[polyhedron.closest_face].c.witness_b, barycentric.z );
- contact.contact_point_in_b.addVectors( confirm.a, confirm.b );
- contact.contact_point_in_b.add( confirm.c );
-
- // Find actual contact point
- contact.contact_point.addVectors( contact.contact_point_in_a, contact.contact_point_in_b );
- contact.contact_point.scale( 0.5 );
-
- // Set objects' local points
- contact.object_a.transform_inverse.transformVector3( contact.contact_point_in_a );
- contact.object_b.transform_inverse.transformVector3( contact.contact_point_in_b );
-
- // Calculate penetration depth
- contact.penetration_depth = polyhedron.closest_point.length();
-
- contact.restitution = ( simplex.object_a.restitution + simplex.object_b.restitution ) / 2;
- contact.friction = ( simplex.object_a.friction + simplex.object_b.friction ) / 2;
-
- Goblin.GjkEpa2.freePolyhedron( polyhedron );
-
- return contact;
- }
-
- polyhedron.addVertex( support_point );
- }
-
- Goblin.GjkEpa2.freePolyhedron( polyhedron );
- return null;
- };
- })(),
-
- Face: function( polyhedron, a, b, c ) {
- this.active = true;
- //this.polyhedron = polyhedron;
- this.a = a;
- this.b = b;
- this.c = c;
- this.normal = new Goblin.Vector3();
- this.neighbors = [];
-
- _tmp_vec3_1.subtractVectors( b.point, a.point );
- _tmp_vec3_2.subtractVectors( c.point, a.point );
- this.normal.crossVectors( _tmp_vec3_1, _tmp_vec3_2 );
- this.normal.normalize();
- }
- };
-
- Goblin.GjkEpa2.Polyhedron = function( simplex ) {
- this.closest_face = null;
- this.closest_face_distance = null;
- this.closest_point = new Goblin.Vector3();
-
- this.faces = [
- //BCD, ACB, CAD, DAB
- new Goblin.GjkEpa2.Face( this, simplex.points[2], simplex.points[1], simplex.points[0] ),
- new Goblin.GjkEpa2.Face( this, simplex.points[3], simplex.points[1], simplex.points[2] ),
- new Goblin.GjkEpa2.Face( this, simplex.points[1], simplex.points[3], simplex.points[0] ),
- new Goblin.GjkEpa2.Face( this, simplex.points[0], simplex.points[3], simplex.points[2] )
- ];
-
- this.faces[0].neighbors.push( this.faces[1], this.faces[2], this.faces[3] );
- this.faces[1].neighbors.push( this.faces[2], this.faces[0], this.faces[3] );
- this.faces[2].neighbors.push( this.faces[1], this.faces[3], this.faces[0] );
- this.faces[3].neighbors.push( this.faces[2], this.faces[1], this.faces[0] );
- };
- Goblin.GjkEpa2.Polyhedron.prototype = {
- addVertex: function( vertex )
- {
- var edges = [], faces = [], i, j, a, b, last_b;
- this.faces[this.closest_face].silhouette( vertex, edges );
-
- // Re-order the edges if needed
- for ( i = 0; i < edges.length - 5; i += 5 ) {
- a = edges[i+3];
- b = edges[i+4];
-
- // Ensure this edge really should be the next one
- if ( i !== 0 && last_b !== a ) {
- // It shouldn't
- for ( j = i + 5; j < edges.length; j += 5 ) {
- if ( edges[j+3] === last_b ) {
- // Found it
- var tmp = edges.slice( i, i + 5 );
- edges[i] = edges[j];
- edges[i+1] = edges[j+1];
- edges[i+2] = edges[j+2];
- edges[i+3] = edges[j+3];
- edges[i+4] = edges[j+4];
- edges[j] = tmp[0];
- edges[j+1] = tmp[1];
- edges[j+2] = tmp[2];
- edges[j+3] = tmp[3];
- edges[j+4] = tmp[4];
-
- a = edges[i+3];
- b = edges[i+4];
- break;
- }
- }
- }
- last_b = b;
- }
-
- for ( i = 0; i < edges.length; i += 5 ) {
- var neighbor = edges[i];
- a = edges[i+3];
- b = edges[i+4];
-
- var face = new Goblin.GjkEpa2.Face( this, b, vertex, a );
- face.neighbors[2] = edges[i];
- faces.push( face );
-
- neighbor.neighbors[neighbor.neighbors.indexOf( edges[i+2] )] = face;
- }
-
- for ( i = 0; i < faces.length; i++ ) {
- faces[i].neighbors[0] = faces[ i + 1 === faces.length ? 0 : i + 1 ];
- faces[i].neighbors[1] = faces[ i - 1 < 0 ? faces.length - 1 : i - 1 ];
- }
-
- Array.prototype.push.apply( this.faces, faces );
-
- return edges;
- },
-
- findFaceClosestToOrigin: (function(){
- var origin = new Goblin.Vector3(),
- point = new Goblin.Vector3();
-
- return function() {
- this.closest_face_distance = Infinity;
-
- var distance, i;
-
- for ( i = 0; i < this.faces.length; i++ ) {
- if ( this.faces[i].active === false ) {
- continue;
- }
-
- Goblin.GeometryMethods.findClosestPointInTriangle( origin, this.faces[i].a.point, this.faces[i].b.point, this.faces[i].c.point, point );
- distance = point.lengthSquared();
- if ( distance < this.closest_face_distance ) {
- this.closest_face_distance = distance;
- this.closest_face = i;
- this.closest_point.copy( point );
- }
- }
- };
- })()
- };
-
- Goblin.GjkEpa2.Face.prototype = {
- /**
- * Determines if a vertex is in front of or behind the face
- *
- * @method classifyVertex
- * @param vertex {vec3} Vertex to classify
- * @return {Number} If greater than 0 then `vertex' is in front of the face
- */
- classifyVertex: function( vertex ) {
- var w = this.normal.dot( this.a.point );
- return this.normal.dot( vertex.point ) - w;
- },
-
- silhouette: function( point, edges, source ) {
- if ( this.active === false ) {
- return;
- }
-
- if ( this.classifyVertex( point ) > 0 ) {
- // This face is visible from `point`. Deactivate this face and alert the neighbors
- this.active = false;
-
- this.neighbors[0].silhouette( point, edges, this );
- this.neighbors[1].silhouette( point, edges, this );
- this.neighbors[2].silhouette( point, edges, this );
- } else if ( source ) {
- // This face is a neighbor to a now-silhouetted face, determine which neighbor and replace it
- var neighbor_idx = this.neighbors.indexOf( source ),
- a, b;
- if ( neighbor_idx === 0 ) {
- a = this.a;
- b = this.b;
- } else if ( neighbor_idx === 1 ) {
- a = this.b;
- b = this.c;
- } else {
- a = this.c;
- b = this.a;
- }
- edges.push( this, neighbor_idx, source, b, a );
- }
- }
- };
-
- (function(){
- var ao = new Goblin.Vector3(),
- ab = new Goblin.Vector3(),
- ac = new Goblin.Vector3(),
- ad = new Goblin.Vector3();
-
- Goblin.GjkEpa2.Simplex = function( object_a, object_b ) {
- this.object_a = object_a;
- this.object_b = object_b;
- this.points = [];
- this.iterations = 0;
- this.next_direction = new Goblin.Vector3();
- this.updateDirection();
- };
- Goblin.GjkEpa2.Simplex.prototype = {
- addPoint: function() {
- if ( ++this.iterations === Goblin.GjkEpa2.max_iterations ) {
- return false;
- }
-
- var support_point = Goblin.ObjectPool.getObject( 'GJK2SupportPoint' );
- Goblin.GjkEpa2.findSupportPoint( this.object_a, this.object_b, this.next_direction, support_point );
- this.points.push( support_point );
-
- if ( this.points[this.points.length-1].point.dot( this.next_direction ) < 0 ) {
- // if the last added point was not past the origin in the direction
- // then the Minkowski difference cannot contain the origin because
- // point added is past the edge of the Minkowski difference
- return false;
- }
-
- if ( this.updateDirection() === true ) {
- // Found a collision
- return null;
- }
-
- return support_point;
- },
-
- findDirectionFromLine: function() {
- ao.scaleVector( this.points[1].point, -1 );
- ab.subtractVectors( this.points[0].point, this.points[1].point );
-
- if ( ab.dot( ao ) < 0 ) {
- // Origin is on the opposite side of A from B
- this.next_direction.copy( ao );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', this.points[1] );
- this.points.length = 1; // Remove second point
- } else {
- // Origin lies between A and B, move on to a 2-simplex
- this.next_direction.crossVectors( ab, ao );
- this.next_direction.cross( ab );
-
- // In the case that `ab` and `ao` are parallel vectors, direction becomes a 0-vector
- if (
- this.next_direction.x === 0 &&
- this.next_direction.y === 0 &&
- this.next_direction.z === 0
- ) {
- ab.normalize();
- this.next_direction.x = 1 - Math.abs( ab.x );
- this.next_direction.y = 1 - Math.abs( ab.y );
- this.next_direction.z = 1 - Math.abs( ab.z );
- }
- }
- },
-
- findDirectionFromTriangle: function() {
- // Triangle
- var a = this.points[2],
- b = this.points[1],
- c = this.points[0];
-
- ao.scaleVector( a.point, -1 ); // ao
- ab.subtractVectors( b.point, a.point ); // ab
- ac.subtractVectors( c.point, a.point ); // ac
-
- // Determine the triangle's normal
- _tmp_vec3_1.crossVectors( ab, ac );
-
- // Edge cross products
- _tmp_vec3_2.crossVectors( ab, _tmp_vec3_1 );
- _tmp_vec3_3.crossVectors( _tmp_vec3_1, ac );
-
- if ( _tmp_vec3_3.dot( ao ) >= 0 ) {
- // Origin lies on side of ac opposite the triangle
- if ( ac.dot( ao ) >= 0 ) {
- // Origin outside of the ac line, so we form a new
- // 1-simplex (line) with points A and C, leaving B behind
- this.points.length = 0;
- this.points.push( c, a );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', b );
-
- // New search direction is from ac towards the origin
- this.next_direction.crossVectors( ac, ao );
- this.next_direction.cross( ac );
- } else {
- // *
- if ( ab.dot( ao ) >= 0 ) {
- // Origin outside of the ab line, so we form a new
- // 1-simplex (line) with points A and B, leaving C behind
- this.points.length = 0;
- this.points.push( b, a );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', c );
-
- // New search direction is from ac towards the origin
- this.next_direction.crossVectors( ab, ao );
- this.next_direction.cross( ab );
- } else {
- // only A gives us a good reference point, start over with a 0-simplex
- this.points.length = 0;
- this.points.push( a );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', b );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', c );
- }
- // *
- }
-
- } else {
-
- // Origin lies on the triangle side of ac
- if ( _tmp_vec3_2.dot( ao ) >= 0 ) {
- // Origin lies on side of ab opposite the triangle
-
- // *
- if ( ab.dot( ao ) >= 0 ) {
- // Origin outside of the ab line, so we form a new
- // 1-simplex (line) with points A and B, leaving C behind
- this.points.length = 0;
- this.points.push( b, a );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', c );
-
- // New search direction is from ac towards the origin
- this.next_direction.crossVectors( ab, ao );
- this.next_direction.cross( ab );
- } else {
- // only A gives us a good reference point, start over with a 0-simplex
- this.points.length = 0;
- this.points.push( a );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', b );
- Goblin.ObjectPool.freeObject( 'GJK2SupportPoint', c );
- }
- // *
-
- } else {
-
- // Origin lies somewhere in the triangle or above/below it
- if ( _tmp_vec3_1.dot( ao ) >= 0 ) {
- // Origin is on the front side of the triangle
- this.next_direction.copy( _tmp_vec3_1 );
- this.points.length = 0;
- this.points.push( a, b, c );
- } else {
- // Origin is on the back side of the triangle
- this.next_direction.copy( _tmp_vec3_1 );
- this.next_direction.scale( -1 );
- }
-
- }
-
- }
- },
-
- getFaceNormal: function( a, b, c, destination ) {
- ab.subtractVectors( b.point, a.point );
- ac.subtractVectors( c.point, a.point );
- destination.crossVectors( ab, ac );
- destination.normalize();
- },
-
- faceNormalDotOrigin: function( a, b, c ) {
- // Find face normal
- this.getFaceNormal( a, b, c, _tmp_vec3_1 );
-
- // Find direction of origin from center of face
- _tmp_vec3_2.addVectors( a.point, b.point );
- _tmp_vec3_2.add( c.point );
- _tmp_vec3_2.scale( -3 );
- _tmp_vec3_2.normalize();
-
- return _tmp_vec3_1.dot( _tmp_vec3_2 );
- },
-
- findDirectionFromTetrahedron: function() {
- var a = this.points[3],
- b = this.points[2],
- c = this.points[1],
- d = this.points[0];
-
- // Check each of the four sides to see which one is facing the origin.
- // Then keep the three points for that triangle and use its normal as the search direction
- // The four faces are BCD, ACB, CAD, DAB
- var closest_face = null,
- closest_dot = Goblin.EPSILON,
- face_dot;
-
- // @TODO we end up calculating the "winning" face normal twice, don't do that
-
- face_dot = this.faceNormalDotOrigin( b, c, d );
- if ( face_dot > closest_dot ) {
- closest_face = 1;
- closest_dot = face_dot;
- }
-
- face_dot = this.faceNormalDotOrigin( a, c, b );
- if ( face_dot > closest_dot ) {
- closest_face = 2;
- closest_dot = face_dot;
- }
-
- face_dot = this.faceNormalDotOrigin( c, a, d );
- if ( face_dot > closest_dot ) {
- closest_face = 3;
- closest_dot = face_dot;
- }
-
- face_dot = this.faceNormalDotOrigin( d, a, b );
- if ( face_dot > closest_dot ) {
- closest_face = 4;
- closest_dot = face_dot;
- }
-
- if ( closest_face === null ) {
- // We have a collision, ready for EPA
- return true;
- } else if ( closest_face === 1 ) {
- // BCD
- this.points.length = 0;
- this.points.push( b, c, d );
- this.getFaceNormal( b, c, d, _tmp_vec3_1 );
- this.next_direction.copy( _tmp_vec3_1 );
- } else if ( closest_face === 2 ) {
- // ACB
- this.points.length = 0;
- this.points.push( a, c, b );
- this.getFaceNormal( a, c, b, _tmp_vec3_1 );
- this.next_direction.copy( _tmp_vec3_1 );
- } else if ( closest_face === 3 ) {
- // CAD
- this.points.length = 0;
- this.points.push( c, a, d );
- this.getFaceNormal( c, a, d, _tmp_vec3_1 );
- this.next_direction.copy( _tmp_vec3_1 );
- } else if ( closest_face === 4 ) {
- // DAB
- this.points.length = 0;
- this.points.push( d, a, b );
- this.getFaceNormal( d, a, b, _tmp_vec3_1 );
- this.next_direction.copy( _tmp_vec3_1 );
- }
- },
-
- containsOrigin: function() {
- var a = this.points[3],
- b = this.points[2],
- c = this.points[1],
- d = this.points[0];
-
- // Check DCA
- ab.subtractVectors( d.point, a.point );
- ad.subtractVectors( c.point, a.point );
- _tmp_vec3_1.crossVectors( ab, ad );
- if ( _tmp_vec3_1.dot( a.point ) > 0 ) {
- return false;
- }
-
- // Check CBA
- ab.subtractVectors( c.point, a.point );
- ad.subtractVectors( b.point, a.point );
- _tmp_vec3_1.crossVectors( ab, ad );
- if ( _tmp_vec3_1.dot( a.point ) > 0 ) {
- return false;
- }
-
- // Check ADB
- ab.subtractVectors( b.point, a.point );
- ad.subtractVectors( d.point, a.point );
- _tmp_vec3_1.crossVectors( ab, ad );
- if ( _tmp_vec3_1.dot( a.point ) > 0 ) {
- return false;
- }
-
- // Check DCB
- ab.subtractVectors( d.point, c.point );
- ad.subtractVectors( b.point, c.point );
- _tmp_vec3_1.crossVectors( ab, ad );
- if ( _tmp_vec3_1.dot( d.point ) > 0 ) {
- return false;
- }
-
- return true;
- },
-
- updateDirection: function() {
- if ( this.points.length === 0 ) {
-
- this.next_direction.subtractVectors( this.object_b.position, this.object_a.position );
-
- } else if ( this.points.length === 1 ) {
-
- this.next_direction.scale( -1 );
-
- } else if ( this.points.length === 2 ) {
-
- this.findDirectionFromLine();
-
- } else if ( this.points.length === 3 ) {
-
- this.findDirectionFromTriangle();
-
- } else {
-
- return this.findDirectionFromTetrahedron();
-
- }
- }
- };
- })();
-
-