import Cartesian3 from "../Core/Cartesian3.js";
import defaultValue from "../Core/defaultValue.js";
import defined from "../Core/defined.js";
import deprecationWarning from "../Core/deprecationWarning.js";
import destroyObject from "../Core/destroyObject.js";
import DeveloperError from "../Core/DeveloperError.js";
import getJsonFromTypedArray from "../Core/getJsonFromTypedArray.js";
import Matrix4 from "../Core/Matrix4.js";
import RuntimeError from "../Core/RuntimeError.js";
import Cesium3DTileBatchTable from "./Cesium3DTileBatchTable.js";
import Vector3DTileGeometry from "./Vector3DTileGeometry.js";
/**
*
* Implements the {@link Cesium3DTileContent} interface.
*
*
* @alias Geometry3DTileContent
* @constructor
*
* @private
*/
function Geometry3DTileContent(
tileset,
tile,
resource,
arrayBuffer,
byteOffset
) {
this._tileset = tileset;
this._tile = tile;
this._resource = resource;
this._geometries = undefined;
this._metadata = undefined;
this._batchTable = undefined;
this._features = undefined;
/**
* Part of the {@link Cesium3DTileContent} interface.
*/
this.featurePropertiesDirty = false;
this._group = undefined;
this._ready = false;
// This is for backwards compatibility. It can be removed once readyPromise is removed.
this._resolveContent = undefined;
this._readyPromise = new Promise((resolve) => {
this._resolveContent = resolve;
});
initialize(this, arrayBuffer, byteOffset);
}
Object.defineProperties(Geometry3DTileContent.prototype, {
featuresLength: {
get: function () {
return defined(this._batchTable) ? this._batchTable.featuresLength : 0;
},
},
pointsLength: {
get: function () {
return 0;
},
},
trianglesLength: {
get: function () {
if (defined(this._geometries)) {
return this._geometries.trianglesLength;
}
return 0;
},
},
geometryByteLength: {
get: function () {
if (defined(this._geometries)) {
return this._geometries.geometryByteLength;
}
return 0;
},
},
texturesByteLength: {
get: function () {
return 0;
},
},
batchTableByteLength: {
get: function () {
return defined(this._batchTable)
? this._batchTable.batchTableByteLength
: 0;
},
},
innerContents: {
get: function () {
return undefined;
},
},
/**
* Returns true when the tile's content is ready to render; otherwise false
*
* @memberof Geometry3DTileContent.prototype
*
* @type {boolean}
* @readonly
* @private
*/
ready: {
get: function () {
return this._ready;
},
},
/**
* Gets the promise that will be resolved when the tile's content is ready to render.
*
* @memberof Geometry3DTileContent.prototype
*
* @type {Promise}
* @readonly
* @deprecated
* @private
*/
readyPromise: {
get: function () {
deprecationWarning(
"Geometry3DTileContent.readyPromise",
"Geometry3DTileContent.readyPromise was deprecated in CesiumJS 1.104. It will be removed in 1.107. Wait for Geometry3DTileContent.ready to return true instead."
);
return this._readyPromise;
},
},
tileset: {
get: function () {
return this._tileset;
},
},
tile: {
get: function () {
return this._tile;
},
},
url: {
get: function () {
return this._resource.getUrlComponent(true);
},
},
metadata: {
get: function () {
return this._metadata;
},
set: function (value) {
this._metadata = value;
},
},
batchTable: {
get: function () {
return this._batchTable;
},
},
group: {
get: function () {
return this._group;
},
set: function (value) {
this._group = value;
},
},
});
function createColorChangedCallback(content) {
return function (batchId, color) {
if (defined(content._geometries)) {
content._geometries.updateCommands(batchId, color);
}
};
}
function getBatchIds(featureTableJson, featureTableBinary) {
let boxBatchIds;
let cylinderBatchIds;
let ellipsoidBatchIds;
let sphereBatchIds;
let i;
const numberOfBoxes = defaultValue(featureTableJson.BOXES_LENGTH, 0);
const numberOfCylinders = defaultValue(featureTableJson.CYLINDERS_LENGTH, 0);
const numberOfEllipsoids = defaultValue(
featureTableJson.ELLIPSOIDS_LENGTH,
0
);
const numberOfSpheres = defaultValue(featureTableJson.SPHERES_LENGTH, 0);
if (numberOfBoxes > 0 && defined(featureTableJson.BOX_BATCH_IDS)) {
const boxBatchIdsByteOffset =
featureTableBinary.byteOffset + featureTableJson.BOX_BATCH_IDS.byteOffset;
boxBatchIds = new Uint16Array(
featureTableBinary.buffer,
boxBatchIdsByteOffset,
numberOfBoxes
);
}
if (numberOfCylinders > 0 && defined(featureTableJson.CYLINDER_BATCH_IDS)) {
const cylinderBatchIdsByteOffset =
featureTableBinary.byteOffset +
featureTableJson.CYLINDER_BATCH_IDS.byteOffset;
cylinderBatchIds = new Uint16Array(
featureTableBinary.buffer,
cylinderBatchIdsByteOffset,
numberOfCylinders
);
}
if (numberOfEllipsoids > 0 && defined(featureTableJson.ELLIPSOID_BATCH_IDS)) {
const ellipsoidBatchIdsByteOffset =
featureTableBinary.byteOffset +
featureTableJson.ELLIPSOID_BATCH_IDS.byteOffset;
ellipsoidBatchIds = new Uint16Array(
featureTableBinary.buffer,
ellipsoidBatchIdsByteOffset,
numberOfEllipsoids
);
}
if (numberOfSpheres > 0 && defined(featureTableJson.SPHERE_BATCH_IDS)) {
const sphereBatchIdsByteOffset =
featureTableBinary.byteOffset +
featureTableJson.SPHERE_BATCH_IDS.byteOffset;
sphereBatchIds = new Uint16Array(
featureTableBinary.buffer,
sphereBatchIdsByteOffset,
numberOfSpheres
);
}
const atLeastOneDefined =
defined(boxBatchIds) ||
defined(cylinderBatchIds) ||
defined(ellipsoidBatchIds) ||
defined(sphereBatchIds);
const atLeastOneUndefined =
(numberOfBoxes > 0 && !defined(boxBatchIds)) ||
(numberOfCylinders > 0 && !defined(cylinderBatchIds)) ||
(numberOfEllipsoids > 0 && !defined(ellipsoidBatchIds)) ||
(numberOfSpheres > 0 && !defined(sphereBatchIds));
if (atLeastOneDefined && atLeastOneUndefined) {
throw new RuntimeError(
"If one group of batch ids is defined, then all batch ids must be defined"
);
}
const allUndefinedBatchIds =
!defined(boxBatchIds) &&
!defined(cylinderBatchIds) &&
!defined(ellipsoidBatchIds) &&
!defined(sphereBatchIds);
if (allUndefinedBatchIds) {
let id = 0;
if (!defined(boxBatchIds) && numberOfBoxes > 0) {
boxBatchIds = new Uint16Array(numberOfBoxes);
for (i = 0; i < numberOfBoxes; ++i) {
boxBatchIds[i] = id++;
}
}
if (!defined(cylinderBatchIds) && numberOfCylinders > 0) {
cylinderBatchIds = new Uint16Array(numberOfCylinders);
for (i = 0; i < numberOfCylinders; ++i) {
cylinderBatchIds[i] = id++;
}
}
if (!defined(ellipsoidBatchIds) && numberOfEllipsoids > 0) {
ellipsoidBatchIds = new Uint16Array(numberOfEllipsoids);
for (i = 0; i < numberOfEllipsoids; ++i) {
ellipsoidBatchIds[i] = id++;
}
}
if (!defined(sphereBatchIds) && numberOfSpheres > 0) {
sphereBatchIds = new Uint16Array(numberOfSpheres);
for (i = 0; i < numberOfSpheres; ++i) {
sphereBatchIds[i] = id++;
}
}
}
return {
boxes: boxBatchIds,
cylinders: cylinderBatchIds,
ellipsoids: ellipsoidBatchIds,
spheres: sphereBatchIds,
};
}
const sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
function initialize(content, arrayBuffer, byteOffset) {
byteOffset = defaultValue(byteOffset, 0);
const uint8Array = new Uint8Array(arrayBuffer);
const view = new DataView(arrayBuffer);
byteOffset += sizeOfUint32; // Skip magic number
const version = view.getUint32(byteOffset, true);
if (version !== 1) {
throw new RuntimeError(
`Only Geometry tile version 1 is supported. Version ${version} is not.`
);
}
byteOffset += sizeOfUint32;
const byteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
if (byteLength === 0) {
content._ready = true;
content._resolveContent(content);
return;
}
const featureTableJSONByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
if (featureTableJSONByteLength === 0) {
throw new RuntimeError(
"Feature table must have a byte length greater than zero"
);
}
const featureTableBinaryByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
const batchTableJSONByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
const batchTableBinaryByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
const featureTableJson = getJsonFromTypedArray(
uint8Array,
byteOffset,
featureTableJSONByteLength
);
byteOffset += featureTableJSONByteLength;
const featureTableBinary = new Uint8Array(
arrayBuffer,
byteOffset,
featureTableBinaryByteLength
);
byteOffset += featureTableBinaryByteLength;
let batchTableJson;
let batchTableBinary;
if (batchTableJSONByteLength > 0) {
// PERFORMANCE_IDEA: is it possible to allocate this on-demand? Perhaps keep the
// arraybuffer/string compressed in memory and then decompress it when it is first accessed.
//
// We could also make another request for it, but that would make the property set/get
// API async, and would double the number of numbers in some cases.
batchTableJson = getJsonFromTypedArray(
uint8Array,
byteOffset,
batchTableJSONByteLength
);
byteOffset += batchTableJSONByteLength;
if (batchTableBinaryByteLength > 0) {
// Has a batch table binary
batchTableBinary = new Uint8Array(
arrayBuffer,
byteOffset,
batchTableBinaryByteLength
);
// Copy the batchTableBinary section and let the underlying ArrayBuffer be freed
batchTableBinary = new Uint8Array(batchTableBinary);
}
}
const numberOfBoxes = defaultValue(featureTableJson.BOXES_LENGTH, 0);
const numberOfCylinders = defaultValue(featureTableJson.CYLINDERS_LENGTH, 0);
const numberOfEllipsoids = defaultValue(
featureTableJson.ELLIPSOIDS_LENGTH,
0
);
const numberOfSpheres = defaultValue(featureTableJson.SPHERES_LENGTH, 0);
const totalPrimitives =
numberOfBoxes + numberOfCylinders + numberOfEllipsoids + numberOfSpheres;
const batchTable = new Cesium3DTileBatchTable(
content,
totalPrimitives,
batchTableJson,
batchTableBinary,
createColorChangedCallback(content)
);
content._batchTable = batchTable;
if (totalPrimitives === 0) {
return;
}
const modelMatrix = content.tile.computedTransform;
let center;
if (defined(featureTableJson.RTC_CENTER)) {
center = Cartesian3.unpack(featureTableJson.RTC_CENTER);
Matrix4.multiplyByPoint(modelMatrix, center, center);
}
const batchIds = getBatchIds(featureTableJson, featureTableBinary);
if (
numberOfBoxes > 0 ||
numberOfCylinders > 0 ||
numberOfEllipsoids > 0 ||
numberOfSpheres > 0
) {
let boxes;
let cylinders;
let ellipsoids;
let spheres;
if (numberOfBoxes > 0) {
const boxesByteOffset =
featureTableBinary.byteOffset + featureTableJson.BOXES.byteOffset;
boxes = new Float32Array(
featureTableBinary.buffer,
boxesByteOffset,
Vector3DTileGeometry.packedBoxLength * numberOfBoxes
);
}
if (numberOfCylinders > 0) {
const cylindersByteOffset =
featureTableBinary.byteOffset + featureTableJson.CYLINDERS.byteOffset;
cylinders = new Float32Array(
featureTableBinary.buffer,
cylindersByteOffset,
Vector3DTileGeometry.packedCylinderLength * numberOfCylinders
);
}
if (numberOfEllipsoids > 0) {
const ellipsoidsByteOffset =
featureTableBinary.byteOffset + featureTableJson.ELLIPSOIDS.byteOffset;
ellipsoids = new Float32Array(
featureTableBinary.buffer,
ellipsoidsByteOffset,
Vector3DTileGeometry.packedEllipsoidLength * numberOfEllipsoids
);
}
if (numberOfSpheres > 0) {
const spheresByteOffset =
featureTableBinary.byteOffset + featureTableJson.SPHERES.byteOffset;
spheres = new Float32Array(
featureTableBinary.buffer,
spheresByteOffset,
Vector3DTileGeometry.packedSphereLength * numberOfSpheres
);
}
content._geometries = new Vector3DTileGeometry({
boxes: boxes,
boxBatchIds: batchIds.boxes,
cylinders: cylinders,
cylinderBatchIds: batchIds.cylinders,
ellipsoids: ellipsoids,
ellipsoidBatchIds: batchIds.ellipsoids,
spheres: spheres,
sphereBatchIds: batchIds.spheres,
center: center,
modelMatrix: modelMatrix,
batchTable: batchTable,
boundingVolume: content.tile.boundingVolume.boundingVolume,
});
return content;
}
return Promise.resolve(content);
}
function createFeatures(content) {
const featuresLength = content.featuresLength;
if (!defined(content._features) && featuresLength > 0) {
const features = new Array(featuresLength);
if (defined(content._geometries)) {
content._geometries.createFeatures(content, features);
}
content._features = features;
}
}
Geometry3DTileContent.prototype.hasProperty = function (batchId, name) {
return this._batchTable.hasProperty(batchId, name);
};
Geometry3DTileContent.prototype.getFeature = function (batchId) {
//>>includeStart('debug', pragmas.debug);
const featuresLength = this.featuresLength;
if (!defined(batchId) || batchId < 0 || batchId >= featuresLength) {
throw new DeveloperError(
`batchId is required and between zero and featuresLength - 1 (${
featuresLength - 1
}).`
);
}
//>>includeEnd('debug');
createFeatures(this);
return this._features[batchId];
};
Geometry3DTileContent.prototype.applyDebugSettings = function (enabled, color) {
if (defined(this._geometries)) {
this._geometries.applyDebugSettings(enabled, color);
}
};
Geometry3DTileContent.prototype.applyStyle = function (style) {
createFeatures(this);
if (defined(this._geometries)) {
this._geometries.applyStyle(style, this._features);
}
};
Geometry3DTileContent.prototype.update = function (tileset, frameState) {
if (defined(this._geometries)) {
this._geometries.classificationType = this._tileset.classificationType;
this._geometries.debugWireframe = this._tileset.debugWireframe;
this._geometries.update(frameState);
}
if (defined(this._batchTable) && this._geometries.ready) {
this._batchTable.update(tileset, frameState);
this._ready = true;
this._resolveContent(this);
}
};
Geometry3DTileContent.prototype.isDestroyed = function () {
return false;
};
Geometry3DTileContent.prototype.destroy = function () {
this._geometries = this._geometries && this._geometries.destroy();
this._batchTable = this._batchTable && this._batchTable.destroy();
return destroyObject(this);
};
export default Geometry3DTileContent;