ModelInstanceCollection.js 39 KB

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