ClassificationModelDrawCommand.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. import BoundingSphere from "../../Core/BoundingSphere.js";
  2. import Check from "../../Core/Check.js";
  3. import defaultValue from "../../Core/defaultValue.js";
  4. import Matrix4 from "../../Core/Matrix4.js";
  5. import DrawCommand from "../../Renderer/DrawCommand.js";
  6. import Pass from "../../Renderer/Pass.js";
  7. import RenderState from "../../Renderer/RenderState.js";
  8. import BlendingState from "../BlendingState.js";
  9. import ClassificationType from "../ClassificationType.js";
  10. import DepthFunction from "../DepthFunction.js";
  11. import StencilConstants from "../StencilConstants.js";
  12. import StencilFunction from "../StencilFunction.js";
  13. import StencilOperation from "../StencilOperation.js";
  14. /**
  15. * A wrapper around the draw commands used to render a classification model,
  16. * i.e. a {@link Model} that classifies another asset. This manages the
  17. * derived commands and returns only the necessary commands depending on the
  18. * given frame state.
  19. *
  20. * @param {object} options An object containing the following options:
  21. * @param {DrawCommand} options.command The draw command from which to derive other commands from.
  22. * @param {PrimitiveRenderResources} options.primitiveRenderResources The render resources of the primitive associated with the command.
  23. *
  24. * @alias ClassificationModelDrawCommand
  25. * @constructor
  26. *
  27. * @private
  28. */
  29. function ClassificationModelDrawCommand(options) {
  30. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  31. const command = options.command;
  32. const renderResources = options.primitiveRenderResources;
  33. //>>includeStart('debug', pragmas.debug);
  34. Check.typeOf.object("options.command", command);
  35. Check.typeOf.object("options.primitiveRenderResources", renderResources);
  36. //>>includeEnd('debug');
  37. const model = renderResources.model;
  38. this._command = command;
  39. this._model = model;
  40. this._runtimePrimitive = renderResources.runtimePrimitive;
  41. // Classification models aren't supported in 2D mode, so there's no need to
  42. // duplicate the model matrix for each derived command.
  43. this._modelMatrix = command.modelMatrix;
  44. this._boundingVolume = command.boundingVolume;
  45. this._cullFace = command.renderState.cull.face;
  46. const type = model.classificationType;
  47. this._classificationType = type;
  48. // ClassificationType has three values: terrain only, 3D Tiles only, or both.
  49. this._classifiesTerrain = type !== ClassificationType.CESIUM_3D_TILE;
  50. this._classifies3DTiles = type !== ClassificationType.TERRAIN;
  51. this._useDebugWireframe = model._enableDebugWireframe && model.debugWireframe;
  52. this._pickId = renderResources.pickId;
  53. this._commandListTerrain = [];
  54. this._commandList3DTiles = [];
  55. this._commandListIgnoreShow = []; // Used for inverted classification.
  56. this._commandListDebugWireframe = [];
  57. this._commandListTerrainPicking = [];
  58. this._commandList3DTilesPicking = [];
  59. initialize(this);
  60. }
  61. function getStencilDepthRenderState(stencilFunction) {
  62. return {
  63. colorMask: {
  64. red: false,
  65. green: false,
  66. blue: false,
  67. alpha: false,
  68. },
  69. stencilTest: {
  70. enabled: true,
  71. frontFunction: stencilFunction,
  72. frontOperation: {
  73. fail: StencilOperation.KEEP,
  74. zFail: StencilOperation.DECREMENT_WRAP,
  75. zPass: StencilOperation.KEEP,
  76. },
  77. backFunction: stencilFunction,
  78. backOperation: {
  79. fail: StencilOperation.KEEP,
  80. zFail: StencilOperation.INCREMENT_WRAP,
  81. zPass: StencilOperation.KEEP,
  82. },
  83. reference: StencilConstants.CESIUM_3D_TILE_MASK,
  84. mask: StencilConstants.CESIUM_3D_TILE_MASK,
  85. },
  86. stencilMask: StencilConstants.CLASSIFICATION_MASK,
  87. depthTest: {
  88. enabled: true,
  89. func: DepthFunction.LESS_OR_EQUAL,
  90. },
  91. depthMask: false,
  92. };
  93. }
  94. const colorRenderState = {
  95. stencilTest: {
  96. enabled: true,
  97. frontFunction: StencilFunction.NOT_EQUAL,
  98. frontOperation: {
  99. fail: StencilOperation.ZERO,
  100. zFail: StencilOperation.ZERO,
  101. zPass: StencilOperation.ZERO,
  102. },
  103. backFunction: StencilFunction.NOT_EQUAL,
  104. backOperation: {
  105. fail: StencilOperation.ZERO,
  106. zFail: StencilOperation.ZERO,
  107. zPass: StencilOperation.ZERO,
  108. },
  109. reference: 0,
  110. mask: StencilConstants.CLASSIFICATION_MASK,
  111. },
  112. stencilMask: StencilConstants.CLASSIFICATION_MASK,
  113. depthTest: {
  114. enabled: false,
  115. },
  116. depthMask: false,
  117. blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND,
  118. };
  119. const pickRenderState = {
  120. stencilTest: {
  121. enabled: true,
  122. frontFunction: StencilFunction.NOT_EQUAL,
  123. frontOperation: {
  124. fail: StencilOperation.ZERO,
  125. zFail: StencilOperation.ZERO,
  126. zPass: StencilOperation.ZERO,
  127. },
  128. backFunction: StencilFunction.NOT_EQUAL,
  129. backOperation: {
  130. fail: StencilOperation.ZERO,
  131. zFail: StencilOperation.ZERO,
  132. zPass: StencilOperation.ZERO,
  133. },
  134. reference: 0,
  135. mask: StencilConstants.CLASSIFICATION_MASK,
  136. },
  137. stencilMask: StencilConstants.CLASSIFICATION_MASK,
  138. depthTest: {
  139. enabled: false,
  140. },
  141. depthMask: false,
  142. };
  143. const scratchDerivedCommands = [];
  144. function initialize(drawCommand) {
  145. const command = drawCommand._command;
  146. const derivedCommands = scratchDerivedCommands;
  147. // If debug wireframe is enabled, don't derive any new commands.
  148. // Render normally in the opaque pass.
  149. if (drawCommand._useDebugWireframe) {
  150. command.pass = Pass.OPAQUE;
  151. derivedCommands.length = 0;
  152. derivedCommands.push(command);
  153. drawCommand._commandListDebugWireframe = createBatchCommands(
  154. drawCommand,
  155. derivedCommands,
  156. drawCommand._commandListDebugWireframe
  157. );
  158. const commandList = drawCommand._commandListDebugWireframe;
  159. const length = commandList.length;
  160. for (let i = 0; i < length; i++) {
  161. // The lengths / offsets of the batches have to be adjusted for wireframe.
  162. // Only PrimitiveType.TRIANGLES is allowed for classification, so this
  163. // just requires doubling the values for the batches.
  164. const command = commandList[i];
  165. command.count *= 2;
  166. command.offset *= 2;
  167. }
  168. return;
  169. }
  170. const model = drawCommand.model;
  171. const allowPicking = model.allowPicking;
  172. if (drawCommand._classifiesTerrain) {
  173. const pass = Pass.TERRAIN_CLASSIFICATION;
  174. const stencilDepthCommand = deriveStencilDepthCommand(command, pass);
  175. const colorCommand = deriveColorCommand(command, pass);
  176. derivedCommands.length = 0;
  177. derivedCommands.push(stencilDepthCommand, colorCommand);
  178. drawCommand._commandListTerrain = createBatchCommands(
  179. drawCommand,
  180. derivedCommands,
  181. drawCommand._commandListTerrain
  182. );
  183. if (allowPicking) {
  184. drawCommand._commandListTerrainPicking = createPickCommands(
  185. drawCommand,
  186. derivedCommands,
  187. drawCommand._commandListTerrainPicking
  188. );
  189. }
  190. }
  191. if (drawCommand._classifies3DTiles) {
  192. const pass = Pass.CESIUM_3D_TILE_CLASSIFICATION;
  193. const stencilDepthCommand = deriveStencilDepthCommand(command, pass);
  194. const colorCommand = deriveColorCommand(command, pass);
  195. derivedCommands.length = 0;
  196. derivedCommands.push(stencilDepthCommand, colorCommand);
  197. drawCommand._commandList3DTiles = createBatchCommands(
  198. drawCommand,
  199. derivedCommands,
  200. drawCommand._commandList3DTiles
  201. );
  202. if (allowPicking) {
  203. drawCommand._commandList3DTilesPicking = createPickCommands(
  204. drawCommand,
  205. derivedCommands,
  206. drawCommand._commandList3DTilesPicking
  207. );
  208. }
  209. }
  210. }
  211. function createBatchCommands(drawCommand, derivedCommands, result) {
  212. const runtimePrimitive = drawCommand._runtimePrimitive;
  213. const batchLengths = runtimePrimitive.batchLengths;
  214. const batchOffsets = runtimePrimitive.batchOffsets;
  215. const numBatches = batchLengths.length;
  216. const numDerivedCommands = derivedCommands.length;
  217. for (let i = 0; i < numBatches; i++) {
  218. const batchLength = batchLengths[i];
  219. const batchOffset = batchOffsets[i];
  220. // For multiple derived commands (e.g. stencil and color commands),
  221. // they must be added in a certain order even within the batches.
  222. for (let j = 0; j < numDerivedCommands; j++) {
  223. const derivedCommand = derivedCommands[j];
  224. const batchCommand = DrawCommand.shallowClone(derivedCommand);
  225. batchCommand.count = batchLength;
  226. batchCommand.offset = batchOffset;
  227. result.push(batchCommand);
  228. }
  229. }
  230. return result;
  231. }
  232. function deriveStencilDepthCommand(command, pass) {
  233. const stencilDepthCommand = DrawCommand.shallowClone(command);
  234. stencilDepthCommand.cull = false;
  235. stencilDepthCommand.pass = pass;
  236. const stencilFunction =
  237. pass === Pass.TERRAIN_CLASSIFICATION
  238. ? StencilFunction.ALWAYS
  239. : StencilFunction.EQUAL;
  240. const renderState = getStencilDepthRenderState(stencilFunction);
  241. stencilDepthCommand.renderState = RenderState.fromCache(renderState);
  242. return stencilDepthCommand;
  243. }
  244. function deriveColorCommand(command, pass) {
  245. const colorCommand = DrawCommand.shallowClone(command);
  246. colorCommand.cull = false;
  247. colorCommand.pass = pass;
  248. colorCommand.renderState = RenderState.fromCache(colorRenderState);
  249. return colorCommand;
  250. }
  251. const scratchPickCommands = [];
  252. function createPickCommands(drawCommand, derivedCommands, commandList) {
  253. const renderState = RenderState.fromCache(pickRenderState);
  254. const stencilDepthCommand = derivedCommands[0];
  255. const colorCommand = derivedCommands[1];
  256. const pickStencilDepthCommand = DrawCommand.shallowClone(stencilDepthCommand);
  257. pickStencilDepthCommand.cull = true;
  258. pickStencilDepthCommand.pickOnly = true;
  259. const pickColorCommand = DrawCommand.shallowClone(colorCommand);
  260. pickColorCommand.cull = true;
  261. pickColorCommand.pickOnly = true;
  262. pickColorCommand.renderState = renderState;
  263. pickColorCommand.pickId = drawCommand._pickId;
  264. const pickCommands = scratchPickCommands;
  265. pickCommands.length = 0;
  266. pickCommands.push(pickStencilDepthCommand, pickColorCommand);
  267. return createBatchCommands(drawCommand, pickCommands, commandList);
  268. }
  269. Object.defineProperties(ClassificationModelDrawCommand.prototype, {
  270. /**
  271. * The main draw command that the other commands are derived from.
  272. *
  273. * @memberof ClassificationModelDrawCommand.prototype
  274. * @type {DrawCommand}
  275. *
  276. * @readonly
  277. * @private
  278. */
  279. command: {
  280. get: function () {
  281. return this._command;
  282. },
  283. },
  284. /**
  285. * The runtime primitive that the draw command belongs to.
  286. *
  287. * @memberof ClassificationModelDrawCommand.prototype
  288. * @type {ModelRuntimePrimitive}
  289. *
  290. * @readonly
  291. * @private
  292. */
  293. runtimePrimitive: {
  294. get: function () {
  295. return this._runtimePrimitive;
  296. },
  297. },
  298. /**
  299. * The batch lengths used to generate multiple draw commands.
  300. *
  301. * @memberof ClassificationModelDrawCommand.prototype
  302. * @type {number[]}
  303. *
  304. * @readonly
  305. * @private
  306. */
  307. batchLengths: {
  308. get: function () {
  309. return this._runtimePrimitive.batchLengths;
  310. },
  311. },
  312. /**
  313. * The batch offsets used to generate multiple draw commands.
  314. *
  315. * @memberof ClassificationModelDrawCommand.prototype
  316. * @type {number[]}
  317. *
  318. * @readonly
  319. * @private
  320. */
  321. batchOffsets: {
  322. get: function () {
  323. return this._runtimePrimitive.batchOffsets;
  324. },
  325. },
  326. /**
  327. * The model that the draw command belongs to.
  328. *
  329. * @memberof ClassificationModelDrawCommand.prototype
  330. * @type {Model}
  331. *
  332. * @readonly
  333. * @private
  334. */
  335. model: {
  336. get: function () {
  337. return this._model;
  338. },
  339. },
  340. /**
  341. * The classification type of the model that this draw command belongs to.
  342. *
  343. * @memberof ClassificationModelDrawCommand.prototype
  344. * @type {ClassificationType}
  345. *
  346. * @readonly
  347. * @private
  348. */
  349. classificationType: {
  350. get: function () {
  351. return this._classificationType;
  352. },
  353. },
  354. /**
  355. * The current model matrix applied to the draw commands.
  356. *
  357. * @memberof ClassificationModelDrawCommand.prototype
  358. * @type {Matrix4}
  359. *
  360. * @readonly
  361. * @private
  362. */
  363. modelMatrix: {
  364. get: function () {
  365. return this._modelMatrix;
  366. },
  367. set: function (value) {
  368. this._modelMatrix = Matrix4.clone(value, this._modelMatrix);
  369. const boundingSphere = this._runtimePrimitive.boundingSphere;
  370. this._boundingVolume = BoundingSphere.transform(
  371. boundingSphere,
  372. this._modelMatrix,
  373. this._boundingVolume
  374. );
  375. },
  376. },
  377. /**
  378. * The bounding volume of the main draw command. This is equivalent
  379. * to the primitive's bounding sphere transformed by the draw
  380. * command's model matrix.
  381. *
  382. * @memberof ClassificationModelDrawCommand.prototype
  383. * @type {BoundingSphere}
  384. *
  385. * @readonly
  386. * @private
  387. */
  388. boundingVolume: {
  389. get: function () {
  390. return this._boundingVolume;
  391. },
  392. },
  393. /**
  394. * Culling is disabled for classification models, so this has no effect on
  395. * how the model renders. This only exists to match the interface of
  396. * {@link ModelDrawCommand}.
  397. *
  398. * @memberof ClassificationModelDrawCommand.prototype
  399. * @type {CullFace}
  400. *
  401. * @private
  402. */
  403. cullFace: {
  404. get: function () {
  405. return this._cullFace;
  406. },
  407. set: function (value) {
  408. this._cullFace = value;
  409. },
  410. },
  411. });
  412. /**
  413. * Pushes the draw commands necessary to render the primitive.
  414. *
  415. * @param {FrameState} frameState The frame state.
  416. * @param {DrawCommand[]} result The array to push the draw commands to.
  417. *
  418. * @returns {DrawCommand[]} The modified result parameter.
  419. *
  420. * @private
  421. */
  422. ClassificationModelDrawCommand.prototype.pushCommands = function (
  423. frameState,
  424. result
  425. ) {
  426. const passes = frameState.passes;
  427. if (passes.render) {
  428. if (this._useDebugWireframe) {
  429. result.push.apply(result, this._commandListDebugWireframe);
  430. return;
  431. }
  432. if (this._classifiesTerrain) {
  433. result.push.apply(result, this._commandListTerrain);
  434. }
  435. if (this._classifies3DTiles) {
  436. result.push.apply(result, this._commandList3DTiles);
  437. }
  438. const useIgnoreShowCommands =
  439. frameState.invertClassification && this._classifies3DTiles;
  440. if (useIgnoreShowCommands) {
  441. if (this._commandListIgnoreShow.length === 0) {
  442. const pass = Pass.CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW;
  443. const command = deriveStencilDepthCommand(this._command, pass);
  444. const derivedCommands = scratchDerivedCommands;
  445. derivedCommands.length = 0;
  446. derivedCommands.push(command);
  447. this._commandListIgnoreShow = createBatchCommands(
  448. this,
  449. derivedCommands,
  450. this._commandListIgnoreShow
  451. );
  452. }
  453. result.push.apply(result, this._commandListIgnoreShow);
  454. }
  455. }
  456. if (passes.pick) {
  457. if (this._classifiesTerrain) {
  458. result.push.apply(result, this._commandListTerrainPicking);
  459. }
  460. if (this._classifies3DTiles) {
  461. result.push.apply(result, this._commandList3DTilesPicking);
  462. }
  463. }
  464. return result;
  465. };
  466. export default ClassificationModelDrawCommand;