Browse Source

EdgesRenderer done

David catuhe 10 years ago
parent
commit
ad1ec3da1e

File diff suppressed because it is too large
+ 862 - 852
dist/preview release - beta/babylon.2.2.d.ts


File diff suppressed because it is too large
+ 22 - 23
dist/preview release - beta/babylon.2.2.js


File diff suppressed because it is too large
+ 153 - 89
dist/preview release - beta/babylon.2.2.max.js


File diff suppressed because it is too large
+ 22 - 23
dist/preview release - beta/babylon.2.2.noworker.js


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

@@ -7,6 +7,7 @@
     - Revamping of FBX exporter. Now supports animations and bones [simonferquel](http://www.github.com/simonferquel), [deltakosh](https://github.com/deltakosh)
     - StandardMaterial.useGlossinessFromSpecularMapAlpha to use specular map alpha as glossiness level [deltakosh](https://github.com/deltakosh)
     - OBJ loader. See [demo here](http://www.babylonjs-playground.com/#28YUR5) [Temechon](https://github.com/Temechon)
+    - EdgesRenderer. See [demo here](http://www.babylonjs-playground.com/#TYAHX#10) [deltakosh](https://github.com/deltakosh)
   - **Updates**
     - Added darkness support for soft shadows [deltakosh](https://github.com/deltakosh)
     - Added scene.getLensFlareSystemByName() [deltakosh](https://github.com/deltakosh)

+ 14 - 21
src/Mesh/babylon.abstractMesh.js

@@ -54,9 +54,8 @@ var BABYLON;
             this._diffPositionForCollisions = new BABYLON.Vector3(0, 0, 0);
             this._newPositionForCollisions = new BABYLON.Vector3(0, 0, 0);
             // Edges
-            this.edgesEpsilon = 0.95;
-            this.edgesWidth = 1.0;
-            this.edgesColor = BABYLON.Color3.Red();
+            this.edgesWidth = 1;
+            this.edgesColor = new BABYLON.Color4(1, 0, 0, 1);
             // Cache
             this._localScaling = BABYLON.Matrix.Zero();
             this._localRotation = BABYLON.Matrix.Zero();
@@ -127,24 +126,18 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        Object.defineProperty(AbstractMesh.prototype, "renderEdges", {
-            // Methods
-            get: function () {
-                return this._edgesRenderer !== undefined;
-            },
-            set: function (value) {
-                if (value && this._edgesRenderer === undefined) {
-                    this._edgesRenderer = new BABYLON.EdgesRenderer(this);
-                    return;
-                }
-                if (!value && this._edgesRenderer !== undefined) {
-                    this._edgesRenderer.dispose();
-                    this._edgesRenderer = undefined;
-                }
-            },
-            enumerable: true,
-            configurable: true
-        });
+        // Methods
+        AbstractMesh.prototype.disableEdgesRendering = function () {
+            if (this._edgesRenderer !== undefined) {
+                this._edgesRenderer.dispose();
+                this._edgesRenderer = undefined;
+            }
+        };
+        AbstractMesh.prototype.enableEdgesRendering = function (epsilon) {
+            if (epsilon === void 0) { epsilon = 0.95; }
+            this.disableEdgesRendering();
+            this._edgesRenderer = new BABYLON.EdgesRenderer(this, epsilon);
+        };
         Object.defineProperty(AbstractMesh.prototype, "isBlocked", {
             get: function () {
                 return false;

+ 9 - 14
src/Mesh/babylon.abstractMesh.ts

@@ -87,9 +87,8 @@
         private _meshToBoneReferal: AbstractMesh;
 
         // Edges
-        public edgesEpsilon = 0.95;
-        public edgesWidth = 1.0;
-        public edgesColor = Color3.Red();
+        public edgesWidth = 1;
+        public edgesColor = new Color4(1, 0, 0, 1);
         public _edgesRenderer: EdgesRenderer;
 
         // Cache
@@ -134,21 +133,17 @@
         }
 
         // Methods
-        public get renderEdges(): boolean {
-            return this._edgesRenderer !== undefined;
-        }
-
-        public set renderEdges(value: boolean) {
-            if (value && this._edgesRenderer === undefined) {
-                this._edgesRenderer = new EdgesRenderer(this);
-                return;
-            }
-
-            if (!value && this._edgesRenderer !== undefined) {
+        public disableEdgesRendering(): void {
+            if (this._edgesRenderer !== undefined) {
                 this._edgesRenderer.dispose();
                 this._edgesRenderer = undefined;
             }
         }
+        public enableEdgesRendering(epsilon = 0.95) {
+            this.disableEdgesRendering();
+
+            this._edgesRenderer = new EdgesRenderer(this, epsilon);
+        }
 
         public get isBlocked(): boolean {
             return false;

+ 18 - 40
src/Mesh/babylon.mesh.vertexData.js

@@ -1051,58 +1051,36 @@ var BABYLON;
         VertexData.ComputeNormals = function (positions, indices, normals) {
             var index = 0;
             // temp Vector3
-            var p1 = BABYLON.Vector3.Zero();
-            var p2 = BABYLON.Vector3.Zero();
-            var p3 = BABYLON.Vector3.Zero();
             var p1p2 = BABYLON.Vector3.Zero();
             var p3p2 = BABYLON.Vector3.Zero();
             var faceNormal = BABYLON.Vector3.Zero();
             var vertexNormali1 = BABYLON.Vector3.Zero();
-            var vertexNormali2 = BABYLON.Vector3.Zero();
-            var vertexNormali3 = BABYLON.Vector3.Zero();
+            for (index = 0; index < positions.length; index++) {
+                normals[index] = 0.0;
+            }
             // indice triplet = 1 face
             var nbFaces = indices.length / 3;
             for (index = 0; index < nbFaces; index++) {
                 var i1 = indices[index * 3];
                 var i2 = indices[index * 3 + 1];
                 var i3 = indices[index * 3 + 2];
-                // setting the temp V3
-                BABYLON.Vector3.FromFloatsToRef(positions[i1 * 3], positions[i1 * 3 + 1], positions[i1 * 3 + 2], p1);
-                BABYLON.Vector3.FromFloatsToRef(positions[i2 * 3], positions[i2 * 3 + 1], positions[i2 * 3 + 2], p2);
-                BABYLON.Vector3.FromFloatsToRef(positions[i3 * 3], positions[i3 * 3 + 1], positions[i3 * 3 + 2], p3);
-                p1.subtractToRef(p2, p1p2);
-                p3.subtractToRef(p2, p3p2);
+                p1p2.x = positions[i1 * 3] - positions[i2 * 3];
+                p1p2.y = positions[i1 * 3 + 1] - positions[i2 * 3 + 1];
+                p1p2.z = positions[i1 * 3 + 2] - positions[i2 * 3 + 2];
+                p3p2.x = positions[i3 * 3] - positions[i2 * 3];
+                p3p2.y = positions[i3 * 3 + 1] - positions[i2 * 3 + 1];
+                p3p2.z = positions[i3 * 3 + 2] - positions[i2 * 3 + 2];
                 BABYLON.Vector3.CrossToRef(p1p2, p3p2, faceNormal);
                 faceNormal.normalize();
-                // All intermediate results are stored in the normals array :
-                // get the normals at i1, i2 and i3 indexes
-                normals[i1 * 3] = normals[i1 * 3] || 0.0;
-                normals[i1 * 3 + 1] = normals[i1 * 3 + 1] || 0.0;
-                normals[i1 * 3 + 2] = normals[i1 * 3 + 2] || 0.0;
-                normals[i2 * 3] = normals[i2 * 3] || 0.0;
-                normals[i2 * 3 + 1] = normals[i2 * 3 + 1] || 0.0;
-                normals[i2 * 3 + 2] = normals[i2 * 3 + 2] || 0.0;
-                normals[i3 * 3] = normals[i3 * 3] || 0.0;
-                normals[i3 * 3 + 1] = normals[i3 * 3 + 1] || 0.0;
-                normals[i3 * 3 + 2] = normals[i3 * 3 + 2] || 0.0;
-                // make intermediate vectors3 from normals values
-                BABYLON.Vector3.FromFloatsToRef(normals[i1 * 3], normals[i1 * 3 + 1], normals[i1 * 3 + 2], vertexNormali1);
-                BABYLON.Vector3.FromFloatsToRef(normals[i2 * 3], normals[i2 * 3 + 1], normals[i2 * 3 + 2], vertexNormali2);
-                BABYLON.Vector3.FromFloatsToRef(normals[i3 * 3], normals[i3 * 3 + 1], normals[i3 * 3 + 2], vertexNormali3);
-                // add the current face normals to these intermediate vectors3
-                vertexNormali1 = vertexNormali1.addInPlace(faceNormal);
-                vertexNormali2 = vertexNormali2.addInPlace(faceNormal);
-                vertexNormali3 = vertexNormali3.addInPlace(faceNormal);
-                // store back intermediate vectors3 into the normals array
-                normals[i1 * 3] = vertexNormali1.x;
-                normals[i1 * 3 + 1] = vertexNormali1.y;
-                normals[i1 * 3 + 2] = vertexNormali1.z;
-                normals[i2 * 3] = vertexNormali2.x;
-                normals[i2 * 3 + 1] = vertexNormali2.y;
-                normals[i2 * 3 + 2] = vertexNormali2.z;
-                normals[i3 * 3] = vertexNormali3.x;
-                normals[i3 * 3 + 1] = vertexNormali3.y;
-                normals[i3 * 3 + 2] = vertexNormali3.z;
+                normals[i1 * 3] += faceNormal.x;
+                normals[i1 * 3 + 1] += faceNormal.y;
+                normals[i1 * 3 + 2] += faceNormal.z;
+                normals[i2 * 3] += faceNormal.x;
+                normals[i2 * 3 + 1] += faceNormal.y;
+                normals[i2 * 3 + 2] += faceNormal.z;
+                normals[i3 * 3] += faceNormal.x;
+                normals[i3 * 3 + 1] += faceNormal.y;
+                normals[i3 * 3 + 2] += faceNormal.z;
             }
             // last normalization
             for (index = 0; index < normals.length / 3; index++) {

+ 2 - 1
src/Mesh/babylon.mesh.vertexData.ts

@@ -1322,7 +1322,7 @@
 
                 Vector3.CrossToRef(p1p2, p3p2, faceNormal);
                 faceNormal.normalize();
-    
+
                 normals[i1 * 3] += faceNormal.x;
                 normals[i1 * 3 + 1] += faceNormal.y;
                 normals[i1 * 3 + 2] += faceNormal.z;
@@ -1402,3 +1402,4 @@
 
 
 
+

+ 98 - 20
src/Rendering/babylon.edgesRenderer.js

@@ -8,20 +8,34 @@ var BABYLON;
         return FaceAdjacencies;
     })();
     var EdgesRenderer = (function () {
-        function EdgesRenderer(source) {
-            this._lines = new Array();
+        // Beware when you use this class with complex objects as the adjacencies computation can be really long
+        function EdgesRenderer(source, epsilon) {
+            if (epsilon === void 0) { epsilon = 0.95; }
+            this._linesPositions = new Array();
+            this._linesNormals = new Array();
+            this._linesIndices = new Array();
+            this._buffers = new Array();
             this._source = source;
-            this._material = new BABYLON.StandardMaterial(this._source.name + "EdgeMaterial", this._source.getScene());
-            this._material.emissiveColor = this._source.edgesColor;
-            this._material.diffuseColor = BABYLON.Color3.Black();
-            this._material.specularColor = BABYLON.Color3.Black();
+            this._epsilon = epsilon;
+            this._prepareRessources();
             this._generateEdgesLines();
         }
-        EdgesRenderer.prototype.dispose = function () {
-            for (var index = 0; index < this._lines.length; index++) {
-                this._lines[index].dispose();
+        EdgesRenderer.prototype._prepareRessources = function () {
+            if (this._lineShader) {
+                return;
             }
-            this._lines = new Array();
+            this._lineShader = new BABYLON.ShaderMaterial("lineShader", this._source.getScene(), "line", {
+                attributes: ["position", "normal"],
+                uniforms: ["worldViewProjection", "color", "width"]
+            });
+            this._lineShader.disableDepthWrite = true;
+            this._lineShader.backFaceCulling = false;
+        };
+        EdgesRenderer.prototype.dispose = function () {
+            this._vb0.dispose();
+            this._vb1.dispose();
+            this._source.getScene().getEngine()._releaseBuffer(this._ib);
+            this._lineShader.dispose();
         };
         EdgesRenderer.prototype._processEdgeForAdjacencies = function (pa, pb, p0, p1, p2) {
             if (pa === p0 && pb === p1 || pa === p1 && pb === p0) {
@@ -42,12 +56,45 @@ var BABYLON;
             }
             else {
                 var dotProduct = BABYLON.Vector3.Dot(faceNormals[faceIndex], faceNormals[edge]);
-                needToCreateLine = dotProduct < this._source.edgesEpsilon;
+                needToCreateLine = dotProduct < this._epsilon;
             }
             if (needToCreateLine) {
-                var scene = this._source.getScene();
-                var lineMesh = BABYLON.Mesh.CreateTube(this._source.name + "edge", [p0, p1], this._source.edgesWidth / 100.0, 5, null, BABYLON.Mesh.CAP_ALL, scene, false, BABYLON.Mesh.DEFAULTSIDE);
-                this._lines.push(lineMesh);
+                var offset = this._linesPositions.length / 3;
+                var normal = p0.subtract(p1);
+                normal.normalize();
+                // Positions
+                this._linesPositions.push(p0.x);
+                this._linesPositions.push(p0.y);
+                this._linesPositions.push(p0.z);
+                this._linesPositions.push(p0.x);
+                this._linesPositions.push(p0.y);
+                this._linesPositions.push(p0.z);
+                this._linesPositions.push(p1.x);
+                this._linesPositions.push(p1.y);
+                this._linesPositions.push(p1.z);
+                this._linesPositions.push(p1.x);
+                this._linesPositions.push(p1.y);
+                this._linesPositions.push(p1.z);
+                // Normals
+                this._linesNormals.push(normal.x);
+                this._linesNormals.push(normal.y);
+                this._linesNormals.push(normal.z);
+                this._linesNormals.push(-normal.x);
+                this._linesNormals.push(-normal.y);
+                this._linesNormals.push(-normal.z);
+                this._linesNormals.push(-normal.x);
+                this._linesNormals.push(-normal.y);
+                this._linesNormals.push(-normal.z);
+                this._linesNormals.push(normal.x);
+                this._linesNormals.push(normal.y);
+                this._linesNormals.push(normal.z);
+                // Indices
+                this._linesIndices.push(offset);
+                this._linesIndices.push(offset + 1);
+                this._linesIndices.push(offset + 2);
+                this._linesIndices.push(offset);
+                this._linesIndices.push(offset + 2);
+                this._linesIndices.push(offset + 3);
             }
         };
         EdgesRenderer.prototype._generateEdgesLines = function () {
@@ -76,14 +123,21 @@ var BABYLON;
             for (index = 0; index < adjacencies.length; index++) {
                 faceAdjacencies = adjacencies[index];
                 for (var otherIndex = index + 1; otherIndex < adjacencies.length; otherIndex++) {
+                    var otherFaceAdjacencies = adjacencies[otherIndex];
                     if (faceAdjacencies.edgesConnectedCount === 3) {
                         break;
                     }
+                    if (otherFaceAdjacencies.edgesConnectedCount === 3) {
+                        continue;
+                    }
                     var otherP0 = indices[otherIndex * 3];
                     var otherP1 = indices[otherIndex * 3 + 1];
                     var otherP2 = indices[otherIndex * 3 + 2];
                     for (var edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
                         var otherEdgeIndex;
+                        if (faceAdjacencies.edges[edgeIndex] !== undefined) {
+                            continue;
+                        }
                         switch (edgeIndex) {
                             case 0:
                                 otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3], indices[index * 3 + 1], otherP0, otherP1, otherP2);
@@ -99,9 +153,12 @@ var BABYLON;
                             continue;
                         }
                         faceAdjacencies.edges[edgeIndex] = otherIndex;
-                        adjacencies[otherIndex].edges[otherEdgeIndex] = index;
+                        otherFaceAdjacencies.edges[otherEdgeIndex] = index;
                         faceAdjacencies.edgesConnectedCount++;
-                        adjacencies[otherIndex].edgesConnectedCount++;
+                        otherFaceAdjacencies.edgesConnectedCount++;
+                        if (faceAdjacencies.edgesConnectedCount === 3) {
+                            break;
+                        }
                     }
                 }
             }
@@ -114,10 +171,31 @@ var BABYLON;
                 this._checkEdge(index, current.edges[2], faceNormals, current.p2, current.p0);
             }
             // Merge into a single mesh
-            this._renderMesh = BABYLON.Mesh.MergeMeshes(this._lines, true, true);
-            this._renderMesh.parent = this._source;
-            this._renderMesh.material = this._material;
-            this._renderMesh.name = this._source.name + "edge";
+            var engine = this._source.getScene().getEngine();
+            this._vb0 = new BABYLON.VertexBuffer(engine, this._linesPositions, BABYLON.VertexBuffer.PositionKind, false);
+            this._vb1 = new BABYLON.VertexBuffer(engine, this._linesNormals, BABYLON.VertexBuffer.NormalKind, false);
+            this._buffers[BABYLON.VertexBuffer.PositionKind] = this._vb0;
+            this._buffers[BABYLON.VertexBuffer.NormalKind] = this._vb1;
+            this._ib = engine.createIndexBuffer(this._linesIndices);
+            this._indicesCount = this._linesIndices.length;
+        };
+        EdgesRenderer.prototype.render = function () {
+            if (!this._lineShader.isReady()) {
+                return;
+            }
+            var scene = this._source.getScene();
+            var engine = scene.getEngine();
+            this._lineShader._preBind();
+            // VBOs
+            engine.bindMultiBuffers(this._buffers, this._ib, this._lineShader.getEffect());
+            scene.resetCachedMaterial();
+            this._lineShader.setColor4("color", this._source.edgesColor);
+            this._lineShader.setFloat("width", this._source.edgesWidth / 100.0);
+            this._lineShader.bind(this._source.getWorldMatrix());
+            // Draw order
+            engine.draw(true, 0, this._indicesCount);
+            this._lineShader.unbind();
+            engine.setDepthWrite(true);
         };
         return EdgesRenderer;
     })();

+ 130 - 25
src/Rendering/babylon.edgesRenderer.ts

@@ -10,27 +10,48 @@
 
     export class EdgesRenderer {
         private _source: AbstractMesh;
-        private _lines = new Array<Mesh>();
-        private _renderMesh: Mesh;
-        private _material: StandardMaterial;
-
-        constructor(source: AbstractMesh) {
+        private _linesPositions = new Array<number>();
+        private _linesNormals = new Array<number>();
+        private _linesIndices = new Array<number>();
+        private _epsilon: number;
+        private _indicesCount: number;
+
+        private _lineShader: ShaderMaterial;
+        private _vb0: VertexBuffer;
+        private _vb1: VertexBuffer;
+        private _ib: WebGLBuffer;
+        private _buffers = new Array<VertexBuffer>();
+
+        // Beware when you use this class with complex objects as the adjacencies computation can be really long
+        constructor(source: AbstractMesh, epsilon = 0.95) {
             this._source = source;
 
-            this._material = new StandardMaterial(this._source.name + "EdgeMaterial", this._source.getScene());
-            this._material.emissiveColor = this._source.edgesColor;
-            this._material.diffuseColor = Color3.Black();
-            this._material.specularColor = Color3.Black();
+            this._epsilon = epsilon;
 
+            this._prepareRessources();
             this._generateEdgesLines();
         }
 
-        public dispose(): void {
-            for (var index = 0; index < this._lines.length; index++) {
-                this._lines[index].dispose();
+        private _prepareRessources(): void {
+            if (this._lineShader) {
+                return;
             }
 
-            this._lines = new Array<LinesMesh>();
+            this._lineShader = new ShaderMaterial("lineShader", this._source.getScene(), "line",
+                {
+                    attributes: ["position", "normal"],
+                    uniforms: ["worldViewProjection", "color", "width"]
+                });
+
+            this._lineShader.disableDepthWrite = true;
+            this._lineShader.backFaceCulling = false;
+        }
+
+        public dispose(): void {
+            this._vb0.dispose();
+            this._vb1.dispose();
+            this._source.getScene().getEngine()._releaseBuffer(this._ib);
+            this._lineShader.dispose();
         }
 
         private _processEdgeForAdjacencies(pa: number, pb: number, p0: number, p1: number, p2: number): number {
@@ -57,14 +78,55 @@
             } else {
                 var dotProduct = Vector3.Dot(faceNormals[faceIndex], faceNormals[edge]);
 
-                needToCreateLine = dotProduct < this._source.edgesEpsilon;
+                needToCreateLine = dotProduct < this._epsilon;
             }
 
             if (needToCreateLine) {
-                var scene = this._source.getScene();
-                var lineMesh = BABYLON.Mesh.CreateTube(this._source.name + "edge", [p0, p1], this._source.edgesWidth / 100.0, 5, null, BABYLON.Mesh.CAP_ALL, scene, false, BABYLON.Mesh.DEFAULTSIDE);
-
-                this._lines.push(lineMesh);
+                var offset = this._linesPositions.length / 3;
+                var normal = p0.subtract(p1);
+                normal.normalize();
+
+                // Positions
+                this._linesPositions.push(p0.x);
+                this._linesPositions.push(p0.y);
+                this._linesPositions.push(p0.z);
+
+                this._linesPositions.push(p0.x);
+                this._linesPositions.push(p0.y);
+                this._linesPositions.push(p0.z);
+
+                this._linesPositions.push(p1.x);
+                this._linesPositions.push(p1.y);
+                this._linesPositions.push(p1.z);
+
+                this._linesPositions.push(p1.x);
+                this._linesPositions.push(p1.y);
+                this._linesPositions.push(p1.z);
+
+                // Normals
+                this._linesNormals.push(normal.x);
+                this._linesNormals.push(normal.y);
+                this._linesNormals.push(normal.z);
+
+                this._linesNormals.push(-normal.x);
+                this._linesNormals.push(-normal.y);
+                this._linesNormals.push(-normal.z);
+
+                this._linesNormals.push(-normal.x);
+                this._linesNormals.push(-normal.y);
+                this._linesNormals.push(-normal.z);
+
+                this._linesNormals.push(normal.x);
+                this._linesNormals.push(normal.y);
+                this._linesNormals.push(normal.z);
+
+                // Indices
+                this._linesIndices.push(offset);
+                this._linesIndices.push(offset + 1);
+                this._linesIndices.push(offset + 2);
+                this._linesIndices.push(offset);
+                this._linesIndices.push(offset + 2);
+                this._linesIndices.push(offset + 3);
             }
         }
 
@@ -101,10 +163,16 @@
                 faceAdjacencies = adjacencies[index];
 
                 for (var otherIndex = index + 1; otherIndex < adjacencies.length; otherIndex++) {
-                    if (faceAdjacencies.edgesConnectedCount === 3) {
+                    var otherFaceAdjacencies = adjacencies[otherIndex];
+
+                    if (faceAdjacencies.edgesConnectedCount === 3) { // Full
                         break;
                     }
 
+                    if (otherFaceAdjacencies.edgesConnectedCount === 3) { // Full
+                        continue;
+                    }
+
                     var otherP0 = indices[otherIndex * 3];
                     var otherP1 = indices[otherIndex * 3 + 1];
                     var otherP2 = indices[otherIndex * 3 + 2];
@@ -112,6 +180,10 @@
                     for (var edgeIndex = 0; edgeIndex < 3; edgeIndex++) {
                         var otherEdgeIndex: number;
 
+                        if (faceAdjacencies.edges[edgeIndex] !== undefined) {
+                            continue;
+                        }
+
                         switch (edgeIndex) {
                             case 0:
                                 otherEdgeIndex = this._processEdgeForAdjacencies(indices[index * 3], indices[index * 3 + 1], otherP0, otherP1, otherP2);
@@ -129,10 +201,14 @@
                         }
 
                         faceAdjacencies.edges[edgeIndex] = otherIndex;
-                        adjacencies[otherIndex].edges[otherEdgeIndex] = index;
+                        otherFaceAdjacencies.edges[otherEdgeIndex] = index;
 
                         faceAdjacencies.edgesConnectedCount++;
-                        adjacencies[otherIndex].edgesConnectedCount++;
+                        otherFaceAdjacencies.edgesConnectedCount++;
+
+                        if (faceAdjacencies.edgesConnectedCount === 3) {
+                            break;
+                        }
                     }
                 }
             }
@@ -148,10 +224,39 @@
             }
 
             // Merge into a single mesh
-            this._renderMesh = Mesh.MergeMeshes(this._lines, true, true);
-            this._renderMesh.parent = this._source;
-            this._renderMesh.material = this._material;
-            this._renderMesh.name = this._source.name + "edge";
+            var engine = this._source.getScene().getEngine();
+            this._vb0 = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
+            this._vb1 = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false);
+
+            this._buffers[VertexBuffer.PositionKind] = this._vb0;
+            this._buffers[VertexBuffer.NormalKind] = this._vb1;
+
+            this._ib = engine.createIndexBuffer(this._linesIndices);
+
+            this._indicesCount = this._linesIndices.length;
+        }
+
+        public render(): void {
+            if (!this._lineShader.isReady()) {
+                return;
+            }
+
+            var scene = this._source.getScene();
+            var engine = scene.getEngine();
+            this._lineShader._preBind();
+
+            // VBOs
+            engine.bindMultiBuffers(this._buffers, this._ib, this._lineShader.getEffect());
+
+            scene.resetCachedMaterial();
+            this._lineShader.setColor4("color", this._source.edgesColor);
+            this._lineShader.setFloat("width", this._source.edgesWidth / 100.0);
+            this._lineShader.bind(this._source.getWorldMatrix());
+
+            // Draw order
+            engine.draw(true, 0, this._indicesCount);
+            this._lineShader.unbind();
+            engine.setDepthWrite(true);
         }
     }
 } 

+ 7 - 0
src/Shaders/line.fragment.fx

@@ -0,0 +1,7 @@
+precision highp float;
+
+uniform vec4 color;
+
+void main(void) {
+	gl_FragColor = color;
+}

+ 21 - 0
src/Shaders/line.vertex.fx

@@ -0,0 +1,21 @@
+precision highp float;
+
+// Attributes
+attribute vec3 position;
+attribute vec3 normal;
+
+// Uniforms
+uniform mat4 worldViewProjection;
+
+uniform float width;
+
+void main(void) {
+	vec4 viewPosition = worldViewProjection * vec4(position, 1.0);
+	vec4 viewNormal = worldViewProjection * vec4(normal, 0.0);
+	vec3 direction = cross(viewNormal.xyz, vec3(0., 0., 1.));
+
+	direction = normalize(direction);
+	viewPosition.xy += direction.xy *  width;
+
+	gl_Position = viewPosition;
+}

+ 22 - 7
src/babylon.scene.js

@@ -129,6 +129,7 @@ var BABYLON;
             this._activeBones = 0;
             this._activeAnimatables = new Array();
             this._transformMatrix = BABYLON.Matrix.Zero();
+            this._edgesRenderers = new BABYLON.SmartArray(16);
             this._uniqueIdCounter = 0;
             this._engine = engine;
             engine.scenes.push(this);
@@ -411,7 +412,8 @@ var BABYLON;
             if (this._pendingData.length > 0) {
                 return false;
             }
-            for (var index = 0; index < this._geometries.length; index++) {
+            var index;
+            for (index = 0; index < this._geometries.length; index++) {
                 var geometry = this._geometries[index];
                 if (geometry.delayLoadState === BABYLON.Engine.DELAYLOADSTATE_LOADING) {
                     return false;
@@ -883,7 +885,8 @@ var BABYLON;
          * @return {BABYLON.Node|null} the node found or null if not found at all.
          */
         Scene.prototype.getLastEntryByID = function (id) {
-            for (var index = this.meshes.length - 1; index >= 0; index--) {
+            var index;
+            for (index = this.meshes.length - 1; index >= 0; index--) {
                 if (this.meshes[index].id === id) {
                     return this.meshes[index];
                 }
@@ -1001,6 +1004,7 @@ var BABYLON;
             this._activeSkeletons.reset();
             this._softwareSkinnedMeshes.reset();
             this._boundingBoxRenderer.reset();
+            this._edgesRenderers.reset();
             if (!this._frustumPlanes) {
                 this._frustumPlanes = BABYLON.Frustum.GetPlanes(this._transformMatrix);
             }
@@ -1074,6 +1078,9 @@ var BABYLON;
             if (mesh.showBoundingBox || this.forceShowBoundingBoxes) {
                 this._boundingBoxRenderer.renderList.push(mesh.getBoundingInfo().boundingBox);
             }
+            if (mesh._edgesRenderer) {
+                this._edgesRenderers.push(mesh._edgesRenderer);
+            }
             if (mesh && mesh.subMeshes) {
                 // Submeshes Octrees
                 var len;
@@ -1150,9 +1157,9 @@ var BABYLON;
             this.postProcessManager._prepareFrame();
             var beforeRenderDate = BABYLON.Tools.Now;
             // Backgrounds
+            var layerIndex;
             if (this.layers.length) {
                 engine.setDepthBuffer(false);
-                var layerIndex;
                 var layer;
                 for (layerIndex = 0; layerIndex < this.layers.length; layerIndex++) {
                     layer = this.layers[layerIndex];
@@ -1168,6 +1175,10 @@ var BABYLON;
             BABYLON.Tools.EndPerformanceCounter("Main render");
             // Bounding boxes
             this._boundingBoxRenderer.render();
+            // Edges
+            for (var edgesRendererIndex = 0; edgesRendererIndex < this._edgesRenderers.length; edgesRendererIndex++) {
+                this._edgesRenderers.data[edgesRendererIndex].render();
+            }
             // Lens flares
             if (this.lensFlaresEnabled) {
                 BABYLON.Tools.StartPerformanceCounter("Lens flares", this.lensFlareSystems.length > 0);
@@ -1274,7 +1285,8 @@ var BABYLON;
             if (this.beforeRender) {
                 this.beforeRender();
             }
-            for (var callbackIndex = 0; callbackIndex < this._onBeforeRenderCallbacks.length; callbackIndex++) {
+            var callbackIndex;
+            for (callbackIndex = 0; callbackIndex < this._onBeforeRenderCallbacks.length; callbackIndex++) {
                 this._onBeforeRenderCallbacks[callbackIndex]();
             }
             // Animations
@@ -1399,7 +1411,8 @@ var BABYLON;
                 var cameraDirection = BABYLON.Vector3.TransformNormal(new BABYLON.Vector3(0, 0, -1), mat);
                 cameraDirection.normalize();
                 audioEngine.audioContext.listener.setOrientation(cameraDirection.x, cameraDirection.y, cameraDirection.z, 0, 1, 0);
-                for (var i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
+                var i;
+                for (i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
                     var sound = this.mainSoundTrack.soundCollection[i];
                     if (sound.useCustomAttenuation) {
                         sound.updateDistanceFromListener();
@@ -1433,7 +1446,8 @@ var BABYLON;
             configurable: true
         });
         Scene.prototype._disableAudio = function () {
-            for (var i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
+            var i;
+            for (i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
                 this.mainSoundTrack.soundCollection[i].pause();
             }
             for (i = 0; i < this.soundTracks.length; i++) {
@@ -1443,7 +1457,8 @@ var BABYLON;
             }
         };
         Scene.prototype._enableAudio = function () {
-            for (var i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
+            var i;
+            for (i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
                 if (this.mainSoundTrack.soundCollection[i].isPaused) {
                     this.mainSoundTrack.soundCollection[i].play();
                 }

+ 26 - 11
src/babylon.scene.ts

@@ -250,6 +250,7 @@
         private _transformMatrix = Matrix.Zero();
         private _pickWithRayInverseMatrix: Matrix;
 
+        private _edgesRenderers = new SmartArray<EdgesRenderer>(16);
         private _boundingBoxRenderer: BoundingBoxRenderer;
         private _outlineRenderer: OutlineRenderer;
 
@@ -304,8 +305,8 @@
 
         public set workerCollisions(enabled: boolean) {
         
-            enabled = (enabled && !!Worker)
-        
+            enabled = (enabled && !!Worker);
+
             this._workerCollisions = enabled;
             if (this.collisionCoordinator) {
                 this.collisionCoordinator.destroy();
@@ -571,8 +572,8 @@
             if (this._pendingData.length > 0) {
                 return false;
             }
-
-            for (var index = 0; index < this._geometries.length; index++) {
+            var index: number;
+            for (index = 0; index < this._geometries.length; index++) {
                 var geometry = this._geometries[index];
 
                 if (geometry.delayLoadState === Engine.DELAYLOADSTATE_LOADING) {
@@ -1138,7 +1139,8 @@
          * @return {BABYLON.Node|null} the node found or null if not found at all.
          */
         public getLastEntryByID(id: string): Node {
-            for (var index = this.meshes.length - 1; index >= 0; index--) {
+            var index: number;
+            for (index = this.meshes.length - 1; index >= 0; index--) {
                 if (this.meshes[index].id === id) {
                     return this.meshes[index];
                 }
@@ -1287,6 +1289,7 @@
             this._activeSkeletons.reset();
             this._softwareSkinnedMeshes.reset();
             this._boundingBoxRenderer.reset();
+            this._edgesRenderers.reset();
 
             if (!this._frustumPlanes) {
                 this._frustumPlanes = Frustum.GetPlanes(this._transformMatrix);
@@ -1379,6 +1382,10 @@
                 this._boundingBoxRenderer.renderList.push(mesh.getBoundingInfo().boundingBox);
             }
 
+            if (mesh._edgesRenderer) {
+                this._edgesRenderers.push(mesh._edgesRenderer);
+            }
+
             if (mesh && mesh.subMeshes) {
                 // Submeshes Octrees
                 var len: number;
@@ -1475,9 +1482,9 @@
 
             var beforeRenderDate = Tools.Now;
             // Backgrounds
+            var layerIndex;
             if (this.layers.length) {
                 engine.setDepthBuffer(false);
-                var layerIndex;
                 var layer;
                 for (layerIndex = 0; layerIndex < this.layers.length; layerIndex++) {
                     layer = this.layers[layerIndex];
@@ -1496,6 +1503,11 @@
             // Bounding boxes
             this._boundingBoxRenderer.render();
 
+            // Edges
+            for (var edgesRendererIndex = 0; edgesRendererIndex < this._edgesRenderers.length; edgesRendererIndex++) {
+                this._edgesRenderers.data[edgesRendererIndex].render();
+            }
+
             // Lens flares
             if (this.lensFlaresEnabled) {
                 Tools.StartPerformanceCounter("Lens flares", this.lensFlareSystems.length > 0);
@@ -1623,8 +1635,8 @@
             if (this.beforeRender) {
                 this.beforeRender();
             }
-
-            for (var callbackIndex = 0; callbackIndex < this._onBeforeRenderCallbacks.length; callbackIndex++) {
+            var callbackIndex: number;
+            for (callbackIndex = 0; callbackIndex < this._onBeforeRenderCallbacks.length; callbackIndex++) {
                 this._onBeforeRenderCallbacks[callbackIndex]();
             }
 
@@ -1777,7 +1789,8 @@
                 var cameraDirection = Vector3.TransformNormal(new Vector3(0, 0, -1), mat);
                 cameraDirection.normalize();
                 audioEngine.audioContext.listener.setOrientation(cameraDirection.x, cameraDirection.y, cameraDirection.z, 0, 1, 0);
-                for (var i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
+                var i: number;
+                for (i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
                     var sound = this.mainSoundTrack.soundCollection[i];
                     if (sound.useCustomAttenuation) {
                         sound.updateDistanceFromListener();
@@ -1810,7 +1823,8 @@
         }
 
         private _disableAudio() {
-            for (var i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
+            var i: number;
+            for (i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
                 this.mainSoundTrack.soundCollection[i].pause();
             }
             for (i = 0; i < this.soundTracks.length; i++) {
@@ -1821,7 +1835,8 @@
         }
 
         private _enableAudio() {
-            for (var i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
+            var i: number;
+            for (i = 0; i < this.mainSoundTrack.soundCollection.length; i++) {
                 if (this.mainSoundTrack.soundCollection[i].isPaused) {
                     this.mainSoundTrack.soundCollection[i].play();
                 }