Browse Source

Merge branch 'master' of https://github.com/BabylonJS/Babylon.js

David `Deltakosh` Catuhe 5 năm trước cách đây
mục cha
commit
8df89706a7
3 tập tin đã thay đổi với 362 bổ sung8 xóa
  1. 1 0
      dist/preview release/what's new.md
  2. 1 0
      readme-es6.md
  3. 360 8
      src/Rendering/edgesRenderer.ts

+ 1 - 0
dist/preview release/what's new.md

@@ -29,6 +29,7 @@
 - Added initial code for user facing DeviceSourceManager ([PolygonalSun](https://github.com/PolygonalSun))
 - Added a Simple and advanced timer, based on observables ([RaananW](https://github.com/RaananW))
 - Don't log a message in `CustomProceduralTexture` if the `texturePath` is not a JSON path ([Popov72](https://github.com/Popov72))
+- Added an alternate option to the mesh edge renderer to generate edges faster / more accurately for unusual geometries (like the ones generated by CSG) ([Popov72](https://github.com/Popov72))
 
 ### Engine
 

+ 1 - 0
readme-es6.md

@@ -55,6 +55,7 @@ import "@babylonjs/core/Materials/standardMaterial";
 // Side-effects only imports allowing Mesh to create default shapes (to enhance tree shaking, the construction methods on mesh are not available if the meshbuilder has not been imported).
 import "@babylonjs/core/Meshes/Builders/sphereBuilder";
 import "@babylonjs/core/Meshes/Builders/boxBuilder";
+import "@babylonjs/core/Meshes/Builders/groundBuilder";
 
 const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
 const engine = new Engine(canvas);

+ 360 - 8
src/Rendering/edgesRenderer.ts

@@ -41,9 +41,9 @@ AbstractMesh.prototype.disableEdgesRendering = function(): AbstractMesh {
     return this;
 };
 
-AbstractMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): AbstractMesh {
+AbstractMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false, options?: IEdgesRendererOptions): AbstractMesh {
     this.disableEdgesRendering();
-    this._edgesRenderer = new EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
+    this._edgesRenderer = new EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices, true, options);
     return this;
 };
 
@@ -131,6 +131,46 @@ export interface IEdgesRenderer extends IDisposable {
 }
 
 /**
+ * Defines the additional options of the edges renderer
+ */
+export interface IEdgesRendererOptions {
+    /**
+     * Gets or sets a boolean indicating that the alternate edge finder algorithm must be used
+     * If not defined, the default value is true
+     */
+    useAlternateEdgeFinder?: boolean;
+
+    /**
+     * Gets or sets a boolean indicating that the vertex merger fast processing must be used.
+     * If not defined, the default value is true.
+     * You should normally leave it undefined (or set it to true), except if you see some artifacts in the edges rendering (can happen with complex geometries)
+     * This option is used only if useAlternateEdgeFinder = true
+     */
+    useFastVertexMerger?: boolean;
+
+    /**
+     * During edges processing, the vertices are merged if they are close enough: epsilonVertexMerge is the limit whithin which vertices are considered to be equal.
+     * The default value is 1e-6
+     * This option is used only if useAlternateEdgeFinder = true
+     */
+    epsilonVertexMerge?: number;
+
+    /**
+     * Gets or sets a boolean indicating that tessellation should be applied before finding the edges. You may need to activate this option if your geometry is a bit
+     * unusual, like having a vertex of a triangle in-between two vertices of an edge of another triangle. It happens often when using CSG to construct meshes.
+     * This option is used only if useAlternateEdgeFinder = true
+     */
+    applyTessellation?: boolean;
+
+    /**
+     * The limit under which 3 vertices are considered to be aligned. 3 vertices PQR are considered aligned if distance(PQ) + distance(QR) - distance(PR) < epsilonVertexAligned
+     * The default value is 1e-6
+     * This option is used only if useAlternateEdgeFinder = true
+     */
+    epsilonVertexAligned?: number;
+}
+
+/**
  * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
  */
 export class EdgesRenderer implements IEdgesRenderer {
@@ -157,6 +197,7 @@ export class EdgesRenderer implements IEdgesRenderer {
     protected _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
     protected _buffersForInstances: { [key: string]: Nullable<VertexBuffer> } = {};
     protected _checkVerticesInsteadOfIndices = false;
+    protected _options: Nullable<IEdgesRendererOptions>;
 
     private _meshRebuildObserver: Nullable<Observer<AbstractMesh>>;
     private _meshDisposeObserver: Nullable<Observer<Node>>;
@@ -191,18 +232,24 @@ export class EdgesRenderer implements IEdgesRenderer {
      * Beware when you use this class with complex objects as the adjacencies computation can be really long
      * @param  source Mesh used to create edges
      * @param  epsilon sum of angles in adjacency to check for edge
-     * @param  checkVerticesInsteadOfIndices bases the edges detection on vertices vs indices
+     * @param  checkVerticesInsteadOfIndices bases the edges detection on vertices vs indices. Note that this parameter is not used if options.useAlternateEdgeFinder = true
      * @param  generateEdgesLines - should generate Lines or only prepare resources.
+     * @param  options The options to apply when generating the edges
      */
-    constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false, generateEdgesLines = true) {
+    constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false, generateEdgesLines = true, options?: IEdgesRendererOptions) {
         this._source = source;
         this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
+        this._options = options ?? null;
 
         this._epsilon = epsilon;
 
         this._prepareRessources();
         if (generateEdgesLines) {
-            this._generateEdgesLines();
+            if (options?.useAlternateEdgeFinder ?? true) {
+                this._generateEdgesLinesAlternate();
+            } else {
+                this._generateEdgesLines();
+            }
         }
 
         this._meshRebuildObserver = this._source.onRebuildObservable.add(() => {
@@ -280,15 +327,16 @@ export class EdgesRenderer implements IEdgesRenderer {
     }
 
     protected _processEdgeForAdjacenciesWithVertices(pa: Vector3, pb: Vector3, p0: Vector3, p1: Vector3, p2: Vector3): number {
-        if (pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p1) || pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p0)) {
+        const eps = 1e-10;
+        if (pa.equalsWithEpsilon(p0, eps) && pb.equalsWithEpsilon(p1, eps) || pa.equalsWithEpsilon(p1, eps) && pb.equalsWithEpsilon(p0, eps)) {
             return 0;
         }
 
-        if (pa.equalsWithEpsilon(p1) && pb.equalsWithEpsilon(p2) || pa.equalsWithEpsilon(p2) && pb.equalsWithEpsilon(p1)) {
+        if (pa.equalsWithEpsilon(p1, eps) && pb.equalsWithEpsilon(p2, eps) || pa.equalsWithEpsilon(p2, eps) && pb.equalsWithEpsilon(p1, eps)) {
             return 1;
         }
 
-        if (pa.equalsWithEpsilon(p2) && pb.equalsWithEpsilon(p0) || pa.equalsWithEpsilon(p0) && pb.equalsWithEpsilon(p2)) {
+        if (pa.equalsWithEpsilon(p2, eps) && pb.equalsWithEpsilon(p0, eps) || pa.equalsWithEpsilon(p0, eps) && pb.equalsWithEpsilon(p2, eps)) {
             return 2;
         }
 
@@ -349,6 +397,310 @@ export class EdgesRenderer implements IEdgesRenderer {
     }
 
     /**
+     * See https://playground.babylonjs.com/#R3JR6V#1 for a visual display of the algorithm
+     */
+    private _tessellateTriangle(edgePoints: Array<Array<[number, number]>>, indexTriangle: number, indices: Array<number>, remapVertexIndices: Array<number>): void {
+
+        const makePointList = (edgePoints: Array<[number, number]>, pointIndices: Array<number>, firstIndex: number) => {
+            if (firstIndex >= 0) {
+                pointIndices.push(firstIndex);
+            }
+
+            for (let i = 0; i < edgePoints.length; ++i) {
+                pointIndices.push(edgePoints[i][0]);
+            }
+        };
+
+        let startEdge = 0;
+
+        if (edgePoints[1].length >= edgePoints[0].length && edgePoints[1].length >= edgePoints[2].length) {
+            startEdge = 1;
+        } else if (edgePoints[2].length >= edgePoints[0].length && edgePoints[2].length >= edgePoints[1].length) {
+            startEdge = 2;
+        }
+
+        for (let e = 0; e < 3; ++e) {
+            if (e === startEdge) {
+                edgePoints[e].sort((a, b) => a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0);
+            } else {
+                edgePoints[e].sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0);
+            }
+        }
+
+        const mainPointIndices: Array<number> = [], otherPointIndices: Array<number> = [];
+
+        makePointList(edgePoints[startEdge], mainPointIndices, -1);
+
+        let numMainPoints = mainPointIndices.length;
+
+        for (let i = startEdge + 2; i >= startEdge + 1; --i) {
+            makePointList(edgePoints[i % 3], otherPointIndices, i !== startEdge + 2 ? remapVertexIndices[indices[indexTriangle + ((i + 1) % 3)]] : -1);
+        }
+
+        const numOtherPoints = otherPointIndices.length;
+
+        let idxMain = 0;
+        let idxOther = 0;
+
+        indices.push(remapVertexIndices[indices[indexTriangle + startEdge]], mainPointIndices[0], otherPointIndices[0]);
+        indices.push(remapVertexIndices[indices[indexTriangle + ((startEdge + 1) % 3)]], otherPointIndices[numOtherPoints - 1], mainPointIndices[numMainPoints - 1]);
+
+        const bucketIsMain = numMainPoints <= numOtherPoints;
+
+        const bucketStep = bucketIsMain ? numMainPoints : numOtherPoints;
+        const bucketLimit = bucketIsMain ? numOtherPoints : numMainPoints;
+        const bucketIdxLimit = bucketIsMain ? numMainPoints - 1 : numOtherPoints - 1;
+        const winding = bucketIsMain ? 0 : 1;
+
+        let numTris = numMainPoints + numOtherPoints - 2;
+
+        let bucketIdx = bucketIsMain ? idxMain : idxOther;
+        let nbucketIdx = bucketIsMain ? idxOther : idxMain;
+        let bucketPoints = bucketIsMain ? mainPointIndices : otherPointIndices;
+        let nbucketPoints = bucketIsMain ? otherPointIndices : mainPointIndices;
+
+        let bucket = 0;
+
+        while (numTris-- > 0) {
+            if (winding) {
+                indices.push(bucketPoints[bucketIdx], nbucketPoints[nbucketIdx]);
+            } else {
+                indices.push(nbucketPoints[nbucketIdx], bucketPoints[bucketIdx]);
+            }
+
+            bucket += bucketStep;
+
+            let lastIdx;
+
+            if (bucket >= bucketLimit && bucketIdx < bucketIdxLimit) {
+                lastIdx = bucketPoints[++bucketIdx];
+                bucket -= bucketLimit;
+            } else {
+                lastIdx = nbucketPoints[++nbucketIdx];
+            }
+
+            indices.push(lastIdx);
+        }
+
+        indices[indexTriangle + 0] = indices[indices.length - 3];
+        indices[indexTriangle + 1] = indices[indices.length - 2];
+        indices[indexTriangle + 2] = indices[indices.length - 1];
+
+        indices.length = indices.length - 3;
+    }
+
+    private _generateEdgesLinesAlternate(): void {
+        var positions = this._source.getVerticesData(VertexBuffer.PositionKind);
+        var indices = this._source.getIndices();
+
+        if (!indices || !positions) {
+            return;
+        }
+
+        if (!Array.isArray(indices)) {
+            indices = Array.from(indices);
+        }
+
+        /**
+         * Find all vertices that are at the same location (with an epsilon) and remapp them on the same vertex
+         */
+        const useFastVertexMerger = this._options?.useFastVertexMerger ?? true;
+        const epsVertexMerge = useFastVertexMerger ? Math.round(-Math.log(this._options?.epsilonVertexMerge ?? 1e-6) / Math.log(10)) : this._options?.epsilonVertexMerge ?? 1e-6;
+        const remapVertexIndices: Array<number> = [];
+        const uniquePositions: Array<number> = []; // list of unique index of vertices - needed for tessellation
+
+        if (useFastVertexMerger) {
+            const mapVertices: { [key: string]: number} = {};
+            for (let v1 = 0; v1 < positions.length; v1 += 3) {
+                const x1 = positions[v1 + 0], y1 = positions[v1 + 1], z1 = positions[v1 + 2];
+
+                const key = x1.toFixed(epsVertexMerge) + "|" + y1.toFixed(epsVertexMerge) + "|" + z1.toFixed(epsVertexMerge);
+
+                if (mapVertices[key] !== undefined) {
+                    remapVertexIndices.push(mapVertices[key]);
+                } else {
+                    const idx = v1 / 3;
+                    mapVertices[key] = idx;
+                    remapVertexIndices.push(idx);
+                    uniquePositions.push(idx);
+                }
+            }
+        } else {
+            for (let v1 = 0; v1 < positions.length; v1 += 3) {
+                const x1 = positions[v1 + 0], y1 = positions[v1 + 1], z1 = positions[v1 + 2];
+                let found = false;
+                for (let v2 = 0; v2 < v1 && !found; v2 += 3) {
+                    const x2 = positions[v2 + 0], y2 = positions[v2 + 1], z2 = positions[v2 + 2];
+
+                    if (Math.abs(x1 - x2) < epsVertexMerge && Math.abs(y1 - y2) < epsVertexMerge && Math.abs(z1 - z2) < epsVertexMerge) {
+                        remapVertexIndices.push(v2 / 3);
+                        found = true;
+                        break;
+                    }
+                }
+
+                if (!found) {
+                    remapVertexIndices.push(v1 / 3);
+                    uniquePositions.push(v1 / 3);
+                }
+            }
+        }
+
+        if (this._options?.applyTessellation) {
+            /**
+             * Tessellate triangles if necessary:
+             *
+             *               A
+             *               +
+             *               |\
+             *               | \
+             *               |  \
+             *             E +   \
+             *              /|    \
+             *             / |     \
+             *            /  |      \
+             *           +---+-------+ B
+             *           D   C
+             *
+             * 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.
+             *
+             * 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
+             */
+
+            // First step: collect the triangles to tessellate
+            const epsVertexAligned = this._options?.epsilonVertexAligned ?? 1e-6;
+            const mustTesselate: Array<{ index: number, edgesPoints: Array<Array<[number, number]>> }> = []; // liste of triangles that must be tessellated
+
+            for (let index = 0; index < indices.length; index += 3) { // loop over all triangles
+                let triangleToTessellate: { index: number, edgesPoints: Array<Array<[number, number]>> } | undefined;
+
+                for (let i = 0; i < 3; ++i) { // loop over the 3 edges of the triangle
+                    let p0Index = remapVertexIndices[indices[index + i]];
+                    let p1Index = remapVertexIndices[indices[index + (i + 1) % 3]];
+                    let p2Index = remapVertexIndices[indices[index + (i + 2) % 3]];
+
+                    if (p0Index === p1Index) { continue; } // degenerated triangle - don't process
+
+                    const p0x = positions[p0Index * 3 + 0], p0y = positions[p0Index * 3 + 1], p0z = positions[p0Index * 3 + 2];
+                    const p1x = positions[p1Index * 3 + 0], p1y = positions[p1Index * 3 + 1], p1z = positions[p1Index * 3 + 2];
+
+                    const p0p1 = Math.sqrt((p1x - p0x) * (p1x - p0x) + (p1y - p0y) * (p1y - p0y) + (p1z - p0z) * (p1z - p0z));
+
+                    for (let v = 0; v < uniquePositions.length - 1; v++) { // loop over all (unique) vertices and look for the ones that would be in-between p0 and p1
+                        const vIndex = uniquePositions[v];
+
+                        if (vIndex === p0Index || vIndex === p1Index || vIndex === p2Index) { continue; } // don't handle the vertex if it is a vertex of the current triangle
+
+                        const x = positions[vIndex * 3 + 0], y = positions[vIndex * 3 + 1], z = positions[vIndex * 3 + 2];
+
+                        const p0p = Math.sqrt((x - p0x) * (x - p0x) + (y - p0y) * (y - p0y) + (z - p0z) * (z - p0z));
+                        const pp1 = Math.sqrt((x - p1x) * (x - p1x) + (y - p1y) * (y - p1y) + (z - p1z) * (z - p1z));
+
+                        if (Math.abs(p0p + pp1 - p0p1) < epsVertexAligned) { // vertices are aligned and p in-between p0 and p1 if distance(p0, p) + distance (p, p1) ~ distance(p0, p1)
+                            if (!triangleToTessellate) {
+                                triangleToTessellate = {
+                                    index: index,
+                                    edgesPoints: [[], [], []],
+                                };
+                                mustTesselate.push(triangleToTessellate);
+                            }
+                            triangleToTessellate.edgesPoints[i].push([vIndex, p0p]);
+                        }
+                    }
+                }
+            }
+
+            // Second step: tesselate the triangles
+            for (let t = 0; t < mustTesselate.length; ++t) {
+                const triangle = mustTesselate[t];
+
+                this._tessellateTriangle(triangle.edgesPoints, triangle.index, indices, remapVertexIndices);
+            }
+
+            (mustTesselate as any) = null;
+        }
+
+        /**
+         * Collect the edges to render
+         */
+        const edges: { [key: string] : { normal: Vector3, done: boolean, index: number, i: number } } = { };
+
+        for (let index = 0; index < indices.length; index += 3) {
+            let faceNormal;
+            for (let i = 0; i < 3; ++i) {
+                let p0Index = remapVertexIndices[indices[index + i]];
+                let p1Index = remapVertexIndices[indices[index + (i + 1) % 3]];
+                let p2Index = remapVertexIndices[indices[index + (i + 2) % 3]];
+
+                if (p0Index === p1Index) { continue; }
+
+                TmpVectors.Vector3[0].copyFromFloats(positions[p0Index * 3 + 0], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
+                TmpVectors.Vector3[1].copyFromFloats(positions[p1Index * 3 + 0], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
+                TmpVectors.Vector3[2].copyFromFloats(positions[p2Index * 3 + 0], positions[p2Index * 3 + 1], positions[p2Index * 3 + 2]);
+
+                if (!faceNormal) {
+                    TmpVectors.Vector3[1].subtractToRef(TmpVectors.Vector3[0], TmpVectors.Vector3[3]);
+                    TmpVectors.Vector3[2].subtractToRef(TmpVectors.Vector3[1], TmpVectors.Vector3[4]);
+                    faceNormal = Vector3.Cross(TmpVectors.Vector3[3], TmpVectors.Vector3[4]);
+                    faceNormal.normalize();
+                }
+
+                if (p0Index > p1Index) {
+                    const tmp = p0Index;
+                    p0Index = p1Index;
+                    p1Index = tmp;
+                }
+
+                const key = p0Index + "_" + p1Index;
+                const ei = edges[key];
+
+                if (ei) {
+                    if (!ei.done) {
+                        const dotProduct = Vector3.Dot(faceNormal, ei.normal);
+
+                        if (dotProduct < this._epsilon) {
+                            this.createLine(TmpVectors.Vector3[0], TmpVectors.Vector3[1], this._linesPositions.length / 3);
+                        }
+
+                        ei.done = true;
+                    }
+                } else {
+                    edges[key] = { normal: faceNormal, done: false, index: index, i: i };
+                }
+            }
+        }
+
+        for (const key in edges) {
+            const ei = edges[key];
+            if (!ei.done) {
+                // Orphaned edge - we must display it
+                let p0Index = remapVertexIndices[indices[ei.index + ei.i]];
+                let p1Index = remapVertexIndices[indices[ei.index + (ei.i + 1) % 3]];
+
+                TmpVectors.Vector3[0].copyFromFloats(positions[p0Index * 3 + 0], positions[p0Index * 3 + 1], positions[p0Index * 3 + 2]);
+                TmpVectors.Vector3[1].copyFromFloats(positions[p1Index * 3 + 0], positions[p1Index * 3 + 1], positions[p1Index * 3 + 2]);
+
+                this.createLine(TmpVectors.Vector3[0], TmpVectors.Vector3[1], this._linesPositions.length / 3);
+            }
+        }
+
+        /**
+         * Merge into a single mesh
+         */
+        var engine = this._source.getScene().getEngine();
+
+        this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
+        this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
+
+        this._buffersForInstances[VertexBuffer.PositionKind] = this._buffers[VertexBuffer.PositionKind];
+        this._buffersForInstances[VertexBuffer.NormalKind] = this._buffers[VertexBuffer.NormalKind];
+
+        this._ib = engine.createIndexBuffer(this._linesIndices);
+
+        this._indicesCount = this._linesIndices.length;
+    }
+
+    /**
      * Generates lines edges from adjacencjes
      * @private
      */