import BinaryHelper from 'vtk.js/Sources/IO/Core/BinaryHelper'; import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper'; import macro from 'vtk.js/Sources/macros'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData';
import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper';
const PLYFormats = { ASCII: 'ascii', BINARY_BIG_ENDIAN: 'binary_big_endian', BINARY_LITTLE_ENDIAN: 'binary_little_endian', };
const mapping = { diffuse_red: 'red', diffuse_green: 'green', diffuse_blue: 'blue', };
const patterns = { patternHeader: /ply([\s\S]*)end_header\r?\n/, patternBody: /end_header\s([\s\S]*)$/, };
function parseHeader(data) { let headerText = ''; let headerLength = 0; const result = patterns.patternHeader.exec(data);
if (result !== null) { headerText = result[1]; headerLength = result[0].length; }
const header = { comments: [], elements: [], headerLength, };
const lines = headerText.split('\n'); let elem; let lineType; let lineValues;
for (let i = 0; i < lines.length; i++) { let line = lines[i]; line = line.trim();
if (line !== '') { let property; lineValues = line.split(/\s+/); 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 (elem !== undefined) { header.elements.push(elem); }
elem = {}; elem.name = lineValues[0]; elem.count = parseInt(lineValues[1], 10); elem.properties = []; break;
case 'property': property = { type: lineValues[0], };
if (property.type === 'list') { property.name = lineValues[3]; property.countType = lineValues[1]; property.itemType = lineValues[2]; } else { property.name = lineValues[1]; }
if (property.name in mapping) { property.name = mapping[property.name]; } elem.properties.push(property); break;
case 'obj_info': header.objInfo = line; break;
default: console.warn('unhandled', lineType, lineValues); break; } } }
if (elem !== undefined) { header.elements.push(elem); }
return header; }
function postProcess(buffer, elements) { const vertElement = elements.find((element) => element.name === 'vertex'); const faceElement = elements.find((element) => element.name === 'face');
let nbVerts = 0; let nbFaces = 0;
if (vertElement) { nbVerts = vertElement.count; } if (faceElement) { nbFaces = faceElement.count; }
const pointValues = new Float32Array(nbVerts * 3); const colorArray = new Uint8Array(nbVerts * 3); const tcoordsArray = new Float32Array(nbVerts * 2); const normalsArray = new Float32Array(nbVerts * 3);
const hasColor = buffer.colors.length > 0; const hasVertTCoords = buffer.uvs.length > 0; const hasNorms = buffer.normals.length > 0; const hasFaceTCoords = buffer.faceVertexUvs.length > 0;
for (let vertIdx = 0; vertIdx < nbVerts; vertIdx++) { let a = vertIdx * 3 + 0; let b = vertIdx * 3 + 1; const c = vertIdx * 3 + 2;
pointValues[a] = buffer.vertices[a]; pointValues[b] = buffer.vertices[b]; pointValues[c] = buffer.vertices[c];
if (hasColor) { colorArray[a] = buffer.colors[a]; colorArray[b] = buffer.colors[b]; colorArray[c] = buffer.colors[c]; }
if (hasVertTCoords) { a = vertIdx * 2 + 0; b = vertIdx * 2 + 1; tcoordsArray[a] = buffer.uvs[a]; tcoordsArray[b] = buffer.uvs[b]; }
if (hasNorms) { normalsArray[a] = buffer.normals[a]; normalsArray[b] = buffer.normals[b]; normalsArray[c] = buffer.normals[c]; } }
if (!hasVertTCoords && hasFaceTCoords) { let idxVerts = 0; let idxCoord = 0; for (let faceIdx = 0; faceIdx < nbFaces; ++faceIdx) { const nbFaceVerts = buffer.indices[idxVerts++]; const texcoords = buffer.faceVertexUvs[idxCoord++]; if (texcoords && nbFaceVerts * 2 === texcoords.length) { for (let vertIdx = 0; vertIdx < nbFaceVerts; ++vertIdx) { const vert = buffer.indices[idxVerts++]; tcoordsArray[vert * 2 + 0] = texcoords[vertIdx * 2 + 0]; tcoordsArray[vert * 2 + 1] = texcoords[vertIdx * 2 + 1]; } } else { idxVerts += nbFaceVerts; } } }
const polydata = vtkPolyData.newInstance();
polydata.getPoints().setData(pointValues, 3); if (hasColor) { polydata.getPointData().setScalars( vtkDataArray.newInstance({ numberOfComponents: 3, values: colorArray, name: 'Scalars', }) ); }
if (hasVertTCoords || hasFaceTCoords) { const da = vtkDataArray.newInstance({ numberOfComponents: 2, values: tcoordsArray, name: 'TextureCoordinates', }); const cpd = polydata.getPointData(); cpd.addArray(da); cpd.setActiveTCoords(da.getName()); }
if (hasNorms) { polydata.getPointData().setNormals( vtkDataArray.newInstance({ numberOfComponents: 3, name: 'Normals', values: normalsArray, }) ); }
polydata.getPolys().setData(Uint32Array.from(buffer.indices));
return polydata; }
function parseNumber(n, type) { let r; 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': r = parseInt(n, 10); break; case 'float': case 'double': case 'float32': case 'float64': r = parseFloat(n); break; default: console.log('Unsupported type'); break; }
return r; }
function parseElement(properties, line) { const values = line.split(/\s+/); const element = {};
for (let i = 0; i < properties.length; i++) { if (properties[i].type === 'list') { const list = []; const n = parseNumber(values.shift(), properties[i].countType);
for (let j = 0; j < n; j++) { list.push(parseNumber(values.shift(), properties[i].itemType)); }
element[properties[i].name] = list; } else { element[properties[i].name] = parseNumber( values.shift(), properties[i].type ); } }
return element; }
function handleElement(buffer, name, element) { if (name === 'vertex') { buffer.vertices.push(element.x, element.y, element.z);
if ('nx' in element && 'ny' in element && 'nz' in element) { buffer.normals.push(element.nx, element.ny, element.nz); }
if ('s' in element && 't' in element) { buffer.uvs.push(element.s, element.t); } else if ('u' in element && 'v' in element) { buffer.uvs.push(element.u, element.v); } else if ('texture_u' in element && 'texture_v' in element) { buffer.uvs.push(element.texture_u, element.texture_v); }
if ('red' in element && 'green' in element && 'blue' in element) { buffer.colors.push(element.red, element.green, element.blue); } } else if (name === 'face') { const vertexIndices = element.vertex_indices || element.vertex_index; const texcoord = element.texcoord;
if (vertexIndices && vertexIndices.length > 0) { buffer.indices.push(vertexIndices.length); vertexIndices.forEach((val, idx) => { buffer.indices.push(val); }); }
buffer.faceVertexUvs.push(texcoord); } }
function binaryRead(dataview, at, type, littleEndian) { let r; switch (type) { case 'int8': case 'char': r = [dataview.getInt8(at), 1]; break; case 'uint8': case 'uchar': r = [dataview.getUint8(at), 1]; break; case 'int16': case 'short': r = [dataview.getInt16(at, littleEndian), 2]; break; case 'uint16': case 'ushort': r = [dataview.getUint16(at, littleEndian), 2]; break; case 'int32': case 'int': r = [dataview.getInt32(at, littleEndian), 4]; break; case 'uint32': case 'uint': r = [dataview.getUint32(at, littleEndian), 4]; break; case 'float32': case 'float': r = [dataview.getFloat32(at, littleEndian), 4]; break; case 'float64': case 'double': r = [dataview.getFloat64(at, littleEndian), 8]; break; default: console.log('Unsupported type'); break; } return r; }
function binaryReadElement(dataview, at, properties, littleEndian) { const element = {}; let result; let read = 0;
for (let i = 0; i < properties.length; i++) { if (properties[i].type === 'list') { const list = [];
result = binaryRead( dataview, at + read, properties[i].countType, littleEndian ); const n = result[0]; read += result[1];
for (let j = 0; j < n; j++) { result = binaryRead( dataview, at + read, properties[i].itemType, littleEndian ); list.push(result[0]); read += result[1]; }
element[properties[i].name] = list; } else { result = binaryRead( dataview, at + read, properties[i].type, littleEndian ); element[properties[i].name] = result[0]; read += result[1]; } }
return [element, read]; }
function vtkPLYReader(publicAPI, model) { model.classHierarchy.push('vtkPLYReader');
if (!model.dataAccessHelper) { model.dataAccessHelper = DataAccessHelper.get('http'); }
function fetchData(url, option = {}) { const { compression, progressCallback } = model; if (option.binary) { return model.dataAccessHelper.fetchBinary(url, { compression, progressCallback, }); } return model.dataAccessHelper.fetchText(publicAPI, url, { compression, progressCallback, }); }
publicAPI.setUrl = (url, option = { binary: true }) => { model.url = url;
const path = url.split('/'); path.pop(); model.baseURL = path.join('/');
model.compression = option.compression;
return publicAPI.loadData({ progressCallback: option.progressCallback, binary: !!option.binary, }); };
publicAPI.loadData = (option = {}) => { const promise = fetchData(model.url, option); promise.then(publicAPI.parse); return promise; };
publicAPI.parse = (content) => { if (typeof content === 'string') { publicAPI.parseAsText(content); } else { publicAPI.parseAsArrayBuffer(content); } };
publicAPI.parseAsArrayBuffer = (content) => { if (!content) { return; } if (content !== model.parseData) { publicAPI.modified(); } else { return; }
const text = BinaryHelper.arrayBufferToString(content); const header = parseHeader(text);
const isBinary = header.format !== PLYFormats.ASCII;
if (!isBinary) { publicAPI.parseAsText(content); return; }
model.parseData = content;
const buffer = { indices: [], vertices: [], normals: [], uvs: [], faceVertexUvs: [], colors: [], };
const littleEndian = header.format === PLYFormats.BINARY_LITTLE_ENDIAN; const arraybuffer = content instanceof ArrayBuffer ? content : content.buffer; const body = new DataView(arraybuffer, header.headerLength); let result; let loc = 0;
for (let elem = 0; elem < header.elements.length; elem++) { for (let idx = 0; idx < header.elements[elem].count; idx++) { result = binaryReadElement( body, loc, header.elements[elem].properties, littleEndian ); loc += result[1]; const element = result[0]; handleElement(buffer, header.elements[elem].name, element); } }
const polydata = postProcess(buffer, header.elements);
model.output[0] = polydata; };
publicAPI.parseAsText = (content) => { if (!content) { return; }
if (content !== model.parseData) { publicAPI.modified(); } else { return; }
model.parseData = content;
const text = BinaryHelper.arrayBufferToString(content); const header = parseHeader(text);
const isBinary = header.format !== PLYFormats.ASCII;
if (isBinary) { publicAPI.parseAsArrayBuffer(content); return; }
const buffer = { indices: [], vertices: [], normals: [], uvs: [], faceVertexUvs: [], colors: [], };
const result = patterns.patternBody.exec(text); let body = ''; if (result !== null) { body = result[1]; }
const lines = body.split('\n'); let elem = 0; let idx = 0;
for (let i = 0; i < lines.length; i++) { let line = lines[i]; line = line.trim(); if (line !== '') { if (idx >= header.elements[elem].count) { elem++; idx = 0; }
const element = parseElement(header.elements[elem].properties, line); handleElement(buffer, header.elements[elem].name, element); idx++; } }
const polydata = postProcess(buffer, header.elements);
model.output[0] = polydata; };
publicAPI.requestData = (inData, outData) => { publicAPI.parse(model.parseData); }; }
const DEFAULT_VALUES = { };
export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues);
macro.obj(publicAPI, model); macro.get(publicAPI, model, ['url', 'baseURL']); macro.setGet(publicAPI, model, ['dataAccessHelper']); macro.algo(publicAPI, model, 0, 1);
vtkPLYReader(publicAPI, model);
if (!model.compression) { model.compression = null; } if (!model.progressCallback) { model.progressCallback = null; } }
export const newInstance = macro.newInstance(extend, 'vtkPLYReader');
export default { extend, newInstance, };
|