ModelInstanceCollection.js 42 KB

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