Browse Source

PLYExporter: Honor data types of buffer attributes. (#33625)

Michael Herzog 1 month ago
parent
commit
a9a7f57f69
2 changed files with 192 additions and 77 deletions
  1. 181 72
      examples/jsm/exporters/PLYExporter.js
  2. 11 5
      examples/misc_exporter_ply.html

+ 181 - 72
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`.
  *

+ 11 - 5
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;

粤ICP备19079148号