// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors // Copyright (C) 2018-2019 HERE Europe B.V. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. const QUANTIZED_MESH_HEADER = new Map([ ['centerX', Float64Array.BYTES_PER_ELEMENT], ['centerY', Float64Array.BYTES_PER_ELEMENT], ['centerZ', Float64Array.BYTES_PER_ELEMENT], ['minHeight', Float32Array.BYTES_PER_ELEMENT], ['maxHeight', Float32Array.BYTES_PER_ELEMENT], ['boundingSphereCenterX', Float64Array.BYTES_PER_ELEMENT], ['boundingSphereCenterY', Float64Array.BYTES_PER_ELEMENT], ['boundingSphereCenterZ', Float64Array.BYTES_PER_ELEMENT], ['boundingSphereRadius', Float64Array.BYTES_PER_ELEMENT], ['horizonOcclusionPointX', Float64Array.BYTES_PER_ELEMENT], ['horizonOcclusionPointY', Float64Array.BYTES_PER_ELEMENT], ['horizonOcclusionPointZ', Float64Array.BYTES_PER_ELEMENT] ]); function decodeZigZag(value) { return (value >> 1) ^ -(value & 1); } function decodeHeader(dataView) { let position = 0; const header = {}; for (const [key, bytesCount] of QUANTIZED_MESH_HEADER) { const getter = bytesCount === 8 ? dataView.getFloat64 : dataView.getFloat32; header[key] = getter.call(dataView, position, true); position += bytesCount; } return {header, headerEndPosition: position}; } function decodeVertexData(dataView, headerEndPosition) { let position = headerEndPosition; const elementsPerVertex = 3; const vertexCount = dataView.getUint32(position, true); const vertexData = new Uint16Array(vertexCount * elementsPerVertex); position += Uint32Array.BYTES_PER_ELEMENT; const bytesPerArrayElement = Uint16Array.BYTES_PER_ELEMENT; const elementArrayLength = vertexCount * bytesPerArrayElement; const uArrayStartPosition = position; const vArrayStartPosition = uArrayStartPosition + elementArrayLength; const heightArrayStartPosition = vArrayStartPosition + elementArrayLength; let u = 0; let v = 0; let height = 0; for (let i = 0; i < vertexCount; i++) { u += decodeZigZag(dataView.getUint16(uArrayStartPosition + bytesPerArrayElement * i, true)); v += decodeZigZag(dataView.getUint16(vArrayStartPosition + bytesPerArrayElement * i, true)); height += decodeZigZag( dataView.getUint16(heightArrayStartPosition + bytesPerArrayElement * i, true) ); vertexData[i] = u; vertexData[i + vertexCount] = v; vertexData[i + vertexCount * 2] = height; } position += elementArrayLength * 3; return {vertexData, vertexDataEndPosition: position}; } function decodeIndex(buffer, position, indicesCount, bytesPerIndex, encoded = true) { let indices; if (bytesPerIndex === 2) { indices = new Uint16Array(buffer, position, indicesCount); } else { indices = new Uint32Array(buffer, position, indicesCount); } if (!encoded) { return indices; } let highest = 0; for (let i = 0; i < indices.length; ++i) { const code = indices[i]; indices[i] = highest - code; if (code === 0) { ++highest; } } return indices; } function decodeTriangleIndices(dataView, vertexData, vertexDataEndPosition) { let position = vertexDataEndPosition; const elementsPerVertex = 3; const vertexCount = vertexData.length / elementsPerVertex; const bytesPerIndex = vertexCount > 65536 ? Uint32Array.BYTES_PER_ELEMENT : Uint16Array.BYTES_PER_ELEMENT; if (position % bytesPerIndex !== 0) { position += bytesPerIndex - (position % bytesPerIndex); } const triangleCount = dataView.getUint32(position, true); position += Uint32Array.BYTES_PER_ELEMENT; const triangleIndicesCount = triangleCount * 3; const triangleIndices = decodeIndex( dataView.buffer, position, triangleIndicesCount, bytesPerIndex ); position += triangleIndicesCount * bytesPerIndex; return { triangleIndicesEndPosition: position, triangleIndices }; } function decodeEdgeIndices(dataView, vertexData, triangleIndicesEndPosition) { let position = triangleIndicesEndPosition; const elementsPerVertex = 3; const vertexCount = vertexData.length / elementsPerVertex; const bytesPerIndex = vertexCount > 65536 ? Uint32Array.BYTES_PER_ELEMENT : Uint16Array.BYTES_PER_ELEMENT; const westVertexCount = dataView.getUint32(position, true); position += Uint32Array.BYTES_PER_ELEMENT; const westIndices = decodeIndex(dataView.buffer, position, westVertexCount, bytesPerIndex, false); position += westVertexCount * bytesPerIndex; const southVertexCount = dataView.getUint32(position, true); position += Uint32Array.BYTES_PER_ELEMENT; const southIndices = decodeIndex( dataView.buffer, position, southVertexCount, bytesPerIndex, false ); position += southVertexCount * bytesPerIndex; const eastVertexCount = dataView.getUint32(position, true); position += Uint32Array.BYTES_PER_ELEMENT; const eastIndices = decodeIndex(dataView.buffer, position, eastVertexCount, bytesPerIndex, false); position += eastVertexCount * bytesPerIndex; const northVertexCount = dataView.getUint32(position, true); position += Uint32Array.BYTES_PER_ELEMENT; const northIndices = decodeIndex( dataView.buffer, position, northVertexCount, bytesPerIndex, false ); position += northVertexCount * bytesPerIndex; return { edgeIndicesEndPosition: position, westIndices, southIndices, eastIndices, northIndices }; } function decodeVertexNormalsExtension(extensionDataView) { return new Uint8Array( extensionDataView.buffer, extensionDataView.byteOffset, extensionDataView.byteLength ); } function decodeWaterMaskExtension(extensionDataView) { return extensionDataView.buffer.slice( extensionDataView.byteOffset, extensionDataView.byteOffset + extensionDataView.byteLength ); } type Extensions = { vertexNormals?: any; waterMask?: any; }; function decodeExtensions(dataView, indicesEndPosition) { const extensions: Extensions = {}; if (dataView.byteLength <= indicesEndPosition) { return {extensions, extensionsEndPosition: indicesEndPosition}; } let position = indicesEndPosition; while (position < dataView.byteLength) { const extensionId = dataView.getUint8(position, true); position += Uint8Array.BYTES_PER_ELEMENT; const extensionLength = dataView.getUint32(position, true); position += Uint32Array.BYTES_PER_ELEMENT; const extensionView = new DataView(dataView.buffer, position, extensionLength); switch (extensionId) { case 1: { extensions.vertexNormals = decodeVertexNormalsExtension(extensionView); break; } case 2: { extensions.waterMask = decodeWaterMaskExtension(extensionView); break; } default: { // console.warn(`Unknown extension with id ${extensionId}`) } } position += extensionLength; } return {extensions, extensionsEndPosition: position}; } export const DECODING_STEPS = { header: 0, vertices: 1, triangleIndices: 2, edgeIndices: 3, extensions: 4 }; const DEFAULT_OPTIONS = { maxDecodingStep: DECODING_STEPS.extensions }; export default function decode(data, userOptions) { const options = Object.assign({}, DEFAULT_OPTIONS, userOptions); const view = new DataView(data); const {header, headerEndPosition} = decodeHeader(view); if (options.maxDecodingStep < DECODING_STEPS.vertices) { return {header}; } const {vertexData, vertexDataEndPosition} = decodeVertexData(view, headerEndPosition); if (options.maxDecodingStep < DECODING_STEPS.triangleIndices) { return {header, vertexData}; } const {triangleIndices, triangleIndicesEndPosition} = decodeTriangleIndices( view, vertexData, vertexDataEndPosition ); if (options.maxDecodingStep < DECODING_STEPS.edgeIndices) { return {header, vertexData, triangleIndices}; } const {westIndices, southIndices, eastIndices, northIndices, edgeIndicesEndPosition} = decodeEdgeIndices(view, vertexData, triangleIndicesEndPosition); if (options.maxDecodingStep < DECODING_STEPS.extensions) { return { header, vertexData, triangleIndices, westIndices, northIndices, eastIndices, southIndices }; } const {extensions} = decodeExtensions(view, edgeIndicesEndPosition); return { header, vertexData, triangleIndices, westIndices, northIndices, eastIndices, southIndices, extensions }; }