diff --git a/Sources/IO/Geometry/DracoReader/example/index.js b/Sources/IO/Geometry/DracoReader/example/index.js index 26466b986b1..0eaa10df760 100644 --- a/Sources/IO/Geometry/DracoReader/example/index.js +++ b/Sources/IO/Geometry/DracoReader/example/index.js @@ -49,16 +49,13 @@ function update() { // Dynamic script loading from CDN // ---------------------------------------------------------------------------- -// Prevent error when draco try to set the export on module -window.module = {}; - // Add new script tag with draco CDN vtkResourceLoader - .loadScript('https://unpkg.com/draco3d@1.3.4/draco_decoder_nodejs.js') - .then(() => { + .loadScript('https://unpkg.com/draco3d@1.5.7/draco_decoder_nodejs.js') + .then(async () => { // Set decoder function to the vtk reader - vtkDracoReader.setDracoDecoder(window.CreateDracoModule); - + // eslint-disable-next-line no-undef + await vtkDracoReader.setDracoDecoder(DracoDecoderModule); // Trigger data download reader .setUrl( diff --git a/Sources/IO/Geometry/DracoReader/index.d.ts b/Sources/IO/Geometry/DracoReader/index.d.ts index cf63c658be0..9085956e811 100755 --- a/Sources/IO/Geometry/DracoReader/index.d.ts +++ b/Sources/IO/Geometry/DracoReader/index.d.ts @@ -118,15 +118,15 @@ export function newInstance( ): vtkDracoReader; /** - * + * Get the draco decoder */ export function getDracoDecoder(): any; /** - * - * @param createDracoModule + * Set the draco decoder + * @param dracoDecoder */ -export function setDracoDecoder(createDracoModule: any): void; +export async function setDracoDecoder(dracoDecoder: any): void; /** * Load the WASM decoder from url and set the decoderModule diff --git a/Sources/IO/Geometry/DracoReader/index.js b/Sources/IO/Geometry/DracoReader/index.js index a07ef724002..58076989029 100644 --- a/Sources/IO/Geometry/DracoReader/index.js +++ b/Sources/IO/Geometry/DracoReader/index.js @@ -1,8 +1,9 @@ -import DataAccessHelper from 'vtk.js/Sources/IO/Core/DataAccessHelper'; 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'; // Enable data soure for DataAccessHelper import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper'; // Just need HTTP @@ -11,8 +12,7 @@ import 'vtk.js/Sources/IO/Core/DataAccessHelper/LiteHttpDataAccessHelper'; // Ju // import 'vtk.js/Sources/IO/Core/DataAccessHelper/JSZipDataAccessHelper'; // zip const { vtkErrorMacro } = macro; -let decoderModule = {}; - +let decoderModule = null; // ---------------------------------------------------------------------------- // static methods // ---------------------------------------------------------------------------- @@ -52,8 +52,12 @@ function setWasmBinary(url, binaryName) { }); } -function setDracoDecoder(createDracoModule) { - decoderModule = createDracoModule({}); +/** + * Set the Draco decoder module + * @param {*} dracoDecoder + */ +async function setDracoDecoder(dracoDecoder) { + decoderModule = await dracoDecoder({}); } function getDracoDecoder() { @@ -63,161 +67,192 @@ function getDracoDecoder() { // ---------------------------------------------------------------------------- // vtkDracoReader methods // ---------------------------------------------------------------------------- - -function decodeBuffer(buffer) { - const byteArray = new Int8Array(buffer); - const decoder = new decoderModule.Decoder(); - const decoderBuffer = new decoderModule.DecoderBuffer(); - decoderBuffer.Init(byteArray, byteArray.length); - - const geometryType = decoder.GetEncodedGeometryType(decoderBuffer); - - let dracoGeometry; - if (geometryType === decoderModule.TRIANGULAR_MESH) { - dracoGeometry = new decoderModule.Mesh(); - const status = decoder.DecodeBufferToMesh(decoderBuffer, dracoGeometry); - if (!status.ok()) { - vtkErrorMacro(`Could not decode Draco file: ${status.error_msg()}`); - } - } else { - vtkErrorMacro('Wrong geometry type, expected mesh, got point cloud.'); +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; } - - decoderModule.destroy(decoderBuffer); - decoderModule.destroy(decoder); - return dracoGeometry; } -function getDracoAttributeAsFloat32Array(dracoGeometry, attributeId) { - const decoder = new decoderModule.Decoder(); - const attribute = decoder.GetAttribute(dracoGeometry, attributeId); - const numberOfComponents = attribute.num_components(); - const numberOfPoints = dracoGeometry.num_points(); - - const attributeData = new decoderModule.DracoFloat32Array(); - decoder.GetAttributeFloatForAllPoints( +/** + * 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, - attributeData + dataType, + byteLength, + ptr ); - let i = numberOfPoints * numberOfComponents; - const attributeArray = new Float32Array(i); - while (i--) { - attributeArray[i] = attributeData.GetValue(i); - } - - return attributeArray; -} + // eslint-disable-next-line new-cap + const array = new attributeType( + decoderModule.HEAPF32.buffer, + ptr, + numValues + ).slice(); -function getPolyDataFromDracoGeometry(dracoGeometry) { - const decoder = new decoderModule.Decoder(); + decoderModule._free(ptr); - // Get position attribute ID - const positionAttributeId = decoder.GetAttributeId( - dracoGeometry, - decoderModule.POSITION - ); - - if (positionAttributeId === -1) { - console.error('No position attribute found in the decoded model.'); - decoderModule.destroy(decoder); - decoderModule.destroy(dracoGeometry); - return null; - } + return { + name: attributeName, + array, + itemSize: numComponents, + }; +} - const positionArray = getDracoAttributeAsFloat32Array( - dracoGeometry, - positionAttributeId, - decoderModule - ); +/** + * 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; +} - // Read indices - let i = dracoGeometry.num_faces(); - const indices = new Uint32Array(i * 4); - const indicesArray = new decoderModule.DracoInt32Array(); - while (i--) { - decoder.GetFaceFromMesh(dracoGeometry, i, indicesArray); - const index = i * 4; - indices[index] = 3; - indices[index + 1] = indicesArray.GetValue(0); - indices[index + 2] = indicesArray.GetValue(1); - indices[index + 3] = indicesArray.GetValue(2); +/** + * 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.resize((4 * indices.length) / 3); + for (let cellId = 0; cellId < nCells; cellId += 3) { + const cell = indices.slice(cellId, cellId + 3); + cells.insertNextCell(cell); } - // Create polyData and add positions and indinces - const cellArray = vtkCellArray.newInstance({ values: indices }); - const polyData = vtkPolyData.newInstance({ polys: cellArray }); - polyData.getPoints().setData(positionArray); + const polyData = vtkPolyData.newInstance({ polys: cells }); - // Look for other attributes - const pointData = polyData.getPointData(); - - // Normals - const normalAttributeId = decoder.GetAttributeId( - dracoGeometry, - decoderModule.NORMAL - ); - - if (normalAttributeId !== -1) { - const normalArray = getDracoAttributeAsFloat32Array( - dracoGeometry, - decoderModule.NORMAL, - decoderModule - ); - - const normals = vtkDataArray.newInstance({ - numberOfComponents: 3, - values: normalArray, - name: 'Normals', - }); - pointData.setNormals(normals); - } + // Look for attributes + const attributeIDs = { + points: 'POSITION', + normals: 'NORMAL', + scalars: 'COLOR', + tcoords: 'TEX_COORD', + }; - // Texture coordinates - const texCoordAttributeId = decoder.GetAttributeId( - dracoGeometry, - decoderModule.TEX_COORD - ); + Object.keys(attributeIDs).forEach((attributeName) => { + const attributeType = Float32Array; - if (texCoordAttributeId !== -1) { - const texCoordArray = getDracoAttributeAsFloat32Array( + const attributeID = decoder.GetAttributeId( dracoGeometry, - texCoordAttributeId, - decoderModule + decoderModule[attributeIDs[attributeName]] ); - const texCoords = vtkDataArray.newInstance({ - numberOfComponents: 2, - values: texCoordArray, - name: 'TCoords', - }); - pointData.setTCoords(texCoords); - } + if (attributeID === -1) return; - // Scalars - const colorAttributeId = decoder.GetAttributeId( - dracoGeometry, - decoderModule.COLOR - ); + const attribute = decoder.GetAttribute(dracoGeometry, attributeID); - if (colorAttributeId !== -1) { - const colorArray = getDracoAttributeAsFloat32Array( + const attributeResult = decodeAttribute( + decoder, dracoGeometry, - colorAttributeId, - decoderModule + attributeName, + attributeType, + attribute ); - const scalars = vtkDataArray.newInstance({ - numberOfComponents: 3, - values: colorArray, - name: 'Scalars', - }); + 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; + } + }); - pointData.setScalars(scalars); + // 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(); } - decoderModule.destroy(decoder); return polyData; } @@ -285,9 +320,30 @@ function vtkDracoReader(publicAPI, model) { } model.parseData = content; - const dracoGeometry = decodeBuffer(content); - const polyData = getPolyDataFromDracoGeometry(dracoGeometry); + + 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; }; diff --git a/Sources/IO/Geometry/GLTFImporter/Extensions.js b/Sources/IO/Geometry/GLTFImporter/Extensions.js index 3075fcc8187..897c0c10ea3 100644 --- a/Sources/IO/Geometry/GLTFImporter/Extensions.js +++ b/Sources/IO/Geometry/GLTFImporter/Extensions.js @@ -86,7 +86,7 @@ export function handleKHRLightsPunctual(extension, transformMatrix, model) { * * @param {object} extension - The KHR_draco_mesh_compression extension object. */ -export async function handleKHRDracoMeshCompression(extension) { +export function handleKHRDracoMeshCompression(extension) { const reader = vtkDracoReader.newInstance(); reader.parse(extension.bufferView); return reader.getOutputData(); diff --git a/Sources/IO/Geometry/GLTFImporter/example/index.js b/Sources/IO/Geometry/GLTFImporter/example/index.js index 3565f6d0966..260de4f6ae9 100644 --- a/Sources/IO/Geometry/GLTFImporter/example/index.js +++ b/Sources/IO/Geometry/GLTFImporter/example/index.js @@ -267,12 +267,12 @@ fetch(`${baseUrl}/${modelsFolder}/model-index.json`) if (selectedFlavor === 'glTF-Draco') { vtkResourceLoader .loadScript( - 'https://unpkg.com/draco3dgltf@1.3.6/draco_decoder_gltf_nodejs.js' + 'https://unpkg.com/draco3dgltf@1.5.7/draco_decoder_gltf_nodejs.js' ) - .then(() => { + .then(async () => { // Set decoder function to the vtk reader // eslint-disable-next-line no-undef - reader.setDracoDecoder(DracoDecoderModule); + await reader.setDracoDecoder(DracoDecoderModule); reader .setUrl(url, { binary: true, sceneId: selectedScene }) .then(reader.onReady(ready)); diff --git a/Sources/IO/Geometry/GLTFImporter/index.d.ts b/Sources/IO/Geometry/GLTFImporter/index.d.ts index 8009302b4be..f75ebe6f233 100644 --- a/Sources/IO/Geometry/GLTFImporter/index.d.ts +++ b/Sources/IO/Geometry/GLTFImporter/index.d.ts @@ -183,25 +183,25 @@ export interface vtkGLTFImporter extends vtkGLTFImporterBase { /** * Set the camera id. - * @param cameraId + * @param cameraId The camera id. */ setCamera(cameraId: string): void; /** * Set the Draco decoder. - * @param mappings + * @param dracoDecoder */ - setDracoDecoder(decoder: any): void; + async setDracoDecoder(dracoDecoder: any): void; /** * Set the vtk Renderer. - * @param renderer + * @param renderer The vtk Renderer. */ setRenderer(renderer: vtkRenderer): void; /** * Switch to a variant. - * @param variantIndex + * @param variantIndex The index of the variant to switch to. */ switchToVariant(variantIndex: number): void; } diff --git a/Sources/IO/Geometry/GLTFImporter/index.js b/Sources/IO/Geometry/GLTFImporter/index.js index 553e60f680a..e5175f976fa 100644 --- a/Sources/IO/Geometry/GLTFImporter/index.js +++ b/Sources/IO/Geometry/GLTFImporter/index.js @@ -144,8 +144,8 @@ function vtkGLTFImporter(publicAPI, model) { publicAPI.parse(model.parseData); }; - publicAPI.setDracoDecoder = (decoder) => { - vtkDracoReader.setDracoDecoder(decoder); + publicAPI.setDracoDecoder = async (dracoDecoder) => { + await vtkDracoReader.setDracoDecoder(dracoDecoder); }; publicAPI.importActors = () => {