UNPKG

loaders.gl

Version:

Framework-independent loaders for 3D graphics formats

590 lines (545 loc) 16 kB
/* Modified from Mikola Lysenko's parse-ply MIT License */ /* eslint-disable */ const isLittleEndian = new Uint32Array(new Uint8Array([1, 2, 3, 4]).buffer)[0] === 0x04030201; const PARSER_STATE = { BEGIN: 0, FORMAT: 1, HEADER: 2, BODY: 4, DONE: 5, ERROR: -1 }; const PLY_FORMAT = { ASCII: 0, BINARY_LITTLE_ENDIAN: 1, BINARY_BIG_ENDIAN: 2, UNKNOWN: -1 }; const PLY_TYPES = { INT: 0, FLOAT: 1, LIST: 2, LIST_INT: 2, LIST_FLOAT: 3 }; const PLY_TYPENAMES = { char: [PLY_TYPES.INT, 1, Int8Array], int8: [PLY_TYPES.INT, 1, Int8Array], uchar: [PLY_TYPES.INT, 1, Uint8Array], uint8: [PLY_TYPES.INT, 1, Uint8Array], short: [PLY_TYPES.INT, 2, Int16Array], int16: [PLY_TYPES.INT, 2, Int16Array], ushort: [PLY_TYPES.INT, 2, Uint16Array], uint16: [PLY_TYPES.INT, 2, Uint16Array], int: [PLY_TYPES.INT, 4, Int32Array], int32: [PLY_TYPES.INT, 4, Int32Array], uint: [PLY_TYPES.INT, 4, Uint32Array], uint32: [PLY_TYPES.INT, 4, Uint32Array], float: [PLY_TYPES.FLOAT, 4, Float32Array], float32: [PLY_TYPES.FLOAT, 4, Float32Array], double: [PLY_TYPES.FLOAT, 8, Float64Array], float64: [PLY_TYPES.FLOAT, 8, Float64Array] }; const SYSTEM_ENDIAN = isLittleEndian ? PLY_FORMAT.BINARY_LITTLE_ENDIAN : PLY_FORMAT.BINARY_BIG_ENDIAN; class PLYProperty { constructor(name, data, type, size0, size1) { this.name = name; this.data = data; this.type = type; this.size0 = size0; this.size1 = size1; } } class PLYElement { constructor(name, count) { this.name = name; this.count = count; this.properties = []; } } const data_buffer = new Uint8Array(8); const float_view = new Float32Array(data_buffer.buffer); const double_view = new Float64Array(data_buffer.buffer); const TRAIL_EOL = new Buffer(1); TRAIL_EOL[0] = 10; export default class PLYParser { constructor() { this.onsuccess = () => {}; this.onerror = () => {}; this.state = PARSER_STATE.BEGIN; this.format = PLY_FORMAT.UNKNOWN; this.elements = []; this.offset = 0; this.buffers = []; this.current_element = 0; this.current_index = 0; this.current_line = 0; this.current_property = 0; this.current_list_property = -1; this.last_line = ''; } getline(max_len) { var ptr = this.offset; var cbuf = 0; var n = 0; var prefix = null; while (n < max_len && cbuf < this.buffers.length) { if (this.buffers[cbuf][ptr] === 10) { this.current_line++; if (cbuf > 0) { prefix.push(this.buffers[cbuf].slice(0, ptr)); this.offset = ptr + 1; if (this.offset >= this.buffers[cbuf].length) { this.offset = 0; this.buffers.splice(0, cbuf + 1); } else { this.buffers.splice(0, cbuf); } this.last_line = prefix.join(''); return this.last_line; } else { this.last_line = this.buffers[0].slice(this.offset, ptr).toString(); this.offset = ptr + 1; if (this.offset >= this.buffers[cbuf].length) { this.offset = 0; this.buffers.shift(); } return this.last_line; } } if (++ptr >= this.buffers[cbuf].length) { if (cbuf === 0) { prefix = [this.buffers[0].slice(this.offset)]; } ++n; ++cbuf; ptr = 0; } } console.log(this.offset); if (n >= max_len) { return '!'; } return null; } getchar() { if (this.buffers.length > 0) { var v = this.buffers[0][this.offset]; this.offset++; if (this.offset > this.buffers[0].length) { this.offset = 0; this.buffers.shift(); } return v; } return -1; } getint(len) { for (var i = 0; i < len; ++i) { var v = this.getchar(); if (v < 0) { return Number.NaN; } data_buffer[i] = v; } var r = 0; if (this.format === PLY_FORMAT.BINARY_LITTLE_ENDIAN) { for (var j = 0; j < len; ++j) { r += data_buffer[j] << (8 * j); } } else { for (var j = 0; j < len; ++j) { r += data_buffer[len - j - 1] << (8 * j); } } return r; } getfloat(len) { for (var i = 0; i < len; ++i) { var v = this.getchar(); if (v < 0) { return Number.NaN; } data_buffer[i] = v; } if (this.format !== SYSTEM_ENDIAN) { for (var i = 0; i < len; ++i) { var t = data_buffer[i]; data_buffer[i] = data_buffer[len - i - 1]; data_buffer[len - i - 1] = t; } } if (len === 4) { return float_view[0]; } else { return double_view[0]; } } clearBuffers() { this.offset = 0; this.buffers = []; } raiseError(message) { if (this.state !== PARSER_STATE.ERROR) { this.state = PARSER_STATE.ERROR; if (this.current_line > 0) { this.onerror( new Error( 'Error parsing PLY: ' + message + ' on line ' + this.current_line + "\n\t'" + this.last_line + "'" ) ); } else { this.onerror(new Error('Error parsing PLY: ' + message)); } this.clearBuffers(); } } createResult() { var result = {}; for (var i = 0; i < this.elements.length; ++i) { var element = this.elements[i]; var props = {}; for (var j = 0; j < element.properties.length; ++j) { var prop = element.properties[j]; props[prop.name] = prop.data; } result[element.name] = props; } return result; } processBinary() { while (this.current_element < this.elements.length) { var c = this.elements[this.current_element]; var props = c.properties; while (this.current_index < c.count) { var idx = this.current_index; while (this.current_property < props.length) { var p = props[this.current_property++]; switch (p.type) { case PLY_TYPES.INT: var vi = this.getint(p.size0); if (isNaN(vi)) { this.raiseError('Invalid integer value'); return false; } p.data[idx] = vi; break; case PLY_TYPES.FLOAT: var vf = this.getfloat(p.size0); if (isNaN(vf)) { this.raiseError('Invalid float value'); return false; } p.data[idx] = vf; break; case PLY_TYPES.LIST_INT: case PLY_TYPES.LIST_FLOAT: var lst; if (this.current_list_property < 0) { var vi = this.getint(p.size0); if (isNaN(vi)) { this.raiseError('Invalid list length'); return false; } lst = new Array(vi); p.data[idx] = lst; this.current_list_property = 0; } else { lst = p.data[idx]; } while (this.current_list_property < lst.length) { var v; if (p.type === PLY_TYPES.LIST_INT) { v = this.getint(p.size1); } else { v = this.getfloat(p.size1); } if (isNaN(v)) { this.raiseError('Invalid value in list'); return false; } lst[this.current_list_property++] = v; } this.current_list_property = -1; break; default: this.raiseError('Uninitialized property type (this should never happen)'); return false; } } this.current_property = 0; this.current_index += 1; } this.current_index = 0; this.current_element++; } this.state = PARSER_STATE.DONE; return true; } processAscii() { while (this.current_element < this.elements.length) { var c = this.elements[this.current_element]; var props = c.properties; while (this.current_index < c.count) { var l = this.getline(1 << 20); if (!l) { return false; } var idx = this.current_index; var toks = l.split(' '); var c_tok = 0; for (var i = 0; i < props.length; ++i) { if (c_tok >= toks.length) { this.raiseError('Not enough values for element'); return false; } var p = props[i]; switch (p.type) { case PLY_TYPES.INT: var vi = parseInt(toks[c_tok++], 10); if (isNaN(vi)) { this.raiseError('Invalid integer value'); return false; } p.data[idx] = vi; break; case PLY_TYPES.FLOAT: var vf = parseFloat(toks[c_tok++]); if (isNaN(vf)) { this.raiseError('Invalid float value'); return false; } p.data[idx] = vf; break; case PLY_TYPES.LIST_INT: case PLY_TYPES.LIST_FLOAT: var vi = parseInt(toks[c_tok++]); if (isNaN(vi) || vi < 0) { this.raiseError('Invalid list length'); return false; } if (vi + c_tok > toks.length) { this.raiseError('List length too long'); return false; } var result = new Array(vi); p.data[idx] = result; if (p.type === PLY_TYPES.LIST_INT) { for (var j = 0; j < vi; ++j) { result[j] = parseInt(toks[c_tok++], 10); if (isNaN(result[j])) { this.raiseError('Invalid integer in list'); return false; } } } else { for (var j = 0; j < vi; ++j) { result[j] = parseFloat(toks[c_tok++]); if (isNaN(result[j])) { this.raiseError('Invalid float in list'); return false; } } } break; default: this.raiseError('Uninitialized property type (this should never happen)'); return false; } } this.current_index += 1; } this.current_index = 0; this.current_element++; } this.state = PARSER_STATE.DONE; return true; } processProperty(name, type) { var sz = this.elements[this.elements.length - 1].count; if (type[0] === 'list') { if (type.length !== 3) { this.raiseError('Invalid list datatype'); return null; } var t0 = PLY_TYPENAMES[type[1]]; var t1 = PLY_TYPENAMES[type[2]]; if (!t0 || t0[0] !== PLY_TYPES.INT) { this.raiseError('List length must be an integer'); return null; } if (!t1) { this.raiseError('Invalid type for list values'); return null; } return new PLYProperty(name, new Array(sz), t1[0] + PLY_TYPES.LIST, t0[1], t1[1]); } var t = PLY_TYPENAMES[type[0]]; if (!t) { this.raiseError('Invalid property datatype'); return null; } var typecons = t[2]; return new PLYProperty(name, new typecons(sz), t[0], t[1], 0); } processToken() { switch (this.state) { case PARSER_STATE.BEGIN: var l = this.getline(4); if (!l) { return false; } if (l === 'ply') { this.state = PARSER_STATE.FORMAT; return true; } else { this.raiseError('Missing/invalid PLY magic number'); return false; } break; case PARSER_STATE.FORMAT: var l = this.getline(64); if (!l) { return false; } var toks = l.split(' '); if (toks.length > 0 && toks[0] === 'comment') { return true; } if (toks.length !== 3 || toks[0] !== 'format' || toks[2] !== '1.0') { this.raiseError('Missing/invalid format specifier'); return false; } switch (toks[1]) { case 'ascii': this.format = PLY_FORMAT.ASCII; break; case 'binary_little_endian': this.format = PLY_FORMAT.BINARY_LITTLE_ENDIAN; break; case 'binary_big_endian': this.format = PLY_FORMAT.BINARY_BIG_ENDIAN; break; default: this.raiseError('Invalid format'); return false; break; } this.state = PARSER_STATE.HEADER; return true; break; case PARSER_STATE.HEADER: var l = this.getline(4096); if (!l) { return false; } var toks = l.split(' '); switch (toks[0]) { case 'element': if (toks.length !== 3) { this.raiseError('Invalid element description'); return false; } var count = parseInt(toks[2], 10); if (isNaN(count) || count < 0) { this.raiseError('Invalid element count'); return false; } this.elements.push(new PLYElement(toks[1], count)); return true; break; case 'property': if (this.elements.length === 0) { this.raiseError('Got a property without an element'); return false; } if (toks.length < 3) { this.raiseError('Invalid property description'); return false; } var prop = this.processProperty(toks[toks.length - 1], toks.slice(1, toks.length - 1)); if (!prop) { return false; } this.elements[this.elements.length - 1].properties.push(prop); return true; break; case 'end_header': this.current_index = 0; this.current_element = 0; this.state = PARSER_STATE.BODY; if (this.format !== PLY_FORMAT.ASCII) { this.current_line = -1; } return true; break; case 'comment': return true; break; default: this.raiseError('Unexpeceted token in header'); return false; } break; case PARSER_STATE.BODY: switch (this.format) { case PLY_FORMAT.ASCII: return this.processAscii(); case PLY_FORMAT.BINARY_LITTLE_ENDIAN: case PLY_FORMAT.BINARY_BIG_ENDIAN: return this.processBinary(); default: this.raiseError('Invalid format, this should not happen'); return false; } break; case PARSER_STATE.DONE: this.clearBuffers(); return false; break; case PARSER_STATE.ERROR: this.clearBuffers(); return false; break; default: this.raiseError('Parser state corrupted'); return false; } return false; } ondata(data) { if (this.state === PARSER_STATE.ERROR) { return; } if (data instanceof Buffer) { this.buffers.push(data); } else { this.buffers.push(new Buffer(data)); } while (this.processToken()) {} } onend() { this.ondata(TRAIL_EOL); this.clearBuffers(); if (this.state === PARSER_STATE.DONE) { this.onsuccess(this.createResult()); } else if (this.state !== PARSER_STATE.ERROR) { this.raiseError('Unexpected EOF while parsing PLY file'); } } parse(buffer) { this.ondata(buffer); this.onend(); } }