import macro from 'vtk.js/Sources/macros'; import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper'; import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; import vtkPolyDataNormals from 'vtk.js/Sources/Filters/Core/PolyDataNormals';
import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper';
const { vtkErrorMacro } = macro; let decoderModule = null;
function setWasmBinary(url, binaryName) { const dracoDecoderType = {};
return new Promise((resolve, reject) => { dracoDecoderType.wasmBinaryFile = binaryName;
const xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = 'arraybuffer';
xhr.onload = () => { if (xhr.status === 200) { dracoDecoderType.wasmBinary = xhr.response; Promise.resolve(window.DracoDecoderModule(dracoDecoderType)).then( (module) => { decoderModule = module; resolve(true); }, reject ); } else { reject(Error(`WASM binary could not be loaded: ${xhr.statusText}`)); } }; xhr.send(null); }); }
async function setDracoDecoder(dracoDecoder) { decoderModule = await dracoDecoder({}); }
function getDracoDecoder() { return decoderModule; }
function getDracoDataType(attributeType) { switch (attributeType) { case Float32Array: return decoderModule.DT_FLOAT32; case Int8Array: return decoderModule.DT_INT8; case Int16Array: return decoderModule.DT_INT16; case Int32Array: return decoderModule.DT_INT32; case Uint8Array: return decoderModule.DT_UINT8; case Uint16Array: return decoderModule.DT_UINT16; case Uint32Array: return decoderModule.DT_UINT32; default: return decoderModule.DT_FLOAT32; } }
function decodeAttribute( decoder, dracoGeometry, attributeName, attributeType, attribute ) { const numComponents = attribute.num_components(); const numPoints = dracoGeometry.num_points(); const numValues = numPoints * numComponents;
const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; const dataType = getDracoDataType(attributeType);
const ptr = decoderModule._malloc(byteLength); decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
const array = new attributeType( decoderModule.HEAPF32.buffer, ptr, numValues ).slice();
decoderModule._free(ptr);
return { name: attributeName, array, itemSize: numComponents, }; }
function decodeIndices(decoder, dracoGeometry) { const numFaces = dracoGeometry.num_faces(); const numIndices = numFaces * 3; const byteLength = numIndices * 4;
const ptr = decoderModule._malloc(byteLength); decoder.GetTrianglesUInt32Array(dracoGeometry, byteLength, ptr); const indices = new Uint32Array( decoderModule.HEAPF32.buffer, ptr, numIndices ).slice(); decoderModule._free(ptr); return indices; }
function getPolyDataFromDracoGeometry(decoder, dracoGeometry) { const indices = decodeIndices(decoder, dracoGeometry); const nCells = indices.length - 2;
const cells = vtkCellArray.newInstance(); cells.resize((4 * indices.length) / 3); for (let cellId = 0; cellId < nCells; cellId += 3) { const cell = indices.slice(cellId, cellId + 3); cells.insertNextCell(cell); }
const polyData = vtkPolyData.newInstance({ polys: cells });
const attributeIDs = { points: 'POSITION', normals: 'NORMAL', scalars: 'COLOR', tcoords: 'TEX_COORD', };
Object.keys(attributeIDs).forEach((attributeName) => { const attributeType = Float32Array;
const attributeID = decoder.GetAttributeId( dracoGeometry, decoderModule[attributeIDs[attributeName]] );
if (attributeID === -1) return;
const attribute = decoder.GetAttribute(dracoGeometry, attributeID);
const attributeResult = decodeAttribute( decoder, dracoGeometry, attributeName, attributeType, attribute );
const pointData = polyData.getPointData(); switch (attributeName) { case 'points': polyData .getPoints() .setData(attributeResult.array, attributeResult.itemSize); break; case 'normals': pointData.setNormals( vtkDataArray.newInstance({ numberOfComponents: attributeResult.itemSize, values: attributeResult.array, name: 'Normals', }) ); break; case 'scalars': pointData.setScalars( vtkDataArray.newInstance({ numberOfComponents: attributeResult.itemSize, values: attributeResult.array, name: 'Scalars', }) ); break; case 'tcoords': pointData.setTCoords( vtkDataArray.newInstance({ numberOfComponents: attributeResult.itemSize, values: attributeResult.array, name: 'TCoords', }) ); break; default: break; } });
const hasNormals = polyData.getPointData().getNormals(); if (!hasNormals) { const pdn = vtkPolyDataNormals.newInstance(); pdn.setInputData(polyData); pdn.setComputePointNormals(true); return pdn.getOutputData(); }
return polyData; }
function vtkDracoReader(publicAPI, model) { model.classHierarchy.push('vtkDracoReader');
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) => { publicAPI.parseAsArrayBuffer(content); };
publicAPI.parseAsArrayBuffer = (content) => { if (!content) { return; } if (content !== model.parseData) { publicAPI.modified(); } else { return; }
model.parseData = content;
const byteArray = new Int8Array(content);
const decoder = new decoderModule.Decoder(); const buffer = new decoderModule.DecoderBuffer(); buffer.Init(byteArray, byteArray.length);
const geometryType = decoder.GetEncodedGeometryType(buffer); let dracoGeometry; if (geometryType === decoderModule.TRIANGULAR_MESH) { dracoGeometry = new decoderModule.Mesh(); const status = decoder.DecodeBufferToMesh(buffer, dracoGeometry); if (!status.ok()) { vtkErrorMacro(`Could not decode Draco file: ${status.error_msg()}`); return; } } else { vtkErrorMacro('Wrong geometry type, expected mesh, got point cloud.'); return; } const polyData = getPolyDataFromDracoGeometry(decoder, dracoGeometry); decoderModule.destroy(dracoGeometry); decoderModule.destroy(buffer); decoderModule.destroy(decoder); model.output[0] = polyData; };
publicAPI.requestData = () => { 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);
vtkDracoReader(publicAPI, model);
if (!model.compression) { model.compression = null; } if (!model.progressCallback) { model.progressCallback = null; } }
export const newInstance = macro.newInstance(extend, 'vtkDracoReader');
export default { extend, newInstance, setDracoDecoder, setWasmBinary, getDracoDecoder, };
|