"use strict"; (() => { // ../loader-utils/src/loader-types.ts async function parseFromContext(data, loaders, options, context) { return context._parse(data, loaders, options, context); } // ../worker-utils/src/lib/node/worker_threads-browser.ts var parentPort = null; // ../worker-utils/src/lib/worker-utils/get-transfer-list.ts function getTransferList(object, recursive = true, transfers) { const transfersSet = transfers || /* @__PURE__ */ new Set(); if (!object) { } else if (isTransferable(object)) { transfersSet.add(object); } else if (isTransferable(object.buffer)) { transfersSet.add(object.buffer); } else if (ArrayBuffer.isView(object)) { } else if (recursive && typeof object === "object") { for (const key in object) { getTransferList(object[key], recursive, transfersSet); } } return transfers === void 0 ? Array.from(transfersSet) : []; } function isTransferable(object) { if (!object) { return false; } if (object instanceof ArrayBuffer) { return true; } if (typeof MessagePort !== "undefined" && object instanceof MessagePort) { return true; } if (typeof ImageBitmap !== "undefined" && object instanceof ImageBitmap) { return true; } if (typeof OffscreenCanvas !== "undefined" && object instanceof OffscreenCanvas) { return true; } return false; } // ../worker-utils/src/lib/worker-farm/worker-body.ts async function getParentPort() { return parentPort; } var onMessageWrapperMap = /* @__PURE__ */ new Map(); var WorkerBody = class { /** Check that we are actually in a worker thread */ static async inWorkerThread() { return typeof self !== "undefined" || Boolean(await getParentPort()); } /* * (type: WorkerMessageType, payload: WorkerMessagePayload) => any */ static set onmessage(onMessage) { async function handleMessage(message) { const parentPort2 = await getParentPort(); const { type, payload } = parentPort2 ? message : message.data; onMessage(type, payload); } getParentPort().then((parentPort2) => { if (parentPort2) { parentPort2.on("message", (message) => { handleMessage(message); }); parentPort2.on("exit", () => console.debug("Node worker closing")); } else { globalThis.onmessage = handleMessage; } }); } static async addEventListener(onMessage) { let onMessageWrapper = onMessageWrapperMap.get(onMessage); if (!onMessageWrapper) { onMessageWrapper = async (message) => { if (!isKnownMessage(message)) { return; } const parentPort3 = await getParentPort(); const { type, payload } = parentPort3 ? message : message.data; onMessage(type, payload); }; } const parentPort2 = await getParentPort(); if (parentPort2) { console.error("not implemented"); } else { globalThis.addEventListener("message", onMessageWrapper); } } static async removeEventListener(onMessage) { const onMessageWrapper = onMessageWrapperMap.get(onMessage); onMessageWrapperMap.delete(onMessage); const parentPort2 = await getParentPort(); if (parentPort2) { console.error("not implemented"); } else { globalThis.removeEventListener("message", onMessageWrapper); } } /** * Send a message from a worker to creating thread (main thread) * @param type * @param payload */ static async postMessage(type, payload) { const data = { source: "loaders.gl", type, payload }; const transferList = getTransferList(payload); const parentPort2 = await getParentPort(); if (parentPort2) { parentPort2.postMessage(data, transferList); } else { globalThis.postMessage(data, transferList); } } }; function isKnownMessage(message) { const { type, data } = message; return type === "message" && data && typeof data.source === "string" && data.source.startsWith("loaders.gl"); } // ../loader-utils/src/lib/worker-loader-utils/create-loader-worker.ts var requestId = 0; async function createLoaderWorker(loader) { if (!await WorkerBody.inWorkerThread()) { return; } WorkerBody.onmessage = async (type, payload) => { switch (type) { case "process": try { const { input, options = {}, context = {} } = payload; const result = await parseData({ loader, arrayBuffer: input, options, // @ts-expect-error fetch missing context: { ...context, _parse: parseOnMainThread } }); WorkerBody.postMessage("done", { result }); } catch (error) { const message = error instanceof Error ? error.message : ""; WorkerBody.postMessage("error", { error: message }); } break; default: } }; } function parseOnMainThread(arrayBuffer, loader, options, context) { return new Promise((resolve, reject) => { const id = requestId++; const onMessage = (type, payload2) => { if (payload2.id !== id) { return; } switch (type) { case "done": WorkerBody.removeEventListener(onMessage); resolve(payload2.result); break; case "error": WorkerBody.removeEventListener(onMessage); reject(payload2.error); break; default: } }; WorkerBody.addEventListener(onMessage); const payload = { id, input: arrayBuffer, options }; WorkerBody.postMessage("process", payload); }); } async function parseData({ loader, arrayBuffer, options, context }) { let data; let parser; if (loader.parseSync || loader.parse) { data = arrayBuffer; parser = loader.parseSync || loader.parse; } else if (loader.parseTextSync) { const textDecoder = new TextDecoder(); data = textDecoder.decode(arrayBuffer); parser = loader.parseTextSync; } else { throw new Error(`Could not load data with ${loader.name} loader`); } options = { ...options, modules: loader && loader.options && loader.options.modules || {}, worker: false }; return await parser(data, { ...options }, context, loader); } // ../loader-utils/src/lib/binary-utils/array-buffer-utils.ts function concatenateTypedArrays(...typedArrays) { const arrays = typedArrays; const TypedArrayConstructor = arrays && arrays.length > 1 && arrays[0].constructor || null; if (!TypedArrayConstructor) { throw new Error( '"concatenateTypedArrays" - incorrect quantity of arguments or arguments have incompatible data types' ); } const sumLength = arrays.reduce((acc, value) => acc + value.length, 0); const result = new TypedArrayConstructor(sumLength); let offset = 0; for (const array of arrays) { result.set(array, offset); offset += array.length; } return result; } // ../schema/src/lib/mesh/mesh-utils.ts function getMeshBoundingBox(attributes) { let minX = Infinity; let minY = Infinity; let minZ = Infinity; let maxX = -Infinity; let maxY = -Infinity; let maxZ = -Infinity; const positions = attributes.POSITION ? attributes.POSITION.value : []; const len = positions && positions.length; for (let i = 0; i < len; i += 3) { const x = positions[i]; const y = positions[i + 1]; const z = positions[i + 2]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; minZ = z < minZ ? z : minZ; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; maxZ = z > maxZ ? z : maxZ; } return [ [minX, minY, minZ], [maxX, maxY, maxZ] ]; } // src/lib/decode-quantized-mesh.ts var QUANTIZED_MESH_HEADER = /* @__PURE__ */ 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 ); } function decodeExtensions(dataView, indicesEndPosition) { const 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: { } } position += extensionLength; } return { extensions, extensionsEndPosition: position }; } var DECODING_STEPS = { header: 0, vertices: 1, triangleIndices: 2, edgeIndices: 3, extensions: 4 }; var DEFAULT_OPTIONS = { maxDecodingStep: DECODING_STEPS.extensions }; 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 }; } // src/lib/helpers/skirt.ts function addSkirt(attributes, triangles, skirtHeight, outsideIndices) { const outsideEdges = outsideIndices ? getOutsideEdgesFromIndices(outsideIndices, attributes.POSITION.value) : getOutsideEdgesFromTriangles(triangles); const newPosition = new attributes.POSITION.value.constructor(outsideEdges.length * 6); const newTexcoord0 = new attributes.TEXCOORD_0.value.constructor(outsideEdges.length * 4); const newTriangles = new triangles.constructor(outsideEdges.length * 6); for (let i = 0; i < outsideEdges.length; i++) { const edge = outsideEdges[i]; updateAttributesForNewEdge({ edge, edgeIndex: i, attributes, skirtHeight, newPosition, newTexcoord0, newTriangles }); } attributes.POSITION.value = concatenateTypedArrays(attributes.POSITION.value, newPosition); attributes.TEXCOORD_0.value = concatenateTypedArrays(attributes.TEXCOORD_0.value, newTexcoord0); const resultTriangles = triangles instanceof Array ? triangles.concat(newTriangles) : concatenateTypedArrays(triangles, newTriangles); return { attributes, triangles: resultTriangles }; } function getOutsideEdgesFromTriangles(triangles) { const edges = []; for (let i = 0; i < triangles.length; i += 3) { edges.push([triangles[i], triangles[i + 1]]); edges.push([triangles[i + 1], triangles[i + 2]]); edges.push([triangles[i + 2], triangles[i]]); } edges.sort((a, b) => Math.min(...a) - Math.min(...b) || Math.max(...a) - Math.max(...b)); const outsideEdges = []; let index = 0; while (index < edges.length) { if (edges[index][0] === edges[index + 1]?.[1] && edges[index][1] === edges[index + 1]?.[0]) { index += 2; } else { outsideEdges.push(edges[index]); index++; } } return outsideEdges; } function getOutsideEdgesFromIndices(indices, position) { indices.westIndices.sort((a, b) => position[3 * a + 1] - position[3 * b + 1]); indices.eastIndices.sort((a, b) => position[3 * b + 1] - position[3 * a + 1]); indices.southIndices.sort((a, b) => position[3 * b] - position[3 * a]); indices.northIndices.sort((a, b) => position[3 * a] - position[3 * b]); const edges = []; for (const index in indices) { const indexGroup = indices[index]; for (let i = 0; i < indexGroup.length - 1; i++) { edges.push([indexGroup[i], indexGroup[i + 1]]); } } return edges; } function updateAttributesForNewEdge({ edge, edgeIndex, attributes, skirtHeight, newPosition, newTexcoord0, newTriangles }) { const positionsLength = attributes.POSITION.value.length; const vertex1Offset = edgeIndex * 2; const vertex2Offset = edgeIndex * 2 + 1; newPosition.set( attributes.POSITION.value.subarray(edge[0] * 3, edge[0] * 3 + 3), vertex1Offset * 3 ); newPosition[vertex1Offset * 3 + 2] = newPosition[vertex1Offset * 3 + 2] - skirtHeight; newPosition.set( attributes.POSITION.value.subarray(edge[1] * 3, edge[1] * 3 + 3), vertex2Offset * 3 ); newPosition[vertex2Offset * 3 + 2] = newPosition[vertex2Offset * 3 + 2] - skirtHeight; newTexcoord0.set( attributes.TEXCOORD_0.value.subarray(edge[0] * 2, edge[0] * 2 + 2), vertex1Offset * 2 ); newTexcoord0.set( attributes.TEXCOORD_0.value.subarray(edge[1] * 2, edge[1] * 2 + 2), vertex2Offset * 2 ); const triangle1Offset = edgeIndex * 2 * 3; newTriangles[triangle1Offset] = edge[0]; newTriangles[triangle1Offset + 1] = positionsLength / 3 + vertex2Offset; newTriangles[triangle1Offset + 2] = edge[1]; newTriangles[triangle1Offset + 3] = positionsLength / 3 + vertex2Offset; newTriangles[triangle1Offset + 4] = edge[0]; newTriangles[triangle1Offset + 5] = positionsLength / 3 + vertex1Offset; } // src/lib/parse-quantized-mesh.ts function parseQuantizedMesh(arrayBuffer, options = {}) { const { bounds } = options; const { header, vertexData, triangleIndices: originalTriangleIndices, westIndices, northIndices, eastIndices, southIndices } = decode(arrayBuffer, DECODING_STEPS.triangleIndices); let triangleIndices = originalTriangleIndices; let attributes = getMeshAttributes(vertexData, header, bounds); const boundingBox = getMeshBoundingBox(attributes); if (options?.skirtHeight) { const { attributes: newAttributes, triangles: newTriangles } = addSkirt( attributes, triangleIndices, options.skirtHeight, { westIndices, northIndices, eastIndices, southIndices } ); attributes = newAttributes; triangleIndices = newTriangles; } return { // Data return by this loader implementation loaderData: { header: {} }, header: { // @ts-ignore vertexCount: triangleIndices.length, boundingBox }, // TODO schema: void 0, topology: "triangle-list", mode: 4, // TRIANGLES indices: { value: triangleIndices, size: 1 }, attributes }; } function getMeshAttributes(vertexData, header, bounds) { const { minHeight, maxHeight } = header; const [minX, minY, maxX, maxY] = bounds || [0, 0, 1, 1]; const xScale = maxX - minX; const yScale = maxY - minY; const zScale = maxHeight - minHeight; const nCoords = vertexData.length / 3; const positions = new Float32Array(nCoords * 3); const texCoords = new Float32Array(nCoords * 2); for (let i = 0; i < nCoords; i++) { const x = vertexData[i] / 32767; const y = vertexData[i + nCoords] / 32767; const z = vertexData[i + nCoords * 2] / 32767; positions[3 * i + 0] = x * xScale + minX; positions[3 * i + 1] = y * yScale + minY; positions[3 * i + 2] = z * zScale + minHeight; texCoords[2 * i + 0] = x; texCoords[2 * i + 1] = y; } return { POSITION: { value: positions, size: 3 }, TEXCOORD_0: { value: texCoords, size: 2 } // TODO: Parse normals if they exist in the file // NORMAL: {}, - optional, but creates the high poly look with lighting }; } // ../../node_modules/@mapbox/martini/index.js var Martini = class { constructor(gridSize = 257) { this.gridSize = gridSize; const tileSize = gridSize - 1; if (tileSize & tileSize - 1) throw new Error( `Expected grid size to be 2^n+1, got ${gridSize}.` ); this.numTriangles = tileSize * tileSize * 2 - 2; this.numParentTriangles = this.numTriangles - tileSize * tileSize; this.indices = new Uint32Array(this.gridSize * this.gridSize); this.coords = new Uint16Array(this.numTriangles * 4); for (let i = 0; i < this.numTriangles; i++) { let id = i + 2; let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0; if (id & 1) { bx = by = cx = tileSize; } else { ax = ay = cy = tileSize; } while ((id >>= 1) > 1) { const mx = ax + bx >> 1; const my = ay + by >> 1; if (id & 1) { bx = ax; by = ay; ax = cx; ay = cy; } else { ax = bx; ay = by; bx = cx; by = cy; } cx = mx; cy = my; } const k = i * 4; this.coords[k + 0] = ax; this.coords[k + 1] = ay; this.coords[k + 2] = bx; this.coords[k + 3] = by; } } createTile(terrain) { return new Tile(terrain, this); } }; var Tile = class { constructor(terrain, martini) { const size = martini.gridSize; if (terrain.length !== size * size) throw new Error( `Expected terrain data of length ${size * size} (${size} x ${size}), got ${terrain.length}.` ); this.terrain = terrain; this.martini = martini; this.errors = new Float32Array(terrain.length); this.update(); } update() { const { numTriangles, numParentTriangles, coords, gridSize: size } = this.martini; const { terrain, errors } = this; for (let i = numTriangles - 1; i >= 0; i--) { const k = i * 4; const ax = coords[k + 0]; const ay = coords[k + 1]; const bx = coords[k + 2]; const by = coords[k + 3]; const mx = ax + bx >> 1; const my = ay + by >> 1; const cx = mx + my - ay; const cy = my + ax - mx; const interpolatedHeight = (terrain[ay * size + ax] + terrain[by * size + bx]) / 2; const middleIndex = my * size + mx; const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]); errors[middleIndex] = Math.max(errors[middleIndex], middleError); if (i < numParentTriangles) { const leftChildIndex = (ay + cy >> 1) * size + (ax + cx >> 1); const rightChildIndex = (by + cy >> 1) * size + (bx + cx >> 1); errors[middleIndex] = Math.max(errors[middleIndex], errors[leftChildIndex], errors[rightChildIndex]); } } } getMesh(maxError = 0) { const { gridSize: size, indices } = this.martini; const { errors } = this; let numVertices = 0; let numTriangles = 0; const max = size - 1; indices.fill(0); function countElements(ax, ay, bx, by, cx, cy) { const mx = ax + bx >> 1; const my = ay + by >> 1; if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { countElements(cx, cy, ax, ay, mx, my); countElements(bx, by, cx, cy, mx, my); } else { indices[ay * size + ax] = indices[ay * size + ax] || ++numVertices; indices[by * size + bx] = indices[by * size + bx] || ++numVertices; indices[cy * size + cx] = indices[cy * size + cx] || ++numVertices; numTriangles++; } } countElements(0, 0, max, max, max, 0); countElements(max, max, 0, 0, 0, max); const vertices = new Uint16Array(numVertices * 2); const triangles = new Uint32Array(numTriangles * 3); let triIndex = 0; function processTriangle(ax, ay, bx, by, cx, cy) { const mx = ax + bx >> 1; const my = ay + by >> 1; if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * size + mx] > maxError) { processTriangle(cx, cy, ax, ay, mx, my); processTriangle(bx, by, cx, cy, mx, my); } else { const a = indices[ay * size + ax] - 1; const b = indices[by * size + bx] - 1; const c = indices[cy * size + cx] - 1; vertices[2 * a] = ax; vertices[2 * a + 1] = ay; vertices[2 * b] = bx; vertices[2 * b + 1] = by; vertices[2 * c] = cx; vertices[2 * c + 1] = cy; triangles[triIndex++] = a; triangles[triIndex++] = b; triangles[triIndex++] = c; } } processTriangle(0, 0, max, max, max, 0); processTriangle(max, max, 0, 0, 0, max); return { vertices, triangles }; } }; // src/lib/delatin/index.ts var Delatin = class { constructor(data, width, height = width) { this.data = data; this.width = width; this.height = height; this.coords = []; this.triangles = []; this._halfedges = []; this._candidates = []; this._queueIndices = []; this._queue = []; this._errors = []; this._rms = []; this._pending = []; this._pendingLen = 0; this._rmsSum = 0; const x1 = width - 1; const y1 = height - 1; const p0 = this._addPoint(0, 0); const p1 = this._addPoint(x1, 0); const p2 = this._addPoint(0, y1); const p3 = this._addPoint(x1, y1); const t0 = this._addTriangle(p3, p0, p2, -1, -1, -1); this._addTriangle(p0, p3, p1, t0, -1, -1); this._flush(); } // refine the mesh until its maximum error gets below the given one run(maxError = 1) { while (this.getMaxError() > maxError) { this.refine(); } } // refine the mesh with a single point refine() { this._step(); this._flush(); } // max error of the current mesh getMaxError() { return this._errors[0]; } // root-mean-square deviation of the current mesh getRMSD() { return this._rmsSum > 0 ? Math.sqrt(this._rmsSum / (this.width * this.height)) : 0; } // height value at a given position heightAt(x, y) { return this.data[this.width * y + x]; } // rasterize and queue all triangles that got added or updated in _step _flush() { const coords = this.coords; for (let i = 0; i < this._pendingLen; i++) { const t = this._pending[i]; const a = 2 * this.triangles[t * 3 + 0]; const b = 2 * this.triangles[t * 3 + 1]; const c = 2 * this.triangles[t * 3 + 2]; this._findCandidate( coords[a], coords[a + 1], coords[b], coords[b + 1], coords[c], coords[c + 1], t ); } this._pendingLen = 0; } // rasterize a triangle, find its max error, and queue it for processing _findCandidate(p0x, p0y, p1x, p1y, p2x, p2y, t) { const minX = Math.min(p0x, p1x, p2x); const minY = Math.min(p0y, p1y, p2y); const maxX = Math.max(p0x, p1x, p2x); const maxY = Math.max(p0y, p1y, p2y); let w00 = orient(p1x, p1y, p2x, p2y, minX, minY); let w01 = orient(p2x, p2y, p0x, p0y, minX, minY); let w02 = orient(p0x, p0y, p1x, p1y, minX, minY); const a01 = p1y - p0y; const b01 = p0x - p1x; const a12 = p2y - p1y; const b12 = p1x - p2x; const a20 = p0y - p2y; const b20 = p2x - p0x; const a = orient(p0x, p0y, p1x, p1y, p2x, p2y); const z0 = this.heightAt(p0x, p0y) / a; const z1 = this.heightAt(p1x, p1y) / a; const z2 = this.heightAt(p2x, p2y) / a; let maxError = 0; let mx = 0; let my = 0; let rms = 0; for (let y = minY; y <= maxY; y++) { let dx = 0; if (w00 < 0 && a12 !== 0) { dx = Math.max(dx, Math.floor(-w00 / a12)); } if (w01 < 0 && a20 !== 0) { dx = Math.max(dx, Math.floor(-w01 / a20)); } if (w02 < 0 && a01 !== 0) { dx = Math.max(dx, Math.floor(-w02 / a01)); } let w0 = w00 + a12 * dx; let w1 = w01 + a20 * dx; let w2 = w02 + a01 * dx; let wasInside = false; for (let x = minX + dx; x <= maxX; x++) { if (w0 >= 0 && w1 >= 0 && w2 >= 0) { wasInside = true; const z = z0 * w0 + z1 * w1 + z2 * w2; const dz = Math.abs(z - this.heightAt(x, y)); rms += dz * dz; if (dz > maxError) { maxError = dz; mx = x; my = y; } } else if (wasInside) { break; } w0 += a12; w1 += a20; w2 += a01; } w00 += b12; w01 += b20; w02 += b01; } if (mx === p0x && my === p0y || mx === p1x && my === p1y || mx === p2x && my === p2y) { maxError = 0; } this._candidates[2 * t] = mx; this._candidates[2 * t + 1] = my; this._rms[t] = rms; this._queuePush(t, maxError, rms); } // process the next triangle in the queue, splitting it with a new point _step() { const t = this._queuePop(); const e0 = t * 3 + 0; const e1 = t * 3 + 1; const e2 = t * 3 + 2; const p0 = this.triangles[e0]; const p1 = this.triangles[e1]; const p2 = this.triangles[e2]; const ax = this.coords[2 * p0]; const ay = this.coords[2 * p0 + 1]; const bx = this.coords[2 * p1]; const by = this.coords[2 * p1 + 1]; const cx = this.coords[2 * p2]; const cy = this.coords[2 * p2 + 1]; const px = this._candidates[2 * t]; const py = this._candidates[2 * t + 1]; const pn = this._addPoint(px, py); if (orient(ax, ay, bx, by, px, py) === 0) { this._handleCollinear(pn, e0); } else if (orient(bx, by, cx, cy, px, py) === 0) { this._handleCollinear(pn, e1); } else if (orient(cx, cy, ax, ay, px, py) === 0) { this._handleCollinear(pn, e2); } else { const h0 = this._halfedges[e0]; const h1 = this._halfedges[e1]; const h2 = this._halfedges[e2]; const t0 = this._addTriangle(p0, p1, pn, h0, -1, -1, e0); const t1 = this._addTriangle(p1, p2, pn, h1, -1, t0 + 1); const t2 = this._addTriangle(p2, p0, pn, h2, t0 + 2, t1 + 1); this._legalize(t0); this._legalize(t1); this._legalize(t2); } } // add coordinates for a new vertex _addPoint(x, y) { const i = this.coords.length >> 1; this.coords.push(x, y); return i; } // add or update a triangle in the mesh _addTriangle(a, b, c, ab, bc, ca, e = this.triangles.length) { const t = e / 3; this.triangles[e + 0] = a; this.triangles[e + 1] = b; this.triangles[e + 2] = c; this._halfedges[e + 0] = ab; this._halfedges[e + 1] = bc; this._halfedges[e + 2] = ca; if (ab >= 0) { this._halfedges[ab] = e + 0; } if (bc >= 0) { this._halfedges[bc] = e + 1; } if (ca >= 0) { this._halfedges[ca] = e + 2; } this._candidates[2 * t + 0] = 0; this._candidates[2 * t + 1] = 0; this._queueIndices[t] = -1; this._rms[t] = 0; this._pending[this._pendingLen++] = t; return e; } _legalize(a) { const b = this._halfedges[a]; if (b < 0) { return; } const a0 = a - a % 3; const b0 = b - b % 3; const al = a0 + (a + 1) % 3; const ar = a0 + (a + 2) % 3; const bl = b0 + (b + 2) % 3; const br = b0 + (b + 1) % 3; const p0 = this.triangles[ar]; const pr = this.triangles[a]; const pl = this.triangles[al]; const p1 = this.triangles[bl]; const coords = this.coords; if (!inCircle( coords[2 * p0], coords[2 * p0 + 1], coords[2 * pr], coords[2 * pr + 1], coords[2 * pl], coords[2 * pl + 1], coords[2 * p1], coords[2 * p1 + 1] )) { return; } const hal = this._halfedges[al]; const har = this._halfedges[ar]; const hbl = this._halfedges[bl]; const hbr = this._halfedges[br]; this._queueRemove(a0 / 3); this._queueRemove(b0 / 3); const t0 = this._addTriangle(p0, p1, pl, -1, hbl, hal, a0); const t1 = this._addTriangle(p1, p0, pr, t0, har, hbr, b0); this._legalize(t0 + 1); this._legalize(t1 + 2); } // handle a case where new vertex is on the edge of a triangle _handleCollinear(pn, a) { const a0 = a - a % 3; const al = a0 + (a + 1) % 3; const ar = a0 + (a + 2) % 3; const p0 = this.triangles[ar]; const pr = this.triangles[a]; const pl = this.triangles[al]; const hal = this._halfedges[al]; const har = this._halfedges[ar]; const b = this._halfedges[a]; if (b < 0) { const t02 = this._addTriangle(pn, p0, pr, -1, har, -1, a0); const t12 = this._addTriangle(p0, pn, pl, t02, -1, hal); this._legalize(t02 + 1); this._legalize(t12 + 2); return; } const b0 = b - b % 3; const bl = b0 + (b + 2) % 3; const br = b0 + (b + 1) % 3; const p1 = this.triangles[bl]; const hbl = this._halfedges[bl]; const hbr = this._halfedges[br]; this._queueRemove(b0 / 3); const t0 = this._addTriangle(p0, pr, pn, har, -1, -1, a0); const t1 = this._addTriangle(pr, p1, pn, hbr, -1, t0 + 1, b0); const t2 = this._addTriangle(p1, pl, pn, hbl, -1, t1 + 1); const t3 = this._addTriangle(pl, p0, pn, hal, t0 + 2, t2 + 1); this._legalize(t0); this._legalize(t1); this._legalize(t2); this._legalize(t3); } // priority queue methods _queuePush(t, error, rms) { const i = this._queue.length; this._queueIndices[t] = i; this._queue.push(t); this._errors.push(error); this._rmsSum += rms; this._queueUp(i); } _queuePop() { const n = this._queue.length - 1; this._queueSwap(0, n); this._queueDown(0, n); return this._queuePopBack(); } _queuePopBack() { const t = this._queue.pop(); this._errors.pop(); this._rmsSum -= this._rms[t]; this._queueIndices[t] = -1; return t; } _queueRemove(t) { const i = this._queueIndices[t]; if (i < 0) { const it = this._pending.indexOf(t); if (it !== -1) { this._pending[it] = this._pending[--this._pendingLen]; } else { throw new Error("Broken triangulation (something went wrong)."); } return; } const n = this._queue.length - 1; if (n !== i) { this._queueSwap(i, n); if (!this._queueDown(i, n)) { this._queueUp(i); } } this._queuePopBack(); } _queueLess(i, j) { return this._errors[i] > this._errors[j]; } _queueSwap(i, j) { const pi = this._queue[i]; const pj = this._queue[j]; this._queue[i] = pj; this._queue[j] = pi; this._queueIndices[pi] = j; this._queueIndices[pj] = i; const e = this._errors[i]; this._errors[i] = this._errors[j]; this._errors[j] = e; } _queueUp(j0) { let j = j0; while (true) { const i = j - 1 >> 1; if (i === j || !this._queueLess(j, i)) { break; } this._queueSwap(i, j); j = i; } } _queueDown(i0, n) { let i = i0; while (true) { const j1 = 2 * i + 1; if (j1 >= n || j1 < 0) { break; } const j2 = j1 + 1; let j = j1; if (j2 < n && this._queueLess(j2, j1)) { j = j2; } if (!this._queueLess(j, i)) { break; } this._queueSwap(i, j); i = j; } return i > i0; } }; function orient(ax, ay, bx, by, cx, cy) { return (bx - cx) * (ay - cy) - (by - cy) * (ax - cx); } function inCircle(ax, ay, bx, by, cx, cy, px, py) { const dx = ax - px; const dy = ay - py; const ex = bx - px; const ey = by - py; const fx = cx - px; const fy = cy - py; const ap = dx * dx + dy * dy; const bp = ex * ex + ey * ey; const cp = fx * fx + fy * fy; return dx * (ey * cp - bp * fy) - dy * (ex * cp - bp * fx) + ap * (ex * fy - ey * fx) < 0; } // src/lib/parse-terrain.ts function makeTerrainMeshFromImage(terrainImage, terrainOptions) { const { meshMaxError, bounds, elevationDecoder } = terrainOptions; const { data, width, height } = terrainImage; let terrain; let mesh; switch (terrainOptions.tesselator) { case "martini": terrain = getTerrain(data, width, height, elevationDecoder, terrainOptions.tesselator); mesh = getMartiniTileMesh(meshMaxError, width, terrain); break; case "delatin": terrain = getTerrain(data, width, height, elevationDecoder, terrainOptions.tesselator); mesh = getDelatinTileMesh(meshMaxError, width, height, terrain); break; default: if (width === height && !(height & width - 1)) { terrain = getTerrain(data, width, height, elevationDecoder, "martini"); mesh = getMartiniTileMesh(meshMaxError, width, terrain); } else { terrain = getTerrain(data, width, height, elevationDecoder, "delatin"); mesh = getDelatinTileMesh(meshMaxError, width, height, terrain); } break; } const { vertices } = mesh; let { triangles } = mesh; let attributes = getMeshAttributes2(vertices, terrain, width, height, bounds); const boundingBox = getMeshBoundingBox(attributes); if (terrainOptions.skirtHeight) { const { attributes: newAttributes, triangles: newTriangles } = addSkirt( attributes, triangles, terrainOptions.skirtHeight ); attributes = newAttributes; triangles = newTriangles; } return { // Data return by this loader implementation loaderData: { header: {} }, header: { vertexCount: triangles.length, boundingBox }, mode: 4, // TRIANGLES indices: { value: Uint32Array.from(triangles), size: 1 }, attributes }; } function getMartiniTileMesh(meshMaxError, width, terrain) { const gridSize = width + 1; const martini = new Martini(gridSize); const tile = martini.createTile(terrain); const { vertices, triangles } = tile.getMesh(meshMaxError); return { vertices, triangles }; } function getDelatinTileMesh(meshMaxError, width, height, terrain) { const tin = new Delatin(terrain, width + 1, height + 1); tin.run(meshMaxError); const { coords, triangles } = tin; const vertices = coords; return { vertices, triangles }; } function getTerrain(imageData, width, height, elevationDecoder, tesselator) { const { rScaler, bScaler, gScaler, offset } = elevationDecoder; const terrain = new Float32Array((width + 1) * (height + 1)); for (let i = 0, y = 0; y < height; y++) { for (let x = 0; x < width; x++, i++) { const k = i * 4; const r = imageData[k + 0]; const g = imageData[k + 1]; const b = imageData[k + 2]; terrain[i + y] = r * rScaler + g * gScaler + b * bScaler + offset; } } if (tesselator === "martini") { for (let i = (width + 1) * width, x = 0; x < width; x++, i++) { terrain[i] = terrain[i - width - 1]; } for (let i = height, y = 0; y < height + 1; y++, i += height + 1) { terrain[i] = terrain[i - 1]; } } return terrain; } function getMeshAttributes2(vertices, terrain, width, height, bounds) { const gridSize = width + 1; const numOfVerticies = vertices.length / 2; const positions = new Float32Array(numOfVerticies * 3); const texCoords = new Float32Array(numOfVerticies * 2); const [minX, minY, maxX, maxY] = bounds || [0, 0, width, height]; const xScale = (maxX - minX) / width; const yScale = (maxY - minY) / height; for (let i = 0; i < numOfVerticies; i++) { const x = vertices[i * 2]; const y = vertices[i * 2 + 1]; const pixelIdx = y * gridSize + x; positions[3 * i + 0] = x * xScale + minX; positions[3 * i + 1] = -y * yScale + maxY; positions[3 * i + 2] = terrain[pixelIdx]; texCoords[2 * i + 0] = x / width; texCoords[2 * i + 1] = y / height; } return { POSITION: { value: positions, size: 3 }, TEXCOORD_0: { value: texCoords, size: 2 } // NORMAL: {}, - optional, but creates the high poly look with lighting }; } // src/lib/utils/version.ts var VERSION = true ? "4.3.1" : "latest"; // src/terrain-loader.ts var TerrainLoader = { dataType: null, batchType: null, name: "Terrain", id: "terrain", module: "terrain", version: VERSION, worker: true, extensions: ["png", "pngraw", "jpg", "jpeg", "gif", "webp", "bmp"], mimeTypes: ["image/png", "image/jpeg", "image/gif", "image/webp", "image/bmp"], options: { terrain: { tesselator: "auto", bounds: void 0, meshMaxError: 10, elevationDecoder: { rScaler: 1, gScaler: 0, bScaler: 0, offset: 0 }, skirtHeight: void 0 } } }; // src/quantized-mesh-loader.ts var QuantizedMeshLoader = { dataType: null, // Mesh, batchType: null, name: "Quantized Mesh", id: "quantized-mesh", module: "terrain", version: VERSION, worker: true, extensions: ["terrain"], mimeTypes: ["application/vnd.quantized-mesh"], options: { "quantized-mesh": { bounds: [0, 0, 1, 1], skirtHeight: null } } }; // src/index.ts var TerrainLoader2 = { ...TerrainLoader, parse: parseTerrain }; async function parseTerrain(arrayBuffer, options, context) { const loadImageOptions = { ...options, mimeType: "application/x.image", image: { ...options?.image, type: "data" } }; const image = await parseFromContext(arrayBuffer, [], loadImageOptions, context); const terrainOptions = { ...TerrainLoader2.options.terrain, ...options?.terrain }; return makeTerrainMeshFromImage(image, terrainOptions); } var QuantizedMeshLoader2 = { ...QuantizedMeshLoader, parseSync: (arrayBuffer, options) => parseQuantizedMesh(arrayBuffer, options?.["quantized-mesh"]), parse: async (arrayBuffer, options) => parseQuantizedMesh(arrayBuffer, options?.["quantized-mesh"]) }; // src/workers/terrain-worker.ts createLoaderWorker(TerrainLoader2); })();