edgesRenderer.ts 20 KB

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