mirror of
https://github.com/wahyd4/three.js.git
synced 2026-08-22 03:15:56 +10:00
If you want to refresh VBOs (and thus have geometry changes reflected in renderer), you need to set dirty flags on geometry object.
There are separate flags for different buffers (as not always all buffers need to be updated and oh my is updating costly):
mesh.geometry.__dirtyVertices = true;
mesh.geometry.__dirtyNormals = true;
mesh.geometry.__dirtyUvs = true;
mesh.geometry.__dirtyTangents = true;
mesh.geometry.__dirtyElements = true;
That was quite tough feature, a lot of refactoring, yet performance is still quite bad :(
The biggest remaining bottleneck seems to be translation between Three.js internal data formats and buffers. I removed as much per-frame arrays creation as I could, but even just iterating through existing data and setting of values is still very costly. Also per-frame normals computation is expensive.
468 lines
10 KiB
JavaScript
468 lines
10 KiB
JavaScript
/**
|
|
* @author mr.doob / http://mrdoob.com/
|
|
* @author kile / http://kile.stravaganza.org/
|
|
* @author alteredq / http://alteredqualia.com/
|
|
*/
|
|
|
|
THREE.Geometry = function () {
|
|
|
|
this.vertices = [];
|
|
this.faces = [];
|
|
this.uvs = [];
|
|
|
|
this.boundingBox = null;
|
|
this.boundingSphere = null;
|
|
|
|
this.geometryChunks = {};
|
|
|
|
this.hasTangents = false;
|
|
|
|
};
|
|
|
|
THREE.Geometry.prototype = {
|
|
|
|
computeCentroids: function () {
|
|
|
|
var f, fl, face;
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
face.centroid.set( 0, 0, 0 );
|
|
|
|
if ( face instanceof THREE.Face3 ) {
|
|
|
|
face.centroid.addSelf( this.vertices[ face.a ].position );
|
|
face.centroid.addSelf( this.vertices[ face.b ].position );
|
|
face.centroid.addSelf( this.vertices[ face.c ].position );
|
|
face.centroid.divideScalar( 3 );
|
|
|
|
} else if ( face instanceof THREE.Face4 ) {
|
|
|
|
face.centroid.addSelf( this.vertices[ face.a ].position );
|
|
face.centroid.addSelf( this.vertices[ face.b ].position );
|
|
face.centroid.addSelf( this.vertices[ face.c ].position );
|
|
face.centroid.addSelf( this.vertices[ face.d ].position );
|
|
face.centroid.divideScalar( 4 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computeFaceNormals: function ( useVertexNormals ) {
|
|
|
|
var n, nl, v, vl, vertex, f, fl, face, vA, vB, vC,
|
|
cb = new THREE.Vector3(), ab = new THREE.Vector3();
|
|
|
|
for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
vertex = this.vertices[ v ];
|
|
vertex.normal.set( 0, 0, 0 );
|
|
|
|
}
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
|
|
if ( useVertexNormals && face.vertexNormals.length ) {
|
|
|
|
cb.set( 0, 0, 0 );
|
|
|
|
for ( n = 0, nl = face.normal.length; n < nl; n++ ) {
|
|
|
|
cb.addSelf( face.vertexNormals[n] );
|
|
|
|
}
|
|
|
|
cb.divideScalar( 3 );
|
|
|
|
if ( ! cb.isZero() ) {
|
|
|
|
cb.normalize();
|
|
|
|
}
|
|
|
|
face.normal.copy( cb );
|
|
|
|
} else {
|
|
|
|
vA = this.vertices[ face.a ];
|
|
vB = this.vertices[ face.b ];
|
|
vC = this.vertices[ face.c ];
|
|
|
|
cb.sub( vC.position, vB.position );
|
|
ab.sub( vA.position, vB.position );
|
|
cb.crossSelf( ab );
|
|
|
|
if ( !cb.isZero() ) {
|
|
|
|
cb.normalize();
|
|
|
|
}
|
|
|
|
face.normal.copy( cb );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computeVertexNormals: function () {
|
|
|
|
var v, vl, f, fl, face, vertices;
|
|
|
|
// create internal buffers for reuse when calling this method repeatedly
|
|
// (otherwise memory allocation / deallocation every frame is big resource hog)
|
|
|
|
if ( this.__tmpVertices == undefined ) {
|
|
|
|
this.__tmpVertices = new Array( this.vertices.length );
|
|
vertices = this.__tmpVertices;
|
|
|
|
for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
vertices[ v ] = new THREE.Vector3();
|
|
|
|
}
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
|
|
if ( face instanceof THREE.Face3 ) {
|
|
|
|
face.vertexNormals = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
|
|
|
|
} else if ( face instanceof THREE.Face4 ) {
|
|
|
|
face.vertexNormals = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vertices = this.__tmpVertices;
|
|
|
|
for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
vertices[ v ].set( 0, 0, 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
|
|
if ( face instanceof THREE.Face3 ) {
|
|
|
|
vertices[ face.a ].addSelf( face.normal );
|
|
vertices[ face.b ].addSelf( face.normal );
|
|
vertices[ face.c ].addSelf( face.normal );
|
|
|
|
} else if ( face instanceof THREE.Face4 ) {
|
|
|
|
vertices[ face.a ].addSelf( face.normal );
|
|
vertices[ face.b ].addSelf( face.normal );
|
|
vertices[ face.c ].addSelf( face.normal );
|
|
vertices[ face.d ].addSelf( face.normal );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
vertices[ v ].normalize();
|
|
|
|
}
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
|
|
if ( face instanceof THREE.Face3 ) {
|
|
|
|
face.vertexNormals[ 0 ].copy( vertices[ face.a ] );
|
|
face.vertexNormals[ 1 ].copy( vertices[ face.b ] );
|
|
face.vertexNormals[ 2 ].copy( vertices[ face.c ] );
|
|
|
|
} else if ( face instanceof THREE.Face4 ) {
|
|
|
|
face.vertexNormals[ 0 ].copy( vertices[ face.a ] );
|
|
face.vertexNormals[ 1 ].copy( vertices[ face.b ] );
|
|
face.vertexNormals[ 2 ].copy( vertices[ face.c ] );
|
|
face.vertexNormals[ 3 ].copy( vertices[ face.d ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computeTangents: function() {
|
|
|
|
// based on http://www.terathon.com/code/tangent.html
|
|
// tangents go to vertices
|
|
|
|
var f, fl, v, vl, face, uv, vA, vB, vC, uvA, uvB, uvC,
|
|
x1, x2, y1, y2, z1, z2,
|
|
s1, s2, t1, t2, r, t, test,
|
|
tan1 = [], tan2 = [],
|
|
sdir = new THREE.Vector3(), tdir = new THREE.Vector3(),
|
|
tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(),
|
|
n = new THREE.Vector3(), w;
|
|
|
|
for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
tan1[ v ] = new THREE.Vector3();
|
|
tan2[ v ] = new THREE.Vector3();
|
|
|
|
}
|
|
|
|
function handleTriangle( context, a, b, c, ua, ub, uc ) {
|
|
|
|
vA = context.vertices[ a ].position;
|
|
vB = context.vertices[ b ].position;
|
|
vC = context.vertices[ c ].position;
|
|
|
|
uvA = uv[ ua ];
|
|
uvB = uv[ ub ];
|
|
uvC = uv[ uc ];
|
|
|
|
x1 = vB.x - vA.x;
|
|
x2 = vC.x - vA.x;
|
|
y1 = vB.y - vA.y;
|
|
y2 = vC.y - vA.y;
|
|
z1 = vB.z - vA.z;
|
|
z2 = vC.z - vA.z;
|
|
|
|
s1 = uvB.u - uvA.u;
|
|
s2 = uvC.u - uvA.u;
|
|
t1 = uvB.v - uvA.v;
|
|
t2 = uvC.v - uvA.v;
|
|
|
|
r = 1.0 / ( s1 * t2 - s2 * t1 );
|
|
sdir.set( ( t2 * x1 - t1 * x2 ) * r,
|
|
( t2 * y1 - t1 * y2 ) * r,
|
|
( t2 * z1 - t1 * z2 ) * r );
|
|
tdir.set( ( s1 * x2 - s2 * x1 ) * r,
|
|
( s1 * y2 - s2 * y1 ) * r,
|
|
( s1 * z2 - s2 * z1 ) * r );
|
|
|
|
tan1[ a ].addSelf( sdir );
|
|
tan1[ b ].addSelf( sdir );
|
|
tan1[ c ].addSelf( sdir );
|
|
|
|
tan2[ a ].addSelf( tdir );
|
|
tan2[ b ].addSelf( tdir );
|
|
tan2[ c ].addSelf( tdir );
|
|
|
|
}
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
uv = this.uvs[ f ];
|
|
|
|
if ( face instanceof THREE.Face3 ) {
|
|
|
|
handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 );
|
|
|
|
this.vertices[ face.a ].normal.copy( face.vertexNormals[ 0 ] );
|
|
this.vertices[ face.b ].normal.copy( face.vertexNormals[ 1 ] );
|
|
this.vertices[ face.c ].normal.copy( face.vertexNormals[ 2 ] );
|
|
|
|
|
|
} else if ( face instanceof THREE.Face4 ) {
|
|
|
|
handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 );
|
|
handleTriangle( this, face.a, face.b, face.d, 0, 1, 3 );
|
|
|
|
this.vertices[ face.a ].normal.copy( face.vertexNormals[ 0 ] );
|
|
this.vertices[ face.b ].normal.copy( face.vertexNormals[ 1 ] );
|
|
this.vertices[ face.c ].normal.copy( face.vertexNormals[ 2 ] );
|
|
this.vertices[ face.d ].normal.copy( face.vertexNormals[ 3 ] );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
n.copy( this.vertices[ v ].normal );
|
|
t = tan1[ v ];
|
|
|
|
// Gram-Schmidt orthogonalize
|
|
|
|
tmp.copy( t );
|
|
tmp.subSelf( n.multiplyScalar( n.dot( t ) ) ).normalize();
|
|
|
|
// Calculate handedness
|
|
|
|
tmp2.cross( this.vertices[ v ].normal, t );
|
|
test = tmp2.dot( tan2[ v ] );
|
|
w = (test < 0.0) ? -1.0 : 1.0;
|
|
|
|
this.vertices[ v ].tangent.set( tmp.x, tmp.y, tmp.z, w );
|
|
|
|
}
|
|
|
|
this.hasTangents = true;
|
|
|
|
},
|
|
|
|
computeBoundingBox: function () {
|
|
|
|
var vertex;
|
|
|
|
if ( this.vertices.length > 0 ) {
|
|
|
|
this.boundingBox = { 'x': [ this.vertices[ 0 ].position.x, this.vertices[ 0 ].position.x ],
|
|
'y': [ this.vertices[ 0 ].position.y, this.vertices[ 0 ].position.y ],
|
|
'z': [ this.vertices[ 0 ].position.z, this.vertices[ 0 ].position.z ] };
|
|
|
|
for ( var v = 1, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
vertex = this.vertices[ v ];
|
|
|
|
if ( vertex.position.x < this.boundingBox.x[ 0 ] ) {
|
|
|
|
this.boundingBox.x[ 0 ] = vertex.position.x;
|
|
|
|
} else if ( vertex.position.x > this.boundingBox.x[ 1 ] ) {
|
|
|
|
this.boundingBox.x[ 1 ] = vertex.position.x;
|
|
|
|
}
|
|
|
|
if ( vertex.position.y < this.boundingBox.y[ 0 ] ) {
|
|
|
|
this.boundingBox.y[ 0 ] = vertex.position.y;
|
|
|
|
} else if ( vertex.position.y > this.boundingBox.y[ 1 ] ) {
|
|
|
|
this.boundingBox.y[ 1 ] = vertex.position.y;
|
|
|
|
}
|
|
|
|
if ( vertex.position.z < this.boundingBox.z[ 0 ] ) {
|
|
|
|
this.boundingBox.z[ 0 ] = vertex.position.z;
|
|
|
|
} else if ( vertex.position.z > this.boundingBox.z[ 1 ] ) {
|
|
|
|
this.boundingBox.z[ 1 ] = vertex.position.z;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computeBoundingSphere: function () {
|
|
|
|
var radius = this.boundingSphere === null ? 0 : this.boundingSphere.radius;
|
|
|
|
for ( var v = 0, vl = this.vertices.length; v < vl; v ++ ) {
|
|
|
|
radius = Math.max( radius, this.vertices[ v ].position.length() );
|
|
|
|
}
|
|
|
|
this.boundingSphere = { radius: radius };
|
|
|
|
},
|
|
|
|
sortFacesByMaterial: function () {
|
|
|
|
// TODO
|
|
// Should optimize by grouping faces with ColorFill / ColorStroke materials
|
|
// which could then use vertex color attributes instead of each being
|
|
// in its separate VBO
|
|
|
|
var i, l, f, fl, face, material, materials, vertices, mhash, ghash, hash_map = {};
|
|
|
|
function materialHash( material ) {
|
|
|
|
var hash_array = [];
|
|
|
|
for ( i = 0, l = material.length; i < l; i++ ) {
|
|
|
|
if ( material[ i ] == undefined ) {
|
|
|
|
hash_array.push( "undefined" );
|
|
|
|
} else {
|
|
|
|
hash_array.push( material[ i ].toString() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return hash_array.join( '_' );
|
|
|
|
}
|
|
|
|
for ( f = 0, fl = this.faces.length; f < fl; f++ ) {
|
|
|
|
face = this.faces[ f ];
|
|
materials = face.materials;
|
|
|
|
mhash = materialHash( materials );
|
|
|
|
if ( hash_map[ mhash ] == undefined ) {
|
|
|
|
hash_map[ mhash ] = { 'hash': mhash, 'counter': 0 };
|
|
|
|
}
|
|
|
|
ghash = hash_map[ mhash ].hash + '_' + hash_map[ mhash ].counter;
|
|
|
|
if ( this.geometryChunks[ ghash ] == undefined ) {
|
|
|
|
this.geometryChunks[ ghash ] = { 'faces': [], 'materials': materials, 'vertices': 0 };
|
|
|
|
}
|
|
|
|
vertices = face instanceof THREE.Face3 ? 3 : 4;
|
|
|
|
if ( this.geometryChunks[ ghash ].vertices + vertices > 65535 ) {
|
|
|
|
hash_map[ mhash ].counter += 1;
|
|
ghash = hash_map[ mhash ].hash + '_' + hash_map[ mhash ].counter;
|
|
|
|
if ( this.geometryChunks[ ghash ] == undefined ) {
|
|
|
|
this.geometryChunks[ ghash ] = { 'faces': [], 'materials': materials, 'vertices': 0 };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.geometryChunks[ ghash ].faces.push( f );
|
|
this.geometryChunks[ ghash ].vertices += vertices;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
toString: function () {
|
|
|
|
return 'THREE.Geometry ( vertices: ' + this.vertices + ', faces: ' + this.faces + ', uvs: ' + this.uvs + ' )';
|
|
|
|
}
|
|
|
|
};
|