edgesRenderer.ts 20 KB

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