From f534dc256b6e476d6424c0564ea93dbafd3dad8f Mon Sep 17 00:00:00 2001 From: Fred Ludlow Date: Fri, 30 Aug 2024 15:04:49 +0100 Subject: [PATCH 1/2] Reinstate the PLY parser --- src/ngl.ts | 1 + src/parser/ply-parser.ts | 896 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 897 insertions(+) create mode 100644 src/parser/ply-parser.ts diff --git a/src/ngl.ts b/src/ngl.ts index 3807e39d..ab941c51 100644 --- a/src/ngl.ts +++ b/src/ngl.ts @@ -155,6 +155,7 @@ import './parser/xplor-parser' import './parser/kin-parser' import './parser/obj-parser' +import './parser/ply-parser' import './parser/csv-parser' import './parser/json-parser' diff --git a/src/parser/ply-parser.ts b/src/parser/ply-parser.ts new file mode 100644 index 00000000..be39cf48 --- /dev/null +++ b/src/parser/ply-parser.ts @@ -0,0 +1,896 @@ +import SurfaceParser from "./surface-parser" +import { ParserRegistry } from "../globals" +import { BufferGeometry, Color, Float32BufferAttribute } from "three" + +/** + * Port of PLYLoader from the MIT-licensed three.js project: + * https://github.com/mrdoob/three.js/blob/97b5d428d598228cae9b206d9a321f18d53a3e86/examples/jsm/loaders/PLYLoader.js + * + * The original code has been modified to work with NGL and TypeScript. + * Adaptation by @fredludlow + * + * Description: A THREE loader for PLY ASCII files (known as the Polygon + * File Format or the Stanford Triangle Format). + * + * Limitations: ASCII decoding assumes file is UTF-8. + * + * Usage: + * const loader = new PLYLoader(); + * loader.load('./models/ply/ascii/dolphins.ply', function (geometry) { + * + * scene.add( new THREE.Mesh( geometry ) ); + * + * } ); + * + * If the PLY file uses non standard property names, they can be mapped while + * loading. For example, the following maps the properties + * “diffuse_(red|green|blue)” in the file to standard color names. + * + * loader.setPropertyNameMapping( { + * diffuse_red: 'red', + * diffuse_green: 'green', + * diffuse_blue: 'blue' + * } ); + * + * Custom properties outside of the defaults for position, uv, normal + * and color attributes can be added using the setCustomPropertyNameMapping method. + * For example, the following maps the element properties “custom_property_a” + * and “custom_property_b” to an attribute “customAttribute” with an item size of 2. + * Attribute item sizes are set from the number of element properties in the property array. + * + * loader.setCustomPropertyNameMapping( { + * customAttribute: ['custom_property_a', 'custom_property_b'], + * } ); + * + */ + +const dataTypes = [ + 'int8', 'char', 'uint8', 'uchar', + 'int16', 'short', 'uint16', 'ushort', + 'int32', 'int', 'uint32', 'uint', + 'float32', 'float', 'float64', 'double' +] as const; + +// Extract the literal union type from the array +type DataType = (typeof dataTypes)[number]; + +// Define a function that checks if the string is a valid DataType +function assertDataType(type: string): type is DataType { + return dataTypes.includes(type as DataType); +} +// Wrapper function to check and return the DataType +function getDataType(type: string): DataType { + if (assertDataType(type)) { + return type; + } + throw new Error(`Unsupported data type: ${type}`); +} + +interface HeaderText { + headerText: string, + headerLength: number +} + +interface BasePLYProperty { + type: DataType; + name: string; + valueReader?: BinaryReader; +} + +interface SinglePLYProperty extends BasePLYProperty{ + isList: false; // Discriminant with a fixed value +} + +interface BinarySinglePLYProperty extends SinglePLYProperty { + valueReader: BinaryReader; +} + +interface ListPLYProperty extends BasePLYProperty { + isList: true; // Discriminant with a different fixed value + countType: DataType; + countReader?: BinaryReader; +} + +interface BinaryListPLYProperty extends ListPLYProperty { + countReader: BinaryReader; + valueReader: BinaryReader; +} + +type PLYProperty = SinglePLYProperty | ListPLYProperty; + +type BinaryPLYProperty = BinarySinglePLYProperty | BinaryListPLYProperty; + +interface PLYElement { + name: string, + count: number, + properties: PLYProperty[], + x: number, + y: number, + z: number, + red: number, + green: number, + blue: number, + [k:string]: any +} + +type PLYElementSpec = Pick + +function assertPLYElementSpec (element: Partial): asserts element is PLYElementSpec { + if (typeof element !== 'object' || element === null) { + throw new Error('Expected element to be an object') + } + if (typeof element.name !== 'string') { + throw new Error('Expected element.name to be a string') + } + if (typeof element.count !== 'number') { + throw new Error('Expected element.count to be a number') + } + if (!Array.isArray(element.properties)) { + throw new Error('Expected element.properties to be an array') + } +} + +// function assertPLYElement (element: Partial): asserts element is PLYElement { +// assertPLYElementSpec(element as PLYElementSpec) +// if (typeof element.x !== 'number') { +// throw new Error('Expected element.x to be a number') +// } +// if (typeof element.y !== 'number') { +// throw new Error('Expected element.y to be a number') +// } +// if (typeof element.z !== 'number') { +// throw new Error('Expected element.z to be a number') +// } +// if (typeof element.red !== 'number') { +// throw new Error('Expected element.red to be a number') +// } +// if (typeof element.green !== 'number') { +// throw new Error('Expected element.green to be a number') +// } +// if (typeof element.blue !== 'number') { +// throw new Error('Expected element.blue to be a number') +// } + +// } + +interface PLYHeader { + format: string, + version: string, + comments: string[], + elements: PLYElementSpec[], + headerLength: number, + objInfo: string +} + +/** JS object that we generate the buffer from */ +interface PLYBuffer { + indices: number[], + vertices: number[], + normals: number[], + uvs: number[], + faceVertexUvs: number[], + colors: number[], + faceVertexColors: number[], + [k: string]: number[] +} + +// Define a type for the reader function that will return either a number or a double (for float64) +type BinaryReader = { + read: (at: number) => number; + size: number; +}; + +function getBinaryReader(dataview: DataView, type: DataType, little_endian: boolean): BinaryReader { + switch (type) { + case 'int8': + case 'char': + return { + read: (at: number) => dataview.getInt8(at), + size: 1 + }; + + case 'uint8': + case 'uchar': + return { + read: (at: number) => dataview.getUint8(at), + size: 1 + }; + + case 'int16': + case 'short': + return { + read: (at: number) => dataview.getInt16(at, little_endian), + size: 2 + }; + + case 'uint16': + case 'ushort': + return { + read: (at: number) => dataview.getUint16(at, little_endian), + size: 2 + }; + + case 'int32': + case 'int': + return { + read: (at: number) => dataview.getInt32(at, little_endian), + size: 4 + }; + + case 'uint32': + case 'uint': + return { + read: (at: number) => dataview.getUint32(at, little_endian), + size: 4 + }; + + case 'float32': + case 'float': + return { + read: (at: number) => dataview.getFloat32(at, little_endian), + size: 4 + }; + + case 'float64': + case 'double': + return { + read: (at: number) => dataview.getFloat64(at, little_endian), + size: 8 + }; + + default: + throw new Error(`Unsupported data type: ${type}`); + } +} + +function binaryReadElement( at: number, properties: BinaryPLYProperty[] ): [PLYElement, number] { + + const element: Partial = {}; + let read = 0; + + for ( let i = 0; i < properties.length; i ++ ) { + + const property = properties[ i ]; + const valueReader = property.valueReader; + + if ( property.isList ) { + + const list = []; + + const n = property.countReader.read( at + read ); + read += property.countReader.size; + + for ( let j = 0; j < n; j ++ ) { + + list.push( valueReader.read( at + read ) ); + read += valueReader.size; + + } + + element[ property.name ] = list; + + } else { + + element[ property.name ] = valueReader.read( at + read ); + read += valueReader.size; + + } + + } + + // assertPLYElement(element) + + return [ element as PLYElement, read ]; // TODO: We're sloppy about what types are actually available in PLYElement + +} + +class PLYLoader { + propertyNameMapping: {[k: string]: string} + customPropertyMapping: {[k: string]: string} + _color: Color = new Color() + + constructor () { + this.propertyNameMapping = {} + this.customPropertyMapping = {} + } + + setPropertyNameMapping (mapping: {[k: string]: string}) { + this.propertyNameMapping = mapping + } + + createBuffer(): PLYBuffer { + + const buffer: PLYBuffer = { + indices: [], + vertices: [], + normals: [], + uvs: [], + faceVertexUvs: [], + colors: [], + faceVertexColors: [] + }; + + for ( const customProperty of Object.keys( this.customPropertyMapping ) ) { + + buffer[ customProperty ] = []; + + } + + return buffer; + + } + + extractHeaderText (bytes: Uint8Array): HeaderText { + let i = 0; + let cont = true; + + let line = ''; + const lines = []; + + const startLine = new TextDecoder().decode( bytes.subarray( 0, 5 ) ); + const hasCRNL = /^ply\r\n/.test( startLine ); + + do { + + const c = String.fromCharCode( bytes[ i ++ ] ); + + if ( c !== '\n' && c !== '\r' ) { + + line += c; + + } else { + + if ( line === 'end_header' ) cont = false; + if ( line !== '' ) { + + lines.push( line ); + line = ''; + + } + + } + + } while ( cont && i < bytes.length ); + + // ascii section using \r\n as line endings + if ( hasCRNL === true ) i ++; + + return { headerText: lines.join( '\r' ) + '\r', headerLength: i }; + + } + + handleElement( buffer: PLYBuffer, elementName: string, element: PLYElement, cacheEntry: MappedAttributes ) { + + if ( elementName === 'vertex' ) { + + buffer.vertices.push( element[ cacheEntry.attrX ], element[ cacheEntry.attrY ], element[ cacheEntry.attrZ ] ); + + if ( cacheEntry.attrNX !== null && cacheEntry.attrNY !== null && cacheEntry.attrNZ !== null ) { + + buffer.normals.push( element[ cacheEntry.attrNX ], element[ cacheEntry.attrNY ], element[ cacheEntry.attrNZ ] ); + + } + + if ( cacheEntry.attrS !== null && cacheEntry.attrT !== null ) { + + buffer.uvs.push( element[ cacheEntry.attrS ], element[ cacheEntry.attrT ] ); + + } + + if ( cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null ) { + + this._color.setRGB( + element[ cacheEntry.attrR ] / 255.0, + element[ cacheEntry.attrG ] / 255.0, + element[ cacheEntry.attrB ] / 255.0 + ).convertSRGBToLinear(); + + buffer.colors.push( this._color.r, this._color.g, this._color.b ); + + } + + for ( const customProperty of Object.keys( this.customPropertyMapping ) ) { + + for ( const elementProperty of this.customPropertyMapping[ customProperty ] ) { + + buffer[ customProperty ].push( element[ elementProperty ] ); + + } + + } + + } else if ( elementName === 'face' ) { + + const vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338 + const texcoord = element.texcoord; + + if ( vertex_indices.length === 3 ) { + + buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] ); + + if ( texcoord && texcoord.length === 6 ) { + + buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] ); + buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] ); + buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] ); + + } + + } else if ( vertex_indices.length === 4 ) { + + buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] ); + buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] ); + + } + + // face colors + + if ( cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null ) { + + this._color.setRGB( + element[ cacheEntry.attrR ] / 255.0, + element[ cacheEntry.attrG ] / 255.0, + element[ cacheEntry.attrB ] / 255.0 + ).convertSRGBToLinear(); + buffer.faceVertexColors.push( this._color.r, this._color.g, this._color.b ); + buffer.faceVertexColors.push( this._color.r, this._color.g, this._color.b ); + buffer.faceVertexColors.push( this._color.r, this._color.g, this._color.b ); + + } + + } + + } + + + parse (data: string|ArrayBuffer): BufferGeometry { + let geometry: BufferGeometry + if (data instanceof ArrayBuffer) { + const bytes = new Uint8Array(data) + const { headerText, headerLength } = this.extractHeaderText( bytes ) + const header = this.parseHeader(headerText, headerLength) + if (header.format === 'ascii') { + const text = new TextDecoder().decode( bytes ) + geometry = this.parseASCII(text, header) + } else { + geometry = this.parseBinary( bytes, header ) + } + + } else { + geometry = this.parseASCII(data, this.parseHeader(data) ) + } + + return geometry + } + + parseHeader (data: string, headerLength: number = 0): PLYHeader { + const patternHeader = /^ply([\s\S]*)end_header(\r\n|\r|\n)/; + let headerText = ''; + const result = patternHeader.exec( data ); + + if ( result !== null ) { + + headerText = result[ 1 ]; + + } + + const header: PLYHeader = { + comments: [], + elements: [], + headerLength: headerLength, + objInfo: '', + format: '', + version: '' + }; + + const lines = headerText.split( /\r\n|\r|\n/ ); + let currentElement: PLYElementSpec|undefined; + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + + if ( line === '' ) continue; + + const lineValues = line.split( /\s+/ ); + const lineType = lineValues.shift(); + line = lineValues.join( ' ' ); + + switch ( lineType ) { + + case 'format': + + header.format = lineValues[ 0 ]; + header.version = lineValues[ 1 ]; + + break; + + case 'comment': + + header.comments.push( line ); + + break; + + case 'element': + + if ( currentElement !== undefined ) { + assertPLYElementSpec(currentElement) + header.elements.push(currentElement) + } + + currentElement = { + name: lineValues[ 0 ], + count: parseInt( lineValues[ 1 ] ), + properties: [], + } + + break + + case 'property': + + if ( currentElement === undefined ) { + + console.warn( 'property without element' ); + continue; + + } + currentElement.properties.push( makePlyPropertySpec( lineValues, this.propertyNameMapping ) ); + + break; + + case 'obj_info': + + header.objInfo = line; + + break; + + + default: + + console.log( 'unhandled', lineType, lineValues ); + + } + + } + + if ( currentElement !== undefined ) { + + header.elements.push( currentElement ); + + } + + return header; + + } + + parseASCII( data: string, header: PLYHeader ): BufferGeometry { + // PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format) + + const buffer = this.createBuffer(); + + const patternBody = /end_header\s+(\S[\s\S]*\S|\S)\s*$/; + let body: string[], matches; + + if ( ( matches = patternBody.exec( data ) ) !== null ) { + + body = matches[ 1 ].split( /\s+/ ); + + } else { + + body = [ ]; + + } + + const tokens = new ArrayStream( body ); + + loop: for ( let i = 0; i < header.elements.length; i ++ ) { + + const elementDesc = header.elements[ i ]; + const attributeMap: MappedAttributes = mapElementAttributes( elementDesc.properties ); + + for ( let j = 0; j < elementDesc.count; j ++ ) { + + // TODO - we're cooercing element to PLYElement inside this call + // handle properly! + const element = parseASCIIElement( elementDesc.properties, tokens ); + + if ( ! element ) break loop; + + this.handleElement( buffer, elementDesc.name, element, attributeMap ); + + } + + } + + return this.postProcess( buffer ); + } + + parseBinary( data: Uint8Array, header: PLYHeader ): BufferGeometry { + const buffer = this.createBuffer(); + + const little_endian = ( header.format === 'binary_little_endian' ); + const body = new DataView( data, header.headerLength ); + let result, loc = 0; + + for ( let currentElement = 0; currentElement < header.elements.length; currentElement ++ ) { + + const elementDesc = header.elements[ currentElement ]; + const properties = elementDesc.properties; + const attributeMap: MappedAttributes = mapElementAttributes( properties ); + + const binaryProperties = this.makeBinaryProperties( properties, body, little_endian ); + + for ( let currentElementCount = 0; currentElementCount < elementDesc.count; currentElementCount ++ ) { + + result = binaryReadElement( loc, binaryProperties ); + loc += result[ 1 ]; + const element = result[ 0 ]; + + this.handleElement( buffer, elementDesc.name, element, attributeMap ); + + } + + } + + return this.postProcess( buffer ); + } + + postProcess( buffer: PLYBuffer ): BufferGeometry { + + let geometry = new BufferGeometry(); + + // mandatory buffer data + + if ( buffer.indices.length > 0 ) { + + geometry.setIndex( buffer.indices ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( buffer.vertices, 3 ) ); + + // optional buffer data + + if ( buffer.normals.length > 0 ) { + + geometry.setAttribute( 'normal', new Float32BufferAttribute( buffer.normals, 3 ) ); + + } + + if ( buffer.uvs.length > 0 ) { + + geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.uvs, 2 ) ); + + } + + if ( buffer.colors.length > 0 ) { + + geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) ); + + } + + if ( buffer.faceVertexUvs.length > 0 || buffer.faceVertexColors.length > 0 ) { + + geometry = geometry.toNonIndexed(); + + if ( buffer.faceVertexUvs.length > 0 ) geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) ); + if ( buffer.faceVertexColors.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.faceVertexColors, 3 ) ); + + } + + // custom buffer data + + for ( const customProperty of Object.keys( this.customPropertyMapping ) ) { + + if ( buffer[ customProperty ].length > 0 ) { + + geometry.setAttribute( + customProperty, + new Float32BufferAttribute( + buffer[ customProperty ], + this.customPropertyMapping[ customProperty ].length + ) + ); + + } + + } + + geometry.computeBoundingSphere(); + + return geometry; + + } + + /** Augments properties with their appropriate valueReader attribute and + * countReader if the property is a list type + */ + makeBinaryProperties( properties: PLYProperty[], body: DataView, little_endian: boolean ): BinaryPLYProperty[] { + const newProperties: BinaryPLYProperty[] = [] + + for ( let i = 0, l = properties.length; i < l; i ++ ) { + + const property = properties[ i ]; + + if (property.isList) { + + newProperties.push({...property, + countReader: getBinaryReader( body, property.countType, little_endian ), + valueReader: getBinaryReader( body, property.type, little_endian ) + }) + } else { + + newProperties.push({ + ...property, + valueReader: getBinaryReader( body, property.type, little_endian ) + }) + } + } + return newProperties + } + +} + + + +function makePlyPropertySpec( propertyValues: string[], propertyNameMapping: {[k: string]: string} ): PLYProperty { + + const prop0 = propertyValues[ 0 ]; + + let property: PLYProperty; + + if (prop0 === 'list') { + property = { + isList: true, + name: propertyValues[3], + type: getDataType(propertyValues[2]), + countType: getDataType(propertyValues[1]), + }; + } else { + property = { + isList: false, + name: propertyValues[1], + type: getDataType(prop0) + }; + } + + if (property.name in propertyNameMapping) { + property.name = propertyNameMapping[property.name]; + } + + return property; +} + +function parseASCIINumber (n: string, type: string): number { + switch (type) { + case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint': + case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32': + + return parseInt(n) + + case 'float': case 'double': case 'float32': case 'float64': + + return parseFloat(n) + } + return 0 +} + +function parseASCIIElement( properties: PLYProperty[], tokens: ArrayStream ): PLYElement | null { + + const element: Partial = { + name: '', + type: '' + }; + + for ( let i = 0; i < properties.length; i ++ ) { + + if ( tokens.empty() ) return null; + + const property = properties[ i ] + + if ( property.isList ) { + + const list = []; + const n = parseASCIINumber( tokens.next(), property.countType ); + + for ( let j = 0; j < n; j ++ ) { + + if ( tokens.empty() ) return null; + + list.push( parseASCIINumber( tokens.next(), property.type ) ); + + } + + element[ property.name ] = list; + + } else { + + element[ properties[ i ].name ] = parseASCIINumber( tokens.next(), properties[ i ].type ); + + } + + } + + return element as PLYElement // TODO: Properly handle types here + +} + +function mapElementAttributes( properties: PLYProperty[] ) { + + const elementNames = properties.map( property => { + + return property.name; + + } ); + + function findAttrName( names: string[] ) { + + for ( let i = 0, l = names.length; i < l; i ++ ) { + + const name = names[ i ]; + + if ( elementNames.includes( name ) ) return name; + + } + + return null; + + } + + return { + attrX: findAttrName( [ 'x', 'px', 'posx' ] ) || 'x', + attrY: findAttrName( [ 'y', 'py', 'posy' ] ) || 'y', + attrZ: findAttrName( [ 'z', 'pz', 'posz' ] ) || 'z', + attrNX: findAttrName( [ 'nx', 'normalx' ] ), + attrNY: findAttrName( [ 'ny', 'normaly' ] ), + attrNZ: findAttrName( [ 'nz', 'normalz' ] ), + attrS: findAttrName( [ 's', 'u', 'texture_u', 'tx' ] ), + attrT: findAttrName( [ 't', 'v', 'texture_v', 'ty' ] ), + attrR: findAttrName( [ 'red', 'diffuse_red', 'r', 'diffuse_r' ] ), + attrG: findAttrName( [ 'green', 'diffuse_green', 'g', 'diffuse_g' ] ), + attrB: findAttrName( [ 'blue', 'diffuse_blue', 'b', 'diffuse_b' ] ), + }; + +} + +type MappedAttributes = ReturnType + + +class PlyParser extends SurfaceParser { + get type () { return 'ply' } + + getLoader () { + return new PLYLoader() + } +} + +class ArrayStream { + arr: Array + i: number + + constructor( arr: Array ) { + + this.arr = arr; + this.i = 0; + + } + + empty() { + + return this.i >= this.arr.length; + + } + + next() { + + return this.arr[ this.i ++ ]; + + } + +} + + +ParserRegistry.add('ply', PlyParser) + +export default PlyParser From 45efded1112562e2477a471b7be41c1512a35b10 Mon Sep 17 00:00:00 2001 From: Fred Ludlow Date: Sat, 31 Aug 2024 09:46:28 +0100 Subject: [PATCH 2/2] ply-parser tidying: Better names for type guards. Autoformat --- src/parser/ply-parser.ts | 408 ++++++++++++++++++++------------------- 1 file changed, 205 insertions(+), 203 deletions(-) diff --git a/src/parser/ply-parser.ts b/src/parser/ply-parser.ts index be39cf48..1e88ed9a 100644 --- a/src/parser/ply-parser.ts +++ b/src/parser/ply-parser.ts @@ -45,9 +45,9 @@ import { BufferGeometry, Color, Float32BufferAttribute } from "three" */ const dataTypes = [ - 'int8', 'char', 'uint8', 'uchar', - 'int16', 'short', 'uint16', 'ushort', - 'int32', 'int', 'uint32', 'uint', + 'int8', 'char', 'uint8', 'uchar', + 'int16', 'short', 'uint16', 'ushort', + 'int32', 'int', 'uint32', 'uint', 'float32', 'float', 'float64', 'double' ] as const; @@ -55,12 +55,13 @@ const dataTypes = [ type DataType = (typeof dataTypes)[number]; // Define a function that checks if the string is a valid DataType -function assertDataType(type: string): type is DataType { +function is(type: string): type is DataType { return dataTypes.includes(type as DataType); } + // Wrapper function to check and return the DataType function getDataType(type: string): DataType { - if (assertDataType(type)) { + if (is(type)) { return type; } throw new Error(`Unsupported data type: ${type}`); @@ -77,7 +78,7 @@ interface BasePLYProperty { valueReader?: BinaryReader; } -interface SinglePLYProperty extends BasePLYProperty{ +interface SinglePLYProperty extends BasePLYProperty { isList: false; // Discriminant with a fixed value } @@ -85,7 +86,7 @@ interface BinarySinglePLYProperty extends SinglePLYProperty { valueReader: BinaryReader; } -interface ListPLYProperty extends BasePLYProperty { +interface ListPLYProperty extends BasePLYProperty { isList: true; // Discriminant with a different fixed value countType: DataType; countReader?: BinaryReader; @@ -110,12 +111,12 @@ interface PLYElement { red: number, green: number, blue: number, - [k:string]: any + [k: string]: any } -type PLYElementSpec = Pick +type PLYElementSpec = Pick -function assertPLYElementSpec (element: Partial): asserts element is PLYElementSpec { +function assertPLYElementSpec(element: Partial): asserts element is PLYElementSpec { if (typeof element !== 'object' || element === null) { throw new Error('Expected element to be an object') } @@ -150,7 +151,7 @@ function assertPLYElementSpec (element: Partial): asserts element is // if (typeof element.blue !== 'number') { // throw new Error('Expected element.blue to be a number') // } - + // } interface PLYHeader { @@ -188,7 +189,7 @@ function getBinaryReader(dataview: DataView, type: DataType, little_endian: bool read: (at: number) => dataview.getInt8(at), size: 1 }; - + case 'uint8': case 'uchar': return { @@ -243,35 +244,35 @@ function getBinaryReader(dataview: DataView, type: DataType, little_endian: bool } } -function binaryReadElement( at: number, properties: BinaryPLYProperty[] ): [PLYElement, number] { +function binaryReadElement(at: number, properties: BinaryPLYProperty[]): [PLYElement, number] { const element: Partial = {}; let read = 0; - for ( let i = 0; i < properties.length; i ++ ) { + for (let i = 0; i < properties.length; i++) { - const property = properties[ i ]; + const property = properties[i]; const valueReader = property.valueReader; - if ( property.isList ) { + if (property.isList) { const list = []; - const n = property.countReader.read( at + read ); + const n = property.countReader.read(at + read); read += property.countReader.size; - for ( let j = 0; j < n; j ++ ) { + for (let j = 0; j < n; j++) { - list.push( valueReader.read( at + read ) ); + list.push(valueReader.read(at + read)); read += valueReader.size; } - element[ property.name ] = list; + element[property.name] = list; } else { - element[ property.name ] = valueReader.read( at + read ); + element[property.name] = valueReader.read(at + read); read += valueReader.size; } @@ -280,21 +281,21 @@ function binaryReadElement( at: number, properties: BinaryPLYProperty[] ): [PLYE // assertPLYElement(element) - return [ element as PLYElement, read ]; // TODO: We're sloppy about what types are actually available in PLYElement + return [element as PLYElement, read]; // TODO: We're sloppy about what types are actually available in PLYElement } class PLYLoader { - propertyNameMapping: {[k: string]: string} - customPropertyMapping: {[k: string]: string} + propertyNameMapping: { [k: string]: string } + customPropertyMapping: { [k: string]: string } _color: Color = new Color() - constructor () { + constructor() { this.propertyNameMapping = {} this.customPropertyMapping = {} } - setPropertyNameMapping (mapping: {[k: string]: string}) { + setPropertyNameMapping(mapping: { [k: string]: string }) { this.propertyNameMapping = mapping } @@ -310,9 +311,9 @@ class PLYLoader { faceVertexColors: [] }; - for ( const customProperty of Object.keys( this.customPropertyMapping ) ) { + for (const customProperty of Object.keys(this.customPropertyMapping)) { - buffer[ customProperty ] = []; + buffer[customProperty] = []; } @@ -320,121 +321,121 @@ class PLYLoader { } - extractHeaderText (bytes: Uint8Array): HeaderText { + extractHeaderText(bytes: Uint8Array): HeaderText { let i = 0; let cont = true; let line = ''; const lines = []; - const startLine = new TextDecoder().decode( bytes.subarray( 0, 5 ) ); - const hasCRNL = /^ply\r\n/.test( startLine ); + const startLine = new TextDecoder().decode(bytes.subarray(0, 5)); + const hasCRNL = /^ply\r\n/.test(startLine); do { - const c = String.fromCharCode( bytes[ i ++ ] ); + const c = String.fromCharCode(bytes[i++]); - if ( c !== '\n' && c !== '\r' ) { + if (c !== '\n' && c !== '\r') { line += c; } else { - if ( line === 'end_header' ) cont = false; - if ( line !== '' ) { + if (line === 'end_header') cont = false; + if (line !== '') { - lines.push( line ); + lines.push(line); line = ''; } } - } while ( cont && i < bytes.length ); + } while (cont && i < bytes.length); // ascii section using \r\n as line endings - if ( hasCRNL === true ) i ++; + if (hasCRNL === true) i++; - return { headerText: lines.join( '\r' ) + '\r', headerLength: i }; + return { headerText: lines.join('\r') + '\r', headerLength: i }; } - handleElement( buffer: PLYBuffer, elementName: string, element: PLYElement, cacheEntry: MappedAttributes ) { + handleElement(buffer: PLYBuffer, elementName: string, element: PLYElement, cacheEntry: MappedAttributes) { - if ( elementName === 'vertex' ) { + if (elementName === 'vertex') { - buffer.vertices.push( element[ cacheEntry.attrX ], element[ cacheEntry.attrY ], element[ cacheEntry.attrZ ] ); + buffer.vertices.push(element[cacheEntry.attrX], element[cacheEntry.attrY], element[cacheEntry.attrZ]); - if ( cacheEntry.attrNX !== null && cacheEntry.attrNY !== null && cacheEntry.attrNZ !== null ) { + if (cacheEntry.attrNX !== null && cacheEntry.attrNY !== null && cacheEntry.attrNZ !== null) { - buffer.normals.push( element[ cacheEntry.attrNX ], element[ cacheEntry.attrNY ], element[ cacheEntry.attrNZ ] ); + buffer.normals.push(element[cacheEntry.attrNX], element[cacheEntry.attrNY], element[cacheEntry.attrNZ]); } - if ( cacheEntry.attrS !== null && cacheEntry.attrT !== null ) { + if (cacheEntry.attrS !== null && cacheEntry.attrT !== null) { - buffer.uvs.push( element[ cacheEntry.attrS ], element[ cacheEntry.attrT ] ); + buffer.uvs.push(element[cacheEntry.attrS], element[cacheEntry.attrT]); } - if ( cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null ) { + if (cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null) { this._color.setRGB( - element[ cacheEntry.attrR ] / 255.0, - element[ cacheEntry.attrG ] / 255.0, - element[ cacheEntry.attrB ] / 255.0 + element[cacheEntry.attrR] / 255.0, + element[cacheEntry.attrG] / 255.0, + element[cacheEntry.attrB] / 255.0 ).convertSRGBToLinear(); - buffer.colors.push( this._color.r, this._color.g, this._color.b ); + buffer.colors.push(this._color.r, this._color.g, this._color.b); } - for ( const customProperty of Object.keys( this.customPropertyMapping ) ) { + for (const customProperty of Object.keys(this.customPropertyMapping)) { - for ( const elementProperty of this.customPropertyMapping[ customProperty ] ) { + for (const elementProperty of this.customPropertyMapping[customProperty]) { - buffer[ customProperty ].push( element[ elementProperty ] ); + buffer[customProperty].push(element[elementProperty]); } } - } else if ( elementName === 'face' ) { + } else if (elementName === 'face') { const vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338 const texcoord = element.texcoord; - if ( vertex_indices.length === 3 ) { + if (vertex_indices.length === 3) { - buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] ); + buffer.indices.push(vertex_indices[0], vertex_indices[1], vertex_indices[2]); - if ( texcoord && texcoord.length === 6 ) { + if (texcoord && texcoord.length === 6) { - buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] ); - buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] ); - buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] ); + buffer.faceVertexUvs.push(texcoord[0], texcoord[1]); + buffer.faceVertexUvs.push(texcoord[2], texcoord[3]); + buffer.faceVertexUvs.push(texcoord[4], texcoord[5]); } - } else if ( vertex_indices.length === 4 ) { + } else if (vertex_indices.length === 4) { - buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] ); - buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] ); + buffer.indices.push(vertex_indices[0], vertex_indices[1], vertex_indices[3]); + buffer.indices.push(vertex_indices[1], vertex_indices[2], vertex_indices[3]); } // face colors - if ( cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null ) { + if (cacheEntry.attrR !== null && cacheEntry.attrG !== null && cacheEntry.attrB !== null) { this._color.setRGB( - element[ cacheEntry.attrR ] / 255.0, - element[ cacheEntry.attrG ] / 255.0, - element[ cacheEntry.attrB ] / 255.0 + element[cacheEntry.attrR] / 255.0, + element[cacheEntry.attrG] / 255.0, + element[cacheEntry.attrB] / 255.0 ).convertSRGBToLinear(); - buffer.faceVertexColors.push( this._color.r, this._color.g, this._color.b ); - buffer.faceVertexColors.push( this._color.r, this._color.g, this._color.b ); - buffer.faceVertexColors.push( this._color.r, this._color.g, this._color.b ); + buffer.faceVertexColors.push(this._color.r, this._color.g, this._color.b); + buffer.faceVertexColors.push(this._color.r, this._color.g, this._color.b); + buffer.faceVertexColors.push(this._color.r, this._color.g, this._color.b); } @@ -443,34 +444,34 @@ class PLYLoader { } - parse (data: string|ArrayBuffer): BufferGeometry { + parse(data: string | ArrayBuffer): BufferGeometry { let geometry: BufferGeometry if (data instanceof ArrayBuffer) { const bytes = new Uint8Array(data) - const { headerText, headerLength } = this.extractHeaderText( bytes ) + const { headerText, headerLength } = this.extractHeaderText(bytes) const header = this.parseHeader(headerText, headerLength) if (header.format === 'ascii') { - const text = new TextDecoder().decode( bytes ) + const text = new TextDecoder().decode(bytes) geometry = this.parseASCII(text, header) } else { - geometry = this.parseBinary( bytes, header ) + geometry = this.parseBinary(bytes, header) } } else { - geometry = this.parseASCII(data, this.parseHeader(data) ) + geometry = this.parseASCII(data, this.parseHeader(data)) } return geometry } - parseHeader (data: string, headerLength: number = 0): PLYHeader { + parseHeader(data: string, headerLength: number = 0): PLYHeader { const patternHeader = /^ply([\s\S]*)end_header(\r\n|\r|\n)/; let headerText = ''; - const result = patternHeader.exec( data ); + const result = patternHeader.exec(data); - if ( result !== null ) { + if (result !== null) { - headerText = result[ 1 ]; + headerText = result[1]; } @@ -483,45 +484,45 @@ class PLYLoader { version: '' }; - const lines = headerText.split( /\r\n|\r|\n/ ); - let currentElement: PLYElementSpec|undefined; + const lines = headerText.split(/\r\n|\r|\n/); + let currentElement: PLYElementSpec | undefined; - for ( let i = 0; i < lines.length; i ++ ) { + for (let i = 0; i < lines.length; i++) { - let line = lines[ i ]; + let line = lines[i]; line = line.trim(); - if ( line === '' ) continue; + if (line === '') continue; - const lineValues = line.split( /\s+/ ); + const lineValues = line.split(/\s+/); const lineType = lineValues.shift(); - line = lineValues.join( ' ' ); + line = lineValues.join(' '); - switch ( lineType ) { + switch (lineType) { case 'format': - header.format = lineValues[ 0 ]; - header.version = lineValues[ 1 ]; + header.format = lineValues[0]; + header.version = lineValues[1]; break; case 'comment': - header.comments.push( line ); + header.comments.push(line); break; case 'element': - if ( currentElement !== undefined ) { + if (currentElement !== undefined) { assertPLYElementSpec(currentElement) header.elements.push(currentElement) } currentElement = { - name: lineValues[ 0 ], - count: parseInt( lineValues[ 1 ] ), + name: lineValues[0], + count: parseInt(lineValues[1]), properties: [], } @@ -529,13 +530,13 @@ class PLYLoader { case 'property': - if ( currentElement === undefined ) { + if (currentElement === undefined) { - console.warn( 'property without element' ); + console.warn('property without element'); continue; } - currentElement.properties.push( makePlyPropertySpec( lineValues, this.propertyNameMapping ) ); + currentElement.properties.push(makePlyPropertySpec(lineValues, this.propertyNameMapping)); break; @@ -548,15 +549,15 @@ class PLYLoader { default: - console.log( 'unhandled', lineType, lineValues ); + console.log('unhandled', lineType, lineValues); } } - if ( currentElement !== undefined ) { + if (currentElement !== undefined) { - header.elements.push( currentElement ); + header.elements.push(currentElement); } @@ -564,7 +565,7 @@ class PLYLoader { } - parseASCII( data: string, header: PLYHeader ): BufferGeometry { + parseASCII(data: string, header: PLYHeader): BufferGeometry { // PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format) const buffer = this.createBuffer(); @@ -572,126 +573,126 @@ class PLYLoader { const patternBody = /end_header\s+(\S[\s\S]*\S|\S)\s*$/; let body: string[], matches; - if ( ( matches = patternBody.exec( data ) ) !== null ) { + if ((matches = patternBody.exec(data)) !== null) { - body = matches[ 1 ].split( /\s+/ ); + body = matches[1].split(/\s+/); } else { - body = [ ]; + body = []; } - const tokens = new ArrayStream( body ); + const tokens = new ArrayStream(body); - loop: for ( let i = 0; i < header.elements.length; i ++ ) { + loop: for (let i = 0; i < header.elements.length; i++) { - const elementDesc = header.elements[ i ]; - const attributeMap: MappedAttributes = mapElementAttributes( elementDesc.properties ); + const elementDesc = header.elements[i]; + const attributeMap: MappedAttributes = mapElementAttributes(elementDesc.properties); - for ( let j = 0; j < elementDesc.count; j ++ ) { + for (let j = 0; j < elementDesc.count; j++) { // TODO - we're cooercing element to PLYElement inside this call // handle properly! - const element = parseASCIIElement( elementDesc.properties, tokens ); + const element = parseASCIIElement(elementDesc.properties, tokens); - if ( ! element ) break loop; + if (!element) break loop; - this.handleElement( buffer, elementDesc.name, element, attributeMap ); + this.handleElement(buffer, elementDesc.name, element, attributeMap); } } - return this.postProcess( buffer ); + return this.postProcess(buffer); } - parseBinary( data: Uint8Array, header: PLYHeader ): BufferGeometry { + parseBinary(data: Uint8Array, header: PLYHeader): BufferGeometry { const buffer = this.createBuffer(); - const little_endian = ( header.format === 'binary_little_endian' ); - const body = new DataView( data, header.headerLength ); - let result, loc = 0; + const little_endian = (header.format === 'binary_little_endian'); + const body = new DataView(data, header.headerLength); + let result, loc = 0; - for ( let currentElement = 0; currentElement < header.elements.length; currentElement ++ ) { + for (let currentElement = 0; currentElement < header.elements.length; currentElement++) { - const elementDesc = header.elements[ currentElement ]; - const properties = elementDesc.properties; - const attributeMap: MappedAttributes = mapElementAttributes( properties ); + const elementDesc = header.elements[currentElement]; + const properties = elementDesc.properties; + const attributeMap: MappedAttributes = mapElementAttributes(properties); - const binaryProperties = this.makeBinaryProperties( properties, body, little_endian ); + const binaryProperties = this.makeBinaryProperties(properties, body, little_endian); - for ( let currentElementCount = 0; currentElementCount < elementDesc.count; currentElementCount ++ ) { + for (let currentElementCount = 0; currentElementCount < elementDesc.count; currentElementCount++) { - result = binaryReadElement( loc, binaryProperties ); - loc += result[ 1 ]; - const element = result[ 0 ]; + result = binaryReadElement(loc, binaryProperties); + loc += result[1]; + const element = result[0]; - this.handleElement( buffer, elementDesc.name, element, attributeMap ); + this.handleElement(buffer, elementDesc.name, element, attributeMap); - } + } - } + } - return this.postProcess( buffer ); + return this.postProcess(buffer); } - postProcess( buffer: PLYBuffer ): BufferGeometry { + postProcess(buffer: PLYBuffer): BufferGeometry { let geometry = new BufferGeometry(); // mandatory buffer data - if ( buffer.indices.length > 0 ) { + if (buffer.indices.length > 0) { - geometry.setIndex( buffer.indices ); + geometry.setIndex(buffer.indices); } - geometry.setAttribute( 'position', new Float32BufferAttribute( buffer.vertices, 3 ) ); + geometry.setAttribute('position', new Float32BufferAttribute(buffer.vertices, 3)); // optional buffer data - if ( buffer.normals.length > 0 ) { + if (buffer.normals.length > 0) { - geometry.setAttribute( 'normal', new Float32BufferAttribute( buffer.normals, 3 ) ); + geometry.setAttribute('normal', new Float32BufferAttribute(buffer.normals, 3)); } - if ( buffer.uvs.length > 0 ) { + if (buffer.uvs.length > 0) { - geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.uvs, 2 ) ); + geometry.setAttribute('uv', new Float32BufferAttribute(buffer.uvs, 2)); } - if ( buffer.colors.length > 0 ) { + if (buffer.colors.length > 0) { - geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) ); + geometry.setAttribute('color', new Float32BufferAttribute(buffer.colors, 3)); } - if ( buffer.faceVertexUvs.length > 0 || buffer.faceVertexColors.length > 0 ) { + if (buffer.faceVertexUvs.length > 0 || buffer.faceVertexColors.length > 0) { geometry = geometry.toNonIndexed(); - if ( buffer.faceVertexUvs.length > 0 ) geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) ); - if ( buffer.faceVertexColors.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.faceVertexColors, 3 ) ); + if (buffer.faceVertexUvs.length > 0) geometry.setAttribute('uv', new Float32BufferAttribute(buffer.faceVertexUvs, 2)); + if (buffer.faceVertexColors.length > 0) geometry.setAttribute('color', new Float32BufferAttribute(buffer.faceVertexColors, 3)); } // custom buffer data - for ( const customProperty of Object.keys( this.customPropertyMapping ) ) { + for (const customProperty of Object.keys(this.customPropertyMapping)) { - if ( buffer[ customProperty ].length > 0 ) { + if (buffer[customProperty].length > 0) { - geometry.setAttribute( + geometry.setAttribute( customProperty, new Float32BufferAttribute( - buffer[ customProperty ], - this.customPropertyMapping[ customProperty ].length + buffer[customProperty], + this.customPropertyMapping[customProperty].length ) - ); + ); } @@ -706,25 +707,26 @@ class PLYLoader { /** Augments properties with their appropriate valueReader attribute and * countReader if the property is a list type */ - makeBinaryProperties( properties: PLYProperty[], body: DataView, little_endian: boolean ): BinaryPLYProperty[] { + makeBinaryProperties(properties: PLYProperty[], body: DataView, little_endian: boolean): BinaryPLYProperty[] { const newProperties: BinaryPLYProperty[] = [] - for ( let i = 0, l = properties.length; i < l; i ++ ) { + for (let i = 0, l = properties.length; i < l; i++) { - const property = properties[ i ]; + const property = properties[i]; if (property.isList) { - newProperties.push({...property, - countReader: getBinaryReader( body, property.countType, little_endian ), - valueReader: getBinaryReader( body, property.type, little_endian ) - }) + newProperties.push({ + ...property, + countReader: getBinaryReader(body, property.countType, little_endian), + valueReader: getBinaryReader(body, property.type, little_endian) + }) } else { newProperties.push({ ...property, - valueReader: getBinaryReader( body, property.type, little_endian ) - }) + valueReader: getBinaryReader(body, property.type, little_endian) + }) } } return newProperties @@ -734,9 +736,9 @@ class PLYLoader { -function makePlyPropertySpec( propertyValues: string[], propertyNameMapping: {[k: string]: string} ): PLYProperty { +function makePlyPropertySpec(propertyValues: string[], propertyNameMapping: { [k: string]: string }): PLYProperty { - const prop0 = propertyValues[ 0 ]; + const prop0 = propertyValues[0]; let property: PLYProperty; @@ -762,7 +764,7 @@ function makePlyPropertySpec( propertyValues: string[], propertyNameMapping: {[k return property; } -function parseASCIINumber (n: string, type: string): number { +function parseASCIINumber(n: string, type: string): number { switch (type) { case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint': case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32': @@ -776,37 +778,37 @@ function parseASCIINumber (n: string, type: string): number { return 0 } -function parseASCIIElement( properties: PLYProperty[], tokens: ArrayStream ): PLYElement | null { +function parseASCIIElement(properties: PLYProperty[], tokens: ArrayStream): PLYElement | null { const element: Partial = { name: '', type: '' }; - for ( let i = 0; i < properties.length; i ++ ) { + for (let i = 0; i < properties.length; i++) { - if ( tokens.empty() ) return null; + if (tokens.empty()) return null; - const property = properties[ i ] + const property = properties[i] - if ( property.isList ) { + if (property.isList) { const list = []; - const n = parseASCIINumber( tokens.next(), property.countType ); + const n = parseASCIINumber(tokens.next(), property.countType); - for ( let j = 0; j < n; j ++ ) { + for (let j = 0; j < n; j++) { - if ( tokens.empty() ) return null; + if (tokens.empty()) return null; - list.push( parseASCIINumber( tokens.next(), property.type ) ); + list.push(parseASCIINumber(tokens.next(), property.type)); } - element[ property.name ] = list; + element[property.name] = list; } else { - element[ properties[ i ].name ] = parseASCIINumber( tokens.next(), properties[ i ].type ); + element[properties[i].name] = parseASCIINumber(tokens.next(), properties[i].type); } @@ -816,21 +818,21 @@ function parseASCIIElement( properties: PLYProperty[], tokens: ArrayStream { + const elementNames = properties.map(property => { return property.name; - } ); + }); - function findAttrName( names: string[] ) { + function findAttrName(names: string[]) { - for ( let i = 0, l = names.length; i < l; i ++ ) { + for (let i = 0, l = names.length; i < l; i++) { - const name = names[ i ]; + const name = names[i]; - if ( elementNames.includes( name ) ) return name; + if (elementNames.includes(name)) return name; } @@ -839,17 +841,17 @@ function mapElementAttributes( properties: PLYProperty[] ) { } return { - attrX: findAttrName( [ 'x', 'px', 'posx' ] ) || 'x', - attrY: findAttrName( [ 'y', 'py', 'posy' ] ) || 'y', - attrZ: findAttrName( [ 'z', 'pz', 'posz' ] ) || 'z', - attrNX: findAttrName( [ 'nx', 'normalx' ] ), - attrNY: findAttrName( [ 'ny', 'normaly' ] ), - attrNZ: findAttrName( [ 'nz', 'normalz' ] ), - attrS: findAttrName( [ 's', 'u', 'texture_u', 'tx' ] ), - attrT: findAttrName( [ 't', 'v', 'texture_v', 'ty' ] ), - attrR: findAttrName( [ 'red', 'diffuse_red', 'r', 'diffuse_r' ] ), - attrG: findAttrName( [ 'green', 'diffuse_green', 'g', 'diffuse_g' ] ), - attrB: findAttrName( [ 'blue', 'diffuse_blue', 'b', 'diffuse_b' ] ), + attrX: findAttrName(['x', 'px', 'posx']) || 'x', + attrY: findAttrName(['y', 'py', 'posy']) || 'y', + attrZ: findAttrName(['z', 'pz', 'posz']) || 'z', + attrNX: findAttrName(['nx', 'normalx']), + attrNY: findAttrName(['ny', 'normaly']), + attrNZ: findAttrName(['nz', 'normalz']), + attrS: findAttrName(['s', 'u', 'texture_u', 'tx']), + attrT: findAttrName(['t', 'v', 'texture_v', 'ty']), + attrR: findAttrName(['red', 'diffuse_red', 'r', 'diffuse_r']), + attrG: findAttrName(['green', 'diffuse_green', 'g', 'diffuse_g']), + attrB: findAttrName(['blue', 'diffuse_blue', 'b', 'diffuse_b']), }; } @@ -858,9 +860,9 @@ type MappedAttributes = ReturnType class PlyParser extends SurfaceParser { - get type () { return 'ply' } - - getLoader () { + get type() { return 'ply' } + + getLoader() { return new PLYLoader() } } @@ -869,24 +871,24 @@ class ArrayStream { arr: Array i: number - constructor( arr: Array ) { + constructor(arr: Array) { - this.arr = arr; - this.i = 0; + this.arr = arr; + this.i = 0; - } + } - empty() { + empty() { - return this.i >= this.arr.length; + return this.i >= this.arr.length; - } + } - next() { + next() { - return this.arr[ this.i ++ ]; + return this.arr[this.i++]; - } + } }