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);
};