ClassificationModel.js 35 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198
  1. import arraySlice from "../Core/arraySlice.js";
  2. import BoundingSphere from "../Core/BoundingSphere.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Cartesian4 from "../Core/Cartesian4.js";
  5. import Color from "../Core/Color.js";
  6. import combine from "../Core/combine.js";
  7. import ComponentDatatype from "../Core/ComponentDatatype.js";
  8. import defaultValue from "../Core/defaultValue.js";
  9. import defer from "../Core/defer.js";
  10. import defined from "../Core/defined.js";
  11. import destroyObject from "../Core/destroyObject.js";
  12. import DeveloperError from "../Core/DeveloperError.js";
  13. import FeatureDetection from "../Core/FeatureDetection.js";
  14. import IndexDatatype from "../Core/IndexDatatype.js";
  15. import Matrix4 from "../Core/Matrix4.js";
  16. import PrimitiveType from "../Core/PrimitiveType.js";
  17. import RuntimeError from "../Core/RuntimeError.js";
  18. import Transforms from "../Core/Transforms.js";
  19. import WebGLConstants from "../Core/WebGLConstants.js";
  20. import addDefaults from "./GltfPipeline/addDefaults.js";
  21. import ForEach from "./GltfPipeline/ForEach.js";
  22. import getAccessorByteStride from "./GltfPipeline/getAccessorByteStride.js";
  23. import numberOfComponentsForType from "./GltfPipeline/numberOfComponentsForType.js";
  24. import parseGlb from "./GltfPipeline/parseGlb.js";
  25. import updateVersion from "./GltfPipeline/updateVersion.js";
  26. import Axis from "./Axis.js";
  27. import ModelLoadResources from "./ModelLoadResources.js";
  28. import ModelUtility from "./ModelUtility.js";
  29. import processModelMaterialsCommon from "./processModelMaterialsCommon.js";
  30. import processPbrMaterials from "./processPbrMaterials.js";
  31. import SceneMode from "./SceneMode.js";
  32. import Vector3DTileBatch from "./Vector3DTileBatch.js";
  33. import Vector3DTilePrimitive from "./Vector3DTilePrimitive.js";
  34. const boundingSphereCartesian3Scratch = new Cartesian3();
  35. const ModelState = ModelUtility.ModelState;
  36. ///////////////////////////////////////////////////////////////////////////
  37. /**
  38. * A 3D model for classifying other 3D assets based on glTF, the runtime 3D asset format.
  39. * This is a special case when a model of a 3D tileset becomes a classifier when setting {@link Cesium3DTileset#classificationType}.
  40. *
  41. * @alias ClassificationModel
  42. * @constructor
  43. *
  44. * @private
  45. *
  46. * @param {Object} options Object with the following properties:
  47. * @param {ArrayBuffer|Uint8Array} options.gltf A binary glTF buffer.
  48. * @param {Boolean} [options.show=true] Determines if the model primitive will be shown.
  49. * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the model from model to world coordinates.
  50. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for each draw command in the model.
  51. * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the model in wireframe.
  52. * @param {ClassificationType} [options.classificationType] What this model will classify.
  53. *
  54. * @exception {RuntimeError} Only binary glTF is supported.
  55. * @exception {RuntimeError} Buffer data must be embedded in the binary glTF.
  56. * @exception {RuntimeError} Only one node is supported for classification and it must have a mesh.
  57. * @exception {RuntimeError} Only one mesh is supported when using b3dm for classification.
  58. * @exception {RuntimeError} Only one primitive per mesh is supported when using b3dm for classification.
  59. * @exception {RuntimeError} The mesh must have a position attribute.
  60. * @exception {RuntimeError} The mesh must have a batch id attribute.
  61. */
  62. function ClassificationModel(options) {
  63. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  64. let gltf = options.gltf;
  65. if (gltf instanceof ArrayBuffer) {
  66. gltf = new Uint8Array(gltf);
  67. }
  68. if (gltf instanceof Uint8Array) {
  69. // Parse and update binary glTF
  70. gltf = parseGlb(gltf);
  71. updateVersion(gltf);
  72. addDefaults(gltf);
  73. processModelMaterialsCommon(gltf);
  74. processPbrMaterials(gltf);
  75. } else {
  76. throw new RuntimeError("Only binary glTF is supported as a classifier.");
  77. }
  78. ForEach.buffer(gltf, function (buffer) {
  79. if (!defined(buffer.extras._pipeline.source)) {
  80. throw new RuntimeError(
  81. "Buffer data must be embedded in the binary gltf."
  82. );
  83. }
  84. });
  85. const gltfNodes = gltf.nodes;
  86. const gltfMeshes = gltf.meshes;
  87. const gltfNode = gltfNodes[0];
  88. const meshId = gltfNode.mesh;
  89. if (gltfNodes.length !== 1 || !defined(meshId)) {
  90. throw new RuntimeError(
  91. "Only one node is supported for classification and it must have a mesh."
  92. );
  93. }
  94. if (gltfMeshes.length !== 1) {
  95. throw new RuntimeError(
  96. "Only one mesh is supported when using b3dm for classification."
  97. );
  98. }
  99. const gltfPrimitives = gltfMeshes[0].primitives;
  100. if (gltfPrimitives.length !== 1) {
  101. throw new RuntimeError(
  102. "Only one primitive per mesh is supported when using b3dm for classification."
  103. );
  104. }
  105. const gltfPositionAttribute = gltfPrimitives[0].attributes.POSITION;
  106. if (!defined(gltfPositionAttribute)) {
  107. throw new RuntimeError("The mesh must have a position attribute.");
  108. }
  109. const gltfBatchIdAttribute = gltfPrimitives[0].attributes._BATCHID;
  110. if (!defined(gltfBatchIdAttribute)) {
  111. throw new RuntimeError("The mesh must have a batch id attribute.");
  112. }
  113. this._gltf = gltf;
  114. /**
  115. * Determines if the model primitive will be shown.
  116. *
  117. * @type {Boolean}
  118. *
  119. * @default true
  120. */
  121. this.show = defaultValue(options.show, true);
  122. /**
  123. * The 4x4 transformation matrix that transforms the model from model to world coordinates.
  124. * When this is the identity matrix, the model is drawn in world coordinates, i.e., Earth's WGS84 coordinates.
  125. * Local reference frames can be used by providing a different transformation matrix, like that returned
  126. * by {@link Transforms.eastNorthUpToFixedFrame}.
  127. *
  128. * @type {Matrix4}
  129. *
  130. * @default {@link Matrix4.IDENTITY}
  131. *
  132. * @example
  133. * const origin = Cesium.Cartesian3.fromDegrees(-95.0, 40.0, 200000.0);
  134. * m.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin);
  135. */
  136. this.modelMatrix = Matrix4.clone(
  137. defaultValue(options.modelMatrix, Matrix4.IDENTITY)
  138. );
  139. this._modelMatrix = Matrix4.clone(this.modelMatrix);
  140. this._ready = false;
  141. this._readyPromise = defer();
  142. /**
  143. * This property is for debugging only; it is not for production use nor is it optimized.
  144. * <p>
  145. * Draws the bounding sphere for each draw command in the model. A glTF primitive corresponds
  146. * to one draw command. A glTF mesh has an array of primitives, often of length one.
  147. * </p>
  148. *
  149. * @type {Boolean}
  150. *
  151. * @default false
  152. */
  153. this.debugShowBoundingVolume = defaultValue(
  154. options.debugShowBoundingVolume,
  155. false
  156. );
  157. this._debugShowBoundingVolume = false;
  158. /**
  159. * This property is for debugging only; it is not for production use nor is it optimized.
  160. * <p>
  161. * Draws the model in wireframe.
  162. * </p>
  163. *
  164. * @type {Boolean}
  165. *
  166. * @default false
  167. */
  168. this.debugWireframe = defaultValue(options.debugWireframe, false);
  169. this._debugWireframe = false;
  170. this._classificationType = options.classificationType;
  171. // Undocumented options
  172. this._vertexShaderLoaded = options.vertexShaderLoaded;
  173. this._classificationShaderLoaded = options.classificationShaderLoaded;
  174. this._uniformMapLoaded = options.uniformMapLoaded;
  175. this._pickIdLoaded = options.pickIdLoaded;
  176. this._ignoreCommands = defaultValue(options.ignoreCommands, false);
  177. this._upAxis = defaultValue(options.upAxis, Axis.Y);
  178. this._batchTable = options.batchTable;
  179. this._computedModelMatrix = new Matrix4(); // Derived from modelMatrix and axis
  180. this._initialRadius = undefined; // Radius without model's scale property, model-matrix scale, animations, or skins
  181. this._boundingSphere = undefined;
  182. this._scaledBoundingSphere = new BoundingSphere();
  183. this._state = ModelState.NEEDS_LOAD;
  184. this._loadResources = undefined;
  185. this._mode = undefined;
  186. this._dirty = false; // true when the model was transformed this frame
  187. this._nodeMatrix = new Matrix4();
  188. this._primitive = undefined;
  189. this._extensionsUsed = undefined; // Cached used glTF extensions
  190. this._extensionsRequired = undefined; // Cached required glTF extensions
  191. this._quantizedUniforms = undefined; // Quantized uniforms for WEB3D_quantized_attributes
  192. this._buffers = {};
  193. this._vertexArray = undefined;
  194. this._shaderProgram = undefined;
  195. this._uniformMap = undefined;
  196. this._geometryByteLength = 0;
  197. this._trianglesLength = 0;
  198. // CESIUM_RTC extension
  199. this._rtcCenter = undefined; // reference to either 3D or 2D
  200. this._rtcCenterEye = undefined; // in eye coordinates
  201. this._rtcCenter3D = undefined; // in world coordinates
  202. this._rtcCenter2D = undefined; // in projected world coordinates
  203. }
  204. Object.defineProperties(ClassificationModel.prototype, {
  205. /**
  206. * The object for the glTF JSON, including properties with default values omitted
  207. * from the JSON provided to this model.
  208. *
  209. * @memberof ClassificationModel.prototype
  210. *
  211. * @type {Object}
  212. * @readonly
  213. *
  214. * @default undefined
  215. */
  216. gltf: {
  217. get: function () {
  218. return this._gltf;
  219. },
  220. },
  221. /**
  222. * The model's bounding sphere in its local coordinate system.
  223. *
  224. * @memberof ClassificationModel.prototype
  225. *
  226. * @type {BoundingSphere}
  227. * @readonly
  228. *
  229. * @default undefined
  230. *
  231. * @exception {DeveloperError} The model is not loaded. Use ClassificationModel.readyPromise or wait for ClassificationModel.ready to be true.
  232. *
  233. * @example
  234. * // Center in WGS84 coordinates
  235. * const center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
  236. */
  237. boundingSphere: {
  238. get: function () {
  239. //>>includeStart('debug', pragmas.debug);
  240. if (this._state !== ModelState.LOADED) {
  241. throw new DeveloperError(
  242. "The model is not loaded. Use ClassificationModel.readyPromise or wait for ClassificationModel.ready to be true."
  243. );
  244. }
  245. //>>includeEnd('debug');
  246. const modelMatrix = this.modelMatrix;
  247. const nonUniformScale = Matrix4.getScale(
  248. modelMatrix,
  249. boundingSphereCartesian3Scratch
  250. );
  251. const scaledBoundingSphere = this._scaledBoundingSphere;
  252. scaledBoundingSphere.center = Cartesian3.multiplyComponents(
  253. this._boundingSphere.center,
  254. nonUniformScale,
  255. scaledBoundingSphere.center
  256. );
  257. scaledBoundingSphere.radius =
  258. Cartesian3.maximumComponent(nonUniformScale) * this._initialRadius;
  259. if (defined(this._rtcCenter)) {
  260. Cartesian3.add(
  261. this._rtcCenter,
  262. scaledBoundingSphere.center,
  263. scaledBoundingSphere.center
  264. );
  265. }
  266. return scaledBoundingSphere;
  267. },
  268. },
  269. /**
  270. * When <code>true</code>, this model is ready to render, i.e., the external binary, image,
  271. * and shader files were downloaded and the WebGL resources were created. This is set to
  272. * <code>true</code> right before {@link ClassificationModel#readyPromise} is resolved.
  273. *
  274. * @memberof ClassificationModel.prototype
  275. *
  276. * @type {Boolean}
  277. * @readonly
  278. *
  279. * @default false
  280. */
  281. ready: {
  282. get: function () {
  283. return this._ready;
  284. },
  285. },
  286. /**
  287. * Gets the promise that will be resolved when this model is ready to render, i.e., when the external binary, image,
  288. * and shader files were downloaded and the WebGL resources were created.
  289. * <p>
  290. * This promise is resolved at the end of the frame before the first frame the model is rendered in.
  291. * </p>
  292. *
  293. * @memberof ClassificationModel.prototype
  294. * @type {Promise.<ClassificationModel>}
  295. * @readonly
  296. *
  297. * @see ClassificationModel#ready
  298. */
  299. readyPromise: {
  300. get: function () {
  301. return this._readyPromise.promise;
  302. },
  303. },
  304. /**
  305. * Returns true if the model was transformed this frame
  306. *
  307. * @memberof ClassificationModel.prototype
  308. *
  309. * @type {Boolean}
  310. * @readonly
  311. *
  312. * @private
  313. */
  314. dirty: {
  315. get: function () {
  316. return this._dirty;
  317. },
  318. },
  319. /**
  320. * Returns an object with all of the glTF extensions used.
  321. *
  322. * @memberof ClassificationModel.prototype
  323. *
  324. * @type {Object}
  325. * @readonly
  326. */
  327. extensionsUsed: {
  328. get: function () {
  329. if (!defined(this._extensionsUsed)) {
  330. this._extensionsUsed = ModelUtility.getUsedExtensions(this.gltf);
  331. }
  332. return this._extensionsUsed;
  333. },
  334. },
  335. /**
  336. * Returns an object with all of the glTF extensions required.
  337. *
  338. * @memberof ClassificationModel.prototype
  339. *
  340. * @type {Object}
  341. * @readonly
  342. */
  343. extensionsRequired: {
  344. get: function () {
  345. if (!defined(this._extensionsRequired)) {
  346. this._extensionsRequired = ModelUtility.getRequiredExtensions(
  347. this.gltf
  348. );
  349. }
  350. return this._extensionsRequired;
  351. },
  352. },
  353. /**
  354. * Gets the model's up-axis.
  355. * By default models are y-up according to the glTF spec, however geo-referenced models will typically be z-up.
  356. *
  357. * @memberof ClassificationModel.prototype
  358. *
  359. * @type {Number}
  360. * @default Axis.Y
  361. * @readonly
  362. *
  363. * @private
  364. */
  365. upAxis: {
  366. get: function () {
  367. return this._upAxis;
  368. },
  369. },
  370. /**
  371. * Gets the model's triangle count.
  372. *
  373. * @private
  374. */
  375. trianglesLength: {
  376. get: function () {
  377. return this._trianglesLength;
  378. },
  379. },
  380. /**
  381. * Gets the model's geometry memory in bytes. This includes all vertex and index buffers.
  382. *
  383. * @private
  384. */
  385. geometryByteLength: {
  386. get: function () {
  387. return this._geometryByteLength;
  388. },
  389. },
  390. /**
  391. * Gets the model's texture memory in bytes.
  392. *
  393. * @private
  394. */
  395. texturesByteLength: {
  396. get: function () {
  397. return 0;
  398. },
  399. },
  400. /**
  401. * Gets the model's classification type.
  402. * @memberof ClassificationModel.prototype
  403. * @type {ClassificationType}
  404. */
  405. classificationType: {
  406. get: function () {
  407. return this._classificationType;
  408. },
  409. },
  410. });
  411. ///////////////////////////////////////////////////////////////////////////
  412. function addBuffersToLoadResources(model) {
  413. const gltf = model.gltf;
  414. const loadResources = model._loadResources;
  415. ForEach.buffer(gltf, function (buffer, id) {
  416. loadResources.buffers[id] = buffer.extras._pipeline.source;
  417. });
  418. }
  419. function parseBufferViews(model) {
  420. const bufferViews = model.gltf.bufferViews;
  421. const vertexBuffersToCreate = model._loadResources.vertexBuffersToCreate;
  422. // Only ARRAY_BUFFER here. ELEMENT_ARRAY_BUFFER created below.
  423. ForEach.bufferView(model.gltf, function (bufferView, id) {
  424. if (bufferView.target === WebGLConstants.ARRAY_BUFFER) {
  425. vertexBuffersToCreate.enqueue(id);
  426. }
  427. });
  428. const indexBuffersToCreate = model._loadResources.indexBuffersToCreate;
  429. const indexBufferIds = {};
  430. // The Cesium Renderer requires knowing the datatype for an index buffer
  431. // at creation type, which is not part of the glTF bufferview so loop
  432. // through glTF accessors to create the bufferview's index buffer.
  433. ForEach.accessor(model.gltf, function (accessor) {
  434. const bufferViewId = accessor.bufferView;
  435. const bufferView = bufferViews[bufferViewId];
  436. if (
  437. bufferView.target === WebGLConstants.ELEMENT_ARRAY_BUFFER &&
  438. !defined(indexBufferIds[bufferViewId])
  439. ) {
  440. indexBufferIds[bufferViewId] = true;
  441. indexBuffersToCreate.enqueue({
  442. id: bufferViewId,
  443. componentType: accessor.componentType,
  444. });
  445. }
  446. });
  447. }
  448. function createVertexBuffer(bufferViewId, model) {
  449. const loadResources = model._loadResources;
  450. const bufferViews = model.gltf.bufferViews;
  451. const bufferView = bufferViews[bufferViewId];
  452. const vertexBuffer = loadResources.getBuffer(bufferView);
  453. model._buffers[bufferViewId] = vertexBuffer;
  454. model._geometryByteLength += vertexBuffer.byteLength;
  455. }
  456. function createIndexBuffer(bufferViewId, componentType, model) {
  457. const loadResources = model._loadResources;
  458. const bufferViews = model.gltf.bufferViews;
  459. const bufferView = bufferViews[bufferViewId];
  460. const indexBuffer = {
  461. typedArray: loadResources.getBuffer(bufferView),
  462. indexDatatype: componentType,
  463. };
  464. model._buffers[bufferViewId] = indexBuffer;
  465. model._geometryByteLength += indexBuffer.typedArray.byteLength;
  466. }
  467. function createBuffers(model) {
  468. const loadResources = model._loadResources;
  469. if (loadResources.pendingBufferLoads !== 0) {
  470. return;
  471. }
  472. const vertexBuffersToCreate = loadResources.vertexBuffersToCreate;
  473. const indexBuffersToCreate = loadResources.indexBuffersToCreate;
  474. while (vertexBuffersToCreate.length > 0) {
  475. createVertexBuffer(vertexBuffersToCreate.dequeue(), model);
  476. }
  477. while (indexBuffersToCreate.length > 0) {
  478. const i = indexBuffersToCreate.dequeue();
  479. createIndexBuffer(i.id, i.componentType, model);
  480. }
  481. }
  482. function modifyShaderForQuantizedAttributes(shader, model) {
  483. const primitive = model.gltf.meshes[0].primitives[0];
  484. const result = ModelUtility.modifyShaderForQuantizedAttributes(
  485. model.gltf,
  486. primitive,
  487. shader
  488. );
  489. model._quantizedUniforms = result.uniforms;
  490. return result.shader;
  491. }
  492. function modifyShader(shader, callback) {
  493. if (defined(callback)) {
  494. shader = callback(shader);
  495. }
  496. return shader;
  497. }
  498. function createProgram(model) {
  499. const gltf = model.gltf;
  500. const positionName = ModelUtility.getAttributeOrUniformBySemantic(
  501. gltf,
  502. "POSITION"
  503. );
  504. const batchIdName = ModelUtility.getAttributeOrUniformBySemantic(
  505. gltf,
  506. "_BATCHID"
  507. );
  508. const attributeLocations = {};
  509. attributeLocations[positionName] = 0;
  510. attributeLocations[batchIdName] = 1;
  511. const modelViewProjectionName = ModelUtility.getAttributeOrUniformBySemantic(
  512. gltf,
  513. "MODELVIEWPROJECTION"
  514. );
  515. let uniformDecl;
  516. let toClip;
  517. if (!defined(modelViewProjectionName)) {
  518. const projectionName = ModelUtility.getAttributeOrUniformBySemantic(
  519. gltf,
  520. "PROJECTION"
  521. );
  522. let modelViewName = ModelUtility.getAttributeOrUniformBySemantic(
  523. gltf,
  524. "MODELVIEW"
  525. );
  526. if (!defined(modelViewName)) {
  527. modelViewName = ModelUtility.getAttributeOrUniformBySemantic(
  528. gltf,
  529. "CESIUM_RTC_MODELVIEW"
  530. );
  531. }
  532. uniformDecl =
  533. `uniform mat4 ${modelViewName};\n` + `uniform mat4 ${projectionName};\n`;
  534. toClip = `${projectionName} * ${modelViewName} * vec4(${positionName}, 1.0)`;
  535. } else {
  536. uniformDecl = `uniform mat4 ${modelViewProjectionName};\n`;
  537. toClip = `${modelViewProjectionName} * vec4(${positionName}, 1.0)`;
  538. }
  539. const computePosition = ` vec4 positionInClipCoords = ${toClip};\n`;
  540. let vs =
  541. `attribute vec3 ${positionName};\n` +
  542. `attribute float ${batchIdName};\n${uniformDecl}void main() {\n${computePosition} gl_Position = czm_depthClamp(positionInClipCoords);\n` +
  543. `}\n`;
  544. const fs =
  545. "#ifdef GL_EXT_frag_depth\n" +
  546. "#extension GL_EXT_frag_depth : enable\n" +
  547. "#endif\n" +
  548. "void main() \n" +
  549. "{ \n" +
  550. " gl_FragColor = vec4(1.0); \n" +
  551. " czm_writeDepthClamp();\n" +
  552. "}\n";
  553. if (model.extensionsUsed.WEB3D_quantized_attributes) {
  554. vs = modifyShaderForQuantizedAttributes(vs, model);
  555. }
  556. const drawVS = modifyShader(vs, model._vertexShaderLoaded);
  557. const drawFS = modifyShader(fs, model._classificationShaderLoaded);
  558. model._shaderProgram = {
  559. vertexShaderSource: drawVS,
  560. fragmentShaderSource: drawFS,
  561. attributeLocations: attributeLocations,
  562. };
  563. }
  564. function getAttributeLocations() {
  565. return {
  566. POSITION: 0,
  567. _BATCHID: 1,
  568. };
  569. }
  570. function createVertexArray(model) {
  571. const loadResources = model._loadResources;
  572. if (!loadResources.finishedBuffersCreation() || defined(model._vertexArray)) {
  573. return;
  574. }
  575. const rendererBuffers = model._buffers;
  576. const gltf = model.gltf;
  577. const accessors = gltf.accessors;
  578. const meshes = gltf.meshes;
  579. const primitives = meshes[0].primitives;
  580. const primitive = primitives[0];
  581. const attributeLocations = getAttributeLocations();
  582. const attributes = {};
  583. ForEach.meshPrimitiveAttribute(primitive, function (
  584. accessorId,
  585. attributeName
  586. ) {
  587. // Skip if the attribute is not used by the material, e.g., because the asset
  588. // was exported with an attribute that wasn't used and the asset wasn't optimized.
  589. const attributeLocation = attributeLocations[attributeName];
  590. if (defined(attributeLocation)) {
  591. const a = accessors[accessorId];
  592. attributes[attributeName] = {
  593. index: attributeLocation,
  594. vertexBuffer: rendererBuffers[a.bufferView],
  595. componentsPerAttribute: numberOfComponentsForType(a.type),
  596. componentDatatype: a.componentType,
  597. offsetInBytes: a.byteOffset,
  598. strideInBytes: getAccessorByteStride(gltf, a),
  599. };
  600. }
  601. });
  602. let indexBuffer;
  603. if (defined(primitive.indices)) {
  604. const accessor = accessors[primitive.indices];
  605. indexBuffer = rendererBuffers[accessor.bufferView];
  606. }
  607. model._vertexArray = {
  608. attributes: attributes,
  609. indexBuffer: indexBuffer,
  610. };
  611. }
  612. const gltfSemanticUniforms = {
  613. PROJECTION: function (uniformState, model) {
  614. return ModelUtility.getGltfSemanticUniforms().PROJECTION(
  615. uniformState,
  616. model
  617. );
  618. },
  619. MODELVIEW: function (uniformState, model) {
  620. return ModelUtility.getGltfSemanticUniforms().MODELVIEW(
  621. uniformState,
  622. model
  623. );
  624. },
  625. CESIUM_RTC_MODELVIEW: function (uniformState, model) {
  626. return ModelUtility.getGltfSemanticUniforms().CESIUM_RTC_MODELVIEW(
  627. uniformState,
  628. model
  629. );
  630. },
  631. MODELVIEWPROJECTION: function (uniformState, model) {
  632. return ModelUtility.getGltfSemanticUniforms().MODELVIEWPROJECTION(
  633. uniformState,
  634. model
  635. );
  636. },
  637. };
  638. function createUniformMap(model, context) {
  639. if (defined(model._uniformMap)) {
  640. return;
  641. }
  642. const uniformMap = {};
  643. ForEach.technique(model.gltf, function (technique) {
  644. ForEach.techniqueUniform(technique, function (uniform, uniformName) {
  645. if (
  646. !defined(uniform.semantic) ||
  647. !defined(gltfSemanticUniforms[uniform.semantic])
  648. ) {
  649. return;
  650. }
  651. uniformMap[uniformName] = gltfSemanticUniforms[uniform.semantic](
  652. context.uniformState,
  653. model
  654. );
  655. });
  656. });
  657. model._uniformMap = uniformMap;
  658. }
  659. function createUniformsForQuantizedAttributes(model, primitive) {
  660. return ModelUtility.createUniformsForQuantizedAttributes(
  661. model.gltf,
  662. primitive,
  663. model._quantizedUniforms
  664. );
  665. }
  666. function triangleCountFromPrimitiveIndices(primitive, indicesCount) {
  667. switch (primitive.mode) {
  668. case PrimitiveType.TRIANGLES:
  669. return indicesCount / 3;
  670. case PrimitiveType.TRIANGLE_STRIP:
  671. case PrimitiveType.TRIANGLE_FAN:
  672. return Math.max(indicesCount - 2, 0);
  673. default:
  674. return 0;
  675. }
  676. }
  677. function createPrimitive(model) {
  678. const batchTable = model._batchTable;
  679. let uniformMap = model._uniformMap;
  680. const vertexArray = model._vertexArray;
  681. const gltf = model.gltf;
  682. const accessors = gltf.accessors;
  683. const gltfMeshes = gltf.meshes;
  684. const primitive = gltfMeshes[0].primitives[0];
  685. const ix = accessors[primitive.indices];
  686. const positionAccessor = primitive.attributes.POSITION;
  687. const minMax = ModelUtility.getAccessorMinMax(gltf, positionAccessor);
  688. const boundingSphere = BoundingSphere.fromCornerPoints(
  689. Cartesian3.fromArray(minMax.min),
  690. Cartesian3.fromArray(minMax.max)
  691. );
  692. let offset;
  693. let count;
  694. if (defined(ix)) {
  695. count = ix.count;
  696. offset = ix.byteOffset / IndexDatatype.getSizeInBytes(ix.componentType); // glTF has offset in bytes. Cesium has offsets in indices
  697. } else {
  698. const positions = accessors[primitive.attributes.POSITION];
  699. count = positions.count;
  700. offset = 0;
  701. }
  702. // Update model triangle count using number of indices
  703. model._trianglesLength += triangleCountFromPrimitiveIndices(primitive, count);
  704. // Allow callback to modify the uniformMap
  705. if (defined(model._uniformMapLoaded)) {
  706. uniformMap = model._uniformMapLoaded(uniformMap);
  707. }
  708. // Add uniforms for decoding quantized attributes if used
  709. if (model.extensionsUsed.WEB3D_quantized_attributes) {
  710. const quantizedUniformMap = createUniformsForQuantizedAttributes(
  711. model,
  712. primitive
  713. );
  714. uniformMap = combine(uniformMap, quantizedUniformMap);
  715. }
  716. let attribute = vertexArray.attributes.POSITION;
  717. let componentDatatype = attribute.componentDatatype;
  718. let typedArray = attribute.vertexBuffer;
  719. let byteOffset = typedArray.byteOffset;
  720. let bufferLength =
  721. typedArray.byteLength / ComponentDatatype.getSizeInBytes(componentDatatype);
  722. let positionsBuffer = ComponentDatatype.createArrayBufferView(
  723. componentDatatype,
  724. typedArray.buffer,
  725. byteOffset,
  726. bufferLength
  727. );
  728. attribute = vertexArray.attributes._BATCHID;
  729. componentDatatype = attribute.componentDatatype;
  730. typedArray = attribute.vertexBuffer;
  731. byteOffset = typedArray.byteOffset;
  732. bufferLength =
  733. typedArray.byteLength / ComponentDatatype.getSizeInBytes(componentDatatype);
  734. let vertexBatchIds = ComponentDatatype.createArrayBufferView(
  735. componentDatatype,
  736. typedArray.buffer,
  737. byteOffset,
  738. bufferLength
  739. );
  740. const buffer = vertexArray.indexBuffer.typedArray;
  741. let indices;
  742. if (vertexArray.indexBuffer.indexDatatype === IndexDatatype.UNSIGNED_SHORT) {
  743. indices = new Uint16Array(
  744. buffer.buffer,
  745. buffer.byteOffset,
  746. buffer.byteLength / Uint16Array.BYTES_PER_ELEMENT
  747. );
  748. } else {
  749. indices = new Uint32Array(
  750. buffer.buffer,
  751. buffer.byteOffset,
  752. buffer.byteLength / Uint32Array.BYTES_PER_ELEMENT
  753. );
  754. }
  755. positionsBuffer = arraySlice(positionsBuffer);
  756. vertexBatchIds = arraySlice(vertexBatchIds);
  757. indices = arraySlice(indices, offset, offset + count);
  758. const batchIds = [];
  759. const indexCounts = [];
  760. const indexOffsets = [];
  761. const batchedIndices = [];
  762. let currentId = vertexBatchIds[indices[0]];
  763. batchIds.push(currentId);
  764. indexOffsets.push(0);
  765. let batchId;
  766. let indexOffset;
  767. let indexCount;
  768. const indicesLength = indices.length;
  769. for (let j = 1; j < indicesLength; ++j) {
  770. batchId = vertexBatchIds[indices[j]];
  771. if (batchId !== currentId) {
  772. indexOffset = indexOffsets[indexOffsets.length - 1];
  773. indexCount = j - indexOffset;
  774. batchIds.push(batchId);
  775. indexCounts.push(indexCount);
  776. indexOffsets.push(j);
  777. batchedIndices.push(
  778. new Vector3DTileBatch({
  779. offset: indexOffset,
  780. count: indexCount,
  781. batchIds: [currentId],
  782. color: Color.WHITE,
  783. })
  784. );
  785. currentId = batchId;
  786. }
  787. }
  788. indexOffset = indexOffsets[indexOffsets.length - 1];
  789. indexCount = indicesLength - indexOffset;
  790. indexCounts.push(indexCount);
  791. batchedIndices.push(
  792. new Vector3DTileBatch({
  793. offset: indexOffset,
  794. count: indexCount,
  795. batchIds: [currentId],
  796. color: Color.WHITE,
  797. })
  798. );
  799. const shader = model._shaderProgram;
  800. const vertexShaderSource = shader.vertexShaderSource;
  801. const fragmentShaderSource = shader.fragmentShaderSource;
  802. const attributeLocations = shader.attributeLocations;
  803. const pickId = defined(model._pickIdLoaded)
  804. ? model._pickIdLoaded()
  805. : undefined;
  806. model._primitive = new Vector3DTilePrimitive({
  807. classificationType: model._classificationType,
  808. positions: positionsBuffer,
  809. indices: indices,
  810. indexOffsets: indexOffsets,
  811. indexCounts: indexCounts,
  812. batchIds: batchIds,
  813. vertexBatchIds: vertexBatchIds,
  814. batchedIndices: batchedIndices,
  815. batchTable: batchTable,
  816. boundingVolume: new BoundingSphere(), // updated in update()
  817. _vertexShaderSource: vertexShaderSource,
  818. _fragmentShaderSource: fragmentShaderSource,
  819. _attributeLocations: attributeLocations,
  820. _uniformMap: uniformMap,
  821. _pickId: pickId,
  822. _modelMatrix: new Matrix4(), // updated in update()
  823. _boundingSphere: boundingSphere, // used to update boundingVolume
  824. });
  825. // Release CPU resources
  826. model._buffers = undefined;
  827. model._vertexArray = undefined;
  828. model._shaderProgram = undefined;
  829. model._uniformMap = undefined;
  830. }
  831. function createRuntimeNodes(model) {
  832. const loadResources = model._loadResources;
  833. if (!loadResources.finished()) {
  834. return;
  835. }
  836. if (defined(model._primitive)) {
  837. return;
  838. }
  839. const gltf = model.gltf;
  840. const nodes = gltf.nodes;
  841. const gltfNode = nodes[0];
  842. model._nodeMatrix = ModelUtility.getTransform(gltfNode, model._nodeMatrix);
  843. createPrimitive(model);
  844. }
  845. function createResources(model, frameState) {
  846. const context = frameState.context;
  847. ModelUtility.checkSupportedGlExtensions(model.gltf.glExtensionsUsed, context);
  848. createBuffers(model); // using glTF bufferViews
  849. createProgram(model);
  850. createVertexArray(model); // using glTF meshes
  851. createUniformMap(model, context); // using glTF materials/techniques
  852. createRuntimeNodes(model); // using glTF scene
  853. }
  854. ///////////////////////////////////////////////////////////////////////////
  855. const scratchComputedTranslation = new Cartesian4();
  856. const scratchComputedMatrixIn2D = new Matrix4();
  857. function updateNodeModelMatrix(
  858. model,
  859. modelTransformChanged,
  860. justLoaded,
  861. projection
  862. ) {
  863. let computedModelMatrix = model._computedModelMatrix;
  864. if (model._mode !== SceneMode.SCENE3D && !model._ignoreCommands) {
  865. const translation = Matrix4.getColumn(
  866. computedModelMatrix,
  867. 3,
  868. scratchComputedTranslation
  869. );
  870. if (!Cartesian4.equals(translation, Cartesian4.UNIT_W)) {
  871. computedModelMatrix = Transforms.basisTo2D(
  872. projection,
  873. computedModelMatrix,
  874. scratchComputedMatrixIn2D
  875. );
  876. model._rtcCenter = model._rtcCenter3D;
  877. } else {
  878. const center = model.boundingSphere.center;
  879. const to2D = Transforms.wgs84To2DModelMatrix(
  880. projection,
  881. center,
  882. scratchComputedMatrixIn2D
  883. );
  884. computedModelMatrix = Matrix4.multiply(
  885. to2D,
  886. computedModelMatrix,
  887. scratchComputedMatrixIn2D
  888. );
  889. if (defined(model._rtcCenter)) {
  890. Matrix4.setTranslation(
  891. computedModelMatrix,
  892. Cartesian4.UNIT_W,
  893. computedModelMatrix
  894. );
  895. model._rtcCenter = model._rtcCenter2D;
  896. }
  897. }
  898. }
  899. const primitive = model._primitive;
  900. if (modelTransformChanged || justLoaded) {
  901. Matrix4.multiplyTransformation(
  902. computedModelMatrix,
  903. model._nodeMatrix,
  904. primitive._modelMatrix
  905. );
  906. BoundingSphere.transform(
  907. primitive._boundingSphere,
  908. primitive._modelMatrix,
  909. primitive._boundingVolume
  910. );
  911. if (defined(model._rtcCenter)) {
  912. Cartesian3.add(
  913. model._rtcCenter,
  914. primitive._boundingVolume.center,
  915. primitive._boundingVolume.center
  916. );
  917. }
  918. }
  919. }
  920. ///////////////////////////////////////////////////////////////////////////
  921. ClassificationModel.prototype.updateCommands = function (batchId, color) {
  922. this._primitive.updateCommands(batchId, color);
  923. };
  924. ClassificationModel.prototype.update = function (frameState) {
  925. if (frameState.mode === SceneMode.MORPHING) {
  926. return;
  927. }
  928. if (!FeatureDetection.supportsWebP.initialized) {
  929. FeatureDetection.supportsWebP.initialize();
  930. return;
  931. }
  932. const supportsWebP = FeatureDetection.supportsWebP();
  933. if (this._state === ModelState.NEEDS_LOAD && defined(this.gltf)) {
  934. this._state = ModelState.LOADING;
  935. if (this._state !== ModelState.FAILED) {
  936. const extensions = this.gltf.extensions;
  937. if (defined(extensions) && defined(extensions.CESIUM_RTC)) {
  938. const center = Cartesian3.fromArray(extensions.CESIUM_RTC.center);
  939. if (!Cartesian3.equals(center, Cartesian3.ZERO)) {
  940. this._rtcCenter3D = center;
  941. const projection = frameState.mapProjection;
  942. const ellipsoid = projection.ellipsoid;
  943. const cartographic = ellipsoid.cartesianToCartographic(
  944. this._rtcCenter3D
  945. );
  946. const projectedCart = projection.project(cartographic);
  947. Cartesian3.fromElements(
  948. projectedCart.z,
  949. projectedCart.x,
  950. projectedCart.y,
  951. projectedCart
  952. );
  953. this._rtcCenter2D = projectedCart;
  954. this._rtcCenterEye = new Cartesian3();
  955. this._rtcCenter = this._rtcCenter3D;
  956. }
  957. }
  958. this._loadResources = new ModelLoadResources();
  959. ModelUtility.parseBuffers(this);
  960. }
  961. }
  962. const loadResources = this._loadResources;
  963. let justLoaded = false;
  964. if (this._state === ModelState.LOADING) {
  965. // Transition from LOADING -> LOADED once resources are downloaded and created.
  966. // Textures may continue to stream in while in the LOADED state.
  967. if (loadResources.pendingBufferLoads === 0) {
  968. ModelUtility.checkSupportedExtensions(
  969. this.extensionsRequired,
  970. supportsWebP
  971. );
  972. addBuffersToLoadResources(this);
  973. parseBufferViews(this);
  974. this._boundingSphere = ModelUtility.computeBoundingSphere(this);
  975. this._initialRadius = this._boundingSphere.radius;
  976. createResources(this, frameState);
  977. }
  978. if (loadResources.finished()) {
  979. this._state = ModelState.LOADED;
  980. justLoaded = true;
  981. }
  982. }
  983. if (defined(loadResources) && this._state === ModelState.LOADED) {
  984. if (!justLoaded) {
  985. createResources(this, frameState);
  986. }
  987. if (loadResources.finished()) {
  988. this._loadResources = undefined; // Clear CPU memory since WebGL resources were created.
  989. }
  990. }
  991. const show = this.show;
  992. if ((show && this._state === ModelState.LOADED) || justLoaded) {
  993. this._dirty = false;
  994. const modelMatrix = this.modelMatrix;
  995. const modeChanged = frameState.mode !== this._mode;
  996. this._mode = frameState.mode;
  997. // ClassificationModel's model matrix needs to be updated
  998. const modelTransformChanged =
  999. !Matrix4.equals(this._modelMatrix, modelMatrix) || modeChanged;
  1000. if (modelTransformChanged || justLoaded) {
  1001. Matrix4.clone(modelMatrix, this._modelMatrix);
  1002. const computedModelMatrix = this._computedModelMatrix;
  1003. Matrix4.clone(modelMatrix, computedModelMatrix);
  1004. if (this._upAxis === Axis.Y) {
  1005. Matrix4.multiplyTransformation(
  1006. computedModelMatrix,
  1007. Axis.Y_UP_TO_Z_UP,
  1008. computedModelMatrix
  1009. );
  1010. } else if (this._upAxis === Axis.X) {
  1011. Matrix4.multiplyTransformation(
  1012. computedModelMatrix,
  1013. Axis.X_UP_TO_Z_UP,
  1014. computedModelMatrix
  1015. );
  1016. }
  1017. }
  1018. // Update modelMatrix throughout the graph as needed
  1019. if (modelTransformChanged || justLoaded) {
  1020. updateNodeModelMatrix(
  1021. this,
  1022. modelTransformChanged,
  1023. justLoaded,
  1024. frameState.mapProjection
  1025. );
  1026. this._dirty = true;
  1027. }
  1028. }
  1029. if (justLoaded) {
  1030. // Called after modelMatrix update.
  1031. const model = this;
  1032. frameState.afterRender.push(function () {
  1033. model._ready = true;
  1034. model._readyPromise.resolve(model);
  1035. });
  1036. return;
  1037. }
  1038. if (show && !this._ignoreCommands) {
  1039. this._primitive.debugShowBoundingVolume = this.debugShowBoundingVolume;
  1040. this._primitive.debugWireframe = this.debugWireframe;
  1041. this._primitive.update(frameState);
  1042. }
  1043. };
  1044. ClassificationModel.prototype.isDestroyed = function () {
  1045. return false;
  1046. };
  1047. ClassificationModel.prototype.destroy = function () {
  1048. this._primitive = this._primitive && this._primitive.destroy();
  1049. return destroyObject(this);
  1050. };
  1051. export default ClassificationModel;