edgesRenderer.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. import { VertexBuffer } from "../Buffers/buffer.js";
  2. import { AbstractMesh } from "../Meshes/abstractMesh.js";
  3. import { LinesMesh, InstancedLinesMesh } from "../Meshes/linesMesh.js";
  4. import { Vector3, TmpVectors } from "../Maths/math.vector.js";
  5. import { Material } from "../Materials/material.js";
  6. import { ShaderMaterial } from "../Materials/shaderMaterial.js";
  7. import { Camera } from "../Cameras/camera.js";
  8. import "../Shaders/line.fragment.js";
  9. import "../Shaders/line.vertex.js";
  10. import { SmartArray } from "../Misc/smartArray.js";
  11. import { DrawWrapper } from "../Materials/drawWrapper.js";
  12. AbstractMesh.prototype.disableEdgesRendering = function () {
  13. if (this._edgesRenderer) {
  14. this._edgesRenderer.dispose();
  15. this._edgesRenderer = null;
  16. }
  17. return this;
  18. };
  19. AbstractMesh.prototype.enableEdgesRendering = function (epsilon = 0.95, checkVerticesInsteadOfIndices = false, options) {
  20. this.disableEdgesRendering();
  21. this._edgesRenderer = new EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices, true, options);
  22. return this;
  23. };
  24. Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
  25. get: function () {
  26. return this._edgesRenderer;
  27. },
  28. enumerable: true,
  29. configurable: true,
  30. });
  31. LinesMesh.prototype.enableEdgesRendering = function (epsilon = 0.95, checkVerticesInsteadOfIndices = false) {
  32. this.disableEdgesRendering();
  33. this._edgesRenderer = new LineEdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
  34. return this;
  35. };
  36. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  37. InstancedLinesMesh.prototype.enableEdgesRendering = function (epsilon = 0.95, checkVerticesInsteadOfIndices = false) {
  38. LinesMesh.prototype.enableEdgesRendering.apply(this, arguments);
  39. return this;
  40. };
  41. /**
  42. * FaceAdjacencies Helper class to generate edges
  43. */
  44. class FaceAdjacencies {
  45. constructor() {
  46. this.edges = [];
  47. this.edgesConnectedCount = 0;
  48. }
  49. }
  50. /**
  51. * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
  52. */
  53. export class EdgesRenderer {
  54. /** Gets the vertices generated by the edge renderer */
  55. get linesPositions() {
  56. return this._linesPositions;
  57. }
  58. /** Gets the normals generated by the edge renderer */
  59. get linesNormals() {
  60. return this._linesNormals;
  61. }
  62. /** Gets the indices generated by the edge renderer */
  63. get linesIndices() {
  64. return this._linesIndices;
  65. }
  66. /**
  67. * Gets or sets the shader used to draw the lines
  68. */
  69. get lineShader() {
  70. return this._lineShader;
  71. }
  72. set lineShader(shader) {
  73. this._lineShader = shader;
  74. }
  75. static _GetShader(scene) {
  76. if (!scene._edgeRenderLineShader) {
  77. const shader = new ShaderMaterial("lineShader", scene, "line", {
  78. attributes: ["position", "normal"],
  79. uniforms: ["world", "viewProjection", "color", "width", "aspectRatio"],
  80. }, false);
  81. shader.disableDepthWrite = true;
  82. shader.backFaceCulling = false;
  83. shader.checkReadyOnEveryCall = scene.getEngine().isWebGPU;
  84. scene._edgeRenderLineShader = shader;
  85. }
  86. return scene._edgeRenderLineShader;
  87. }
  88. /**
  89. * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
  90. * Beware when you use this class with complex objects as the adjacencies computation can be really long
  91. * @param source Mesh used to create edges
  92. * @param epsilon sum of angles in adjacency to check for edge
  93. * @param checkVerticesInsteadOfIndices bases the edges detection on vertices vs indices. Note that this parameter is not used if options.useAlternateEdgeFinder = true
  94. * @param generateEdgesLines - should generate Lines or only prepare resources.
  95. * @param options The options to apply when generating the edges
  96. */
  97. constructor(source, epsilon = 0.95, checkVerticesInsteadOfIndices = false, generateEdgesLines = true, options) {
  98. /**
  99. * Define the size of the edges with an orthographic camera
  100. */
  101. this.edgesWidthScalerForOrthographic = 1000.0;
  102. /**
  103. * Define the size of the edges with a perspective camera
  104. */
  105. this.edgesWidthScalerForPerspective = 50.0;
  106. this._linesPositions = new Array();
  107. this._linesNormals = new Array();
  108. this._linesIndices = new Array();
  109. this._buffers = {};
  110. this._buffersForInstances = {};
  111. this._checkVerticesInsteadOfIndices = false;
  112. /** Gets or sets a boolean indicating if the edgesRenderer is active */
  113. this.isEnabled = true;
  114. /**
  115. * List of instances to render in case the source mesh has instances
  116. */
  117. this.customInstances = new SmartArray(32);
  118. this._source = source;
  119. this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
  120. this._options = options ?? null;
  121. this._epsilon = epsilon;
  122. if (this._source.getScene().getEngine().isWebGPU) {
  123. this._drawWrapper = new DrawWrapper(source.getEngine());
  124. }
  125. this._prepareRessources();
  126. if (generateEdgesLines) {
  127. if (options?.useAlternateEdgeFinder ?? true) {
  128. this._generateEdgesLinesAlternate();
  129. }
  130. else {
  131. this._generateEdgesLines();
  132. }
  133. }
  134. this._meshRebuildObserver = this._source.onRebuildObservable.add(() => {
  135. this._rebuild();
  136. });
  137. this._meshDisposeObserver = this._source.onDisposeObservable.add(() => {
  138. this.dispose();
  139. });
  140. }
  141. _prepareRessources() {
  142. if (this._lineShader) {
  143. return;
  144. }
  145. this._lineShader = EdgesRenderer._GetShader(this._source.getScene());
  146. }
  147. /** @internal */
  148. _rebuild() {
  149. let buffer = this._buffers[VertexBuffer.PositionKind];
  150. if (buffer) {
  151. buffer._rebuild();
  152. }
  153. buffer = this._buffers[VertexBuffer.NormalKind];
  154. if (buffer) {
  155. buffer._rebuild();
  156. }
  157. const scene = this._source.getScene();
  158. const engine = scene.getEngine();
  159. this._ib = engine.createIndexBuffer(this._linesIndices);
  160. }
  161. /**
  162. * Releases the required resources for the edges renderer
  163. */
  164. dispose() {
  165. this._source.onRebuildObservable.remove(this._meshRebuildObserver);
  166. this._source.onDisposeObservable.remove(this._meshDisposeObserver);
  167. let buffer = this._buffers[VertexBuffer.PositionKind];
  168. if (buffer) {
  169. buffer.dispose();
  170. this._buffers[VertexBuffer.PositionKind] = null;
  171. }
  172. buffer = this._buffers[VertexBuffer.NormalKind];
  173. if (buffer) {
  174. buffer.dispose();
  175. this._buffers[VertexBuffer.NormalKind] = null;
  176. }
  177. if (this._ib) {
  178. this._source.getScene().getEngine()._releaseBuffer(this._ib);
  179. }
  180. this._lineShader.dispose();
  181. this._drawWrapper?.dispose();
  182. }
  183. _processEdgeForAdjacencies(pa, pb, p0, p1, p2) {
  184. if ((pa === p0 && pb === p1) || (pa === p1 && pb === p0)) {
  185. return 0;
  186. }
  187. if ((pa === p1 && pb === p2) || (pa === p2 && pb === p1)) {
  188. return 1;
  189. }
  190. if ((pa === p2 && pb === p0) || (pa === p0 && pb === p2)) {
  191. return 2;
  192. }
  193. return -1;
  194. }
  195. _processEdgeForAdjacenciesWithVertices(pa, pb, p0, p1, p2) {
  196. const eps = 1e-10;
  197. if ((pa.equalsWithEpsilon(p0, eps) && pb.equalsWithEpsilon(p1, eps)) || (pa.equalsWithEpsilon(p1, eps) && pb.equalsWithEpsilon(p0, eps))) {
  198. return 0;
  199. }
  200. if ((pa.equalsWithEpsilon(p1, eps) && pb.equalsWithEpsilon(p2, eps)) || (pa.equalsWithEpsilon(p2, eps) && pb.equalsWithEpsilon(p1, eps))) {
  201. return 1;
  202. }
  203. if ((pa.equalsWithEpsilon(p2, eps) && pb.equalsWithEpsilon(p0, eps)) || (pa.equalsWithEpsilon(p0, eps) && pb.equalsWithEpsilon(p2, eps))) {
  204. return 2;
  205. }
  206. return -1;
  207. }
  208. /**
  209. * Checks if the pair of p0 and p1 is en edge
  210. * @param faceIndex
  211. * @param edge
  212. * @param faceNormals
  213. * @param p0
  214. * @param p1
  215. * @private
  216. */
  217. _checkEdge(faceIndex, edge, faceNormals, p0, p1) {
  218. let needToCreateLine;
  219. if (edge === undefined) {
  220. needToCreateLine = true;
  221. }
  222. else {
  223. const dotProduct = Vector3.Dot(faceNormals[faceIndex], faceNormals[edge]);
  224. needToCreateLine = dotProduct < this._epsilon;
  225. }
  226. if (needToCreateLine) {
  227. this.createLine(p0, p1, this._linesPositions.length / 3);
  228. }
  229. }
  230. /**
  231. * push line into the position, normal and index buffer
  232. * @param p0
  233. * @param p1
  234. * @param offset
  235. * @protected
  236. */
  237. // eslint-disable-next-line @typescript-eslint/naming-convention
  238. createLine(p0, p1, offset) {
  239. // Positions
  240. this._linesPositions.push(p0.x, p0.y, p0.z, p0.x, p0.y, p0.z, p1.x, p1.y, p1.z, p1.x, p1.y, p1.z);
  241. // Normals
  242. this._linesNormals.push(p1.x, p1.y, p1.z, -1, p1.x, p1.y, p1.z, 1, p0.x, p0.y, p0.z, -1, p0.x, p0.y, p0.z, 1);
  243. // Indices
  244. this._linesIndices.push(offset, offset + 1, offset + 2, offset, offset + 2, offset + 3);
  245. }
  246. /**
  247. * See https://playground.babylonjs.com/#R3JR6V#1 for a visual display of the algorithm
  248. * @param edgePoints
  249. * @param indexTriangle
  250. * @param indices
  251. * @param remapVertexIndices
  252. */
  253. _tessellateTriangle(edgePoints, indexTriangle, indices, remapVertexIndices) {
  254. const makePointList = (edgePoints, pointIndices, firstIndex) => {
  255. if (firstIndex >= 0) {
  256. pointIndices.push(firstIndex);
  257. }
  258. for (let i = 0; i < edgePoints.length; ++i) {
  259. pointIndices.push(edgePoints[i][0]);
  260. }
  261. };
  262. let startEdge = 0;
  263. if (edgePoints[1].length >= edgePoints[0].length && edgePoints[1].length >= edgePoints[2].length) {
  264. startEdge = 1;
  265. }
  266. else if (edgePoints[2].length >= edgePoints[0].length && edgePoints[2].length >= edgePoints[1].length) {
  267. startEdge = 2;
  268. }
  269. for (let e = 0; e < 3; ++e) {
  270. if (e === startEdge) {
  271. edgePoints[e].sort((a, b) => (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0));
  272. }
  273. else {
  274. edgePoints[e].sort((a, b) => (a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0));
  275. }
  276. }
  277. const mainPointIndices = [], otherPointIndices = [];
  278. makePointList(edgePoints[startEdge], mainPointIndices, -1);
  279. const numMainPoints = mainPointIndices.length;
  280. for (let i = startEdge + 2; i >= startEdge + 1; --i) {
  281. makePointList(edgePoints[i % 3], otherPointIndices, i !== startEdge + 2 ? remapVertexIndices[indices[indexTriangle + ((i + 1) % 3)]] : -1);
  282. }
  283. const numOtherPoints = otherPointIndices.length;
  284. const idxMain = 0;
  285. const idxOther = 0;
  286. indices.push(remapVertexIndices[indices[indexTriangle + startEdge]], mainPointIndices[0], otherPointIndices[0]);
  287. indices.push(remapVertexIndices[indices[indexTriangle + ((startEdge + 1) % 3)]], otherPointIndices[numOtherPoints - 1], mainPointIndices[numMainPoints - 1]);
  288. const bucketIsMain = numMainPoints <= numOtherPoints;
  289. const bucketStep = bucketIsMain ? numMainPoints : numOtherPoints;
  290. const bucketLimit = bucketIsMain ? numOtherPoints : numMainPoints;
  291. const bucketIdxLimit = bucketIsMain ? numMainPoints - 1 : numOtherPoints - 1;
  292. const winding = bucketIsMain ? 0 : 1;
  293. let numTris = numMainPoints + numOtherPoints - 2;
  294. let bucketIdx = bucketIsMain ? idxMain : idxOther;
  295. let nbucketIdx = bucketIsMain ? idxOther : idxMain;
  296. const bucketPoints = bucketIsMain ? mainPointIndices : otherPointIndices;
  297. const nbucketPoints = bucketIsMain ? otherPointIndices : mainPointIndices;
  298. let bucket = 0;
  299. while (numTris-- > 0) {
  300. if (winding) {
  301. indices.push(bucketPoints[bucketIdx], nbucketPoints[nbucketIdx]);
  302. }
  303. else {
  304. indices.push(nbucketPoints[nbucketIdx], bucketPoints[bucketIdx]);
  305. }
  306. bucket += bucketStep;
  307. let lastIdx;
  308. if (bucket >= bucketLimit && bucketIdx < bucketIdxLimit) {
  309. lastIdx = bucketPoints[++bucketIdx];
  310. bucket -= bucketLimit;
  311. }
  312. else {
  313. lastIdx = nbucketPoints[++nbucketIdx];
  314. }
  315. indices.push(lastIdx);
  316. }
  317. indices[indexTriangle + 0] = indices[indices.length - 3];
  318. indices[indexTriangle + 1] = indices[indices.length - 2];
  319. indices[indexTriangle + 2] = indices[indices.length - 1];
  320. indices.length = indices.length - 3;
  321. }
  322. _generateEdgesLinesAlternate() {
  323. const positions = this._source.getVerticesData(VertexBuffer.PositionKind);
  324. let indices = this._source.getIndices();
  325. if (!indices || !positions) {
  326. return;
  327. }
  328. if (!Array.isArray(indices)) {
  329. indices = Array.from(indices);
  330. }
  331. /**
  332. * Find all vertices that are at the same location (with an epsilon) and remapp them on the same vertex
  333. */
  334. const useFastVertexMerger = this._options?.useFastVertexMerger ?? true;
  335. const epsVertexMerge = useFastVertexMerger ? Math.round(-Math.log(this._options?.epsilonVertexMerge ?? 1e-6) / Math.log(10)) : this._options?.epsilonVertexMerge ?? 1e-6;
  336. const remapVertexIndices = [];
  337. const uniquePositions = []; // list of unique index of vertices - needed for tessellation
  338. if (useFastVertexMerger) {
  339. const mapVertices = {};
  340. for (let v1 = 0; v1 < positions.length; v1 += 3) {
  341. const x1 = positions[v1 + 0], y1 = positions[v1 + 1], z1 = positions[v1 + 2];
  342. const key = x1.toFixed(epsVertexMerge) + "|" + y1.toFixed(epsVertexMerge) + "|" + z1.toFixed(epsVertexMerge);
  343. if (mapVertices[key] !== undefined) {
  344. remapVertexIndices.push(mapVertices[key]);
  345. }
  346. else {
  347. const idx = v1 / 3;
  348. mapVertices[key] = idx;
  349. remapVertexIndices.push(idx);
  350. uniquePositions.push(idx);
  351. }
  352. }
  353. }
  354. else {
  355. for (let v1 = 0; v1 < positions.length; v1 += 3) {
  356. const x1 = positions[v1 + 0], y1 = positions[v1 + 1], z1 = positions[v1 + 2];
  357. let found = false;
  358. for (let v2 = 0; v2 < v1 && !found; v2 += 3) {
  359. const x2 = positions[v2 + 0], y2 = positions[v2 + 1], z2 = positions[v2 + 2];
  360. if (Math.abs(x1 - x2) < epsVertexMerge && Math.abs(y1 - y2) < epsVertexMerge && Math.abs(z1 - z2) < epsVertexMerge) {
  361. remapVertexIndices.push(v2 / 3);
  362. found = true;
  363. break;
  364. }
  365. }
  366. if (!found) {
  367. remapVertexIndices.push(v1 / 3);
  368. uniquePositions.push(v1 / 3);
  369. }
  370. }
  371. }
  372. if (this._options?.applyTessellation) {
  373. /**
  374. * Tessellate triangles if necessary:
  375. *
  376. * A
  377. * +
  378. * |\
  379. * | \
  380. * | \
  381. * E + \
  382. * /| \
  383. * / | \
  384. * / | \
  385. * +---+-------+ B
  386. * D C
  387. *
  388. * For the edges to be rendered correctly, the ABC triangle has to be split into ABE and BCE, else AC is considered to be an edge, whereas only AE should be.
  389. *
  390. * The tessellation process looks for the vertices like E that are in-between two other vertices making of an edge and create new triangles as necessary
  391. */
  392. // First step: collect the triangles to tessellate
  393. const epsVertexAligned = this._options?.epsilonVertexAligned ?? 1e-6;
  394. const mustTesselate = []; // liste of triangles that must be tessellated
  395. for (let index = 0; index < indices.length; index += 3) {
  396. // loop over all triangles
  397. let triangleToTessellate;
  398. for (let i = 0; i < 3; ++i) {
  399. // loop over the 3 edges of the triangle
  400. const p0Index = remapVertexIndices[indices[index + i]];
  401. const p1Index = remapVertexIndices[indices[index + ((i + 1) % 3)]];
  402. const p2Index = remapVertexIndices[indices[index + ((i + 2) % 3)]];
  403. if (p0Index === p1Index) {
  404. continue;
  405. } // degenerated triangle - don't process
  406. const p0x = positions[p0Index * 3 + 0], p0y = positions[p0Index * 3 + 1], p0z = positions[p0Index * 3 + 2];
  407. const p1x = positions[p1Index * 3 + 0], p1y = positions[p1Index * 3 + 1], p1z = positions[p1Index * 3 + 2];
  408. const p0p1 = Math.sqrt((p1x - p0x) * (p1x - p0x) + (p1y - p0y) * (p1y - p0y) + (p1z - p0z) * (p1z - p0z));
  409. for (let v = 0; v < uniquePositions.length - 1; v++) {
  410. // loop over all (unique) vertices and look for the ones that would be in-between p0 and p1
  411. const vIndex = uniquePositions[v];
  412. if (vIndex === p0Index || vIndex === p1Index || vIndex === p2Index) {
  413. continue;
  414. } // don't handle the vertex if it is a vertex of the current triangle
  415. const x = positions[vIndex * 3 + 0], y = positions[vIndex * 3 + 1], z = positions[vIndex * 3 + 2];
  416. const p0p = Math.sqrt((x - p0x) * (x - p0x) + (y - p0y) * (y - p0y) + (z - p0z) * (z - p0z));
  417. const pp1 = Math.sqrt((x - p1x) * (x - p1x) + (y - p1y) * (y - p1y) + (z - p1z) * (z - p1z));
  418. if (Math.abs(p0p + pp1 - p0p1) < epsVertexAligned) {
  419. // vertices are aligned and p in-between p0 and p1 if distance(p0, p) + distance (p, p1) ~ distance(p0, p1)
  420. if (!triangleToTessellate) {
  421. triangleToTessellate = {
  422. index: index,
  423. edgesPoints: [[], [], []],
  424. };
  425. mustTesselate.push(triangleToTessellate);
  426. }
  427. triangleToTessellate.edgesPoints[i].push([vIndex, p0p]);
  428. }
  429. }
  430. }
  431. }
  432. // Second step: tesselate the triangles
  433. for (let t = 0; t < mustTesselate.length; ++t) {
  434. const triangle = mustTesselate[t];
  435. this._tessellateTriangle(triangle.edgesPoints, triangle.index, indices, remapVertexIndices);
  436. }
  437. mustTesselate.length = 0;
  438. }
  439. /**
  440. * Collect the edges to render
  441. */
  442. const edges = {};
  443. for (let index = 0; index < indices.length; index += 3) {
  444. let faceNormal;
  445. for (let i = 0; i < 3; ++i) {
  446. let p0Index = remapVertexIndices[indices[index + i]];
  447. let p1Index = remapVertexIndices[indices[index + ((i + 1) % 3)]];
  448. const p2Index = remapVertexIndices[indices[index + ((i + 2) % 3)]];
  449. if (p0Index === p1Index || ((p0Index === p2Index || p1Index === p2Index) && this._options?.removeDegeneratedTriangles)) {
  450. continue;
  451. }
  452. TmpVectors.Vector3[0].copyFromFloats(positions[p0Index * 3 + 0], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
  453. TmpVectors.Vector3[1].copyFromFloats(positions[p1Index * 3 + 0], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
  454. TmpVectors.Vector3[2].copyFromFloats(positions[p2Index * 3 + 0], positions[p2Index * 3 + 1], positions[p2Index * 3 + 2]);
  455. if (!faceNormal) {
  456. TmpVectors.Vector3[1].subtractToRef(TmpVectors.Vector3[0], TmpVectors.Vector3[3]);
  457. TmpVectors.Vector3[2].subtractToRef(TmpVectors.Vector3[1], TmpVectors.Vector3[4]);
  458. faceNormal = Vector3.Cross(TmpVectors.Vector3[3], TmpVectors.Vector3[4]);
  459. faceNormal.normalize();
  460. }
  461. if (p0Index > p1Index) {
  462. const tmp = p0Index;
  463. p0Index = p1Index;
  464. p1Index = tmp;
  465. }
  466. const key = p0Index + "_" + p1Index;
  467. const ei = edges[key];
  468. if (ei) {
  469. if (!ei.done) {
  470. const dotProduct = Vector3.Dot(faceNormal, ei.normal);
  471. if (dotProduct < this._epsilon) {
  472. this.createLine(TmpVectors.Vector3[0], TmpVectors.Vector3[1], this._linesPositions.length / 3);
  473. }
  474. ei.done = true;
  475. }
  476. }
  477. else {
  478. edges[key] = { normal: faceNormal, done: false, index: index, i: i };
  479. }
  480. }
  481. }
  482. for (const key in edges) {
  483. const ei = edges[key];
  484. if (!ei.done) {
  485. // Orphaned edge - we must display it
  486. const p0Index = remapVertexIndices[indices[ei.index + ei.i]];
  487. const p1Index = remapVertexIndices[indices[ei.index + ((ei.i + 1) % 3)]];
  488. TmpVectors.Vector3[0].copyFromFloats(positions[p0Index * 3 + 0], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
  489. TmpVectors.Vector3[1].copyFromFloats(positions[p1Index * 3 + 0], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
  490. this.createLine(TmpVectors.Vector3[0], TmpVectors.Vector3[1], this._linesPositions.length / 3);
  491. }
  492. }
  493. /**
  494. * Merge into a single mesh
  495. */
  496. const engine = this._source.getScene().getEngine();
  497. this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
  498. this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
  499. this._buffersForInstances[VertexBuffer.PositionKind] = this._buffers[VertexBuffer.PositionKind];
  500. this._buffersForInstances[VertexBuffer.NormalKind] = this._buffers[VertexBuffer.NormalKind];
  501. this._ib = engine.createIndexBuffer(this._linesIndices);
  502. this._indicesCount = this._linesIndices.length;
  503. }
  504. /**
  505. * Generates lines edges from adjacencjes
  506. * @private
  507. */
  508. _generateEdgesLines() {
  509. const positions = this._source.getVerticesData(VertexBuffer.PositionKind);
  510. const indices = this._source.getIndices();
  511. if (!indices || !positions) {
  512. return;
  513. }
  514. // First let's find adjacencies
  515. const adjacencies = [];
  516. const faceNormals = [];
  517. let index;
  518. let faceAdjacencies;
  519. // Prepare faces
  520. for (index = 0; index < indices.length; index += 3) {
  521. faceAdjacencies = new FaceAdjacencies();
  522. const p0Index = indices[index];
  523. const p1Index = indices[index + 1];
  524. const p2Index = indices[index + 2];
  525. faceAdjacencies.p0 = new Vector3(positions[p0Index * 3], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
  526. faceAdjacencies.p1 = new Vector3(positions[p1Index * 3], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
  527. faceAdjacencies.p2 = new Vector3(positions[p2Index * 3], positions[p2Index * 3 + 1], positions[p2Index * 3 + 2]);
  528. const faceNormal = Vector3.Cross(faceAdjacencies.p1.subtract(faceAdjacencies.p0), faceAdjacencies.p2.subtract(faceAdjacencies.p1));
  529. faceNormal.normalize();
  530. faceNormals.push(faceNormal);
  531. adjacencies.push(faceAdjacencies);
  532. }
  533. // Scan
  534. for (index = 0; index < adjacencies.length; index++) {
  535. faceAdjacencies = adjacencies[index];
  536. for (let otherIndex = index + 1; otherIndex < adjacencies.length; otherIndex++) {
  537. const otherFaceAdjacencies = adjacencies[otherIndex];
  538. if (faceAdjacencies.edgesConnectedCount === 3) {
  539. // Full
  540. break;
  541. }
  542. if (otherFaceAdjacencies.edgesConnectedCount === 3) {
  543. // Full
  544. continue;
  545. }
  546. const otherP0 = indices[otherIndex * 3];
  547. const otherP1 = indices[otherIndex * 3 + 1];
  548. const otherP2 = indices[otherIndex * 3 + 2];
  549. for (let edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
  550. let otherEdgeIndex = 0;
  551. if (faceAdjacencies.edges[edgeIndex] !== undefined) {
  552. continue;
  553. }
  554. switch (edgeIndex) {
  555. case 0:
  556. if (this._checkVerticesInsteadOfIndices) {
  557. otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p0, faceAdjacencies.p1, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
  558. }
  559. else {
  560. otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3], indices[index * 3 + 1], otherP0, otherP1, otherP2);
  561. }
  562. break;
  563. case 1:
  564. if (this._checkVerticesInsteadOfIndices) {
  565. otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p1, faceAdjacencies.p2, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
  566. }
  567. else {
  568. otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3 + 1], indices[index * 3 + 2], otherP0, otherP1, otherP2);
  569. }
  570. break;
  571. case 2:
  572. if (this._checkVerticesInsteadOfIndices) {
  573. otherEdgeIndex = this._processEdgeForAdjacenciesWithVertices(faceAdjacencies.p2, faceAdjacencies.p0, otherFaceAdjacencies.p0, otherFaceAdjacencies.p1, otherFaceAdjacencies.p2);
  574. }
  575. else {
  576. otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3 + 2], indices[index * 3], otherP0, otherP1, otherP2);
  577. }
  578. break;
  579. }
  580. if (otherEdgeIndex === -1) {
  581. continue;
  582. }
  583. faceAdjacencies.edges[edgeIndex] = otherIndex;
  584. otherFaceAdjacencies.edges[otherEdgeIndex] = index;
  585. faceAdjacencies.edgesConnectedCount++;
  586. otherFaceAdjacencies.edgesConnectedCount++;
  587. if (faceAdjacencies.edgesConnectedCount === 3) {
  588. break;
  589. }
  590. }
  591. }
  592. }
  593. // Create lines
  594. for (index = 0; index < adjacencies.length; index++) {
  595. // 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
  596. const current = adjacencies[index];
  597. this._checkEdge(index, current.edges[0], faceNormals, current.p0, current.p1);
  598. this._checkEdge(index, current.edges[1], faceNormals, current.p1, current.p2);
  599. this._checkEdge(index, current.edges[2], faceNormals, current.p2, current.p0);
  600. }
  601. // Merge into a single mesh
  602. const engine = this._source.getScene().getEngine();
  603. this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
  604. this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
  605. this._buffersForInstances[VertexBuffer.PositionKind] = this._buffers[VertexBuffer.PositionKind];
  606. this._buffersForInstances[VertexBuffer.NormalKind] = this._buffers[VertexBuffer.NormalKind];
  607. this._ib = engine.createIndexBuffer(this._linesIndices);
  608. this._indicesCount = this._linesIndices.length;
  609. }
  610. /**
  611. * Checks whether or not the edges renderer is ready to render.
  612. * @returns true if ready, otherwise false.
  613. */
  614. isReady() {
  615. return this._lineShader.isReady(this._source, (this._source.hasInstances && this.customInstances.length > 0) || this._source.hasThinInstances);
  616. }
  617. /**
  618. * Renders the edges of the attached mesh,
  619. */
  620. render() {
  621. const scene = this._source.getScene();
  622. const currentDrawWrapper = this._lineShader._getDrawWrapper();
  623. if (this._drawWrapper) {
  624. this._lineShader._setDrawWrapper(this._drawWrapper);
  625. }
  626. if (!this.isReady() || !scene.activeCamera) {
  627. this._lineShader._setDrawWrapper(currentDrawWrapper);
  628. return;
  629. }
  630. const hasInstances = this._source.hasInstances && this.customInstances.length > 0;
  631. const useBuffersWithInstances = hasInstances || this._source.hasThinInstances;
  632. let instanceCount = 0;
  633. if (useBuffersWithInstances) {
  634. this._buffersForInstances["world0"] = this._source.getVertexBuffer("world0");
  635. this._buffersForInstances["world1"] = this._source.getVertexBuffer("world1");
  636. this._buffersForInstances["world2"] = this._source.getVertexBuffer("world2");
  637. this._buffersForInstances["world3"] = this._source.getVertexBuffer("world3");
  638. if (hasInstances) {
  639. const instanceStorage = this._source._instanceDataStorage;
  640. instanceCount = this.customInstances.length;
  641. if (!instanceStorage.instancesData) {
  642. if (!this._source.getScene()._activeMeshesFrozen) {
  643. this.customInstances.reset();
  644. }
  645. return;
  646. }
  647. if (!instanceStorage.isFrozen) {
  648. let offset = 0;
  649. for (let i = 0; i < instanceCount; ++i) {
  650. this.customInstances.data[i].copyToArray(instanceStorage.instancesData, offset);
  651. offset += 16;
  652. }
  653. instanceStorage.instancesBuffer.updateDirectly(instanceStorage.instancesData, 0, instanceCount);
  654. }
  655. }
  656. else {
  657. instanceCount = this._source.thinInstanceCount;
  658. }
  659. }
  660. const engine = scene.getEngine();
  661. this._lineShader._preBind();
  662. if (this._source.edgesColor.a !== 1) {
  663. engine.setAlphaMode(2);
  664. }
  665. else {
  666. engine.setAlphaMode(0);
  667. }
  668. // VBOs
  669. engine.bindBuffers(useBuffersWithInstances ? this._buffersForInstances : this._buffers, this._ib, this._lineShader.getEffect());
  670. scene.resetCachedMaterial();
  671. this._lineShader.setColor4("color", this._source.edgesColor);
  672. if (scene.activeCamera.mode === Camera.ORTHOGRAPHIC_CAMERA) {
  673. this._lineShader.setFloat("width", this._source.edgesWidth / this.edgesWidthScalerForOrthographic);
  674. }
  675. else {
  676. this._lineShader.setFloat("width", this._source.edgesWidth / this.edgesWidthScalerForPerspective);
  677. }
  678. this._lineShader.setFloat("aspectRatio", engine.getAspectRatio(scene.activeCamera));
  679. this._lineShader.bind(this._source.getWorldMatrix());
  680. // Draw order
  681. engine.drawElementsType(Material.TriangleFillMode, 0, this._indicesCount, instanceCount);
  682. this._lineShader.unbind();
  683. if (useBuffersWithInstances) {
  684. engine.unbindInstanceAttributes();
  685. }
  686. if (!this._source.getScene()._activeMeshesFrozen) {
  687. this.customInstances.reset();
  688. }
  689. this._lineShader._setDrawWrapper(currentDrawWrapper);
  690. }
  691. }
  692. /**
  693. * LineEdgesRenderer for LineMeshes to remove unnecessary triangulation
  694. */
  695. export class LineEdgesRenderer extends EdgesRenderer {
  696. /**
  697. * This constructor turns off auto generating edges line in Edges Renderer to make it here.
  698. * @param source LineMesh used to generate edges
  699. * @param epsilon not important (specified angle for edge detection)
  700. * @param checkVerticesInsteadOfIndices not important for LineMesh
  701. */
  702. constructor(source, epsilon = 0.95, checkVerticesInsteadOfIndices = false) {
  703. super(source, epsilon, checkVerticesInsteadOfIndices, false);
  704. this._generateEdgesLines();
  705. }
  706. /**
  707. * Generate edges for each line in LinesMesh. Every Line should be rendered as edge.
  708. */
  709. _generateEdgesLines() {
  710. const positions = this._source.getVerticesData(VertexBuffer.PositionKind);
  711. const indices = this._source.getIndices();
  712. if (!indices || !positions) {
  713. return;
  714. }
  715. const p0 = TmpVectors.Vector3[0];
  716. const p1 = TmpVectors.Vector3[1];
  717. const len = indices.length - 1;
  718. for (let i = 0, offset = 0; i < len; i += 2, offset += 4) {
  719. Vector3.FromArrayToRef(positions, 3 * indices[i], p0);
  720. Vector3.FromArrayToRef(positions, 3 * indices[i + 1], p1);
  721. this.createLine(p0, p1, offset);
  722. }
  723. // Merge into a single mesh
  724. const engine = this._source.getScene().getEngine();
  725. this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
  726. this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
  727. this._ib = engine.createIndexBuffer(this._linesIndices);
  728. this._indicesCount = this._linesIndices.length;
  729. }
  730. }
  731. //# sourceMappingURL=edgesRenderer.js.map