@loaders.gl/terrain
Version:
Framework-independent loader for terrain raster formats
943 lines (930 loc) • 33.2 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// dist/index.js
var dist_exports = {};
__export(dist_exports, {
QuantizedMeshLoader: () => QuantizedMeshLoader2,
QuantizedMeshWorkerLoader: () => QuantizedMeshLoader,
TerrainLoader: () => TerrainLoader2,
TerrainWorkerLoader: () => TerrainLoader,
parseTerrain: () => parseTerrain
});
module.exports = __toCommonJS(dist_exports);
var import_loader_utils2 = require("@loaders.gl/loader-utils");
// dist/lib/parse-quantized-mesh.js
var import_schema = require("@loaders.gl/schema");
// dist/lib/decode-quantized-mesh.js
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
};
}
// dist/lib/helpers/skirt.js
var import_loader_utils = require("@loaders.gl/loader-utils");
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 = (0, import_loader_utils.concatenateTypedArrays)(attributes.POSITION.value, newPosition);
attributes.TEXCOORD_0.value = (0, import_loader_utils.concatenateTypedArrays)(attributes.TEXCOORD_0.value, newTexcoord0);
const resultTriangles = triangles instanceof Array ? triangles.concat(newTriangles) : (0, import_loader_utils.concatenateTypedArrays)(triangles, newTriangles);
return {
attributes,
triangles: resultTriangles
};
}
function getOutsideEdgesFromTriangles(triangles) {
var _a, _b;
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] === ((_a = edges[index + 1]) == null ? void 0 : _a[1]) && edges[index][1] === ((_b = edges[index + 1]) == null ? void 0 : _b[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;
}
// dist/lib/parse-quantized-mesh.js
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 = (0, import_schema.getMeshBoundingBox)(attributes);
if (options == null ? void 0 : 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
};
}
// dist/lib/parse-terrain.js
var import_schema2 = require("@loaders.gl/schema");
var import_martini = __toESM(require("@mapbox/martini"), 1);
// dist/lib/delatin/index.js
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;
}
// dist/lib/parse-terrain.js
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 = (0, import_schema2.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 import_martini.default(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
};
}
// dist/lib/utils/version.js
var VERSION = true ? "4.3.2" : "latest";
// dist/terrain-loader.js
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
}
}
};
// dist/quantized-mesh-loader.js
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
}
}
};
// dist/index.js
var TerrainLoader2 = {
...TerrainLoader,
parse: parseTerrain
};
async function parseTerrain(arrayBuffer, options, context) {
const loadImageOptions = {
...options,
mimeType: "application/x.image",
image: { ...options == null ? void 0 : options.image, type: "data" }
};
const image = await (0, import_loader_utils2.parseFromContext)(arrayBuffer, [], loadImageOptions, context);
const terrainOptions = { ...TerrainLoader2.options.terrain, ...options == null ? void 0 : options.terrain };
return makeTerrainMeshFromImage(image, terrainOptions);
}
var QuantizedMeshLoader2 = {
...QuantizedMeshLoader,
parseSync: (arrayBuffer, options) => parseQuantizedMesh(arrayBuffer, options == null ? void 0 : options["quantized-mesh"]),
parse: async (arrayBuffer, options) => parseQuantizedMesh(arrayBuffer, options == null ? void 0 : options["quantized-mesh"])
};
//# sourceMappingURL=index.cjs.map