ModelInstanceCollection.js 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  1. import BoundingSphere from "../Core/BoundingSphere.js";
  2. import Cartesian3 from "../Core/Cartesian3.js";
  3. import clone from "../Core/clone.js";
  4. import Color from "../Core/Color.js";
  5. import ComponentDatatype from "../Core/ComponentDatatype.js";
  6. import defaultValue from "../Core/defaultValue.js";
  7. import defer from "../Core/defer.js";
  8. import defined from "../Core/defined.js";
  9. import destroyObject from "../Core/destroyObject.js";
  10. import DeveloperError from "../Core/DeveloperError.js";
  11. import ImageBasedLighting from "./ImageBasedLighting.js";
  12. import Matrix4 from "../Core/Matrix4.js";
  13. import PrimitiveType from "../Core/PrimitiveType.js";
  14. import Resource from "../Core/Resource.js";
  15. import RuntimeError from "../Core/RuntimeError.js";
  16. import Transforms from "../Core/Transforms.js";
  17. import Buffer from "../Renderer/Buffer.js";
  18. import BufferUsage from "../Renderer/BufferUsage.js";
  19. import DrawCommand from "../Renderer/DrawCommand.js";
  20. import Pass from "../Renderer/Pass.js";
  21. import RenderState from "../Renderer/RenderState.js";
  22. import ShaderSource from "../Renderer/ShaderSource.js";
  23. import ForEach from "./GltfPipeline/ForEach.js";
  24. import Model from "./Model.js";
  25. import ModelInstance from "./ModelInstance.js";
  26. import ModelUtility from "./ModelUtility.js";
  27. import SceneMode from "./SceneMode.js";
  28. import ShadowMode from "./ShadowMode.js";
  29. import SplitDirection from "./SplitDirection.js";
  30. const LoadState = {
  31. NEEDS_LOAD: 0,
  32. LOADING: 1,
  33. LOADED: 2,
  34. FAILED: 3,
  35. };
  36. /**
  37. * A 3D model instance collection. All instances reference the same underlying model, but have unique
  38. * per-instance properties like model matrix, pick id, etc.
  39. *
  40. * Instances are rendered relative-to-center and for best results instances should be positioned close to one another.
  41. * Otherwise there may be precision issues if, for example, instances are placed on opposite sides of the globe.
  42. *
  43. * @alias ModelInstanceCollection
  44. * @constructor
  45. *
  46. * @param {Object} options Object with the following properties:
  47. * @param {Object[]} [options.instances] An array of instances, where each instance contains a modelMatrix and optional batchId when options.batchTable is defined.
  48. * @param {Cesium3DTileBatchTable} [options.batchTable] The batch table of the instanced 3D Tile.
  49. * @param {Resource|String} [options.url] The url to the .gltf file.
  50. * @param {Object} [options.requestType] The request type, used for request prioritization
  51. * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] A glTF JSON object, or a binary glTF buffer.
  52. * @param {Resource|String} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
  53. * @param {Boolean} [options.dynamic=false] Hint if instance model matrices will be updated frequently.
  54. * @param {Boolean} [options.show=true] Determines if the collection will be shown.
  55. * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each instance is pickable with {@link Scene#pick}.
  56. * @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.
  57. * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
  58. * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the collection casts or receives shadows from light sources.
  59. * @param {Cartesian3} [options.lightColor] The light color when shading models. When <code>undefined</code> the scene's light color is used instead.
  60. * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting for this tileset.
  61. * @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.
  62. * @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.
  63. * @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.
  64. * @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.
  65. * @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.
  66. * @param {Boolean} [options.showCreditsOnScreen=false] Whether to display the credits of this model on screen.
  67. * @param {SplitDirection} [options.splitDirection=SplitDirection.NONE] The {@link SplitDirection} split to apply to this collection.
  68. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection.
  69. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe.
  70. * @exception {DeveloperError} Must specify either <options.gltf> or <options.url>, but not both.
  71. * @exception {DeveloperError} Shader program cannot be optimized for instancing. Parameters cannot have any of the following semantics: MODEL, MODELINVERSE, MODELVIEWINVERSE, MODELVIEWPROJECTIONINVERSE, MODELINVERSETRANSPOSE.
  72. *
  73. * @private
  74. */
  75. function ModelInstanceCollection(options) {
  76. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  77. //>>includeStart('debug', pragmas.debug);
  78. if (!defined(options.gltf) && !defined(options.url)) {
  79. throw new DeveloperError("Either options.gltf or options.url is required.");
  80. }
  81. if (defined(options.gltf) && defined(options.url)) {
  82. throw new DeveloperError(
  83. "Cannot pass in both options.gltf and options.url."
  84. );
  85. }
  86. //>>includeEnd('debug');
  87. this.show = defaultValue(options.show, true);
  88. this._instancingSupported = false;
  89. this._dynamic = defaultValue(options.dynamic, false);
  90. this._allowPicking = defaultValue(options.allowPicking, true);
  91. this._ready = false;
  92. this._readyPromise = defer();
  93. this._state = LoadState.NEEDS_LOAD;
  94. this._dirty = false;
  95. // Undocumented options
  96. this._cull = defaultValue(options.cull, true);
  97. this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE);
  98. this._instances = createInstances(this, options.instances);
  99. // When the model instance collection is backed by an i3dm tile,
  100. // use its batch table resources to modify the shaders, attributes, and uniform maps.
  101. this._batchTable = options.batchTable;
  102. this._model = undefined;
  103. this._vertexBufferTypedArray = undefined; // Hold onto the vertex buffer contents when dynamic is true
  104. this._vertexBuffer = undefined;
  105. this._batchIdBuffer = undefined;
  106. this._instancedUniformsByProgram = undefined;
  107. this._drawCommands = [];
  108. this._modelCommands = undefined;
  109. this._renderStates = undefined;
  110. this._disableCullingRenderStates = undefined;
  111. this._boundingSphere = createBoundingSphere(this);
  112. this._center = Cartesian3.clone(this._boundingSphere.center);
  113. this._rtcTransform = new Matrix4();
  114. this._rtcModelView = new Matrix4(); // Holds onto uniform
  115. this._mode = undefined;
  116. this.modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
  117. this._modelMatrix = Matrix4.clone(this.modelMatrix);
  118. // Passed on to Model
  119. this._url = Resource.createIfNeeded(options.url);
  120. this._requestType = options.requestType;
  121. this._gltf = options.gltf;
  122. this._basePath = Resource.createIfNeeded(options.basePath);
  123. this._asynchronous = options.asynchronous;
  124. this._incrementallyLoadTextures = options.incrementallyLoadTextures;
  125. this._upAxis = options.upAxis; // Undocumented option
  126. this._forwardAxis = options.forwardAxis; // Undocumented option
  127. this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
  128. this._shadows = this.shadows;
  129. this._pickIdLoaded = options.pickIdLoaded;
  130. /**
  131. * The {@link SplitDirection} to apply to this collection.
  132. *
  133. * @type {SplitDirection}
  134. * @default {@link SplitDirection.NONE}
  135. */
  136. this.splitDirection = defaultValue(
  137. options.splitDirection,
  138. SplitDirection.NONE
  139. );
  140. this.debugShowBoundingVolume = defaultValue(
  141. options.debugShowBoundingVolume,
  142. false
  143. );
  144. this._debugShowBoundingVolume = false;
  145. this.debugWireframe = defaultValue(options.debugWireframe, false);
  146. this._debugWireframe = false;
  147. if (defined(options.imageBasedLighting)) {
  148. this._imageBasedLighting = options.imageBasedLighting;
  149. this._shouldDestroyImageBasedLighting = false;
  150. } else {
  151. // Create image-based lighting from the old constructor parameters.
  152. this._imageBasedLighting = new ImageBasedLighting({
  153. imageBasedLightingFactor: options.imageBasedLightingFactor,
  154. luminanceAtZenith: options.luminanceAtZenith,
  155. sphericalHarmonicCoefficients: options.sphericalHarmonicCoefficients,
  156. specularEnvironmentMaps: options.specularEnvironmentMaps,
  157. });
  158. this._shouldDestroyImageBasedLighting = true;
  159. }
  160. this.backFaceCulling = defaultValue(options.backFaceCulling, true);
  161. this._backFaceCulling = this.backFaceCulling;
  162. this.showCreditsOnScreen = defaultValue(options.showCreditsOnScreen, false);
  163. }
  164. Object.defineProperties(ModelInstanceCollection.prototype, {
  165. allowPicking: {
  166. get: function () {
  167. return this._allowPicking;
  168. },
  169. },
  170. length: {
  171. get: function () {
  172. return this._instances.length;
  173. },
  174. },
  175. activeAnimations: {
  176. get: function () {
  177. return this._model.activeAnimations;
  178. },
  179. },
  180. ready: {
  181. get: function () {
  182. return this._ready;
  183. },
  184. },
  185. readyPromise: {
  186. get: function () {
  187. return this._readyPromise.promise;
  188. },
  189. },
  190. imageBasedLighting: {
  191. get: function () {
  192. return this._imageBasedLighting;
  193. },
  194. set: function (value) {
  195. if (value !== this._imageBasedLighting) {
  196. if (
  197. this._shouldDestroyImageBasedLighting &&
  198. !this._imageBasedLighting.isDestroyed()
  199. ) {
  200. this._imageBasedLighting.destroy();
  201. }
  202. this._imageBasedLighting = value;
  203. this._shouldDestroyImageBasedLighting = false;
  204. }
  205. },
  206. },
  207. imageBasedLightingFactor: {
  208. get: function () {
  209. return this._imageBasedLighting.imageBasedLightingFactor;
  210. },
  211. set: function (value) {
  212. this._imageBasedLighting.imageBasedLightingFactor = value;
  213. },
  214. },
  215. luminanceAtZenith: {
  216. get: function () {
  217. return this._imageBasedLighting.luminanceAtZenith;
  218. },
  219. set: function (value) {
  220. this._imageBasedLighting.luminanceAtZenith = value;
  221. },
  222. },
  223. sphericalHarmonicCoefficients: {
  224. get: function () {
  225. return this._imageBasedLighting.sphericalHarmonicCoefficients;
  226. },
  227. set: function (value) {
  228. this._imageBasedLighting.sphericalHarmonicCoefficients = value;
  229. },
  230. },
  231. specularEnvironmentMaps: {
  232. get: function () {
  233. return this._imageBasedLighting.specularEnvironmentMaps;
  234. },
  235. set: function (value) {
  236. this._imageBasedLighting.specularEnvironmentMaps = value;
  237. },
  238. },
  239. });
  240. function createInstances(collection, instancesOptions) {
  241. instancesOptions = defaultValue(instancesOptions, []);
  242. const length = instancesOptions.length;
  243. const instances = new Array(length);
  244. for (let i = 0; i < length; ++i) {
  245. const instanceOptions = instancesOptions[i];
  246. const modelMatrix = instanceOptions.modelMatrix;
  247. const instanceId = defaultValue(instanceOptions.batchId, i);
  248. instances[i] = new ModelInstance(collection, modelMatrix, instanceId);
  249. }
  250. return instances;
  251. }
  252. function createBoundingSphere(collection) {
  253. const instancesLength = collection.length;
  254. const points = new Array(instancesLength);
  255. for (let i = 0; i < instancesLength; ++i) {
  256. points[i] = Matrix4.getTranslation(
  257. collection._instances[i]._modelMatrix,
  258. new Cartesian3()
  259. );
  260. }
  261. return BoundingSphere.fromPoints(points);
  262. }
  263. const scratchCartesian = new Cartesian3();
  264. const scratchMatrix = new Matrix4();
  265. ModelInstanceCollection.prototype.expandBoundingSphere = function (
  266. instanceModelMatrix
  267. ) {
  268. const translation = Matrix4.getTranslation(
  269. instanceModelMatrix,
  270. scratchCartesian
  271. );
  272. BoundingSphere.expand(
  273. this._boundingSphere,
  274. translation,
  275. this._boundingSphere
  276. );
  277. };
  278. function getCheckUniformSemanticFunction(
  279. modelSemantics,
  280. supportedSemantics,
  281. programId,
  282. uniformMap
  283. ) {
  284. return function (uniform, uniformName) {
  285. const semantic = uniform.semantic;
  286. if (defined(semantic) && modelSemantics.indexOf(semantic) > -1) {
  287. if (supportedSemantics.indexOf(semantic) > -1) {
  288. uniformMap[uniformName] = semantic;
  289. } else {
  290. throw new RuntimeError(
  291. `${
  292. "Shader program cannot be optimized for instancing. " + 'Uniform "'
  293. }${uniformName}" in program "${programId}" uses unsupported semantic "${semantic}"`
  294. );
  295. }
  296. }
  297. };
  298. }
  299. function getInstancedUniforms(collection, programId) {
  300. if (defined(collection._instancedUniformsByProgram)) {
  301. return collection._instancedUniformsByProgram[programId];
  302. }
  303. const instancedUniformsByProgram = {};
  304. collection._instancedUniformsByProgram = instancedUniformsByProgram;
  305. // When using CESIUM_RTC_MODELVIEW the CESIUM_RTC center is ignored. Instances are always rendered relative-to-center.
  306. const modelSemantics = [
  307. "MODEL",
  308. "MODELVIEW",
  309. "CESIUM_RTC_MODELVIEW",
  310. "MODELVIEWPROJECTION",
  311. "MODELINVERSE",
  312. "MODELVIEWINVERSE",
  313. "MODELVIEWPROJECTIONINVERSE",
  314. "MODELINVERSETRANSPOSE",
  315. "MODELVIEWINVERSETRANSPOSE",
  316. ];
  317. const supportedSemantics = [
  318. "MODELVIEW",
  319. "CESIUM_RTC_MODELVIEW",
  320. "MODELVIEWPROJECTION",
  321. "MODELVIEWINVERSETRANSPOSE",
  322. ];
  323. const techniques = collection._model._sourceTechniques;
  324. for (const techniqueId in techniques) {
  325. if (techniques.hasOwnProperty(techniqueId)) {
  326. const technique = techniques[techniqueId];
  327. const program = technique.program;
  328. // Different techniques may share the same program, skip if already processed.
  329. // This assumes techniques that share a program do not declare different semantics for the same uniforms.
  330. if (!defined(instancedUniformsByProgram[program])) {
  331. const uniformMap = {};
  332. instancedUniformsByProgram[program] = uniformMap;
  333. ForEach.techniqueUniform(
  334. technique,
  335. getCheckUniformSemanticFunction(
  336. modelSemantics,
  337. supportedSemantics,
  338. programId,
  339. uniformMap
  340. )
  341. );
  342. }
  343. }
  344. }
  345. return instancedUniformsByProgram[programId];
  346. }
  347. function getVertexShaderCallback(collection) {
  348. return function (vs, programId) {
  349. const instancedUniforms = getInstancedUniforms(collection, programId);
  350. const usesBatchTable = defined(collection._batchTable);
  351. let renamedSource = ShaderSource.replaceMain(vs, "czm_instancing_main");
  352. let globalVarsHeader = "";
  353. let globalVarsMain = "";
  354. for (const uniform in instancedUniforms) {
  355. if (instancedUniforms.hasOwnProperty(uniform)) {
  356. const semantic = instancedUniforms[uniform];
  357. let varName;
  358. if (semantic === "MODELVIEW" || semantic === "CESIUM_RTC_MODELVIEW") {
  359. varName = "czm_instanced_modelView";
  360. } else if (semantic === "MODELVIEWPROJECTION") {
  361. varName = "czm_instanced_modelViewProjection";
  362. globalVarsHeader += "mat4 czm_instanced_modelViewProjection;\n";
  363. globalVarsMain +=
  364. "czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n";
  365. } else if (semantic === "MODELVIEWINVERSETRANSPOSE") {
  366. varName = "czm_instanced_modelViewInverseTranspose";
  367. globalVarsHeader += "mat3 czm_instanced_modelViewInverseTranspose;\n";
  368. globalVarsMain +=
  369. "czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n";
  370. }
  371. // Remove the uniform declaration
  372. let regex = new RegExp(`uniform.*${uniform}.*`);
  373. renamedSource = renamedSource.replace(regex, "");
  374. // Replace all occurrences of the uniform with the global variable
  375. regex = new RegExp(`${uniform}\\b`, "g");
  376. renamedSource = renamedSource.replace(regex, varName);
  377. }
  378. }
  379. // czm_instanced_model is the model matrix of the instance relative to center
  380. // czm_instanced_modifiedModelView is the transform from the center to view
  381. // czm_instanced_nodeTransform is the local offset of the node within the model
  382. const uniforms =
  383. "uniform mat4 czm_instanced_modifiedModelView;\n" +
  384. "uniform mat4 czm_instanced_nodeTransform;\n";
  385. let batchIdAttribute;
  386. let pickAttribute;
  387. let pickVarying;
  388. if (usesBatchTable) {
  389. batchIdAttribute = "attribute float a_batchId;\n";
  390. pickAttribute = "";
  391. pickVarying = "";
  392. } else {
  393. batchIdAttribute = "";
  394. pickAttribute =
  395. "attribute vec4 pickColor;\n" + "varying vec4 v_pickColor;\n";
  396. pickVarying = " v_pickColor = pickColor;\n";
  397. }
  398. let instancedSource =
  399. `${uniforms + globalVarsHeader}mat4 czm_instanced_modelView;\n` +
  400. `attribute vec4 czm_modelMatrixRow0;\n` +
  401. `attribute vec4 czm_modelMatrixRow1;\n` +
  402. `attribute vec4 czm_modelMatrixRow2;\n${batchIdAttribute}${pickAttribute}${renamedSource}void main()\n` +
  403. `{\n` +
  404. ` 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` +
  405. ` czm_instanced_modelView = czm_instanced_modifiedModelView * czm_instanced_model * czm_instanced_nodeTransform;\n${globalVarsMain} czm_instancing_main();\n${pickVarying}}\n`;
  406. if (usesBatchTable) {
  407. const gltf = collection._model.gltf;
  408. const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
  409. gltf,
  410. programId
  411. );
  412. instancedSource = collection._batchTable.getVertexShaderCallback(
  413. true,
  414. "a_batchId",
  415. diffuseAttributeOrUniformName
  416. )(instancedSource);
  417. }
  418. return instancedSource;
  419. };
  420. }
  421. function getFragmentShaderCallback(collection) {
  422. return function (fs, programId) {
  423. const batchTable = collection._batchTable;
  424. if (defined(batchTable)) {
  425. const gltf = collection._model.gltf;
  426. const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
  427. gltf,
  428. programId
  429. );
  430. fs = batchTable.getFragmentShaderCallback(
  431. true,
  432. diffuseAttributeOrUniformName,
  433. false
  434. )(fs);
  435. } else {
  436. fs = `varying vec4 v_pickColor;\n${fs}`;
  437. }
  438. return fs;
  439. };
  440. }
  441. function createModifiedModelView(collection, context) {
  442. return function () {
  443. return Matrix4.multiply(
  444. context.uniformState.view,
  445. collection._rtcTransform,
  446. collection._rtcModelView
  447. );
  448. };
  449. }
  450. function createNodeTransformFunction(node) {
  451. return function () {
  452. return node.computedMatrix;
  453. };
  454. }
  455. function getUniformMapCallback(collection, context) {
  456. return function (uniformMap, programId, node) {
  457. uniformMap = clone(uniformMap);
  458. uniformMap.czm_instanced_modifiedModelView = createModifiedModelView(
  459. collection,
  460. context
  461. );
  462. uniformMap.czm_instanced_nodeTransform = createNodeTransformFunction(node);
  463. // Remove instanced uniforms from the uniform map
  464. const instancedUniforms = getInstancedUniforms(collection, programId);
  465. for (const uniform in instancedUniforms) {
  466. if (instancedUniforms.hasOwnProperty(uniform)) {
  467. delete uniformMap[uniform];
  468. }
  469. }
  470. if (defined(collection._batchTable)) {
  471. uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap);
  472. }
  473. return uniformMap;
  474. };
  475. }
  476. function getVertexShaderNonInstancedCallback(collection) {
  477. return function (vs, programId) {
  478. if (defined(collection._batchTable)) {
  479. const gltf = collection._model.gltf;
  480. const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
  481. gltf,
  482. programId
  483. );
  484. vs = collection._batchTable.getVertexShaderCallback(
  485. true,
  486. "a_batchId",
  487. diffuseAttributeOrUniformName
  488. )(vs);
  489. // Treat a_batchId as a uniform rather than a vertex attribute
  490. vs = `uniform float a_batchId\n;${vs}`;
  491. }
  492. return vs;
  493. };
  494. }
  495. function getFragmentShaderNonInstancedCallback(collection) {
  496. return function (fs, programId) {
  497. const batchTable = collection._batchTable;
  498. if (defined(batchTable)) {
  499. const gltf = collection._model.gltf;
  500. const diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(
  501. gltf,
  502. programId
  503. );
  504. fs = batchTable.getFragmentShaderCallback(
  505. true,
  506. diffuseAttributeOrUniformName,
  507. false
  508. )(fs);
  509. } else {
  510. fs = `uniform vec4 czm_pickColor;\n${fs}`;
  511. }
  512. return fs;
  513. };
  514. }
  515. function getUniformMapNonInstancedCallback(collection) {
  516. return function (uniformMap) {
  517. if (defined(collection._batchTable)) {
  518. uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap);
  519. }
  520. return uniformMap;
  521. };
  522. }
  523. function getVertexBufferTypedArray(collection) {
  524. const instances = collection._instances;
  525. const instancesLength = collection.length;
  526. const collectionCenter = collection._center;
  527. const vertexSizeInFloats = 12;
  528. let bufferData = collection._vertexBufferTypedArray;
  529. if (!defined(bufferData)) {
  530. bufferData = new Float32Array(instancesLength * vertexSizeInFloats);
  531. }
  532. if (collection._dynamic) {
  533. // Hold onto the buffer data so we don't have to allocate new memory every frame.
  534. collection._vertexBufferTypedArray = bufferData;
  535. }
  536. for (let i = 0; i < instancesLength; ++i) {
  537. const modelMatrix = instances[i]._modelMatrix;
  538. // Instance matrix is relative to center
  539. const instanceMatrix = Matrix4.clone(modelMatrix, scratchMatrix);
  540. instanceMatrix[12] -= collectionCenter.x;
  541. instanceMatrix[13] -= collectionCenter.y;
  542. instanceMatrix[14] -= collectionCenter.z;
  543. const offset = i * vertexSizeInFloats;
  544. // First three rows of the model matrix
  545. bufferData[offset + 0] = instanceMatrix[0];
  546. bufferData[offset + 1] = instanceMatrix[4];
  547. bufferData[offset + 2] = instanceMatrix[8];
  548. bufferData[offset + 3] = instanceMatrix[12];
  549. bufferData[offset + 4] = instanceMatrix[1];
  550. bufferData[offset + 5] = instanceMatrix[5];
  551. bufferData[offset + 6] = instanceMatrix[9];
  552. bufferData[offset + 7] = instanceMatrix[13];
  553. bufferData[offset + 8] = instanceMatrix[2];
  554. bufferData[offset + 9] = instanceMatrix[6];
  555. bufferData[offset + 10] = instanceMatrix[10];
  556. bufferData[offset + 11] = instanceMatrix[14];
  557. }
  558. return bufferData;
  559. }
  560. function createVertexBuffer(collection, context) {
  561. let i;
  562. const instances = collection._instances;
  563. const instancesLength = collection.length;
  564. const dynamic = collection._dynamic;
  565. const usesBatchTable = defined(collection._batchTable);
  566. if (usesBatchTable) {
  567. const batchIdBufferData = new Uint16Array(instancesLength);
  568. for (i = 0; i < instancesLength; ++i) {
  569. batchIdBufferData[i] = instances[i]._instanceId;
  570. }
  571. collection._batchIdBuffer = Buffer.createVertexBuffer({
  572. context: context,
  573. typedArray: batchIdBufferData,
  574. usage: BufferUsage.STATIC_DRAW,
  575. });
  576. }
  577. if (!usesBatchTable) {
  578. const pickIdBuffer = new Uint8Array(instancesLength * 4);
  579. for (i = 0; i < instancesLength; ++i) {
  580. const pickId = collection._pickIds[i];
  581. const pickColor = pickId.color;
  582. const offset = i * 4;
  583. pickIdBuffer[offset] = Color.floatToByte(pickColor.red);
  584. pickIdBuffer[offset + 1] = Color.floatToByte(pickColor.green);
  585. pickIdBuffer[offset + 2] = Color.floatToByte(pickColor.blue);
  586. pickIdBuffer[offset + 3] = Color.floatToByte(pickColor.alpha);
  587. }
  588. collection._pickIdBuffer = Buffer.createVertexBuffer({
  589. context: context,
  590. typedArray: pickIdBuffer,
  591. usage: BufferUsage.STATIC_DRAW,
  592. });
  593. }
  594. const vertexBufferTypedArray = getVertexBufferTypedArray(collection);
  595. collection._vertexBuffer = Buffer.createVertexBuffer({
  596. context: context,
  597. typedArray: vertexBufferTypedArray,
  598. usage: dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW,
  599. });
  600. }
  601. function updateVertexBuffer(collection) {
  602. const vertexBufferTypedArray = getVertexBufferTypedArray(collection);
  603. collection._vertexBuffer.copyFromArrayView(vertexBufferTypedArray);
  604. }
  605. function createPickIds(collection, context) {
  606. // PERFORMANCE_IDEA: we could skip the pick buffer completely by allocating
  607. // a continuous range of pickIds and then converting the base pickId + batchId
  608. // to RGBA in the shader. The only consider is precision issues, which might
  609. // not be an issue in WebGL 2.
  610. const instances = collection._instances;
  611. const instancesLength = instances.length;
  612. const pickIds = new Array(instancesLength);
  613. for (let i = 0; i < instancesLength; ++i) {
  614. pickIds[i] = context.createPickId(instances[i]);
  615. }
  616. return pickIds;
  617. }
  618. function createModel(collection, context) {
  619. const instancingSupported = collection._instancingSupported;
  620. const usesBatchTable = defined(collection._batchTable);
  621. const allowPicking = collection._allowPicking;
  622. const modelOptions = {
  623. url: collection._url,
  624. requestType: collection._requestType,
  625. gltf: collection._gltf,
  626. basePath: collection._basePath,
  627. shadows: collection._shadows,
  628. cacheKey: undefined,
  629. asynchronous: collection._asynchronous,
  630. allowPicking: allowPicking,
  631. incrementallyLoadTextures: collection._incrementallyLoadTextures,
  632. upAxis: collection._upAxis,
  633. forwardAxis: collection._forwardAxis,
  634. precreatedAttributes: undefined,
  635. vertexShaderLoaded: undefined,
  636. fragmentShaderLoaded: undefined,
  637. uniformMapLoaded: undefined,
  638. pickIdLoaded: collection._pickIdLoaded,
  639. ignoreCommands: true,
  640. opaquePass: collection._opaquePass,
  641. imageBasedLighting: collection._imageBasedLighting,
  642. showOutline: collection.showOutline,
  643. showCreditsOnScreen: collection.showCreditsOnScreen,
  644. };
  645. if (!usesBatchTable) {
  646. collection._pickIds = createPickIds(collection, context);
  647. }
  648. if (instancingSupported) {
  649. createVertexBuffer(collection, context);
  650. const vertexSizeInFloats = 12;
  651. const componentSizeInBytes = ComponentDatatype.getSizeInBytes(
  652. ComponentDatatype.FLOAT
  653. );
  654. const instancedAttributes = {
  655. czm_modelMatrixRow0: {
  656. index: 0, // updated in Model
  657. vertexBuffer: collection._vertexBuffer,
  658. componentsPerAttribute: 4,
  659. componentDatatype: ComponentDatatype.FLOAT,
  660. normalize: false,
  661. offsetInBytes: 0,
  662. strideInBytes: componentSizeInBytes * vertexSizeInFloats,
  663. instanceDivisor: 1,
  664. },
  665. czm_modelMatrixRow1: {
  666. index: 0, // updated in Model
  667. vertexBuffer: collection._vertexBuffer,
  668. componentsPerAttribute: 4,
  669. componentDatatype: ComponentDatatype.FLOAT,
  670. normalize: false,
  671. offsetInBytes: componentSizeInBytes * 4,
  672. strideInBytes: componentSizeInBytes * vertexSizeInFloats,
  673. instanceDivisor: 1,
  674. },
  675. czm_modelMatrixRow2: {
  676. index: 0, // updated in Model
  677. vertexBuffer: collection._vertexBuffer,
  678. componentsPerAttribute: 4,
  679. componentDatatype: ComponentDatatype.FLOAT,
  680. normalize: false,
  681. offsetInBytes: componentSizeInBytes * 8,
  682. strideInBytes: componentSizeInBytes * vertexSizeInFloats,
  683. instanceDivisor: 1,
  684. },
  685. };
  686. // When using a batch table, add a batch id attribute
  687. if (usesBatchTable) {
  688. instancedAttributes.a_batchId = {
  689. index: 0, // updated in Model
  690. vertexBuffer: collection._batchIdBuffer,
  691. componentsPerAttribute: 1,
  692. componentDatatype: ComponentDatatype.UNSIGNED_SHORT,
  693. normalize: false,
  694. offsetInBytes: 0,
  695. strideInBytes: 0,
  696. instanceDivisor: 1,
  697. };
  698. }
  699. if (!usesBatchTable) {
  700. instancedAttributes.pickColor = {
  701. index: 0, // updated in Model
  702. vertexBuffer: collection._pickIdBuffer,
  703. componentsPerAttribute: 4,
  704. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  705. normalize: true,
  706. offsetInBytes: 0,
  707. strideInBytes: 0,
  708. instanceDivisor: 1,
  709. };
  710. }
  711. modelOptions.precreatedAttributes = instancedAttributes;
  712. modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection);
  713. modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection);
  714. modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context);
  715. if (defined(collection._url)) {
  716. modelOptions.cacheKey = `${collection._url.getUrlComponent()}#instanced`;
  717. }
  718. } else {
  719. modelOptions.vertexShaderLoaded = getVertexShaderNonInstancedCallback(
  720. collection
  721. );
  722. modelOptions.fragmentShaderLoaded = getFragmentShaderNonInstancedCallback(
  723. collection
  724. );
  725. modelOptions.uniformMapLoaded = getUniformMapNonInstancedCallback(
  726. collection,
  727. context
  728. );
  729. }
  730. if (defined(collection._url)) {
  731. collection._model = Model.fromGltf(modelOptions);
  732. } else {
  733. collection._model = new Model(modelOptions);
  734. }
  735. }
  736. function updateWireframe(collection, force) {
  737. if (collection._debugWireframe !== collection.debugWireframe || force) {
  738. collection._debugWireframe = collection.debugWireframe;
  739. // This assumes the original primitive was TRIANGLES and that the triangles
  740. // are connected for the wireframe to look perfect.
  741. const primitiveType = collection.debugWireframe
  742. ? PrimitiveType.LINES
  743. : PrimitiveType.TRIANGLES;
  744. const commands = collection._drawCommands;
  745. const length = commands.length;
  746. for (let i = 0; i < length; ++i) {
  747. commands[i].primitiveType = primitiveType;
  748. }
  749. }
  750. }
  751. function getDisableCullingRenderState(renderState) {
  752. const rs = clone(renderState, true);
  753. rs.cull.enabled = false;
  754. return RenderState.fromCache(rs);
  755. }
  756. function updateBackFaceCulling(collection, force) {
  757. if (collection._backFaceCulling !== collection.backFaceCulling || force) {
  758. collection._backFaceCulling = collection.backFaceCulling;
  759. const commands = collection._drawCommands;
  760. const length = commands.length;
  761. let i;
  762. if (!defined(collection._disableCullingRenderStates)) {
  763. collection._disableCullingRenderStates = new Array(length);
  764. collection._renderStates = new Array(length);
  765. for (i = 0; i < length; ++i) {
  766. const renderState = commands[i].renderState;
  767. const derivedRenderState = getDisableCullingRenderState(renderState);
  768. collection._disableCullingRenderStates[i] = derivedRenderState;
  769. collection._renderStates[i] = renderState;
  770. }
  771. }
  772. for (i = 0; i < length; ++i) {
  773. commands[i].renderState = collection._backFaceCulling
  774. ? collection._renderStates[i]
  775. : collection._disableCullingRenderStates[i];
  776. }
  777. }
  778. }
  779. function updateShowBoundingVolume(collection, force) {
  780. if (
  781. collection.debugShowBoundingVolume !==
  782. collection._debugShowBoundingVolume ||
  783. force
  784. ) {
  785. collection._debugShowBoundingVolume = collection.debugShowBoundingVolume;
  786. const commands = collection._drawCommands;
  787. const length = commands.length;
  788. for (let i = 0; i < length; ++i) {
  789. commands[i].debugShowBoundingVolume = collection.debugShowBoundingVolume;
  790. }
  791. }
  792. }
  793. function createCommands(collection, drawCommands) {
  794. const commandsLength = drawCommands.length;
  795. const instancesLength = collection.length;
  796. const boundingSphere = collection._boundingSphere;
  797. const cull = collection._cull;
  798. for (let i = 0; i < commandsLength; ++i) {
  799. const drawCommand = DrawCommand.shallowClone(drawCommands[i]);
  800. drawCommand.instanceCount = instancesLength;
  801. drawCommand.boundingVolume = boundingSphere;
  802. drawCommand.cull = cull;
  803. if (defined(collection._batchTable)) {
  804. drawCommand.pickId = collection._batchTable.getPickId();
  805. } else {
  806. drawCommand.pickId = "v_pickColor";
  807. }
  808. collection._drawCommands.push(drawCommand);
  809. }
  810. }
  811. function createBatchIdFunction(batchId) {
  812. return function () {
  813. return batchId;
  814. };
  815. }
  816. function createPickColorFunction(color) {
  817. return function () {
  818. return color;
  819. };
  820. }
  821. function createCommandsNonInstanced(collection, drawCommands) {
  822. // When instancing is disabled, create commands for every instance.
  823. const instances = collection._instances;
  824. const commandsLength = drawCommands.length;
  825. const instancesLength = collection.length;
  826. const batchTable = collection._batchTable;
  827. const usesBatchTable = defined(batchTable);
  828. const cull = collection._cull;
  829. for (let i = 0; i < commandsLength; ++i) {
  830. for (let j = 0; j < instancesLength; ++j) {
  831. const drawCommand = DrawCommand.shallowClone(drawCommands[i]);
  832. drawCommand.modelMatrix = new Matrix4(); // Updated in updateCommandsNonInstanced
  833. drawCommand.boundingVolume = new BoundingSphere(); // Updated in updateCommandsNonInstanced
  834. drawCommand.cull = cull;
  835. drawCommand.uniformMap = clone(drawCommand.uniformMap);
  836. if (usesBatchTable) {
  837. drawCommand.uniformMap.a_batchId = createBatchIdFunction(
  838. instances[j]._instanceId
  839. );
  840. } else {
  841. const pickId = collection._pickIds[j];
  842. drawCommand.uniformMap.czm_pickColor = createPickColorFunction(
  843. pickId.color
  844. );
  845. }
  846. collection._drawCommands.push(drawCommand);
  847. }
  848. }
  849. }
  850. function updateCommandsNonInstanced(collection) {
  851. const modelCommands = collection._modelCommands;
  852. const commandsLength = modelCommands.length;
  853. const instancesLength = collection.length;
  854. const collectionTransform = collection._rtcTransform;
  855. const collectionCenter = collection._center;
  856. for (let i = 0; i < commandsLength; ++i) {
  857. const modelCommand = modelCommands[i];
  858. for (let j = 0; j < instancesLength; ++j) {
  859. const commandIndex = i * instancesLength + j;
  860. const drawCommand = collection._drawCommands[commandIndex];
  861. let instanceMatrix = Matrix4.clone(
  862. collection._instances[j]._modelMatrix,
  863. scratchMatrix
  864. );
  865. instanceMatrix[12] -= collectionCenter.x;
  866. instanceMatrix[13] -= collectionCenter.y;
  867. instanceMatrix[14] -= collectionCenter.z;
  868. instanceMatrix = Matrix4.multiply(
  869. collectionTransform,
  870. instanceMatrix,
  871. scratchMatrix
  872. );
  873. const nodeMatrix = modelCommand.modelMatrix;
  874. const modelMatrix = drawCommand.modelMatrix;
  875. Matrix4.multiply(instanceMatrix, nodeMatrix, modelMatrix);
  876. const nodeBoundingSphere = modelCommand.boundingVolume;
  877. const boundingSphere = drawCommand.boundingVolume;
  878. BoundingSphere.transform(
  879. nodeBoundingSphere,
  880. instanceMatrix,
  881. boundingSphere
  882. );
  883. }
  884. }
  885. }
  886. function getModelCommands(model) {
  887. const nodeCommands = model._nodeCommands;
  888. const length = nodeCommands.length;
  889. const drawCommands = [];
  890. for (let i = 0; i < length; ++i) {
  891. const nc = nodeCommands[i];
  892. if (nc.show) {
  893. drawCommands.push(nc.command);
  894. }
  895. }
  896. return drawCommands;
  897. }
  898. function commandsDirty(model) {
  899. const nodeCommands = model._nodeCommands;
  900. const length = nodeCommands.length;
  901. let commandsDirty = false;
  902. for (let i = 0; i < length; i++) {
  903. const nc = nodeCommands[i];
  904. if (nc.command.dirty) {
  905. nc.command.dirty = false;
  906. commandsDirty = true;
  907. }
  908. }
  909. return commandsDirty;
  910. }
  911. function generateModelCommands(modelInstanceCollection, instancingSupported) {
  912. modelInstanceCollection._drawCommands = [];
  913. const modelCommands = getModelCommands(modelInstanceCollection._model);
  914. if (instancingSupported) {
  915. createCommands(modelInstanceCollection, modelCommands);
  916. } else {
  917. createCommandsNonInstanced(modelInstanceCollection, modelCommands);
  918. updateCommandsNonInstanced(modelInstanceCollection);
  919. }
  920. }
  921. function updateShadows(collection, force) {
  922. if (collection.shadows !== collection._shadows || force) {
  923. collection._shadows = collection.shadows;
  924. const castShadows = ShadowMode.castShadows(collection.shadows);
  925. const receiveShadows = ShadowMode.receiveShadows(collection.shadows);
  926. const drawCommands = collection._drawCommands;
  927. const length = drawCommands.length;
  928. for (let i = 0; i < length; ++i) {
  929. const drawCommand = drawCommands[i];
  930. drawCommand.castShadows = castShadows;
  931. drawCommand.receiveShadows = receiveShadows;
  932. }
  933. }
  934. }
  935. ModelInstanceCollection.prototype.update = function (frameState) {
  936. if (frameState.mode === SceneMode.MORPHING) {
  937. return;
  938. }
  939. if (!this.show) {
  940. return;
  941. }
  942. if (this.length === 0) {
  943. return;
  944. }
  945. const context = frameState.context;
  946. if (this._state === LoadState.NEEDS_LOAD) {
  947. this._state = LoadState.LOADING;
  948. this._instancingSupported = context.instancedArrays;
  949. createModel(this, context);
  950. const that = this;
  951. this._model.readyPromise.catch(function (error) {
  952. that._state = LoadState.FAILED;
  953. that._readyPromise.reject(error);
  954. });
  955. }
  956. const instancingSupported = this._instancingSupported;
  957. const model = this._model;
  958. model.imageBasedLighting = this._imageBasedLighting;
  959. model.showCreditsOnScreen = this.showCreditsOnScreen;
  960. model.splitDirection = this.splitDirection;
  961. model.update(frameState);
  962. if (model.ready && this._state === LoadState.LOADING) {
  963. this._state = LoadState.LOADED;
  964. this._ready = true;
  965. // Expand bounding volume to fit the radius of the loaded model including the model's offset from the center
  966. const modelRadius =
  967. model.boundingSphere.radius +
  968. Cartesian3.magnitude(model.boundingSphere.center);
  969. this._boundingSphere.radius += modelRadius;
  970. this._modelCommands = getModelCommands(model);
  971. generateModelCommands(this, instancingSupported);
  972. this._readyPromise.resolve(this);
  973. return;
  974. }
  975. if (this._state !== LoadState.LOADED) {
  976. return;
  977. }
  978. const modeChanged = frameState.mode !== this._mode;
  979. const modelMatrix = this.modelMatrix;
  980. const modelMatrixChanged = !Matrix4.equals(this._modelMatrix, modelMatrix);
  981. if (modeChanged || modelMatrixChanged) {
  982. this._mode = frameState.mode;
  983. Matrix4.clone(modelMatrix, this._modelMatrix);
  984. let rtcTransform = Matrix4.multiplyByTranslation(
  985. this._modelMatrix,
  986. this._center,
  987. this._rtcTransform
  988. );
  989. if (this._mode !== SceneMode.SCENE3D) {
  990. rtcTransform = Transforms.basisTo2D(
  991. frameState.mapProjection,
  992. rtcTransform,
  993. rtcTransform
  994. );
  995. }
  996. Matrix4.getTranslation(rtcTransform, this._boundingSphere.center);
  997. }
  998. if (instancingSupported && this._dirty) {
  999. // If at least one instance has moved assume the collection is now dynamic
  1000. this._dynamic = true;
  1001. this._dirty = false;
  1002. // PERFORMANCE_IDEA: only update dirty sub-sections instead of the whole collection
  1003. updateVertexBuffer(this);
  1004. }
  1005. // If the model was set to rebuild shaders during update, rebuild instanced commands.
  1006. const modelCommandsDirty = commandsDirty(model);
  1007. if (modelCommandsDirty) {
  1008. generateModelCommands(this, instancingSupported);
  1009. }
  1010. // If any node changes due to an animation, update the commands. This could be inefficient if the model is
  1011. // composed of many nodes and only one changes, however it is probably fine in the general use case.
  1012. // Only applies when instancing is disabled. The instanced shader automatically handles node transformations.
  1013. if (
  1014. !instancingSupported &&
  1015. (model.dirty || this._dirty || modeChanged || modelMatrixChanged)
  1016. ) {
  1017. updateCommandsNonInstanced(this);
  1018. }
  1019. updateShadows(this, modelCommandsDirty);
  1020. updateWireframe(this, modelCommandsDirty);
  1021. updateBackFaceCulling(this, modelCommandsDirty);
  1022. updateShowBoundingVolume(this, modelCommandsDirty);
  1023. const passes = frameState.passes;
  1024. if (!passes.render && !passes.pick) {
  1025. return;
  1026. }
  1027. const commandList = frameState.commandList;
  1028. const commands = this._drawCommands;
  1029. const commandsLength = commands.length;
  1030. for (let i = 0; i < commandsLength; ++i) {
  1031. commandList.push(commands[i]);
  1032. }
  1033. };
  1034. ModelInstanceCollection.prototype.isDestroyed = function () {
  1035. return false;
  1036. };
  1037. ModelInstanceCollection.prototype.destroy = function () {
  1038. this._model = this._model && this._model.destroy();
  1039. const pickIds = this._pickIds;
  1040. if (defined(pickIds)) {
  1041. const length = pickIds.length;
  1042. for (let i = 0; i < length; ++i) {
  1043. pickIds[i].destroy();
  1044. }
  1045. }
  1046. if (
  1047. this._shouldDestroyImageBasedLighting &&
  1048. !this._imageBasedLighting.isDestroyed()
  1049. ) {
  1050. this._imageBasedLighting.destroy();
  1051. }
  1052. this._imageBasedLighting = undefined;
  1053. return destroyObject(this);
  1054. };
  1055. export default ModelInstanceCollection;