UNPKG

cannon

Version:

A lightweight 3D physics engine written in JavaScript.

561 lines (494 loc) 14.5 kB
module.exports = Trimesh; var Shape = require('./Shape'); var Vec3 = require('../math/Vec3'); var Quaternion = require('../math/Quaternion'); var Transform = require('../math/Transform'); var AABB = require('../collision/AABB'); var Octree = require('../utils/Octree'); /** * @class Trimesh * @constructor * @param {array} vertices * @param {array} indices * @extends Shape * @example * // How to make a mesh with a single triangle * var vertices = [ * 0, 0, 0, // vertex 0 * 1, 0, 0, // vertex 1 * 0, 1, 0 // vertex 2 * ]; * var indices = [ * 0, 1, 2 // triangle 0 * ]; * var trimeshShape = new Trimesh(vertices, indices); */ function Trimesh(vertices, indices) { Shape.call(this); this.type = Shape.types.TRIMESH; /** * @property vertices * @type {Array} */ this.vertices = new Float32Array(vertices); /** * Array of integers, indicating which vertices each triangle consists of. The length of this array is thus 3 times the number of triangles. * @property indices * @type {Array} */ this.indices = new Int16Array(indices); /** * The normals data. * @property normals * @type {Array} */ this.normals = new Float32Array(indices.length); /** * The local AABB of the mesh. * @property aabb * @type {Array} */ this.aabb = new AABB(); /** * References to vertex pairs, making up all unique edges in the trimesh. * @property {array} edges */ this.edges = null; /** * Local scaling of the mesh. Use .setScale() to set it. * @property {Vec3} scale */ this.scale = new Vec3(1, 1, 1); /** * The indexed triangles. Use .updateTree() to update it. * @property {Octree} tree */ this.tree = new Octree(); this.updateEdges(); this.updateNormals(); this.updateAABB(); this.updateBoundingSphereRadius(); this.updateTree(); } Trimesh.prototype = new Shape(); Trimesh.prototype.constructor = Trimesh; var computeNormals_n = new Vec3(); /** * @method updateTree */ Trimesh.prototype.updateTree = function(){ var tree = this.tree; tree.reset(); tree.aabb.copy(this.aabb); var scale = this.scale; // The local mesh AABB is scaled, but the octree AABB should be unscaled tree.aabb.lowerBound.x *= 1 / scale.x; tree.aabb.lowerBound.y *= 1 / scale.y; tree.aabb.lowerBound.z *= 1 / scale.z; tree.aabb.upperBound.x *= 1 / scale.x; tree.aabb.upperBound.y *= 1 / scale.y; tree.aabb.upperBound.z *= 1 / scale.z; // Insert all triangles var triangleAABB = new AABB(); var a = new Vec3(); var b = new Vec3(); var c = new Vec3(); var points = [a, b, c]; for (var i = 0; i < this.indices.length / 3; i++) { //this.getTriangleVertices(i, a, b, c); // Get unscaled triangle verts var i3 = i * 3; this._getUnscaledVertex(this.indices[i3], a); this._getUnscaledVertex(this.indices[i3 + 1], b); this._getUnscaledVertex(this.indices[i3 + 2], c); triangleAABB.setFromPoints(points); tree.insert(triangleAABB, i); } tree.removeEmptyNodes(); }; var unscaledAABB = new AABB(); /** * Get triangles in a local AABB from the trimesh. * @method getTrianglesInAABB * @param {AABB} aabb * @param {array} result An array of integers, referencing the queried triangles. */ Trimesh.prototype.getTrianglesInAABB = function(aabb, result){ unscaledAABB.copy(aabb); // Scale it to local var scale = this.scale; var isx = scale.x; var isy = scale.y; var isz = scale.z; var l = unscaledAABB.lowerBound; var u = unscaledAABB.upperBound; l.x /= isx; l.y /= isy; l.z /= isz; u.x /= isx; u.y /= isy; u.z /= isz; return this.tree.aabbQuery(unscaledAABB, result); }; /** * @method setScale * @param {Vec3} scale */ Trimesh.prototype.setScale = function(scale){ var wasUniform = this.scale.x === this.scale.y === this.scale.z; var isUniform = scale.x === scale.y === scale.z; if(!(wasUniform && isUniform)){ // Non-uniform scaling. Need to update normals. this.updateNormals(); } this.scale.copy(scale); this.updateAABB(); this.updateBoundingSphereRadius(); }; /** * Compute the normals of the faces. Will save in the .normals array. * @method updateNormals */ Trimesh.prototype.updateNormals = function(){ var n = computeNormals_n; // Generate normals var normals = this.normals; for(var i=0; i < this.indices.length / 3; i++){ var i3 = i * 3; var a = this.indices[i3], b = this.indices[i3 + 1], c = this.indices[i3 + 2]; this.getVertex(a, va); this.getVertex(b, vb); this.getVertex(c, vc); Trimesh.computeNormal(vb, va, vc, n); normals[i3] = n.x; normals[i3 + 1] = n.y; normals[i3 + 2] = n.z; } }; /** * Update the .edges property * @method updateEdges */ Trimesh.prototype.updateEdges = function(){ var edges = {}; var add = function(indexA, indexB){ var key = a < b ? a + '_' + b : b + '_' + a; edges[key] = true; }; for(var i=0; i < this.indices.length / 3; i++){ var i3 = i * 3; var a = this.indices[i3], b = this.indices[i3 + 1], c = this.indices[i3 + 2]; add(a,b); add(b,c); add(c,a); } var keys = Object.keys(edges); this.edges = new Int16Array(keys.length * 2); for (var i = 0; i < keys.length; i++) { var indices = keys[i].split('_'); this.edges[2 * i] = parseInt(indices[0], 10); this.edges[2 * i + 1] = parseInt(indices[1], 10); } }; /** * Get an edge vertex * @method getEdgeVertex * @param {number} edgeIndex * @param {number} firstOrSecond 0 or 1, depending on which one of the vertices you need. * @param {Vec3} vertexStore Where to store the result */ Trimesh.prototype.getEdgeVertex = function(edgeIndex, firstOrSecond, vertexStore){ var vertexIndex = this.edges[edgeIndex * 2 + (firstOrSecond ? 1 : 0)]; this.getVertex(vertexIndex, vertexStore); }; var getEdgeVector_va = new Vec3(); var getEdgeVector_vb = new Vec3(); /** * Get a vector along an edge. * @method getEdgeVector * @param {number} edgeIndex * @param {Vec3} vectorStore */ Trimesh.prototype.getEdgeVector = function(edgeIndex, vectorStore){ var va = getEdgeVector_va; var vb = getEdgeVector_vb; this.getEdgeVertex(edgeIndex, 0, va); this.getEdgeVertex(edgeIndex, 1, vb); vb.vsub(va, vectorStore); }; /** * Get face normal given 3 vertices * @static * @method computeNormal * @param {Vec3} va * @param {Vec3} vb * @param {Vec3} vc * @param {Vec3} target */ var cb = new Vec3(); var ab = new Vec3(); Trimesh.computeNormal = function ( va, vb, vc, target ) { vb.vsub(va,ab); vc.vsub(vb,cb); cb.cross(ab,target); if ( !target.isZero() ) { target.normalize(); } }; var va = new Vec3(); var vb = new Vec3(); var vc = new Vec3(); /** * Get vertex i. * @method getVertex * @param {number} i * @param {Vec3} out * @return {Vec3} The "out" vector object */ Trimesh.prototype.getVertex = function(i, out){ var scale = this.scale; this._getUnscaledVertex(i, out); out.x *= scale.x; out.y *= scale.y; out.z *= scale.z; return out; }; /** * Get raw vertex i * @private * @method _getUnscaledVertex * @param {number} i * @param {Vec3} out * @return {Vec3} The "out" vector object */ Trimesh.prototype._getUnscaledVertex = function(i, out){ var i3 = i * 3; var vertices = this.vertices; return out.set( vertices[i3], vertices[i3 + 1], vertices[i3 + 2] ); }; /** * Get a vertex from the trimesh,transformed by the given position and quaternion. * @method getWorldVertex * @param {number} i * @param {Vec3} pos * @param {Quaternion} quat * @param {Vec3} out * @return {Vec3} The "out" vector object */ Trimesh.prototype.getWorldVertex = function(i, pos, quat, out){ this.getVertex(i, out); Transform.pointToWorldFrame(pos, quat, out, out); return out; }; /** * Get the three vertices for triangle i. * @method getTriangleVertices * @param {number} i * @param {Vec3} a * @param {Vec3} b * @param {Vec3} c */ Trimesh.prototype.getTriangleVertices = function(i, a, b, c){ var i3 = i * 3; this.getVertex(this.indices[i3], a); this.getVertex(this.indices[i3 + 1], b); this.getVertex(this.indices[i3 + 2], c); }; /** * Compute the normal of triangle i. * @method getNormal * @param {Number} i * @param {Vec3} target * @return {Vec3} The "target" vector object */ Trimesh.prototype.getNormal = function(i, target){ var i3 = i * 3; return target.set( this.normals[i3], this.normals[i3 + 1], this.normals[i3 + 2] ); }; var cli_aabb = new AABB(); /** * @method calculateLocalInertia * @param {Number} mass * @param {Vec3} target * @return {Vec3} The "target" vector object */ Trimesh.prototype.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 this.computeLocalAABB(cli_aabb); var x = cli_aabb.upperBound.x - cli_aabb.lowerBound.x, y = cli_aabb.upperBound.y - cli_aabb.lowerBound.y, z = cli_aabb.upperBound.z - cli_aabb.lowerBound.z; return target.set( 1.0 / 12.0 * mass * ( 2*y*2*y + 2*z*2*z ), 1.0 / 12.0 * mass * ( 2*x*2*x + 2*z*2*z ), 1.0 / 12.0 * mass * ( 2*y*2*y + 2*x*2*x ) ); }; var computeLocalAABB_worldVert = new Vec3(); /** * Compute the local AABB for the trimesh * @method computeLocalAABB * @param {AABB} aabb */ Trimesh.prototype.computeLocalAABB = function(aabb){ var l = aabb.lowerBound, u = aabb.upperBound, n = this.vertices.length, vertices = this.vertices, v = computeLocalAABB_worldVert; this.getVertex(0, v); l.copy(v); u.copy(v); for(var i=0; i !== n; i++){ this.getVertex(i, v); if(v.x < l.x){ l.x = v.x; } else if(v.x > u.x){ u.x = v.x; } if(v.y < l.y){ l.y = v.y; } else if(v.y > u.y){ u.y = v.y; } if(v.z < l.z){ l.z = v.z; } else if(v.z > u.z){ u.z = v.z; } } }; /** * Update the .aabb property * @method updateAABB */ Trimesh.prototype.updateAABB = function(){ this.computeLocalAABB(this.aabb); }; /** * Will update the .boundingSphereRadius property * @method updateBoundingSphereRadius */ Trimesh.prototype.updateBoundingSphereRadius = function(){ // Assume points are distributed with local (0,0,0) as center var max2 = 0; var vertices = this.vertices; var v = new Vec3(); for(var i=0, N=vertices.length / 3; i !== N; i++) { this.getVertex(i, v); var norm2 = v.norm2(); if(norm2 > max2){ max2 = norm2; } } this.boundingSphereRadius = Math.sqrt(max2); }; var tempWorldVertex = new Vec3(); var calculateWorldAABB_frame = new Transform(); var calculateWorldAABB_aabb = new AABB(); /** * @method calculateWorldAABB * @param {Vec3} pos * @param {Quaternion} quat * @param {Vec3} min * @param {Vec3} max */ Trimesh.prototype.calculateWorldAABB = function(pos,quat,min,max){ /* var n = this.vertices.length / 3, verts = this.vertices; var minx,miny,minz,maxx,maxy,maxz; var v = tempWorldVertex; for(var i=0; i<n; i++){ this.getVertex(i, v); quat.vmult(v, v); pos.vadd(v, v); 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); */ // Faster approximation using local AABB var frame = calculateWorldAABB_frame; var result = calculateWorldAABB_aabb; frame.position = pos; frame.quaternion = quat; this.aabb.toWorldFrame(frame, result); min.copy(result.lowerBound); max.copy(result.upperBound); }; /** * Get approximate volume * @method volume * @return {Number} */ Trimesh.prototype.volume = function(){ return 4.0 * Math.PI * this.boundingSphereRadius / 3.0; }; /** * Create a Trimesh instance, shaped as a torus. * @static * @method createTorus * @param {number} [radius=1] * @param {number} [tube=0.5] * @param {number} [radialSegments=8] * @param {number} [tubularSegments=6] * @param {number} [arc=6.283185307179586] * @return {Trimesh} A torus */ Trimesh.createTorus = function (radius, tube, radialSegments, tubularSegments, arc) { radius = radius || 1; tube = tube || 0.5; radialSegments = radialSegments || 8; tubularSegments = tubularSegments || 6; arc = arc || Math.PI * 2; var vertices = []; var indices = []; for ( var j = 0; j <= radialSegments; j ++ ) { for ( var i = 0; i <= tubularSegments; i ++ ) { var u = i / tubularSegments * arc; var v = j / radialSegments * Math.PI * 2; var x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); var y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); var z = tube * Math.sin( v ); vertices.push( x, y, z ); } } for ( var j = 1; j <= radialSegments; j ++ ) { for ( var i = 1; i <= tubularSegments; i ++ ) { var a = ( tubularSegments + 1 ) * j + i - 1; var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; var c = ( tubularSegments + 1 ) * ( j - 1 ) + i; var d = ( tubularSegments + 1 ) * j + i; indices.push(a, b, d); indices.push(b, c, d); } } return new Trimesh(vertices, indices); };