boundingBoxRenderer.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. import { Scene } from "../scene";
  2. import { VertexBuffer } from "../Meshes/buffer";
  3. import { SubMesh } from "../Meshes/subMesh";
  4. import { AbstractMesh } from "../Meshes/abstractMesh";
  5. import { VertexData } from "../Meshes/mesh.vertexData";
  6. import { Matrix } from "../Maths/math.vector";
  7. import { SmartArray } from "../Misc/smartArray";
  8. import { Nullable, FloatArray, IndicesArray } from "../types";
  9. import { ISceneComponent, SceneComponentConstants } from "../sceneComponent";
  10. import { BoundingBox } from "../Culling/boundingBox";
  11. import { Effect } from "../Materials/effect";
  12. import { Material } from "../Materials/material";
  13. import { ShaderMaterial } from "../Materials/shaderMaterial";
  14. import "../Meshes/Builders/boxBuilder";
  15. import "../Shaders/color.fragment";
  16. import "../Shaders/color.vertex";
  17. import { DataBuffer } from '../Meshes/dataBuffer';
  18. import { Color3 } from '../Maths/math.color';
  19. import { Observable } from '../Misc/observable';
  20. declare module "../scene" {
  21. export interface Scene {
  22. /** @hidden (Backing field) */
  23. _boundingBoxRenderer: BoundingBoxRenderer;
  24. /** @hidden (Backing field) */
  25. _forceShowBoundingBoxes: boolean;
  26. /**
  27. * Gets or sets a boolean indicating if all bounding boxes must be rendered
  28. */
  29. forceShowBoundingBoxes: boolean;
  30. /**
  31. * Gets the bounding box renderer associated with the scene
  32. * @returns a BoundingBoxRenderer
  33. */
  34. getBoundingBoxRenderer(): BoundingBoxRenderer;
  35. }
  36. }
  37. Object.defineProperty(Scene.prototype, "forceShowBoundingBoxes", {
  38. get: function(this: Scene) {
  39. return this._forceShowBoundingBoxes || false;
  40. },
  41. set: function(this: Scene, value: boolean) {
  42. this._forceShowBoundingBoxes = value;
  43. // Lazyly creates a BB renderer if needed.
  44. if (value) {
  45. this.getBoundingBoxRenderer();
  46. }
  47. },
  48. enumerable: true,
  49. configurable: true
  50. });
  51. Scene.prototype.getBoundingBoxRenderer = function(): BoundingBoxRenderer {
  52. if (!this._boundingBoxRenderer) {
  53. this._boundingBoxRenderer = new BoundingBoxRenderer(this);
  54. }
  55. return this._boundingBoxRenderer;
  56. };
  57. declare module "../Meshes/abstractMesh" {
  58. export interface AbstractMesh {
  59. /** @hidden (Backing field) */
  60. _showBoundingBox: boolean;
  61. /**
  62. * Gets or sets a boolean indicating if the bounding box must be rendered as well (false by default)
  63. */
  64. showBoundingBox: boolean;
  65. }
  66. }
  67. Object.defineProperty(AbstractMesh.prototype, "showBoundingBox", {
  68. get: function(this: AbstractMesh) {
  69. return this._showBoundingBox || false;
  70. },
  71. set: function(this: AbstractMesh, value: boolean) {
  72. this._showBoundingBox = value;
  73. // Lazyly creates a BB renderer if needed.
  74. if (value) {
  75. this.getScene().getBoundingBoxRenderer();
  76. }
  77. },
  78. enumerable: true,
  79. configurable: true
  80. });
  81. /**
  82. * Component responsible of rendering the bounding box of the meshes in a scene.
  83. * This is usually used through the mesh.showBoundingBox or the scene.forceShowBoundingBoxes properties
  84. */
  85. export class BoundingBoxRenderer implements ISceneComponent {
  86. /**
  87. * The component name helpful to identify the component in the list of scene components.
  88. */
  89. public readonly name = SceneComponentConstants.NAME_BOUNDINGBOXRENDERER;
  90. /**
  91. * The scene the component belongs to.
  92. */
  93. public scene: Scene;
  94. /**
  95. * Color of the bounding box lines placed in front of an object
  96. */
  97. public frontColor = new Color3(1, 1, 1);
  98. /**
  99. * Color of the bounding box lines placed behind an object
  100. */
  101. public backColor = new Color3(0.1, 0.1, 0.1);
  102. /**
  103. * Defines if the renderer should show the back lines or not
  104. */
  105. public showBackLines = true;
  106. /**
  107. * Observable raised before rendering a bounding box
  108. */
  109. public onBeforeBoxRenderingObservable = new Observable<BoundingBox>();
  110. /**
  111. * Observable raised after rendering a bounding box
  112. */
  113. public onAfterBoxRenderingObservable = new Observable<BoundingBox>();
  114. /**
  115. * Observable raised after resources are created
  116. */
  117. public onResourcesReadyObservable = new Observable<BoundingBoxRenderer>();
  118. /**
  119. * When false, no bounding boxes will be rendered
  120. */
  121. public enabled = true;
  122. /**
  123. * @hidden
  124. */
  125. public renderList = new SmartArray<BoundingBox>(32);
  126. private _colorShader: ShaderMaterial;
  127. private _vertexBuffers: { [key: string]: Nullable<VertexBuffer> } = {};
  128. private _indexBuffer: DataBuffer;
  129. private _fillIndexBuffer: Nullable<DataBuffer> = null;
  130. private _fillIndexData: Nullable<IndicesArray> = null;
  131. /**
  132. * Instantiates a new bounding box renderer in a scene.
  133. * @param scene the scene the renderer renders in
  134. */
  135. constructor(scene: Scene) {
  136. this.scene = scene;
  137. scene._addComponent(this);
  138. }
  139. /**
  140. * Registers the component in a given scene
  141. */
  142. public register(): void {
  143. this.scene._beforeEvaluateActiveMeshStage.registerStep(SceneComponentConstants.STEP_BEFOREEVALUATEACTIVEMESH_BOUNDINGBOXRENDERER, this, this.reset);
  144. this.scene._preActiveMeshStage.registerStep(SceneComponentConstants.STEP_PREACTIVEMESH_BOUNDINGBOXRENDERER, this, this._preActiveMesh);
  145. this.scene._evaluateSubMeshStage.registerStep(SceneComponentConstants.STEP_EVALUATESUBMESH_BOUNDINGBOXRENDERER, this, this._evaluateSubMesh);
  146. this.scene._afterRenderingGroupDrawStage.registerStep(SceneComponentConstants.STEP_AFTERRENDERINGGROUPDRAW_BOUNDINGBOXRENDERER, this, this.render);
  147. }
  148. private _evaluateSubMesh(mesh: AbstractMesh, subMesh: SubMesh): void {
  149. if (mesh.showSubMeshesBoundingBox) {
  150. const boundingInfo = subMesh.getBoundingInfo();
  151. if (boundingInfo !== null && boundingInfo !== undefined) {
  152. boundingInfo.boundingBox._tag = mesh.renderingGroupId;
  153. this.renderList.push(boundingInfo.boundingBox);
  154. }
  155. }
  156. }
  157. private _preActiveMesh(mesh: AbstractMesh): void {
  158. if (mesh.showBoundingBox || this.scene.forceShowBoundingBoxes) {
  159. let boundingInfo = mesh.getBoundingInfo();
  160. boundingInfo.boundingBox._tag = mesh.renderingGroupId;
  161. this.renderList.push(boundingInfo.boundingBox);
  162. }
  163. }
  164. private _prepareResources(): void {
  165. if (this._colorShader) {
  166. return;
  167. }
  168. this._colorShader = new ShaderMaterial("colorShader", this.scene, "color",
  169. {
  170. attributes: [VertexBuffer.PositionKind],
  171. uniforms: ["world", "viewProjection", "color"]
  172. });
  173. this._colorShader.reservedDataStore = {
  174. hidden: true
  175. };
  176. var engine = this.scene.getEngine();
  177. var boxdata = VertexData.CreateBox({ size: 1.0 });
  178. this._vertexBuffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, <FloatArray>boxdata.positions, VertexBuffer.PositionKind, false);
  179. this._createIndexBuffer();
  180. this._fillIndexData = boxdata.indices;
  181. this.onResourcesReadyObservable.notifyObservers(this);
  182. }
  183. private _createIndexBuffer(): void {
  184. var engine = this.scene.getEngine();
  185. 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]);
  186. }
  187. /**
  188. * Rebuilds the elements related to this component in case of
  189. * context lost for instance.
  190. */
  191. public rebuild(): void {
  192. let vb = this._vertexBuffers[VertexBuffer.PositionKind];
  193. if (vb) {
  194. vb._rebuild();
  195. }
  196. this._createIndexBuffer();
  197. }
  198. /**
  199. * @hidden
  200. */
  201. public reset(): void {
  202. this.renderList.reset();
  203. }
  204. /**
  205. * Render the bounding boxes of a specific rendering group
  206. * @param renderingGroupId defines the rendering group to render
  207. */
  208. public render(renderingGroupId: number): void {
  209. if (this.renderList.length === 0 || !this.enabled) {
  210. return;
  211. }
  212. this._prepareResources();
  213. if (!this._colorShader.isReady()) {
  214. return;
  215. }
  216. var engine = this.scene.getEngine();
  217. engine.setDepthWrite(false);
  218. this._colorShader._preBind();
  219. for (var boundingBoxIndex = 0; boundingBoxIndex < this.renderList.length; boundingBoxIndex++) {
  220. var boundingBox = this.renderList.data[boundingBoxIndex];
  221. if (boundingBox._tag !== renderingGroupId) {
  222. continue;
  223. }
  224. this.onBeforeBoxRenderingObservable.notifyObservers(boundingBox);
  225. var min = boundingBox.minimum;
  226. var max = boundingBox.maximum;
  227. var diff = max.subtract(min);
  228. var median = min.add(diff.scale(0.5));
  229. var worldMatrix = Matrix.Scaling(diff.x, diff.y, diff.z)
  230. .multiply(Matrix.Translation(median.x, median.y, median.z))
  231. .multiply(boundingBox.getWorldMatrix());
  232. // VBOs
  233. engine.bindBuffers(this._vertexBuffers, this._indexBuffer, <Effect>this._colorShader.getEffect());
  234. if (this.showBackLines) {
  235. // Back
  236. engine.setDepthFunctionToGreaterOrEqual();
  237. this.scene.resetCachedMaterial();
  238. this._colorShader.setColor4("color", this.backColor.toColor4());
  239. this._colorShader.bind(worldMatrix);
  240. // Draw order
  241. engine.drawElementsType(Material.LineListDrawMode, 0, 24);
  242. }
  243. // Front
  244. engine.setDepthFunctionToLess();
  245. this.scene.resetCachedMaterial();
  246. this._colorShader.setColor4("color", this.frontColor.toColor4());
  247. this._colorShader.bind(worldMatrix);
  248. // Draw order
  249. engine.drawElementsType(Material.LineListDrawMode, 0, 24);
  250. this.onAfterBoxRenderingObservable.notifyObservers(boundingBox);
  251. }
  252. this._colorShader.unbind();
  253. engine.setDepthFunctionToLessOrEqual();
  254. engine.setDepthWrite(true);
  255. }
  256. /**
  257. * In case of occlusion queries, we can render the occlusion bounding box through this method
  258. * @param mesh Define the mesh to render the occlusion bounding box for
  259. */
  260. public renderOcclusionBoundingBox(mesh: AbstractMesh): void {
  261. this._prepareResources();
  262. if (!this._colorShader.isReady() || !mesh._boundingInfo) {
  263. return;
  264. }
  265. var engine = this.scene.getEngine();
  266. if (!this._fillIndexBuffer) {
  267. this._fillIndexBuffer = engine.createIndexBuffer(this._fillIndexData!);
  268. }
  269. engine.setDepthWrite(false);
  270. engine.setColorWrite(false);
  271. this._colorShader._preBind();
  272. var boundingBox = mesh._boundingInfo.boundingBox;
  273. var min = boundingBox.minimum;
  274. var max = boundingBox.maximum;
  275. var diff = max.subtract(min);
  276. var median = min.add(diff.scale(0.5));
  277. var worldMatrix = Matrix.Scaling(diff.x, diff.y, diff.z)
  278. .multiply(Matrix.Translation(median.x, median.y, median.z))
  279. .multiply(boundingBox.getWorldMatrix());
  280. engine.bindBuffers(this._vertexBuffers, this._fillIndexBuffer, <Effect>this._colorShader.getEffect());
  281. engine.setDepthFunctionToLess();
  282. this.scene.resetCachedMaterial();
  283. this._colorShader.bind(worldMatrix);
  284. engine.drawElementsType(Material.TriangleFillMode, 0, 36);
  285. this._colorShader.unbind();
  286. engine.setDepthFunctionToLessOrEqual();
  287. engine.setDepthWrite(true);
  288. engine.setColorWrite(true);
  289. }
  290. /**
  291. * Dispose and release the resources attached to this renderer.
  292. */
  293. public dispose(): void {
  294. if (!this._colorShader) {
  295. return;
  296. }
  297. this.onBeforeBoxRenderingObservable.clear();
  298. this.onAfterBoxRenderingObservable.clear();
  299. this.onResourcesReadyObservable.clear();
  300. this.renderList.dispose();
  301. this._colorShader.dispose();
  302. var buffer = this._vertexBuffers[VertexBuffer.PositionKind];
  303. if (buffer) {
  304. buffer.dispose();
  305. this._vertexBuffers[VertexBuffer.PositionKind] = null;
  306. }
  307. this.scene.getEngine()._releaseBuffer(this._indexBuffer);
  308. if (this._fillIndexBuffer) {
  309. this.scene.getEngine()._releaseBuffer(this._fillIndexBuffer);
  310. this._fillIndexBuffer = null;
  311. }
  312. }
  313. }