/** * @author mrdoob / http://mrdoob.com/ * @author alteredq / http://alteredqualia.com/ */ THREE.GeometryUtils = { // Merge two geometries or geometry and geometry from object (using object's transform) merge: function ( geometry1, object2 /* mesh | geometry */ ) { var matrix, normalMatrix, vertexOffset = geometry1.vertices.length, uvPosition = geometry1.faceVertexUvs[ 0 ].length, geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2, vertices1 = geometry1.vertices, vertices2 = geometry2.vertices, faces1 = geometry1.faces, faces2 = geometry2.faces, uvs1 = geometry1.faceVertexUvs[ 0 ], uvs2 = geometry2.faceVertexUvs[ 0 ]; if ( object2 instanceof THREE.Mesh ) { object2.matrixAutoUpdate && object2.updateMatrix(); matrix = object2.matrix; normalMatrix = new THREE.Matrix3(); normalMatrix.getInverse( matrix ); normalMatrix.transpose(); } // vertices for ( var i = 0, il = vertices2.length; i < il; i ++ ) { var vertex = vertices2[ i ]; var vertexCopy = vertex.clone(); if ( matrix ) vertexCopy.applyMatrix4( matrix ); vertices1.push( vertexCopy ); } // faces for ( i = 0, il = faces2.length; i < il; i ++ ) { var face = faces2[ i ], faceCopy, normal, color, faceVertexNormals = face.vertexNormals, faceVertexColors = face.vertexColors; if ( face instanceof THREE.Face3 ) { faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); } else if ( face instanceof THREE.Face4 ) { faceCopy = new THREE.Face4( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset, face.d + vertexOffset ); } faceCopy.normal.copy( face.normal ); if ( normalMatrix ) { faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); } for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { normal = faceVertexNormals[ j ].clone(); if ( normalMatrix ) { normal.applyMatrix3( normalMatrix ).normalize(); } faceCopy.vertexNormals.push( normal ); } faceCopy.color.copy( face.color ); for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { color = faceVertexColors[ j ]; faceCopy.vertexColors.push( color.clone() ); } faceCopy.materialIndex = face.materialIndex; faceCopy.centroid.copy( face.centroid ); if ( matrix ) { faceCopy.centroid.applyMatrix4( matrix ); } faces1.push( faceCopy ); } // uvs for ( i = 0, il = uvs2.length; i < il; i ++ ) { var uv = uvs2[ i ], uvCopy = []; for ( var j = 0, jl = uv.length; j < jl; j ++ ) { uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) ); } uvs1.push( uvCopy ); } }, removeMaterials: function ( geometry, materialIndexArray ) { var materialIndexMap = {}; for ( var i = 0, il = materialIndexArray.length; i < il; i ++ ) { materialIndexMap[ materialIndexArray[i] ] = true; } var face, newFaces = []; for ( var i = 0, il = geometry.faces.length; i < il; i ++ ) { face = geometry.faces[ i ]; if ( ! ( face.materialIndex in materialIndexMap ) ) newFaces.push( face ); } geometry.faces = newFaces; }, // Get random point in triangle (via barycentric coordinates) // (uniform distribution) // http://www.cgafaq.info/wiki/Random_Point_In_Triangle randomPointInTriangle: function ( vectorA, vectorB, vectorC ) { var a, b, c, point = new THREE.Vector3(), tmp = THREE.GeometryUtils.__v1; a = THREE.GeometryUtils.random(); b = THREE.GeometryUtils.random(); if ( ( a + b ) > 1 ) { a = 1 - a; b = 1 - b; } c = 1 - a - b; point.copy( vectorA ); point.multiplyScalar( a ); tmp.copy( vectorB ); tmp.multiplyScalar( b ); point.add( tmp ); tmp.copy( vectorC ); tmp.multiplyScalar( c ); point.add( tmp ); return point; }, // Get random point in face (triangle / quad) // (uniform distribution) randomPointInFace: function ( face, geometry, useCachedAreas ) { var vA, vB, vC, vD; if ( face instanceof THREE.Face3 ) { vA = geometry.vertices[ face.a ]; vB = geometry.vertices[ face.b ]; vC = geometry.vertices[ face.c ]; return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC ); } else if ( face instanceof THREE.Face4 ) { vA = geometry.vertices[ face.a ]; vB = geometry.vertices[ face.b ]; vC = geometry.vertices[ face.c ]; vD = geometry.vertices[ face.d ]; var area1, area2; if ( useCachedAreas ) { if ( face._area1 && face._area2 ) { area1 = face._area1; area2 = face._area2; } else { area1 = THREE.GeometryUtils.triangleArea( vA, vB, vD ); area2 = THREE.GeometryUtils.triangleArea( vB, vC, vD ); face._area1 = area1; face._area2 = area2; } } else { area1 = THREE.GeometryUtils.triangleArea( vA, vB, vD ), area2 = THREE.GeometryUtils.triangleArea( vB, vC, vD ); } var r = THREE.GeometryUtils.random() * ( area1 + area2 ); if ( r < area1 ) { return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vD ); } else { return THREE.GeometryUtils.randomPointInTriangle( vB, vC, vD ); } } }, // Get uniformly distributed random points in mesh // - create array with cumulative sums of face areas // - pick random number from 0 to total area // - find corresponding place in area array by binary search // - get random point in face randomPointsInGeometry: function ( geometry, n ) { var face, i, faces = geometry.faces, vertices = geometry.vertices, il = faces.length, totalArea = 0, cumulativeAreas = [], vA, vB, vC, vD; // precompute face areas for ( i = 0; i < il; i ++ ) { face = faces[ i ]; if ( face instanceof THREE.Face3 ) { vA = vertices[ face.a ]; vB = vertices[ face.b ]; vC = vertices[ face.c ]; face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC ); } else if ( face instanceof THREE.Face4 ) { vA = vertices[ face.a ]; vB = vertices[ face.b ]; vC = vertices[ face.c ]; vD = vertices[ face.d ]; face._area1 = THREE.GeometryUtils.triangleArea( vA, vB, vD ); face._area2 = THREE.GeometryUtils.triangleArea( vB, vC, vD ); face._area = face._area1 + face._area2; } totalArea += face._area; cumulativeAreas[ i ] = totalArea; } // binary search cumulative areas array function binarySearchIndices( value ) { function binarySearch( start, end ) { // return closest larger index // if exact number is not found if ( end < start ) return start; var mid = start + Math.floor( ( end - start ) / 2 ); if ( cumulativeAreas[ mid ] > value ) { return binarySearch( start, mid - 1 ); } else if ( cumulativeAreas[ mid ] < value ) { return binarySearch( mid + 1, end ); } else { return mid; } } var result = binarySearch( 0, cumulativeAreas.length - 1 ) return result; } // pick random face weighted by face area var r, index, result = []; var stats = {}; for ( i = 0; i < n; i ++ ) { r = THREE.GeometryUtils.random() * totalArea; index = binarySearchIndices( r ); result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true ); if ( ! stats[ index ] ) { stats[ index ] = 1; } else { stats[ index ] += 1; } } return result; }, // Get triangle area (half of parallelogram) // http://mathworld.wolfram.com/TriangleArea.html triangleArea: function ( vectorA, vectorB, vectorC ) { var tmp1 = THREE.GeometryUtils.__v1, tmp2 = THREE.GeometryUtils.__v2; tmp1.subVectors( vectorB, vectorA ); tmp2.subVectors( vectorC, vectorA ); tmp1.cross( tmp2 ); return 0.5 * tmp1.length(); }, // Center geometry so that 0,0,0 is in center of bounding box center: function ( geometry ) { geometry.computeBoundingBox(); var bb = geometry.boundingBox; var offset = new THREE.Vector3(); offset.addVectors( bb.min, bb.max ); offset.multiplyScalar( -0.5 ); geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset ) ); geometry.computeBoundingBox(); return offset; }, // Normalize UVs to be from <0,1> // (for now just the first set of UVs) normalizeUVs: function ( geometry ) { var uvSet = geometry.faceVertexUvs[ 0 ]; for ( var i = 0, il = uvSet.length; i < il; i ++ ) { var uvs = uvSet[ i ]; for ( var j = 0, jl = uvs.length; j < jl; j ++ ) { // texture repeat if( uvs[ j ].x !== 1.0 ) uvs[ j ].x = uvs[ j ].x - Math.floor( uvs[ j ].x ); if( uvs[ j ].y !== 1.0 ) uvs[ j ].y = uvs[ j ].y - Math.floor( uvs[ j ].y ); } } }, triangulateQuads: function ( geometry ) { var i, il, j, jl; var faces = []; var faceUvs = []; var faceVertexUvs = []; for ( i = 0, il = geometry.faceUvs.length; i < il; i ++ ) { faceUvs[ i ] = []; } for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) { faceVertexUvs[ i ] = []; } for ( i = 0, il = geometry.faces.length; i < il; i ++ ) { var face = geometry.faces[ i ]; if ( face instanceof THREE.Face4 ) { var a = face.a; var b = face.b; var c = face.c; var d = face.d; var triA = new THREE.Face3(); var triB = new THREE.Face3(); triA.color.copy( face.color ); triB.color.copy( face.color ); triA.materialIndex = face.materialIndex; triB.materialIndex = face.materialIndex; triA.a = a; triA.b = b; triA.c = d; triB.a = b; triB.b = c; triB.c = d; if ( face.vertexColors.length === 4 ) { triA.vertexColors[ 0 ] = face.vertexColors[ 0 ].clone(); triA.vertexColors[ 1 ] = face.vertexColors[ 1 ].clone(); triA.vertexColors[ 2 ] = face.vertexColors[ 3 ].clone(); triB.vertexColors[ 0 ] = face.vertexColors[ 1 ].clone(); triB.vertexColors[ 1 ] = face.vertexColors[ 2 ].clone(); triB.vertexColors[ 2 ] = face.vertexColors[ 3 ].clone(); } faces.push( triA, triB ); for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) { if ( geometry.faceVertexUvs[ j ].length ) { var uvs = geometry.faceVertexUvs[ j ][ i ]; var uvA = uvs[ 0 ]; var uvB = uvs[ 1 ]; var uvC = uvs[ 2 ]; var uvD = uvs[ 3 ]; var uvsTriA = [ uvA.clone(), uvB.clone(), uvD.clone() ]; var uvsTriB = [ uvB.clone(), uvC.clone(), uvD.clone() ]; faceVertexUvs[ j ].push( uvsTriA, uvsTriB ); } } for ( j = 0, jl = geometry.faceUvs.length; j < jl; j ++ ) { if ( geometry.faceUvs[ j ].length ) { var faceUv = geometry.faceUvs[ j ][ i ]; faceUvs[ j ].push( faceUv, faceUv ); } } } else { faces.push( face ); for ( j = 0, jl = geometry.faceUvs.length; j < jl; j ++ ) { faceUvs[ j ].push( geometry.faceUvs[ j ][ i ] ); } for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) { faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] ); } } } geometry.faces = faces; geometry.faceUvs = faceUvs; geometry.faceVertexUvs = faceVertexUvs; geometry.computeCentroids(); geometry.computeFaceNormals(); geometry.computeVertexNormals(); if ( geometry.hasTangents ) geometry.computeTangents(); }, setMaterialIndex: function ( geometry, index, startFace, endFace ){ var faces = geometry.faces; var start = startFace || 0; var end = endFace || faces.length - 1; for ( var i = start; i <= end; i ++ ) { faces[i].materialIndex = index; } } }; THREE.GeometryUtils.random = THREE.Math.random16; THREE.GeometryUtils.__v1 = new THREE.Vector3(); THREE.GeometryUtils.__v2 = new THREE.Vector3();