UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

342 lines (312 loc) 10.7 kB
import { m as macro } from '../../macros2.js'; import DataAccessHelper from '../Core/DataAccessHelper.js'; import vtkCellArray from '../../Common/Core/CellArray.js'; import vtkDataArray from '../../Common/Core/DataArray.js'; import vtkPolyData from '../../Common/DataModel/PolyData.js'; import vtkPolyDataNormals from '../../Filters/Core/PolyDataNormals.js'; import '../Core/DataAccessHelper/LiteHttpDataAccessHelper.js'; // import 'vtk.js/Sources/IO/Core/DataAccessHelper/HttpDataAccessHelper'; // HTTP + zip // import 'vtk.js/Sources/IO/Core/DataAccessHelper/HtmlDataAccessHelper'; // html + base64 + zip // import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; // zip const { vtkErrorMacro } = macro; let decoderModule = null; // ---------------------------------------------------------------------------- // static methods // ---------------------------------------------------------------------------- /** * Load the WASM decoder from url and set the decoderModule * @param url * @param binaryName * @return {Promise<boolean>} */ 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; // Use Promise.resolve to be compatible with versions before Draco 1.4.0 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); }); } /** * Set the Draco decoder module * @param {*} dracoDecoder */ async function setDracoDecoder(dracoDecoder) { decoderModule = await dracoDecoder({}); } function getDracoDecoder() { return decoderModule; } // ---------------------------------------------------------------------------- // vtkDracoReader methods // ---------------------------------------------------------------------------- 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; } } /** * Decode a single attribute * @param {*} decoder The Draco decoder * @param {*} dracoGeometry The geometry to decode * @param {*} attributeName The name of the attribute * @param {*} attributeType The type of the attribute * @param {*} attribute The attribute to decode * @returns object with name, array, itemSize */ 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); // eslint-disable-next-line new-cap const array = new attributeType(decoderModule.HEAPF32.buffer, ptr, numValues).slice(); decoderModule._free(ptr); return { name: attributeName, array, itemSize: numComponents }; } /** * Decode the indices of the geometry * @param {*} decoder The Draco decoder * @param {*} dracoGeometry The geometry to decode * @returns The indices array of the geometry */ 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; } /** * Get the polyData from the Draco geometry * @param {*} decoder The Draco decoder * @param {*} dracoGeometry The geometry to decode * @returns {vtkPolyData} The polyData of the geometry */ function getPolyDataFromDracoGeometry(decoder, dracoGeometry) { const indices = decodeIndices(decoder, dracoGeometry); const nCells = indices.length - 2; const cells = vtkCellArray.newInstance(); cells.allocate(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 }); // Look for attributes 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; } }); // we will generate normals if they're missing 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) { // Set our className model.classHierarchy.push('vtkDracoReader'); // Create default dataAccessHelper if not available if (!model.dataAccessHelper) { model.dataAccessHelper = DataAccessHelper.get('http'); } // Internal method to fetch Array 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 }); } // Set DataSet url publicAPI.setUrl = (url, option = { binary: true }) => { model.url = url; // Remove the file in the URL const path = url.split('/'); path.pop(); model.baseURL = path.join('/'); model.compression = option.compression; // Fetch metadata return publicAPI.loadData({ progressCallback: option.progressCallback, binary: !!option.binary }); }; // Fetch the actual data arrays 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); }; } // ---------------------------------------------------------------------------- // Object factory // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { // baseURL: null, // dataAccessHelper: null, // url: null, }; // ---------------------------------------------------------------------------- function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); // Build VTK API macro.obj(publicAPI, model); macro.get(publicAPI, model, ['url', 'baseURL']); macro.setGet(publicAPI, model, ['dataAccessHelper']); macro.algo(publicAPI, model, 0, 1); // vtkDracoReader methods vtkDracoReader(publicAPI, model); // To support destructuring if (!model.compression) { model.compression = null; } if (!model.progressCallback) { model.progressCallback = null; } } // ---------------------------------------------------------------------------- const newInstance = macro.newInstance(extend, 'vtkDracoReader'); // ---------------------------------------------------------------------------- var vtkDracoReader$1 = { extend, newInstance, setDracoDecoder, setWasmBinary, getDracoDecoder }; export { vtkDracoReader$1 as default, extend, newInstance };