babylon.edgesRenderer.ts 19 KB

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