Spaces:
Running
Running
| /** | |
| * @author Garrett Johnson / http://gkjohnson.github.io/ | |
| * https://github.com/gkjohnson/ply-exporter-js | |
| * | |
| * Usage: | |
| * var exporter = new THREE.PLYExporter(); | |
| * | |
| * // second argument is a list of options | |
| * exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ] }); | |
| * | |
| * Format Definition: | |
| * http://paulbourke.net/dataformats/ply/ | |
| */ | |
| THREE.PLYExporter = function () {}; | |
| THREE.PLYExporter.prototype = { | |
| constructor: THREE.PLYExporter, | |
| parse: function ( object, onDone, options ) { | |
| if ( onDone && typeof onDone === 'object' ) { | |
| console.warn( 'THREE.PLYExporter: The options parameter is now the third argument to the "parse" function. See the documentation for the new API.' ); | |
| options = onDone; | |
| onDone = undefined; | |
| } | |
| // Iterate over the valid meshes in the object | |
| function traverseMeshes( cb ) { | |
| object.traverse( function ( child ) { | |
| if ( child.isMesh === true ) { | |
| var mesh = child; | |
| var geometry = mesh.geometry; | |
| if ( geometry.isGeometry === true ) { | |
| geometry = geomToBufferGeom.get( geometry ); | |
| } | |
| if ( geometry.isBufferGeometry === true ) { | |
| if ( geometry.getAttribute( 'position' ) !== undefined ) { | |
| cb( mesh, geometry ); | |
| } | |
| } | |
| } | |
| } ); | |
| } | |
| // Default options | |
| var defaultOptions = { | |
| binary: false, | |
| excludeAttributes: [] // normal, uv, color, index | |
| }; | |
| options = Object.assign( defaultOptions, options ); | |
| var excludeAttributes = options.excludeAttributes; | |
| var geomToBufferGeom = new WeakMap(); | |
| var includeNormals = false; | |
| var includeColors = false; | |
| var includeUVs = false; | |
| // count the vertices, check which properties are used, | |
| // and cache the BufferGeometry | |
| var vertexCount = 0; | |
| var faceCount = 0; | |
| object.traverse( function ( child ) { | |
| if ( child.isMesh === true ) { | |
| var mesh = child; | |
| var geometry = mesh.geometry; | |
| if ( geometry.isGeometry === true ) { | |
| var bufferGeometry = geomToBufferGeom.get( geometry ) || new THREE.BufferGeometry().setFromObject( mesh ); | |
| geomToBufferGeom.set( geometry, bufferGeometry ); | |
| geometry = bufferGeometry; | |
| } | |
| if ( geometry.isBufferGeometry === true ) { | |
| var vertices = geometry.getAttribute( 'position' ); | |
| var normals = geometry.getAttribute( 'normal' ); | |
| var uvs = geometry.getAttribute( 'uv' ); | |
| var colors = geometry.getAttribute( 'color' ); | |
| var indices = geometry.getIndex(); | |
| if ( vertices === undefined ) { | |
| return; | |
| } | |
| vertexCount += vertices.count; | |
| faceCount += indices ? indices.count / 3 : vertices.count / 3; | |
| if ( normals !== undefined ) includeNormals = true; | |
| if ( uvs !== undefined ) includeUVs = true; | |
| if ( colors !== undefined ) includeColors = true; | |
| } | |
| } | |
| } ); | |
| var includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; | |
| includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1; | |
| includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1; | |
| includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1; | |
| if ( includeIndices && faceCount !== Math.floor( faceCount ) ) { | |
| // point cloud meshes will not have an index array and may not have a | |
| // number of vertices that is divisble by 3 (and therefore representable | |
| // as triangles) | |
| console.error( | |
| 'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' + | |
| 'number of indices is not divisible by 3.' | |
| ); | |
| return null; | |
| } | |
| // get how many bytes will be needed to save out the faces | |
| // so we can use a minimal amount of memory / data | |
| var indexByteCount = 1; | |
| if ( vertexCount > 256 ) { // 2^8 bits | |
| indexByteCount = 2; | |
| } | |
| if ( vertexCount > 65536 ) { // 2^16 bits | |
| indexByteCount = 4; | |
| } | |
| var header = | |
| 'ply\n' + | |
| `format ${ options.binary ? 'binary_big_endian' : 'ascii' } 1.0\n` + | |
| `element vertex ${vertexCount}\n` + | |
| // position | |
| 'property float x\n' + | |
| 'property float y\n' + | |
| 'property float z\n'; | |
| if ( includeNormals === true ) { | |
| // normal | |
| header += | |
| 'property float nx\n' + | |
| 'property float ny\n' + | |
| 'property float nz\n'; | |
| } | |
| if ( includeUVs === true ) { | |
| // uvs | |
| header += | |
| 'property float s\n' + | |
| 'property float t\n'; | |
| } | |
| if ( includeColors === true ) { | |
| // colors | |
| header += | |
| 'property uchar red\n' + | |
| 'property uchar green\n' + | |
| 'property uchar blue\n'; | |
| } | |
| if ( includeIndices === true ) { | |
| // faces | |
| header += | |
| `element face ${faceCount}\n` + | |
| `property list uchar uint${ indexByteCount * 8 } vertex_index\n`; | |
| } | |
| header += 'end_header\n'; | |
| // Generate attribute data | |
| var vertex = new THREE.Vector3(); | |
| var normalMatrixWorld = new THREE.Matrix3(); | |
| var result = null; | |
| if ( options.binary === true ) { | |
| // Binary File Generation | |
| var headerBin = new TextEncoder().encode( header ); | |
| // 3 position values at 4 bytes | |
| // 3 normal values at 4 bytes | |
| // 3 color channels with 1 byte | |
| // 2 uv values at 4 bytes | |
| var vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); | |
| // 1 byte shape desciptor | |
| // 3 vertex indices at ${indexByteCount} bytes | |
| var faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; | |
| var output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); | |
| new Uint8Array( output.buffer ).set( headerBin, 0 ); | |
| var vOffset = headerBin.length; | |
| var fOffset = headerBin.length + vertexListLength; | |
| var writtenVertices = 0; | |
| traverseMeshes( function ( mesh, geometry ) { | |
| var vertices = geometry.getAttribute( 'position' ); | |
| var normals = geometry.getAttribute( 'normal' ); | |
| var uvs = geometry.getAttribute( 'uv' ); | |
| var colors = geometry.getAttribute( 'color' ); | |
| var indices = geometry.getIndex(); | |
| normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); | |
| for ( var i = 0, l = vertices.count; i < l; i ++ ) { | |
| vertex.x = vertices.getX( i ); | |
| vertex.y = vertices.getY( i ); | |
| vertex.z = vertices.getZ( i ); | |
| vertex.applyMatrix4( mesh.matrixWorld ); | |
| // Position information | |
| output.setFloat32( vOffset, vertex.x ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, vertex.y ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, vertex.z ); | |
| vOffset += 4; | |
| // Normal information | |
| if ( includeNormals === true ) { | |
| if ( normals != null ) { | |
| vertex.x = normals.getX( i ); | |
| vertex.y = normals.getY( i ); | |
| vertex.z = normals.getZ( i ); | |
| vertex.applyMatrix3( normalMatrixWorld ); | |
| output.setFloat32( vOffset, vertex.x ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, vertex.y ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, vertex.z ); | |
| vOffset += 4; | |
| } else { | |
| output.setFloat32( vOffset, 0 ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, 0 ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, 0 ); | |
| vOffset += 4; | |
| } | |
| } | |
| // UV information | |
| if ( includeUVs === true ) { | |
| if ( uvs != null ) { | |
| output.setFloat32( vOffset, uvs.getX( i ) ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, uvs.getY( i ) ); | |
| vOffset += 4; | |
| } else if ( includeUVs !== false ) { | |
| output.setFloat32( vOffset, 0 ); | |
| vOffset += 4; | |
| output.setFloat32( vOffset, 0 ); | |
| vOffset += 4; | |
| } | |
| } | |
| // Color information | |
| if ( includeColors === true ) { | |
| if ( colors != null ) { | |
| output.setUint8( vOffset, Math.floor( colors.getX( i ) * 255 ) ); | |
| vOffset += 1; | |
| output.setUint8( vOffset, Math.floor( colors.getY( i ) * 255 ) ); | |
| vOffset += 1; | |
| output.setUint8( vOffset, Math.floor( colors.getZ( i ) * 255 ) ); | |
| vOffset += 1; | |
| } else { | |
| output.setUint8( vOffset, 255 ); | |
| vOffset += 1; | |
| output.setUint8( vOffset, 255 ); | |
| vOffset += 1; | |
| output.setUint8( vOffset, 255 ); | |
| vOffset += 1; | |
| } | |
| } | |
| } | |
| if ( includeIndices === true ) { | |
| // Create the face list | |
| var faceIndexFunc = `setUint${indexByteCount * 8}`; | |
| if ( indices !== null ) { | |
| for ( var i = 0, l = indices.count; i < l; i += 3 ) { | |
| output.setUint8( fOffset, 3 ); | |
| fOffset += 1; | |
| output[ faceIndexFunc ]( fOffset, indices.getX( i + 0 ) + writtenVertices ); | |
| fOffset += indexByteCount; | |
| output[ faceIndexFunc ]( fOffset, indices.getX( i + 1 ) + writtenVertices ); | |
| fOffset += indexByteCount; | |
| output[ faceIndexFunc ]( fOffset, indices.getX( i + 2 ) + writtenVertices ); | |
| fOffset += indexByteCount; | |
| } | |
| } else { | |
| for ( var i = 0, l = vertices.count; i < l; i += 3 ) { | |
| output.setUint8( fOffset, 3 ); | |
| fOffset += 1; | |
| output[ faceIndexFunc ]( fOffset, writtenVertices + i ); | |
| fOffset += indexByteCount; | |
| output[ faceIndexFunc ]( fOffset, writtenVertices + i + 1 ); | |
| fOffset += indexByteCount; | |
| output[ faceIndexFunc ]( fOffset, writtenVertices + i + 2 ); | |
| fOffset += indexByteCount; | |
| } | |
| } | |
| } | |
| // Save the amount of verts we've already written so we can offset | |
| // the face index on the next mesh | |
| writtenVertices += vertices.count; | |
| } ); | |
| result = output.buffer; | |
| } else { | |
| // Ascii File Generation | |
| // count the number of vertices | |
| var writtenVertices = 0; | |
| var vertexList = ''; | |
| var faceList = ''; | |
| traverseMeshes( function ( mesh, geometry ) { | |
| var vertices = geometry.getAttribute( 'position' ); | |
| var normals = geometry.getAttribute( 'normal' ); | |
| var uvs = geometry.getAttribute( 'uv' ); | |
| var colors = geometry.getAttribute( 'color' ); | |
| var indices = geometry.getIndex(); | |
| normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); | |
| // form each line | |
| for ( var i = 0, l = vertices.count; i < l; i ++ ) { | |
| vertex.x = vertices.getX( i ); | |
| vertex.y = vertices.getY( i ); | |
| vertex.z = vertices.getZ( i ); | |
| vertex.applyMatrix4( mesh.matrixWorld ); | |
| // Position information | |
| var line = | |
| vertex.x + ' ' + | |
| vertex.y + ' ' + | |
| vertex.z; | |
| // Normal information | |
| if ( includeNormals === true ) { | |
| if ( normals != null ) { | |
| vertex.x = normals.getX( i ); | |
| vertex.y = normals.getY( i ); | |
| vertex.z = normals.getZ( i ); | |
| vertex.applyMatrix3( normalMatrixWorld ); | |
| line += ' ' + | |
| vertex.x + ' ' + | |
| vertex.y + ' ' + | |
| vertex.z; | |
| } else { | |
| line += ' 0 0 0'; | |
| } | |
| } | |
| // UV information | |
| if ( includeUVs === true ) { | |
| if ( uvs != null ) { | |
| line += ' ' + | |
| uvs.getX( i ) + ' ' + | |
| uvs.getY( i ); | |
| } else if ( includeUVs !== false ) { | |
| line += ' 0 0'; | |
| } | |
| } | |
| // Color information | |
| if ( includeColors === true ) { | |
| if ( colors != null ) { | |
| line += ' ' + | |
| Math.floor( colors.getX( i ) * 255 ) + ' ' + | |
| Math.floor( colors.getY( i ) * 255 ) + ' ' + | |
| Math.floor( colors.getZ( i ) * 255 ); | |
| } else { | |
| line += ' 255 255 255'; | |
| } | |
| } | |
| vertexList += line + '\n'; | |
| } | |
| // Create the face list | |
| if ( includeIndices === true ) { | |
| if ( indices !== null ) { | |
| for ( var i = 0, l = indices.count; i < l; i += 3 ) { | |
| faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`; | |
| faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`; | |
| faceList += ` ${ indices.getX( i + 2 ) + writtenVertices }\n`; | |
| } | |
| } else { | |
| for ( var i = 0, l = vertices.count; i < l; i += 3 ) { | |
| faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`; | |
| } | |
| } | |
| faceCount += indices ? indices.count / 3 : vertices.count / 3; | |
| } | |
| writtenVertices += vertices.count; | |
| } ); | |
| result = `${ header }${vertexList}\n${ includeIndices ? `${faceList}\n` : '' }`; | |
| } | |
| if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) ); | |
| return result; | |
| } | |
| }; | |