boundingBoxRenderer.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. import { Scene } from "../scene.js";
  2. import { VertexBuffer } from "../Buffers/buffer.js";
  3. import { AbstractMesh } from "../Meshes/abstractMesh.js";
  4. import { Matrix } from "../Maths/math.vector.js";
  5. import { SmartArray } from "../Misc/smartArray.js";
  6. import { SceneComponentConstants } from "../sceneComponent.js";
  7. import { Material } from "../Materials/material.js";
  8. import { ShaderMaterial } from "../Materials/shaderMaterial.js";
  9. import { Color3 } from "../Maths/math.color.js";
  10. import { Observable } from "../Misc/observable.js";
  11. import { DrawWrapper } from "../Materials/drawWrapper.js";
  12. import { UniformBuffer } from "../Materials/uniformBuffer.js";
  13. import { CreateBoxVertexData } from "../Meshes/Builders/boxBuilder.js";
  14. import "../Shaders/boundingBoxRenderer.fragment.js";
  15. import "../Shaders/boundingBoxRenderer.vertex.js";
  16. Object.defineProperty(Scene.prototype, "forceShowBoundingBoxes", {
  17. get: function () {
  18. return this._forceShowBoundingBoxes || false;
  19. },
  20. set: function (value) {
  21. this._forceShowBoundingBoxes = value;
  22. // Lazyly creates a BB renderer if needed.
  23. if (value) {
  24. this.getBoundingBoxRenderer();
  25. }
  26. },
  27. enumerable: true,
  28. configurable: true,
  29. });
  30. Scene.prototype.getBoundingBoxRenderer = function () {
  31. if (!this._boundingBoxRenderer) {
  32. this._boundingBoxRenderer = new BoundingBoxRenderer(this);
  33. }
  34. return this._boundingBoxRenderer;
  35. };
  36. Object.defineProperty(AbstractMesh.prototype, "showBoundingBox", {
  37. get: function () {
  38. return this._showBoundingBox || false;
  39. },
  40. set: function (value) {
  41. this._showBoundingBox = value;
  42. // Lazyly creates a BB renderer if needed.
  43. if (value) {
  44. this.getScene().getBoundingBoxRenderer();
  45. }
  46. },
  47. enumerable: true,
  48. configurable: true,
  49. });
  50. /**
  51. * Component responsible of rendering the bounding box of the meshes in a scene.
  52. * This is usually used through the mesh.showBoundingBox or the scene.forceShowBoundingBoxes properties
  53. */
  54. export class BoundingBoxRenderer {
  55. /**
  56. * Instantiates a new bounding box renderer in a scene.
  57. * @param scene the scene the renderer renders in
  58. */
  59. constructor(scene) {
  60. /**
  61. * The component name helpful to identify the component in the list of scene components.
  62. */
  63. this.name = SceneComponentConstants.NAME_BOUNDINGBOXRENDERER;
  64. /**
  65. * Color of the bounding box lines placed in front of an object
  66. */
  67. this.frontColor = new Color3(1, 1, 1);
  68. /**
  69. * Color of the bounding box lines placed behind an object
  70. */
  71. this.backColor = new Color3(0.1, 0.1, 0.1);
  72. /**
  73. * Defines if the renderer should show the back lines or not
  74. */
  75. this.showBackLines = true;
  76. /**
  77. * Observable raised before rendering a bounding box
  78. */
  79. this.onBeforeBoxRenderingObservable = new Observable();
  80. /**
  81. * Observable raised after rendering a bounding box
  82. */
  83. this.onAfterBoxRenderingObservable = new Observable();
  84. /**
  85. * Observable raised after resources are created
  86. */
  87. this.onResourcesReadyObservable = new Observable();
  88. /**
  89. * When false, no bounding boxes will be rendered
  90. */
  91. this.enabled = true;
  92. /**
  93. * @internal
  94. */
  95. this.renderList = new SmartArray(32);
  96. this._vertexBuffers = {};
  97. this._fillIndexBuffer = null;
  98. this._fillIndexData = null;
  99. this.scene = scene;
  100. scene._addComponent(this);
  101. this._uniformBufferFront = new UniformBuffer(this.scene.getEngine(), undefined, undefined, "BoundingBoxRendererFront", !this.scene.getEngine().isWebGPU);
  102. this._buildUniformLayout(this._uniformBufferFront);
  103. this._uniformBufferBack = new UniformBuffer(this.scene.getEngine(), undefined, undefined, "BoundingBoxRendererBack", !this.scene.getEngine().isWebGPU);
  104. this._buildUniformLayout(this._uniformBufferBack);
  105. }
  106. _buildUniformLayout(ubo) {
  107. ubo.addUniform("color", 4);
  108. ubo.addUniform("world", 16);
  109. ubo.addUniform("viewProjection", 16);
  110. ubo.addUniform("viewProjectionR", 16);
  111. ubo.create();
  112. }
  113. /**
  114. * Registers the component in a given scene
  115. */
  116. register() {
  117. this.scene._beforeEvaluateActiveMeshStage.registerStep(SceneComponentConstants.STEP_BEFOREEVALUATEACTIVEMESH_BOUNDINGBOXRENDERER, this, this.reset);
  118. this.scene._preActiveMeshStage.registerStep(SceneComponentConstants.STEP_PREACTIVEMESH_BOUNDINGBOXRENDERER, this, this._preActiveMesh);
  119. this.scene._evaluateSubMeshStage.registerStep(SceneComponentConstants.STEP_EVALUATESUBMESH_BOUNDINGBOXRENDERER, this, this._evaluateSubMesh);
  120. this.scene._afterRenderingGroupDrawStage.registerStep(SceneComponentConstants.STEP_AFTERRENDERINGGROUPDRAW_BOUNDINGBOXRENDERER, this, this.render);
  121. }
  122. _evaluateSubMesh(mesh, subMesh) {
  123. if (mesh.showSubMeshesBoundingBox) {
  124. const boundingInfo = subMesh.getBoundingInfo();
  125. if (boundingInfo !== null && boundingInfo !== undefined) {
  126. boundingInfo.boundingBox._tag = mesh.renderingGroupId;
  127. this.renderList.push(boundingInfo.boundingBox);
  128. }
  129. }
  130. }
  131. _preActiveMesh(mesh) {
  132. if (mesh.showBoundingBox || this.scene.forceShowBoundingBoxes) {
  133. const boundingInfo = mesh.getBoundingInfo();
  134. boundingInfo.boundingBox._tag = mesh.renderingGroupId;
  135. this.renderList.push(boundingInfo.boundingBox);
  136. }
  137. }
  138. _prepareResources() {
  139. if (this._colorShader) {
  140. return;
  141. }
  142. this._colorShader = new ShaderMaterial("colorShader", this.scene, "boundingBoxRenderer", {
  143. attributes: [VertexBuffer.PositionKind],
  144. uniforms: ["world", "viewProjection", "viewProjectionR", "color"],
  145. uniformBuffers: ["BoundingBoxRenderer"],
  146. }, false);
  147. this._colorShader.doNotSerialize = true;
  148. this._colorShader.reservedDataStore = {
  149. hidden: true,
  150. };
  151. this._colorShaderForOcclusionQuery = new ShaderMaterial("colorShaderOccQuery", this.scene, "boundingBoxRenderer", {
  152. attributes: [VertexBuffer.PositionKind],
  153. uniforms: ["world", "viewProjection", "viewProjectionR", "color"],
  154. uniformBuffers: ["BoundingBoxRenderer"],
  155. }, true);
  156. this._colorShaderForOcclusionQuery.doNotSerialize = true;
  157. this._colorShaderForOcclusionQuery.reservedDataStore = {
  158. hidden: true,
  159. };
  160. const engine = this.scene.getEngine();
  161. const boxdata = CreateBoxVertexData({ size: 1.0 });
  162. this._vertexBuffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, boxdata.positions, VertexBuffer.PositionKind, false);
  163. this._createIndexBuffer();
  164. this._fillIndexData = boxdata.indices;
  165. this.onResourcesReadyObservable.notifyObservers(this);
  166. }
  167. _createIndexBuffer() {
  168. const engine = this.scene.getEngine();
  169. this._indexBuffer = engine.createIndexBuffer([0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 7, 1, 6, 2, 5, 3, 4]);
  170. }
  171. /**
  172. * Rebuilds the elements related to this component in case of
  173. * context lost for instance.
  174. */
  175. rebuild() {
  176. const vb = this._vertexBuffers[VertexBuffer.PositionKind];
  177. if (vb) {
  178. vb._rebuild();
  179. }
  180. this._createIndexBuffer();
  181. }
  182. /**
  183. * @internal
  184. */
  185. reset() {
  186. this.renderList.reset();
  187. }
  188. /**
  189. * Render the bounding boxes of a specific rendering group
  190. * @param renderingGroupId defines the rendering group to render
  191. */
  192. render(renderingGroupId) {
  193. if (this.renderList.length === 0 || !this.enabled) {
  194. return;
  195. }
  196. this._prepareResources();
  197. if (!this._colorShader.isReady()) {
  198. return;
  199. }
  200. const engine = this.scene.getEngine();
  201. engine.setDepthWrite(false);
  202. const transformMatrix = this.scene.getTransformMatrix();
  203. for (let boundingBoxIndex = 0; boundingBoxIndex < this.renderList.length; boundingBoxIndex++) {
  204. const boundingBox = this.renderList.data[boundingBoxIndex];
  205. if (boundingBox._tag !== renderingGroupId) {
  206. continue;
  207. }
  208. this._createWrappersForBoundingBox(boundingBox);
  209. this.onBeforeBoxRenderingObservable.notifyObservers(boundingBox);
  210. const min = boundingBox.minimum;
  211. const max = boundingBox.maximum;
  212. const diff = max.subtract(min);
  213. const median = min.add(diff.scale(0.5));
  214. const worldMatrix = Matrix.Scaling(diff.x, diff.y, diff.z)
  215. .multiply(Matrix.Translation(median.x, median.y, median.z))
  216. .multiply(boundingBox.getWorldMatrix());
  217. const useReverseDepthBuffer = engine.useReverseDepthBuffer;
  218. if (this.showBackLines) {
  219. const drawWrapperBack = boundingBox._drawWrapperBack ?? this._colorShader._getDrawWrapper();
  220. this._colorShader._preBind(drawWrapperBack);
  221. engine.bindBuffers(this._vertexBuffers, this._indexBuffer, this._colorShader.getEffect());
  222. // Back
  223. if (useReverseDepthBuffer) {
  224. engine.setDepthFunctionToLessOrEqual();
  225. }
  226. else {
  227. engine.setDepthFunctionToGreaterOrEqual();
  228. }
  229. this._uniformBufferBack.bindToEffect(drawWrapperBack.effect, "BoundingBoxRenderer");
  230. this._uniformBufferBack.updateColor4("color", this.backColor, 1);
  231. this._uniformBufferBack.updateMatrix("world", worldMatrix);
  232. this._uniformBufferBack.updateMatrix("viewProjection", transformMatrix);
  233. this._uniformBufferBack.update();
  234. // Draw order
  235. engine.drawElementsType(Material.LineListDrawMode, 0, 24);
  236. }
  237. const drawWrapperFront = boundingBox._drawWrapperFront ?? this._colorShader._getDrawWrapper();
  238. this._colorShader._preBind(drawWrapperFront);
  239. engine.bindBuffers(this._vertexBuffers, this._indexBuffer, this._colorShader.getEffect());
  240. // Front
  241. if (useReverseDepthBuffer) {
  242. engine.setDepthFunctionToGreater();
  243. }
  244. else {
  245. engine.setDepthFunctionToLess();
  246. }
  247. this._uniformBufferFront.bindToEffect(drawWrapperFront.effect, "BoundingBoxRenderer");
  248. this._uniformBufferFront.updateColor4("color", this.frontColor, 1);
  249. this._uniformBufferFront.updateMatrix("world", worldMatrix);
  250. this._uniformBufferFront.updateMatrix("viewProjection", transformMatrix);
  251. this._uniformBufferFront.update();
  252. // Draw order
  253. engine.drawElementsType(Material.LineListDrawMode, 0, 24);
  254. this.onAfterBoxRenderingObservable.notifyObservers(boundingBox);
  255. }
  256. this._colorShader.unbind();
  257. engine.setDepthFunctionToLessOrEqual();
  258. engine.setDepthWrite(true);
  259. }
  260. _createWrappersForBoundingBox(boundingBox) {
  261. if (!boundingBox._drawWrapperFront) {
  262. const engine = this.scene.getEngine();
  263. boundingBox._drawWrapperFront = new DrawWrapper(engine);
  264. boundingBox._drawWrapperBack = new DrawWrapper(engine);
  265. boundingBox._drawWrapperFront.setEffect(this._colorShader.getEffect());
  266. boundingBox._drawWrapperBack.setEffect(this._colorShader.getEffect());
  267. }
  268. }
  269. /**
  270. * In case of occlusion queries, we can render the occlusion bounding box through this method
  271. * @param mesh Define the mesh to render the occlusion bounding box for
  272. */
  273. renderOcclusionBoundingBox(mesh) {
  274. const engine = this.scene.getEngine();
  275. if (this._renderPassIdForOcclusionQuery === undefined) {
  276. this._renderPassIdForOcclusionQuery = engine.createRenderPassId(`Render pass for occlusion query`);
  277. }
  278. const currentRenderPassId = engine.currentRenderPassId;
  279. engine.currentRenderPassId = this._renderPassIdForOcclusionQuery;
  280. this._prepareResources();
  281. const subMesh = mesh.subMeshes[0];
  282. if (!this._colorShaderForOcclusionQuery.isReady(mesh, undefined, subMesh) || !mesh.hasBoundingInfo) {
  283. engine.currentRenderPassId = currentRenderPassId;
  284. return;
  285. }
  286. if (!this._fillIndexBuffer) {
  287. this._fillIndexBuffer = engine.createIndexBuffer(this._fillIndexData);
  288. }
  289. const useReverseDepthBuffer = engine.useReverseDepthBuffer;
  290. engine.setDepthWrite(false);
  291. engine.setColorWrite(false);
  292. const boundingBox = mesh.getBoundingInfo().boundingBox;
  293. const min = boundingBox.minimum;
  294. const max = boundingBox.maximum;
  295. const diff = max.subtract(min);
  296. const median = min.add(diff.scale(0.5));
  297. const worldMatrix = Matrix.Scaling(diff.x, diff.y, diff.z)
  298. .multiply(Matrix.Translation(median.x, median.y, median.z))
  299. .multiply(boundingBox.getWorldMatrix());
  300. const drawWrapper = subMesh._drawWrapper;
  301. this._colorShaderForOcclusionQuery._preBind(drawWrapper);
  302. engine.bindBuffers(this._vertexBuffers, this._fillIndexBuffer, drawWrapper.effect);
  303. if (useReverseDepthBuffer) {
  304. engine.setDepthFunctionToGreater();
  305. }
  306. else {
  307. engine.setDepthFunctionToLess();
  308. }
  309. this.scene.resetCachedMaterial();
  310. this._uniformBufferFront.bindToEffect(drawWrapper.effect, "BoundingBoxRenderer");
  311. this._uniformBufferFront.updateMatrix("world", worldMatrix);
  312. this._uniformBufferFront.updateMatrix("viewProjection", this.scene.getTransformMatrix());
  313. this._uniformBufferFront.update();
  314. engine.drawElementsType(Material.TriangleFillMode, 0, 36);
  315. this._colorShaderForOcclusionQuery.unbind();
  316. engine.setDepthFunctionToLessOrEqual();
  317. engine.setDepthWrite(true);
  318. engine.setColorWrite(true);
  319. engine.currentRenderPassId = currentRenderPassId;
  320. }
  321. /**
  322. * Dispose and release the resources attached to this renderer.
  323. */
  324. dispose() {
  325. if (this._renderPassIdForOcclusionQuery !== undefined) {
  326. this.scene.getEngine().releaseRenderPassId(this._renderPassIdForOcclusionQuery);
  327. this._renderPassIdForOcclusionQuery = undefined;
  328. }
  329. if (!this._colorShader) {
  330. return;
  331. }
  332. this.onBeforeBoxRenderingObservable.clear();
  333. this.onAfterBoxRenderingObservable.clear();
  334. this.onResourcesReadyObservable.clear();
  335. this.renderList.dispose();
  336. this._colorShader.dispose();
  337. this._colorShaderForOcclusionQuery.dispose();
  338. this._uniformBufferFront.dispose();
  339. this._uniformBufferBack.dispose();
  340. const buffer = this._vertexBuffers[VertexBuffer.PositionKind];
  341. if (buffer) {
  342. buffer.dispose();
  343. this._vertexBuffers[VertexBuffer.PositionKind] = null;
  344. }
  345. this.scene.getEngine()._releaseBuffer(this._indexBuffer);
  346. if (this._fillIndexBuffer) {
  347. this.scene.getEngine()._releaseBuffer(this._fillIndexBuffer);
  348. this._fillIndexBuffer = null;
  349. }
  350. }
  351. }
  352. //# sourceMappingURL=boundingBoxRenderer.js.map