babylon.edgesRenderer.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. module BABYLON {
  2. export interface AbstractMesh {
  3. /**
  4. * Disables the mesh edge rendering mode
  5. * @returns the currentAbstractMesh
  6. */
  7. disableEdgesRendering(): AbstractMesh;
  8. /**
  9. * Enables the edge rendering mode on the mesh.
  10. * This mode makes the mesh edges visible
  11. * @param epsilon defines the maximal distance between two angles to detect a face
  12. * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
  13. * @returns the currentAbstractMesh
  14. * @see https://www.babylonjs-playground.com/#19O9TU#0
  15. */
  16. enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): AbstractMesh;
  17. /**
  18. * Gets the edgesRenderer associated with the mesh
  19. */
  20. edgesRenderer: Nullable<EdgesRenderer>;
  21. }
  22. AbstractMesh.prototype.disableEdgesRendering = function(): AbstractMesh {
  23. if (this._edgesRenderer) {
  24. this._edgesRenderer.dispose();
  25. this._edgesRenderer = null;
  26. }
  27. return this;
  28. };
  29. AbstractMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): AbstractMesh {
  30. this.disableEdgesRendering();
  31. this._edgesRenderer = new EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
  32. return this;
  33. };
  34. Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
  35. get: function(this: AbstractMesh) {
  36. return this._edgesRenderer;
  37. },
  38. enumerable: true,
  39. configurable: true
  40. });
  41. export interface LinesMesh {
  42. /**
  43. * Enables the edge rendering mode on the mesh.
  44. * This mode makes the mesh edges visible
  45. * @param epsilon defines the maximal distance between two angles to detect a face
  46. * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
  47. * @returns the currentAbstractMesh
  48. * @see https://www.babylonjs-playground.com/#19O9TU#0
  49. */
  50. enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): AbstractMesh;
  51. }
  52. LinesMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): AbstractMesh {
  53. this.disableEdgesRendering();
  54. this._edgesRenderer = new LineEdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
  55. return this;
  56. };
  57. export interface InstancedLinesMesh {
  58. /**
  59. * Enables the edge rendering mode on the mesh.
  60. * This mode makes the mesh edges visible
  61. * @param epsilon defines the maximal distance between two angles to detect a face
  62. * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
  63. * @returns the current InstancedLinesMesh
  64. * @see https://www.babylonjs-playground.com/#19O9TU#0
  65. */
  66. enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): InstancedLinesMesh;
  67. }
  68. InstancedLinesMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): InstancedLinesMesh {
  69. LinesMesh.prototype.enableEdgesRendering.apply(this, arguments);
  70. return this;
  71. };
  72. /**
  73. * FaceAdjacencies Helper class to generate edges
  74. */
  75. class FaceAdjacencies {
  76. public edges = new Array<number>();
  77. public p0: Vector3;
  78. public p1: Vector3;
  79. public p2: Vector3;
  80. public edgesConnectedCount = 0;
  81. }
  82. /**
  83. * Defines the minimum contract an Edges renderer should follow.
  84. */
  85. export interface IEdgesRenderer extends IDisposable {
  86. /**
  87. * Gets or sets a boolean indicating if the edgesRenderer is active
  88. */
  89. isEnabled: boolean;
  90. /**
  91. * Renders the edges of the attached mesh,
  92. */
  93. render(): void;
  94. /**
  95. * Checks wether or not the edges renderer is ready to render.
  96. * @return true if ready, otherwise false.
  97. */
  98. isReady(): boolean;
  99. }
  100. /**
  101. * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
  102. */
  103. export class EdgesRenderer implements IEdgesRenderer {
  104. /**
  105. * Define the size of the edges with an orthographic camera
  106. */
  107. public edgesWidthScalerForOrthographic = 1000.0;
  108. /**
  109. * Define the size of the edges with a perspective camera
  110. */
  111. public edgesWidthScalerForPerspective = 50.0;
  112. protected _source: AbstractMesh;
  113. protected _linesPositions = new Array<number>();
  114. protected _linesNormals = new Array<number>();
  115. protected _linesIndices = new Array<number>();
  116. protected _epsilon: number;
  117. protected _indicesCount: number;
  118. protected _lineShader: ShaderMaterial;
  119. protected _ib: WebGLBuffer;
  120. protected _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
  121. protected _checkVerticesInsteadOfIndices = false;
  122. private _meshRebuildObserver: Nullable<Observer<AbstractMesh>>;
  123. private _meshDisposeObserver: Nullable<Observer<Node>>;
  124. /** Gets or sets a boolean indicating if the edgesRenderer is active */
  125. public isEnabled = true;
  126. /**
  127. * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
  128. * Beware when you use this class with complex objects as the adjacencies computation can be really long
  129. * @param source Mesh used to create edges
  130. * @param epsilon sum of angles in adjacency to check for edge
  131. * @param checkVerticesInsteadOfIndices
  132. * @param generateEdgesLines - should generate Lines or only prepare resources.
  133. */
  134. constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false, generateEdgesLines = true) {
  135. this._source = source;
  136. this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
  137. this._epsilon = epsilon;
  138. this._prepareRessources();
  139. if (generateEdgesLines) {
  140. this._generateEdgesLines();
  141. }
  142. this._meshRebuildObserver = this._source.onRebuildObservable.add(() => {
  143. this._rebuild();
  144. });
  145. this._meshDisposeObserver = this._source.onDisposeObservable.add(() => {
  146. this.dispose();
  147. });
  148. }
  149. protected _prepareRessources(): void {
  150. if (this._lineShader) {
  151. return;
  152. }
  153. this._lineShader = new ShaderMaterial("lineShader", this._source.getScene(), "line",
  154. {
  155. attributes: ["position", "normal"],
  156. uniforms: ["worldViewProjection", "color", "width", "aspectRatio"]
  157. });
  158. this._lineShader.disableDepthWrite = true;
  159. this._lineShader.backFaceCulling = false;
  160. }
  161. /** @hidden */
  162. public _rebuild(): void {
  163. var buffer = this._buffers[VertexBuffer.PositionKind];
  164. if (buffer) {
  165. buffer._rebuild();
  166. }
  167. buffer = this._buffers[VertexBuffer.NormalKind];
  168. if (buffer) {
  169. buffer._rebuild();
  170. }
  171. var scene = this._source.getScene();
  172. var engine = scene.getEngine();
  173. this._ib = engine.createIndexBuffer(this._linesIndices);
  174. }
  175. /**
  176. * Releases the required resources for the edges renderer
  177. */
  178. public dispose(): void {
  179. this._source.onRebuildObservable.remove(this._meshRebuildObserver);
  180. this._source.onDisposeObservable.remove(this._meshDisposeObserver);
  181. var buffer = this._buffers[VertexBuffer.PositionKind];
  182. if (buffer) {
  183. buffer.dispose();
  184. this._buffers[VertexBuffer.PositionKind] = null;
  185. }
  186. buffer = this._buffers[VertexBuffer.NormalKind];
  187. if (buffer) {
  188. buffer.dispose();
  189. this._buffers[VertexBuffer.NormalKind] = null;
  190. }
  191. this._source.getScene().getEngine()._releaseBuffer(this._ib);
  192. this._lineShader.dispose();
  193. }
  194. protected _processEdgeForAdjacencies(pa: number, pb: number, p0: number, p1: number, p2: number): number {
  195. if (pa === p0 && pb === p1 || pa === p1 && pb === p0) {
  196. return 0;
  197. }
  198. if (pa === p1 && pb === p2 || pa === p2 && pb === p1) {
  199. return 1;
  200. }
  201. if (pa === p2 && pb === p0 || pa === p0 && pb === p2) {
  202. return 2;
  203. }
  204. return -1;
  205. }
  206. protected _processEdgeForAdjacenciesWithVertices(pa: Vector3, pb: Vector3, p0: Vector3, p1: Vector3, p2: Vector3): number {
  207. if (pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p1) || pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p0)) {
  208. return 0;
  209. }
  210. if (pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p2) || pa.equalsWithEpsilon(p2) && pb.equalsWithEpsilon(p1)) {
  211. return 1;
  212. }
  213. if (pa.equalsWithEpsilon(p2) && pb.equalsWithEpsilon(p0) || pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p2)) {
  214. return 2;
  215. }
  216. return -1;
  217. }
  218. /**
  219. * Checks if the pair of p0 and p1 is en edge
  220. * @param faceIndex
  221. * @param edge
  222. * @param faceNormals
  223. * @param p0
  224. * @param p1
  225. * @private
  226. */
  227. protected _checkEdge(faceIndex: number, edge: number, faceNormals: Array<Vector3>, p0: Vector3, p1: Vector3): void {
  228. var needToCreateLine;
  229. if (edge === undefined) {
  230. needToCreateLine = true;
  231. } else {
  232. var dotProduct = Vector3.Dot(faceNormals[faceIndex], faceNormals[edge]);
  233. needToCreateLine = dotProduct < this._epsilon;
  234. }
  235. if (needToCreateLine) {
  236. this.createLine(p0, p1, this._linesPositions.length / 3);
  237. }
  238. }
  239. /**
  240. * push line into the position, normal and index buffer
  241. * @protected
  242. */
  243. protected createLine(p0: Vector3, p1: Vector3, offset: number) {
  244. // Positions
  245. this._linesPositions.push(
  246. p0.x, p0.y, p0.z,
  247. p0.x, p0.y, p0.z,
  248. p1.x, p1.y, p1.z,
  249. p1.x, p1.y, p1.z
  250. );
  251. // Normals
  252. this._linesNormals.push(
  253. p1.x, p1.y, p1.z, -1,
  254. p1.x, p1.y, p1.z, 1,
  255. p0.x, p0.y, p0.z, -1,
  256. p0.x, p0.y, p0.z, 1
  257. );
  258. // Indices
  259. this._linesIndices.push(
  260. offset, offset + 1, offset + 2,
  261. offset, offset + 2, offset + 3
  262. );
  263. }
  264. /**
  265. * Generates lines edges from adjacencjes
  266. * @private
  267. */
  268. _generateEdgesLines(): void {
  269. var positions = this._source.getVerticesData(VertexBuffer.PositionKind);
  270. var indices = this._source.getIndices();
  271. if (!indices || !positions) {
  272. return;
  273. }
  274. // First let's find adjacencies
  275. var adjacencies = new Array<FaceAdjacencies>();
  276. var faceNormals = new Array<Vector3>();
  277. var index: number;
  278. var faceAdjacencies: FaceAdjacencies;
  279. // Prepare faces
  280. for (index = 0; index < indices.length; index += 3) {
  281. faceAdjacencies = new FaceAdjacencies();
  282. var p0Index = indices[index];
  283. var p1Index = indices[index + 1];
  284. var p2Index = indices[index + 2];
  285. faceAdjacencies.p0 = new Vector3(positions[p0Index * 3], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
  286. faceAdjacencies.p1 = new Vector3(positions[p1Index * 3], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
  287. faceAdjacencies.p2 = new Vector3(positions[p2Index * 3], positions[p2Index * 3 + 1], positions[p2Index * 3 + 2]);
  288. var faceNormal = Vector3.Cross(faceAdjacencies.p1.subtract(faceAdjacencies.p0), faceAdjacencies.p2.subtract(faceAdjacencies.p1));
  289. faceNormal.normalize();
  290. faceNormals.push(faceNormal);
  291. adjacencies.push(faceAdjacencies);
  292. }
  293. // Scan
  294. for (index = 0; index < adjacencies.length; index++) {
  295. faceAdjacencies = adjacencies[index];
  296. for (var otherIndex = index + 1; otherIndex < adjacencies.length; otherIndex++) {
  297. var otherFaceAdjacencies = adjacencies[otherIndex];
  298. if (faceAdjacencies.edgesConnectedCount === 3) { // Full
  299. break;
  300. }
  301. if (otherFaceAdjacencies.edgesConnectedCount === 3) { // Full
  302. continue;
  303. }
  304. var otherP0 = indices[otherIndex * 3];
  305. var otherP1 = indices[otherIndex * 3 + 1];
  306. var otherP2 = indices[otherIndex * 3 + 2];
  307. for (var edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
  308. var otherEdgeIndex: number = 0;
  309. if (faceAdjacencies.edges[edgeIndex] !== undefined) {
  310. continue;
  311. }
  312. switch (edgeIndex) {
  313. case 0:
  314. if (this._checkVerticesInsteadOfIndices) {
  315. otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p0, faceAdjacencies.p1, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
  316. } else {
  317. otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3], indices[index * 3 + 1], otherP0, otherP1, otherP2);
  318. }
  319. break;
  320. case 1:
  321. if (this._checkVerticesInsteadOfIndices) {
  322. otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p1, faceAdjacencies.p2, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
  323. } else {
  324. otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3 + 1], indices[index * 3 + 2], otherP0, otherP1, otherP2);
  325. }
  326. break;
  327. case 2:
  328. if (this._checkVerticesInsteadOfIndices) {
  329. otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p2, faceAdjacencies.p0, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
  330. } else {
  331. otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3 + 2], indices[index * 3], otherP0, otherP1, otherP2);
  332. }
  333. break;
  334. }
  335. if (otherEdgeIndex === -1) {
  336. continue;
  337. }
  338. faceAdjacencies.edges[edgeIndex] = otherIndex;
  339. otherFaceAdjacencies.edges[otherEdgeIndex] = index;
  340. faceAdjacencies.edgesConnectedCount++;
  341. otherFaceAdjacencies.edgesConnectedCount++;
  342. if (faceAdjacencies.edgesConnectedCount === 3) {
  343. break;
  344. }
  345. }
  346. }
  347. }
  348. // Create lines
  349. for (index = 0; index < adjacencies.length; index++) {
  350. // We need a line when a face has no adjacency on a specific edge or if all the adjacencies has an angle greater than epsilon
  351. var current = adjacencies[index];
  352. this._checkEdge(index, current.edges[0], faceNormals, current.p0, current.p1);
  353. this._checkEdge(index, current.edges[1], faceNormals, current.p1, current.p2);
  354. this._checkEdge(index, current.edges[2], faceNormals, current.p2, current.p0);
  355. }
  356. // Merge into a single mesh
  357. var engine = this._source.getScene().getEngine();
  358. this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
  359. this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
  360. this._ib = engine.createIndexBuffer(this._linesIndices);
  361. this._indicesCount = this._linesIndices.length;
  362. }
  363. /**
  364. * Checks wether or not the edges renderer is ready to render.
  365. * @return true if ready, otherwise false.
  366. */
  367. public isReady(): boolean {
  368. return this._lineShader.isReady();
  369. }
  370. /**
  371. * Renders the edges of the attached mesh,
  372. */
  373. public render(): void {
  374. var scene = this._source.getScene();
  375. if (!this.isReady() || !scene.activeCamera) {
  376. return;
  377. }
  378. var engine = scene.getEngine();
  379. this._lineShader._preBind();
  380. if (this._source.edgesColor.a !== 1) {
  381. engine.setAlphaMode(Engine.ALPHA_COMBINE);
  382. } else {
  383. engine.setAlphaMode(Engine.ALPHA_DISABLE);
  384. }
  385. // VBOs
  386. engine.bindBuffers(this._buffers, this._ib, <Effect>this._lineShader.getEffect());
  387. scene.resetCachedMaterial();
  388. this._lineShader.setColor4("color", this._source.edgesColor);
  389. if (scene.activeCamera.mode === Camera.ORTHOGRAPHIC_CAMERA) {
  390. this._lineShader.setFloat("width", this._source.edgesWidth / this.edgesWidthScalerForOrthographic);
  391. } else {
  392. this._lineShader.setFloat("width", this._source.edgesWidth / this.edgesWidthScalerForPerspective);
  393. }
  394. this._lineShader.setFloat("aspectRatio", engine.getAspectRatio(scene.activeCamera));
  395. this._lineShader.bind(this._source.getWorldMatrix());
  396. // Draw order
  397. engine.drawElementsType(Material.TriangleFillMode, 0, this._indicesCount);
  398. this._lineShader.unbind();
  399. }
  400. }
  401. }