import arraySlice from '../Core/arraySlice.js';
import BoundingSphere from '../Core/BoundingSphere.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Cartesian4 from '../Core/Cartesian4.js';
import Color from '../Core/Color.js';
import combine from '../Core/combine.js';
import ComponentDatatype from '../Core/ComponentDatatype.js';
import defaultValue from '../Core/defaultValue.js';
import defined from '../Core/defined.js';
import defineProperties from '../Core/defineProperties.js';
import destroyObject from '../Core/destroyObject.js';
import DeveloperError from '../Core/DeveloperError.js';
import FeatureDetection from '../Core/FeatureDetection.js';
import IndexDatatype from '../Core/IndexDatatype.js';
import Matrix4 from '../Core/Matrix4.js';
import PrimitiveType from '../Core/PrimitiveType.js';
import RuntimeError from '../Core/RuntimeError.js';
import Transforms from '../Core/Transforms.js';
import WebGLConstants from '../Core/WebGLConstants.js';
import addDefaults from '../ThirdParty/GltfPipeline/addDefaults.js';
import ForEach from '../ThirdParty/GltfPipeline/ForEach.js';
import getAccessorByteStride from '../ThirdParty/GltfPipeline/getAccessorByteStride.js';
import numberOfComponentsForType from '../ThirdParty/GltfPipeline/numberOfComponentsForType.js';
import parseGlb from '../ThirdParty/GltfPipeline/parseGlb.js';
import updateVersion from '../ThirdParty/GltfPipeline/updateVersion.js';
import when from '../ThirdParty/when.js';
import Axis from './Axis.js';
import ModelLoadResources from './ModelLoadResources.js';
import ModelUtility from './ModelUtility.js';
import processModelMaterialsCommon from './processModelMaterialsCommon.js';
import processPbrMaterials from './processPbrMaterials.js';
import SceneMode from './SceneMode.js';
import Vector3DTileBatch from './Vector3DTileBatch.js';
import Vector3DTilePrimitive from './Vector3DTilePrimitive.js';
var boundingSphereCartesian3Scratch = new Cartesian3();
var ModelState = ModelUtility.ModelState;
///////////////////////////////////////////////////////////////////////////
/**
* A 3D model for classifying other 3D assets based on glTF, the runtime 3D asset format.
* This is a special case when a model of a 3D tileset becomes a classifier when setting {@link Cesium3DTileset#classificationType}.
*
* @alias ClassificationModel
* @constructor
*
* @private
*
* @param {Object} options Object with the following properties:
* @param {ArrayBuffer|Uint8Array} options.gltf A binary glTF buffer.
* @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
* @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
* @param {ClassificationType} [options.classificationType] What this model will classify.
*
* @exception {RuntimeError} Only binary glTF is supported.
* @exception {RuntimeError} Buffer data must be embedded in the binary glTF.
* @exception {RuntimeError} Only one node is supported for classification and it must have a mesh.
* @exception {RuntimeError} Only one mesh is supported when using b3dm for classification.
* @exception {RuntimeError} Only one primitive per mesh is supported when using b3dm for classification.
* @exception {RuntimeError} The mesh must have a position attribute.
* @exception {RuntimeError} The mesh must have a batch id attribute.
*/
function ClassificationModel(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
var gltf = options.gltf;
if (gltf instanceof ArrayBuffer) {
gltf = new Uint8Array(gltf);
}
if (gltf instanceof Uint8Array) {
// Parse and update binary glTF
gltf = parseGlb(gltf);
updateVersion(gltf);
addDefaults(gltf);
processModelMaterialsCommon(gltf);
processPbrMaterials(gltf);
} else {
throw new RuntimeError('Only binary glTF is supported as a classifier.');
}
ForEach.buffer(gltf, function(buffer) {
if (!defined(buffer.extras._pipeline.source)) {
throw new RuntimeError('Buffer data must be embedded in the binary gltf.');
}
});
var gltfNodes = gltf.nodes;
var gltfMeshes = gltf.meshes;
var gltfNode = gltfNodes[0];
var meshId = gltfNode.mesh;
if (gltfNodes.length !== 1 || !defined(meshId)) {
throw new RuntimeError('Only one node is supported for classification and it must have a mesh.');
}
if (gltfMeshes.length !== 1) {
throw new RuntimeError('Only one mesh is supported when using b3dm for classification.');
}
var gltfPrimitives = gltfMeshes[0].primitives;
if (gltfPrimitives.length !== 1) {
throw new RuntimeError('Only one primitive per mesh is supported when using b3dm for classification.');
}
var gltfPositionAttribute = gltfPrimitives[0].attributes.POSITION;
if (!defined(gltfPositionAttribute)) {
throw new RuntimeError('The mesh must have a position attribute.');
}
var gltfBatchIdAttribute = gltfPrimitives[0].attributes._BATCHID;
if (!defined(gltfBatchIdAttribute)) {
throw new RuntimeError('The mesh must have a batch id attribute.');
}
this._gltf = gltf;
/**
* Determines if the model primitive will be shown.
*
* @type {Boolean}
*
* @default true
*/
this.show = defaultValue(options.show, true);
/**
* The 4x4 transformation matrix that transforms the model from model to world coordinates.
* When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
* Local reference frames can be used by providing a different transformation matrix, like that returned
* by {@link Transforms.eastNorthUpToFixedFrame}.
*
* @type {Matrix4}
*
* @default {@link Matrix4.IDENTITY}
*
* @example
* var origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
* m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
*/
this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY));
this._modelMatrix = Matrix4.clone(this.modelMatrix);
this._ready = false;
this._readyPromise = when.defer();
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the bounding sphere for each draw command in the model. A glTF primitive corresponds
* to one draw command. A glTF mesh has an array of primitives, often of length one.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
this._debugShowBoundingVolume = false;
/**
* This property is for debugging only; it is not for production use nor is it optimized.
*
* Draws the model in wireframe.
*
*
* @type {Boolean}
*
* @default false
*/
this.debugWireframe = defaultValue(options.debugWireframe, false);
this._debugWireframe = false;
this._classificationType = options.classificationType;
// Undocumented options
this._vertexShaderLoaded = options.vertexShaderLoaded;
this._classificationShaderLoaded = options.classificationShaderLoaded;
this._uniformMapLoaded = options.uniformMapLoaded;
this._pickIdLoaded = options.pickIdLoaded;
this._ignoreCommands = defaultValue(options.ignoreCommands, false);
this._upAxis = defaultValue(options.upAxis, Axis.Y);
this._batchTable = options.batchTable;
this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and axis
this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins
this._boundingSphere = undefined;
this._scaledBoundingSphere = new BoundingSphere();
this._state = ModelState.NEEDS_LOAD;
this._loadResources = undefined;
this._mode = undefined;
this._dirty = false; // true when the model was transformed this frame
this._nodeMatrix = new Matrix4();
this._primitive = undefined;
this._extensionsUsed = undefined; // Cached used glTF extensions
this._extensionsRequired = undefined; // Cached required glTF extensions
this._quantizedUniforms = undefined; // Quantized uniforms for WEB3D_quantized_attributes
this._buffers = {};
this._vertexArray = undefined;
this._shaderProgram = undefined;
this._uniformMap = undefined;
this._geometryByteLength = 0;
this._trianglesLength = 0;
// CESIUM_RTC extension
this._rtcCenter = undefined; // reference to either 3D or 2D
this._rtcCenterEye = undefined; // in eye coordinates
this._rtcCenter3D = undefined; // in world coordinates
this._rtcCenter2D = undefined; // in projected world coordinates
}
defineProperties(ClassificationModel.prototype, {
/**
* The object for the glTF JSON, including properties with default values omitted
* from the JSON provided to this model.
*
* @memberof ClassificationModel.prototype
*
* @type {Object}
* @readonly
*
* @default undefined
*/
gltf : {
get : function() {
return this._gltf;
}
},
/**
* The model's bounding sphere in its local coordinate system.
*
* @memberof ClassificationModel.prototype
*
* @type {BoundingSphere}
* @readonly
*
* @default undefined
*
* @exception {DeveloperError} The model is not loaded. Use ClassificationModel.readyPromise or wait for ClassificationModel.ready to be true.
*
* @example
* // Center in WGS84 coordinates
* var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
*/
boundingSphere : {
get : function() {
//>>includeStart('debug', pragmas.debug);
if (this._state !== ModelState.LOADED) {
throw new DeveloperError('The model is not loaded. Use ClassificationModel.readyPromise or wait for ClassificationModel.ready to be true.');
}
//>>includeEnd('debug');
var modelMatrix = this.modelMatrix;
var nonUniformScale = Matrix4.getScale(modelMatrix, boundingSphereCartesian3Scratch);
var scaledBoundingSphere = this._scaledBoundingSphere;
scaledBoundingSphere.center = Cartesian3.multiplyComponents(this._boundingSphere.center, nonUniformScale, scaledBoundingSphere.center);
scaledBoundingSphere.radius = Cartesian3.maximumComponent(nonUniformScale) * this._initialRadius;
if (defined(this._rtcCenter)) {
Cartesian3.add(this._rtcCenter, scaledBoundingSphere.center, scaledBoundingSphere.center);
}
return scaledBoundingSphere;
}
},
/**
* When true
, this model is ready to render, i.e., the external binary, image,
* and shader files were downloaded and the WebGL resources were created. This is set to
* true
right before {@link ClassificationModel#readyPromise} is resolved.
*
* @memberof ClassificationModel.prototype
*
* @type {Boolean}
* @readonly
*
* @default false
*/
ready : {
get : function() {
return this._ready;
}
},
/**
* Gets the promise that will be resolved when this model is ready to render, i.e., when the external binary, image,
* and shader files were downloaded and the WebGL resources were created.
*
* This promise is resolved at the end of the frame before the first frame the model is rendered in.
*
*
* @memberof ClassificationModel.prototype
* @type {Promise.}
* @readonly
*
* @see ClassificationModel#ready
*/
readyPromise : {
get : function() {
return this._readyPromise.promise;
}
},
/**
* Returns true if the model was transformed this frame
*
* @memberof ClassificationModel.prototype
*
* @type {Boolean}
* @readonly
*
* @private
*/
dirty : {
get : function() {
return this._dirty;
}
},
/**
* Returns an object with all of the glTF extensions used.
*
* @memberof ClassificationModel.prototype
*
* @type {Object}
* @readonly
*/
extensionsUsed : {
get : function() {
if (!defined(this._extensionsUsed)) {
this._extensionsUsed = ModelUtility.getUsedExtensions(this.gltf);
}
return this._extensionsUsed;
}
},
/**
* Returns an object with all of the glTF extensions required.
*
* @memberof ClassificationModel.prototype
*
* @type {Object}
* @readonly
*/
extensionsRequired : {
get : function() {
if (!defined(this._extensionsRequired)) {
this._extensionsRequired = ModelUtility.getRequiredExtensions(this.gltf);
}
return this._extensionsRequired;
}
},
/**
* Gets the model's up-axis.
* By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up.
*
* @memberof ClassificationModel.prototype
*
* @type {Number}
* @default Axis.Y
* @readonly
*
* @private
*/
upAxis : {
get : function() {
return this._upAxis;
}
},
/**
* Gets the model's triangle count.
*
* @private
*/
trianglesLength : {
get : function() {
return this._trianglesLength;
}
},
/**
* Gets the model's geometry memory in bytes. This includes all vertex and index buffers.
*
* @private
*/
geometryByteLength : {
get : function() {
return this._geometryByteLength;
}
},
/**
* Gets the model's texture memory in bytes.
*
* @private
*/
texturesByteLength : {
get : function() {
return 0;
}
},
/**
* Gets the model's classification type.
* @memberof ClassificationModel.prototype
* @type {ClassificationType}
*/
classificationType : {
get : function() {
return this._classificationType;
}
}
});
///////////////////////////////////////////////////////////////////////////
function addBuffersToLoadResources(model) {
var gltf = model.gltf;
var loadResources = model._loadResources;
ForEach.buffer(gltf, function(buffer, id) {
loadResources.buffers[id] = buffer.extras._pipeline.source;
});
}
function parseBufferViews(model) {
var bufferViews = model.gltf.bufferViews;
var vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate;
// Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below.
ForEach.bufferView(model.gltf, function(bufferView, id) {
if (bufferView.target === WebGLConstants.ARRAY_BUFFER) {
vertexBuffersToCreate.enqueue(id);
}
});
var indexBuffersToCreate = model._loadResources.indexBuffersToCreate;
var indexBufferIds = {};
// The Cesium Renderer requires knowing the datatype for an index buffer
// at creation type, which is not part of the glTF bufferview so loop
// through glTF accessors to create the bufferview's index buffer.
ForEach.accessor(model.gltf, function(accessor) {
var bufferViewId = accessor.bufferView;
var bufferView = bufferViews[bufferViewId];
if ((bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER) && !defined(indexBufferIds[bufferViewId])) {
indexBufferIds[bufferViewId] = true;
indexBuffersToCreate.enqueue({
id : bufferViewId,
componentType : accessor.componentType
});
}
});
}
function createVertexBuffer(bufferViewId, model) {
var loadResources = model._loadResources;
var bufferViews = model.gltf.bufferViews;
var bufferView = bufferViews[bufferViewId];
var vertexBuffer = loadResources.getBuffer(bufferView);
model._buffers[bufferViewId] = vertexBuffer;
model._geometryByteLength += vertexBuffer.byteLength;
}
function createIndexBuffer(bufferViewId, componentType, model) {
var loadResources = model._loadResources;
var bufferViews = model.gltf.bufferViews;
var bufferView = bufferViews[bufferViewId];
var indexBuffer = {
typedArray : loadResources.getBuffer(bufferView),
indexDatatype : componentType
};
model._buffers[bufferViewId] = indexBuffer;
model._geometryByteLength += indexBuffer.typedArray.byteLength;
}
function createBuffers(model) {
var loadResources = model._loadResources;
if (loadResources.pendingBufferLoads !== 0) {
return;
}
var vertexBuffersToCreate = loadResources.vertexBuffersToCreate;
var indexBuffersToCreate = loadResources.indexBuffersToCreate;
while (vertexBuffersToCreate.length > 0) {
createVertexBuffer(vertexBuffersToCreate.dequeue(), model);
}
while (indexBuffersToCreate.length > 0) {
var i = indexBuffersToCreate.dequeue();
createIndexBuffer(i.id, i.componentType, model);
}
}
function modifyShaderForQuantizedAttributes(shader, model) {
var primitive = model.gltf.meshes[0].primitives[0];
var result = ModelUtility.modifyShaderForQuantizedAttributes(model.gltf, primitive, shader);
model._quantizedUniforms = result.uniforms;
return result.shader;
}
function modifyShader(shader, callback) {
if (defined(callback)) {
shader = callback(shader);
}
return shader;
}
function createProgram(model) {
var gltf = model.gltf;
var positionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'POSITION');
var batchIdName = ModelUtility.getAttributeOrUniformBySemantic(gltf, '_BATCHID');
var attributeLocations = {};
attributeLocations[positionName] = 0;
attributeLocations[batchIdName] = 1;
var modelViewProjectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEWPROJECTION');
var uniformDecl;
var toClip;
if (!defined(modelViewProjectionName)) {
var projectionName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'PROJECTION');
var modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'MODELVIEW');
if (!defined(modelViewName)) {
modelViewName = ModelUtility.getAttributeOrUniformBySemantic(gltf, 'CESIUM_RTC_MODELVIEW');
}
uniformDecl =
'uniform mat4 ' + modelViewName + ';\n' +
'uniform mat4 ' + projectionName + ';\n';
toClip = projectionName + ' * ' + modelViewName + ' * vec4(' + positionName + ', 1.0)';
} else {
uniformDecl = 'uniform mat4 ' + modelViewProjectionName + ';\n';
toClip = modelViewProjectionName + ' * vec4(' + positionName + ', 1.0)';
}
var computePosition = ' vec4 positionInClipCoords = ' + toClip + ';\n';
var vs =
'attribute vec3 ' + positionName + ';\n' +
'attribute float ' + batchIdName + ';\n' +
uniformDecl +
'void main() {\n' +
computePosition +
' gl_Position = czm_depthClampFarPlane(positionInClipCoords);\n' +
'}\n';
var fs =
'#ifdef GL_EXT_frag_depth\n' +
'#extension GL_EXT_frag_depth : enable\n' +
'#endif\n' +
'void main() \n' +
'{ \n' +
' gl_FragColor = vec4(1.0); \n' +
' czm_writeDepthClampedToFarPlane();\n' +
'}\n';
if (model.extensionsUsed.WEB3D_quantized_attributes) {
vs = modifyShaderForQuantizedAttributes(vs, model);
}
var drawVS = modifyShader(vs, model._vertexShaderLoaded);
var drawFS = modifyShader(fs, model._classificationShaderLoaded);
drawVS = ModelUtility.modifyVertexShaderForLogDepth(drawVS, toClip);
drawFS = ModelUtility.modifyFragmentShaderForLogDepth(drawFS);
model._shaderProgram = {
vertexShaderSource : drawVS,
fragmentShaderSource : drawFS,
attributeLocations : attributeLocations
};
}
function getAttributeLocations() {
return {
POSITION : 0,
_BATCHID : 1
};
}
function createVertexArray(model) {
var loadResources = model._loadResources;
if (!loadResources.finishedBuffersCreation() || defined(model._vertexArray)) {
return;
}
var rendererBuffers = model._buffers;
var gltf = model.gltf;
var accessors = gltf.accessors;
var meshes = gltf.meshes;
var primitives = meshes[0].primitives;
var primitive = primitives[0];
var attributeLocations = getAttributeLocations();
var attributes = {};
ForEach.meshPrimitiveAttribute(primitive, function(accessorId, attributeName) {
// Skip if the attribute is not used by the material, e.g., because the asset
// was exported with an attribute that wasn't used and the asset wasn't optimized.
var attributeLocation = attributeLocations[attributeName];
if (defined(attributeLocation)) {
var a = accessors[accessorId];
attributes[attributeName] = {
index: attributeLocation,
vertexBuffer: rendererBuffers[a.bufferView],
componentsPerAttribute: numberOfComponentsForType(a.type),
componentDatatype: a.componentType,
offsetInBytes: a.byteOffset,
strideInBytes: getAccessorByteStride(gltf, a)
};
}
});
var indexBuffer;
if (defined(primitive.indices)) {
var accessor = accessors[primitive.indices];
indexBuffer = rendererBuffers[accessor.bufferView];
}
model._vertexArray = {
attributes : attributes,
indexBuffer : indexBuffer
};
}
var gltfSemanticUniforms = {
PROJECTION : function(uniformState, model) {
return ModelUtility.getGltfSemanticUniforms().PROJECTION(uniformState, model);
},
MODELVIEW : function(uniformState, model) {
return ModelUtility.getGltfSemanticUniforms().MODELVIEW(uniformState, model);
},
CESIUM_RTC_MODELVIEW : function(uniformState, model) {
return ModelUtility.getGltfSemanticUniforms().CESIUM_RTC_MODELVIEW(uniformState, model);
},
MODELVIEWPROJECTION : function(uniformState, model) {
return ModelUtility.getGltfSemanticUniforms().MODELVIEWPROJECTION(uniformState, model);
}
};
function createUniformMap(model, context) {
if (defined(model._uniformMap)) {
return;
}
var uniformMap = {};
ForEach.technique(model.gltf, function(technique) {
ForEach.techniqueUniform(technique, function(uniform, uniformName) {
if (!defined(uniform.semantic) || !defined(gltfSemanticUniforms[uniform.semantic])) {
return;
}
uniformMap[uniformName] = gltfSemanticUniforms[uniform.semantic](context.uniformState, model);
});
});
model._uniformMap = uniformMap;
}
function createUniformsForQuantizedAttributes(model, primitive) {
return ModelUtility.createUniformsForQuantizedAttributes(model.gltf, primitive, model._quantizedUniforms);
}
function triangleCountFromPrimitiveIndices(primitive, indicesCount) {
switch (primitive.mode) {
case PrimitiveType.TRIANGLES:
return (indicesCount / 3);
case PrimitiveType.TRIANGLE_STRIP:
case PrimitiveType.TRIANGLE_FAN:
return Math.max(indicesCount - 2, 0);
default:
return 0;
}
}
function createPrimitive(model) {
var batchTable = model._batchTable;
var uniformMap = model._uniformMap;
var vertexArray = model._vertexArray;
var gltf = model.gltf;
var accessors = gltf.accessors;
var gltfMeshes = gltf.meshes;
var primitive = gltfMeshes[0].primitives[0];
var ix = accessors[primitive.indices];
var positionAccessor = primitive.attributes.POSITION;
var minMax = ModelUtility.getAccessorMinMax(gltf, positionAccessor);
var boundingSphere = BoundingSphere.fromCornerPoints(Cartesian3.fromArray(minMax.min), Cartesian3.fromArray(minMax.max));
var offset;
var count;
if (defined(ix)) {
count = ix.count;
offset = (ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType)); // glTF has offset in bytes. Cesium has offsets in indices
}
else {
var positions = accessors[primitive.attributes.POSITION];
count = positions.count;
offset = 0;
}
// Update model triangle count using number of indices
model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count);
// Allow callback to modify the uniformMap
if (defined(model._uniformMapLoaded)) {
uniformMap = model._uniformMapLoaded(uniformMap);
}
// Add uniforms for decoding quantized attributes if used
if (model.extensionsUsed.WEB3D_quantized_attributes) {
var quantizedUniformMap = createUniformsForQuantizedAttributes(model, primitive);
uniformMap = combine(uniformMap, quantizedUniformMap);
}
var attribute = vertexArray.attributes.POSITION;
var componentDatatype = attribute.componentDatatype;
var typedArray = attribute.vertexBuffer;
var byteOffset = typedArray.byteOffset;
var bufferLength = typedArray.byteLength / ComponentDatatype.getSizeInBytes(componentDatatype);
var positionsBuffer = ComponentDatatype.createArrayBufferView(componentDatatype, typedArray.buffer, byteOffset, bufferLength);
attribute = vertexArray.attributes._BATCHID;
componentDatatype = attribute.componentDatatype;
typedArray = attribute.vertexBuffer;
byteOffset = typedArray.byteOffset;
bufferLength = typedArray.byteLength / ComponentDatatype.getSizeInBytes(componentDatatype);
var vertexBatchIds = ComponentDatatype.createArrayBufferView(componentDatatype, typedArray.buffer, byteOffset, bufferLength);
var buffer = vertexArray.indexBuffer.typedArray;
var indices;
if (vertexArray.indexBuffer.indexDatatype === IndexDatatype.UNSIGNED_SHORT) {
indices = new Uint16Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Uint16Array.BYTES_PER_ELEMENT);
} else {
indices = new Uint32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Uint32Array.BYTES_PER_ELEMENT);
}
positionsBuffer = arraySlice(positionsBuffer);
vertexBatchIds = arraySlice(vertexBatchIds);
indices = arraySlice(indices, offset, offset + count);
var batchIds = [];
var indexCounts = [];
var indexOffsets = [];
var batchedIndices = [];
var currentId = vertexBatchIds[indices[0]];
batchIds.push(currentId);
indexOffsets.push(0);
var batchId;
var indexOffset;
var indexCount;
var indicesLength = indices.length;
for (var j = 1; j < indicesLength; ++j) {
batchId = vertexBatchIds[indices[j]];
if (batchId !== currentId) {
indexOffset = indexOffsets[indexOffsets.length - 1];
indexCount = j - indexOffset;
batchIds.push(batchId);
indexCounts.push(indexCount);
indexOffsets.push(j);
batchedIndices.push(new Vector3DTileBatch({
offset : indexOffset,
count : indexCount,
batchIds : [currentId],
color : Color.WHITE
}));
currentId = batchId;
}
}
indexOffset = indexOffsets[indexOffsets.length - 1];
indexCount = indicesLength - indexOffset;
indexCounts.push(indexCount);
batchedIndices.push(new Vector3DTileBatch({
offset : indexOffset,
count : indexCount,
batchIds : [currentId],
color : Color.WHITE
}));
var shader = model._shaderProgram;
var vertexShaderSource = shader.vertexShaderSource;
var fragmentShaderSource = shader.fragmentShaderSource;
var attributeLocations = shader.attributeLocations;
var pickId = defined(model._pickIdLoaded) ? model._pickIdLoaded() : undefined;
model._primitive = new Vector3DTilePrimitive({
classificationType : model._classificationType,
positions : positionsBuffer,
indices : indices,
indexOffsets : indexOffsets,
indexCounts : indexCounts,
batchIds : batchIds,
vertexBatchIds : vertexBatchIds,
batchedIndices : batchedIndices,
batchTable : batchTable,
boundingVolume : new BoundingSphere(), // updated in update()
_vertexShaderSource : vertexShaderSource,
_fragmentShaderSource : fragmentShaderSource,
_attributeLocations : attributeLocations,
_uniformMap : uniformMap,
_pickId : pickId,
_modelMatrix : new Matrix4(), // updated in update()
_boundingSphere : boundingSphere // used to update boundingVolume
});
// Release CPU resources
model._buffers = undefined;
model._vertexArray = undefined;
model._shaderProgram = undefined;
model._uniformMap = undefined;
}
function createRuntimeNodes(model) {
var loadResources = model._loadResources;
if (!loadResources.finished()) {
return;
}
if (defined(model._primitive)) {
return;
}
var gltf = model.gltf;
var nodes = gltf.nodes;
var gltfNode = nodes[0];
model._nodeMatrix = ModelUtility.getTransform(gltfNode, model._nodeMatrix);
createPrimitive(model);
}
function createResources(model, frameState) {
var context = frameState.context;
ModelUtility.checkSupportedGlExtensions(model.gltf.glExtensionsUsed, context);
createBuffers(model); // using glTF bufferViews
createProgram(model);
createVertexArray(model); // using glTF meshes
createUniformMap(model, context); // using glTF materials/techniques
createRuntimeNodes(model); // using glTF scene
}
///////////////////////////////////////////////////////////////////////////
var scratchComputedTranslation = new Cartesian4();
var scratchComputedMatrixIn2D = new Matrix4();
function updateNodeModelMatrix(model, modelTransformChanged, justLoaded, projection) {
var computedModelMatrix = model._computedModelMatrix;
if ((model._mode !== SceneMode.SCENE3D) && !model._ignoreCommands) {
var translation = Matrix4.getColumn(computedModelMatrix, 3, scratchComputedTranslation);
if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) {
computedModelMatrix = Transforms.basisTo2D(projection, computedModelMatrix, scratchComputedMatrixIn2D);
model._rtcCenter = model._rtcCenter3D;
} else {
var center = model.boundingSphere.center;
var to2D = Transforms.wgs84To2DModelMatrix(projection, center, scratchComputedMatrixIn2D);
computedModelMatrix = Matrix4.multiply(to2D, computedModelMatrix, scratchComputedMatrixIn2D);
if (defined(model._rtcCenter)) {
Matrix4.setTranslation(computedModelMatrix, Cartesian4.UNIT_W, computedModelMatrix);
model._rtcCenter = model._rtcCenter2D;
}
}
}
var primitive = model._primitive;
if (modelTransformChanged || justLoaded) {
Matrix4.multiplyTransformation(computedModelMatrix, model._nodeMatrix, primitive._modelMatrix);
BoundingSphere.transform(primitive._boundingSphere, primitive._modelMatrix, primitive._boundingVolume);
if (defined(model._rtcCenter)) {
Cartesian3.add(model._rtcCenter, primitive._boundingVolume.center, primitive._boundingVolume.center);
}
}
}
///////////////////////////////////////////////////////////////////////////
ClassificationModel.prototype.updateCommands = function(batchId, color) {
this._primitive.updateCommands(batchId, color);
};
ClassificationModel.prototype.update = function(frameState) {
if (frameState.mode === SceneMode.MORPHING) {
return;
}
if (!FeatureDetection.supportsWebP.initialized) {
FeatureDetection.supportsWebP.initialize();
return;
}
var supportsWebP = FeatureDetection.supportsWebP();
if ((this._state === ModelState.NEEDS_LOAD) && defined(this.gltf)) {
this._state = ModelState.LOADING;
if (this._state !== ModelState.FAILED) {
var extensions = this.gltf.extensions;
if (defined(extensions) && defined(extensions.CESIUM_RTC)) {
var center = Cartesian3.fromArray(extensions.CESIUM_RTC.center);
if (!Cartesian3.equals(center, Cartesian3.ZERO)) {
this._rtcCenter3D = center;
var projection = frameState.mapProjection;
var ellipsoid = projection.ellipsoid;
var cartographic = ellipsoid.cartesianToCartographic(this._rtcCenter3D);
var projectedCart = projection.project(cartographic);
Cartesian3.fromElements(projectedCart.z, projectedCart.x, projectedCart.y, projectedCart);
this._rtcCenter2D = projectedCart;
this._rtcCenterEye = new Cartesian3();
this._rtcCenter = this._rtcCenter3D;
}
}
this._loadResources = new ModelLoadResources();
ModelUtility.parseBuffers(this);
}
}
var loadResources = this._loadResources;
var justLoaded = false;
if (this._state === ModelState.LOADING) {
// Transition from LOADING -> LOADED once resources are downloaded and created.
// Textures may continue to stream in while in the LOADED state.
if (loadResources.pendingBufferLoads === 0) {
ModelUtility.checkSupportedExtensions(this.extensionsRequired, supportsWebP);
addBuffersToLoadResources(this);
parseBufferViews(this);
this._boundingSphere = ModelUtility.computeBoundingSphere(this);
this._initialRadius = this._boundingSphere.radius;
createResources(this, frameState);
}
if (loadResources.finished()) {
this._state = ModelState.LOADED;
justLoaded = true;
}
}
if (defined(loadResources) && (this._state === ModelState.LOADED)) {
if (!justLoaded) {
createResources(this, frameState);
}
if (loadResources.finished()) {
this._loadResources = undefined; // Clear CPU memory since WebGL resources were created.
}
}
var show = this.show;
if ((show && this._state === ModelState.LOADED) || justLoaded) {
this._dirty = false;
var modelMatrix = this.modelMatrix;
var modeChanged = frameState.mode !== this._mode;
this._mode = frameState.mode;
// ClassificationModel's model matrix needs to be updated
var modelTransformChanged = !Matrix4.equals(this._modelMatrix, modelMatrix) || modeChanged;
if (modelTransformChanged || justLoaded) {
Matrix4.clone(modelMatrix, this._modelMatrix);
var computedModelMatrix = this._computedModelMatrix;
Matrix4.clone(modelMatrix, computedModelMatrix);
if (this._upAxis === Axis.Y) {
Matrix4.multiplyTransformation(computedModelMatrix, Axis.Y_UP_TO_Z_UP, computedModelMatrix);
} else if (this._upAxis === Axis.X) {
Matrix4.multiplyTransformation(computedModelMatrix, Axis.X_UP_TO_Z_UP, computedModelMatrix);
}
}
// Update modelMatrix throughout the graph as needed
if (modelTransformChanged || justLoaded) {
updateNodeModelMatrix(this, modelTransformChanged, justLoaded, frameState.mapProjection);
this._dirty = true;
}
}
if (justLoaded) {
// Called after modelMatrix update.
var model = this;
frameState.afterRender.push(function() {
model._ready = true;
model._readyPromise.resolve(model);
});
return;
}
if (show && !this._ignoreCommands) {
this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
this._primitive.debugWireframe = this.debugWireframe;
this._primitive.update(frameState);
}
};
ClassificationModel.prototype.isDestroyed = function() {
return false;
};
ClassificationModel.prototype.destroy = function() {
this._primitive = this._primitive && this._primitive.destroy();
return destroyObject(this);
};
export default ClassificationModel;