Add existing to tracked

This commit is contained in:
Jay
2026-08-11 09:53:42 -04:00
parent afe07f3055
commit ffd6e3d73c
8531 changed files with 4396230 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
import PrimitivePipeline from "../Scene/PrimitivePipeline.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
function combineGeometry(packedParameters, transferableObjects) {
const parameters =
PrimitivePipeline.unpackCombineGeometryParameters(packedParameters);
const results = PrimitivePipeline.combineGeometry(parameters);
return PrimitivePipeline.packCombineGeometryResults(
results,
transferableObjects,
);
}
export default createTaskProcessorWorker(combineGeometry);
+10
View File
@@ -0,0 +1,10 @@
import BoxGeometry from "../Core/BoxGeometry.js";
import defined from "../Core/defined.js";
function createBoxGeometry(boxGeometry, offset) {
if (defined(offset)) {
boxGeometry = BoxGeometry.unpack(boxGeometry, offset);
}
return BoxGeometry.createGeometry(boxGeometry);
}
export default createBoxGeometry;
+10
View File
@@ -0,0 +1,10 @@
import BoxOutlineGeometry from "../Core/BoxOutlineGeometry.js";
import defined from "../Core/defined.js";
function createBoxOutlineGeometry(boxGeometry, offset) {
if (defined(offset)) {
boxGeometry = BoxOutlineGeometry.unpack(boxGeometry, offset);
}
return BoxOutlineGeometry.createGeometry(boxGeometry);
}
export default createBoxOutlineGeometry;
+18
View File
@@ -0,0 +1,18 @@
import Cartesian3 from "../Core/Cartesian3.js";
import CircleGeometry from "../Core/CircleGeometry.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createCircleGeometry(circleGeometry, offset) {
if (defined(offset)) {
circleGeometry = CircleGeometry.unpack(circleGeometry, offset);
}
circleGeometry._ellipseGeometry._center = Cartesian3.clone(
circleGeometry._ellipseGeometry._center,
);
circleGeometry._ellipseGeometry._ellipsoid = Ellipsoid.clone(
circleGeometry._ellipseGeometry._ellipsoid,
);
return CircleGeometry.createGeometry(circleGeometry);
}
export default createCircleGeometry;
@@ -0,0 +1,18 @@
import Cartesian3 from "../Core/Cartesian3.js";
import CircleOutlineGeometry from "../Core/CircleOutlineGeometry.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createCircleOutlineGeometry(circleGeometry, offset) {
if (defined(offset)) {
circleGeometry = CircleOutlineGeometry.unpack(circleGeometry, offset);
}
circleGeometry._ellipseGeometry._center = Cartesian3.clone(
circleGeometry._ellipseGeometry._center,
);
circleGeometry._ellipseGeometry._ellipsoid = Ellipsoid.clone(
circleGeometry._ellipseGeometry._ellipsoid,
);
return CircleOutlineGeometry.createGeometry(circleGeometry);
}
export default createCircleOutlineGeometry;
@@ -0,0 +1,10 @@
import CoplanarPolygonGeometry from "../Core/CoplanarPolygonGeometry.js";
import defined from "../Core/defined.js";
function createCoplanarPolygonGeometry(polygonGeometry, offset) {
if (defined(offset)) {
polygonGeometry = CoplanarPolygonGeometry.unpack(polygonGeometry, offset);
}
return CoplanarPolygonGeometry.createGeometry(polygonGeometry);
}
export default createCoplanarPolygonGeometry;
@@ -0,0 +1,15 @@
import CoplanarPolygonOutlineGeometry from "../Core/CoplanarPolygonOutlineGeometry.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createCoplanarPolygonOutlineGeometry(polygonGeometry, offset) {
if (defined(offset)) {
polygonGeometry = CoplanarPolygonOutlineGeometry.unpack(
polygonGeometry,
offset,
);
}
polygonGeometry._ellipsoid = Ellipsoid.clone(polygonGeometry._ellipsoid);
return CoplanarPolygonOutlineGeometry.createGeometry(polygonGeometry);
}
export default createCoplanarPolygonOutlineGeometry;
+12
View File
@@ -0,0 +1,12 @@
import CorridorGeometry from "../Core/CorridorGeometry.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createCorridorGeometry(corridorGeometry, offset) {
if (defined(offset)) {
corridorGeometry = CorridorGeometry.unpack(corridorGeometry, offset);
}
corridorGeometry._ellipsoid = Ellipsoid.clone(corridorGeometry._ellipsoid);
return CorridorGeometry.createGeometry(corridorGeometry);
}
export default createCorridorGeometry;
@@ -0,0 +1,17 @@
import CorridorOutlineGeometry from "../Core/CorridorOutlineGeometry.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createCorridorOutlineGeometry(corridorOutlineGeometry, offset) {
if (defined(offset)) {
corridorOutlineGeometry = CorridorOutlineGeometry.unpack(
corridorOutlineGeometry,
offset,
);
}
corridorOutlineGeometry._ellipsoid = Ellipsoid.clone(
corridorOutlineGeometry._ellipsoid,
);
return CorridorOutlineGeometry.createGeometry(corridorOutlineGeometry);
}
export default createCorridorOutlineGeometry;
+10
View File
@@ -0,0 +1,10 @@
import CylinderGeometry from "../Core/CylinderGeometry.js";
import defined from "../Core/defined.js";
function createCylinderGeometry(cylinderGeometry, offset) {
if (defined(offset)) {
cylinderGeometry = CylinderGeometry.unpack(cylinderGeometry, offset);
}
return CylinderGeometry.createGeometry(cylinderGeometry);
}
export default createCylinderGeometry;
@@ -0,0 +1,10 @@
import CylinderOutlineGeometry from "../Core/CylinderOutlineGeometry.js";
import defined from "../Core/defined.js";
function createCylinderOutlineGeometry(cylinderGeometry, offset) {
if (defined(offset)) {
cylinderGeometry = CylinderOutlineGeometry.unpack(cylinderGeometry, offset);
}
return CylinderOutlineGeometry.createGeometry(cylinderGeometry);
}
export default createCylinderOutlineGeometry;
+14
View File
@@ -0,0 +1,14 @@
import Cartesian3 from "../Core/Cartesian3.js";
import defined from "../Core/defined.js";
import EllipseGeometry from "../Core/EllipseGeometry.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createEllipseGeometry(ellipseGeometry, offset) {
if (defined(offset)) {
ellipseGeometry = EllipseGeometry.unpack(ellipseGeometry, offset);
}
ellipseGeometry._center = Cartesian3.clone(ellipseGeometry._center);
ellipseGeometry._ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid);
return EllipseGeometry.createGeometry(ellipseGeometry);
}
export default createEllipseGeometry;
@@ -0,0 +1,14 @@
import Cartesian3 from "../Core/Cartesian3.js";
import defined from "../Core/defined.js";
import EllipseOutlineGeometry from "../Core/EllipseOutlineGeometry.js";
import Ellipsoid from "../Core/Ellipsoid.js";
function createEllipseOutlineGeometry(ellipseGeometry, offset) {
if (defined(offset)) {
ellipseGeometry = EllipseOutlineGeometry.unpack(ellipseGeometry, offset);
}
ellipseGeometry._center = Cartesian3.clone(ellipseGeometry._center);
ellipseGeometry._ellipsoid = Ellipsoid.clone(ellipseGeometry._ellipsoid);
return EllipseOutlineGeometry.createGeometry(ellipseGeometry);
}
export default createEllipseOutlineGeometry;
+10
View File
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import EllipsoidGeometry from "../Core/EllipsoidGeometry.js";
function createEllipsoidGeometry(ellipsoidGeometry, offset) {
if (defined(offset)) {
ellipsoidGeometry = EllipsoidGeometry.unpack(ellipsoidGeometry, offset);
}
return EllipsoidGeometry.createGeometry(ellipsoidGeometry);
}
export default createEllipsoidGeometry;
@@ -0,0 +1,13 @@
import defined from "../Core/defined.js";
import EllipsoidOutlineGeometry from "../Core/EllipsoidOutlineGeometry.js";
function createEllipsoidOutlineGeometry(ellipsoidGeometry, offset) {
if (defined(ellipsoidGeometry.buffer, offset)) {
ellipsoidGeometry = EllipsoidOutlineGeometry.unpack(
ellipsoidGeometry,
offset,
);
}
return EllipsoidOutlineGeometry.createGeometry(ellipsoidGeometry);
}
export default createEllipsoidOutlineGeometry;
+10
View File
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import FrustumGeometry from "../Core/FrustumGeometry.js";
function createFrustumGeometry(frustumGeometry, offset) {
if (defined(offset)) {
frustumGeometry = FrustumGeometry.unpack(frustumGeometry, offset);
}
return FrustumGeometry.createGeometry(frustumGeometry);
}
export default createFrustumGeometry;
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import FrustumOutlineGeometry from "../Core/FrustumOutlineGeometry.js";
function createFrustumOutlineGeometry(frustumGeometry, offset) {
if (defined(offset)) {
frustumGeometry = FrustumOutlineGeometry.unpack(frustumGeometry, offset);
}
return FrustumOutlineGeometry.createGeometry(frustumGeometry);
}
export default createFrustumOutlineGeometry;
+78
View File
@@ -0,0 +1,78 @@
import DeveloperError from "../Core/DeveloperError.js";
import defined from "../Core/defined.js";
import PrimitivePipeline from "../Scene/PrimitivePipeline.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
/* global require */
const moduleCache = {};
async function getModule(moduleName, modulePath) {
let module = moduleCache[modulePath] ?? moduleCache[moduleName];
if (defined(module)) {
return module;
}
if (defined(modulePath)) {
// ignore moduleName and use the path to import
if (typeof exports === "object") {
// Use CommonJS-style require.
module = require(modulePath);
} else {
// Use ESM-style dynamic import
const result = await import(modulePath);
module = result.default;
}
moduleCache[modulePath] = module;
return module;
}
if (typeof exports === "object") {
// Use CommonJS-style require.
module = require(`Workers/${moduleName}`);
} else {
// Use ESM-style dynamic import
const result = defined(modulePath)
? await import(modulePath)
: await import(`./${moduleName}.js`);
module = result.default;
}
moduleCache[moduleName] = module;
return module;
}
async function createGeometry(parameters, transferableObjects) {
const subTasks = parameters.subTasks;
const length = subTasks.length;
const resultsOrPromises = new Array(length);
for (let i = 0; i < length; i++) {
const task = subTasks[i];
const geometry = task.geometry;
const moduleName = task.moduleName;
const modulePath = task.modulePath;
if (defined(moduleName) && defined(modulePath)) {
throw new DeveloperError("Must only set moduleName or modulePath");
}
if (defined(moduleName) || defined(modulePath)) {
resultsOrPromises[i] = getModule(moduleName, modulePath).then(
(createFunction) => createFunction(geometry, task.offset),
);
} else {
// Already created geometry
resultsOrPromises[i] = geometry;
}
}
return Promise.all(resultsOrPromises).then(function (results) {
return PrimitivePipeline.packCreateGeometryResults(
results,
transferableObjects,
);
});
}
export default createTaskProcessorWorker(createGeometry);
@@ -0,0 +1,16 @@
import ApproximateTerrainHeights from "../Core/ApproximateTerrainHeights.js";
import defined from "../Core/defined.js";
import GroundPolylineGeometry from "../Core/GroundPolylineGeometry.js";
function createGroundPolylineGeometry(groundPolylineGeometry, offset) {
return ApproximateTerrainHeights.initialize().then(function () {
if (defined(offset)) {
groundPolylineGeometry = GroundPolylineGeometry.unpack(
groundPolylineGeometry,
offset,
);
}
return GroundPolylineGeometry.createGeometry(groundPolylineGeometry);
});
}
export default createGroundPolylineGeometry;
+10
View File
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import PlaneGeometry from "../Core/PlaneGeometry.js";
function createPlaneGeometry(planeGeometry, offset) {
if (defined(offset)) {
planeGeometry = PlaneGeometry.unpack(planeGeometry, offset);
}
return PlaneGeometry.createGeometry(planeGeometry);
}
export default createPlaneGeometry;
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import PlaneOutlineGeometry from "../Core/PlaneOutlineGeometry.js";
function createPlaneOutlineGeometry(planeGeometry, offset) {
if (defined(offset)) {
planeGeometry = PlaneOutlineGeometry.unpack(planeGeometry, offset);
}
return PlaneOutlineGeometry.createGeometry(planeGeometry);
}
export default createPlaneOutlineGeometry;
+12
View File
@@ -0,0 +1,12 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import PolygonGeometry from "../Core/PolygonGeometry.js";
function createPolygonGeometry(polygonGeometry, offset) {
if (defined(offset)) {
polygonGeometry = PolygonGeometry.unpack(polygonGeometry, offset);
}
polygonGeometry._ellipsoid = Ellipsoid.clone(polygonGeometry._ellipsoid);
return PolygonGeometry.createGeometry(polygonGeometry);
}
export default createPolygonGeometry;
@@ -0,0 +1,12 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import PolygonOutlineGeometry from "../Core/PolygonOutlineGeometry.js";
function createPolygonOutlineGeometry(polygonGeometry, offset) {
if (defined(offset)) {
polygonGeometry = PolygonOutlineGeometry.unpack(polygonGeometry, offset);
}
polygonGeometry._ellipsoid = Ellipsoid.clone(polygonGeometry._ellipsoid);
return PolygonOutlineGeometry.createGeometry(polygonGeometry);
}
export default createPolygonOutlineGeometry;
+12
View File
@@ -0,0 +1,12 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import PolylineGeometry from "../Core/PolylineGeometry.js";
function createPolylineGeometry(polylineGeometry, offset) {
if (defined(offset)) {
polylineGeometry = PolylineGeometry.unpack(polylineGeometry, offset);
}
polylineGeometry._ellipsoid = Ellipsoid.clone(polylineGeometry._ellipsoid);
return PolylineGeometry.createGeometry(polylineGeometry);
}
export default createPolylineGeometry;
@@ -0,0 +1,17 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import PolylineVolumeGeometry from "../Core/PolylineVolumeGeometry.js";
function createPolylineVolumeGeometry(polylineVolumeGeometry, offset) {
if (defined(offset)) {
polylineVolumeGeometry = PolylineVolumeGeometry.unpack(
polylineVolumeGeometry,
offset,
);
}
polylineVolumeGeometry._ellipsoid = Ellipsoid.clone(
polylineVolumeGeometry._ellipsoid,
);
return PolylineVolumeGeometry.createGeometry(polylineVolumeGeometry);
}
export default createPolylineVolumeGeometry;
@@ -0,0 +1,22 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import PolylineVolumeOutlineGeometry from "../Core/PolylineVolumeOutlineGeometry.js";
function createPolylineVolumeOutlineGeometry(
polylineVolumeOutlineGeometry,
offset,
) {
if (defined(offset)) {
polylineVolumeOutlineGeometry = PolylineVolumeOutlineGeometry.unpack(
polylineVolumeOutlineGeometry,
offset,
);
}
polylineVolumeOutlineGeometry._ellipsoid = Ellipsoid.clone(
polylineVolumeOutlineGeometry._ellipsoid,
);
return PolylineVolumeOutlineGeometry.createGeometry(
polylineVolumeOutlineGeometry,
);
}
export default createPolylineVolumeOutlineGeometry;
+14
View File
@@ -0,0 +1,14 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import Rectangle from "../Core/Rectangle.js";
import RectangleGeometry from "../Core/RectangleGeometry.js";
function createRectangleGeometry(rectangleGeometry, offset) {
if (defined(offset)) {
rectangleGeometry = RectangleGeometry.unpack(rectangleGeometry, offset);
}
rectangleGeometry._ellipsoid = Ellipsoid.clone(rectangleGeometry._ellipsoid);
rectangleGeometry._rectangle = Rectangle.clone(rectangleGeometry._rectangle);
return RectangleGeometry.createGeometry(rectangleGeometry);
}
export default createRectangleGeometry;
@@ -0,0 +1,17 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import Rectangle from "../Core/Rectangle.js";
import RectangleOutlineGeometry from "../Core/RectangleOutlineGeometry.js";
function createRectangleOutlineGeometry(rectangleGeometry, offset) {
if (defined(offset)) {
rectangleGeometry = RectangleOutlineGeometry.unpack(
rectangleGeometry,
offset,
);
}
rectangleGeometry._ellipsoid = Ellipsoid.clone(rectangleGeometry._ellipsoid);
rectangleGeometry._rectangle = Rectangle.clone(rectangleGeometry._rectangle);
return RectangleOutlineGeometry.createGeometry(rectangleGeometry);
}
export default createRectangleOutlineGeometry;
@@ -0,0 +1,17 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import SimplePolylineGeometry from "../Core/SimplePolylineGeometry.js";
function createSimplePolylineGeometry(simplePolylineGeometry, offset) {
if (defined(offset)) {
simplePolylineGeometry = SimplePolylineGeometry.unpack(
simplePolylineGeometry,
offset,
);
}
simplePolylineGeometry._ellipsoid = Ellipsoid.clone(
simplePolylineGeometry._ellipsoid,
);
return SimplePolylineGeometry.createGeometry(simplePolylineGeometry);
}
export default createSimplePolylineGeometry;
+10
View File
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import SphereGeometry from "../Core/SphereGeometry.js";
function createSphereGeometry(sphereGeometry, offset) {
if (defined(offset)) {
sphereGeometry = SphereGeometry.unpack(sphereGeometry, offset);
}
return SphereGeometry.createGeometry(sphereGeometry);
}
export default createSphereGeometry;
@@ -0,0 +1,10 @@
import defined from "../Core/defined.js";
import SphereOutlineGeometry from "../Core/SphereOutlineGeometry.js";
function createSphereOutlineGeometry(sphereGeometry, offset) {
if (defined(offset)) {
sphereGeometry = SphereOutlineGeometry.unpack(sphereGeometry, offset);
}
return SphereOutlineGeometry.createGeometry(sphereGeometry);
}
export default createSphereOutlineGeometry;
+113
View File
@@ -0,0 +1,113 @@
import formatError from "../Core/formatError.js";
/**
* Creates an adapter function to allow a calculation function to operate as a Web Worker,
* paired with TaskProcessor, to receive tasks and return results.
*
* @function createTaskProcessorWorker
*
* @param {createTaskProcessorWorker.WorkerFunction} workerFunction The calculation function,
* which takes parameters and returns a result.
* @returns {createTaskProcessorWorker.TaskProcessorWorkerFunction} A function that adapts the
* calculation function to work as a Web Worker onmessage listener with TaskProcessor.
*
*
* @example
* function doCalculation(parameters, transferableObjects) {
* // calculate some result using the inputs in parameters
* return result;
* }
*
* return Cesium.createTaskProcessorWorker(doCalculation);
* // the resulting function is compatible with TaskProcessor
*
* @see TaskProcessor
* @see {@link http://www.w3.org/TR/workers/|Web Workers}
* @see {@link http://www.w3.org/TR/html5/common-dom-interfaces.html#transferable-objects|Transferable objects}
*/
function createTaskProcessorWorker(workerFunction) {
async function onMessageHandler({ data }) {
const transferableObjects = [];
const responseMessage = {
id: data.id,
result: undefined,
error: undefined,
};
self.CESIUM_BASE_URL = data.baseUrl;
try {
const result = await workerFunction(data.parameters, transferableObjects);
responseMessage.result = result;
} catch (error) {
if (error instanceof Error) {
responseMessage.error = {
name: error.name,
message: error.message,
stack: error.stack,
};
} else {
responseMessage.error = error;
}
}
if (!data.canTransferArrayBuffer) {
transferableObjects.length = 0;
}
try {
postMessage(responseMessage, transferableObjects);
} catch (error) {
// something went wrong trying to post the message, post a simpler
// error that we can be sure will be cloneable
responseMessage.result = undefined;
responseMessage.error = `postMessage failed with error: ${formatError(
error,
)}\n with responseMessage: ${JSON.stringify(responseMessage)}`;
postMessage(responseMessage);
}
}
function onMessageErrorHandler(event) {
postMessage({
id: event.data?.id,
error: `postMessage failed with error: ${JSON.stringify(event)}`,
});
}
self.onmessage = onMessageHandler;
self.onmessageerror = onMessageErrorHandler;
return self;
}
/**
* A function that performs a calculation in a Web Worker.
* @callback createTaskProcessorWorker.WorkerFunction
*
* @param {object} parameters Parameters to the calculation.
* @param {Array} transferableObjects An array that should be filled with references to objects inside
* the result that should be transferred back to the main document instead of copied.
* @returns {object} The result of the calculation.
*
* @example
* function calculate(parameters, transferableObjects) {
* // perform whatever calculation is necessary.
* const typedArray = new Float32Array(0);
*
* // typed arrays are transferable
* transferableObjects.push(typedArray)
*
* return {
* typedArray : typedArray
* };
* }
*/
/**
* A Web Worker message event handler function that handles the interaction with TaskProcessor,
* specifically, task ID management and posting a response message containing the result.
* @callback createTaskProcessorWorker.TaskProcessorWorkerFunction
*
* @param {object} event The onmessage event object.
*/
export default createTaskProcessorWorker;
@@ -0,0 +1,541 @@
import AttributeCompression from "../Core/AttributeCompression.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import combine from "../Core/combine.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import CesiumMath from "../Core/Math.js";
import Rectangle from "../Core/Rectangle.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const MAX_SHORT = 32767;
const MITER_BREAK = Math.cos(CesiumMath.toRadians(150.0));
const scratchBVCartographic = new Cartographic();
const scratchEncodedPosition = new Cartesian3();
function decodePositions(
uBuffer,
vBuffer,
heightBuffer,
rectangle,
minimumHeight,
maximumHeight,
ellipsoid,
) {
const positionsLength = uBuffer.length;
const decodedPositions = new Float64Array(positionsLength * 3);
for (let i = 0; i < positionsLength; ++i) {
const u = uBuffer[i];
const v = vBuffer[i];
const h = heightBuffer[i];
const lon = CesiumMath.lerp(rectangle.west, rectangle.east, u / MAX_SHORT);
const lat = CesiumMath.lerp(
rectangle.south,
rectangle.north,
v / MAX_SHORT,
);
const alt = CesiumMath.lerp(minimumHeight, maximumHeight, h / MAX_SHORT);
const cartographic = Cartographic.fromRadians(
lon,
lat,
alt,
scratchBVCartographic,
);
const decodedPosition = ellipsoid.cartographicToCartesian(
cartographic,
scratchEncodedPosition,
);
Cartesian3.pack(decodedPosition, decodedPositions, i * 3);
}
return decodedPositions;
}
function getPositionOffsets(counts) {
const countsLength = counts.length;
const positionOffsets = new Uint32Array(countsLength + 1);
let offset = 0;
for (let i = 0; i < countsLength; ++i) {
positionOffsets[i] = offset;
offset += counts[i];
}
positionOffsets[countsLength] = offset;
return positionOffsets;
}
const previousCompressedCartographicScratch = new Cartographic();
const currentCompressedCartographicScratch = new Cartographic();
function removeDuplicates(uBuffer, vBuffer, heightBuffer, counts) {
const countsLength = counts.length;
const positionsLength = uBuffer.length;
const markRemoval = new Uint8Array(positionsLength);
const previous = previousCompressedCartographicScratch;
const current = currentCompressedCartographicScratch;
let offset = 0;
for (let i = 0; i < countsLength; i++) {
const count = counts[i];
let updatedCount = count;
for (let j = 1; j < count; j++) {
const index = offset + j;
const previousIndex = index - 1;
current.longitude = uBuffer[index];
current.latitude = vBuffer[index];
previous.longitude = uBuffer[previousIndex];
previous.latitude = vBuffer[previousIndex];
if (Cartographic.equals(current, previous)) {
updatedCount--;
markRemoval[previousIndex] = 1;
}
}
counts[i] = updatedCount;
offset += count;
}
let nextAvailableIndex = 0;
for (let k = 0; k < positionsLength; k++) {
if (markRemoval[k] !== 1) {
uBuffer[nextAvailableIndex] = uBuffer[k];
vBuffer[nextAvailableIndex] = vBuffer[k];
heightBuffer[nextAvailableIndex] = heightBuffer[k];
nextAvailableIndex++;
}
}
}
function VertexAttributesAndIndices(volumesCount) {
const vertexCount = volumesCount * 8;
const vec3Floats = vertexCount * 3;
const vec4Floats = vertexCount * 4;
this.startEllipsoidNormals = new Float32Array(vec3Floats);
this.endEllipsoidNormals = new Float32Array(vec3Floats);
this.startPositionAndHeights = new Float32Array(vec4Floats);
this.startFaceNormalAndVertexCornerIds = new Float32Array(vec4Floats);
this.endPositionAndHeights = new Float32Array(vec4Floats);
this.endFaceNormalAndHalfWidths = new Float32Array(vec4Floats);
this.vertexBatchIds = new Uint16Array(vertexCount);
this.indices = IndexDatatype.createTypedArray(vertexCount, 36 * volumesCount);
this.vec3Offset = 0;
this.vec4Offset = 0;
this.batchIdOffset = 0;
this.indexOffset = 0;
this.volumeStartIndex = 0;
}
const towardCurrScratch = new Cartesian3();
const towardNextScratch = new Cartesian3();
function computeMiteredNormal(
previousPosition,
position,
nextPosition,
ellipsoidSurfaceNormal,
result,
) {
const towardNext = Cartesian3.subtract(
nextPosition,
position,
towardNextScratch,
);
let towardCurr = Cartesian3.subtract(
position,
previousPosition,
towardCurrScratch,
);
Cartesian3.normalize(towardNext, towardNext);
Cartesian3.normalize(towardCurr, towardCurr);
if (Cartesian3.dot(towardNext, towardCurr) < MITER_BREAK) {
towardCurr = Cartesian3.multiplyByScalar(
towardCurr,
-1.0,
towardCurrScratch,
);
}
Cartesian3.add(towardNext, towardCurr, result);
if (Cartesian3.equals(result, Cartesian3.ZERO)) {
result = Cartesian3.subtract(previousPosition, position);
}
// Make sure the normal is orthogonal to the ellipsoid surface normal
Cartesian3.cross(result, ellipsoidSurfaceNormal, result);
Cartesian3.cross(ellipsoidSurfaceNormal, result, result);
Cartesian3.normalize(result, result);
return result;
}
// Winding order is reversed so each segment's volume is inside-out
// 3-----------7
// /| left /|
// / | 1 / |
// 2-----------6 5 end
// | / | /
// start |/ right |/
// 0-----------4
//
const REFERENCE_INDICES = [
0,
2,
6,
0,
6,
4, // right
0,
1,
3,
0,
3,
2, // start face
0,
4,
5,
0,
5,
1, // bottom
5,
3,
1,
5,
7,
3, // left
7,
5,
4,
7,
4,
6, // end face
7,
6,
2,
7,
2,
3, // top
];
const REFERENCE_INDICES_LENGTH = REFERENCE_INDICES.length;
const positionScratch = new Cartesian3();
const scratchStartEllipsoidNormal = new Cartesian3();
const scratchStartFaceNormal = new Cartesian3();
const scratchEndEllipsoidNormal = new Cartesian3();
const scratchEndFaceNormal = new Cartesian3();
VertexAttributesAndIndices.prototype.addVolume = function (
preStartRTC,
startRTC,
endRTC,
postEndRTC,
startHeight,
endHeight,
halfWidth,
batchId,
center,
ellipsoid,
) {
let position = Cartesian3.add(startRTC, center, positionScratch);
const startEllipsoidNormal = ellipsoid.geodeticSurfaceNormal(
position,
scratchStartEllipsoidNormal,
);
position = Cartesian3.add(endRTC, center, positionScratch);
const endEllipsoidNormal = ellipsoid.geodeticSurfaceNormal(
position,
scratchEndEllipsoidNormal,
);
const startFaceNormal = computeMiteredNormal(
preStartRTC,
startRTC,
endRTC,
startEllipsoidNormal,
scratchStartFaceNormal,
);
const endFaceNormal = computeMiteredNormal(
postEndRTC,
endRTC,
startRTC,
endEllipsoidNormal,
scratchEndFaceNormal,
);
const startEllipsoidNormals = this.startEllipsoidNormals;
const endEllipsoidNormals = this.endEllipsoidNormals;
const startPositionAndHeights = this.startPositionAndHeights;
const startFaceNormalAndVertexCornerIds =
this.startFaceNormalAndVertexCornerIds;
const endPositionAndHeights = this.endPositionAndHeights;
const endFaceNormalAndHalfWidths = this.endFaceNormalAndHalfWidths;
const vertexBatchIds = this.vertexBatchIds;
let batchIdOffset = this.batchIdOffset;
let vec3Offset = this.vec3Offset;
let vec4Offset = this.vec4Offset;
let i;
for (i = 0; i < 8; i++) {
Cartesian3.pack(startEllipsoidNormal, startEllipsoidNormals, vec3Offset);
Cartesian3.pack(endEllipsoidNormal, endEllipsoidNormals, vec3Offset);
Cartesian3.pack(startRTC, startPositionAndHeights, vec4Offset);
startPositionAndHeights[vec4Offset + 3] = startHeight;
Cartesian3.pack(endRTC, endPositionAndHeights, vec4Offset);
endPositionAndHeights[vec4Offset + 3] = endHeight;
Cartesian3.pack(
startFaceNormal,
startFaceNormalAndVertexCornerIds,
vec4Offset,
);
startFaceNormalAndVertexCornerIds[vec4Offset + 3] = i;
Cartesian3.pack(endFaceNormal, endFaceNormalAndHalfWidths, vec4Offset);
endFaceNormalAndHalfWidths[vec4Offset + 3] = halfWidth;
vertexBatchIds[batchIdOffset++] = batchId;
vec3Offset += 3;
vec4Offset += 4;
}
this.batchIdOffset = batchIdOffset;
this.vec3Offset = vec3Offset;
this.vec4Offset = vec4Offset;
const indices = this.indices;
const volumeStartIndex = this.volumeStartIndex;
const indexOffset = this.indexOffset;
for (i = 0; i < REFERENCE_INDICES_LENGTH; i++) {
indices[indexOffset + i] = REFERENCE_INDICES[i] + volumeStartIndex;
}
this.volumeStartIndex += 8;
this.indexOffset += REFERENCE_INDICES_LENGTH;
};
const scratchRectangle = new Rectangle();
const scratchEllipsoid = new Ellipsoid();
const scratchCenter = new Cartesian3();
const scratchPrev = new Cartesian3();
const scratchP0 = new Cartesian3();
const scratchP1 = new Cartesian3();
const scratchNext = new Cartesian3();
function createVectorTileClampedPolylines(parameters, transferableObjects) {
const encodedPositions = new Uint16Array(parameters.positions);
const widths = new Uint16Array(parameters.widths);
const counts = new Uint32Array(parameters.counts);
const batchIds = new Uint16Array(parameters.batchIds);
// Unpack tile decoding parameters
const rectangle = scratchRectangle;
const ellipsoid = scratchEllipsoid;
const center = scratchCenter;
const packedBuffer = new Float64Array(parameters.packedBuffer);
let offset = 0;
const minimumHeight = packedBuffer[offset++];
const maximumHeight = packedBuffer[offset++];
Rectangle.unpack(packedBuffer, offset, rectangle);
offset += Rectangle.packedLength;
Ellipsoid.unpack(packedBuffer, offset, ellipsoid);
offset += Ellipsoid.packedLength;
Cartesian3.unpack(packedBuffer, offset, center);
let i;
// Unpack positions and generate volumes
let positionsLength = encodedPositions.length / 3;
const uBuffer = encodedPositions.subarray(0, positionsLength);
const vBuffer = encodedPositions.subarray(
positionsLength,
2 * positionsLength,
);
const heightBuffer = encodedPositions.subarray(
2 * positionsLength,
3 * positionsLength,
);
AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer);
removeDuplicates(uBuffer, vBuffer, heightBuffer, counts);
// Figure out how many volumes and how many vertices there will be.
const countsLength = counts.length;
let volumesCount = 0;
for (i = 0; i < countsLength; i++) {
const polylinePositionCount = counts[i];
volumesCount += polylinePositionCount - 1;
}
const attribsAndIndices = new VertexAttributesAndIndices(volumesCount);
const positions = decodePositions(
uBuffer,
vBuffer,
heightBuffer,
rectangle,
minimumHeight,
maximumHeight,
ellipsoid,
center,
);
positionsLength = uBuffer.length;
const positionsRTC = new Float32Array(positionsLength * 3);
for (i = 0; i < positionsLength; ++i) {
positionsRTC[i * 3] = positions[i * 3] - center.x;
positionsRTC[i * 3 + 1] = positions[i * 3 + 1] - center.y;
positionsRTC[i * 3 + 2] = positions[i * 3 + 2] - center.z;
}
let currentPositionIndex = 0;
let currentHeightIndex = 0;
for (i = 0; i < countsLength; i++) {
const polylineVolumeCount = counts[i] - 1;
const halfWidth = widths[i] * 0.5;
const batchId = batchIds[i];
const volumeFirstPositionIndex = currentPositionIndex;
for (let j = 0; j < polylineVolumeCount; j++) {
const volumeStart = Cartesian3.unpack(
positionsRTC,
currentPositionIndex,
scratchP0,
);
const volumeEnd = Cartesian3.unpack(
positionsRTC,
currentPositionIndex + 3,
scratchP1,
);
let startHeight = heightBuffer[currentHeightIndex];
let endHeight = heightBuffer[currentHeightIndex + 1];
startHeight = CesiumMath.lerp(
minimumHeight,
maximumHeight,
startHeight / MAX_SHORT,
);
endHeight = CesiumMath.lerp(
minimumHeight,
maximumHeight,
endHeight / MAX_SHORT,
);
currentHeightIndex++;
let preStart = scratchPrev;
let postEnd = scratchNext;
if (j === 0) {
// Check if this volume is like a loop
const finalPositionIndex =
volumeFirstPositionIndex + polylineVolumeCount * 3;
const finalPosition = Cartesian3.unpack(
positionsRTC,
finalPositionIndex,
scratchPrev,
);
if (Cartesian3.equals(finalPosition, volumeStart)) {
Cartesian3.unpack(positionsRTC, finalPositionIndex - 3, preStart);
} else {
const offsetPastStart = Cartesian3.subtract(
volumeStart,
volumeEnd,
scratchPrev,
);
preStart = Cartesian3.add(offsetPastStart, volumeStart, scratchPrev);
}
} else {
Cartesian3.unpack(positionsRTC, currentPositionIndex - 3, preStart);
}
if (j === polylineVolumeCount - 1) {
// Check if this volume is like a loop
const firstPosition = Cartesian3.unpack(
positionsRTC,
volumeFirstPositionIndex,
scratchNext,
);
if (Cartesian3.equals(firstPosition, volumeEnd)) {
Cartesian3.unpack(
positionsRTC,
volumeFirstPositionIndex + 3,
postEnd,
);
} else {
const offsetPastEnd = Cartesian3.subtract(
volumeEnd,
volumeStart,
scratchNext,
);
postEnd = Cartesian3.add(offsetPastEnd, volumeEnd, scratchNext);
}
} else {
Cartesian3.unpack(positionsRTC, currentPositionIndex + 6, postEnd);
}
attribsAndIndices.addVolume(
preStart,
volumeStart,
volumeEnd,
postEnd,
startHeight,
endHeight,
halfWidth,
batchId,
center,
ellipsoid,
);
currentPositionIndex += 3;
}
currentPositionIndex += 3;
currentHeightIndex++;
}
const indices = attribsAndIndices.indices;
transferableObjects.push(attribsAndIndices.startEllipsoidNormals.buffer);
transferableObjects.push(attribsAndIndices.endEllipsoidNormals.buffer);
transferableObjects.push(attribsAndIndices.startPositionAndHeights.buffer);
transferableObjects.push(
attribsAndIndices.startFaceNormalAndVertexCornerIds.buffer,
);
transferableObjects.push(attribsAndIndices.endPositionAndHeights.buffer);
transferableObjects.push(attribsAndIndices.endFaceNormalAndHalfWidths.buffer);
transferableObjects.push(attribsAndIndices.vertexBatchIds.buffer);
transferableObjects.push(indices.buffer);
let results = {
indexDatatype:
indices.BYTES_PER_ELEMENT === 2
? IndexDatatype.UNSIGNED_SHORT
: IndexDatatype.UNSIGNED_INT,
startEllipsoidNormals: attribsAndIndices.startEllipsoidNormals.buffer,
endEllipsoidNormals: attribsAndIndices.endEllipsoidNormals.buffer,
startPositionAndHeights: attribsAndIndices.startPositionAndHeights.buffer,
startFaceNormalAndVertexCornerIds:
attribsAndIndices.startFaceNormalAndVertexCornerIds.buffer,
endPositionAndHeights: attribsAndIndices.endPositionAndHeights.buffer,
endFaceNormalAndHalfWidths:
attribsAndIndices.endFaceNormalAndHalfWidths.buffer,
vertexBatchIds: attribsAndIndices.vertexBatchIds.buffer,
indices: indices.buffer,
};
if (parameters.keepDecodedPositions) {
const positionOffsets = getPositionOffsets(counts);
transferableObjects.push(positions.buffer, positionOffsets.buffer);
results = combine(results, {
decodedPositions: positions.buffer,
decodedPositionOffsets: positionOffsets.buffer,
});
}
return results;
}
export default createTaskProcessorWorker(createVectorTileClampedPolylines);
@@ -0,0 +1,414 @@
import BoundingSphere from "../Core/BoundingSphere.js";
import BoxGeometry from "../Core/BoxGeometry.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Color from "../Core/Color.js";
import CylinderGeometry from "../Core/CylinderGeometry.js";
import defined from "../Core/defined.js";
import EllipsoidGeometry from "../Core/EllipsoidGeometry.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import Matrix4 from "../Core/Matrix4.js";
import Vector3DTileBatch from "../Scene/Vector3DTileBatch.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const scratchCartesian = new Cartesian3();
const packedBoxLength = Matrix4.packedLength + Cartesian3.packedLength;
const packedCylinderLength = Matrix4.packedLength + 2;
const packedEllipsoidLength = Matrix4.packedLength + Cartesian3.packedLength;
const packedSphereLength = Cartesian3.packedLength + 1;
const scratchModelMatrixAndBV = {
modelMatrix: new Matrix4(),
boundingVolume: new BoundingSphere(),
};
function boxModelMatrixAndBoundingVolume(boxes, index) {
let boxIndex = index * packedBoxLength;
const dimensions = Cartesian3.unpack(boxes, boxIndex, scratchCartesian);
boxIndex += Cartesian3.packedLength;
const boxModelMatrix = Matrix4.unpack(
boxes,
boxIndex,
scratchModelMatrixAndBV.modelMatrix,
);
Matrix4.multiplyByScale(boxModelMatrix, dimensions, boxModelMatrix);
const boundingVolume = scratchModelMatrixAndBV.boundingVolume;
Cartesian3.clone(Cartesian3.ZERO, boundingVolume.center);
boundingVolume.radius = Math.sqrt(3.0);
return scratchModelMatrixAndBV;
}
function cylinderModelMatrixAndBoundingVolume(cylinders, index) {
let cylinderIndex = index * packedCylinderLength;
const cylinderRadius = cylinders[cylinderIndex++];
const length = cylinders[cylinderIndex++];
const scale = Cartesian3.fromElements(
cylinderRadius,
cylinderRadius,
length,
scratchCartesian,
);
const cylinderModelMatrix = Matrix4.unpack(
cylinders,
cylinderIndex,
scratchModelMatrixAndBV.modelMatrix,
);
Matrix4.multiplyByScale(cylinderModelMatrix, scale, cylinderModelMatrix);
const boundingVolume = scratchModelMatrixAndBV.boundingVolume;
Cartesian3.clone(Cartesian3.ZERO, boundingVolume.center);
boundingVolume.radius = Math.sqrt(2.0);
return scratchModelMatrixAndBV;
}
function ellipsoidModelMatrixAndBoundingVolume(ellipsoids, index) {
let ellipsoidIndex = index * packedEllipsoidLength;
const radii = Cartesian3.unpack(ellipsoids, ellipsoidIndex, scratchCartesian);
ellipsoidIndex += Cartesian3.packedLength;
const ellipsoidModelMatrix = Matrix4.unpack(
ellipsoids,
ellipsoidIndex,
scratchModelMatrixAndBV.modelMatrix,
);
Matrix4.multiplyByScale(ellipsoidModelMatrix, radii, ellipsoidModelMatrix);
const boundingVolume = scratchModelMatrixAndBV.boundingVolume;
Cartesian3.clone(Cartesian3.ZERO, boundingVolume.center);
boundingVolume.radius = 1.0;
return scratchModelMatrixAndBV;
}
function sphereModelMatrixAndBoundingVolume(spheres, index) {
let sphereIndex = index * packedSphereLength;
const sphereRadius = spheres[sphereIndex++];
const sphereTranslation = Cartesian3.unpack(
spheres,
sphereIndex,
scratchCartesian,
);
const sphereModelMatrix = Matrix4.fromTranslation(
sphereTranslation,
scratchModelMatrixAndBV.modelMatrix,
);
Matrix4.multiplyByUniformScale(
sphereModelMatrix,
sphereRadius,
sphereModelMatrix,
);
const boundingVolume = scratchModelMatrixAndBV.boundingVolume;
Cartesian3.clone(Cartesian3.ZERO, boundingVolume.center);
boundingVolume.radius = 1.0;
return scratchModelMatrixAndBV;
}
const scratchPosition = new Cartesian3();
function createPrimitive(
options,
primitive,
primitiveBatchIds,
geometry,
getModelMatrixAndBoundingVolume,
) {
if (!defined(primitive)) {
return;
}
const numberOfPrimitives = primitiveBatchIds.length;
const geometryPositions = geometry.attributes.position.values;
const geometryIndices = geometry.indices;
const positions = options.positions;
const vertexBatchIds = options.vertexBatchIds;
const indices = options.indices;
const batchIds = options.batchIds;
const batchTableColors = options.batchTableColors;
const batchedIndices = options.batchedIndices;
const indexOffsets = options.indexOffsets;
const indexCounts = options.indexCounts;
const boundingVolumes = options.boundingVolumes;
const modelMatrix = options.modelMatrix;
const center = options.center;
let positionOffset = options.positionOffset;
let batchIdIndex = options.batchIdIndex;
let indexOffset = options.indexOffset;
const batchedIndicesOffset = options.batchedIndicesOffset;
for (let i = 0; i < numberOfPrimitives; ++i) {
const primitiveModelMatrixAndBV = getModelMatrixAndBoundingVolume(
primitive,
i,
);
const primitiveModelMatrix = primitiveModelMatrixAndBV.modelMatrix;
Matrix4.multiply(modelMatrix, primitiveModelMatrix, primitiveModelMatrix);
const batchId = primitiveBatchIds[i];
const positionsLength = geometryPositions.length;
for (let j = 0; j < positionsLength; j += 3) {
const position = Cartesian3.unpack(geometryPositions, j, scratchPosition);
Matrix4.multiplyByPoint(primitiveModelMatrix, position, position);
Cartesian3.subtract(position, center, position);
Cartesian3.pack(position, positions, positionOffset * 3 + j);
vertexBatchIds[batchIdIndex++] = batchId;
}
const indicesLength = geometryIndices.length;
for (let k = 0; k < indicesLength; ++k) {
indices[indexOffset + k] = geometryIndices[k] + positionOffset;
}
const offset = i + batchedIndicesOffset;
batchedIndices[offset] = new Vector3DTileBatch({
offset: indexOffset,
count: indicesLength,
color: Color.fromRgba(batchTableColors[batchId]),
batchIds: [batchId],
});
batchIds[offset] = batchId;
indexOffsets[offset] = indexOffset;
indexCounts[offset] = indicesLength;
boundingVolumes[offset] = BoundingSphere.transform(
primitiveModelMatrixAndBV.boundingVolume,
primitiveModelMatrix,
);
positionOffset += positionsLength / 3;
indexOffset += indicesLength;
}
options.positionOffset = positionOffset;
options.batchIdIndex = batchIdIndex;
options.indexOffset = indexOffset;
options.batchedIndicesOffset += numberOfPrimitives;
}
const scratchCenter = new Cartesian3();
const scratchMatrix4 = new Matrix4();
function unpackBuffer(buffer) {
const packedBuffer = new Float64Array(buffer);
let offset = 0;
Cartesian3.unpack(packedBuffer, offset, scratchCenter);
offset += Cartesian3.packedLength;
Matrix4.unpack(packedBuffer, offset, scratchMatrix4);
}
function packedBatchedIndicesLength(batchedIndices) {
const length = batchedIndices.length;
let count = 0;
for (let i = 0; i < length; ++i) {
count += Color.packedLength + 3 + batchedIndices[i].batchIds.length;
}
return count;
}
function packBuffer(indicesBytesPerElement, batchedIndices, boundingVolumes) {
const numBVs = boundingVolumes.length;
const length =
1 +
1 +
numBVs * BoundingSphere.packedLength +
1 +
packedBatchedIndicesLength(batchedIndices);
const packedBuffer = new Float64Array(length);
let offset = 0;
packedBuffer[offset++] = indicesBytesPerElement;
packedBuffer[offset++] = numBVs;
for (let i = 0; i < numBVs; ++i) {
BoundingSphere.pack(boundingVolumes[i], packedBuffer, offset);
offset += BoundingSphere.packedLength;
}
const indicesLength = batchedIndices.length;
packedBuffer[offset++] = indicesLength;
for (let j = 0; j < indicesLength; ++j) {
const batchedIndex = batchedIndices[j];
Color.pack(batchedIndex.color, packedBuffer, offset);
offset += Color.packedLength;
packedBuffer[offset++] = batchedIndex.offset;
packedBuffer[offset++] = batchedIndex.count;
const batchIds = batchedIndex.batchIds;
const batchIdsLength = batchIds.length;
packedBuffer[offset++] = batchIdsLength;
for (let k = 0; k < batchIdsLength; ++k) {
packedBuffer[offset++] = batchIds[k];
}
}
return packedBuffer;
}
function createVectorTileGeometries(parameters, transferableObjects) {
const boxes = defined(parameters.boxes)
? new Float32Array(parameters.boxes)
: undefined;
const boxBatchIds = defined(parameters.boxBatchIds)
? new Uint16Array(parameters.boxBatchIds)
: undefined;
const cylinders = defined(parameters.cylinders)
? new Float32Array(parameters.cylinders)
: undefined;
const cylinderBatchIds = defined(parameters.cylinderBatchIds)
? new Uint16Array(parameters.cylinderBatchIds)
: undefined;
const ellipsoids = defined(parameters.ellipsoids)
? new Float32Array(parameters.ellipsoids)
: undefined;
const ellipsoidBatchIds = defined(parameters.ellipsoidBatchIds)
? new Uint16Array(parameters.ellipsoidBatchIds)
: undefined;
const spheres = defined(parameters.spheres)
? new Float32Array(parameters.spheres)
: undefined;
const sphereBatchIds = defined(parameters.sphereBatchIds)
? new Uint16Array(parameters.sphereBatchIds)
: undefined;
const numberOfBoxes = defined(boxes) ? boxBatchIds.length : 0;
const numberOfCylinders = defined(cylinders) ? cylinderBatchIds.length : 0;
const numberOfEllipsoids = defined(ellipsoids) ? ellipsoidBatchIds.length : 0;
const numberOfSpheres = defined(spheres) ? sphereBatchIds.length : 0;
const boxGeometry = BoxGeometry.getUnitBox();
const cylinderGeometry = CylinderGeometry.getUnitCylinder();
const ellipsoidGeometry = EllipsoidGeometry.getUnitEllipsoid();
const boxPositions = boxGeometry.attributes.position.values;
const cylinderPositions = cylinderGeometry.attributes.position.values;
const ellipsoidPositions = ellipsoidGeometry.attributes.position.values;
let numberOfPositions = boxPositions.length * numberOfBoxes;
numberOfPositions += cylinderPositions.length * numberOfCylinders;
numberOfPositions +=
ellipsoidPositions.length * (numberOfEllipsoids + numberOfSpheres);
const boxIndices = boxGeometry.indices;
const cylinderIndices = cylinderGeometry.indices;
const ellipsoidIndices = ellipsoidGeometry.indices;
let numberOfIndices = boxIndices.length * numberOfBoxes;
numberOfIndices += cylinderIndices.length * numberOfCylinders;
numberOfIndices +=
ellipsoidIndices.length * (numberOfEllipsoids + numberOfSpheres);
const positions = new Float32Array(numberOfPositions);
const vertexBatchIds = new Uint16Array(numberOfPositions / 3);
const indices = IndexDatatype.createTypedArray(
numberOfPositions / 3,
numberOfIndices,
);
const numberOfGeometries =
numberOfBoxes + numberOfCylinders + numberOfEllipsoids + numberOfSpheres;
const batchIds = new Uint16Array(numberOfGeometries);
const batchedIndices = new Array(numberOfGeometries);
const indexOffsets = new Uint32Array(numberOfGeometries);
const indexCounts = new Uint32Array(numberOfGeometries);
const boundingVolumes = new Array(numberOfGeometries);
unpackBuffer(parameters.packedBuffer);
const options = {
batchTableColors: new Uint32Array(parameters.batchTableColors),
positions: positions,
vertexBatchIds: vertexBatchIds,
indices: indices,
batchIds: batchIds,
batchedIndices: batchedIndices,
indexOffsets: indexOffsets,
indexCounts: indexCounts,
boundingVolumes: boundingVolumes,
positionOffset: 0,
batchIdIndex: 0,
indexOffset: 0,
batchedIndicesOffset: 0,
modelMatrix: scratchMatrix4,
center: scratchCenter,
};
createPrimitive(
options,
boxes,
boxBatchIds,
boxGeometry,
boxModelMatrixAndBoundingVolume,
);
createPrimitive(
options,
cylinders,
cylinderBatchIds,
cylinderGeometry,
cylinderModelMatrixAndBoundingVolume,
);
createPrimitive(
options,
ellipsoids,
ellipsoidBatchIds,
ellipsoidGeometry,
ellipsoidModelMatrixAndBoundingVolume,
);
createPrimitive(
options,
spheres,
sphereBatchIds,
ellipsoidGeometry,
sphereModelMatrixAndBoundingVolume,
);
const packedBuffer = packBuffer(
indices.BYTES_PER_ELEMENT,
batchedIndices,
boundingVolumes,
);
transferableObjects.push(
positions.buffer,
vertexBatchIds.buffer,
indices.buffer,
);
transferableObjects.push(
batchIds.buffer,
indexOffsets.buffer,
indexCounts.buffer,
);
transferableObjects.push(packedBuffer.buffer);
return {
positions: positions.buffer,
vertexBatchIds: vertexBatchIds.buffer,
indices: indices.buffer,
indexOffsets: indexOffsets.buffer,
indexCounts: indexCounts.buffer,
batchIds: batchIds.buffer,
packedBuffer: packedBuffer.buffer,
};
}
export default createTaskProcessorWorker(createVectorTileGeometries);
+81
View File
@@ -0,0 +1,81 @@
import AttributeCompression from "../Core/AttributeCompression.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import CesiumMath from "../Core/Math.js";
import Rectangle from "../Core/Rectangle.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const maxShort = 32767;
const scratchBVCartographic = new Cartographic();
const scratchEncodedPosition = new Cartesian3();
const scratchRectangle = new Rectangle();
const scratchEllipsoid = new Ellipsoid();
const scratchMinMaxHeights = {
min: undefined,
max: undefined,
};
function unpackBuffer(packedBuffer) {
packedBuffer = new Float64Array(packedBuffer);
let offset = 0;
scratchMinMaxHeights.min = packedBuffer[offset++];
scratchMinMaxHeights.max = packedBuffer[offset++];
Rectangle.unpack(packedBuffer, offset, scratchRectangle);
offset += Rectangle.packedLength;
Ellipsoid.unpack(packedBuffer, offset, scratchEllipsoid);
}
function createVectorTilePoints(parameters, transferableObjects) {
const positions = new Uint16Array(parameters.positions);
unpackBuffer(parameters.packedBuffer);
const rectangle = scratchRectangle;
const ellipsoid = scratchEllipsoid;
const minimumHeight = scratchMinMaxHeights.min;
const maximumHeight = scratchMinMaxHeights.max;
const positionsLength = positions.length / 3;
const uBuffer = positions.subarray(0, positionsLength);
const vBuffer = positions.subarray(positionsLength, 2 * positionsLength);
const heightBuffer = positions.subarray(
2 * positionsLength,
3 * positionsLength,
);
AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer);
const decoded = new Float64Array(positions.length);
for (let i = 0; i < positionsLength; ++i) {
const u = uBuffer[i];
const v = vBuffer[i];
const h = heightBuffer[i];
const lon = CesiumMath.lerp(rectangle.west, rectangle.east, u / maxShort);
const lat = CesiumMath.lerp(rectangle.south, rectangle.north, v / maxShort);
const alt = CesiumMath.lerp(minimumHeight, maximumHeight, h / maxShort);
const cartographic = Cartographic.fromRadians(
lon,
lat,
alt,
scratchBVCartographic,
);
const decodedPosition = ellipsoid.cartographicToCartesian(
cartographic,
scratchEncodedPosition,
);
Cartesian3.pack(decodedPosition, decoded, i * 3);
}
transferableObjects.push(decoded.buffer);
return {
positions: decoded.buffer,
};
}
export default createTaskProcessorWorker(createVectorTilePoints);
+412
View File
@@ -0,0 +1,412 @@
import AttributeCompression from "../Core/AttributeCompression.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import Color from "../Core/Color.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import CesiumMath from "../Core/Math.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const scratchCenter = new Cartesian3();
const scratchEllipsoid = new Ellipsoid();
const scratchRectangle = new Rectangle();
const scratchScalars = {
min: undefined,
max: undefined,
indexBytesPerElement: undefined,
};
function unpackBuffer(buffer) {
const packedBuffer = new Float64Array(buffer);
let offset = 0;
scratchScalars.indexBytesPerElement = packedBuffer[offset++];
scratchScalars.min = packedBuffer[offset++];
scratchScalars.max = packedBuffer[offset++];
Cartesian3.unpack(packedBuffer, offset, scratchCenter);
offset += Cartesian3.packedLength;
Ellipsoid.unpack(packedBuffer, offset, scratchEllipsoid);
offset += Ellipsoid.packedLength;
Rectangle.unpack(packedBuffer, offset, scratchRectangle);
}
function packedBatchedIndicesLength(batchedIndices) {
const length = batchedIndices.length;
let count = 0;
for (let i = 0; i < length; ++i) {
count += Color.packedLength + 3 + batchedIndices[i].batchIds.length;
}
return count;
}
function packBuffer(indexDatatype, boundingVolumes, batchedIndices) {
const numBVs = boundingVolumes.length;
const length =
1 +
1 +
numBVs * OrientedBoundingBox.packedLength +
1 +
packedBatchedIndicesLength(batchedIndices);
const packedBuffer = new Float64Array(length);
let offset = 0;
packedBuffer[offset++] = indexDatatype;
packedBuffer[offset++] = numBVs;
for (let i = 0; i < numBVs; ++i) {
OrientedBoundingBox.pack(boundingVolumes[i], packedBuffer, offset);
offset += OrientedBoundingBox.packedLength;
}
const indicesLength = batchedIndices.length;
packedBuffer[offset++] = indicesLength;
for (let j = 0; j < indicesLength; ++j) {
const batchedIndex = batchedIndices[j];
Color.pack(batchedIndex.color, packedBuffer, offset);
offset += Color.packedLength;
packedBuffer[offset++] = batchedIndex.offset;
packedBuffer[offset++] = batchedIndex.count;
const batchIds = batchedIndex.batchIds;
const batchIdsLength = batchIds.length;
packedBuffer[offset++] = batchIdsLength;
for (let k = 0; k < batchIdsLength; ++k) {
packedBuffer[offset++] = batchIds[k];
}
}
return packedBuffer;
}
const maxShort = 32767;
const scratchEncodedPosition = new Cartesian3();
const scratchNormal = new Cartesian3();
const scratchScaledNormal = new Cartesian3();
const scratchMinHeightPosition = new Cartesian3();
const scratchMaxHeightPosition = new Cartesian3();
const scratchBVCartographic = new Cartographic();
const scratchBVRectangle = new Rectangle();
function createVectorTilePolygons(parameters, transferableObjects) {
unpackBuffer(parameters.packedBuffer);
let indices;
const indexBytesPerElement = scratchScalars.indexBytesPerElement;
if (indexBytesPerElement === 2) {
indices = new Uint16Array(parameters.indices);
} else {
indices = new Uint32Array(parameters.indices);
}
const positions = new Uint16Array(parameters.positions);
const counts = new Uint32Array(parameters.counts);
const indexCounts = new Uint32Array(parameters.indexCounts);
const batchIds = new Uint32Array(parameters.batchIds);
const batchTableColors = new Uint32Array(parameters.batchTableColors);
const boundingVolumes = new Array(counts.length);
const center = scratchCenter;
const ellipsoid = scratchEllipsoid;
let rectangle = scratchRectangle;
const minHeight = scratchScalars.min;
const maxHeight = scratchScalars.max;
let minimumHeights = parameters.minimumHeights;
let maximumHeights = parameters.maximumHeights;
if (defined(minimumHeights) && defined(maximumHeights)) {
minimumHeights = new Float32Array(minimumHeights);
maximumHeights = new Float32Array(maximumHeights);
}
let i;
let j;
let rgba;
const positionsLength = positions.length / 2;
const uBuffer = positions.subarray(0, positionsLength);
const vBuffer = positions.subarray(positionsLength, 2 * positionsLength);
AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer);
const decodedPositions = new Float64Array(positionsLength * 3);
for (i = 0; i < positionsLength; ++i) {
const u = uBuffer[i];
const v = vBuffer[i];
const x = CesiumMath.lerp(rectangle.west, rectangle.east, u / maxShort);
const y = CesiumMath.lerp(rectangle.south, rectangle.north, v / maxShort);
const cart = Cartographic.fromRadians(x, y, 0.0, scratchBVCartographic);
const decodedPosition = ellipsoid.cartographicToCartesian(
cart,
scratchEncodedPosition,
);
Cartesian3.pack(decodedPosition, decodedPositions, i * 3);
}
const countsLength = counts.length;
const offsets = new Array(countsLength);
const indexOffsets = new Array(countsLength);
let currentOffset = 0;
let currentIndexOffset = 0;
for (i = 0; i < countsLength; ++i) {
offsets[i] = currentOffset;
indexOffsets[i] = currentIndexOffset;
currentOffset += counts[i];
currentIndexOffset += indexCounts[i];
}
const batchedPositions = new Float32Array(positionsLength * 3 * 2);
const batchedIds = new Uint16Array(positionsLength * 2);
const batchedIndexOffsets = new Uint32Array(indexOffsets.length);
const batchedIndexCounts = new Uint32Array(indexCounts.length);
let batchedIndices = [];
const colorToBuffers = {};
for (i = 0; i < countsLength; ++i) {
rgba = batchTableColors[i];
if (!defined(colorToBuffers[rgba])) {
colorToBuffers[rgba] = {
positionLength: counts[i],
indexLength: indexCounts[i],
offset: 0,
indexOffset: 0,
batchIds: [i],
};
} else {
colorToBuffers[rgba].positionLength += counts[i];
colorToBuffers[rgba].indexLength += indexCounts[i];
colorToBuffers[rgba].batchIds.push(i);
}
}
// get the offsets and counts for the positions and indices of each primitive
let buffer;
let byColorPositionOffset = 0;
let byColorIndexOffset = 0;
for (rgba in colorToBuffers) {
if (colorToBuffers.hasOwnProperty(rgba)) {
buffer = colorToBuffers[rgba];
buffer.offset = byColorPositionOffset;
buffer.indexOffset = byColorIndexOffset;
const positionLength = buffer.positionLength * 2;
const indexLength = buffer.indexLength * 2 + buffer.positionLength * 6;
byColorPositionOffset += positionLength;
byColorIndexOffset += indexLength;
buffer.indexLength = indexLength;
}
}
const batchedDrawCalls = [];
for (rgba in colorToBuffers) {
if (colorToBuffers.hasOwnProperty(rgba)) {
buffer = colorToBuffers[rgba];
batchedDrawCalls.push({
color: Color.fromRgba(parseInt(rgba)),
offset: buffer.indexOffset,
count: buffer.indexLength,
batchIds: buffer.batchIds,
});
}
}
for (i = 0; i < countsLength; ++i) {
rgba = batchTableColors[i];
buffer = colorToBuffers[rgba];
const positionOffset = buffer.offset;
let positionIndex = positionOffset * 3;
let batchIdIndex = positionOffset;
const polygonOffset = offsets[i];
const polygonCount = counts[i];
const batchId = batchIds[i];
let polygonMinimumHeight = minHeight;
let polygonMaximumHeight = maxHeight;
if (defined(minimumHeights) && defined(maximumHeights)) {
polygonMinimumHeight = minimumHeights[i];
polygonMaximumHeight = maximumHeights[i];
}
let minLat = Number.POSITIVE_INFINITY;
let maxLat = Number.NEGATIVE_INFINITY;
let minLon = Number.POSITIVE_INFINITY;
let maxLon = Number.NEGATIVE_INFINITY;
for (j = 0; j < polygonCount; ++j) {
const position = Cartesian3.unpack(
decodedPositions,
polygonOffset * 3 + j * 3,
scratchEncodedPosition,
);
ellipsoid.scaleToGeodeticSurface(position, position);
const carto = ellipsoid.cartesianToCartographic(
position,
scratchBVCartographic,
);
const lat = carto.latitude;
const lon = carto.longitude;
minLat = Math.min(lat, minLat);
maxLat = Math.max(lat, maxLat);
minLon = Math.min(lon, minLon);
maxLon = Math.max(lon, maxLon);
const normal = ellipsoid.geodeticSurfaceNormal(position, scratchNormal);
let scaledNormal = Cartesian3.multiplyByScalar(
normal,
polygonMinimumHeight,
scratchScaledNormal,
);
const minHeightPosition = Cartesian3.add(
position,
scaledNormal,
scratchMinHeightPosition,
);
scaledNormal = Cartesian3.multiplyByScalar(
normal,
polygonMaximumHeight,
scaledNormal,
);
const maxHeightPosition = Cartesian3.add(
position,
scaledNormal,
scratchMaxHeightPosition,
);
Cartesian3.subtract(maxHeightPosition, center, maxHeightPosition);
Cartesian3.subtract(minHeightPosition, center, minHeightPosition);
Cartesian3.pack(maxHeightPosition, batchedPositions, positionIndex);
Cartesian3.pack(minHeightPosition, batchedPositions, positionIndex + 3);
batchedIds[batchIdIndex] = batchId;
batchedIds[batchIdIndex + 1] = batchId;
positionIndex += 6;
batchIdIndex += 2;
}
rectangle = scratchBVRectangle;
rectangle.west = minLon;
rectangle.east = maxLon;
rectangle.south = minLat;
rectangle.north = maxLat;
boundingVolumes[i] = OrientedBoundingBox.fromRectangle(
rectangle,
minHeight,
maxHeight,
ellipsoid,
);
let indicesIndex = buffer.indexOffset;
const indexOffset = indexOffsets[i];
const indexCount = indexCounts[i];
batchedIndexOffsets[i] = indicesIndex;
for (j = 0; j < indexCount; j += 3) {
const i0 = indices[indexOffset + j] - polygonOffset;
const i1 = indices[indexOffset + j + 1] - polygonOffset;
const i2 = indices[indexOffset + j + 2] - polygonOffset;
// triangle on the top of the extruded polygon
batchedIndices[indicesIndex++] = i0 * 2 + positionOffset;
batchedIndices[indicesIndex++] = i1 * 2 + positionOffset;
batchedIndices[indicesIndex++] = i2 * 2 + positionOffset;
// triangle on the bottom of the extruded polygon
batchedIndices[indicesIndex++] = i2 * 2 + 1 + positionOffset;
batchedIndices[indicesIndex++] = i1 * 2 + 1 + positionOffset;
batchedIndices[indicesIndex++] = i0 * 2 + 1 + positionOffset;
}
// indices for the walls of the extruded polygon
for (j = 0; j < polygonCount; ++j) {
const v0 = j;
const v1 = (j + 1) % polygonCount;
batchedIndices[indicesIndex++] = v0 * 2 + 1 + positionOffset;
batchedIndices[indicesIndex++] = v1 * 2 + positionOffset;
batchedIndices[indicesIndex++] = v0 * 2 + positionOffset;
batchedIndices[indicesIndex++] = v0 * 2 + 1 + positionOffset;
batchedIndices[indicesIndex++] = v1 * 2 + 1 + positionOffset;
batchedIndices[indicesIndex++] = v1 * 2 + positionOffset;
}
buffer.offset += polygonCount * 2;
buffer.indexOffset = indicesIndex;
batchedIndexCounts[i] = indicesIndex - batchedIndexOffsets[i];
}
batchedIndices = IndexDatatype.createTypedArray(
batchedPositions.length / 3,
batchedIndices,
);
const batchedIndicesLength = batchedDrawCalls.length;
for (let m = 0; m < batchedIndicesLength; ++m) {
const tempIds = batchedDrawCalls[m].batchIds;
let count = 0;
const tempIdsLength = tempIds.length;
for (let n = 0; n < tempIdsLength; ++n) {
count += batchedIndexCounts[tempIds[n]];
}
batchedDrawCalls[m].count = count;
}
const indexDatatype =
batchedIndices.BYTES_PER_ELEMENT === 2
? IndexDatatype.UNSIGNED_SHORT
: IndexDatatype.UNSIGNED_INT;
const packedBuffer = packBuffer(
indexDatatype,
boundingVolumes,
batchedDrawCalls,
);
transferableObjects.push(
batchedPositions.buffer,
batchedIndices.buffer,
batchedIndexOffsets.buffer,
batchedIndexCounts.buffer,
batchedIds.buffer,
packedBuffer.buffer,
);
return {
positions: batchedPositions.buffer,
indices: batchedIndices.buffer,
indexOffsets: batchedIndexOffsets.buffer,
indexCounts: batchedIndexCounts.buffer,
batchIds: batchedIds.buffer,
packedBuffer: packedBuffer.buffer,
};
}
export default createTaskProcessorWorker(createVectorTilePolygons);
+210
View File
@@ -0,0 +1,210 @@
import Cartesian3 from "../Core/Cartesian3.js";
import combine from "../Core/combine.js";
import decodeVectorPolylinePositions from "../Core/decodeVectorPolylinePositions.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import Rectangle from "../Core/Rectangle.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const scratchRectangle = new Rectangle();
const scratchEllipsoid = new Ellipsoid();
const scratchCenter = new Cartesian3();
const scratchMinMaxHeights = {
min: undefined,
max: undefined,
};
function unpackBuffer(packedBuffer) {
packedBuffer = new Float64Array(packedBuffer);
let offset = 0;
scratchMinMaxHeights.min = packedBuffer[offset++];
scratchMinMaxHeights.max = packedBuffer[offset++];
Rectangle.unpack(packedBuffer, offset, scratchRectangle);
offset += Rectangle.packedLength;
Ellipsoid.unpack(packedBuffer, offset, scratchEllipsoid);
offset += Ellipsoid.packedLength;
Cartesian3.unpack(packedBuffer, offset, scratchCenter);
}
function getPositionOffsets(counts) {
const countsLength = counts.length;
const positionOffsets = new Uint32Array(countsLength + 1);
let offset = 0;
for (let i = 0; i < countsLength; ++i) {
positionOffsets[i] = offset;
offset += counts[i];
}
positionOffsets[countsLength] = offset;
return positionOffsets;
}
const scratchP0 = new Cartesian3();
const scratchP1 = new Cartesian3();
const scratchPrev = new Cartesian3();
const scratchCur = new Cartesian3();
const scratchNext = new Cartesian3();
function createVectorTilePolylines(parameters, transferableObjects) {
const encodedPositions = new Uint16Array(parameters.positions);
const widths = new Uint16Array(parameters.widths);
const counts = new Uint32Array(parameters.counts);
const batchIds = new Uint16Array(parameters.batchIds);
unpackBuffer(parameters.packedBuffer);
const rectangle = scratchRectangle;
const ellipsoid = scratchEllipsoid;
const center = scratchCenter;
const minimumHeight = scratchMinMaxHeights.min;
const maximumHeight = scratchMinMaxHeights.max;
const positions = decodeVectorPolylinePositions(
encodedPositions,
rectangle,
minimumHeight,
maximumHeight,
ellipsoid,
);
const positionsLength = positions.length / 3;
const size = positionsLength * 4 - 4;
const curPositions = new Float32Array(size * 3);
const prevPositions = new Float32Array(size * 3);
const nextPositions = new Float32Array(size * 3);
const expandAndWidth = new Float32Array(size * 2);
const vertexBatchIds = new Uint16Array(size);
let positionIndex = 0;
let expandAndWidthIndex = 0;
let batchIdIndex = 0;
let i;
let offset = 0;
let length = counts.length;
for (i = 0; i < length; ++i) {
const count = counts[i];
const width = widths[i];
const batchId = batchIds[i];
for (let j = 0; j < count; ++j) {
let previous;
if (j === 0) {
const p0 = Cartesian3.unpack(positions, offset * 3, scratchP0);
const p1 = Cartesian3.unpack(positions, (offset + 1) * 3, scratchP1);
previous = Cartesian3.subtract(p0, p1, scratchPrev);
Cartesian3.add(p0, previous, previous);
} else {
previous = Cartesian3.unpack(
positions,
(offset + j - 1) * 3,
scratchPrev,
);
}
const current = Cartesian3.unpack(
positions,
(offset + j) * 3,
scratchCur,
);
let next;
if (j === count - 1) {
const p2 = Cartesian3.unpack(
positions,
(offset + count - 1) * 3,
scratchP0,
);
const p3 = Cartesian3.unpack(
positions,
(offset + count - 2) * 3,
scratchP1,
);
next = Cartesian3.subtract(p2, p3, scratchNext);
Cartesian3.add(p2, next, next);
} else {
next = Cartesian3.unpack(positions, (offset + j + 1) * 3, scratchNext);
}
Cartesian3.subtract(previous, center, previous);
Cartesian3.subtract(current, center, current);
Cartesian3.subtract(next, center, next);
const startK = j === 0 ? 2 : 0;
const endK = j === count - 1 ? 2 : 4;
for (let k = startK; k < endK; ++k) {
Cartesian3.pack(current, curPositions, positionIndex);
Cartesian3.pack(previous, prevPositions, positionIndex);
Cartesian3.pack(next, nextPositions, positionIndex);
positionIndex += 3;
const direction = k - 2 < 0 ? -1.0 : 1.0;
expandAndWidth[expandAndWidthIndex++] = 2 * (k % 2) - 1;
expandAndWidth[expandAndWidthIndex++] = direction * width;
vertexBatchIds[batchIdIndex++] = batchId;
}
}
offset += count;
}
const indices = IndexDatatype.createTypedArray(size, positionsLength * 6 - 6);
let index = 0;
let indicesIndex = 0;
length = positionsLength - 1;
for (i = 0; i < length; ++i) {
indices[indicesIndex++] = index;
indices[indicesIndex++] = index + 2;
indices[indicesIndex++] = index + 1;
indices[indicesIndex++] = index + 1;
indices[indicesIndex++] = index + 2;
indices[indicesIndex++] = index + 3;
index += 4;
}
transferableObjects.push(
curPositions.buffer,
prevPositions.buffer,
nextPositions.buffer,
);
transferableObjects.push(
expandAndWidth.buffer,
vertexBatchIds.buffer,
indices.buffer,
);
let results = {
indexDatatype:
indices.BYTES_PER_ELEMENT === 2
? IndexDatatype.UNSIGNED_SHORT
: IndexDatatype.UNSIGNED_INT,
currentPositions: curPositions.buffer,
previousPositions: prevPositions.buffer,
nextPositions: nextPositions.buffer,
expandAndWidth: expandAndWidth.buffer,
batchIds: vertexBatchIds.buffer,
indices: indices.buffer,
};
if (parameters.keepDecodedPositions) {
const positionOffsets = getPositionOffsets(counts);
transferableObjects.push(positions.buffer, positionOffsets.buffer);
results = combine(results, {
decodedPositions: positions.buffer,
decodedPositionOffsets: positionOffsets.buffer,
});
}
return results;
}
export default createTaskProcessorWorker(createVectorTilePolylines);
@@ -0,0 +1,46 @@
import Cesium3DTilesTerrainGeometryProcessor from "../Core/Cesium3DTilesTerrainGeometryProcessor.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
/**
* @private
*
* @param {Cesium3DTilesTerrainGeometryProcessor.CreateMeshOptions} options An object describing options for mesh creation.
* @param {ArrayBuffer[]} transferableObjects An array of buffers that can be transferred back to the main thread.
* @returns {Promise<object>} A promise that resolves to an object containing selected info from the created TerrainMesh.
*/
function createVerticesFromCesium3DTilesTerrain(options, transferableObjects) {
const meshPromise = Cesium3DTilesTerrainGeometryProcessor.createMesh(options);
return meshPromise.then(function (mesh) {
const verticesBuffer = mesh.vertices.buffer;
const indicesBuffer = mesh.indices.buffer;
const westIndicesBuffer = mesh.westIndicesSouthToNorth.buffer;
const southIndicesBuffer = mesh.southIndicesEastToWest.buffer;
const eastIndicesBuffer = mesh.eastIndicesNorthToSouth.buffer;
const northIndicesBuffer = mesh.northIndicesWestToEast.buffer;
transferableObjects.push(
verticesBuffer,
indicesBuffer,
westIndicesBuffer,
southIndicesBuffer,
eastIndicesBuffer,
northIndicesBuffer,
);
return {
verticesBuffer: verticesBuffer,
indicesBuffer: indicesBuffer,
vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
encoding: mesh.encoding,
westIndicesBuffer: westIndicesBuffer,
southIndicesBuffer: southIndicesBuffer,
eastIndicesBuffer: eastIndicesBuffer,
northIndicesBuffer: northIndicesBuffer,
};
});
}
export default createTaskProcessorWorker(
createVerticesFromCesium3DTilesTerrain,
);
@@ -0,0 +1,651 @@
import AxisAlignedBoundingBox from "../Core/AxisAlignedBoundingBox.js";
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import EllipsoidalOccluder from "../Core/EllipsoidalOccluder.js";
import CesiumMath from "../Core/Math.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
import RuntimeError from "../Core/RuntimeError.js";
import TerrainEncoding from "../Core/TerrainEncoding.js";
import Transforms from "../Core/Transforms.js";
import WebMercatorProjection from "../Core/WebMercatorProjection.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
const sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
const sizeOfFloat = Float32Array.BYTES_PER_ELEMENT;
const sizeOfDouble = Float64Array.BYTES_PER_ELEMENT;
function indexOfEpsilon(arr, elem, elemType) {
elemType = elemType ?? CesiumMath;
const count = arr.length;
for (let i = 0; i < count; ++i) {
if (elemType.equalsEpsilon(arr[i], elem, CesiumMath.EPSILON12)) {
return i;
}
}
return -1;
}
function createVerticesFromGoogleEarthEnterpriseBuffer(
parameters,
transferableObjects,
) {
parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid);
parameters.rectangle = Rectangle.clone(parameters.rectangle);
const statistics = processBuffer(
parameters.buffer,
parameters.relativeToCenter,
parameters.ellipsoid,
parameters.rectangle,
parameters.nativeRectangle,
parameters.exaggeration,
parameters.exaggerationRelativeHeight,
parameters.skirtHeight,
parameters.includeWebMercatorT,
parameters.negativeAltitudeExponentBias,
parameters.negativeElevationThreshold,
);
const vertices = statistics.vertices;
transferableObjects.push(vertices.buffer);
const indices = statistics.indices;
transferableObjects.push(indices.buffer);
return {
vertices: vertices.buffer,
indices: indices.buffer,
numberOfAttributes: statistics.encoding.stride,
minimumHeight: statistics.minimumHeight,
maximumHeight: statistics.maximumHeight,
boundingSphere3D: statistics.boundingSphere3D,
orientedBoundingBox: statistics.orientedBoundingBox,
occludeePointInScaledSpace: statistics.occludeePointInScaledSpace,
encoding: statistics.encoding,
vertexCountWithoutSkirts: statistics.vertexCountWithoutSkirts,
indexCountWithoutSkirts: statistics.indexCountWithoutSkirts,
westIndicesSouthToNorth: statistics.westIndicesSouthToNorth,
southIndicesEastToWest: statistics.southIndicesEastToWest,
eastIndicesNorthToSouth: statistics.eastIndicesNorthToSouth,
northIndicesWestToEast: statistics.northIndicesWestToEast,
};
}
const scratchCartographic = new Cartographic();
const scratchCartesian = new Cartesian3();
const minimumScratch = new Cartesian3();
const maximumScratch = new Cartesian3();
const matrix4Scratch = new Matrix4();
function processBuffer(
buffer,
relativeToCenter,
ellipsoid,
rectangle,
nativeRectangle,
exaggeration,
exaggerationRelativeHeight,
skirtHeight,
includeWebMercatorT,
negativeAltitudeExponentBias,
negativeElevationThreshold,
) {
let geographicWest;
let geographicSouth;
let geographicEast;
let geographicNorth;
let rectangleWidth, rectangleHeight;
if (!defined(rectangle)) {
geographicWest = CesiumMath.toRadians(nativeRectangle.west);
geographicSouth = CesiumMath.toRadians(nativeRectangle.south);
geographicEast = CesiumMath.toRadians(nativeRectangle.east);
geographicNorth = CesiumMath.toRadians(nativeRectangle.north);
rectangleWidth = CesiumMath.toRadians(rectangle.width);
rectangleHeight = CesiumMath.toRadians(rectangle.height);
} else {
geographicWest = rectangle.west;
geographicSouth = rectangle.south;
geographicEast = rectangle.east;
geographicNorth = rectangle.north;
rectangleWidth = rectangle.width;
rectangleHeight = rectangle.height;
}
// Keep track of quad borders so we can remove duplicates around the borders
const quadBorderLatitudes = [geographicSouth, geographicNorth];
const quadBorderLongitudes = [geographicWest, geographicEast];
const fromENU = Transforms.eastNorthUpToFixedFrame(
relativeToCenter,
ellipsoid,
);
const toENU = Matrix4.inverseTransformation(fromENU, matrix4Scratch);
let southMercatorY;
let oneOverMercatorHeight;
if (includeWebMercatorT) {
southMercatorY =
WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicSouth);
oneOverMercatorHeight =
1.0 /
(WebMercatorProjection.geodeticLatitudeToMercatorAngle(geographicNorth) -
southMercatorY);
}
const hasExaggeration = exaggeration !== 1.0;
const includeGeodeticSurfaceNormals = hasExaggeration;
const dv = new DataView(buffer);
let minHeight = Number.POSITIVE_INFINITY;
let maxHeight = Number.NEGATIVE_INFINITY;
const minimum = minimumScratch;
minimum.x = Number.POSITIVE_INFINITY;
minimum.y = Number.POSITIVE_INFINITY;
minimum.z = Number.POSITIVE_INFINITY;
const maximum = maximumScratch;
maximum.x = Number.NEGATIVE_INFINITY;
maximum.y = Number.NEGATIVE_INFINITY;
maximum.z = Number.NEGATIVE_INFINITY;
// Compute sizes
let offset = 0;
let size = 0;
let indicesSize = 0;
let quadSize;
let quad;
for (quad = 0; quad < 4; ++quad) {
let o = offset;
quadSize = dv.getUint32(o, true);
o += sizeOfUint32;
const x = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0);
o += sizeOfDouble;
if (indexOfEpsilon(quadBorderLongitudes, x) === -1) {
quadBorderLongitudes.push(x);
}
const y = CesiumMath.toRadians(dv.getFloat64(o, true) * 180.0);
o += sizeOfDouble;
if (indexOfEpsilon(quadBorderLatitudes, y) === -1) {
quadBorderLatitudes.push(y);
}
o += 2 * sizeOfDouble; // stepX + stepY
let c = dv.getInt32(o, true); // Read point count
o += sizeOfInt32;
size += c;
c = dv.getInt32(o, true); // Read index count
indicesSize += c * 3;
offset += quadSize + sizeOfUint32; // Jump to next quad
}
// Quad Border points to remove duplicates
const quadBorderPoints = [];
const quadBorderIndices = [];
// Create arrays
const positions = new Array(size);
const uvs = new Array(size);
const heights = new Array(size);
const webMercatorTs = includeWebMercatorT ? new Array(size) : [];
const geodeticSurfaceNormals = includeGeodeticSurfaceNormals
? new Array(size)
: [];
const indices = new Array(indicesSize);
// Points are laid out in rows starting at SW, so storing border points as we
// come across them all points will be adjacent.
const westBorder = [];
const southBorder = [];
const eastBorder = [];
const northBorder = [];
// Each tile is split into 4 parts
let pointOffset = 0;
let indicesOffset = 0;
offset = 0;
for (quad = 0; quad < 4; ++quad) {
quadSize = dv.getUint32(offset, true);
offset += sizeOfUint32;
const startQuad = offset;
const originX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
offset += sizeOfDouble;
const originY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
offset += sizeOfDouble;
const stepX = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
const halfStepX = stepX * 0.5;
offset += sizeOfDouble;
const stepY = CesiumMath.toRadians(dv.getFloat64(offset, true) * 180.0);
const halfStepY = stepY * 0.5;
offset += sizeOfDouble;
const numPoints = dv.getInt32(offset, true);
offset += sizeOfInt32;
const numFaces = dv.getInt32(offset, true);
offset += sizeOfInt32;
//const level = dv.getInt32(offset, true);
offset += sizeOfInt32;
// Keep track of quad indices to overall tile indices
const indicesMapping = new Array(numPoints);
for (let i = 0; i < numPoints; ++i) {
const longitude = originX + dv.getUint8(offset++) * stepX;
scratchCartographic.longitude = longitude;
const latitude = originY + dv.getUint8(offset++) * stepY;
scratchCartographic.latitude = latitude;
let height = dv.getFloat32(offset, true);
offset += sizeOfFloat;
// In order to support old clients, negative altitude values are stored as
// height/-2^32. Old clients see the value as really close to 0 but new clients multiply
// by -2^32 to get the real negative altitude value.
if (height !== 0 && height < negativeElevationThreshold) {
height *= -Math.pow(2, negativeAltitudeExponentBias);
}
// Height is stored in units of (1/EarthRadius) or (1/6371010.0)
height *= 6371010.0;
scratchCartographic.height = height;
// Is it along a quad border - if so check if already exists and use that index
if (
indexOfEpsilon(quadBorderLongitudes, longitude) !== -1 ||
indexOfEpsilon(quadBorderLatitudes, latitude) !== -1
) {
const index = indexOfEpsilon(
quadBorderPoints,
scratchCartographic,
Cartographic,
);
if (index === -1) {
quadBorderPoints.push(Cartographic.clone(scratchCartographic));
quadBorderIndices.push(pointOffset);
} else {
indicesMapping[i] = quadBorderIndices[index];
continue;
}
}
indicesMapping[i] = pointOffset;
if (Math.abs(longitude - geographicWest) < halfStepX) {
westBorder.push({
index: pointOffset,
cartographic: Cartographic.clone(scratchCartographic),
});
} else if (Math.abs(longitude - geographicEast) < halfStepX) {
eastBorder.push({
index: pointOffset,
cartographic: Cartographic.clone(scratchCartographic),
});
} else if (Math.abs(latitude - geographicSouth) < halfStepY) {
southBorder.push({
index: pointOffset,
cartographic: Cartographic.clone(scratchCartographic),
});
} else if (Math.abs(latitude - geographicNorth) < halfStepY) {
northBorder.push({
index: pointOffset,
cartographic: Cartographic.clone(scratchCartographic),
});
}
minHeight = Math.min(height, minHeight);
maxHeight = Math.max(height, maxHeight);
heights[pointOffset] = height;
const pos = ellipsoid.cartographicToCartesian(scratchCartographic);
positions[pointOffset] = pos;
if (includeWebMercatorT) {
webMercatorTs[pointOffset] =
(WebMercatorProjection.geodeticLatitudeToMercatorAngle(latitude) -
southMercatorY) *
oneOverMercatorHeight;
}
if (includeGeodeticSurfaceNormals) {
const normal = ellipsoid.geodeticSurfaceNormal(pos);
geodeticSurfaceNormals[pointOffset] = normal;
}
Matrix4.multiplyByPoint(toENU, pos, scratchCartesian);
Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum);
Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum);
let u = (longitude - geographicWest) / (geographicEast - geographicWest);
u = CesiumMath.clamp(u, 0.0, 1.0);
let v =
(latitude - geographicSouth) / (geographicNorth - geographicSouth);
v = CesiumMath.clamp(v, 0.0, 1.0);
uvs[pointOffset] = new Cartesian2(u, v);
++pointOffset;
}
const facesElementCount = numFaces * 3;
for (let j = 0; j < facesElementCount; ++j, ++indicesOffset) {
indices[indicesOffset] = indicesMapping[dv.getUint16(offset, true)];
offset += sizeOfUint16;
}
if (quadSize !== offset - startQuad) {
throw new RuntimeError("Invalid terrain tile.");
}
}
positions.length = pointOffset;
uvs.length = pointOffset;
heights.length = pointOffset;
if (includeWebMercatorT) {
webMercatorTs.length = pointOffset;
}
if (includeGeodeticSurfaceNormals) {
geodeticSurfaceNormals.length = pointOffset;
}
const vertexCountWithoutSkirts = pointOffset;
const indexCountWithoutSkirts = indicesOffset;
// Add skirt points
const skirtOptions = {
hMin: minHeight,
lastBorderPoint: undefined,
skirtHeight: skirtHeight,
toENU: toENU,
ellipsoid: ellipsoid,
minimum: minimum,
maximum: maximum,
};
// Sort counter clockwise from NW corner
// Corner points are in the east/west arrays
westBorder.sort(function (a, b) {
return b.cartographic.latitude - a.cartographic.latitude;
});
southBorder.sort(function (a, b) {
return a.cartographic.longitude - b.cartographic.longitude;
});
eastBorder.sort(function (a, b) {
return a.cartographic.latitude - b.cartographic.latitude;
});
northBorder.sort(function (a, b) {
return b.cartographic.longitude - a.cartographic.longitude;
});
const percentage = 0.00001;
addSkirt(
positions,
heights,
uvs,
webMercatorTs,
geodeticSurfaceNormals,
indices,
skirtOptions,
westBorder,
-percentage * rectangleWidth,
true,
-percentage * rectangleHeight,
);
addSkirt(
positions,
heights,
uvs,
webMercatorTs,
geodeticSurfaceNormals,
indices,
skirtOptions,
southBorder,
-percentage * rectangleHeight,
false,
);
addSkirt(
positions,
heights,
uvs,
webMercatorTs,
geodeticSurfaceNormals,
indices,
skirtOptions,
eastBorder,
percentage * rectangleWidth,
true,
percentage * rectangleHeight,
);
addSkirt(
positions,
heights,
uvs,
webMercatorTs,
geodeticSurfaceNormals,
indices,
skirtOptions,
northBorder,
percentage * rectangleHeight,
false,
);
// Since the corner between the north and west sides is in the west array, generate the last
// two triangles between the last north vertex and the first west vertex
if (westBorder.length > 0 && northBorder.length > 0) {
const firstBorderIndex = westBorder[0].index;
const firstSkirtIndex = vertexCountWithoutSkirts;
const lastBorderIndex = northBorder[northBorder.length - 1].index;
const lastSkirtIndex = positions.length - 1;
indices.push(
lastBorderIndex,
lastSkirtIndex,
firstSkirtIndex,
firstSkirtIndex,
firstBorderIndex,
lastBorderIndex,
);
}
size = positions.length; // Get new size with skirt vertices
const boundingSphere3D = BoundingSphere.fromPoints(positions);
let orientedBoundingBox;
if (defined(rectangle)) {
orientedBoundingBox = OrientedBoundingBox.fromRectangle(
rectangle,
minHeight,
maxHeight,
ellipsoid,
);
}
const occluder = new EllipsoidalOccluder(ellipsoid);
const occludeePointInScaledSpace =
occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(
relativeToCenter,
positions,
minHeight,
);
const aaBox = new AxisAlignedBoundingBox(minimum, maximum, relativeToCenter);
const encoding = new TerrainEncoding(
relativeToCenter,
aaBox,
skirtOptions.hMin,
maxHeight,
fromENU,
false,
includeWebMercatorT,
includeGeodeticSurfaceNormals,
exaggeration,
exaggerationRelativeHeight,
);
const vertices = new Float32Array(size * encoding.stride);
let bufferIndex = 0;
for (let k = 0; k < size; ++k) {
bufferIndex = encoding.encode(
vertices,
bufferIndex,
positions[k],
uvs[k],
heights[k],
undefined,
webMercatorTs[k],
geodeticSurfaceNormals[k],
);
}
const westIndicesSouthToNorth = westBorder
.map(function (vertex) {
return vertex.index;
})
.reverse();
const southIndicesEastToWest = southBorder
.map(function (vertex) {
return vertex.index;
})
.reverse();
const eastIndicesNorthToSouth = eastBorder
.map(function (vertex) {
return vertex.index;
})
.reverse();
const northIndicesWestToEast = northBorder
.map(function (vertex) {
return vertex.index;
})
.reverse();
southIndicesEastToWest.unshift(
eastIndicesNorthToSouth[eastIndicesNorthToSouth.length - 1],
);
southIndicesEastToWest.push(westIndicesSouthToNorth[0]);
northIndicesWestToEast.unshift(
westIndicesSouthToNorth[westIndicesSouthToNorth.length - 1],
);
northIndicesWestToEast.push(eastIndicesNorthToSouth[0]);
return {
vertices: vertices,
indices: new Uint16Array(indices),
maximumHeight: maxHeight,
minimumHeight: minHeight,
encoding: encoding,
boundingSphere3D: boundingSphere3D,
orientedBoundingBox: orientedBoundingBox,
occludeePointInScaledSpace: occludeePointInScaledSpace,
vertexCountWithoutSkirts: vertexCountWithoutSkirts,
indexCountWithoutSkirts: indexCountWithoutSkirts,
westIndicesSouthToNorth: westIndicesSouthToNorth,
southIndicesEastToWest: southIndicesEastToWest,
eastIndicesNorthToSouth: eastIndicesNorthToSouth,
northIndicesWestToEast: northIndicesWestToEast,
};
}
function addSkirt(
positions,
heights,
uvs,
webMercatorTs,
geodeticSurfaceNormals,
indices,
skirtOptions,
borderPoints,
fudgeFactor,
eastOrWest,
cornerFudge,
) {
const count = borderPoints.length;
for (let j = 0; j < count; ++j) {
const borderPoint = borderPoints[j];
const borderCartographic = borderPoint.cartographic;
const borderIndex = borderPoint.index;
const currentIndex = positions.length;
const longitude = borderCartographic.longitude;
let latitude = borderCartographic.latitude;
latitude = CesiumMath.clamp(
latitude,
-CesiumMath.PI_OVER_TWO,
CesiumMath.PI_OVER_TWO,
); // Don't go over the poles
const height = borderCartographic.height - skirtOptions.skirtHeight;
skirtOptions.hMin = Math.min(skirtOptions.hMin, height);
Cartographic.fromRadians(longitude, latitude, height, scratchCartographic);
// Adjust sides to angle out
if (eastOrWest) {
scratchCartographic.longitude += fudgeFactor;
}
// Adjust top or bottom to angle out
// Since corners are in the east/west arrays angle the first and last points as well
if (!eastOrWest) {
scratchCartographic.latitude += fudgeFactor;
} else if (j === count - 1) {
scratchCartographic.latitude += cornerFudge;
} else if (j === 0) {
scratchCartographic.latitude -= cornerFudge;
}
const pos =
skirtOptions.ellipsoid.cartographicToCartesian(scratchCartographic);
positions.push(pos);
heights.push(height);
uvs.push(Cartesian2.clone(uvs[borderIndex])); // Copy UVs from border point
if (webMercatorTs.length > 0) {
webMercatorTs.push(webMercatorTs[borderIndex]);
}
if (geodeticSurfaceNormals.length > 0) {
geodeticSurfaceNormals.push(geodeticSurfaceNormals[borderIndex]);
}
Matrix4.multiplyByPoint(skirtOptions.toENU, pos, scratchCartesian);
const minimum = skirtOptions.minimum;
const maximum = skirtOptions.maximum;
Cartesian3.minimumByComponent(scratchCartesian, minimum, minimum);
Cartesian3.maximumByComponent(scratchCartesian, maximum, maximum);
const lastBorderPoint = skirtOptions.lastBorderPoint;
if (defined(lastBorderPoint)) {
const lastBorderIndex = lastBorderPoint.index;
indices.push(
lastBorderIndex,
currentIndex - 1,
currentIndex,
currentIndex,
borderIndex,
lastBorderIndex,
);
}
skirtOptions.lastBorderPoint = borderPoint;
}
}
export default createTaskProcessorWorker(
createVerticesFromGoogleEarthEnterpriseBuffer,
);
@@ -0,0 +1,53 @@
import Ellipsoid from "../Core/Ellipsoid.js";
import HeightmapEncoding from "../Core/HeightmapEncoding.js";
import HeightmapTessellator from "../Core/HeightmapTessellator.js";
import Rectangle from "../Core/Rectangle.js";
import RuntimeError from "../Core/RuntimeError.js";
import Lerc from "lerc";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
function createVerticesFromHeightmap(parameters, transferableObjects) {
// LERC encoded buffers must be decoded, then we can process them like normal
if (parameters.encoding === HeightmapEncoding.LERC) {
let result;
try {
result = Lerc.decode(parameters.heightmap);
} catch (error) {
throw new RuntimeError(error);
}
const lercStatistics = result.statistics[0];
if (lercStatistics.minValue === Number.MAX_VALUE) {
throw new RuntimeError("Invalid tile data");
}
parameters.heightmap = result.pixels[0];
parameters.width = result.width;
parameters.height = result.height;
}
parameters.ellipsoid = Ellipsoid.clone(parameters.ellipsoid);
parameters.rectangle = Rectangle.clone(parameters.rectangle);
const statistics = HeightmapTessellator.computeVertices(parameters);
const vertices = statistics.vertices;
transferableObjects.push(vertices.buffer);
return {
vertices: vertices.buffer,
numberOfAttributes: statistics.encoding.stride,
minimumHeight: statistics.minimumHeight,
maximumHeight: statistics.maximumHeight,
gridWidth: parameters.width,
gridHeight: parameters.height,
boundingSphere3D: statistics.boundingSphere3D,
orientedBoundingBox: statistics.orientedBoundingBox,
occludeePointInScaledSpace: statistics.occludeePointInScaledSpace,
encoding: statistics.encoding,
westIndicesSouthToNorth: statistics.westIndicesSouthToNorth,
southIndicesEastToWest: statistics.southIndicesEastToWest,
eastIndicesNorthToSouth: statistics.eastIndicesNorthToSouth,
northIndicesWestToEast: statistics.northIndicesWestToEast,
};
}
export default createTaskProcessorWorker(createVerticesFromHeightmap);
@@ -0,0 +1,549 @@
import AxisAlignedBoundingBox from "../Core/AxisAlignedBoundingBox.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import EllipsoidalOccluder from "../Core/EllipsoidalOccluder.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import CesiumMath from "../Core/Math.js";
import Matrix4 from "../Core/Matrix4.js";
import Rectangle from "../Core/Rectangle.js";
import TerrainEncoding from "../Core/TerrainEncoding.js";
import TerrainProvider from "../Core/TerrainProvider.js";
import Transforms from "../Core/Transforms.js";
import WebMercatorProjection from "../Core/WebMercatorProjection.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const maxShort = 32767;
const cartesian3Scratch = new Cartesian3();
const scratchMinimum = new Cartesian3();
const scratchMaximum = new Cartesian3();
const cartographicScratch = new Cartographic();
const toPack = new Cartesian2();
function createVerticesFromQuantizedTerrainMesh(
parameters,
transferableObjects,
) {
const quantizedVertices = parameters.quantizedVertices;
const quantizedVertexCount = quantizedVertices.length / 3;
const octEncodedNormals = parameters.octEncodedNormals;
const edgeVertexCount =
parameters.westIndices.length +
parameters.eastIndices.length +
parameters.southIndices.length +
parameters.northIndices.length;
const includeWebMercatorT = parameters.includeWebMercatorT;
const exaggeration = parameters.exaggeration;
const exaggerationRelativeHeight = parameters.exaggerationRelativeHeight;
const hasExaggeration = exaggeration !== 1.0;
const includeGeodeticSurfaceNormals = hasExaggeration;
const rectangle = Rectangle.clone(parameters.rectangle);
const west = rectangle.west;
const south = rectangle.south;
const east = rectangle.east;
const north = rectangle.north;
const ellipsoid = Ellipsoid.clone(parameters.ellipsoid);
const minimumHeight = parameters.minimumHeight;
const maximumHeight = parameters.maximumHeight;
const center = parameters.relativeToCenter;
const fromENU = Transforms.eastNorthUpToFixedFrame(center, ellipsoid);
const toENU = Matrix4.inverseTransformation(fromENU, new Matrix4());
let southMercatorY;
let oneOverMercatorHeight;
if (includeWebMercatorT) {
southMercatorY =
WebMercatorProjection.geodeticLatitudeToMercatorAngle(south);
oneOverMercatorHeight =
1.0 /
(WebMercatorProjection.geodeticLatitudeToMercatorAngle(north) -
southMercatorY);
}
const uBuffer = quantizedVertices.subarray(0, quantizedVertexCount);
const vBuffer = quantizedVertices.subarray(
quantizedVertexCount,
2 * quantizedVertexCount,
);
const heightBuffer = quantizedVertices.subarray(
quantizedVertexCount * 2,
3 * quantizedVertexCount,
);
const hasVertexNormals = defined(octEncodedNormals);
const uvs = new Array(quantizedVertexCount);
const heights = new Array(quantizedVertexCount);
const positions = new Array(quantizedVertexCount);
const webMercatorTs = includeWebMercatorT
? new Array(quantizedVertexCount)
: [];
const geodeticSurfaceNormals = includeGeodeticSurfaceNormals
? new Array(quantizedVertexCount)
: [];
const minimum = scratchMinimum;
minimum.x = Number.POSITIVE_INFINITY;
minimum.y = Number.POSITIVE_INFINITY;
minimum.z = Number.POSITIVE_INFINITY;
const maximum = scratchMaximum;
maximum.x = Number.NEGATIVE_INFINITY;
maximum.y = Number.NEGATIVE_INFINITY;
maximum.z = Number.NEGATIVE_INFINITY;
let minLongitude = Number.POSITIVE_INFINITY;
let maxLongitude = Number.NEGATIVE_INFINITY;
let minLatitude = Number.POSITIVE_INFINITY;
let maxLatitude = Number.NEGATIVE_INFINITY;
for (let i = 0; i < quantizedVertexCount; ++i) {
const rawU = uBuffer[i];
const rawV = vBuffer[i];
const u = rawU / maxShort;
const v = rawV / maxShort;
const height = CesiumMath.lerp(
minimumHeight,
maximumHeight,
heightBuffer[i] / maxShort,
);
cartographicScratch.longitude = CesiumMath.lerp(west, east, u);
cartographicScratch.latitude = CesiumMath.lerp(south, north, v);
cartographicScratch.height = height;
minLongitude = Math.min(cartographicScratch.longitude, minLongitude);
maxLongitude = Math.max(cartographicScratch.longitude, maxLongitude);
minLatitude = Math.min(cartographicScratch.latitude, minLatitude);
maxLatitude = Math.max(cartographicScratch.latitude, maxLatitude);
const position = ellipsoid.cartographicToCartesian(cartographicScratch);
uvs[i] = new Cartesian2(u, v);
heights[i] = height;
positions[i] = position;
if (includeWebMercatorT) {
webMercatorTs[i] =
(WebMercatorProjection.geodeticLatitudeToMercatorAngle(
cartographicScratch.latitude,
) -
southMercatorY) *
oneOverMercatorHeight;
}
if (includeGeodeticSurfaceNormals) {
geodeticSurfaceNormals[i] = ellipsoid.geodeticSurfaceNormal(position);
}
Matrix4.multiplyByPoint(toENU, position, cartesian3Scratch);
Cartesian3.minimumByComponent(cartesian3Scratch, minimum, minimum);
Cartesian3.maximumByComponent(cartesian3Scratch, maximum, maximum);
}
const westIndicesSouthToNorth = copyAndSort(
parameters.westIndices,
function (a, b) {
return uvs[a].y - uvs[b].y;
},
);
const eastIndicesNorthToSouth = copyAndSort(
parameters.eastIndices,
function (a, b) {
return uvs[b].y - uvs[a].y;
},
);
const southIndicesEastToWest = copyAndSort(
parameters.southIndices,
function (a, b) {
return uvs[b].x - uvs[a].x;
},
);
const northIndicesWestToEast = copyAndSort(
parameters.northIndices,
function (a, b) {
return uvs[a].x - uvs[b].x;
},
);
let occludeePointInScaledSpace;
if (minimumHeight < 0.0) {
// Horizon culling point needs to be recomputed since the tile is at least partly under the ellipsoid.
const occluder = new EllipsoidalOccluder(ellipsoid);
occludeePointInScaledSpace =
occluder.computeHorizonCullingPointPossiblyUnderEllipsoid(
center,
positions,
minimumHeight,
);
}
let hMin = minimumHeight;
hMin = Math.min(
hMin,
findMinMaxSkirts(
parameters.westIndices,
parameters.westSkirtHeight,
heights,
uvs,
rectangle,
ellipsoid,
toENU,
minimum,
maximum,
),
);
hMin = Math.min(
hMin,
findMinMaxSkirts(
parameters.southIndices,
parameters.southSkirtHeight,
heights,
uvs,
rectangle,
ellipsoid,
toENU,
minimum,
maximum,
),
);
hMin = Math.min(
hMin,
findMinMaxSkirts(
parameters.eastIndices,
parameters.eastSkirtHeight,
heights,
uvs,
rectangle,
ellipsoid,
toENU,
minimum,
maximum,
),
);
hMin = Math.min(
hMin,
findMinMaxSkirts(
parameters.northIndices,
parameters.northSkirtHeight,
heights,
uvs,
rectangle,
ellipsoid,
toENU,
minimum,
maximum,
),
);
const aaBox = new AxisAlignedBoundingBox(minimum, maximum, center);
const encoding = new TerrainEncoding(
center,
aaBox,
hMin,
maximumHeight,
fromENU,
hasVertexNormals,
includeWebMercatorT,
includeGeodeticSurfaceNormals,
exaggeration,
exaggerationRelativeHeight,
);
const vertexStride = encoding.stride;
const size =
quantizedVertexCount * vertexStride + edgeVertexCount * vertexStride;
const vertexBuffer = new Float32Array(size);
let bufferIndex = 0;
for (let j = 0; j < quantizedVertexCount; ++j) {
if (hasVertexNormals) {
const n = j * 2.0;
toPack.x = octEncodedNormals[n];
toPack.y = octEncodedNormals[n + 1];
}
bufferIndex = encoding.encode(
vertexBuffer,
bufferIndex,
positions[j],
uvs[j],
heights[j],
toPack,
webMercatorTs[j],
geodeticSurfaceNormals[j],
);
}
const edgeTriangleCount = Math.max(0, (edgeVertexCount - 4) * 2);
const indexBufferLength = parameters.indices.length + edgeTriangleCount * 3;
const indexBuffer = IndexDatatype.createTypedArray(
quantizedVertexCount + edgeVertexCount,
indexBufferLength,
);
indexBuffer.set(parameters.indices, 0);
const percentage = 0.0001;
const lonOffset = (maxLongitude - minLongitude) * percentage;
const latOffset = (maxLatitude - minLatitude) * percentage;
const westLongitudeOffset = -lonOffset;
const westLatitudeOffset = 0.0;
const eastLongitudeOffset = lonOffset;
const eastLatitudeOffset = 0.0;
const northLongitudeOffset = 0.0;
const northLatitudeOffset = latOffset;
const southLongitudeOffset = 0.0;
const southLatitudeOffset = -latOffset;
// Add skirts.
let vertexBufferIndex = quantizedVertexCount * vertexStride;
addSkirt(
vertexBuffer,
vertexBufferIndex,
westIndicesSouthToNorth,
encoding,
heights,
uvs,
octEncodedNormals,
ellipsoid,
rectangle,
parameters.westSkirtHeight,
southMercatorY,
oneOverMercatorHeight,
westLongitudeOffset,
westLatitudeOffset,
);
vertexBufferIndex += parameters.westIndices.length * vertexStride;
addSkirt(
vertexBuffer,
vertexBufferIndex,
southIndicesEastToWest,
encoding,
heights,
uvs,
octEncodedNormals,
ellipsoid,
rectangle,
parameters.southSkirtHeight,
southMercatorY,
oneOverMercatorHeight,
southLongitudeOffset,
southLatitudeOffset,
);
vertexBufferIndex += parameters.southIndices.length * vertexStride;
addSkirt(
vertexBuffer,
vertexBufferIndex,
eastIndicesNorthToSouth,
encoding,
heights,
uvs,
octEncodedNormals,
ellipsoid,
rectangle,
parameters.eastSkirtHeight,
southMercatorY,
oneOverMercatorHeight,
eastLongitudeOffset,
eastLatitudeOffset,
);
vertexBufferIndex += parameters.eastIndices.length * vertexStride;
addSkirt(
vertexBuffer,
vertexBufferIndex,
northIndicesWestToEast,
encoding,
heights,
uvs,
octEncodedNormals,
ellipsoid,
rectangle,
parameters.northSkirtHeight,
southMercatorY,
oneOverMercatorHeight,
northLongitudeOffset,
northLatitudeOffset,
);
TerrainProvider.addSkirtIndices(
westIndicesSouthToNorth,
southIndicesEastToWest,
eastIndicesNorthToSouth,
northIndicesWestToEast,
quantizedVertexCount,
indexBuffer,
parameters.indices.length,
);
transferableObjects.push(vertexBuffer.buffer, indexBuffer.buffer);
return {
vertices: vertexBuffer.buffer,
indices: indexBuffer.buffer,
westIndicesSouthToNorth: westIndicesSouthToNorth,
southIndicesEastToWest: southIndicesEastToWest,
eastIndicesNorthToSouth: eastIndicesNorthToSouth,
northIndicesWestToEast: northIndicesWestToEast,
vertexStride: vertexStride,
center: center,
minimumHeight: minimumHeight,
maximumHeight: maximumHeight,
occludeePointInScaledSpace: occludeePointInScaledSpace,
encoding: encoding,
indexCountWithoutSkirts: parameters.indices.length,
};
}
function findMinMaxSkirts(
edgeIndices,
edgeHeight,
heights,
uvs,
rectangle,
ellipsoid,
toENU,
minimum,
maximum,
) {
let hMin = Number.POSITIVE_INFINITY;
const north = rectangle.north;
const south = rectangle.south;
let east = rectangle.east;
const west = rectangle.west;
if (east < west) {
east += CesiumMath.TWO_PI;
}
const length = edgeIndices.length;
for (let i = 0; i < length; ++i) {
const index = edgeIndices[i];
const h = heights[index];
const uv = uvs[index];
cartographicScratch.longitude = CesiumMath.lerp(west, east, uv.x);
cartographicScratch.latitude = CesiumMath.lerp(south, north, uv.y);
cartographicScratch.height = h - edgeHeight;
const position = ellipsoid.cartographicToCartesian(
cartographicScratch,
cartesian3Scratch,
);
Matrix4.multiplyByPoint(toENU, position, position);
Cartesian3.minimumByComponent(position, minimum, minimum);
Cartesian3.maximumByComponent(position, maximum, maximum);
hMin = Math.min(hMin, cartographicScratch.height);
}
return hMin;
}
function addSkirt(
vertexBuffer,
vertexBufferIndex,
edgeVertices,
encoding,
heights,
uvs,
octEncodedNormals,
ellipsoid,
rectangle,
skirtLength,
southMercatorY,
oneOverMercatorHeight,
longitudeOffset,
latitudeOffset,
) {
const hasVertexNormals = defined(octEncodedNormals);
const north = rectangle.north;
const south = rectangle.south;
let east = rectangle.east;
const west = rectangle.west;
if (east < west) {
east += CesiumMath.TWO_PI;
}
const length = edgeVertices.length;
for (let i = 0; i < length; ++i) {
const index = edgeVertices[i];
const h = heights[index];
const uv = uvs[index];
cartographicScratch.longitude =
CesiumMath.lerp(west, east, uv.x) + longitudeOffset;
cartographicScratch.latitude =
CesiumMath.lerp(south, north, uv.y) + latitudeOffset;
cartographicScratch.height = h - skirtLength;
const position = ellipsoid.cartographicToCartesian(
cartographicScratch,
cartesian3Scratch,
);
if (hasVertexNormals) {
const n = index * 2.0;
toPack.x = octEncodedNormals[n];
toPack.y = octEncodedNormals[n + 1];
}
let webMercatorT;
if (encoding.hasWebMercatorT) {
webMercatorT =
(WebMercatorProjection.geodeticLatitudeToMercatorAngle(
cartographicScratch.latitude,
) -
southMercatorY) *
oneOverMercatorHeight;
}
let geodeticSurfaceNormal;
if (encoding.hasGeodeticSurfaceNormals) {
geodeticSurfaceNormal = ellipsoid.geodeticSurfaceNormal(position);
}
vertexBufferIndex = encoding.encode(
vertexBuffer,
vertexBufferIndex,
position,
uv,
cartographicScratch.height,
toPack,
webMercatorT,
geodeticSurfaceNormal,
);
}
}
function copyAndSort(typedArray, comparator) {
let copy;
if (typeof typedArray.slice === "function") {
copy = typedArray.slice();
if (typeof copy.sort !== "function") {
// Sliced typed array isn't sortable, so we can't use it.
copy = undefined;
}
}
if (!defined(copy)) {
copy = Array.prototype.slice.call(typedArray);
}
copy.sort(comparator);
return copy;
}
export default createTaskProcessorWorker(
createVerticesFromQuantizedTerrainMesh,
);
+12
View File
@@ -0,0 +1,12 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import WallGeometry from "../Core/WallGeometry.js";
function createWallGeometry(wallGeometry, offset) {
if (defined(offset)) {
wallGeometry = WallGeometry.unpack(wallGeometry, offset);
}
wallGeometry._ellipsoid = Ellipsoid.clone(wallGeometry._ellipsoid);
return WallGeometry.createGeometry(wallGeometry);
}
export default createWallGeometry;
@@ -0,0 +1,12 @@
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import WallOutlineGeometry from "../Core/WallOutlineGeometry.js";
function createWallOutlineGeometry(wallGeometry, offset) {
if (defined(offset)) {
wallGeometry = WallOutlineGeometry.unpack(wallGeometry, offset);
}
wallGeometry._ellipsoid = Ellipsoid.clone(wallGeometry._ellipsoid);
return WallOutlineGeometry.createGeometry(wallGeometry);
}
export default createWallOutlineGeometry;
+386
View File
@@ -0,0 +1,386 @@
// Draco API uses many capitalized non-constructor methods.
/* eslint-disable new-cap */
import ComponentDatatype from "../Core/ComponentDatatype.js";
import defined from "../Core/defined.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import RuntimeError from "../Core/RuntimeError.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
import dracoModule from "draco3d/draco_decoder_nodejs.js";
let draco;
function decodeIndexArray(dracoGeometry, dracoDecoder) {
const numPoints = dracoGeometry.num_points();
const numFaces = dracoGeometry.num_faces();
const faceIndices = new draco.DracoInt32Array();
const numIndices = numFaces * 3;
const indexArray = IndexDatatype.createTypedArray(numPoints, numIndices);
let offset = 0;
for (let i = 0; i < numFaces; ++i) {
dracoDecoder.GetFaceFromMesh(dracoGeometry, i, faceIndices);
indexArray[offset + 0] = faceIndices.GetValue(0);
indexArray[offset + 1] = faceIndices.GetValue(1);
indexArray[offset + 2] = faceIndices.GetValue(2);
offset += 3;
}
draco.destroy(faceIndices);
return {
typedArray: indexArray,
numberOfIndices: numIndices,
};
}
function decodeQuantizedDracoTypedArray(
dracoGeometry,
dracoDecoder,
dracoAttribute,
quantization,
vertexArrayLength,
) {
let vertexArray;
let attributeData;
if (quantization.quantizationBits <= 8) {
attributeData = new draco.DracoUInt8Array();
vertexArray = new Uint8Array(vertexArrayLength);
dracoDecoder.GetAttributeUInt8ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
} else if (quantization.quantizationBits <= 16) {
attributeData = new draco.DracoUInt16Array();
vertexArray = new Uint16Array(vertexArrayLength);
dracoDecoder.GetAttributeUInt16ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
} else {
attributeData = new draco.DracoFloat32Array();
vertexArray = new Float32Array(vertexArrayLength);
dracoDecoder.GetAttributeFloatForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
}
for (let i = 0; i < vertexArrayLength; ++i) {
vertexArray[i] = attributeData.GetValue(i);
}
draco.destroy(attributeData);
return vertexArray;
}
function decodeDracoTypedArray(
dracoGeometry,
dracoDecoder,
dracoAttribute,
vertexArrayLength,
) {
let vertexArray;
let attributeData;
// Some attribute types are casted down to 32 bit since Draco only returns 32 bit values
switch (dracoAttribute.data_type()) {
case 1:
case 11: // DT_INT8 or DT_BOOL
attributeData = new draco.DracoInt8Array();
vertexArray = new Int8Array(vertexArrayLength);
dracoDecoder.GetAttributeInt8ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
case 2: // DT_UINT8
attributeData = new draco.DracoUInt8Array();
vertexArray = new Uint8Array(vertexArrayLength);
dracoDecoder.GetAttributeUInt8ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
case 3: // DT_INT16
attributeData = new draco.DracoInt16Array();
vertexArray = new Int16Array(vertexArrayLength);
dracoDecoder.GetAttributeInt16ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
case 4: // DT_UINT16
attributeData = new draco.DracoUInt16Array();
vertexArray = new Uint16Array(vertexArrayLength);
dracoDecoder.GetAttributeUInt16ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
case 5:
case 7: // DT_INT32 or DT_INT64
attributeData = new draco.DracoInt32Array();
vertexArray = new Int32Array(vertexArrayLength);
dracoDecoder.GetAttributeInt32ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
case 6:
case 8: // DT_UINT32 or DT_UINT64
attributeData = new draco.DracoUInt32Array();
vertexArray = new Uint32Array(vertexArrayLength);
dracoDecoder.GetAttributeUInt32ForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
case 9:
case 10: // DT_FLOAT32 or DT_FLOAT64
attributeData = new draco.DracoFloat32Array();
vertexArray = new Float32Array(vertexArrayLength);
dracoDecoder.GetAttributeFloatForAllPoints(
dracoGeometry,
dracoAttribute,
attributeData,
);
break;
}
for (let i = 0; i < vertexArrayLength; ++i) {
vertexArray[i] = attributeData.GetValue(i);
}
draco.destroy(attributeData);
return vertexArray;
}
function decodeAttribute(dracoGeometry, dracoDecoder, dracoAttribute) {
const numPoints = dracoGeometry.num_points();
const numComponents = dracoAttribute.num_components();
let quantization;
let transform = new draco.AttributeQuantizationTransform();
if (transform.InitFromAttribute(dracoAttribute)) {
const minValues = new Array(numComponents);
for (let i = 0; i < numComponents; ++i) {
minValues[i] = transform.min_value(i);
}
quantization = {
quantizationBits: transform.quantization_bits(),
minValues: minValues,
range: transform.range(),
octEncoded: false,
};
}
draco.destroy(transform);
transform = new draco.AttributeOctahedronTransform();
if (transform.InitFromAttribute(dracoAttribute)) {
quantization = {
quantizationBits: transform.quantization_bits(),
octEncoded: true,
};
}
draco.destroy(transform);
const vertexArrayLength = numPoints * numComponents;
let vertexArray;
if (defined(quantization)) {
vertexArray = decodeQuantizedDracoTypedArray(
dracoGeometry,
dracoDecoder,
dracoAttribute,
quantization,
vertexArrayLength,
);
} else {
vertexArray = decodeDracoTypedArray(
dracoGeometry,
dracoDecoder,
dracoAttribute,
vertexArrayLength,
);
}
const componentDatatype = ComponentDatatype.fromTypedArray(vertexArray);
return {
array: vertexArray,
data: {
componentsPerAttribute: numComponents,
componentDatatype: componentDatatype,
byteOffset: dracoAttribute.byte_offset(),
byteStride:
ComponentDatatype.getSizeInBytes(componentDatatype) * numComponents,
normalized: dracoAttribute.normalized(),
quantization: quantization,
},
};
}
function decodePointCloud(parameters) {
const dracoDecoder = new draco.Decoder();
if (parameters.dequantizeInShader) {
dracoDecoder.SkipAttributeTransform(draco.POSITION);
dracoDecoder.SkipAttributeTransform(draco.NORMAL);
}
const buffer = new draco.DecoderBuffer();
buffer.Init(parameters.buffer, parameters.buffer.length);
const geometryType = dracoDecoder.GetEncodedGeometryType(buffer);
if (geometryType !== draco.POINT_CLOUD) {
throw new RuntimeError("Draco geometry type must be POINT_CLOUD.");
}
const dracoPointCloud = new draco.PointCloud();
const decodingStatus = dracoDecoder.DecodeBufferToPointCloud(
buffer,
dracoPointCloud,
);
if (!decodingStatus.ok() || dracoPointCloud.ptr === 0) {
throw new RuntimeError(
`Error decoding draco point cloud: ${decodingStatus.error_msg()}`,
);
}
draco.destroy(buffer);
const result = {};
const properties = parameters.properties;
for (const propertyName in properties) {
if (properties.hasOwnProperty(propertyName)) {
let dracoAttribute;
if (propertyName === "POSITION" || propertyName === "NORMAL") {
const dracoAttributeId = dracoDecoder.GetAttributeId(
dracoPointCloud,
draco[propertyName],
);
dracoAttribute = dracoDecoder.GetAttribute(
dracoPointCloud,
dracoAttributeId,
);
} else {
const attributeId = properties[propertyName];
dracoAttribute = dracoDecoder.GetAttributeByUniqueId(
dracoPointCloud,
attributeId,
);
}
result[propertyName] = decodeAttribute(
dracoPointCloud,
dracoDecoder,
dracoAttribute,
);
}
}
draco.destroy(dracoPointCloud);
draco.destroy(dracoDecoder);
return result;
}
function decodePrimitive(parameters) {
const dracoDecoder = new draco.Decoder();
if (parameters.dequantizeInShader) {
for (let i = 0; i < parameters.attributesToSkipTransform.length; ++i) {
dracoDecoder.SkipAttributeTransform(
draco[parameters.attributesToSkipTransform[i]],
);
}
}
const bufferView = parameters.bufferView;
const buffer = new draco.DecoderBuffer();
buffer.Init(parameters.array, bufferView.byteLength);
const geometryType = dracoDecoder.GetEncodedGeometryType(buffer);
if (geometryType !== draco.TRIANGULAR_MESH) {
throw new RuntimeError("Unsupported draco mesh geometry type.");
}
const dracoGeometry = new draco.Mesh();
const decodingStatus = dracoDecoder.DecodeBufferToMesh(buffer, dracoGeometry);
if (!decodingStatus.ok() || dracoGeometry.ptr === 0) {
throw new RuntimeError(
`Error decoding draco mesh geometry: ${decodingStatus.error_msg()}`,
);
}
draco.destroy(buffer);
const attributeData = {};
const compressedAttributes = parameters.compressedAttributes;
for (const attributeName in compressedAttributes) {
if (compressedAttributes.hasOwnProperty(attributeName)) {
const compressedAttribute = compressedAttributes[attributeName];
const dracoAttribute = dracoDecoder.GetAttributeByUniqueId(
dracoGeometry,
compressedAttribute,
);
attributeData[attributeName] = decodeAttribute(
dracoGeometry,
dracoDecoder,
dracoAttribute,
);
}
}
const result = {
indexArray: decodeIndexArray(dracoGeometry, dracoDecoder),
attributeData: attributeData,
};
draco.destroy(dracoGeometry);
draco.destroy(dracoDecoder);
return result;
}
async function decode(parameters, transferableObjects) {
if (defined(parameters.bufferView)) {
return decodePrimitive(parameters);
}
return decodePointCloud(parameters);
}
async function initWorker(parameters, transferableObjects) {
// Require and compile WebAssembly module, or use fallback if not supported
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig) && defined(wasmConfig.wasmBinaryFile)) {
draco = await dracoModule(wasmConfig);
} else {
draco = await dracoModule();
}
return true;
}
async function decodeDraco(parameters, transferableObjects) {
// Expect the first message to be to load a web assembly module
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig)) {
return initWorker(parameters, transferableObjects);
}
return decode(parameters, transferableObjects);
}
export default createTaskProcessorWorker(decodeDraco);
@@ -0,0 +1,268 @@
import decodeGoogleEarthEnterpriseData from "../Core/decodeGoogleEarthEnterpriseData.js";
import GoogleEarthEnterpriseTileInformation from "../Core/GoogleEarthEnterpriseTileInformation.js";
import RuntimeError from "../Core/RuntimeError.js";
import { inflate } from "pako/browser/inflate";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
// Datatype sizes
const sizeOfUint16 = Uint16Array.BYTES_PER_ELEMENT;
const sizeOfInt32 = Int32Array.BYTES_PER_ELEMENT;
const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
const Types = {
METADATA: 0,
TERRAIN: 1,
DBROOT: 2,
};
Types.fromString = function (s) {
if (s === "Metadata") {
return Types.METADATA;
} else if (s === "Terrain") {
return Types.TERRAIN;
} else if (s === "DbRoot") {
return Types.DBROOT;
}
};
function decodeGoogleEarthEnterprisePacket(parameters, transferableObjects) {
const type = Types.fromString(parameters.type);
let buffer = parameters.buffer;
decodeGoogleEarthEnterpriseData(parameters.key, buffer);
const uncompressedTerrain = uncompressPacket(buffer);
buffer = uncompressedTerrain.buffer;
const length = uncompressedTerrain.length;
switch (type) {
case Types.METADATA:
return processMetadata(buffer, length, parameters.quadKey);
case Types.TERRAIN:
return processTerrain(buffer, length, transferableObjects);
case Types.DBROOT:
transferableObjects.push(buffer);
return {
buffer: buffer,
};
}
}
const qtMagic = 32301;
function processMetadata(buffer, totalSize, quadKey) {
const dv = new DataView(buffer);
let offset = 0;
const magic = dv.getUint32(offset, true);
offset += sizeOfUint32;
if (magic !== qtMagic) {
throw new RuntimeError("Invalid magic");
}
const dataTypeId = dv.getUint32(offset, true);
offset += sizeOfUint32;
if (dataTypeId !== 1) {
throw new RuntimeError("Invalid data type. Must be 1 for QuadTreePacket");
}
// Tile format version
const quadVersion = dv.getUint32(offset, true);
offset += sizeOfUint32;
if (quadVersion !== 2) {
throw new RuntimeError(
"Invalid QuadTreePacket version. Only version 2 is supported.",
);
}
const numInstances = dv.getInt32(offset, true);
offset += sizeOfInt32;
const dataInstanceSize = dv.getInt32(offset, true);
offset += sizeOfInt32;
if (dataInstanceSize !== 32) {
throw new RuntimeError("Invalid instance size.");
}
const dataBufferOffset = dv.getInt32(offset, true);
offset += sizeOfInt32;
const dataBufferSize = dv.getInt32(offset, true);
offset += sizeOfInt32;
const metaBufferSize = dv.getInt32(offset, true);
offset += sizeOfInt32;
// Offset from beginning of packet (instances + current offset)
if (dataBufferOffset !== numInstances * dataInstanceSize + offset) {
throw new RuntimeError("Invalid dataBufferOffset");
}
// Verify the packets is all there header + instances + dataBuffer + metaBuffer
if (dataBufferOffset + dataBufferSize + metaBufferSize !== totalSize) {
throw new RuntimeError("Invalid packet offsets");
}
// Read all the instances
const instances = [];
for (let i = 0; i < numInstances; ++i) {
const bitfield = dv.getUint8(offset);
++offset;
++offset; // 2 byte align
const cnodeVersion = dv.getUint16(offset, true);
offset += sizeOfUint16;
const imageVersion = dv.getUint16(offset, true);
offset += sizeOfUint16;
const terrainVersion = dv.getUint16(offset, true);
offset += sizeOfUint16;
// Number of channels stored in the dataBuffer
offset += sizeOfUint16;
offset += sizeOfUint16; // 4 byte align
// Channel type offset into dataBuffer
offset += sizeOfInt32;
// Channel version offset into dataBuffer
offset += sizeOfInt32;
offset += 8; // Ignore image neighbors for now
// Data providers
const imageProvider = dv.getUint8(offset++);
const terrainProvider = dv.getUint8(offset++);
offset += sizeOfUint16; // 4 byte align
instances.push(
new GoogleEarthEnterpriseTileInformation(
bitfield,
cnodeVersion,
imageVersion,
terrainVersion,
imageProvider,
terrainProvider,
),
);
}
const tileInfo = [];
let index = 0;
function populateTiles(parentKey, parent, level) {
let isLeaf = false;
if (level === 4) {
if (parent.hasSubtree()) {
return; // We have a subtree, so just return
}
isLeaf = true; // No subtree, so set all children to null
}
for (let i = 0; i < 4; ++i) {
const childKey = parentKey + i.toString();
if (isLeaf) {
// No subtree so set all children to null
tileInfo[childKey] = null;
} else if (level < 4) {
// We are still in the middle of the subtree, so add child
// only if their bits are set, otherwise set child to null.
if (!parent.hasChild(i)) {
tileInfo[childKey] = null;
} else {
if (index === numInstances) {
console.log("Incorrect number of instances");
return;
}
const instance = instances[index++];
tileInfo[childKey] = instance;
populateTiles(childKey, instance, level + 1);
}
}
}
}
let level = 0;
const root = instances[index++];
if (quadKey === "") {
// Root tile has data at its root and one less level
++level;
} else {
tileInfo[quadKey] = root; // This will only contain the child bitmask
}
populateTiles(quadKey, root, level);
return tileInfo;
}
const numMeshesPerPacket = 5;
const numSubMeshesPerMesh = 4;
// Each terrain packet will have 5 meshes - each contain 4 sub-meshes:
// 1 even level mesh and its 4 odd level children.
// Any remaining bytes after the 20 sub-meshes contains water surface meshes,
// which are ignored.
function processTerrain(buffer, totalSize, transferableObjects) {
const dv = new DataView(buffer);
// Find the sub-meshes.
const advanceMesh = function (pos) {
for (let sub = 0; sub < numSubMeshesPerMesh; ++sub) {
const size = dv.getUint32(pos, true);
pos += sizeOfUint32;
pos += size;
if (pos > totalSize) {
throw new RuntimeError("Malformed terrain packet found.");
}
}
return pos;
};
let offset = 0;
const terrainMeshes = [];
while (terrainMeshes.length < numMeshesPerPacket) {
const start = offset;
offset = advanceMesh(offset);
const mesh = buffer.slice(start, offset);
transferableObjects.push(mesh);
terrainMeshes.push(mesh);
}
return terrainMeshes;
}
const compressedMagic = 0x7468dead;
const compressedMagicSwap = 0xadde6874;
function uncompressPacket(data) {
// The layout of this decoded data is
// Magic Uint32
// Size Uint32
// [GZipped chunk of Size bytes]
// Pullout magic and verify we have the correct data
const dv = new DataView(data);
let offset = 0;
const magic = dv.getUint32(offset, true);
offset += sizeOfUint32;
if (magic !== compressedMagic && magic !== compressedMagicSwap) {
throw new RuntimeError("Invalid magic");
}
// Get the size of the compressed buffer - the endianness depends on which magic was used
const size = dv.getUint32(offset, magic === compressedMagic);
offset += sizeOfUint32;
const compressedPacket = new Uint8Array(data, offset);
const uncompressedPacket = inflate(compressedPacket);
if (uncompressedPacket.length !== size) {
throw new RuntimeError("Size of packet doesn't match header");
}
return uncompressedPacket;
}
export default createTaskProcessorWorker(decodeGoogleEarthEnterprisePacket);
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
import defined from "../Core/defined.js";
import { initSync, radix_sort_gaussians_indexes } from "@cesium/wasm-splats";
//load built wasm modules for sorting. Ensure we can load webassembly and we support SIMD.
async function initWorker(parameters, transferableObjects) {
// Require and compile WebAssembly module, or use fallback if not supported
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig) && defined(wasmConfig.wasmBinary)) {
initSync({ module: wasmConfig.wasmBinary });
return true;
}
}
function generateGaussianSortWorker(parameters, transferableObjects) {
// Handle initialization
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig)) {
return initWorker(parameters, transferableObjects);
}
const { primitive, sortType } = parameters;
if (sortType === "Index") {
return radix_sort_gaussians_indexes(
primitive.positions,
primitive.modelView,
primitive.count,
);
}
}
export default createTaskProcessorWorker(generateGaussianSortWorker);
@@ -0,0 +1,39 @@
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
import defined from "../Core/defined.js";
import { initSync, generate_splat_texture } from "@cesium/wasm-splats";
//load built wasm modules for sorting. Ensure we can load webassembly and we support SIMD.
async function initWorker(parameters, transferableObjects) {
// Require and compile WebAssembly module, or use fallback if not supported
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig) && defined(wasmConfig.wasmBinary)) {
initSync({ module: wasmConfig.wasmBinary });
return true;
}
return false;
}
async function generateSplatTextureWorker(parameters, transferableObjects) {
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig)) {
return initWorker(parameters, transferableObjects);
}
const { attributes, count } = parameters;
const result = generate_splat_texture(
attributes.positions,
attributes.scales,
attributes.rotations,
attributes.colors,
count,
);
return {
data: result.data,
width: result.width,
height: result.height,
};
}
export default createTaskProcessorWorker(generateSplatTextureWorker);
@@ -0,0 +1,124 @@
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
import Matrix4 from "../Core/Matrix4.js";
import Cartesian3 from "../Core/Cartesian3.js";
import AxisAlignedBoundingBox from "../Core/AxisAlignedBoundingBox.js";
const scratchAABBCornerMin = new Cartesian3();
const scratchAABBCornerMax = new Cartesian3();
const scratchTrianglePoints = [
new Cartesian3(),
new Cartesian3(),
new Cartesian3(),
];
const scratchTriangleAABB = new AxisAlignedBoundingBox();
const TILE_AABB_MAX = new Cartesian3(0.5, 0.5, 0.5);
const TILE_AABB_MIN = new Cartesian3(-0.5, -0.5, -0.5);
/**
* Builds the next layer of the terrain picker's quadtree by determining which triangles intersect
* each of the four child nodes. (Essentially distributing the parent's triangles to its children.)
*
* Takes in the AABBs of the four child nodes in the tree's local space, an inverse transform
* to convert triangle positions to the tree's local space, and the parent node's triangle indices and positions.
*
* Returns an four arrays - one for each child node - containing the indices of the triangles that intersect each node.
* @ignore
*/
function incrementallyBuildTerrainPicker(parameters, transferableObjects) {
// Rehydrate worker inputs
const aabbs = new Float64Array(parameters.aabbs);
const nodeAABBs = Array.from({ length: 4 }, (_, i) => {
const min = Cartesian3.unpack(aabbs, i * 6, scratchAABBCornerMin);
const max = Cartesian3.unpack(aabbs, i * 6 + 3, scratchAABBCornerMax);
return AxisAlignedBoundingBox.fromCorners(
min,
max,
new AxisAlignedBoundingBox(),
);
});
const inverseTransformArray = new Float64Array(parameters.inverseTransform);
const inverseTransform = Matrix4.unpack(
inverseTransformArray,
0,
new Matrix4(),
);
const triangleIndices = new Uint32Array(parameters.triangleIndices);
const trianglePositions = new Float64Array(parameters.trianglePositions);
const intersectingTrianglesArrays = Array.from({ length: 4 }, () => []);
for (let j = 0; j < triangleIndices.length; j++) {
Cartesian3.unpack(trianglePositions, j * 9, scratchTrianglePoints[0]);
Cartesian3.unpack(trianglePositions, j * 9 + 3, scratchTrianglePoints[1]);
Cartesian3.unpack(trianglePositions, j * 9 + 6, scratchTrianglePoints[2]);
const triangleAABB = createAABBFromTriangle(
inverseTransform,
scratchTrianglePoints,
);
for (let i = 0; i < 4; i++) {
const aabbsIntersect =
nodeAABBs[i].intersectAxisAlignedBoundingBox(triangleAABB);
if (!aabbsIntersect) {
continue;
}
intersectingTrianglesArrays[i].push(triangleIndices[j]);
}
}
const intersectingTrianglesTypedArrays = intersectingTrianglesArrays.map(
(array) => {
const uintArray = new Uint32Array(array);
transferableObjects.push(uintArray.buffer);
return uintArray.buffer;
},
);
return {
intersectingTrianglesArrays: intersectingTrianglesTypedArrays,
};
}
/**
* Creates a tree-space axis-aligned bounding box from the given triangle points and inverse transform (from world to tree space).
* @param {Matrix4} inverseTransform transform from world space to tree local space
* @param {Cartesian3[]} trianglePoints array of 3 Cartesian3 points representing the triangle
* @returns {AxisAlignedBoundingBox} the axis-aligned bounding box enclosing the triangle in tree local space
* @ignore
*/
function createAABBFromTriangle(inverseTransform, trianglePoints) {
Matrix4.multiplyByPoint(
inverseTransform,
trianglePoints[0],
trianglePoints[0],
);
Matrix4.multiplyByPoint(
inverseTransform,
trianglePoints[1],
trianglePoints[1],
);
Matrix4.multiplyByPoint(
inverseTransform,
trianglePoints[2],
trianglePoints[2],
);
const aabb = AxisAlignedBoundingBox.fromPoints(
trianglePoints,
scratchTriangleAABB,
);
// In 2D mode, sometimes the height-scale of a tile is 0. See {@link TerrainMesh#computeTransform2D}.
// This makes the inverseTransform degenerate, so we set the height-scale to 1 to be prevent that. However, this is artificial and
// can lead to the triangle's AABB extending beyond the (height) bounds of the tile's AABB.
// Thus, we clamp the triangle's AABB to the tile's local-space AABB.
Cartesian3.clamp(aabb.minimum, TILE_AABB_MIN, TILE_AABB_MAX, aabb.minimum);
Cartesian3.clamp(aabb.maximum, TILE_AABB_MIN, TILE_AABB_MAX, aabb.maximum);
return aabb;
}
export default createTaskProcessorWorker(incrementallyBuildTerrainPicker);
+308
View File
@@ -0,0 +1,308 @@
import defined from "../Core/defined.js";
import Check from "../Core/Check.js";
import PixelFormat from "../Core/PixelFormat.js";
import RuntimeError from "../Core/RuntimeError.js";
import VulkanConstants from "../Core//VulkanConstants.js";
import PixelDatatype from "../Renderer/PixelDatatype.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
import { read } from "ktx-parse";
import basis from "../ThirdParty/Workers/basis_transcoder.js";
const faceOrder = [
"positiveX",
"negativeX",
"positiveY",
"negativeY",
"positiveZ",
"negativeZ",
];
// Flags
const colorModelETC1S = 163;
const colorModelUASTC = 166;
let transcoderModule;
function transcode(parameters, transferableObjects) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("transcoderModule", transcoderModule);
//>>includeEnd('debug');
const data = parameters.ktx2Buffer;
const supportedTargetFormats = parameters.supportedTargetFormats;
let header;
try {
header = read(data);
} catch (e) {
throw new RuntimeError("Invalid KTX2 file.");
}
if (header.layerCount !== 0) {
throw new RuntimeError("KTX2 texture arrays are not supported.");
}
if (header.pixelDepth !== 0) {
throw new RuntimeError("KTX2 3D textures are unsupported.");
}
const dfd = header.dataFormatDescriptor[0];
const result = new Array(header.levelCount);
if (
header.vkFormat === 0x0 &&
(dfd.colorModel === colorModelETC1S || dfd.colorModel === colorModelUASTC)
) {
// Compressed, initialize transcoder module
transcodeCompressed(
data,
header,
supportedTargetFormats,
transcoderModule,
transferableObjects,
result,
);
} else {
transferableObjects.push(data.buffer);
parseUncompressed(header, result);
}
return result;
}
// Parser for uncompressed
function parseUncompressed(header, result) {
const internalFormat =
header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8_SRGB
? PixelFormat.RGB
: PixelFormat.RGBA;
let datatype;
if (header.vkFormat === VulkanConstants.VK_FORMAT_R8G8B8A8_UNORM) {
datatype = PixelDatatype.UNSIGNED_BYTE;
} else if (
header.vkFormat === VulkanConstants.VK_FORMAT_R16G16B16A16_SFLOAT
) {
datatype = PixelDatatype.HALF_FLOAT;
} else if (
header.vkFormat === VulkanConstants.VK_FORMAT_R32G32B32A32_SFLOAT
) {
datatype = PixelDatatype.FLOAT;
}
for (let i = 0; i < header.levels.length; ++i) {
const level = {};
result[i] = level;
const levelBuffer = header.levels[i].levelData;
const width = header.pixelWidth >> i;
const height = header.pixelHeight >> i;
const faceLength =
width * height * PixelFormat.componentsLength(internalFormat);
for (let j = 0; j < header.faceCount; ++j) {
// multiply levelBuffer.byteOffset by the size in bytes of the pixel data type
const faceByteOffset =
levelBuffer.byteOffset + faceLength * header.typeSize * j;
let faceView;
if (!defined(datatype) || PixelDatatype.sizeInBytes(datatype) === 1) {
faceView = new Uint8Array(
levelBuffer.buffer,
faceByteOffset,
faceLength,
);
} else if (PixelDatatype.sizeInBytes(datatype) === 2) {
faceView = new Uint16Array(
levelBuffer.buffer,
faceByteOffset,
faceLength,
);
} else {
faceView = new Float32Array(
levelBuffer.buffer,
faceByteOffset,
faceLength,
);
}
level[faceOrder[j]] = {
internalFormat: internalFormat,
datatype: datatype,
width: width,
height: height,
levelBuffer: faceView,
};
}
}
}
function transcodeCompressed(
data,
header,
supportedTargetFormats,
transcoderModule,
transferableObjects,
result,
) {
const ktx2File = new transcoderModule.KTX2File(data);
let width = ktx2File.getWidth();
let height = ktx2File.getHeight();
const levels = ktx2File.getLevels();
const hasAlpha = ktx2File.getHasAlpha();
if (!(width > 0) || !(height > 0) || !(levels > 0)) {
ktx2File.close();
ktx2File.delete();
throw new RuntimeError("Invalid KTX2 file");
}
let internalFormat, transcoderFormat;
const dfd = header.dataFormatDescriptor[0];
const BasisFormat = transcoderModule.transcoder_texture_format;
// Determine target format based on platform support
if (dfd.colorModel === colorModelETC1S) {
if (supportedTargetFormats.etc) {
internalFormat = hasAlpha
? PixelFormat.RGBA8_ETC2_EAC
: PixelFormat.RGB8_ETC2;
transcoderFormat = hasAlpha
? BasisFormat.cTFETC2_RGBA
: BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.etc1 && !hasAlpha) {
internalFormat = PixelFormat.RGB_ETC1;
transcoderFormat = BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.s3tc) {
internalFormat = hasAlpha ? PixelFormat.RGBA_DXT5 : PixelFormat.RGB_DXT1;
transcoderFormat = hasAlpha
? BasisFormat.cTFBC3_RGBA
: BasisFormat.cTFBC1_RGB;
} else if (supportedTargetFormats.pvrtc) {
internalFormat = hasAlpha
? PixelFormat.RGBA_PVRTC_4BPPV1
: PixelFormat.RGB_PVRTC_4BPPV1;
transcoderFormat = hasAlpha
? BasisFormat.cTFPVRTC1_4_RGBA
: BasisFormat.cTFPVRTC1_4_RGB;
} else if (supportedTargetFormats.astc) {
internalFormat = PixelFormat.RGBA_ASTC;
transcoderFormat = BasisFormat.cTFASTC_4x4_RGBA;
} else if (supportedTargetFormats.bc7) {
internalFormat = PixelFormat.RGBA_BC7;
transcoderFormat = BasisFormat.cTFBC7_RGBA;
} else {
throw new RuntimeError(
"No transcoding format target available for ETC1S compressed ktx2.",
);
}
} else if (dfd.colorModel === colorModelUASTC) {
if (supportedTargetFormats.astc) {
internalFormat = PixelFormat.RGBA_ASTC;
transcoderFormat = BasisFormat.cTFASTC_4x4_RGBA;
} else if (supportedTargetFormats.bc7) {
internalFormat = PixelFormat.RGBA_BC7;
transcoderFormat = BasisFormat.cTFBC7_RGBA;
} else if (supportedTargetFormats.s3tc) {
internalFormat = hasAlpha ? PixelFormat.RGBA_DXT5 : PixelFormat.RGB_DXT1;
transcoderFormat = hasAlpha
? BasisFormat.cTFBC3_RGBA
: BasisFormat.cTFBC1_RGB;
} else if (supportedTargetFormats.etc) {
internalFormat = hasAlpha
? PixelFormat.RGBA8_ETC2_EAC
: PixelFormat.RGB8_ETC2;
transcoderFormat = hasAlpha
? BasisFormat.cTFETC2_RGBA
: BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.etc1 && !hasAlpha) {
internalFormat = PixelFormat.RGB_ETC1;
transcoderFormat = BasisFormat.cTFETC1_RGB;
} else if (supportedTargetFormats.pvrtc) {
internalFormat = hasAlpha
? PixelFormat.RGBA_PVRTC_4BPPV1
: PixelFormat.RGB_PVRTC_4BPPV1;
transcoderFormat = hasAlpha
? BasisFormat.cTFPVRTC1_4_RGBA
: BasisFormat.cTFPVRTC1_4_RGB;
} else {
throw new RuntimeError(
"No transcoding format target available for UASTC compressed ktx2.",
);
}
}
if (!ktx2File.startTranscoding()) {
ktx2File.close();
ktx2File.delete();
throw new RuntimeError("startTranscoding() failed");
}
for (let i = 0; i < header.levels.length; ++i) {
const level = {};
result[i] = level;
width = header.pixelWidth >> i;
height = header.pixelHeight >> i;
// Since supercompressed cubemaps are unsupported, this function
// does not iterate over KTX2 faces and assumes faceCount = 1.
const dstSize = ktx2File.getImageTranscodedSizeInBytes(
i, // level index
0, // layer index
0, // face index
transcoderFormat.value,
);
const dst = new Uint8Array(dstSize);
const transcoded = ktx2File.transcodeImage(
dst,
i, // level index
0, // layer index
0, // face index
transcoderFormat.value,
0, // get_alpha_for_opaque_formats
-1, // channel0
-1, // channel1
);
if (!defined(transcoded)) {
throw new RuntimeError("transcodeImage() failed.");
}
transferableObjects.push(dst.buffer);
level[faceOrder[0]] = {
internalFormat: internalFormat,
width: width,
height: height,
levelBuffer: dst,
};
}
ktx2File.close();
ktx2File.delete();
return result;
}
async function initWorker(parameters, transferableObjects) {
// Require and compile WebAssembly module, or use fallback if not supported
const wasmConfig = parameters.webAssemblyConfig;
const basisTranscoder = basis ?? self.BASIS;
if (defined(wasmConfig.wasmBinaryFile)) {
transcoderModule = await basisTranscoder(wasmConfig);
} else {
transcoderModule = await basisTranscoder();
}
transcoderModule.initializeBasis();
return true;
}
function transcodeKTX2(parameters, transferableObjects) {
// Expect the first message to be to load a web assembly module
const wasmConfig = parameters.webAssemblyConfig;
if (defined(wasmConfig)) {
return initWorker(parameters, transferableObjects);
}
return transcode(parameters, transferableObjects);
}
export default createTaskProcessorWorker(transcodeKTX2);
+16
View File
@@ -0,0 +1,16 @@
self.onmessage = function (event) {
const array = event.data.array;
const postMessage = self.webkitPostMessage || self.postMessage;
try {
// transfer the test array back to the caller
postMessage(
{
array: array,
},
[array.buffer],
);
} catch (e) {
postMessage({});
}
};
@@ -0,0 +1,681 @@
import AttributeCompression from "../Core/AttributeCompression.js";
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Cartographic from "../Core/Cartographic.js";
import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import EllipsoidalOccluder from "../Core/EllipsoidalOccluder.js";
import IndexDatatype from "../Core/IndexDatatype.js";
import Intersections2D from "../Core/Intersections2D.js";
import CesiumMath from "../Core/Math.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
import Rectangle from "../Core/Rectangle.js";
import TerrainEncoding from "../Core/TerrainEncoding.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
const maxShort = 32767;
const halfMaxShort = (maxShort / 2) | 0;
const clipScratch = [];
const clipScratch2 = [];
const verticesScratch = [];
const cartographicScratch = new Cartographic();
let cartesian3Scratch = new Cartesian3();
const uScratch = [];
const vScratch = [];
const heightScratch = [];
const indicesScratch = [];
const normalsScratch = [];
const horizonOcclusionPointScratch = new Cartesian3();
const boundingSphereScratch = new BoundingSphere();
const orientedBoundingBoxScratch = new OrientedBoundingBox();
const decodeTexCoordsScratch = new Cartesian2();
const octEncodedNormalScratch = new Cartesian3();
function upsampleQuantizedTerrainMesh(parameters, transferableObjects) {
const isEastChild = parameters.isEastChild;
const isNorthChild = parameters.isNorthChild;
const minU = isEastChild ? halfMaxShort : 0;
const maxU = isEastChild ? maxShort : halfMaxShort;
const minV = isNorthChild ? halfMaxShort : 0;
const maxV = isNorthChild ? maxShort : halfMaxShort;
const uBuffer = uScratch;
const vBuffer = vScratch;
const heightBuffer = heightScratch;
const normalBuffer = normalsScratch;
uBuffer.length = 0;
vBuffer.length = 0;
heightBuffer.length = 0;
normalBuffer.length = 0;
const indices = indicesScratch;
indices.length = 0;
const vertexMap = {};
const parentVertices = parameters.vertices;
let parentIndices = parameters.indices;
parentIndices = parentIndices.subarray(0, parameters.indexCountWithoutSkirts);
const encoding = TerrainEncoding.clone(parameters.encoding);
const hasVertexNormals = encoding.hasVertexNormals;
let vertexCount = 0;
const quantizedVertexCount = parameters.vertexCountWithoutSkirts;
const parentMinimumHeight = parameters.minimumHeight;
const parentMaximumHeight = parameters.maximumHeight;
const parentUBuffer = new Array(quantizedVertexCount);
const parentVBuffer = new Array(quantizedVertexCount);
const parentHeightBuffer = new Array(quantizedVertexCount);
const parentNormalBuffer = hasVertexNormals
? new Array(quantizedVertexCount * 2)
: undefined;
const threshold = 20;
let height;
let i, n;
let u, v;
for (i = 0, n = 0; i < quantizedVertexCount; ++i, n += 2) {
const texCoords = encoding.decodeTextureCoordinates(
parentVertices,
i,
decodeTexCoordsScratch,
);
height = encoding.decodeHeight(parentVertices, i);
u = CesiumMath.clamp((texCoords.x * maxShort) | 0, 0, maxShort);
v = CesiumMath.clamp((texCoords.y * maxShort) | 0, 0, maxShort);
parentHeightBuffer[i] = CesiumMath.clamp(
(((height - parentMinimumHeight) /
(parentMaximumHeight - parentMinimumHeight)) *
maxShort) |
0,
0,
maxShort,
);
if (u < threshold) {
u = 0;
}
if (v < threshold) {
v = 0;
}
if (maxShort - u < threshold) {
u = maxShort;
}
if (maxShort - v < threshold) {
v = maxShort;
}
parentUBuffer[i] = u;
parentVBuffer[i] = v;
if (hasVertexNormals) {
const encodedNormal = encoding.getOctEncodedNormal(
parentVertices,
i,
octEncodedNormalScratch,
);
parentNormalBuffer[n] = encodedNormal.x;
parentNormalBuffer[n + 1] = encodedNormal.y;
}
if (
((isEastChild && u >= halfMaxShort) ||
(!isEastChild && u <= halfMaxShort)) &&
((isNorthChild && v >= halfMaxShort) ||
(!isNorthChild && v <= halfMaxShort))
) {
vertexMap[i] = vertexCount;
uBuffer.push(u);
vBuffer.push(v);
heightBuffer.push(parentHeightBuffer[i]);
if (hasVertexNormals) {
normalBuffer.push(parentNormalBuffer[n]);
normalBuffer.push(parentNormalBuffer[n + 1]);
}
++vertexCount;
}
}
const triangleVertices = [];
triangleVertices.push(new Vertex());
triangleVertices.push(new Vertex());
triangleVertices.push(new Vertex());
const clippedTriangleVertices = [];
clippedTriangleVertices.push(new Vertex());
clippedTriangleVertices.push(new Vertex());
clippedTriangleVertices.push(new Vertex());
let clippedIndex;
let clipped2;
for (i = 0; i < parentIndices.length; i += 3) {
const i0 = parentIndices[i];
const i1 = parentIndices[i + 1];
const i2 = parentIndices[i + 2];
const u0 = parentUBuffer[i0];
const u1 = parentUBuffer[i1];
const u2 = parentUBuffer[i2];
triangleVertices[0].initializeIndexed(
parentUBuffer,
parentVBuffer,
parentHeightBuffer,
parentNormalBuffer,
i0,
);
triangleVertices[1].initializeIndexed(
parentUBuffer,
parentVBuffer,
parentHeightBuffer,
parentNormalBuffer,
i1,
);
triangleVertices[2].initializeIndexed(
parentUBuffer,
parentVBuffer,
parentHeightBuffer,
parentNormalBuffer,
i2,
);
// Clip triangle on the east-west boundary.
const clipped = Intersections2D.clipTriangleAtAxisAlignedThreshold(
halfMaxShort,
isEastChild,
u0,
u1,
u2,
clipScratch,
);
// Get the first clipped triangle, if any.
clippedIndex = 0;
if (clippedIndex >= clipped.length) {
continue;
}
clippedIndex = clippedTriangleVertices[0].initializeFromClipResult(
clipped,
clippedIndex,
triangleVertices,
);
if (clippedIndex >= clipped.length) {
continue;
}
clippedIndex = clippedTriangleVertices[1].initializeFromClipResult(
clipped,
clippedIndex,
triangleVertices,
);
if (clippedIndex >= clipped.length) {
continue;
}
clippedIndex = clippedTriangleVertices[2].initializeFromClipResult(
clipped,
clippedIndex,
triangleVertices,
);
// Clip the triangle against the North-south boundary.
clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(
halfMaxShort,
isNorthChild,
clippedTriangleVertices[0].getV(),
clippedTriangleVertices[1].getV(),
clippedTriangleVertices[2].getV(),
clipScratch2,
);
addClippedPolygon(
uBuffer,
vBuffer,
heightBuffer,
normalBuffer,
indices,
vertexMap,
clipped2,
clippedTriangleVertices,
hasVertexNormals,
);
// If there's another vertex in the original clipped result,
// it forms a second triangle. Clip it as well.
if (clippedIndex < clipped.length) {
clippedTriangleVertices[2].clone(clippedTriangleVertices[1]);
clippedTriangleVertices[2].initializeFromClipResult(
clipped,
clippedIndex,
triangleVertices,
);
clipped2 = Intersections2D.clipTriangleAtAxisAlignedThreshold(
halfMaxShort,
isNorthChild,
clippedTriangleVertices[0].getV(),
clippedTriangleVertices[1].getV(),
clippedTriangleVertices[2].getV(),
clipScratch2,
);
addClippedPolygon(
uBuffer,
vBuffer,
heightBuffer,
normalBuffer,
indices,
vertexMap,
clipped2,
clippedTriangleVertices,
hasVertexNormals,
);
}
}
const uOffset = isEastChild ? -maxShort : 0;
const vOffset = isNorthChild ? -maxShort : 0;
const westIndices = [];
const southIndices = [];
const eastIndices = [];
const northIndices = [];
let minimumHeight = Number.MAX_VALUE;
let maximumHeight = -minimumHeight;
const cartesianVertices = verticesScratch;
cartesianVertices.length = 0;
const ellipsoid = Ellipsoid.clone(parameters.ellipsoid);
const rectangle = Rectangle.clone(parameters.childRectangle);
const north = rectangle.north;
const south = rectangle.south;
let east = rectangle.east;
const west = rectangle.west;
if (east < west) {
east += CesiumMath.TWO_PI;
}
for (i = 0; i < uBuffer.length; ++i) {
u = Math.round(uBuffer[i]);
if (u <= minU) {
westIndices.push(i);
u = 0;
} else if (u >= maxU) {
eastIndices.push(i);
u = maxShort;
} else {
u = u * 2 + uOffset;
}
uBuffer[i] = u;
v = Math.round(vBuffer[i]);
if (v <= minV) {
southIndices.push(i);
v = 0;
} else if (v >= maxV) {
northIndices.push(i);
v = maxShort;
} else {
v = v * 2 + vOffset;
}
vBuffer[i] = v;
height = CesiumMath.lerp(
parentMinimumHeight,
parentMaximumHeight,
heightBuffer[i] / maxShort,
);
if (height < minimumHeight) {
minimumHeight = height;
}
if (height > maximumHeight) {
maximumHeight = height;
}
heightBuffer[i] = height;
cartographicScratch.longitude = CesiumMath.lerp(west, east, u / maxShort);
cartographicScratch.latitude = CesiumMath.lerp(south, north, v / maxShort);
cartographicScratch.height = height;
ellipsoid.cartographicToCartesian(cartographicScratch, cartesian3Scratch);
cartesianVertices.push(cartesian3Scratch.x);
cartesianVertices.push(cartesian3Scratch.y);
cartesianVertices.push(cartesian3Scratch.z);
}
const boundingSphere = BoundingSphere.fromVertices(
cartesianVertices,
Cartesian3.ZERO,
3,
boundingSphereScratch,
);
const orientedBoundingBox = OrientedBoundingBox.fromRectangle(
rectangle,
minimumHeight,
maximumHeight,
ellipsoid,
orientedBoundingBoxScratch,
);
const occluder = new EllipsoidalOccluder(ellipsoid);
const horizonOcclusionPoint =
occluder.computeHorizonCullingPointFromVerticesPossiblyUnderEllipsoid(
boundingSphere.center,
cartesianVertices,
3,
boundingSphere.center,
minimumHeight,
horizonOcclusionPointScratch,
);
const heightRange = maximumHeight - minimumHeight;
const vertices = new Uint16Array(
uBuffer.length + vBuffer.length + heightBuffer.length,
);
for (i = 0; i < uBuffer.length; ++i) {
vertices[i] = uBuffer[i];
}
let start = uBuffer.length;
for (i = 0; i < vBuffer.length; ++i) {
vertices[start + i] = vBuffer[i];
}
start += vBuffer.length;
for (i = 0; i < heightBuffer.length; ++i) {
vertices[start + i] =
(maxShort * (heightBuffer[i] - minimumHeight)) / heightRange;
}
const indicesTypedArray = IndexDatatype.createTypedArray(
uBuffer.length,
indices,
);
let encodedNormals;
if (hasVertexNormals) {
const normalArray = new Uint8Array(normalBuffer);
transferableObjects.push(
vertices.buffer,
indicesTypedArray.buffer,
normalArray.buffer,
);
encodedNormals = normalArray.buffer;
} else {
transferableObjects.push(vertices.buffer, indicesTypedArray.buffer);
}
return {
vertices: vertices.buffer,
encodedNormals: encodedNormals,
indices: indicesTypedArray.buffer,
minimumHeight: minimumHeight,
maximumHeight: maximumHeight,
westIndices: westIndices,
southIndices: southIndices,
eastIndices: eastIndices,
northIndices: northIndices,
boundingSphere: boundingSphere,
orientedBoundingBox: orientedBoundingBox,
horizonOcclusionPoint: horizonOcclusionPoint,
};
}
function Vertex() {
this.vertexBuffer = undefined;
this.index = undefined;
this.first = undefined;
this.second = undefined;
this.ratio = undefined;
}
Vertex.prototype.clone = function (result) {
if (!defined(result)) {
result = new Vertex();
}
result.uBuffer = this.uBuffer;
result.vBuffer = this.vBuffer;
result.heightBuffer = this.heightBuffer;
result.normalBuffer = this.normalBuffer;
result.index = this.index;
result.first = this.first;
result.second = this.second;
result.ratio = this.ratio;
return result;
};
Vertex.prototype.initializeIndexed = function (
uBuffer,
vBuffer,
heightBuffer,
normalBuffer,
index,
) {
this.uBuffer = uBuffer;
this.vBuffer = vBuffer;
this.heightBuffer = heightBuffer;
this.normalBuffer = normalBuffer;
this.index = index;
this.first = undefined;
this.second = undefined;
this.ratio = undefined;
};
Vertex.prototype.initializeFromClipResult = function (
clipResult,
index,
vertices,
) {
let nextIndex = index + 1;
if (clipResult[index] !== -1) {
vertices[clipResult[index]].clone(this);
} else {
this.vertexBuffer = undefined;
this.index = undefined;
this.first = vertices[clipResult[nextIndex]];
++nextIndex;
this.second = vertices[clipResult[nextIndex]];
++nextIndex;
this.ratio = clipResult[nextIndex];
++nextIndex;
}
return nextIndex;
};
Vertex.prototype.getKey = function () {
if (this.isIndexed()) {
return this.index;
}
return JSON.stringify({
first: this.first.getKey(),
second: this.second.getKey(),
ratio: this.ratio,
});
};
Vertex.prototype.isIndexed = function () {
return defined(this.index);
};
Vertex.prototype.getH = function () {
if (defined(this.index)) {
return this.heightBuffer[this.index];
}
return CesiumMath.lerp(this.first.getH(), this.second.getH(), this.ratio);
};
Vertex.prototype.getU = function () {
if (defined(this.index)) {
return this.uBuffer[this.index];
}
return CesiumMath.lerp(this.first.getU(), this.second.getU(), this.ratio);
};
Vertex.prototype.getV = function () {
if (defined(this.index)) {
return this.vBuffer[this.index];
}
return CesiumMath.lerp(this.first.getV(), this.second.getV(), this.ratio);
};
let encodedScratch = new Cartesian2();
// An upsampled triangle may be clipped twice before it is assigned an index
// In this case, we need a buffer to handle the recursion of getNormalX() and getNormalY().
let depth = -1;
const cartesianScratch1 = [new Cartesian3(), new Cartesian3()];
const cartesianScratch2 = [new Cartesian3(), new Cartesian3()];
function lerpOctEncodedNormal(vertex, result) {
++depth;
let first = cartesianScratch1[depth];
let second = cartesianScratch2[depth];
first = AttributeCompression.octDecode(
vertex.first.getNormalX(),
vertex.first.getNormalY(),
first,
);
second = AttributeCompression.octDecode(
vertex.second.getNormalX(),
vertex.second.getNormalY(),
second,
);
cartesian3Scratch = Cartesian3.lerp(
first,
second,
vertex.ratio,
cartesian3Scratch,
);
Cartesian3.normalize(cartesian3Scratch, cartesian3Scratch);
AttributeCompression.octEncode(cartesian3Scratch, result);
--depth;
return result;
}
Vertex.prototype.getNormalX = function () {
if (defined(this.index)) {
return this.normalBuffer[this.index * 2];
}
encodedScratch = lerpOctEncodedNormal(this, encodedScratch);
return encodedScratch.x;
};
Vertex.prototype.getNormalY = function () {
if (defined(this.index)) {
return this.normalBuffer[this.index * 2 + 1];
}
encodedScratch = lerpOctEncodedNormal(this, encodedScratch);
return encodedScratch.y;
};
const polygonVertices = [];
polygonVertices.push(new Vertex());
polygonVertices.push(new Vertex());
polygonVertices.push(new Vertex());
polygonVertices.push(new Vertex());
function addClippedPolygon(
uBuffer,
vBuffer,
heightBuffer,
normalBuffer,
indices,
vertexMap,
clipped,
triangleVertices,
hasVertexNormals,
) {
if (clipped.length === 0) {
return;
}
let numVertices = 0;
let clippedIndex = 0;
while (clippedIndex < clipped.length) {
clippedIndex = polygonVertices[numVertices++].initializeFromClipResult(
clipped,
clippedIndex,
triangleVertices,
);
}
for (let i = 0; i < numVertices; ++i) {
const polygonVertex = polygonVertices[i];
if (!polygonVertex.isIndexed()) {
const key = polygonVertex.getKey();
if (defined(vertexMap[key])) {
polygonVertex.newIndex = vertexMap[key];
} else {
const newIndex = uBuffer.length;
uBuffer.push(polygonVertex.getU());
vBuffer.push(polygonVertex.getV());
heightBuffer.push(polygonVertex.getH());
if (hasVertexNormals) {
normalBuffer.push(polygonVertex.getNormalX());
normalBuffer.push(polygonVertex.getNormalY());
}
polygonVertex.newIndex = newIndex;
vertexMap[key] = newIndex;
}
} else {
polygonVertex.newIndex = vertexMap[polygonVertex.index];
polygonVertex.uBuffer = uBuffer;
polygonVertex.vBuffer = vBuffer;
polygonVertex.heightBuffer = heightBuffer;
if (hasVertexNormals) {
polygonVertex.normalBuffer = normalBuffer;
}
}
}
if (numVertices === 3) {
// A triangle.
indices.push(polygonVertices[0].newIndex);
indices.push(polygonVertices[1].newIndex);
indices.push(polygonVertices[2].newIndex);
} else if (numVertices === 4) {
// A quad - two triangles.
indices.push(polygonVertices[0].newIndex);
indices.push(polygonVertices[1].newIndex);
indices.push(polygonVertices[2].newIndex);
indices.push(polygonVertices[0].newIndex);
indices.push(polygonVertices[2].newIndex);
indices.push(polygonVertices[3].newIndex);
}
}
export default createTaskProcessorWorker(upsampleQuantizedTerrainMesh);
@@ -0,0 +1,55 @@
import Cesium3DTilesTerrainGeometryProcessor from "../Core/Cesium3DTilesTerrainGeometryProcessor.js";
import createTaskProcessorWorker from "./createTaskProcessorWorker.js";
/**
* @private
* @param {Cesium3DTilesTerrainGeometryProcessor.UpsampleMeshOptions} options An object describing options for mesh upsampling.
* @param {ArrayBuffer[]} transferableObjects An array of buffers that can be transferred back to the main thread.
* @returns {TerrainMeshProxy} An object containing selected info from the upsampled TerrainMesh.
*/
function upsampleVerticesFromCesium3DTilesTerrain(
options,
transferableObjects,
) {
const mesh = Cesium3DTilesTerrainGeometryProcessor.upsampleMesh(options);
const verticesBuffer = mesh.vertices.buffer;
const indicesBuffer = mesh.indices.buffer;
const westIndicesBuffer = mesh.westIndicesSouthToNorth.buffer;
const southIndicesBuffer = mesh.southIndicesEastToWest.buffer;
const eastIndicesBuffer = mesh.eastIndicesNorthToSouth.buffer;
const northIndicesBuffer = mesh.northIndicesWestToEast.buffer;
transferableObjects.push(
verticesBuffer,
indicesBuffer,
westIndicesBuffer,
southIndicesBuffer,
eastIndicesBuffer,
northIndicesBuffer,
);
/** @type {TerrainMeshProxy} */
const result = {
verticesBuffer: verticesBuffer,
indicesBuffer: indicesBuffer,
vertexCountWithoutSkirts: mesh.vertexCountWithoutSkirts,
indexCountWithoutSkirts: mesh.indexCountWithoutSkirts,
encoding: mesh.encoding,
westIndicesBuffer: westIndicesBuffer,
southIndicesBuffer: southIndicesBuffer,
eastIndicesBuffer: eastIndicesBuffer,
northIndicesBuffer: northIndicesBuffer,
minimumHeight: mesh.minimumHeight,
maximumHeight: mesh.maximumHeight,
boundingSphere: mesh.boundingSphere3D,
orientedBoundingBox: mesh.orientedBoundingBox,
horizonOcclusionPoint: mesh.horizonOcclusionPoint,
};
return result;
}
export default createTaskProcessorWorker(
upsampleVerticesFromCesium3DTilesTerrain,
);