diff --git a/examples/jsm/exporters/PLYExporter.js b/examples/jsm/exporters/PLYExporter.js index 22feb7d2a1b0f6..58f9af3206ef62 100644 --- a/examples/jsm/exporters/PLYExporter.js +++ b/examples/jsm/exporters/PLYExporter.js @@ -74,6 +74,13 @@ class PLYExporter { let includeColors = false; let includeUVs = false; + // derive types from the attribtue's typed array (the exporter assumes + // all attributes of the same group e.g. "position" share the same data type) + let positionType = 'float'; + let normalType = 'float'; + let uvType = 'float'; + let colorType = 'uchar'; + // count the vertices, check which properties are used, // and cache the BufferGeometry let vertexCount = 0; @@ -101,11 +108,28 @@ class PLYExporter { vertexCount += vertices.count; faceCount += indices ? indices.count / 3 : vertices.count / 3; - if ( normals !== undefined ) includeNormals = true; + positionType = getPlyType( vertices.array ); + + if ( normals !== undefined ) { + + includeNormals = true; + normalType = getPlyType( normals.array ); + + } + + if ( uvs !== undefined ) { - if ( uvs !== undefined ) includeUVs = true; + includeUVs = true; + uvType = getPlyType( uvs.array ); - if ( colors !== undefined ) includeColors = true; + } + + if ( colors !== undefined ) { + + includeColors = true; + colorType = getPlyType( colors.array ); + + } } else if ( child.isPoints ) { @@ -118,9 +142,21 @@ class PLYExporter { vertexCount += vertices.count; - if ( normals !== undefined ) includeNormals = true; + positionType = getPlyType( vertices.array ); + + if ( normals !== undefined ) { - if ( colors !== undefined ) includeColors = true; + includeNormals = true; + normalType = getPlyType( normals.array ); + + } + + if ( colors !== undefined ) { + + includeColors = true; + colorType = getPlyType( colors.array ); + + } includeIndices = false; @@ -159,17 +195,17 @@ class PLYExporter { `element vertex ${vertexCount}\n` + // position - 'property float x\n' + - 'property float y\n' + - 'property float z\n'; + `property ${positionType} x\n` + + `property ${positionType} y\n` + + `property ${positionType} z\n`; if ( includeNormals === true ) { // normal header += - 'property float nx\n' + - 'property float ny\n' + - 'property float nz\n'; + `property ${normalType} nx\n` + + `property ${normalType} ny\n` + + `property ${normalType} nz\n`; } @@ -177,8 +213,8 @@ class PLYExporter { // uvs header += - 'property float s\n' + - 'property float t\n'; + `property ${uvType} s\n` + + `property ${uvType} t\n`; } @@ -186,9 +222,9 @@ class PLYExporter { // colors header += - 'property uchar red\n' + - 'property uchar green\n' + - 'property uchar blue\n'; + `property ${colorType} red\n` + + `property ${colorType} green\n` + + `property ${colorType} blue\n`; } @@ -214,11 +250,19 @@ class PLYExporter { // Binary File Generation const 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 - const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); + const posWriter = getBinaryWriter( positionType ); + const normalWriter = includeNormals ? getBinaryWriter( normalType ) : null; + const uvWriter = includeUVs ? getBinaryWriter( uvType ) : null; + const colorWriter = includeColors ? getBinaryWriter( colorType ) : null; + const colorIsFloat = isFloatType( colorType ); + const colorScale = getColorScale( colorType ); + + const vertexListLength = vertexCount * ( + 3 * posWriter.size + + ( includeNormals ? 3 * normalWriter.size : 0 ) + + ( includeUVs ? 2 * uvWriter.size : 0 ) + + ( includeColors ? 3 * colorWriter.size : 0 ) + ); // 1 byte shape descriptor // 3 vertex indices at ${indexByteCount} bytes @@ -248,14 +292,14 @@ class PLYExporter { // Position information - output.setFloat32( vOffset, vertex.x, options.littleEndian ); - vOffset += 4; + posWriter.write( output, vOffset, vertex.x, options.littleEndian ); + vOffset += posWriter.size; - output.setFloat32( vOffset, vertex.y, options.littleEndian ); - vOffset += 4; + posWriter.write( output, vOffset, vertex.y, options.littleEndian ); + vOffset += posWriter.size; - output.setFloat32( vOffset, vertex.z, options.littleEndian ); - vOffset += 4; + posWriter.write( output, vOffset, vertex.z, options.littleEndian ); + vOffset += posWriter.size; // Normal information if ( includeNormals === true ) { @@ -266,25 +310,25 @@ class PLYExporter { vertex.applyMatrix3( normalMatrixWorld ).normalize(); - output.setFloat32( vOffset, vertex.x, options.littleEndian ); - vOffset += 4; + normalWriter.write( output, vOffset, vertex.x, options.littleEndian ); + vOffset += normalWriter.size; - output.setFloat32( vOffset, vertex.y, options.littleEndian ); - vOffset += 4; + normalWriter.write( output, vOffset, vertex.y, options.littleEndian ); + vOffset += normalWriter.size; - output.setFloat32( vOffset, vertex.z, options.littleEndian ); - vOffset += 4; + normalWriter.write( output, vOffset, vertex.z, options.littleEndian ); + vOffset += normalWriter.size; } else { - output.setFloat32( vOffset, 0, options.littleEndian ); - vOffset += 4; + normalWriter.write( output, vOffset, 0, options.littleEndian ); + vOffset += normalWriter.size; - output.setFloat32( vOffset, 0, options.littleEndian ); - vOffset += 4; + normalWriter.write( output, vOffset, 0, options.littleEndian ); + vOffset += normalWriter.size; - output.setFloat32( vOffset, 0, options.littleEndian ); - vOffset += 4; + normalWriter.write( output, vOffset, 0, options.littleEndian ); + vOffset += normalWriter.size; } @@ -295,19 +339,19 @@ class PLYExporter { if ( uvs != null ) { - output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian ); - vOffset += 4; + uvWriter.write( output, vOffset, uvs.getX( i ), options.littleEndian ); + vOffset += uvWriter.size; - output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian ); - vOffset += 4; + uvWriter.write( output, vOffset, uvs.getY( i ), options.littleEndian ); + vOffset += uvWriter.size; } else { - output.setFloat32( vOffset, 0, options.littleEndian ); - vOffset += 4; + uvWriter.write( output, vOffset, 0, options.littleEndian ); + vOffset += uvWriter.size; - output.setFloat32( vOffset, 0, options.littleEndian ); - vOffset += 4; + uvWriter.write( output, vOffset, 0, options.littleEndian ); + vOffset += uvWriter.size; } @@ -322,25 +366,31 @@ class PLYExporter { ColorManagement.workingToColorSpace( tempColor, SRGBColorSpace ); - output.setUint8( vOffset, Math.floor( tempColor.r * 255 ) ); - vOffset += 1; + const r = colorIsFloat ? tempColor.r : Math.round( tempColor.r * colorScale ); + const g = colorIsFloat ? tempColor.g : Math.round( tempColor.g * colorScale ); + const b = colorIsFloat ? tempColor.b : Math.round( tempColor.b * colorScale ); + + colorWriter.write( output, vOffset, r, options.littleEndian ); + vOffset += colorWriter.size; - output.setUint8( vOffset, Math.floor( tempColor.g * 255 ) ); - vOffset += 1; + colorWriter.write( output, vOffset, g, options.littleEndian ); + vOffset += colorWriter.size; - output.setUint8( vOffset, Math.floor( tempColor.b * 255 ) ); - vOffset += 1; + colorWriter.write( output, vOffset, b, options.littleEndian ); + vOffset += colorWriter.size; } else { - output.setUint8( vOffset, 255 ); - vOffset += 1; + const white = colorIsFloat ? 1 : colorScale; + + colorWriter.write( output, vOffset, white, options.littleEndian ); + vOffset += colorWriter.size; - output.setUint8( vOffset, 255 ); - vOffset += 1; + colorWriter.write( output, vOffset, white, options.littleEndian ); + vOffset += colorWriter.size; - output.setUint8( vOffset, 255 ); - vOffset += 1; + colorWriter.write( output, vOffset, white, options.littleEndian ); + vOffset += colorWriter.size; } @@ -409,6 +459,14 @@ class PLYExporter { let vertexList = ''; let faceList = ''; + const positionIsFloat = isFloatType( positionType ); + const normalIsFloat = isFloatType( normalType ); + const uvIsFloat = isFloatType( uvType ); + const colorIsFloat = isFloatType( colorType ); + const colorScale = getColorScale( colorType ); + + const encode = ( v, isFloat ) => isFloat ? v : Math.round( v ); + traverseMeshes( function ( mesh, geometry ) { const vertices = geometry.getAttribute( 'position' ); @@ -429,9 +487,9 @@ class PLYExporter { // Position information let line = - vertex.x + ' ' + - vertex.y + ' ' + - vertex.z; + encode( vertex.x, positionIsFloat ) + ' ' + + encode( vertex.y, positionIsFloat ) + ' ' + + encode( vertex.z, positionIsFloat ); // Normal information if ( includeNormals === true ) { @@ -443,9 +501,9 @@ class PLYExporter { vertex.applyMatrix3( normalMatrixWorld ).normalize(); line += ' ' + - vertex.x + ' ' + - vertex.y + ' ' + - vertex.z; + encode( vertex.x, normalIsFloat ) + ' ' + + encode( vertex.y, normalIsFloat ) + ' ' + + encode( vertex.z, normalIsFloat ); } else { @@ -461,8 +519,8 @@ class PLYExporter { if ( uvs != null ) { line += ' ' + - uvs.getX( i ) + ' ' + - uvs.getY( i ); + encode( uvs.getX( i ), uvIsFloat ) + ' ' + + encode( uvs.getY( i ), uvIsFloat ); } else { @@ -481,14 +539,16 @@ class PLYExporter { ColorManagement.workingToColorSpace( tempColor, SRGBColorSpace ); - line += ' ' + - Math.floor( tempColor.r * 255 ) + ' ' + - Math.floor( tempColor.g * 255 ) + ' ' + - Math.floor( tempColor.b * 255 ); + const r = colorIsFloat ? tempColor.r : Math.round( tempColor.r * colorScale ); + const g = colorIsFloat ? tempColor.g : Math.round( tempColor.g * colorScale ); + const b = colorIsFloat ? tempColor.b : Math.round( tempColor.b * colorScale ); + + line += ` ${r} ${g} ${b}`; } else { - line += ' 255 255 255'; + const white = colorIsFloat ? 1 : colorScale; + line += ` ${white} ${white} ${white}`; } @@ -541,6 +601,55 @@ class PLYExporter { } +function getPlyType( array ) { + + if ( array instanceof Int8Array ) return 'char'; + if ( array instanceof Uint8Array || array instanceof Uint8ClampedArray ) return 'uchar'; + if ( array instanceof Int16Array ) return 'short'; + if ( array instanceof Uint16Array ) return 'ushort'; + if ( array instanceof Int32Array ) return 'int'; + if ( array instanceof Uint32Array ) return 'uint'; + if ( array instanceof Float32Array ) return 'float'; + if ( array instanceof Float64Array ) return 'double'; + return 'float'; + +} + +function getBinaryWriter( type ) { + + switch ( type ) { + + case 'char': return { write: ( dv, at, v ) => dv.setInt8( at, v ), size: 1 }; + case 'uchar': return { write: ( dv, at, v ) => dv.setUint8( at, v ), size: 1 }; + case 'short': return { write: ( dv, at, v, le ) => dv.setInt16( at, v, le ), size: 2 }; + case 'ushort': return { write: ( dv, at, v, le ) => dv.setUint16( at, v, le ), size: 2 }; + case 'int': return { write: ( dv, at, v, le ) => dv.setInt32( at, v, le ), size: 4 }; + case 'uint': return { write: ( dv, at, v, le ) => dv.setUint32( at, v, le ), size: 4 }; + case 'float': return { write: ( dv, at, v, le ) => dv.setFloat32( at, v, le ), size: 4 }; + case 'double': return { write: ( dv, at, v, le ) => dv.setFloat64( at, v, le ), size: 8 }; + + } + +} + +function isFloatType( type ) { + + return type === 'float' || type === 'double'; + +} + +function getColorScale( type ) { + + switch ( type ) { + + case 'uchar': return 255; + case 'ushort': return 65535; + default: return 1; + + } + +} + /** * Export options of `PLYExporter`. * diff --git a/examples/misc_exporter_ply.html b/examples/misc_exporter_ply.html index dcb0712a045000..983a5615d73018 100644 --- a/examples/misc_exporter_ply.html +++ b/examples/misc_exporter_ply.html @@ -86,15 +86,21 @@ const material = new THREE.MeshPhongMaterial( { vertexColors: true } ); // color vertices based on vertex positions - const colors = geometry.getAttribute( 'position' ).array.slice(); - for ( let i = 0, l = colors.length; i < l; i ++ ) { + const positions = geometry.getAttribute( 'position' ); + const colors = new THREE.Uint8BufferAttribute( positions.array, 3, true ); + const vertex = new THREE.Vector3(); + + for ( let i = 0, l = positions.count; i < l; i ++ ) { - if ( colors[ i ] > 0 ) colors[ i ] = 0.5; - else colors[ i ] = 0; + vertex.fromBufferAttribute( positions, i ); + + colors.setX( i, vertex.x > 0 ? 0.5 : 0 ); + colors.setY( i, vertex.y > 0 ? 0.5 : 0 ); + colors.setZ( i, vertex.z > 0 ? 0.5 : 0 ); } - geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3, false ) ); + geometry.setAttribute( 'color', colors ); mesh = new THREE.Mesh( geometry, material ); mesh.castShadow = true;