topical media & game development
mobile-query-three-plugins-cannonjs-vendor-cannon.js-src-objects-ConvexPolyhedron.js / js
@class CANNON.ConvexPolyhedron
@extends CANNON.Shape
@brief A set of points in space describing a convex shape.
author: qiao / github.com/qiao (original author, see github.com/qiao/three.js/commit/85026f0c769e4000148a67d45a9e9b9c5108836f)
author: schteppe / github.com/schteppe
see: www.altdevblogaday.com/2011/05/13/contact-generation-between-3d-convex-meshes/
see: bullet.googlecode.com/svn/trunk/src/BulletCollision/NarrowPhaseCollision/btPolyhedralContactClipping.cpp
@todo move the clipping functions to ContactGenerator?
parameter: array points An array of CANNON.Vec3's
CANNON.ConvexPolyhedron = function( points , faces , normals ) {
var that = this;
CANNON.Shape.call( this );
this.type = CANNON.Shape.types.CONVEXPOLYHEDRON;
@property array vertices
@memberof CANNON.ConvexPolyhedron
@brief Array of CANNON.Vec3
this.vertices = points||[];
@property array faces
@memberof CANNON.ConvexPolyhedron
@brief Array of integer arrays, indicating which vertices each face consists of
@todo Needed?
this.faces = faces||[];
@property array faceNormals
@memberof CANNON.ConvexPolyhedron
@brief Array of CANNON.Vec3
@todo Needed?
this.faceNormals = normals||[];
@property array uniqueEdges
@memberof CANNON.ConvexPolyhedron
@brief Array of CANNON.Vec3
this.uniqueEdges = [];
var nv = this.vertices.length;
for(var pi=0; pi<nv; pi++){
var p = this.vertices[pi];
if(!(p instanceof CANNON.Vec3)){
throw "Argument 1 must be instance of CANNON.Vec3";
return false;
}
this.vertices.push(p);
}
for(var i=0; i<this.faces.length; i++){
var numVertices = this.faces[i].length;
var NbTris = numVertices;
for(var j=0; j<NbTris; j++){
var k = ( j+1 ) % numVertices;
var edge = new CANNON.Vec3();
this.vertices[this.faces[i][j]].vsub(this.vertices[this.faces[i][k]],edge);
edge.normalize();
var found = false;
for(var p=0;p<this.uniqueEdges.length;p++){
if (this.uniqueEdges[p].almostEquals(edge) ||
this.uniqueEdges[p].almostEquals(edge)){
found = true;
break;
}
}
if (!found){
this.uniqueEdges.push(edge);
}
if (edge) {
edge.face1 = i;
} else {
var ed;
ed.m_face0 = i;
edges.insert(vp,ed);
}
}
}
/*
* Get max and min dot product of a convex hull at position (pos,quat) projected onto an axis. Results are saved in the array maxmin.
*
parameter: CANNON.ConvexPolyhedron hull
*
parameter: CANNON.Vec3 axis
*
parameter: CANNON.Vec3 pos
*
parameter: CANNON.Quaternion quat
*
parameter: array maxmin maxmin[0] and maxmin[1] will be set to maximum and minimum, respectively.
*/
var worldVertex = new CANNON.Vec3();
function project(hull,axis,pos,quat,maxmin){
var n = hull.vertices.length;
var max = null;
var min = null;
var vs = hull.vertices;
for(var i=0; i<n; i++){
vs[i].copy(worldVertex);
quat.vmult(worldVertex,worldVertex);
worldVertex.vadd(pos,worldVertex);
var val = worldVertex.dot(axis);
if(max===null || val>max)
max = val;
if(min===null || val<min)
min = val;
}
if(min>max){
// Inconsistent - swap
var temp = min;
min = max;
max = temp;
}
// Output
maxmin[0] = max;
maxmin[1] = min;
}
@method testSepAxis
@memberof CANNON.ConvexPolyhedron
@brief Test separating axis against two hulls. Both hulls are projected onto the axis and the overlap size is returned if there is one.
parameter: CANNON.Vec3 axis
parameter: CANNON.ConvexPolyhedron hullB
parameter: CANNON.Vec3 posA
parameter: CANNON.Quaternion quatA
parameter: CANNON.Vec3 posB
parameter: CANNON.Quaternion quatB
returns: float The overlap depth, or FALSE if no penetration.
this.testSepAxis = function(axis, hullB, posA, quatA, posB, quatB){
var maxminA=[], maxminB=[], hullA=this;
project(hullA, axis, posA, quatA, maxminA);
project(hullB, axis, posB, quatB, maxminB);
var maxA = maxminA[0];
var minA = maxminA[1];
var maxB = maxminB[0];
var minB = maxminB[1];
if(maxA<minB || maxB<minA){
//console.log(minA,maxA,minB,maxB);
return false; // Separated
}
var d0 = maxA - minB;
var d1 = maxB - minA;
depth = d0<d1 ? d0:d1;
return depth;
}
@method findSeparatingAxis
@memberof CANNON.ConvexPolyhedron
@brief Find the separating axis between this hull and another
parameter: CANNON.ConvexPolyhedron hullB
parameter: CANNON.Vec3 posA
parameter: CANNON.Quaternion quatA
parameter: CANNON.Vec3 posB
parameter: CANNON.Quaternion quatB
parameter: CANNON.Vec3 target The target vector to save the axis in
returns: bool Returns false if a separation is found, else true
var faceANormalWS3 = new CANNON.Vec3();
var Worldnormal1 = new CANNON.Vec3();
var deltaC = new CANNON.Vec3();
var worldEdge0 = new CANNON.Vec3();
var worldEdge1 = new CANNON.Vec3();
var Cross = new CANNON.Vec3();
this.findSeparatingAxis = function(hullB,posA,quatA,posB,quatB,target){
var dmin = Infinity;
var hullA = this;
var curPlaneTests=0;
var numFacesA = hullA.faces.length;
// Test normals from hullA
for(var i=0; i<numFacesA; i++){
// Get world face normal
hullA.faceNormals[i].copy(faceANormalWS3);
quatA.vmult(faceANormalWS3,faceANormalWS3);
//posA.vadd(faceANormalWS3,faceANormalWS3); // Needed?
//console.log("face normal:",hullA.faceNormals[i].toString(),"world face normal:",faceANormalWS3);
var d = hullA.testSepAxis(faceANormalWS3, hullB, posA, quatA, posB, quatB);
if(d===false){
return false;
}
if(d<dmin){
dmin = d;
faceANormalWS3.copy(target);
}
}
// Test normals from hullB
var numFacesB = hullB.faces.length;
for(var i=0;i<numFacesB;i++){
hullB.faceNormals[i].copy(Worldnormal1);
quatB.vmult(Worldnormal1,Worldnormal1);
//posB.vadd(Worldnormal1,Worldnormal1);
//console.log("facenormal",hullB.faceNormals[i].toString(),"world:",Worldnormal1.toString());
curPlaneTests++;
var d = hullA.testSepAxis(Worldnormal1, hullB,posA,quatA,posB,quatB);
if(d===false){
return false;
}
if(d<dmin){
dmin = d;
Worldnormal1.copy(target);
}
}
var edgeAstart,edgeAend,edgeBstart,edgeBend;
var curEdgeEdge = 0;
// Test edges
for(var e0=0; e0<hullA.uniqueEdges.length; e0++){
// Get world edge
hullA.uniqueEdges[e0].copy(worldEdge0);
quatA.vmult(worldEdge0,worldEdge0);
//posA.vadd(worldEdge0,worldEdge0); // needed?
//console.log("edge0:",worldEdge0.toString());
for(var e1=0; e1<hullB.uniqueEdges.length; e1++){
hullB.uniqueEdges[e1].copy(worldEdge1);
quatB.vmult(worldEdge1,worldEdge1);
//posB.vadd(worldEdge1,worldEdge1); // needed?
//console.log("edge1:",worldEdge1.toString());
worldEdge0.cross(worldEdge1,Cross);
curEdgeEdge++;
if(!Cross.almostZero()){
Cross.normalize();
var dist = hullA.testSepAxis( Cross, hullB, posA,quatA,posB,quatB);
if(dist===false){
return false;
}
if(dist<dmin){
dmin = dist;
Cross.copy(target);
}
}
}
}
posB.vsub(posA,deltaC);
if((deltaC.dot(target))>0.0)
target.negate(target);
return true;
}
@method clipAgainstHull
@memberof CANNON.ConvexPolyhedron
@brief Clip this hull against another hull
parameter: CANNON.Vec3 posA
parameter: CANNON.Quaternion quatA
parameter: CANNON.ConvexPolyhedron hullB
parameter: CANNON.Vec3 posB
parameter: CANNON.Quaternion quatB
parameter: CANNON.Vec3 separatingNormal
parameter: float minDist Clamp distance
parameter: float maxDist
parameter: array result The an array of contact point objects, see clipFaceAgainstHull
see: bullet.googlecode.com/svn/trunk/src/BulletCollision/NarrowPhaseCollision/btPolyhedralContactClipping.cpp
var WorldNormal = new CANNON.Vec3();
this.clipAgainstHull = function(posA,quatA,hullB,posB,quatB,separatingNormal,minDist,maxDist,result){
if(!(posA instanceof CANNON.Vec3))
throw new Error("posA must be Vec3");
if(!(quatA instanceof CANNON.Quaternion))
throw new Error("quatA must be Quaternion");
var hullA = this;
var curMaxDist = maxDist;
var closestFaceB = -1;
var dmax = -Infinity;
for(var face=0; face < hullB.faces.length; face++){
hullB.faceNormals[face].copy(WorldNormal);
quatB.vmult(WorldNormal,WorldNormal);
posB.vadd(WorldNormal,WorldNormal);
var d = WorldNormal.dot(separatingNormal);
if (d > dmax){
dmax = d;
closestFaceB = face;
}
}
var worldVertsB1 = [];
polyB = hullB.faces[closestFaceB];
var numVertices = polyB.length;
for(var e0=0; e0<numVertices; e0++){
var b = hullB.vertices[polyB[e0]];
var worldb = new CANNON.Vec3();
b.copy(worldb);
quatB.vmult(worldb,worldb);
posB.vadd(worldb,worldb);
worldVertsB1.push(worldb);
}
//console.log("--- clipping face: ",worldVertsB1);
if (closestFaceB>=0)
this.clipFaceAgainstHull(separatingNormal,
posA,
quatA,
worldVertsB1,
minDist,
maxDist,
result);
};
@method clipFaceAgainstHull
@memberof CANNON.ConvexPolyhedron
@brief Clip a face against a hull.
parameter: CANNON.Vec3 separatingNormal
parameter: CANNON.Vec3 posA
parameter: CANNON.Quaternion quatA
parameter: Array worldVertsB1 An array of CANNON.Vec3 with vertices in the world frame.
parameter: float minDist Distance clamping
parameter: float maxDist
parameter: Array result Array to store resulting contact points in. Will be objects with properties: point, depth, normal. These are represented in world coordinates.
var faceANormalWS = new CANNON.Vec3();
var edge0 = new CANNON.Vec3();
var WorldEdge0 = new CANNON.Vec3();
var worldPlaneAnormal1 = new CANNON.Vec3();
var planeNormalWS1 = new CANNON.Vec3();
var worldA1 = new CANNON.Vec3();
var localPlaneNormal = new CANNON.Vec3();
var planeNormalWS = new CANNON.Vec3();
this.clipFaceAgainstHull = function(separatingNormal, posA, quatA, worldVertsB1, minDist, maxDist,result){
if(!(separatingNormal instanceof CANNON.Vec3))
throw new Error("sep normal must be vector");
if(!(worldVertsB1 instanceof Array))
throw new Error("world verts must be array");
minDist = Number(minDist);
maxDist = Number(maxDist);
var hullA = this;
var worldVertsB2 = [];
var pVtxIn = worldVertsB1;
var pVtxOut = worldVertsB2;
// Find the face with normal closest to the separating axis
var closestFaceA = -1;
var dmin = Infinity;
for(var face=0; face<hullA.faces.length; face++){
hullA.faceNormals[face].copy(faceANormalWS);
quatA.vmult(faceANormalWS,faceANormalWS);
posA.vadd(faceANormalWS,faceANormalWS);
var d = faceANormalWS.dot(separatingNormal);
if (d < dmin){
dmin = d;
closestFaceA = face;
}
}
if (closestFaceA<0){
console.log("--- did not find any closest face... ---");
return;
}
//console.log("closest A: ",closestFaceA);
// Get the face and construct connected faces
var polyA = hullA.faces[closestFaceA];
polyA.connectedFaces = [];
for(var i=0; i<hullA.faces.length; i++)
for(var j=0; j<hullA.faces[i].length; j++)
if(polyA.indexOf(hullA.faces[i][j])!==-1 && // Sharing a vertex
i!==closestFaceA && // Not the one we are looking for connections from
polyA.connectedFaces.indexOf(i)===-1 // Not already added
)
polyA.connectedFaces.push(i);
// Clip the polygon to the back of the planes of all faces of hull A, that are adjacent to the witness face
var numContacts = pVtxIn.length;
var numVerticesA = polyA.length;
var res = [];
for(var e0=0; e0<numVerticesA; e0++){
var a = hullA.vertices[polyA[e0]];
var b = hullA.vertices[polyA[(e0+1)\%numVerticesA]];
a.vsub(b,edge0);
edge0.copy(WorldEdge0);
quatA.vmult(WorldEdge0,WorldEdge0);
posA.vadd(WorldEdge0,WorldEdge0);
this.faceNormals[closestFaceA].copy(worldPlaneAnormal1);//transA.getBasis()* btVector3(polyA.m_plane[0],polyA.m_plane[1],polyA.m_plane[2]);
quatA.vmult(worldPlaneAnormal1,worldPlaneAnormal1);
posA.vadd(worldPlaneAnormal1,worldPlaneAnormal1);
WorldEdge0.cross(worldPlaneAnormal1,planeNormalWS1);
planeNormalWS1.negate(planeNormalWS1);
a.copy(worldA1);
quatA.vmult(worldA1,worldA1);
posA.vadd(worldA1,worldA1);
var planeEqWS1 = -worldA1.dot(planeNormalWS1);
var planeEqWS;
if(true){
var otherFace = polyA.connectedFaces[e0];
this.faceNormals[otherFace].copy(localPlaneNormal);
var localPlaneEq = planeConstant(otherFace);
localPlaneNormal.copy(planeNormalWS);
quatA.vmult(planeNormalWS,planeNormalWS);
//posA.vadd(planeNormalWS,planeNormalWS);
var planeEqWS = localPlaneEq - planeNormalWS.dot(posA);
} else {
planeNormalWS1.copy(planeNormalWS);
planeEqWS = planeEqWS1;
}
// Clip face against our constructed plane
//console.log("clipping polygon ",printFace(closestFaceA)," against plane ",planeNormalWS, planeEqWS);
this.clipFaceAgainstPlane(pVtxIn, pVtxOut, planeNormalWS, planeEqWS);
//console.log(" - clip result: ",pVtxOut);
// Throw away all clipped points, but save the reamining until next clip
while(pVtxIn.length) pVtxIn.shift();
while(pVtxOut.length) pVtxIn.push(pVtxOut.shift());
}
//console.log("Resulting points after clip:",pVtxIn);
// only keep contact points that are behind the witness face
this.faceNormals[closestFaceA].copy(localPlaneNormal);
var localPlaneEq = planeConstant(closestFaceA);
localPlaneNormal.copy(planeNormalWS);
quatA.vmult(planeNormalWS,planeNormalWS);
var planeEqWS = localPlaneEq - planeNormalWS.dot(posA);
for (var i=0; i<pVtxIn.length; i++){
var depth = planeNormalWS.dot(pVtxIn[i]) + planeEqWS; //???
/*console.log("depth calc from normal=",planeNormalWS.toString()," and constant "+planeEqWS+" and vertex ",pVtxIn[i].toString()," gives "+depth);*/
if (depth <=minDist){
console.log("clamped: depth="+depth+" to minDist="+(minDist+""));
depth = minDist;
}
if (depth <=maxDist){
var point = pVtxIn[i];
if(depth<=0){
/*console.log("Got contact point ",point.toString(),
", depth=",depth,
"contact normal=",separatingNormal.toString(),
"plane",planeNormalWS.toString(),
"planeConstant",planeEqWS);*/
var p = {
point:point,
normal:planeNormalWS,
depth: depth,
};
result.push(p);
}
}
}
}
@method clipFaceAgainstPlane
@memberof CANNON.ConvexPolyhedron
@brief Clip a face in a hull against the back of a plane.
parameter: Array inVertices
parameter: Array outVertices
parameter: CANNON.Vec3 planeNormal
parameter: float planeConstant The constant in the mathematical plane equation
this.clipFaceAgainstPlane = function(inVertices,outVertices, planeNormal, planeConstant){
if(!(planeNormal instanceof CANNON.Vec3))
throw new Error("planeNormal must be Vec3, "+planeNormal+" given");
if(!(inVertices instanceof Array))
throw new Error("invertices must be Array, "+inVertices+" given");
if(!(outVertices instanceof Array))
throw new Error("outvertices must be Array, "+outVertices+" given");
var n_dot_first, n_dot_last;
var numVerts = inVertices.length;
if(numVerts < 2)
return outVertices;
var firstVertex = inVertices[inVertices.length-1];
var lastVertex = inVertices[0];
n_dot_first = planeNormal.dot(firstVertex) + planeConstant;
for(var vi = 0; vi < numVerts; vi++){
lastVertex = inVertices[vi];
n_dot_last = planeNormal.dot(lastVertex) + planeConstant;
if(n_dot_first < 0){
if(n_dot_last < 0){
// Start < 0, end < 0, so output lastVertex
var newv = new CANNON.Vec3();
lastVertex.copy(newv);
outVertices.push(newv);
} else {
// Start < 0, end >= 0, so output intersection
var newv = new CANNON.Vec3();
firstVertex.lerp(lastVertex,
n_dot_first / (n_dot_first - n_dot_last),
newv);
outVertices.push(newv);
}
} else {
if(n_dot_last<0){
// Start >= 0, end < 0 so output intersection and end
var newv = new CANNON.Vec3();
firstVertex.lerp(lastVertex,
n_dot_first / (n_dot_first - n_dot_last),
newv);
outVertices.push(newv);
outVertices.push(lastVertex);
}
}
firstVertex = lastVertex;
n_dot_first = n_dot_last;
}
return outVertices;
}
/*
* Whether the face is visible from the vertex
*
parameter: array face
*
parameter: CANNON.Vec3 vertex
*/
function visible( face, vertex ) {
var va = that.vertices[ face[ 0 ] ];
var vb = that.vertices[ face[ 1 ] ];
var vc = that.vertices[ face[ 2 ] ];
var n = new CANNON.Vec3();
normal( va, vb, vc, n );
// distance from face to origin
var dist = n.dot( va );
return n.dot( vertex ) >= dist;
}
var that = this;
function normalOfFace(i,target){
var f = that.faces[i];
var va = that.vertices[f[0]];
var vb = that.vertices[f[1]];
var vc = that.vertices[f[2]];
return normal(va,vb,vc,target);
}
function planeConstant(face_i,target){
var f = that.faces[face_i];
var n = that.faceNormals[face_i];
var v = that.vertices[f[0]];
var c = -n.dot(v);
return c;
}
/*
* @brief Get face normal given 3 vertices
*
parameter: CANNON.Vec3 va
*
parameter: CANNON.Vec3 vb
*
parameter: CANNON.Vec3 vc
*
parameter: CANNON.Vec3 target
* @todo unit test?
*/
var cb = new CANNON.Vec3();
var ab = new CANNON.Vec3();
function normal( va, vb, vc, target ) {
vb.vsub(va,ab);
vc.vsub(vb,cb);
cb.cross(ab,target);
if ( !target.isZero() ) {
target.normalize();
}
}
function printFace(i){
var f = that.faces[i], s = "";
for(var j=0; j<f.length; j++)
s += " ("+that.vertices[f[j]]+")";
return s;
}
/*
* Detect whether two edges are equal.
* Note that when constructing the convex hull, two same edges can only
* be of the negative direction.
*
returns: bool
*/
function equalEdge( ea, eb ) {
return ea[ 0 ] === eb[ 1 ] && ea[ 1 ] === eb[ 0 ];
}
/*
* Create a random offset between -1e-6 and 1e-6.
*
returns: float
*/
function randomOffset() {
return ( Math.random() - 0.5 ) * 2 * 1e-6;
}
this.calculateLocalInertia = function(mass,target){
// Approximate with box inertia
// Exact inertia calculation is overkill, but see http://geometrictools.com/Documentation/PolyhedralMassProperties.pdf for the correct way to do it
var x = this.aabbmax.x - this.aabbmin.x,
y = this.aabbmax.y - this.aabbmin.y,
z = this.aabbmax.z - this.aabbmin.z;
target.x = 1.0 / 12.0 * mass * ( 2*y*2*y + 2*z*2*z );
target.y = 1.0 / 12.0 * mass * ( 2*x*2*x + 2*z*2*z );
target.z = 1.0 / 12.0 * mass * ( 2*y*2*y + 2*x*2*x );
}
this.computeAABB = function(){
var n = this.vertices.length,
aabbmin = this.aabbmin,
aabbmax = this.aabbmax,
vertices = this.vertices;
aabbmin.set(Infinity,Infinity,Infinity);
aabbmax.set(-Infinity,-Infinity,-Infinity);
for(var i=0; i<n; i++){
var v = vertices[i];
if (v.x < aabbmin.x) aabbmin.x = v.x;
else if(v.x > aabbmax.x) aabbmax.x = v.x;
if (v.y < aabbmin.y) aabbmin.y = v.y;
else if(v.y > aabbmax.y) aabbmax.y = v.y;
if (v.z < aabbmin.z) aabbmin.z = v.z;
else if(v.z > aabbmax.z) aabbmax.z = v.z;
}
}
this.boundingSphereRadius = function(){
// Assume points are distributed with local (0,0,0) as center
var max2 = 0;
for(var i=0; i<this.vertices.length; i++) {
var norm2 = this.vertices[i].norm2();
if(norm2>max2)
max2 = norm2;
}
return Math.sqrt(max2);
}
this.computeAABB();
};
CANNON.ConvexPolyhedron.prototype = new CANNON.Shape();
CANNON.ConvexPolyhedron.prototype.constructor = CANNON.ConvexPolyhedron;
var tempWorldVertex = new CANNON.Vec3();
CANNON.ConvexPolyhedron.prototype.calculateWorldAABB = function(pos,quat,min,max){
var n = this.vertices.length, verts = this.vertices;
var minx,miny,minz,maxx,maxy,maxz;
for(var i=0; i<n; i++){
verts[i].copy(tempWorldVertex);
quat.vmult(tempWorldVertex,tempWorldVertex);
pos.vadd(tempWorldVertex,tempWorldVertex);
var v = tempWorldVertex;
if (v.x < minx || minx==undefined) minx = v.x;
else if(v.x > maxx || maxx==undefined) maxx = v.x;
if (v.y < miny || miny==undefined) miny = v.y;
else if(v.y > maxy || maxy==undefined) maxy = v.y;
if (v.z < minz || minz==undefined) minz = v.z;
else if(v.z > maxz || maxz==undefined) maxz = v.z;
}
min.set(minx,miny,minz);
max.set(maxx,maxy,maxz);
};
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.