1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195 |
- import BoundingSphere from "../Core/BoundingSphere.js";
- import Cartesian3 from "../Core/Cartesian3.js";
- import clone from "../Core/clone.js";
- import Color from "../Core/Color.js";
- import ComponentDatatype from "../Core/ComponentDatatype.js";
- import defaultValue from "../Core/defaultValue.js";
- import defer from "../Core/defer.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import ImageBasedLighting from "./ImageBasedLighting.js";
- import Matrix4 from "../Core/Matrix4.js";
- import PrimitiveType from "../Core/PrimitiveType.js";
- import Resource from "../Core/Resource.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import Transforms from "../Core/Transforms.js";
- import Buffer from "../Renderer/Buffer.js";
- import BufferUsage from "../Renderer/BufferUsage.js";
- import DrawCommand from "../Renderer/DrawCommand.js";
- import Pass from "../Renderer/Pass.js";
- import RenderState from "../Renderer/RenderState.js";
- import ShaderSource from "../Renderer/ShaderSource.js";
- import ForEach from "./GltfPipeline/ForEach.js";
- import Model from "./Model.js";
- import ModelInstance from "./ModelInstance.js";
- import ModelUtility from "./ModelUtility.js";
- import SceneMode from "./SceneMode.js";
- import ShadowMode from "./ShadowMode.js";
- import SplitDirection from "./SplitDirection.js";
- const LoadState = {
- NEEDS_LOAD: 0,
- LOADING: 1,
- LOADED: 2,
- FAILED: 3,
- };
- /**
- * A 3D model instance collection. All instances reference the same underlying model, but have unique
- * per-instance properties like model matrix, pick id, etc.
- *
- * Instances are rendered relative-to-center and for best results instances should be positioned close to one another.
- * Otherwise there may be precision issues if, for example, instances are placed on opposite sides of the globe.
- *
- * @alias ModelInstanceCollection
- * @constructor
- *
- * @param {Object} options Object with the following properties:
- * @param {Object[]} [options.instances] An array of instances, where each instance contains a modelMatrix and optional batchId when options.batchTable is defined.
- * @param {Cesium3DTileBatchTable} [options.batchTable] The batch table of the instanced 3D Tile.
- * @param {Resource|String} [options.url] The url to the .gltf file.
- * @param {Object} [options.requestType] The request type, used for request prioritization
- * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] A glTF JSON object, or a binary glTF buffer.
- * @param {Resource|String} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
- * @param {Boolean} [options.dynamic=false] Hint if instance model matrices will be updated frequently.
- * @param {Boolean} [options.show=true] Determines if the collection will be shown.
- * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each instance is pickable with {@link Scene#pick}.
- * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
- * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
- * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the collection casts or receives shadows from light sources.
- * @param {Cartesian3} [options.lightColor] The light color when shading models. When <code>undefined</code> the scene's light color is used instead.
- * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting for this tileset.
- * @param {Cartesian2} [options.imageBasedLightingFactor=new Cartesian2(1.0, 1.0)] Scales diffuse and specular image-based lighting from the earth, sky, atmosphere and star skybox. Deprecated in Cesium 1.92, will be removed in Cesium 1.94.
- * @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map. Deprecated in Cesium 1.92, will be removed in Cesium 1.94.
- * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting. Deprecated in Cesium 1.92, will be removed in Cesium 1.94.
- * @param {String} [options.specularEnvironmentMaps] A URL to a KTX2 file that contains a cube map of the specular lighting and the convoluted specular mipmaps. Deprecated in Cesium 1.92, will be removed in Cesium 1.94.
- * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled.
- * @param {Boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen.
- * @param {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this collection.
- * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection.
- * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe.
- * @exception {DeveloperError} Must specify either <options.gltf> or <options.url>, but not both.
- * @exception {DeveloperError} Shader program cannot be optimized for instancing. Parameters cannot have any of the following semantics: MODEL, MODELINVERSE, MODELVIEWINVERSE, MODELVIEWPROJECTIONINVERSE, MODELINVERSETRANSPOSE.
- *
- * @private
- */
- function ModelInstanceCollection(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- //>>includeStart('debug', pragmas.debug);
- if (!defined(options.gltf) && !defined(options.url)) {
- throw new DeveloperError("Either options.gltf or options.url is required.");
- }
- if (defined(options.gltf) && defined(options.url)) {
- throw new DeveloperError(
- "Cannot pass in both options.gltf and options.url."
- );
- }
- //>>includeEnd('debug');
- this.show = defaultValue(options.show, true);
- this._instancingSupported = false;
- this._dynamic = defaultValue(options.dynamic, false);
- this._allowPicking = defaultValue(options.allowPicking, true);
- this._ready = false;
- this._readyPromise = defer();
- this._state = LoadState.NEEDS_LOAD;
- this._dirty = false;
- // Undocumented options
- this._cull = defaultValue(options.cull, true);
- this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE);
- this._instances = createInstances(this, options.instances);
- // When the model instance collection is backed by an i3dm tile,
- // use its batch table resources to modify the shaders, attributes, and uniform maps.
- this._batchTable = options.batchTable;
- this._model = undefined;
- this._vertexBufferTypedArray = undefined; // Hold onto the vertex buffer contents when dynamic is true
- this._vertexBuffer = undefined;
- this._batchIdBuffer = undefined;
- this._instancedUniformsByProgram = undefined;
- this._drawCommands = [];
- this._modelCommands = undefined;
- this._renderStates = undefined;
- this._disableCullingRenderStates = undefined;
- this._boundingSphere = createBoundingSphere(this);
- this._center = Cartesian3.clone(this._boundingSphere.center);
- this._rtcTransform = new Matrix4();
- this._rtcModelView = new Matrix4(); // Holds onto uniform
- this._mode = undefined;
- this.modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
- this._modelMatrix = Matrix4.clone(this.modelMatrix);
- // Passed on to Model
- this._url = Resource.createIfNeeded(options.url);
- this._requestType = options.requestType;
- this._gltf = options.gltf;
- this._basePath = Resource.createIfNeeded(options.basePath);
- this._asynchronous = options.asynchronous;
- this._incrementallyLoadTextures = options.incrementallyLoadTextures;
- this._upAxis = options.upAxis; // Undocumented option
- this._forwardAxis = options.forwardAxis; // Undocumented option
- this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
- this._shadows = this.shadows;
- this._pickIdLoaded = options.pickIdLoaded;
- /**
- * The {@link SplitDirection} to apply to this collection.
- *
- * @type {SplitDirection}
- * @default {@link SplitDirection.NONE}
- */
- this.splitDirection = defaultValue(
- options.splitDirection,
- SplitDirection.NONE
- );
- this.debugShowBoundingVolume = defaultValue(
- options.debugShowBoundingVolume,
- false
- );
- this._debugShowBoundingVolume = false;
- this.debugWireframe = defaultValue(options.debugWireframe, false);
- this._debugWireframe = false;
- if (defined(options.imageBasedLighting)) {
- this._imageBasedLighting = options.imageBasedLighting;
- this._shouldDestroyImageBasedLighting = false;
- } else {
- // Create image-based lighting from the old constructor parameters.
- this._imageBasedLighting = new ImageBasedLighting({
- imageBasedLightingFactor: options.imageBasedLightingFactor,
- luminanceAtZenith: options.luminanceAtZenith,
- sphericalHarmonicCoefficients: options.sphericalHarmonicCoefficients,
- specularEnvironmentMaps: options.specularEnvironmentMaps,
- });
- this._shouldDestroyImageBasedLighting = true;
- }
- this.backFaceCulling = defaultValue(options.backFaceCulling, true);
- this._backFaceCulling = this.backFaceCulling;
- this.showCreditsOnScreen = defaultValue(options.showCreditsOnScreen, false);
- }
- Object.defineProperties(ModelInstanceCollection.prototype, {
- allowPicking: {
- get: function () {
- return this._allowPicking;
- },
- },
- length: {
- get: function () {
- return this._instances.length;
- },
- },
- activeAnimations: {
- get: function () {
- return this._model.activeAnimations;
- },
- },
- ready: {
- get: function () {
- return this._ready;
- },
- },
- readyPromise: {
- get: function () {
- return this._readyPromise.promise;
- },
- },
- imageBasedLighting: {
- get: function () {
- return this._imageBasedLighting;
- },
- set: function (value) {
- if (value !== this._imageBasedLighting) {
- if (
- this._shouldDestroyImageBasedLighting &&
- !this._imageBasedLighting.isDestroyed()
- ) {
- this._imageBasedLighting.destroy();
- }
- this._imageBasedLighting = value;
- this._shouldDestroyImageBasedLighting = false;
- }
- },
- },
- imageBasedLightingFactor: {
- get: function () {
- return this._imageBasedLighting.imageBasedLightingFactor;
- },
- set: function (value) {
- this._imageBasedLighting.imageBasedLightingFactor = value;
- },
- },
- luminanceAtZenith: {
- get: function () {
- return this._imageBasedLighting.luminanceAtZenith;
- },
- set: function (value) {
- this._imageBasedLighting.luminanceAtZenith = value;
- },
- },
- sphericalHarmonicCoefficients: {
- get: function () {
- return this._imageBasedLighting.sphericalHarmonicCoefficients;
- },
- set: function (value) {
- this._imageBasedLighting.sphericalHarmonicCoefficients = value;
- },
- },
- specularEnvironmentMaps: {
- get: function () {
- return this._imageBasedLighting.specularEnvironmentMaps;
- },
- set: function (value) {
- this._imageBasedLighting.specularEnvironmentMaps = value;
- },
- },
- });
- function createInstances(collection, instancesOptions) {
- instancesOptions = defaultValue(instancesOptions, []);
- const length = instancesOptions.length;
- const instances = new Array(length);
- for (let i = 0; i < length; ++i) {
- const instanceOptions = instancesOptions[i];
- const modelMatrix = instanceOptions.modelMatrix;
- const instanceId = defaultValue(instanceOptions.batchId, i);
- instances[i] = new ModelInstance(collection, modelMatrix, instanceId);
- }
- return instances;
- }
- function createBoundingSphere(collection) {
- const instancesLength = collection.length;
- const points = new Array(instancesLength);
- for (let i = 0; i < instancesLength; ++i) {
- points[i] = Matrix4.getTranslation(
- collection._instances[i]._modelMatrix,
- new Cartesian3()
- );
- }
- return BoundingSphere.fromPoints(points);
- }
- const scratchCartesian = new Cartesian3();
- const scratchMatrix = new Matrix4();
- ModelInstanceCollection.prototype.expandBoundingSphere = function (
- instanceModelMatrix
- ) {
- const translation = Matrix4.getTranslation(
- instanceModelMatrix,
- scratchCartesian
- );
- BoundingSphere.expand(
- this._boundingSphere,
- translation,
- this._boundingSphere
- );
- };
- function getCheckUniformSemanticFunction(
- modelSemantics,
- supportedSemantics,
- programId,
- uniformMap
- ) {
- return function (uniform, uniformName) {
- const semantic = uniform.semantic;
- if (defined(semantic) && modelSemantics.indexOf(semantic) > -1) {
- if (supportedSemantics.indexOf(semantic) > -1) {
- uniformMap[uniformName] = semantic;
- } else {
- throw new RuntimeError(
- `${
- "Shader program cannot be optimized for instancing. " + 'Uniform "'
- }${uniformName}" in program "${programId}" uses unsupported semantic "${semantic}"`
- );
- }
- }
- };
- }
- function getInstancedUniforms(collection, programId) {
- if (defined(collection._instancedUniformsByProgram)) {
- return collection._instancedUniformsByProgram[programId];
- }
- const instancedUniformsByProgram = {};
- collection._instancedUniformsByProgram = instancedUniformsByProgram;
- // When using CESIUM_RTC_MODELVIEW the CESIUM_RTC center is ignored. Instances are always rendered relative-to-center.
- const modelSemantics = [
- "MODEL",
- "MODELVIEW",
- "CESIUM_RTC_MODELVIEW",
- "MODELVIEWPROJECTION",
- "MODELINVERSE",
- "MODELVIEWINVERSE",
- "MODELVIEWPROJECTIONINVERSE",
- "MODELINVERSETRANSPOSE",
- "MODELVIEWINVERSETRANSPOSE",
- ];
- const supportedSemantics = [
- "MODELVIEW",
- "CESIUM_RTC_MODELVIEW",
- "MODELVIEWPROJECTION",
- "MODELVIEWINVERSETRANSPOSE",
- ];
- const techniques = collection._model._sourceTechniques;
- for (const techniqueId in techniques) {
- if (techniques.hasOwnProperty(techniqueId)) {
- const technique = techniques[techniqueId];
- const program = technique.program;
- // Different techniques may share the same program, skip if already processed.
- // This assumes techniques that share a program do not declare different semantics for the same uniforms.
- if (!defined(instancedUniformsByProgram[program])) {
- const uniformMap = {};
- instancedUniformsByProgram[program] = uniformMap;
- ForEach.techniqueUniform(
- technique,
- getCheckUniformSemanticFunction(
- modelSemantics,
- supportedSemantics,
- programId,
- uniformMap
- )
- );
- }
- }
- }
- return instancedUniformsByProgram[programId];
- }
- function getVertexShaderCallback(collection) {
- return function (vs, programId) {
- const instancedUniforms = getInstancedUniforms(collection, programId);
- const usesBatchTable = defined(collection._batchTable);
- let renamedSource = ShaderSource.replaceMain(vs, "czm_instancing_main");
- let globalVarsHeader = "";
- let globalVarsMain = "";
- for (const uniform in instancedUniforms) {
- if (instancedUniforms.hasOwnProperty(uniform)) {
- const semantic = instancedUniforms[uniform];
- let varName;
- if (semantic === "MODELVIEW" || semantic === "CESIUM_RTC_MODELVIEW") {
- varName = "czm_instanced_modelView";
- } else if (semantic === "MODELVIEWPROJECTION") {
- varName = "czm_instanced_modelViewProjection";
- globalVarsHeader += "mat4 czm_instanced_modelViewProjection;\n";
- globalVarsMain +=
- "czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n";
- } else if (semantic === "MODELVIEWINVERSETRANSPOSE") {
- varName = "czm_instanced_modelViewInverseTranspose";
- globalVarsHeader += "mat3 czm_instanced_modelViewInverseTranspose;\n";
- globalVarsMain +=
- "czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n";
- }
- // Remove the uniform declaration
- let regex = new RegExp(`uniform.*${uniform}.*`);
- renamedSource = renamedSource.replace(regex, "");
- // Replace all occurrences of the uniform with the global variable
- regex = new RegExp(`${uniform}\\b`, "g");
- renamedSource = renamedSource.replace(regex, varName);
- }
- }
- // czm_instanced_model is the model matrix of the instance relative to center
- // czm_instanced_modifiedModelView is the transform from the center to view
- // czm_instanced_nodeTransform is the local offset of the node within the model
- const uniforms =
- "uniform mat4 czm_instanced_modifiedModelView;\n" +
- "uniform mat4 czm_instanced_nodeTransform;\n";
- let batchIdAttribute;
- let pickAttribute;
- let pickVarying;
- if (usesBatchTable) {
- batchIdAttribute = "attribute float a_batchId;\n";
- pickAttribute = "";
- pickVarying = "";
- } else {
- batchIdAttribute = "";
- pickAttribute =
- "attribute vec4 pickColor;\n" + "varying vec4 v_pickColor;\n";
- pickVarying = " v_pickColor = pickColor;\n";
- }
- let instancedSource =
- `${uniforms + globalVarsHeader}mat4 czm_instanced_modelView;\n` +
- `attribute vec4 czm_modelMatrixRow0;\n` +
- `attribute vec4 czm_modelMatrixRow1;\n` +
- `attribute vec4 czm_modelMatrixRow2;\n${batchIdAttribute}${pickAttribute}${renamedSource}void main()\n` +
- `{\n` +
- ` mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n` +
- ` czm_instanced_modelView = czm_instanced_modifiedModelView * czm_instanced_model * czm_instanced_nodeTransform;\n${globalVarsMain} czm_instancing_main();\n${pickVarying}}\n`;
- if (usesBatchTable) {
- const gltf = collection._model.gltf;
- const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
- gltf,
- programId
- );
- instancedSource = collection._batchTable.getVertexShaderCallback(
- true,
- "a_batchId",
- diffuseAttributeOrUniformName
- )(instancedSource);
- }
- return instancedSource;
- };
- }
- function getFragmentShaderCallback(collection) {
- return function (fs, programId) {
- const batchTable = collection._batchTable;
- if (defined(batchTable)) {
- const gltf = collection._model.gltf;
- const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
- gltf,
- programId
- );
- fs = batchTable.getFragmentShaderCallback(
- true,
- diffuseAttributeOrUniformName,
- false
- )(fs);
- } else {
- fs = `varying vec4 v_pickColor;\n${fs}`;
- }
- return fs;
- };
- }
- function createModifiedModelView(collection, context) {
- return function () {
- return Matrix4.multiply(
- context.uniformState.view,
- collection._rtcTransform,
- collection._rtcModelView
- );
- };
- }
- function createNodeTransformFunction(node) {
- return function () {
- return node.computedMatrix;
- };
- }
- function getUniformMapCallback(collection, context) {
- return function (uniformMap, programId, node) {
- uniformMap = clone(uniformMap);
- uniformMap.czm_instanced_modifiedModelView = createModifiedModelView(
- collection,
- context
- );
- uniformMap.czm_instanced_nodeTransform = createNodeTransformFunction(node);
- // Remove instanced uniforms from the uniform map
- const instancedUniforms = getInstancedUniforms(collection, programId);
- for (const uniform in instancedUniforms) {
- if (instancedUniforms.hasOwnProperty(uniform)) {
- delete uniformMap[uniform];
- }
- }
- if (defined(collection._batchTable)) {
- uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap);
- }
- return uniformMap;
- };
- }
- function getVertexShaderNonInstancedCallback(collection) {
- return function (vs, programId) {
- if (defined(collection._batchTable)) {
- const gltf = collection._model.gltf;
- const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
- gltf,
- programId
- );
- vs = collection._batchTable.getVertexShaderCallback(
- true,
- "a_batchId",
- diffuseAttributeOrUniformName
- )(vs);
- // Treat a_batchId as a uniform rather than a vertex attribute
- vs = `uniform float a_batchId\n;${vs}`;
- }
- return vs;
- };
- }
- function getFragmentShaderNonInstancedCallback(collection) {
- return function (fs, programId) {
- const batchTable = collection._batchTable;
- if (defined(batchTable)) {
- const gltf = collection._model.gltf;
- const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
- gltf,
- programId
- );
- fs = batchTable.getFragmentShaderCallback(
- true,
- diffuseAttributeOrUniformName,
- false
- )(fs);
- } else {
- fs = `uniform vec4 czm_pickColor;\n${fs}`;
- }
- return fs;
- };
- }
- function getUniformMapNonInstancedCallback(collection) {
- return function (uniformMap) {
- if (defined(collection._batchTable)) {
- uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap);
- }
- return uniformMap;
- };
- }
- function getVertexBufferTypedArray(collection) {
- const instances = collection._instances;
- const instancesLength = collection.length;
- const collectionCenter = collection._center;
- const vertexSizeInFloats = 12;
- let bufferData = collection._vertexBufferTypedArray;
- if (!defined(bufferData)) {
- bufferData = new Float32Array(instancesLength * vertexSizeInFloats);
- }
- if (collection._dynamic) {
- // Hold onto the buffer data so we don't have to allocate new memory every frame.
- collection._vertexBufferTypedArray = bufferData;
- }
- for (let i = 0; i < instancesLength; ++i) {
- const modelMatrix = instances[i]._modelMatrix;
- // Instance matrix is relative to center
- const instanceMatrix = Matrix4.clone(modelMatrix, scratchMatrix);
- instanceMatrix[12] -= collectionCenter.x;
- instanceMatrix[13] -= collectionCenter.y;
- instanceMatrix[14] -= collectionCenter.z;
- const offset = i * vertexSizeInFloats;
- // First three rows of the model matrix
- bufferData[offset + 0] = instanceMatrix[0];
- bufferData[offset + 1] = instanceMatrix[4];
- bufferData[offset + 2] = instanceMatrix[8];
- bufferData[offset + 3] = instanceMatrix[12];
- bufferData[offset + 4] = instanceMatrix[1];
- bufferData[offset + 5] = instanceMatrix[5];
- bufferData[offset + 6] = instanceMatrix[9];
- bufferData[offset + 7] = instanceMatrix[13];
- bufferData[offset + 8] = instanceMatrix[2];
- bufferData[offset + 9] = instanceMatrix[6];
- bufferData[offset + 10] = instanceMatrix[10];
- bufferData[offset + 11] = instanceMatrix[14];
- }
- return bufferData;
- }
- function createVertexBuffer(collection, context) {
- let i;
- const instances = collection._instances;
- const instancesLength = collection.length;
- const dynamic = collection._dynamic;
- const usesBatchTable = defined(collection._batchTable);
- if (usesBatchTable) {
- const batchIdBufferData = new Uint16Array(instancesLength);
- for (i = 0; i < instancesLength; ++i) {
- batchIdBufferData[i] = instances[i]._instanceId;
- }
- collection._batchIdBuffer = Buffer.createVertexBuffer({
- context: context,
- typedArray: batchIdBufferData,
- usage: BufferUsage.STATIC_DRAW,
- });
- }
- if (!usesBatchTable) {
- const pickIdBuffer = new Uint8Array(instancesLength * 4);
- for (i = 0; i < instancesLength; ++i) {
- const pickId = collection._pickIds[i];
- const pickColor = pickId.color;
- const offset = i * 4;
- pickIdBuffer[offset] = Color.floatToByte(pickColor.red);
- pickIdBuffer[offset + 1] = Color.floatToByte(pickColor.green);
- pickIdBuffer[offset + 2] = Color.floatToByte(pickColor.blue);
- pickIdBuffer[offset + 3] = Color.floatToByte(pickColor.alpha);
- }
- collection._pickIdBuffer = Buffer.createVertexBuffer({
- context: context,
- typedArray: pickIdBuffer,
- usage: BufferUsage.STATIC_DRAW,
- });
- }
- const vertexBufferTypedArray = getVertexBufferTypedArray(collection);
- collection._vertexBuffer = Buffer.createVertexBuffer({
- context: context,
- typedArray: vertexBufferTypedArray,
- usage: dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW,
- });
- }
- function updateVertexBuffer(collection) {
- const vertexBufferTypedArray = getVertexBufferTypedArray(collection);
- collection._vertexBuffer.copyFromArrayView(vertexBufferTypedArray);
- }
- function createPickIds(collection, context) {
- // PERFORMANCE_IDEA: we could skip the pick buffer completely by allocating
- // a continuous range of pickIds and then converting the base pickId + batchId
- // to RGBA in the shader. The only consider is precision issues, which might
- // not be an issue in WebGL 2.
- const instances = collection._instances;
- const instancesLength = instances.length;
- const pickIds = new Array(instancesLength);
- for (let i = 0; i < instancesLength; ++i) {
- pickIds[i] = context.createPickId(instances[i]);
- }
- return pickIds;
- }
- function createModel(collection, context) {
- const instancingSupported = collection._instancingSupported;
- const usesBatchTable = defined(collection._batchTable);
- const allowPicking = collection._allowPicking;
- const modelOptions = {
- url: collection._url,
- requestType: collection._requestType,
- gltf: collection._gltf,
- basePath: collection._basePath,
- shadows: collection._shadows,
- cacheKey: undefined,
- asynchronous: collection._asynchronous,
- allowPicking: allowPicking,
- incrementallyLoadTextures: collection._incrementallyLoadTextures,
- upAxis: collection._upAxis,
- forwardAxis: collection._forwardAxis,
- precreatedAttributes: undefined,
- vertexShaderLoaded: undefined,
- fragmentShaderLoaded: undefined,
- uniformMapLoaded: undefined,
- pickIdLoaded: collection._pickIdLoaded,
- ignoreCommands: true,
- opaquePass: collection._opaquePass,
- imageBasedLighting: collection._imageBasedLighting,
- showOutline: collection.showOutline,
- showCreditsOnScreen: collection.showCreditsOnScreen,
- };
- if (!usesBatchTable) {
- collection._pickIds = createPickIds(collection, context);
- }
- if (instancingSupported) {
- createVertexBuffer(collection, context);
- const vertexSizeInFloats = 12;
- const componentSizeInBytes = ComponentDatatype.getSizeInBytes(
- ComponentDatatype.FLOAT
- );
- const instancedAttributes = {
- czm_modelMatrixRow0: {
- index: 0, // updated in Model
- vertexBuffer: collection._vertexBuffer,
- componentsPerAttribute: 4,
- componentDatatype: ComponentDatatype.FLOAT,
- normalize: false,
- offsetInBytes: 0,
- strideInBytes: componentSizeInBytes * vertexSizeInFloats,
- instanceDivisor: 1,
- },
- czm_modelMatrixRow1: {
- index: 0, // updated in Model
- vertexBuffer: collection._vertexBuffer,
- componentsPerAttribute: 4,
- componentDatatype: ComponentDatatype.FLOAT,
- normalize: false,
- offsetInBytes: componentSizeInBytes * 4,
- strideInBytes: componentSizeInBytes * vertexSizeInFloats,
- instanceDivisor: 1,
- },
- czm_modelMatrixRow2: {
- index: 0, // updated in Model
- vertexBuffer: collection._vertexBuffer,
- componentsPerAttribute: 4,
- componentDatatype: ComponentDatatype.FLOAT,
- normalize: false,
- offsetInBytes: componentSizeInBytes * 8,
- strideInBytes: componentSizeInBytes * vertexSizeInFloats,
- instanceDivisor: 1,
- },
- };
- // When using a batch table, add a batch id attribute
- if (usesBatchTable) {
- instancedAttributes.a_batchId = {
- index: 0, // updated in Model
- vertexBuffer: collection._batchIdBuffer,
- componentsPerAttribute: 1,
- componentDatatype: ComponentDatatype.UNSIGNED_SHORT,
- normalize: false,
- offsetInBytes: 0,
- strideInBytes: 0,
- instanceDivisor: 1,
- };
- }
- if (!usesBatchTable) {
- instancedAttributes.pickColor = {
- index: 0, // updated in Model
- vertexBuffer: collection._pickIdBuffer,
- componentsPerAttribute: 4,
- componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
- normalize: true,
- offsetInBytes: 0,
- strideInBytes: 0,
- instanceDivisor: 1,
- };
- }
- modelOptions.precreatedAttributes = instancedAttributes;
- modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection);
- modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection);
- modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context);
- if (defined(collection._url)) {
- modelOptions.cacheKey = `${collection._url.getUrlComponent()}#instanced`;
- }
- } else {
- modelOptions.vertexShaderLoaded = getVertexShaderNonInstancedCallback(
- collection
- );
- modelOptions.fragmentShaderLoaded = getFragmentShaderNonInstancedCallback(
- collection
- );
- modelOptions.uniformMapLoaded = getUniformMapNonInstancedCallback(
- collection,
- context
- );
- }
- if (defined(collection._url)) {
- collection._model = Model.fromGltf(modelOptions);
- } else {
- collection._model = new Model(modelOptions);
- }
- }
- function updateWireframe(collection, force) {
- if (collection._debugWireframe !== collection.debugWireframe || force) {
- collection._debugWireframe = collection.debugWireframe;
- // This assumes the original primitive was TRIANGLES and that the triangles
- // are connected for the wireframe to look perfect.
- const primitiveType = collection.debugWireframe
- ? PrimitiveType.LINES
- : PrimitiveType.TRIANGLES;
- const commands = collection._drawCommands;
- const length = commands.length;
- for (let i = 0; i < length; ++i) {
- commands[i].primitiveType = primitiveType;
- }
- }
- }
- function getDisableCullingRenderState(renderState) {
- const rs = clone(renderState, true);
- rs.cull.enabled = false;
- return RenderState.fromCache(rs);
- }
- function updateBackFaceCulling(collection, force) {
- if (collection._backFaceCulling !== collection.backFaceCulling || force) {
- collection._backFaceCulling = collection.backFaceCulling;
- const commands = collection._drawCommands;
- const length = commands.length;
- let i;
- if (!defined(collection._disableCullingRenderStates)) {
- collection._disableCullingRenderStates = new Array(length);
- collection._renderStates = new Array(length);
- for (i = 0; i < length; ++i) {
- const renderState = commands[i].renderState;
- const derivedRenderState = getDisableCullingRenderState(renderState);
- collection._disableCullingRenderStates[i] = derivedRenderState;
- collection._renderStates[i] = renderState;
- }
- }
- for (i = 0; i < length; ++i) {
- commands[i].renderState = collection._backFaceCulling
- ? collection._renderStates[i]
- : collection._disableCullingRenderStates[i];
- }
- }
- }
- function updateShowBoundingVolume(collection, force) {
- if (
- collection.debugShowBoundingVolume !==
- collection._debugShowBoundingVolume ||
- force
- ) {
- collection._debugShowBoundingVolume = collection.debugShowBoundingVolume;
- const commands = collection._drawCommands;
- const length = commands.length;
- for (let i = 0; i < length; ++i) {
- commands[i].debugShowBoundingVolume = collection.debugShowBoundingVolume;
- }
- }
- }
- function createCommands(collection, drawCommands) {
- const commandsLength = drawCommands.length;
- const instancesLength = collection.length;
- const boundingSphere = collection._boundingSphere;
- const cull = collection._cull;
- for (let i = 0; i < commandsLength; ++i) {
- const drawCommand = DrawCommand.shallowClone(drawCommands[i]);
- drawCommand.instanceCount = instancesLength;
- drawCommand.boundingVolume = boundingSphere;
- drawCommand.cull = cull;
- if (defined(collection._batchTable)) {
- drawCommand.pickId = collection._batchTable.getPickId();
- } else {
- drawCommand.pickId = "v_pickColor";
- }
- collection._drawCommands.push(drawCommand);
- }
- }
- function createBatchIdFunction(batchId) {
- return function () {
- return batchId;
- };
- }
- function createPickColorFunction(color) {
- return function () {
- return color;
- };
- }
- function createCommandsNonInstanced(collection, drawCommands) {
- // When instancing is disabled, create commands for every instance.
- const instances = collection._instances;
- const commandsLength = drawCommands.length;
- const instancesLength = collection.length;
- const batchTable = collection._batchTable;
- const usesBatchTable = defined(batchTable);
- const cull = collection._cull;
- for (let i = 0; i < commandsLength; ++i) {
- for (let j = 0; j < instancesLength; ++j) {
- const drawCommand = DrawCommand.shallowClone(drawCommands[i]);
- drawCommand.modelMatrix = new Matrix4(); // Updated in updateCommandsNonInstanced
- drawCommand.boundingVolume = new BoundingSphere(); // Updated in updateCommandsNonInstanced
- drawCommand.cull = cull;
- drawCommand.uniformMap = clone(drawCommand.uniformMap);
- if (usesBatchTable) {
- drawCommand.uniformMap.a_batchId = createBatchIdFunction(
- instances[j]._instanceId
- );
- } else {
- const pickId = collection._pickIds[j];
- drawCommand.uniformMap.czm_pickColor = createPickColorFunction(
- pickId.color
- );
- }
- collection._drawCommands.push(drawCommand);
- }
- }
- }
- function updateCommandsNonInstanced(collection) {
- const modelCommands = collection._modelCommands;
- const commandsLength = modelCommands.length;
- const instancesLength = collection.length;
- const collectionTransform = collection._rtcTransform;
- const collectionCenter = collection._center;
- for (let i = 0; i < commandsLength; ++i) {
- const modelCommand = modelCommands[i];
- for (let j = 0; j < instancesLength; ++j) {
- const commandIndex = i * instancesLength + j;
- const drawCommand = collection._drawCommands[commandIndex];
- let instanceMatrix = Matrix4.clone(
- collection._instances[j]._modelMatrix,
- scratchMatrix
- );
- instanceMatrix[12] -= collectionCenter.x;
- instanceMatrix[13] -= collectionCenter.y;
- instanceMatrix[14] -= collectionCenter.z;
- instanceMatrix = Matrix4.multiply(
- collectionTransform,
- instanceMatrix,
- scratchMatrix
- );
- const nodeMatrix = modelCommand.modelMatrix;
- const modelMatrix = drawCommand.modelMatrix;
- Matrix4.multiply(instanceMatrix, nodeMatrix, modelMatrix);
- const nodeBoundingSphere = modelCommand.boundingVolume;
- const boundingSphere = drawCommand.boundingVolume;
- BoundingSphere.transform(
- nodeBoundingSphere,
- instanceMatrix,
- boundingSphere
- );
- }
- }
- }
- function getModelCommands(model) {
- const nodeCommands = model._nodeCommands;
- const length = nodeCommands.length;
- const drawCommands = [];
- for (let i = 0; i < length; ++i) {
- const nc = nodeCommands[i];
- if (nc.show) {
- drawCommands.push(nc.command);
- }
- }
- return drawCommands;
- }
- function commandsDirty(model) {
- const nodeCommands = model._nodeCommands;
- const length = nodeCommands.length;
- let commandsDirty = false;
- for (let i = 0; i < length; i++) {
- const nc = nodeCommands[i];
- if (nc.command.dirty) {
- nc.command.dirty = false;
- commandsDirty = true;
- }
- }
- return commandsDirty;
- }
- function generateModelCommands(modelInstanceCollection, instancingSupported) {
- modelInstanceCollection._drawCommands = [];
- const modelCommands = getModelCommands(modelInstanceCollection._model);
- if (instancingSupported) {
- createCommands(modelInstanceCollection, modelCommands);
- } else {
- createCommandsNonInstanced(modelInstanceCollection, modelCommands);
- updateCommandsNonInstanced(modelInstanceCollection);
- }
- }
- function updateShadows(collection, force) {
- if (collection.shadows !== collection._shadows || force) {
- collection._shadows = collection.shadows;
- const castShadows = ShadowMode.castShadows(collection.shadows);
- const receiveShadows = ShadowMode.receiveShadows(collection.shadows);
- const drawCommands = collection._drawCommands;
- const length = drawCommands.length;
- for (let i = 0; i < length; ++i) {
- const drawCommand = drawCommands[i];
- drawCommand.castShadows = castShadows;
- drawCommand.receiveShadows = receiveShadows;
- }
- }
- }
- ModelInstanceCollection.prototype.update = function (frameState) {
- if (frameState.mode === SceneMode.MORPHING) {
- return;
- }
- if (!this.show) {
- return;
- }
- if (this.length === 0) {
- return;
- }
- const context = frameState.context;
- if (this._state === LoadState.NEEDS_LOAD) {
- this._state = LoadState.LOADING;
- this._instancingSupported = context.instancedArrays;
- createModel(this, context);
- const that = this;
- this._model.readyPromise.catch(function (error) {
- that._state = LoadState.FAILED;
- that._readyPromise.reject(error);
- });
- }
- const instancingSupported = this._instancingSupported;
- const model = this._model;
- model.imageBasedLighting = this._imageBasedLighting;
- model.showCreditsOnScreen = this.showCreditsOnScreen;
- model.splitDirection = this.splitDirection;
- model.update(frameState);
- if (model.ready && this._state === LoadState.LOADING) {
- this._state = LoadState.LOADED;
- this._ready = true;
- // Expand bounding volume to fit the radius of the loaded model including the model's offset from the center
- const modelRadius =
- model.boundingSphere.radius +
- Cartesian3.magnitude(model.boundingSphere.center);
- this._boundingSphere.radius += modelRadius;
- this._modelCommands = getModelCommands(model);
- generateModelCommands(this, instancingSupported);
- this._readyPromise.resolve(this);
- return;
- }
- if (this._state !== LoadState.LOADED) {
- return;
- }
- const modeChanged = frameState.mode !== this._mode;
- const modelMatrix = this.modelMatrix;
- const modelMatrixChanged = !Matrix4.equals(this._modelMatrix, modelMatrix);
- if (modeChanged || modelMatrixChanged) {
- this._mode = frameState.mode;
- Matrix4.clone(modelMatrix, this._modelMatrix);
- let rtcTransform = Matrix4.multiplyByTranslation(
- this._modelMatrix,
- this._center,
- this._rtcTransform
- );
- if (this._mode !== SceneMode.SCENE3D) {
- rtcTransform = Transforms.basisTo2D(
- frameState.mapProjection,
- rtcTransform,
- rtcTransform
- );
- }
- Matrix4.getTranslation(rtcTransform, this._boundingSphere.center);
- }
- if (instancingSupported && this._dirty) {
- // If at least one instance has moved assume the collection is now dynamic
- this._dynamic = true;
- this._dirty = false;
- // PERFORMANCE_IDEA: only update dirty sub-sections instead of the whole collection
- updateVertexBuffer(this);
- }
- // If the model was set to rebuild shaders during update, rebuild instanced commands.
- const modelCommandsDirty = commandsDirty(model);
- if (modelCommandsDirty) {
- generateModelCommands(this, instancingSupported);
- }
- // If any node changes due to an animation, update the commands. This could be inefficient if the model is
- // composed of many nodes and only one changes, however it is probably fine in the general use case.
- // Only applies when instancing is disabled. The instanced shader automatically handles node transformations.
- if (
- !instancingSupported &&
- (model.dirty || this._dirty || modeChanged || modelMatrixChanged)
- ) {
- updateCommandsNonInstanced(this);
- }
- updateShadows(this, modelCommandsDirty);
- updateWireframe(this, modelCommandsDirty);
- updateBackFaceCulling(this, modelCommandsDirty);
- updateShowBoundingVolume(this, modelCommandsDirty);
- const passes = frameState.passes;
- if (!passes.render && !passes.pick) {
- return;
- }
- const commandList = frameState.commandList;
- const commands = this._drawCommands;
- const commandsLength = commands.length;
- for (let i = 0; i < commandsLength; ++i) {
- commandList.push(commands[i]);
- }
- };
- ModelInstanceCollection.prototype.isDestroyed = function () {
- return false;
- };
- ModelInstanceCollection.prototype.destroy = function () {
- this._model = this._model && this._model.destroy();
- const pickIds = this._pickIds;
- if (defined(pickIds)) {
- const length = pickIds.length;
- for (let i = 0; i < length; ++i) {
- pickIds[i].destroy();
- }
- }
- if (
- this._shouldDestroyImageBasedLighting &&
- !this._imageBasedLighting.isDestroyed()
- ) {
- this._imageBasedLighting.destroy();
- }
- this._imageBasedLighting = undefined;
- return destroyObject(this);
- };
- export default ModelInstanceCollection;
|