cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
480 lines (415 loc) • 21.4 kB
JavaScript
import AttributeCompression from '../Core/AttributeCompression.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Color from '../Core/Color.js';
import ComponentDatatype from '../Core/ComponentDatatype.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 Ellipsoid from '../Core/Ellipsoid.js';
import getStringFromTypedArray from '../Core/getStringFromTypedArray.js';
import Matrix3 from '../Core/Matrix3.js';
import Matrix4 from '../Core/Matrix4.js';
import Quaternion from '../Core/Quaternion.js';
import RequestType from '../Core/RequestType.js';
import RuntimeError from '../Core/RuntimeError.js';
import Transforms from '../Core/Transforms.js';
import TranslationRotationScale from '../Core/TranslationRotationScale.js';
import Pass from '../Renderer/Pass.js';
import Axis from './Axis.js';
import Cesium3DTileBatchTable from './Cesium3DTileBatchTable.js';
import Cesium3DTileFeature from './Cesium3DTileFeature.js';
import Cesium3DTileFeatureTable from './Cesium3DTileFeatureTable.js';
import ModelInstanceCollection from './ModelInstanceCollection.js';
/**
* Represents the contents of a
* {@link https://github.com/CesiumGS/3d-tiles/tree/master/specification/TileFormats/Instanced3DModel|Instanced 3D Model}
* tile in a {@link https://github.com/CesiumGS/3d-tiles/tree/master/specification|3D Tiles} tileset.
* <p>
* Implements the {@link Cesium3DTileContent} interface.
* </p>
*
* @alias Instanced3DModel3DTileContent
* @constructor
*
* @private
*/
function Instanced3DModel3DTileContent(tileset, tile, resource, arrayBuffer, byteOffset) {
this._tileset = tileset;
this._tile = tile;
this._resource = resource;
this._modelInstanceCollection = undefined;
this._batchTable = undefined;
this._features = undefined;
this.featurePropertiesDirty = false;
initialize(this, arrayBuffer, byteOffset);
}
// This can be overridden for testing purposes
Instanced3DModel3DTileContent._deprecationWarning = deprecationWarning;
Object.defineProperties(Instanced3DModel3DTileContent.prototype, {
featuresLength : {
get : function() {
return this._batchTable.featuresLength;
}
},
pointsLength : {
get : function() {
return 0;
}
},
trianglesLength : {
get : function() {
var model = this._modelInstanceCollection._model;
if (defined(model)) {
return model.trianglesLength;
}
return 0;
}
},
geometryByteLength : {
get : function() {
var model = this._modelInstanceCollection._model;
if (defined(model)) {
return model.geometryByteLength;
}
return 0;
}
},
texturesByteLength : {
get : function() {
var model = this._modelInstanceCollection._model;
if (defined(model)) {
return model.texturesByteLength;
}
return 0;
}
},
batchTableByteLength : {
get : function() {
return this._batchTable.memorySizeInBytes;
}
},
innerContents : {
get : function() {
return undefined;
}
},
readyPromise : {
get : function() {
return this._modelInstanceCollection.readyPromise;
}
},
tileset : {
get : function() {
return this._tileset;
}
},
tile : {
get : function() {
return this._tile;
}
},
url: {
get: function() {
return this._resource.getUrlComponent(true);
}
},
batchTable : {
get : function() {
return this._batchTable;
}
}
});
function getPickIdCallback(content) {
return function() {
return content._batchTable.getPickId();
};
}
var sizeOfUint32 = Uint32Array.BYTES_PER_ELEMENT;
var propertyScratch1 = new Array(4);
var propertyScratch2 = new Array(4);
function initialize(content, arrayBuffer, byteOffset) {
var byteStart = defaultValue(byteOffset, 0);
byteOffset = byteStart;
var uint8Array = new Uint8Array(arrayBuffer);
var view = new DataView(arrayBuffer);
byteOffset += sizeOfUint32; // Skip magic
var version = view.getUint32(byteOffset, true);
if (version !== 1) {
throw new RuntimeError('Only Instanced 3D Model version 1 is supported. Version ' + version + ' is not.');
}
byteOffset += sizeOfUint32;
var byteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
var featureTableJsonByteLength = view.getUint32(byteOffset, true);
if (featureTableJsonByteLength === 0) {
throw new RuntimeError('featureTableJsonByteLength is zero, the feature table must be defined.');
}
byteOffset += sizeOfUint32;
var featureTableBinaryByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
var batchTableJsonByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
var batchTableBinaryByteLength = view.getUint32(byteOffset, true);
byteOffset += sizeOfUint32;
var gltfFormat = view.getUint32(byteOffset, true);
if (gltfFormat !== 1 && gltfFormat !== 0) {
throw new RuntimeError('Only glTF format 0 (uri) or 1 (embedded) are supported. Format ' + gltfFormat + ' is not.');
}
byteOffset += sizeOfUint32;
var featureTableString = getStringFromTypedArray(uint8Array, byteOffset, featureTableJsonByteLength);
var featureTableJson = JSON.parse(featureTableString);
byteOffset += featureTableJsonByteLength;
var featureTableBinary = new Uint8Array(arrayBuffer, byteOffset, featureTableBinaryByteLength);
byteOffset += featureTableBinaryByteLength;
var featureTable = new Cesium3DTileFeatureTable(featureTableJson, featureTableBinary);
var instancesLength = featureTable.getGlobalProperty('INSTANCES_LENGTH');
featureTable.featuresLength = instancesLength;
if (!defined(instancesLength)) {
throw new RuntimeError('Feature table global property: INSTANCES_LENGTH must be defined');
}
var batchTableJson;
var batchTableBinary;
if (batchTableJsonByteLength > 0) {
var batchTableString = getStringFromTypedArray(uint8Array, byteOffset, batchTableJsonByteLength);
batchTableJson = JSON.parse(batchTableString);
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);
byteOffset += batchTableBinaryByteLength;
}
}
content._batchTable = new Cesium3DTileBatchTable(content, instancesLength, batchTableJson, batchTableBinary);
var gltfByteLength = byteStart + byteLength - byteOffset;
if (gltfByteLength === 0) {
throw new RuntimeError('glTF byte length is zero, i3dm must have a glTF to instance.');
}
var gltfView;
if (byteOffset % 4 === 0) {
gltfView = new Uint8Array(arrayBuffer, byteOffset, gltfByteLength);
} else {
// Create a copy of the glb so that it is 4-byte aligned
Instanced3DModel3DTileContent._deprecationWarning('i3dm-glb-unaligned', 'The embedded glb is not aligned to a 4-byte boundary.');
gltfView = new Uint8Array(uint8Array.subarray(byteOffset, byteOffset + gltfByteLength));
}
var tileset = content._tileset;
// Create model instance collection
var collectionOptions = {
instances : new Array(instancesLength),
batchTable : content._batchTable,
cull : false, // Already culled by 3D Tiles
url : undefined,
requestType : RequestType.TILES3D,
gltf : undefined,
basePath : undefined,
incrementallyLoadTextures : false,
upAxis : tileset._gltfUpAxis,
forwardAxis : Axis.X,
opaquePass : Pass.CESIUM_3D_TILE, // Draw opaque portions during the 3D Tiles pass
pickIdLoaded : getPickIdCallback(content),
imageBasedLightingFactor : tileset.imageBasedLightingFactor,
lightColor : tileset.lightColor,
luminanceAtZenith : tileset.luminanceAtZenith,
sphericalHarmonicCoefficients : tileset.sphericalHarmonicCoefficients,
specularEnvironmentMaps : tileset.specularEnvironmentMaps
};
if (gltfFormat === 0) {
var gltfUrl = getStringFromTypedArray(gltfView);
// We need to remove padding from the end of the model URL in case this tile was part of a composite tile.
// This removes all white space and null characters from the end of the string.
gltfUrl = gltfUrl.replace(/[\s\0]+$/, '');
collectionOptions.url = content._resource.getDerivedResource({
url: gltfUrl
});
} else {
collectionOptions.gltf = gltfView;
collectionOptions.basePath = content._resource.clone();
}
var eastNorthUp = featureTable.getGlobalProperty('EAST_NORTH_UP');
var rtcCenter;
var rtcCenterArray = featureTable.getGlobalProperty('RTC_CENTER', ComponentDatatype.FLOAT, 3);
if (defined(rtcCenterArray)) {
rtcCenter = Cartesian3.unpack(rtcCenterArray);
}
var instances = collectionOptions.instances;
var instancePosition = new Cartesian3();
var instancePositionArray = new Array(3);
var instanceNormalRight = new Cartesian3();
var instanceNormalUp = new Cartesian3();
var instanceNormalForward = new Cartesian3();
var instanceRotation = new Matrix3();
var instanceQuaternion = new Quaternion();
var instanceScale = new Cartesian3();
var instanceTranslationRotationScale = new TranslationRotationScale();
var instanceTransform = new Matrix4();
for (var i = 0; i < instancesLength; i++) {
// Get the instance position
var position = featureTable.getProperty('POSITION', ComponentDatatype.FLOAT, 3, i, propertyScratch1);
if (!defined(position)) {
position = instancePositionArray;
var positionQuantized = featureTable.getProperty('POSITION_QUANTIZED', ComponentDatatype.UNSIGNED_SHORT, 3, i, propertyScratch1);
if (!defined(positionQuantized)) {
throw new RuntimeError('Either POSITION or POSITION_QUANTIZED must be defined for each instance.');
}
var quantizedVolumeOffset = featureTable.getGlobalProperty('QUANTIZED_VOLUME_OFFSET', ComponentDatatype.FLOAT, 3);
if (!defined(quantizedVolumeOffset)) {
throw new RuntimeError('Global property: QUANTIZED_VOLUME_OFFSET must be defined for quantized positions.');
}
var quantizedVolumeScale = featureTable.getGlobalProperty('QUANTIZED_VOLUME_SCALE', ComponentDatatype.FLOAT, 3);
if (!defined(quantizedVolumeScale)) {
throw new RuntimeError('Global property: QUANTIZED_VOLUME_SCALE must be defined for quantized positions.');
}
for (var j = 0; j < 3; j++) {
position[j] = (positionQuantized[j] / 65535.0 * quantizedVolumeScale[j]) + quantizedVolumeOffset[j];
}
}
Cartesian3.unpack(position, 0, instancePosition);
if (defined(rtcCenter)) {
Cartesian3.add(instancePosition, rtcCenter, instancePosition);
}
instanceTranslationRotationScale.translation = instancePosition;
// Get the instance rotation
var normalUp = featureTable.getProperty('NORMAL_UP', ComponentDatatype.FLOAT, 3, i, propertyScratch1);
var normalRight = featureTable.getProperty('NORMAL_RIGHT', ComponentDatatype.FLOAT, 3, i, propertyScratch2);
var hasCustomOrientation = false;
if (defined(normalUp)) {
if (!defined(normalRight)) {
throw new RuntimeError('To define a custom orientation, both NORMAL_UP and NORMAL_RIGHT must be defined.');
}
Cartesian3.unpack(normalUp, 0, instanceNormalUp);
Cartesian3.unpack(normalRight, 0, instanceNormalRight);
hasCustomOrientation = true;
} else {
var octNormalUp = featureTable.getProperty('NORMAL_UP_OCT32P', ComponentDatatype.UNSIGNED_SHORT, 2, i, propertyScratch1);
var octNormalRight = featureTable.getProperty('NORMAL_RIGHT_OCT32P', ComponentDatatype.UNSIGNED_SHORT, 2, i, propertyScratch2);
if (defined(octNormalUp)) {
if (!defined(octNormalRight)) {
throw new RuntimeError('To define a custom orientation with oct-encoded vectors, both NORMAL_UP_OCT32P and NORMAL_RIGHT_OCT32P must be defined.');
}
AttributeCompression.octDecodeInRange(octNormalUp[0], octNormalUp[1], 65535, instanceNormalUp);
AttributeCompression.octDecodeInRange(octNormalRight[0], octNormalRight[1], 65535, instanceNormalRight);
hasCustomOrientation = true;
} else if (eastNorthUp) {
Transforms.eastNorthUpToFixedFrame(instancePosition, Ellipsoid.WGS84, instanceTransform);
Matrix4.getMatrix3(instanceTransform, instanceRotation);
} else {
Matrix3.clone(Matrix3.IDENTITY, instanceRotation);
}
}
if (hasCustomOrientation) {
Cartesian3.cross(instanceNormalRight, instanceNormalUp, instanceNormalForward);
Cartesian3.normalize(instanceNormalForward, instanceNormalForward);
Matrix3.setColumn(instanceRotation, 0, instanceNormalRight, instanceRotation);
Matrix3.setColumn(instanceRotation, 1, instanceNormalUp, instanceRotation);
Matrix3.setColumn(instanceRotation, 2, instanceNormalForward, instanceRotation);
}
Quaternion.fromRotationMatrix(instanceRotation, instanceQuaternion);
instanceTranslationRotationScale.rotation = instanceQuaternion;
// Get the instance scale
instanceScale = Cartesian3.fromElements(1.0, 1.0, 1.0, instanceScale);
var scale = featureTable.getProperty('SCALE', ComponentDatatype.FLOAT, 1, i);
if (defined(scale)) {
Cartesian3.multiplyByScalar(instanceScale, scale, instanceScale);
}
var nonUniformScale = featureTable.getProperty('SCALE_NON_UNIFORM', ComponentDatatype.FLOAT, 3, i, propertyScratch1);
if (defined(nonUniformScale)) {
instanceScale.x *= nonUniformScale[0];
instanceScale.y *= nonUniformScale[1];
instanceScale.z *= nonUniformScale[2];
}
instanceTranslationRotationScale.scale = instanceScale;
// Get the batchId
var batchId = featureTable.getProperty('BATCH_ID', ComponentDatatype.UNSIGNED_SHORT, 1, i);
if (!defined(batchId)) {
// If BATCH_ID semantic is undefined, batchId is just the instance number
batchId = i;
}
// Create the model matrix and the instance
Matrix4.fromTranslationRotationScale(instanceTranslationRotationScale, instanceTransform);
var modelMatrix = instanceTransform.clone();
instances[i] = {
modelMatrix : modelMatrix,
batchId : batchId
};
}
content._modelInstanceCollection = new ModelInstanceCollection(collectionOptions);
}
function createFeatures(content) {
var featuresLength = content.featuresLength;
if (!defined(content._features) && (featuresLength > 0)) {
var features = new Array(featuresLength);
for (var i = 0; i < featuresLength; ++i) {
features[i] = new Cesium3DTileFeature(content, i);
}
content._features = features;
}
}
Instanced3DModel3DTileContent.prototype.hasProperty = function(batchId, name) {
return this._batchTable.hasProperty(batchId, name);
};
Instanced3DModel3DTileContent.prototype.getFeature = function(batchId) {
var featuresLength = this.featuresLength;
//>>includeStart('debug', pragmas.debug);
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];
};
Instanced3DModel3DTileContent.prototype.applyDebugSettings = function(enabled, color) {
color = enabled ? color : Color.WHITE;
this._batchTable.setAllColor(color);
};
Instanced3DModel3DTileContent.prototype.applyStyle = function(style) {
this._batchTable.applyStyle(style);
};
Instanced3DModel3DTileContent.prototype.update = function(tileset, frameState) {
var commandStart = frameState.commandList.length;
// In the PROCESSING state we may be calling update() to move forward
// the content's resource loading. In the READY state, it will
// actually generate commands.
this._batchTable.update(tileset, frameState);
this._modelInstanceCollection.modelMatrix = this._tile.computedTransform;
this._modelInstanceCollection.shadows = this._tileset.shadows;
this._modelInstanceCollection.lightColor = this._tileset.lightColor;
this._modelInstanceCollection.luminanceAtZenith = this._tileset.luminanceAtZenith;
this._modelInstanceCollection.sphericalHarmonicCoefficients = this._tileset.sphericalHarmonicCoefficients;
this._modelInstanceCollection.specularEnvironmentMaps = this._tileset.specularEnvironmentMaps;
this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe;
var model = this._modelInstanceCollection._model;
if (defined(model)) {
// Update for clipping planes
var tilesetClippingPlanes = this._tileset.clippingPlanes;
model.clippingPlanesOriginMatrix = this._tileset.clippingPlanesOriginMatrix;
if (defined(tilesetClippingPlanes) && this._tile.clippingPlanesDirty) {
// Dereference the clipping planes from the model if they are irrelevant - saves on shading
// Link/Dereference directly to avoid ownership checks.
model._clippingPlanes = (tilesetClippingPlanes.enabled && this._tile._isClipped) ? tilesetClippingPlanes : undefined;
}
// If the model references a different ClippingPlaneCollection due to the tileset's collection being replaced with a
// ClippingPlaneCollection that gives this tile the same clipping status, update the model to use the new ClippingPlaneCollection.
if (defined(tilesetClippingPlanes) && defined(model._clippingPlanes) && model._clippingPlanes !== tilesetClippingPlanes) {
model._clippingPlanes = tilesetClippingPlanes;
}
}
this._modelInstanceCollection.update(frameState);
// If any commands were pushed, add derived commands
var commandEnd = frameState.commandList.length;
if ((commandStart < commandEnd) && (frameState.passes.render || frameState.passes.pick)) {
this._batchTable.addDerivedCommands(frameState, commandStart, false);
}
};
Instanced3DModel3DTileContent.prototype.isDestroyed = function() {
return false;
};
Instanced3DModel3DTileContent.prototype.destroy = function() {
this._modelInstanceCollection = this._modelInstanceCollection && this._modelInstanceCollection.destroy();
this._batchTable = this._batchTable && this._batchTable.destroy();
return destroyObject(this);
};
export default Instanced3DModel3DTileContent;