فهرست منبع

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

Temechon 8 سال پیش
والد
کامیت
3403066e54
39فایلهای تغییر یافته به همراه4985 افزوده شده و 3437 حذف شده
  1. 8 3
      canvas2D/src/Engine/babylon.canvas2d.ts
  2. 11 20
      canvas2D/src/Engine/babylon.group2d.ts
  3. 85 45
      canvas2D/src/Engine/babylon.text2d.ts
  4. 19 1
      canvas2D/src/shaders/text2d.fragment.fx
  5. 10 0
      dist/preview release/babylon.canvas2d.d.ts
  6. 6 6
      dist/preview release/babylon.canvas2d.js
  7. 67 30
      dist/preview release/babylon.canvas2d.max.js
  8. 25 25
      dist/preview release/babylon.core.js
  9. 3073 3040
      dist/preview release/babylon.d.ts
  10. 34 34
      dist/preview release/babylon.js
  11. 506 36
      dist/preview release/babylon.max.js
  12. 35 35
      dist/preview release/babylon.noworker.js
  13. 2 0
      dist/preview release/what's new.md
  14. 1 0
      src/Actions/babylon.actionManager.js
  15. 2 0
      src/Actions/babylon.actionManager.ts
  16. 7 0
      src/Animations/babylon.animatable.js
  17. 8 0
      src/Animations/babylon.animatable.ts
  18. 256 0
      src/Bones/babylon.bone.js
  19. 287 90
      src/Bones/babylon.bone.ts
  20. 9 0
      src/Bones/babylon.skeleton.js
  21. 14 0
      src/Bones/babylon.skeleton.ts
  22. 13 1
      src/Cameras/babylon.camera.js
  23. 14 2
      src/Cameras/babylon.camera.ts
  24. 1 1
      src/Materials/Textures/babylon.cubeTexture.js
  25. 1 1
      src/Materials/Textures/babylon.cubeTexture.ts
  26. 187 18
      src/Materials/Textures/babylon.fontTexture.js
  27. 219 19
      src/Materials/Textures/babylon.fontTexture.ts
  28. 1 1
      src/Materials/Textures/babylon.hdrCubeTexture.js
  29. 1 1
      src/Materials/Textures/babylon.hdrCubeTexture.ts
  30. 5 5
      src/Materials/Textures/babylon.texture.js
  31. 6 4
      src/Materials/Textures/babylon.texture.ts
  32. 1 1
      src/Materials/babylon.fresnelParameters.js
  33. 1 1
      src/Materials/babylon.fresnelParameters.ts
  34. 6 3
      src/Math/babylon.math.js
  35. 29 0
      src/Math/babylon.math.ts
  36. 6 3
      src/Sprites/babylon.spriteManager.js
  37. 6 6
      src/Sprites/babylon.spriteManager.ts
  38. 12 2
      src/babylon.scene.js
  39. 11 3
      src/babylon.scene.ts

+ 8 - 3
canvas2D/src/Engine/babylon.canvas2d.ts

@@ -495,6 +495,7 @@
         private _updatePointerInfo(eventData: PointerInfoBase, localPosition: Vector2): boolean {
             let s = this.scale;
             let pii = this._primPointerInfo;
+            pii.cancelBubble = false;
             if (!pii.canvasPointerPos) {
                 pii.canvasPointerPos = Vector2.Zero();
             }
@@ -606,7 +607,7 @@
                 let capturedPrim = this.getCapturedPrimitive(this._primPointerInfo.pointerId);
 
                 // Notify the previous "over" prim that the pointer is no longer over it
-                if ((capturedPrim && capturedPrim === prevPrim) || (!capturedPrim && prevPrim)) {
+                if ((capturedPrim && capturedPrim === prevPrim) || (!capturedPrim && prevPrim && !prevPrim.isDisposed)) {
                     this._primPointerInfo.updateRelatedTarget(prevPrim, this._previousOverPrimitive.intersectionLocation);
                     this._bubbleNotifyPrimPointerObserver(prevPrim, PrimitivePointerInfo.PointerOut, null);
                 }
@@ -1047,6 +1048,10 @@
             this._setupInteraction(enable);
         }
 
+        public get fitRenderingDevice(): boolean {
+            return this._fitRenderingDevice;
+        }
+
         public get designSize(): Size {
             return this._designSize;
         }
@@ -1081,7 +1086,7 @@
                     new Rectangle2D({
                         id: "ProfileBorder", border: "#FFFFFFFF", borderThickness: 2, roundRadius: 5, fill: "#C04040C0", marginAlignment: "h: left, v: top", margin: "10", padding: "10", children:
                         [
-                            new Text2D("Stats", { id: "ProfileInfoText", marginAlignment: "h: left, v: top", fontName: "10pt Lucida Console" })
+                            new Text2D("Stats", { id: "ProfileInfoText", marginAlignment: "h: left, v: top", fontName: "12pt Lucida Console", fontSignedDistanceField: true })
                         ]
                     })
 
@@ -1532,7 +1537,7 @@
 
                 // Create a Sprite that will be used to render this cache, the "__cachedSpriteOfGroup__" starting id is a hack to bypass exception throwing in case of the Canvas doesn't normally allows direct primitives
                 else {
-                    sprite = new Sprite2D(map, { parent: parent, id: `__cachedSpriteOfGroup__${group.id}`, x: group.actualPosition.x * scale.x, y: group.actualPosition.y * scale.y, spriteSize: originalSize, spriteLocation: node.pos, dontInheritParentScale: true });
+                    sprite = new Sprite2D(map, { parent: parent, id: `__cachedSpriteOfGroup__${group.id}`, x: group.actualPosition.x, y: group.actualPosition.y, spriteSize: originalSize, spriteLocation: node.pos, dontInheritParentScale: true });
                     sprite.origin = group.origin.clone();
                     sprite.addExternalData("__cachedGroup__", group);
                     sprite.pointerEventObservable.add((e, s) => {

+ 11 - 20
canvas2D/src/Engine/babylon.group2d.ts

@@ -373,12 +373,9 @@
 
             // The dimension must be overridden when using the designSize feature, the ratio is maintain to compute a uniform scale, which is mandatory but if the designSize's ratio is different from the rendering surface's ratio, content will be clipped in some cases.
             // So we set the width/height to the rendering's one because that's what we want for the viewport!
-            if (this instanceof Canvas2D) {
-                let c = <Canvas2D><any>this;
-                if (c.designSize != null) {
-                    sw = this.owner.engine.getRenderWidth();
-                    sh = this.owner.engine.getRenderHeight();
-                }
+            if ((this instanceof Canvas2D || this.id === "__cachedCanvasGroup__") && this.owner.designSize != null) {
+                sw = this.owner.engine.getRenderWidth();
+                sh = this.owner.engine.getRenderHeight();
             }
 
             // Setup the size of the rendering viewport
@@ -830,8 +827,13 @@
             }
 
             if (isCanvas && this.owner.cachingStrategy===Canvas2D.CACHESTRATEGY_CANVAS && this.owner.isScreenSpace) {
-                Group2D._s.width = this.owner.engine.getRenderWidth();
-                Group2D._s.height = this.owner.engine.getRenderHeight();
+                if(this.owner.designSize || this.owner.fitRenderingDevice){
+                    Group2D._s.width = this.owner.engine.getRenderWidth();
+                    Group2D._s.height = this.owner.engine.getRenderHeight();
+                }
+                else{
+                    Group2D._s.copyFrom(this.owner.size);
+                }
             } else {
                 Group2D._s.width = Math.ceil(this.actualSize.width * scale.x * rs);
                 Group2D._s.height = Math.ceil(this.actualSize.height * scale.y * rs);
@@ -912,23 +914,12 @@
             // For now we only support these property changes
             // TODO: add more! :)
             switch (prop.id) {
-                case Prim2DBase.actualScaleProperty.id:
                 case Prim2DBase.actualPositionProperty.id:
-                    let noResizeScale = rd._noResizeOnScale;
-                    let isCanvas = parent == null;
-                    let scale: Vector2;
-                    if (noResizeScale) {
-                        scale = isCanvas ? Group2D._unS : this.parent.actualScale;
-                    } else {
-                        scale = this.actualScale;
-                    }
-
-                    cachedSprite.actualPosition = this.actualPosition.multiply(scale);
+                    cachedSprite.actualPosition = this.actualPosition.clone();
                     if (cachedSprite.position != null) {
                         cachedSprite.position = cachedSprite.actualPosition.clone();
                     }
                     break;
-
                 case Prim2DBase.rotationProperty.id:
                     cachedSprite.rotation = this.rotation;
                     break;

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 85 - 45
canvas2D/src/Engine/babylon.text2d.ts


+ 19 - 1
canvas2D/src/shaders/text2d.fragment.fx

@@ -1,10 +1,28 @@
-varying vec4 vColor;
+//#extension GL_OES_standard_derivatives : enable
+
+varying vec4 vColor;
 varying vec2 vUV;
 
 // Samplers
 uniform sampler2D diffuseSampler;
 
 void main(void) {
+#ifdef SignedDistanceField
+	float dist = texture2D(diffuseSampler, vUV).r;
+	if (dist < 0.5) {
+		discard;
+	}
+
+	// Another way using derivative, commented right now because I don't know if it worth doing it
+	//float edgeDistance = 0.5;
+	//float edgeWidth = 0.7 * length(vec2(dFdx(dist), dFdy(dist)));
+	//float opacity = dist * smoothstep(edgeDistance - edgeWidth, edgeDistance + edgeWidth, dist);
+
+	//float opacity = smoothstep(0.25, 0.75, dist);
+	gl_FragColor = vec4(vColor.xyz*dist, 1.0);
+#else
 	vec4 color = texture2D(diffuseSampler, vUV);
 	gl_FragColor = color*vColor;
+#endif
+
 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 10 - 0
dist/preview release/babylon.canvas2d.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 6 - 6
dist/preview release/babylon.canvas2d.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 67 - 30
dist/preview release/babylon.canvas2d.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 25 - 25
dist/preview release/babylon.core.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 3073 - 3040
dist/preview release/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 34 - 34
dist/preview release/babylon.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 506 - 36
dist/preview release/babylon.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 35 - 35
dist/preview release/babylon.noworker.js


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

@@ -8,6 +8,8 @@
 - Canvas2D moved to a separate folder in main repo. Now you need to also include babylon.cavans2d.js to get Canvas@D feature ([deltakosh](https://github.com/deltakosh))
 
 ### Updates
+- Added Bone.getAbsolutePosition and Bone.getAbsolutePositionToRef ([abow](https://github.com/abow))
+- Added Bone.setYawPitchRoll ([abow](https://github.com/abow))
 - Added Bone.rotate ([abow](https://github.com/abow))
 - Added Bone.scale ([abow](https://github.com/abow))
 - Added Node.getDirection ([abow](https://github.com/abow))

+ 1 - 0
src/Actions/babylon.actionManager.js

@@ -60,6 +60,7 @@ var BABYLON;
         function ActionManager(scene) {
             // Members
             this.actions = new Array();
+            this.hoverCursor = '';
             this._scene = scene;
             scene._actionManagers.push(this);
         }

+ 2 - 0
src/Actions/babylon.actionManager.ts

@@ -144,6 +144,8 @@
         // Members
         public actions = new Array<Action>();
 
+        public hoverCursor: string = '';
+
         private _scene: Scene;
 
         constructor(scene: Scene) {

+ 7 - 0
src/Animations/babylon.animatable.js

@@ -64,6 +64,13 @@ var BABYLON;
         };
         Animatable.prototype.goToFrame = function (frame) {
             var animations = this._animations;
+            if (animations[0]) {
+                var fps = animations[0].framePerSecond;
+                var currentFrame = animations[0].currentFrame;
+                var adjustTime = frame - currentFrame;
+                var delay = adjustTime * 1000 / fps;
+                this._localDelayOffset -= delay;
+            }
             for (var index = 0; index < animations.length; index++) {
                 animations[index].goToFrame(frame);
             }

+ 8 - 0
src/Animations/babylon.animatable.ts

@@ -74,6 +74,14 @@
         public goToFrame(frame: number): void {
             var animations = this._animations;
 
+            if (animations[0]) {
+                var fps = animations[0].framePerSecond;
+                var currentFrame = animations[0].currentFrame;
+                var adjustTime = frame - currentFrame;
+                var delay = adjustTime * 1000 / fps;
+                this._localDelayOffset -= delay;
+            }
+
             for (var index = 0; index < animations.length; index++) {
                 animations[index].goToFrame(frame);
             }

+ 256 - 0
src/Bones/babylon.bone.js

@@ -15,6 +15,26 @@ var BABYLON;
             this._worldTransform = new BABYLON.Matrix();
             this._absoluteTransform = new BABYLON.Matrix();
             this._invertedAbsoluteTransform = new BABYLON.Matrix();
+            this._scaleMatrix = BABYLON.Matrix.Identity();
+            this._scaleVector = new BABYLON.Vector3(1, 1, 1);
+            this._negateScaleChildren = new BABYLON.Vector3(1, 1, 1);
+            this._syncScaleVector = function () {
+                var lm = this.getLocalMatrix();
+                var xsq = (lm.m[0] * lm.m[0] + lm.m[1] * lm.m[1] + lm.m[2] * lm.m[2]);
+                var ysq = (lm.m[4] * lm.m[4] + lm.m[5] * lm.m[5] + lm.m[6] * lm.m[6]);
+                var zsq = (lm.m[8] * lm.m[8] + lm.m[9] * lm.m[9] + lm.m[10] * lm.m[10]);
+                var xs = lm.m[0] * lm.m[1] * lm.m[2] * lm.m[3] < 0 ? -1 : 1;
+                var ys = lm.m[4] * lm.m[5] * lm.m[6] * lm.m[7] < 0 ? -1 : 1;
+                var zs = lm.m[8] * lm.m[9] * lm.m[10] * lm.m[11] < 0 ? -1 : 1;
+                this._scaleVector.x = xs * Math.sqrt(xsq);
+                this._scaleVector.y = ys * Math.sqrt(ysq);
+                this._scaleVector.z = zs * Math.sqrt(zsq);
+                if (this._parent) {
+                    this._scaleVector.x /= this._parent._negateScaleChildren.x;
+                    this._scaleVector.y /= this._parent._negateScaleChildren.y;
+                    this._scaleVector.z /= this._parent._negateScaleChildren.z;
+                }
+            };
             this._skeleton = skeleton;
             this._matrix = matrix;
             this._baseMatrix = matrix;
@@ -138,6 +158,242 @@ var BABYLON;
             this.animations[0].createRange(rangeName, from + frameOffset, to + frameOffset);
             return true;
         };
+        Bone.prototype.translate = function (vec) {
+            var lm = this.getLocalMatrix();
+            lm.m[12] += vec.x;
+            lm.m[13] += vec.y;
+            lm.m[14] += vec.z;
+            this.markAsDirty();
+        };
+        Bone.prototype.setPosition = function (position) {
+            var lm = this.getLocalMatrix();
+            lm.m[12] = position.x;
+            lm.m[13] = position.y;
+            lm.m[14] = position.z;
+            this.markAsDirty();
+        };
+        Bone.prototype.setAbsolutePosition = function (position, mesh) {
+            if (mesh === void 0) { mesh = null; }
+            this._skeleton.computeAbsoluteTransforms();
+            var tmat = BABYLON.Tmp.Matrix[0];
+            var vec = BABYLON.Tmp.Vector3[0];
+            if (mesh) {
+                tmat.copyFrom(this._parent.getAbsoluteTransform());
+                tmat.multiplyToRef(mesh.getWorldMatrix(), tmat);
+            }
+            else {
+                tmat.copyFrom(this._parent.getAbsoluteTransform());
+            }
+            tmat.invert();
+            BABYLON.Vector3.TransformCoordinatesToRef(position, tmat, vec);
+            var lm = this.getLocalMatrix();
+            lm.m[12] = vec.x;
+            lm.m[13] = vec.y;
+            lm.m[14] = vec.z;
+            this.markAsDirty();
+        };
+        Bone.prototype.setScale = function (x, y, z, scaleChildren) {
+            if (scaleChildren === void 0) { scaleChildren = false; }
+            if (this.animations[0] && !this.animations[0].isStopped()) {
+                if (!scaleChildren) {
+                    this._negateScaleChildren.x = 1 / x;
+                    this._negateScaleChildren.y = 1 / y;
+                    this._negateScaleChildren.z = 1 / z;
+                }
+                this._syncScaleVector();
+            }
+            this.scale(x / this._scaleVector.x, y / this._scaleVector.y, z / this._scaleVector.z, scaleChildren);
+        };
+        Bone.prototype.scale = function (x, y, z, scaleChildren) {
+            if (scaleChildren === void 0) { scaleChildren = false; }
+            var locMat = this.getLocalMatrix();
+            var origLocMat = BABYLON.Tmp.Matrix[0];
+            origLocMat.copyFrom(locMat);
+            var origLocMatInv = BABYLON.Tmp.Matrix[1];
+            origLocMatInv.copyFrom(origLocMat);
+            origLocMatInv.invert();
+            var scaleMat = BABYLON.Tmp.Matrix[2];
+            BABYLON.Matrix.FromValuesToRef(x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1, scaleMat);
+            this._scaleMatrix.multiplyToRef(scaleMat, this._scaleMatrix);
+            this._scaleVector.x *= x;
+            this._scaleVector.y *= y;
+            this._scaleVector.z *= z;
+            locMat.multiplyToRef(origLocMatInv, locMat);
+            locMat.multiplyToRef(scaleMat, locMat);
+            locMat.multiplyToRef(origLocMat, locMat);
+            var parent = this.getParent();
+            if (parent) {
+                locMat.multiplyToRef(parent.getAbsoluteTransform(), this.getAbsoluteTransform());
+            }
+            else {
+                this.getAbsoluteTransform().copyFrom(locMat);
+            }
+            var len = this.children.length;
+            scaleMat.invert();
+            for (var i = 0; i < len; i++) {
+                var child = this.children[i];
+                var cm = child.getLocalMatrix();
+                cm.multiplyToRef(scaleMat, cm);
+                var lm = child.getLocalMatrix();
+                lm.m[12] *= x;
+                lm.m[13] *= y;
+                lm.m[14] *= z;
+            }
+            this.computeAbsoluteTransforms();
+            if (scaleChildren) {
+                for (var i = 0; i < len; i++) {
+                    this.children[i].scale(x, y, z, scaleChildren);
+                }
+            }
+            this.markAsDirty();
+        };
+        Bone.prototype.setYawPitchRoll = function (yaw, pitch, roll, space, mesh) {
+            if (space === void 0) { space = BABYLON.Space.LOCAL; }
+            if (mesh === void 0) { mesh = null; }
+            var rotMat = BABYLON.Tmp.Matrix[0];
+            BABYLON.Matrix.RotationYawPitchRollToRef(yaw, pitch, roll, rotMat);
+            var rotMatInv = BABYLON.Tmp.Matrix[1];
+            this._getNegativeRotationToRef(rotMatInv, space, mesh);
+            rotMatInv.multiplyToRef(rotMat, rotMat);
+            this._rotateWithMatrix(rotMat, space, mesh);
+        };
+        Bone.prototype.rotate = function (axis, amount, space, mesh) {
+            if (space === void 0) { space = BABYLON.Space.LOCAL; }
+            if (mesh === void 0) { mesh = null; }
+            var rmat = BABYLON.Tmp.Matrix[0];
+            rmat.m[12] = 0;
+            rmat.m[13] = 0;
+            rmat.m[14] = 0;
+            BABYLON.Matrix.RotationAxisToRef(axis, amount, rmat);
+            this._rotateWithMatrix(rmat, space, mesh);
+        };
+        Bone.prototype.setAxisAngle = function (axis, angle, space, mesh) {
+            var rotMat = BABYLON.Tmp.Matrix[0];
+            BABYLON.Matrix.RotationAxisToRef(axis, angle, rotMat);
+            var rotMatInv = BABYLON.Tmp.Matrix[1];
+            this._getNegativeRotationToRef(rotMatInv, space, mesh);
+            rotMatInv.multiplyToRef(rotMat, rotMat);
+            this._rotateWithMatrix(rotMat, space, mesh);
+        };
+        Bone.prototype._rotateWithMatrix = function (rmat, space, mesh) {
+            if (space === void 0) { space = BABYLON.Space.LOCAL; }
+            if (mesh === void 0) { mesh = null; }
+            var lmat = this.getLocalMatrix();
+            var lx = lmat.m[12];
+            var ly = lmat.m[13];
+            var lz = lmat.m[14];
+            var parent = this.getParent();
+            var parentScale = BABYLON.Tmp.Matrix[3];
+            var parentScaleInv = BABYLON.Tmp.Matrix[4];
+            if (parent) {
+                if (space == BABYLON.Space.WORLD) {
+                    if (mesh) {
+                        parentScale.copyFrom(mesh.getWorldMatrix());
+                        parent.getAbsoluteTransform().multiplyToRef(parentScale, parentScale);
+                    }
+                    else {
+                        parentScale.copyFrom(parent.getAbsoluteTransform());
+                    }
+                }
+                else {
+                    parentScale = parent._scaleMatrix;
+                }
+                parentScaleInv.copyFrom(parentScale);
+                parentScaleInv.invert();
+                lmat.multiplyToRef(parentScale, lmat);
+                lmat.multiplyToRef(rmat, lmat);
+                lmat.multiplyToRef(parentScaleInv, lmat);
+            }
+            else {
+                if (space == BABYLON.Space.WORLD && mesh) {
+                    parentScale.copyFrom(mesh.getWorldMatrix());
+                    parentScaleInv.copyFrom(parentScale);
+                    parentScaleInv.invert();
+                    lmat.multiplyToRef(parentScale, lmat);
+                    lmat.multiplyToRef(rmat, lmat);
+                    lmat.multiplyToRef(parentScaleInv, lmat);
+                }
+                else {
+                    lmat.multiplyToRef(rmat, lmat);
+                }
+            }
+            lmat.m[12] = lx;
+            lmat.m[13] = ly;
+            lmat.m[14] = lz;
+            this.computeAbsoluteTransforms();
+            this.markAsDirty();
+        };
+        Bone.prototype._getNegativeRotationToRef = function (rotMatInv, space, mesh) {
+            if (space === void 0) { space = BABYLON.Space.LOCAL; }
+            if (mesh === void 0) { mesh = null; }
+            if (space == BABYLON.Space.WORLD) {
+                rotMatInv.copyFrom(this.getAbsoluteTransform());
+                if (mesh) {
+                    rotMatInv.multiplyToRef(mesh.getWorldMatrix(), rotMatInv);
+                }
+                rotMatInv.invert();
+                var scaleMatrix = BABYLON.Tmp.Matrix[2];
+                scaleMatrix.copyFrom(this._scaleMatrix);
+                scaleMatrix.m[0] *= -1;
+                rotMatInv.multiplyToRef(scaleMatrix, rotMatInv);
+            }
+            else {
+                rotMatInv.copyFrom(this.getLocalMatrix());
+                rotMatInv.invert();
+                var scaleMatrix = BABYLON.Tmp.Matrix[2];
+                scaleMatrix.copyFrom(this._scaleMatrix);
+                if (this._parent) {
+                    var pscaleMatrix = BABYLON.Tmp.Matrix[3];
+                    pscaleMatrix.copyFrom(this._parent._scaleMatrix);
+                    pscaleMatrix.invert();
+                    pscaleMatrix.multiplyToRef(rotMatInv, rotMatInv);
+                }
+                else {
+                    scaleMatrix.m[0] *= -1;
+                }
+                rotMatInv.multiplyToRef(scaleMatrix, rotMatInv);
+            }
+        };
+        Bone.prototype.getScale = function () {
+            return this._scaleVector.clone();
+        };
+        Bone.prototype.getScaleToRef = function (result) {
+            result.copyFrom(this._scaleVector);
+        };
+        Bone.prototype.getAbsolutePosition = function (mesh) {
+            if (mesh === void 0) { mesh = null; }
+            var pos = BABYLON.Vector3.Zero();
+            this.getAbsolutePositionToRef(mesh, pos);
+            return pos;
+        };
+        Bone.prototype.getAbsolutePositionToRef = function (mesh, result) {
+            if (mesh === void 0) { mesh = null; }
+            this._skeleton.computeAbsoluteTransforms();
+            var tmat = BABYLON.Tmp.Matrix[0];
+            if (mesh) {
+                tmat.copyFrom(this.getAbsoluteTransform());
+                tmat.multiplyToRef(mesh.getWorldMatrix(), tmat);
+            }
+            else {
+                tmat = this.getAbsoluteTransform();
+            }
+            result.x = tmat.m[12];
+            result.y = tmat.m[13];
+            result.z = tmat.m[14];
+        };
+        Bone.prototype.computeAbsoluteTransforms = function () {
+            if (this._parent) {
+                this._matrix.multiplyToRef(this._parent._absoluteTransform, this._absoluteTransform);
+            }
+            else {
+                this._absoluteTransform.copyFrom(this._matrix);
+            }
+            var children = this.children;
+            var len = children.length;
+            for (var i = 0; i < len; i++) {
+                children[i].computeAbsoluteTransforms();
+            }
+        };
         return Bone;
     }(BABYLON.Node));
     BABYLON.Bone = Bone;

+ 287 - 90
src/Bones/babylon.bone.ts

@@ -13,9 +13,10 @@
         private _invertedAbsoluteTransform = new Matrix();
         private _parent: Bone;
 
-        private _scaleMatrix: Matrix;
-        private _scaleVector: Vector3;
-
+        private _scaleMatrix = Matrix.Identity();
+        private _scaleVector = new Vector3(1, 1, 1);
+        private _negateScaleChildren = new Vector3(1, 1, 1);
+        
         constructor(public name: string, skeleton: Skeleton, parentBone: Bone, matrix: Matrix, restPose?: Matrix) {
             super(name, skeleton.getScene());
             this._skeleton = skeleton;
@@ -165,156 +166,261 @@
             return true;
         }
 
-        public scale (x: number, y: number, z: number, scaleChildren = false) {
+        public translate (vec: Vector3): void {
+
+            var lm = this.getLocalMatrix();
+
+            lm.m[12] += vec.x;
+            lm.m[13] += vec.y;
+            lm.m[14] += vec.z;
+
+            this.markAsDirty();
+	        
+        }
+
+        public setPosition (position: Vector3): void {
+
+            var lm = this.getLocalMatrix();
+
+            lm.m[12] = position.x;
+            lm.m[13] = position.y;
+            lm.m[14] = position.z;
+
+            this.markAsDirty();
+	        
+        }
+
+        public setAbsolutePosition (position: Vector3, mesh: AbstractMesh = null): void {
+
+            this._skeleton.computeAbsoluteTransforms();
+
+            var tmat = Tmp.Matrix[0];
+            var vec = Tmp.Vector3[0];
+
+            if (mesh) {
+                tmat.copyFrom(this._parent.getAbsoluteTransform());
+                tmat.multiplyToRef(mesh.getWorldMatrix(), tmat);
+            }else {
+                tmat.copyFrom(this._parent.getAbsoluteTransform());
+            }
+
+            tmat.invert();
+			Vector3.TransformCoordinatesToRef(position, tmat, vec);
+
+			var lm = this.getLocalMatrix();
+            lm.m[12] = vec.x;
+            lm.m[13] = vec.y;
+            lm.m[14] = vec.z;
+            
+            this.markAsDirty();
+			
+	        
+        }
+
+        public setScale (x: number, y: number, z: number, scaleChildren = false): void {
+
+            if (this.animations[0] && !this.animations[0].isStopped()) {
+                if (!scaleChildren) {
+                    this._negateScaleChildren.x = 1/x;
+                    this._negateScaleChildren.y = 1/y;
+                    this._negateScaleChildren.z = 1/z;
+                }
+                this._syncScaleVector();
+            }
+
+	        this.scale(x / this._scaleVector.x, y / this._scaleVector.y, z / this._scaleVector.z, scaleChildren);
+
+        }
+
+        public scale (x: number, y: number, z: number, scaleChildren = false): void {
 	
             var locMat = this.getLocalMatrix();
-            
-            var origLocMat = BABYLON.Tmp.Matrix[0];
+            var origLocMat = Tmp.Matrix[0];
             origLocMat.copyFrom(locMat);
-            
-            var origLocMatInv = BABYLON.Tmp.Matrix[1];
+
+            var origLocMatInv = Tmp.Matrix[1];
             origLocMatInv.copyFrom(origLocMat);
             origLocMatInv.invert();
-            
-            var scaleMat = BABYLON.Tmp.Matrix[2];
-            BABYLON.Matrix.FromValuesToRef(x, 0, 0, 0,
-                                            0, y, 0, 0,
-                                            0, 0, z, 0,
-                                            0, 0, 0, 1, scaleMat);
-                                                
+
+            var scaleMat = Tmp.Matrix[2];
+            Matrix.FromValuesToRef(x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1, scaleMat);
             this._scaleMatrix.multiplyToRef(scaleMat, this._scaleMatrix);
-            
             this._scaleVector.x *= x;
             this._scaleVector.y *= y;
             this._scaleVector.z *= z;
-                    
+
             locMat.multiplyToRef(origLocMatInv, locMat);
             locMat.multiplyToRef(scaleMat, locMat);
             locMat.multiplyToRef(origLocMat, locMat);
-            
+
             var parent = this.getParent();
-            
+
             if (parent) {
                 locMat.multiplyToRef(parent.getAbsoluteTransform(), this.getAbsoluteTransform());
-            } else {
+            }else {
                 this.getAbsoluteTransform().copyFrom(locMat);
             }
-            
+
             var len = this.children.length;
+
+            scaleMat.invert();
             
-            for (var i = 0; i < len; i++){
-                
-                var parentAbsMat = this.children[i]._parent.getAbsoluteTransform();
-                
-                this.children[i].getLocalMatrix().multiplyToRef(parentAbsMat, this.children[i].getAbsoluteTransform());
-            
+            for (var i = 0; i < len; i++) {
+                var child = this.children[i];
+                var cm = child.getLocalMatrix();
+                cm.multiplyToRef(scaleMat, cm);
+                var lm = child.getLocalMatrix();
+                lm.m[12] *= x;
+                lm.m[13] *= y;
+                lm.m[14] *= z;
             }
-            
-            if (this.children[0] && !scaleChildren) {
 
-                scaleMat.invert();
-
-                var cm = this.children[0].getLocalMatrix();
-
-                cm.multiplyToRef(scaleMat, cm);
-                
-                var lm = this.children[0].getLocalMatrix();
+            this.computeAbsoluteTransforms();
+            
+            if (scaleChildren) {
+                for (var i = 0; i < len; i++) {
+                    this.children[i].scale(x, y, z, scaleChildren);
                     
-                lm.m[12] *= this._scaleVector.x;
-                lm.m[13] *= this._scaleVector.y;
-                lm.m[14] *= this._scaleVector.z;
+                }
+            }          
 
-            }
-            
             this.markAsDirty();
 
         }
 
-        public rotate (axis: BABYLON.Vector3, amount: number, space = BABYLON.Space.LOCAL, mesh: BABYLON.AbstractMesh = null) {
-
-            var lmat = this.getLocalMatrix();
+        public setYawPitchRoll (yaw: number, pitch: number, roll: number, space = Space.LOCAL, mesh: AbstractMesh = null): void {
+	
+            var rotMat = Tmp.Matrix[0];
+            Matrix.RotationYawPitchRollToRef(yaw, pitch, roll, rotMat);
             
-            var lx = lmat.m[12];
-            var ly = lmat.m[13];
-            var lz = lmat.m[14];
+            var rotMatInv = Tmp.Matrix[1];
+            
+            this._getNegativeRotationToRef(rotMatInv, space, mesh);
+	
+            rotMatInv.multiplyToRef(rotMat, rotMat);
+            
+            this._rotateWithMatrix(rotMat, space, mesh);
             
-            var rmat = BABYLON.Tmp.Matrix[0];
+        }
+
+        public rotate (axis: Vector3, amount: number, space = Space.LOCAL, mesh: AbstractMesh = null): void {
+            
+            var rmat = Tmp.Matrix[0];
             rmat.m[12] = 0;
             rmat.m[13] = 0;
             rmat.m[14] = 0;
             
-            var parent = this.getParent();
+            Matrix.RotationAxisToRef(axis, amount, rmat);
+            
+            this._rotateWithMatrix(rmat, space, mesh);
+            
+        }
 
-            BABYLON.Matrix.RotationAxisToRef(axis, amount, rmat);
+        public setAxisAngle (axis: Vector3, angle: number, space = Space.LOCAL, mesh: AbstractMesh = null): void {
 
-            var parentScale = BABYLON.Tmp.Matrix[1];
-            var parentScaleInv = BABYLON.Tmp.Matrix[2];
+            var rotMat = Tmp.Matrix[0];
+            Matrix.RotationAxisToRef(axis, angle, rotMat);
+            var rotMatInv = Tmp.Matrix[1];
+            
+            this._getNegativeRotationToRef(rotMatInv, space, mesh);
             
+            rotMatInv.multiplyToRef(rotMat, rotMat);
+            this._rotateWithMatrix(rotMat, space, mesh);
+
+        }
+
+        public setRotationMatrix (rotMat: Matrix, space = Space.LOCAL, mesh: AbstractMesh = null): void {
+
+            var rotMatInv = Tmp.Matrix[1];
+            
+            this._getNegativeRotationToRef(rotMatInv, space, mesh);
+
+            rotMatInv.multiplyToRef(rotMat, rotMat);
+            
+            this._rotateWithMatrix(rotMat, space, mesh);
+
+        }
+
+        private _rotateWithMatrix (rmat: Matrix, space = Space.LOCAL, mesh: AbstractMesh = null): void {
+
+            var lmat = this.getLocalMatrix();
+            var lx = lmat.m[12];
+            var ly = lmat.m[13];
+            var lz = lmat.m[14];
+            var parent = this.getParent();
+            var parentScale = Tmp.Matrix[3];
+            var parentScaleInv = Tmp.Matrix[4];
+
             if (parent) {
-                
-                if (space == BABYLON.Space.WORLD) {
-                    
+                if (space == Space.WORLD) {
                     if (mesh) {
                         parentScale.copyFrom(mesh.getWorldMatrix());
                         parent.getAbsoluteTransform().multiplyToRef(parentScale, parentScale);
-                    } else {
+                    }else {
                         parentScale.copyFrom(parent.getAbsoluteTransform());
                     }
-                    
-                } else {
-                    
+                }else {
                     parentScale = parent._scaleMatrix;
-                    
                 }
-
                 parentScaleInv.copyFrom(parentScale);
                 parentScaleInv.invert();
-                
                 lmat.multiplyToRef(parentScale, lmat);
                 lmat.multiplyToRef(rmat, lmat);
                 lmat.multiplyToRef(parentScaleInv, lmat);
-                
-            } else {
-                
-                if (space == BABYLON.Space.WORLD && mesh) {
-
+            }else {
+                if (space == Space.WORLD && mesh) {
                     parentScale.copyFrom(mesh.getWorldMatrix());
-
                     parentScaleInv.copyFrom(parentScale);
                     parentScaleInv.invert();
-                    
                     lmat.multiplyToRef(parentScale, lmat);
                     lmat.multiplyToRef(rmat, lmat);
                     lmat.multiplyToRef(parentScaleInv, lmat);
-                    
-                } else {
-                    
+                }else {
                     lmat.multiplyToRef(rmat, lmat);
-
                 }
-
             }
-            
+
             lmat.m[12] = lx;
             lmat.m[13] = ly;
             lmat.m[14] = lz;
+
+            this.computeAbsoluteTransforms();
+
+            this.markAsDirty();
             
-            if (parent) {
-                var parentAbsMat = this._parent.getAbsoluteTransform();
-                lmat.multiplyToRef(parentAbsMat, this.getAbsoluteTransform());
+        }
+
+        private _getNegativeRotationToRef(rotMatInv: Matrix, space = Space.LOCAL, mesh: AbstractMesh = null): void {
+
+            if (space == Space.WORLD) {
+                var scaleMatrix = BABYLON.Tmp.Matrix[2];
+				scaleMatrix.copyFrom(this._scaleMatrix);
+				rotMatInv.copyFrom(this.getAbsoluteTransform());
+				if (mesh) {
+                    rotMatInv.multiplyToRef(mesh.getWorldMatrix(), rotMatInv);
+					var meshScale = BABYLON.Tmp.Matrix[3];
+					BABYLON.Matrix.ScalingToRef(mesh.scaling.x, mesh.scaling.y, mesh.scaling.z, meshScale);
+					scaleMatrix.multiplyToRef(meshScale, scaleMatrix);
+                }
+				rotMatInv.invert();
+                scaleMatrix.m[0] *= -1;
+                rotMatInv.multiplyToRef(scaleMatrix, rotMatInv);
             } else {
-                this.getAbsoluteTransform().copyFrom(lmat);
-            }
-            
-            var len = this.children.length;
-            
-            for (var i = 0; i < len; i++){
-                var parentAbsMat = this.children[i]._parent.getAbsoluteTransform();
-                this.children[i].getLocalMatrix().multiplyToRef(parentAbsMat, this.children[i].getAbsoluteTransform());
+                rotMatInv.copyFrom(this.getLocalMatrix());
+                rotMatInv.invert();
+                var scaleMatrix = Tmp.Matrix[2];
+                scaleMatrix.copyFrom(this._scaleMatrix);
+                if (this._parent) {
+                    var pscaleMatrix = Tmp.Matrix[3];
+                    pscaleMatrix.copyFrom(this._parent._scaleMatrix);
+                    pscaleMatrix.invert();
+                    pscaleMatrix.multiplyToRef(rotMatInv, rotMatInv);
+                } else {
+                    scaleMatrix.m[0] *= -1;
+                }
+                rotMatInv.multiplyToRef(scaleMatrix, rotMatInv);
             }
-            
-            this.markAsDirty();
-            
+
         }
 
         public getScale(): Vector3 {
@@ -323,11 +429,102 @@
             
         }
 
-        public getScaleToRef(result:Vector3): void {
+        public getScaleToRef(result: Vector3): void {
 	
             result.copyFrom(this._scaleVector);
             
         }
 
+        public getAbsolutePosition (mesh: AbstractMesh = null): Vector3 {
+
+            var pos = Vector3.Zero();
+
+            this.getAbsolutePositionToRef(mesh, pos);
+
+            return pos;
+
+        }
+
+        public getAbsolutePositionToRef (mesh: AbstractMesh = null, result: Vector3): void {
+
+            this._skeleton.computeAbsoluteTransforms();
+            
+            var tmat = Tmp.Matrix[0];
+
+            if (mesh) {
+                tmat.copyFrom(this.getAbsoluteTransform());
+                tmat.multiplyToRef(mesh.getWorldMatrix(), tmat);
+            }else{
+                tmat = this.getAbsoluteTransform();
+            }
+
+            result.x = tmat.m[12];
+            result.y = tmat.m[13];
+            result.z = tmat.m[14];
+
+        }
+
+        public computeAbsoluteTransforms (): void {
+
+            if (this._parent) {
+                this._matrix.multiplyToRef(this._parent._absoluteTransform, this._absoluteTransform);
+            } else {
+                this._absoluteTransform.copyFrom(this._matrix);
+            }
+
+            var children = this.children;
+            var len = children.length;
+
+            for (var i = 0; i < len; i++) {
+                children[i].computeAbsoluteTransforms();
+            }
+
+        }
+
+        private _syncScaleVector = function(): void{
+            
+            var lm = this.getLocalMatrix();
+            
+            var xsq = (lm.m[0] * lm.m[0] + lm.m[1] * lm.m[1] + lm.m[2] * lm.m[2]);
+            var ysq = (lm.m[4] * lm.m[4] + lm.m[5] * lm.m[5] + lm.m[6] * lm.m[6]);
+            var zsq = (lm.m[8] * lm.m[8] + lm.m[9] * lm.m[9] + lm.m[10] * lm.m[10]);
+            
+            var xs = lm.m[0] * lm.m[1] * lm.m[2] * lm.m[3] < 0 ? -1 : 1;
+            var ys = lm.m[4] * lm.m[5] * lm.m[6] * lm.m[7] < 0 ? -1 : 1;
+            var zs = lm.m[8] * lm.m[9] * lm.m[10] * lm.m[11] < 0 ? -1 : 1;
+            
+            this._scaleVector.x = xs * Math.sqrt(xsq);
+            this._scaleVector.y = ys * Math.sqrt(ysq);
+            this._scaleVector.z = zs * Math.sqrt(zsq);
+            
+            if (this._parent) {
+                this._scaleVector.x /= this._parent._negateScaleChildren.x;
+                this._scaleVector.y /= this._parent._negateScaleChildren.y;
+                this._scaleVector.z /= this._parent._negateScaleChildren.z;
+            }
+
+        }
+
+        public getDirection (localAxis: Vector3){
+
+            var result = Vector3.Zero();
+
+            this.getDirectionToRef(localAxis, result);
+            
+            return result;
+
+        }
+
+        public getDirectionToRef (localAxis: Vector3, result: Vector3) {
+
+            this._skeleton.computeAbsoluteTransforms();
+            Vector3.TransformNormalToRef(localAxis, this.getAbsoluteTransform(), result);
+            
+            if (this._scaleVector.x != 1 || this._scaleVector.y != 1 || this._scaleVector.z != 1) {
+                result.normalize();
+            }
+
+        }
+
     }
 } 

+ 9 - 0
src/Bones/babylon.skeleton.js

@@ -10,6 +10,7 @@ var BABYLON;
             this._meshesWithPoseMatrix = new Array();
             this._identity = BABYLON.Matrix.Identity();
             this._ranges = {};
+            this._lastAbsoluteTransformsUpdateId = -1;
             this.bones = [];
             this._scene = scene;
             scene.skeletons.push(this);
@@ -333,6 +334,14 @@ var BABYLON;
             }
             return skeleton;
         };
+        Skeleton.prototype.computeAbsoluteTransforms = function (forceUpdate) {
+            if (forceUpdate === void 0) { forceUpdate = false; }
+            var renderId = this._scene.getRenderId();
+            if (this._lastAbsoluteTransformsUpdateId != renderId || forceUpdate) {
+                this.bones[0].computeAbsoluteTransforms();
+                this._lastAbsoluteTransformsUpdateId = renderId;
+            }
+        };
         return Skeleton;
     }());
     BABYLON.Skeleton = Skeleton;

+ 14 - 0
src/Bones/babylon.skeleton.ts

@@ -13,6 +13,8 @@
 
         private _ranges: { [name: string]: AnimationRange; } = {};
 
+        private _lastAbsoluteTransformsUpdateId = -1;
+
         constructor(public name: string, public id: string, scene: Scene) {
             this.bones = [];
 
@@ -402,5 +404,17 @@
             }
             return skeleton;
         }
+
+        public computeAbsoluteTransforms (forceUpdate = false): void {
+
+            var renderId = this._scene.getRenderId();
+            
+            if (this._lastAbsoluteTransformsUpdateId != renderId || forceUpdate ) {
+                this.bones[0].computeAbsoluteTransforms();
+                this._lastAbsoluteTransformsUpdateId = renderId;
+            }
+            
+        }
+
     }
 }

+ 13 - 1
src/Cameras/babylon.camera.js

@@ -35,6 +35,7 @@ var BABYLON;
             // Cache
             this._computedViewMatrix = BABYLON.Matrix.Identity();
             this._projectionMatrix = new BABYLON.Matrix();
+            this._doNotComputeProjectionMatrix = false;
             this._postProcesses = new Array();
             this._transformMatrix = BABYLON.Matrix.Zero();
             this._webvrViewMatrix = BABYLON.Matrix.Identity();
@@ -346,8 +347,19 @@ var BABYLON;
             this._currentRenderId = this.getScene().getRenderId();
             return this._computedViewMatrix;
         };
+        Camera.prototype.freezeProjectionMatrix = function (projection) {
+            this._doNotComputeProjectionMatrix = true;
+            if (projection !== undefined) {
+                this._projectionMatrix = projection;
+            }
+        };
+        ;
+        Camera.prototype.unfreezeProjectionMatrix = function () {
+            this._doNotComputeProjectionMatrix = false;
+        };
+        ;
         Camera.prototype.getProjectionMatrix = function (force) {
-            if (!force && this._isSynchronizedProjectionMatrix()) {
+            if (this._doNotComputeProjectionMatrix || (!force && this._isSynchronizedProjectionMatrix())) {
                 return this._projectionMatrix;
             }
             this._refreshFrustumPlanes = true;

+ 14 - 2
src/Cameras/babylon.camera.ts

@@ -123,6 +123,7 @@
         // Cache
         private _computedViewMatrix = Matrix.Identity();
         public _projectionMatrix = new Matrix();
+        private _doNotComputeProjectionMatrix = false;
         private _worldMatrix: Matrix;
         public _postProcesses = new Array<PostProcess>();
         private _transformMatrix = Matrix.Zero();
@@ -413,8 +414,19 @@
             return this._computedViewMatrix;
         }
 
+        public freezeProjectionMatrix(projection?: Matrix): void {
+            this._doNotComputeProjectionMatrix = true;
+            if (projection !== undefined) {
+                this._projectionMatrix = projection;
+            }
+        };
+        
+        public unfreezeProjectionMatrix(): void {
+            this._doNotComputeProjectionMatrix = false;
+        };
+        
         public getProjectionMatrix(force?: boolean): Matrix {
-            if (!force && this._isSynchronizedProjectionMatrix()) {
+            if (this._doNotComputeProjectionMatrix || (!force && this._isSynchronizedProjectionMatrix())) {
                 return this._projectionMatrix;
             }
 
@@ -784,4 +796,4 @@
             return camera;
         }
     }
-}
+}

+ 1 - 1
src/Materials/Textures/babylon.cubeTexture.js

@@ -43,7 +43,7 @@ var BABYLON;
                 if (this._texture.isReady) {
                     BABYLON.Tools.SetImmediate(function () { return onLoad(); });
                 }
-                else {
+                else if (onLoad) {
                     this._texture.onLoadedCallbacks.push(onLoad);
                 }
             }

+ 1 - 1
src/Materials/Textures/babylon.cubeTexture.ts

@@ -52,7 +52,7 @@
             } else {
                 if (this._texture.isReady) {
                     Tools.SetImmediate(() => onLoad());
-                } else {
+                } else if (onLoad) {
                     this._texture.onLoadedCallbacks.push(onLoad);
                 }
             }

+ 187 - 18
src/Materials/Textures/babylon.fontTexture.js

@@ -25,10 +25,11 @@ var BABYLON;
          * @param samplingMode the texture sampling mode
          * @param superSample if true the FontTexture will be created with a font of a size twice bigger than the given one but all properties (lineHeight, charWidth, etc.) will be according to the original size. This is made to improve the text quality.
          */
-        function FontTexture(name, font, scene, maxCharCount, samplingMode, superSample) {
+        function FontTexture(name, font, scene, maxCharCount, samplingMode, superSample, signedDistanceField) {
             if (maxCharCount === void 0) { maxCharCount = 200; }
             if (samplingMode === void 0) { samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE; }
             if (superSample === void 0) { superSample = false; }
+            if (signedDistanceField === void 0) { signedDistanceField = false; }
             _super.call(this, null, scene, true, false, samplingMode);
             this._charInfos = {};
             this._curCharCount = 0;
@@ -37,8 +38,11 @@ var BABYLON;
             this.name = name;
             this.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
             this.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this._sdfScale = 8;
+            this._signedDistanceField = signedDistanceField;
             this._superSample = false;
-            if (superSample) {
+            // SDF will use supersample no matter what, the resolution is otherwise too poor to produce correct result
+            if (superSample || signedDistanceField) {
                 var sfont = this.getSuperSampleFont(font);
                 if (sfont) {
                     this._superSample = true;
@@ -50,22 +54,25 @@ var BABYLON;
             this._context = this._canvas.getContext("2d");
             this._context.font = font;
             this._context.fillStyle = "white";
+            this._context.textBaseline = "top";
             this._cachedFontId = null;
             var res = this.getFontHeight(font);
-            this._lineHeightSuper = res.height;
-            this._lineHeight = this._superSample ? (this._lineHeightSuper / 2) : this._lineHeightSuper;
+            this._lineHeightSuper = res.height + 4;
+            this._lineHeight = this._superSample ? (Math.ceil(this._lineHeightSuper / 2)) : this._lineHeightSuper;
             this._offset = res.offset - 1;
+            this._xMargin = 1 + Math.ceil(this._lineHeightSuper / 15); // Right now this empiric formula seems to work...
+            this._yMargin = this._xMargin;
             var maxCharWidth = this._context.measureText("W").width;
             this._spaceWidthSuper = this._context.measureText(" ").width;
             this._spaceWidth = this._superSample ? (this._spaceWidthSuper / 2) : this._spaceWidthSuper;
             // This is an approximate size, but should always be able to fit at least the maxCharCount
-            var totalEstSurface = this._lineHeightSuper * maxCharWidth * maxCharCount;
+            var totalEstSurface = (this._lineHeightSuper + this._yMargin) * (maxCharWidth + this._xMargin) * maxCharCount;
             var edge = Math.sqrt(totalEstSurface);
             var textSize = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
             // Create the texture that will store the font characters
             this._texture = scene.getEngine().createDynamicTexture(textSize, textSize, false, samplingMode);
             var textureSize = this.getSize();
-            this.hasAlpha = true;
+            this.hasAlpha = this._signedDistanceField === false;
             // Recreate a new canvas with the final size: the one matching the texture (resizing the previous one doesn't work as one would expect...)
             this._canvas = document.createElement("canvas");
             this._canvas.width = textureSize.width;
@@ -75,6 +82,24 @@ var BABYLON;
             this._context.font = font;
             this._context.fillStyle = "white";
             this._context.imageSmoothingEnabled = false;
+            this._context.clearRect(0, 0, textureSize.width, textureSize.height);
+            // Create a canvas for the signed distance field mode, we only have to store one char, the purpose is to render a char scaled _sdfScale times
+            //  into this 2D context, then get the bitmap data, create the sdf char and push the result in the _context (which hold the whole Font Texture content)
+            // So you can see this context as an intermediate one, because it is.
+            if (this._signedDistanceField) {
+                var sdfC = document.createElement("canvas");
+                var s = this._sdfScale;
+                sdfC.width = maxCharWidth * s;
+                sdfC.height = this._lineHeightSuper * s;
+                var sdfCtx = sdfC.getContext("2d");
+                sdfCtx.scale(s, s);
+                sdfCtx.textBaseline = "top";
+                sdfCtx.font = font;
+                sdfCtx.fillStyle = "white";
+                sdfCtx.imageSmoothingEnabled = false;
+                this._sdfCanvas = sdfC;
+                this._sdfContext = sdfCtx;
+            }
             this._currentFreePosition = BABYLON.Vector2.Zero();
             // Add the basic ASCII based characters
             for (var i = 0x20; i < 0x7F; i++) {
@@ -90,6 +115,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(FontTexture.prototype, "isSignedDistanceField", {
+            get: function () {
+                return this._signedDistanceField;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(FontTexture.prototype, "spaceWidth", {
             get: function () {
                 return this._spaceWidth;
@@ -104,32 +136,34 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
-        FontTexture.GetCachedFontTexture = function (scene, fontName, supersample) {
+        FontTexture.GetCachedFontTexture = function (scene, fontName, supersample, signedDistanceField) {
             if (supersample === void 0) { supersample = false; }
+            if (signedDistanceField === void 0) { signedDistanceField = false; }
             var s = scene;
             if (!s.__fontTextureCache__) {
                 s.__fontTextureCache__ = new BABYLON.StringDictionary();
             }
             var dic = s.__fontTextureCache__;
-            var lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS");
+            var lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS") + (signedDistanceField ? "_+SDF" : "_-SDF");
             var ft = dic.get(lfn);
             if (ft) {
                 ++ft._usedCounter;
                 return ft;
             }
-            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, BABYLON.Texture.BILINEAR_SAMPLINGMODE, supersample);
+            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, BABYLON.Texture.BILINEAR_SAMPLINGMODE, supersample, signedDistanceField);
             ft._cachedFontId = lfn;
             dic.add(lfn, ft);
             return ft;
         };
-        FontTexture.ReleaseCachedFontTexture = function (scene, fontName, supersample) {
+        FontTexture.ReleaseCachedFontTexture = function (scene, fontName, supersample, signedDistanceField) {
             if (supersample === void 0) { supersample = false; }
+            if (signedDistanceField === void 0) { signedDistanceField = false; }
             var s = scene;
             var dic = s.__fontTextureCache__;
             if (!dic) {
                 return;
             }
-            var lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS");
+            var lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS") + (signedDistanceField ? "_+SDF" : "_-SDF");
             var font = dic.get(lfn);
             if (--font._usedCounter === 0) {
                 dic.remove(lfn);
@@ -154,29 +188,164 @@ var BABYLON;
             var textureSize = this.getSize();
             // we reached the end of the current line?
             var width = Math.round(measure.width);
-            var xMargin = 1 + Math.ceil(this._lineHeightSuper / 15); // Right now this empiric formula seems to work...
-            var yMargin = xMargin;
-            if (this._currentFreePosition.x + width + xMargin > textureSize.width) {
+            if (this._currentFreePosition.x + width + this._xMargin > textureSize.width) {
                 this._currentFreePosition.x = 0;
-                this._currentFreePosition.y += this._lineHeightSuper + yMargin;
+                this._currentFreePosition.y += this._lineHeightSuper + this._yMargin;
                 // No more room?
                 if (this._currentFreePosition.y > textureSize.height) {
                     return this.getChar("!");
                 }
             }
-            // Draw the character in the texture
-            this._context.fillText(char, this._currentFreePosition.x, this._currentFreePosition.y - this._offset);
+            // In sdf mode we render the character in an intermediate 2D context which scale the character this._sdfScale times (which is required to compute the sdf map accurately)
+            if (this._signedDistanceField) {
+                this._sdfContext.clearRect(0, 0, this._sdfCanvas.width, this._sdfCanvas.height);
+                this._sdfContext.fillText(char, 0, -this._offset);
+                var data = this._sdfContext.getImageData(0, 0, width * this._sdfScale, this._sdfCanvas.height);
+                var res = this._computeSDFChar(data);
+                this._context.putImageData(res, this._currentFreePosition.x, this._currentFreePosition.y);
+            }
+            else {
+                // Draw the character in the HTML canvas
+                this._context.fillText(char, this._currentFreePosition.x, this._currentFreePosition.y - this._offset);
+            }
             // Fill the CharInfo object
             info.topLeftUV = new BABYLON.Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
             info.bottomRightUV = new BABYLON.Vector2((this._currentFreePosition.x + width) / textureSize.width, info.topLeftUV.y + ((this._lineHeightSuper + 2) / textureSize.height));
+            if (this._signedDistanceField) {
+                var off = 1 / textureSize.width;
+                info.topLeftUV.addInPlace(new BABYLON.Vector2(off, off));
+                info.bottomRightUV.addInPlace(new BABYLON.Vector2(off, off));
+            }
             info.charWidth = this._superSample ? (width / 2) : width;
             // Add the info structure
             this._charInfos[char] = info;
             this._curCharCount++;
             // Set the next position
-            this._currentFreePosition.x += width + xMargin;
+            this._currentFreePosition.x += width + this._xMargin;
             return info;
         };
+        FontTexture.prototype._computeSDFChar = function (source) {
+            var scl = this._sdfScale;
+            var sw = source.width;
+            var sh = source.height;
+            var dw = sw / scl;
+            var dh = sh / scl;
+            var roffx = 0;
+            var roffy = 0;
+            // We shouldn't look beyond half of the biggest between width and height
+            var radius = scl;
+            var br = radius - 1;
+            var lookupSrc = function (dx, dy, offX, offY, lookVis) {
+                var sx = dx * scl;
+                var sy = dy * scl;
+                // Looking out of the area? return true to make the test going on
+                if (((sx + offX) < 0) || ((sx + offX) >= sw) || ((sy + offY) < 0) || ((sy + offY) >= sh)) {
+                    return true;
+                }
+                // Get the pixel we want
+                var val = source.data[(((sy + offY) * sw) + (sx + offX)) * 4];
+                var res = (val > 0) === lookVis;
+                if (!res) {
+                    roffx = offX;
+                    roffy = offY;
+                }
+                return res;
+            };
+            var lookupArea = function (dx, dy, lookVis) {
+                // Fast rejection test, if we have the same result in N, S, W, E at a distance which is the radius-1 then it means the data will be consistent in this area. That's because we've scale the rendering of the letter "radius" times, so a letter's pixel will be at least radius wide
+                if (lookupSrc(dx, dy, 0, br, lookVis) &&
+                    lookupSrc(dx, dy, 0, -br, lookVis) &&
+                    lookupSrc(dx, dy, -br, 0, lookVis) &&
+                    lookupSrc(dx, dy, br, 0, lookVis)) {
+                    return 0;
+                }
+                for (var i = 1; i <= radius; i++) {
+                    // Quick test N, S, W, E
+                    if (!lookupSrc(dx, dy, 0, i, lookVis) || !lookupSrc(dx, dy, 0, -i, lookVis) || !lookupSrc(dx, dy, -i, 0, lookVis) || !lookupSrc(dx, dy, i, 0, lookVis)) {
+                        return i * i; // Squared Distance is simple to compute in this case
+                    }
+                    // Test the frame area (except the N, S, W, E spots) from the nearest point from the center to the further one
+                    for (var j = 1; j <= i; j++) {
+                        if (!lookupSrc(dx, dy, -j, i, lookVis) || !lookupSrc(dx, dy, j, i, lookVis) ||
+                            !lookupSrc(dx, dy, i, -j, lookVis) || !lookupSrc(dx, dy, i, j, lookVis) ||
+                            !lookupSrc(dx, dy, -j, -i, lookVis) || !lookupSrc(dx, dy, j, -i, lookVis) ||
+                            !lookupSrc(dx, dy, -i, -j, lookVis) || !lookupSrc(dx, dy, -i, j, lookVis)) {
+                            // We found the nearest texel having and opposite state, store the squared length
+                            var res_1 = (i * i) + (j * j);
+                            var count = 1;
+                            // To improve quality we will  sample the texels around this one, so it's 8 samples, we consider only the one having an opposite state, add them to the current res and will will compute the average at the end
+                            if (!lookupSrc(dx, dy, roffx - 1, roffy, lookVis)) {
+                                res_1 += (roffx - 1) * (roffx - 1) + roffy * roffy;
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx + 1, roffy, lookVis)) {
+                                res_1 += (roffx + 1) * (roffx + 1) + roffy * roffy;
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx, roffy - 1, lookVis)) {
+                                res_1 += roffx * roffx + (roffy - 1) * (roffy - 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx, roffy + 1, lookVis)) {
+                                res_1 += roffx * roffx + (roffy + 1) * (roffy + 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx - 1, roffy - 1, lookVis)) {
+                                res_1 += (roffx - 1) * (roffx - 1) + (roffy - 1) * (roffy - 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx + 1, roffy + 1, lookVis)) {
+                                res_1 += (roffx + 1) * (roffx + 1) + (roffy + 1) * (roffy + 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx + 1, roffy - 1, lookVis)) {
+                                res_1 += (roffx + 1) * (roffx + 1) + (roffy - 1) * (roffy - 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx - 1, roffy + 1, lookVis)) {
+                                res_1 += (roffx - 1) * (roffx - 1) + (roffy + 1) * (roffy + 1);
+                                ++count;
+                            }
+                            // Compute the average based on the accumulated distance
+                            return res_1 / count;
+                        }
+                    }
+                }
+                return 0;
+            };
+            var tmp = new Array(dw * dh);
+            for (var y = 0; y < dh; y++) {
+                for (var x = 0; x < dw; x++) {
+                    var curState = lookupSrc(x, y, 0, 0, true);
+                    var d = lookupArea(x, y, curState);
+                    if (d === 0) {
+                        d = radius * radius * 2;
+                    }
+                    tmp[(y * dw) + x] = curState ? d : -d;
+                }
+            }
+            var res = this._context.createImageData(dw, dh);
+            var size = dw * dh;
+            for (var j = 0; j < size; j++) {
+                var d = tmp[j];
+                var sign = (d < 0) ? -1 : 1;
+                d = Math.sqrt(Math.abs(d)) * sign;
+                d *= 127.5 / radius;
+                d += 127.5;
+                if (d < 0) {
+                    d = 0;
+                }
+                else if (d > 255) {
+                    d = 255;
+                }
+                d += 0.5;
+                res.data[j * 4 + 0] = d;
+                res.data[j * 4 + 1] = d;
+                res.data[j * 4 + 2] = d;
+                res.data[j * 4 + 3] = 255;
+            }
+            return res;
+        };
         FontTexture.prototype.measureText = function (text, tabulationSize) {
             if (tabulationSize === void 0) { tabulationSize = 4; }
             var maxWidth = 0;

+ 219 - 19
src/Materials/Textures/babylon.fontTexture.ts

@@ -26,6 +26,8 @@
         private _context: CanvasRenderingContext2D;
         private _lineHeight: number;
         private _lineHeightSuper: number;
+        private _xMargin: number;
+        private _yMargin: number;
         private _offset: number;
         private _currentFreePosition: Vector2;
         private _charInfos: ICharInfoMap = {};
@@ -35,12 +37,20 @@
         private _spaceWidthSuper;
         private _usedCounter = 1;
         private _superSample: boolean;
+        private _sdfCanvas: HTMLCanvasElement;
+        private _sdfContext: CanvasRenderingContext2D;
+        private _signedDistanceField: boolean;
         private _cachedFontId: string;
+        private _sdfScale: number;
 
         public get isSuperSampled(): boolean {
             return this._superSample;
         }
 
+        public get isSignedDistanceField(): boolean {
+            return this._signedDistanceField;
+        }
+
         public get spaceWidth(): number {
             return this._spaceWidth;
         }
@@ -49,7 +59,7 @@
             return this._lineHeight;
         }
 
-        public static GetCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false) {
+        public static GetCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false, signedDistanceField: boolean = false) {
             let s = <any>scene;
             if (!s.__fontTextureCache__) {
                 s.__fontTextureCache__ = new StringDictionary<FontTexture>();
@@ -57,28 +67,28 @@
 
             let dic = <StringDictionary<FontTexture>>s.__fontTextureCache__;
 
-            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS");
+            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS") + (signedDistanceField ? "_+SDF" : "_-SDF");
             let ft = dic.get(lfn);
             if (ft) {
                 ++ft._usedCounter;
                 return ft;
             }
 
-            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, Texture.BILINEAR_SAMPLINGMODE, supersample);
+            ft = new FontTexture(null, fontName, scene, supersample ? 100 : 200, Texture.BILINEAR_SAMPLINGMODE, supersample, signedDistanceField);
             ft._cachedFontId = lfn;
             dic.add(lfn, ft);
 
             return ft;
         }
 
-        public static ReleaseCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false) {
+        public static ReleaseCachedFontTexture(scene: Scene, fontName: string, supersample: boolean = false, signedDistanceField: boolean = false) {
             let s = <any>scene;
             let dic = <StringDictionary<FontTexture>>s.__fontTextureCache__;
             if (!dic) {
                 return;
             }
 
-            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS");
+            let lfn = fontName.toLocaleLowerCase() + (supersample ? "_+SS" : "_-SS") + (signedDistanceField ? "_+SDF" : "_-SDF");
             var font = dic.get(lfn);
             if (--font._usedCounter === 0) {
                 dic.remove(lfn);
@@ -95,7 +105,7 @@
          * @param samplingMode the texture sampling mode
          * @param superSample if true the FontTexture will be created with a font of a size twice bigger than the given one but all properties (lineHeight, charWidth, etc.) will be according to the original size. This is made to improve the text quality.
          */
-        constructor(name: string, font: string, scene: Scene, maxCharCount=200, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, superSample: boolean = false) {
+        constructor(name: string, font: string, scene: Scene, maxCharCount=200, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE, superSample: boolean = false, signedDistanceField: boolean = false) {
             super(null, scene, true, false, samplingMode);
 
             this.name = name;
@@ -103,8 +113,12 @@
             this.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.wrapV = Texture.CLAMP_ADDRESSMODE;
 
+            this._sdfScale = 8;
+            this._signedDistanceField = signedDistanceField;
             this._superSample = false;
-            if (superSample) {
+
+            // SDF will use supersample no matter what, the resolution is otherwise too poor to produce correct result
+            if (superSample || signedDistanceField) {
                 let sfont = this.getSuperSampleFont(font);
                 if (sfont) {
                     this._superSample = true;
@@ -117,19 +131,22 @@
             this._context = this._canvas.getContext("2d");
             this._context.font = font;
             this._context.fillStyle = "white";
+            this._context.textBaseline = "top";
             this._cachedFontId = null;
 
             var res = this.getFontHeight(font);
-            this._lineHeightSuper = res.height;
-            this._lineHeight = this._superSample ? (this._lineHeightSuper / 2) : this._lineHeightSuper;
-            this._offset = res.offset-1;
+            this._lineHeightSuper = res.height+4;
+            this._lineHeight = this._superSample ? (Math.ceil(this._lineHeightSuper / 2)) : this._lineHeightSuper;
+            this._offset = res.offset - 1;
+            this._xMargin = 1 + Math.ceil(this._lineHeightSuper / 15);    // Right now this empiric formula seems to work...
+            this._yMargin = this._xMargin;
 
             var maxCharWidth = this._context.measureText("W").width;
             this._spaceWidthSuper = this._context.measureText(" ").width;
             this._spaceWidth = this._superSample ? (this._spaceWidthSuper / 2) : this._spaceWidthSuper;
 
             // This is an approximate size, but should always be able to fit at least the maxCharCount
-            var totalEstSurface = this._lineHeightSuper * maxCharWidth * maxCharCount;
+            var totalEstSurface = (this._lineHeightSuper + this._yMargin) * (maxCharWidth + this._xMargin) * maxCharCount;
             var edge = Math.sqrt(totalEstSurface);
             var textSize = Math.pow(2, Math.ceil(Math.log(edge) / Math.log(2)));
 
@@ -137,7 +154,7 @@
             this._texture = scene.getEngine().createDynamicTexture(textSize, textSize, false, samplingMode);
             var textureSize = this.getSize();
 
-            this.hasAlpha = true;
+            this.hasAlpha = this._signedDistanceField===false;
 
             // Recreate a new canvas with the final size: the one matching the texture (resizing the previous one doesn't work as one would expect...)
             this._canvas = document.createElement("canvas");
@@ -148,6 +165,26 @@
             this._context.font = font;
             this._context.fillStyle = "white";
             this._context.imageSmoothingEnabled = false;
+            this._context.clearRect(0, 0, textureSize.width, textureSize.height);
+
+            // Create a canvas for the signed distance field mode, we only have to store one char, the purpose is to render a char scaled _sdfScale times
+            //  into this 2D context, then get the bitmap data, create the sdf char and push the result in the _context (which hold the whole Font Texture content)
+            // So you can see this context as an intermediate one, because it is.
+            if (this._signedDistanceField) {
+                let sdfC = document.createElement("canvas");
+                let s = this._sdfScale;
+                sdfC.width = maxCharWidth * s;
+                sdfC.height = this._lineHeightSuper * s;
+                let sdfCtx = sdfC.getContext("2d");
+                sdfCtx.scale(s, s);
+                sdfCtx.textBaseline = "top";
+                sdfCtx.font = font;
+                sdfCtx.fillStyle = "white";
+                sdfCtx.imageSmoothingEnabled = false;
+
+                this._sdfCanvas = sdfC;
+                this._sdfContext = sdfCtx;
+            }
 
             this._currentFreePosition = Vector2.Zero();
 
@@ -156,6 +193,7 @@
                 var c = String.fromCharCode(i);
                 this.getChar(c);
             }
+
             this.update();
         }
 
@@ -181,11 +219,9 @@
 
             // we reached the end of the current line?
             let width = Math.round(measure.width);
-            var xMargin = 1 + Math.ceil(this._lineHeightSuper / 15);    // Right now this empiric formula seems to work...
-            var yMargin = xMargin;
-            if (this._currentFreePosition.x + width + xMargin > textureSize.width) {
+            if (this._currentFreePosition.x + width + this._xMargin > textureSize.width) {
                 this._currentFreePosition.x = 0;
-                this._currentFreePosition.y += this._lineHeightSuper + yMargin;
+                this._currentFreePosition.y += this._lineHeightSuper + this._yMargin;
 
                 // No more room?
                 if (this._currentFreePosition.y > textureSize.height) {
@@ -193,12 +229,29 @@
                 }
             }
 
-            // Draw the character in the texture
-            this._context.fillText(char, this._currentFreePosition.x, this._currentFreePosition.y - this._offset);
+            // In sdf mode we render the character in an intermediate 2D context which scale the character this._sdfScale times (which is required to compute the sdf map accurately)
+            if (this._signedDistanceField) {
+                this._sdfContext.clearRect(0, 0, this._sdfCanvas.width, this._sdfCanvas.height);
+                this._sdfContext.fillText(char, 0, -this._offset);
+                let data = this._sdfContext.getImageData(0, 0, width*this._sdfScale, this._sdfCanvas.height);
+
+                let res = this._computeSDFChar(data);
+                this._context.putImageData(res, this._currentFreePosition.x, this._currentFreePosition.y);
+            } else {
+                // Draw the character in the HTML canvas
+                this._context.fillText(char, this._currentFreePosition.x, this._currentFreePosition.y - this._offset);
+            }
 
             // Fill the CharInfo object
             info.topLeftUV = new Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
             info.bottomRightUV = new Vector2((this._currentFreePosition.x + width) / textureSize.width, info.topLeftUV.y + ((this._lineHeightSuper + 2) / textureSize.height));
+
+            if (this._signedDistanceField) {
+                let off = 1/textureSize.width;
+                info.topLeftUV.addInPlace(new Vector2(off, off));
+                info.bottomRightUV.addInPlace(new Vector2(off, off));
+            }
+
             info.charWidth = this._superSample ? (width/2) : width;
 
             // Add the info structure
@@ -206,11 +259,158 @@
             this._curCharCount++;
 
             // Set the next position
-            this._currentFreePosition.x += width + xMargin;
+            this._currentFreePosition.x += width + this._xMargin;
 
             return info;
         }
 
+        private _computeSDFChar(source: ImageData): ImageData {
+            let scl = this._sdfScale;
+            let sw = source.width;
+            let sh = source.height;
+            let dw = sw / scl;
+            let dh = sh / scl;
+            let roffx = 0;
+            let roffy = 0;
+
+            // We shouldn't look beyond half of the biggest between width and height
+            let radius = scl;
+            let br = radius - 1;
+
+            let lookupSrc = (dx: number, dy: number, offX: number, offY: number, lookVis: boolean): boolean => {
+                let sx = dx * scl;
+                let sy = dy * scl;
+
+                // Looking out of the area? return true to make the test going on
+                if (((sx + offX) < 0) || ((sx + offX) >= sw) || ((sy + offY) < 0) || ((sy + offY) >= sh)) {
+                    return true;
+                }
+
+                // Get the pixel we want
+                let val = source.data[(((sy + offY) * sw) + (sx + offX)) * 4];
+
+                let res = (val > 0) === lookVis;
+                if (!res) {
+                    roffx = offX;
+                    roffy = offY;
+                }
+                return res;
+            }
+
+            let lookupArea = (dx: number, dy: number, lookVis: boolean): number => {
+
+                // Fast rejection test, if we have the same result in N, S, W, E at a distance which is the radius-1 then it means the data will be consistent in this area. That's because we've scale the rendering of the letter "radius" times, so a letter's pixel will be at least radius wide
+                if (lookupSrc(dx, dy, 0, br, lookVis) &&
+                    lookupSrc(dx, dy, 0, -br, lookVis) &&
+                    lookupSrc(dx, dy, -br, 0, lookVis) &&
+                    lookupSrc(dx, dy, br, 0, lookVis)) {
+                    return 0;
+                }
+
+                for (let i = 1; i <= radius; i++) {
+                    // Quick test N, S, W, E
+                    if (!lookupSrc(dx, dy, 0, i, lookVis) || !lookupSrc(dx, dy, 0, -i, lookVis) || !lookupSrc(dx, dy, -i, 0, lookVis) || !lookupSrc(dx, dy, i, 0, lookVis)) {
+                        return i * i;   // Squared Distance is simple to compute in this case
+                    }
+
+                    // Test the frame area (except the N, S, W, E spots) from the nearest point from the center to the further one
+                    for (let j = 1; j <= i; j++) {
+                        if (
+                            !lookupSrc(dx, dy, -j, i, lookVis) || !lookupSrc(dx, dy, j, i, lookVis) ||
+                            !lookupSrc(dx, dy, i, -j, lookVis) || !lookupSrc(dx, dy, i, j, lookVis) ||
+                            !lookupSrc(dx, dy, -j, -i, lookVis) || !lookupSrc(dx, dy, j, -i, lookVis) ||
+                            !lookupSrc(dx, dy, -i, -j, lookVis) || !lookupSrc(dx, dy, -i, j, lookVis)) {
+
+                            // We found the nearest texel having and opposite state, store the squared length
+                            let res = (i * i) + (j * j);
+                            let count = 1;
+
+                            // To improve quality we will  sample the texels around this one, so it's 8 samples, we consider only the one having an opposite state, add them to the current res and will will compute the average at the end
+                            if (!lookupSrc(dx, dy, roffx - 1, roffy, lookVis)) {
+                                res += (roffx - 1) * (roffx - 1) + roffy * roffy;
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx + 1, roffy, lookVis)) {
+                                res += (roffx + 1) * (roffx + 1) + roffy * roffy;
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx, roffy - 1, lookVis)) {
+                                res += roffx * roffx + (roffy - 1) * (roffy - 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx, roffy + 1, lookVis)) {
+                                res += roffx * roffx + (roffy + 1) * (roffy + 1);
+                                ++count;
+                            }
+
+                            if (!lookupSrc(dx, dy, roffx - 1, roffy - 1, lookVis)) {
+                                res += (roffx - 1) * (roffx - 1) + (roffy - 1) * (roffy - 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx + 1, roffy + 1, lookVis)) {
+                                res += (roffx + 1) * (roffx + 1) + (roffy + 1) * (roffy + 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx + 1, roffy - 1, lookVis)) {
+                                res += (roffx + 1) * (roffx + 1) + (roffy - 1) * (roffy - 1);
+                                ++count;
+                            }
+                            if (!lookupSrc(dx, dy, roffx - 1, roffy + 1, lookVis)) {
+                                res += (roffx - 1) * (roffx - 1) + (roffy + 1) * (roffy + 1);
+                                ++count;
+                            }
+
+                            // Compute the average based on the accumulated distance
+                            return res / count;
+                        }
+                    }
+                }
+
+                return 0;
+            }
+
+            let tmp = new Array<number>(dw * dh);
+            for (let y = 0; y < dh; y++) {
+                for (let x = 0; x < dw; x++) {
+
+                    let curState = lookupSrc(x, y, 0, 0, true);
+
+                    let d = lookupArea(x, y, curState);
+                    if (d === 0) {
+                        d = radius * radius * 2;
+                    }
+                    tmp[(y * dw) + x] = curState ? d : -d;
+                }
+            }
+
+            let res = this._context.createImageData(dw, dh);
+
+            let size = dw * dh;
+            for (let j = 0; j < size; j++) {
+                let d = tmp[j];
+
+                let sign = (d < 0) ? -1 : 1;
+
+                d = Math.sqrt(Math.abs(d)) * sign;
+
+                d *= 127.5 / radius;
+                d += 127.5;
+                if (d < 0) {
+                    d = 0;
+                } else if (d > 255) {
+                    d = 255;
+                }
+                d += 0.5;
+
+                res.data[j*4 + 0] = d;
+                res.data[j*4 + 1] = d;
+                res.data[j*4 + 2] = d;
+                res.data[j*4 + 3] = 255;
+            }
+
+            return res;
+        }
+
         public measureText(text: string, tabulationSize: number = 4): Size {
             let maxWidth: number = 0;
             let curWidth: number = 0;

+ 1 - 1
src/Materials/Textures/babylon.hdrCubeTexture.js

@@ -300,7 +300,7 @@ var BABYLON;
             var texture = null;
             if (parsedTexture.name && !parsedTexture.isRenderTarget) {
                 var size = parsedTexture.isBABYLONPreprocessed ? null : parsedTexture.size;
-                texture = new BABYLON.HDRCubeTexture(rootUrl + parsedTexture.name, scene, size, texture.generateHarmonics, texture.useInGammaSpace, texture.usePMREMGenerator);
+                texture = new BABYLON.HDRCubeTexture(rootUrl + parsedTexture.name, scene, size, parsedTexture.generateHarmonics, parsedTexture.useInGammaSpace, parsedTexture.usePMREMGenerator);
                 texture.name = parsedTexture.name;
                 texture.hasAlpha = parsedTexture.hasAlpha;
                 texture.level = parsedTexture.level;

+ 1 - 1
src/Materials/Textures/babylon.hdrCubeTexture.ts

@@ -385,7 +385,7 @@ module BABYLON {
             if (parsedTexture.name && !parsedTexture.isRenderTarget) {
                 var size = parsedTexture.isBABYLONPreprocessed ? null : parsedTexture.size;
                 texture = new BABYLON.HDRCubeTexture(rootUrl + parsedTexture.name, scene, size,
-                    texture.generateHarmonics, texture.useInGammaSpace, texture.usePMREMGenerator);
+                    parsedTexture.generateHarmonics, parsedTexture.useInGammaSpace, parsedTexture.usePMREMGenerator);
                 texture.name = parsedTexture.name;
                 texture.hasAlpha = parsedTexture.hasAlpha;
                 texture.level = parsedTexture.level;

+ 5 - 5
src/Materials/Textures/babylon.texture.js

@@ -213,6 +213,10 @@ var BABYLON;
             return new Texture("data:" + name, scene, noMipmap, invertY, samplingMode, onLoad, onError, data);
         };
         Texture.Parse = function (parsedTexture, scene, rootUrl) {
+            if (parsedTexture.customType) {
+                var customTexture = BABYLON.Tools.Instantiate(parsedTexture.customType);
+                return customTexture.Parse(parsedTexture, scene, rootUrl);
+            }
             if (parsedTexture.isCube) {
                 return BABYLON.CubeTexture.Parse(parsedTexture, scene, rootUrl);
             }
@@ -220,11 +224,7 @@ var BABYLON;
                 return null;
             }
             var texture = BABYLON.SerializationHelper.Parse(function () {
-                if (parsedTexture.customType) {
-                    var customTexture = BABYLON.Tools.Instantiate(parsedTexture.customType);
-                    return customTexture.Parse(parsedTexture, scene, rootUrl);
-                }
-                else if (parsedTexture.mirrorPlane) {
+                if (parsedTexture.mirrorPlane) {
                     var mirrorTexture = new BABYLON.MirrorTexture(parsedTexture.name, parsedTexture.renderTargetSize, scene);
                     mirrorTexture._waitingRenderList = parsedTexture.renderList;
                     mirrorTexture.mirrorPlane = BABYLON.Plane.FromArray(parsedTexture.mirrorPlane);

+ 6 - 4
src/Materials/Textures/babylon.texture.ts

@@ -268,6 +268,11 @@
         }
 
         public static Parse(parsedTexture: any, scene: Scene, rootUrl: string): BaseTexture {
+            if (parsedTexture.customType) { 
+                var customTexture = Tools.Instantiate(parsedTexture.customType);
+                return customTexture.Parse(parsedTexture, scene, rootUrl);
+            }
+
             if (parsedTexture.isCube) {
                 return CubeTexture.Parse(parsedTexture, scene, rootUrl);
             }
@@ -277,10 +282,7 @@
             }
 
             var texture = SerializationHelper.Parse(() => {
-                if (parsedTexture.customType) {
-                    var customTexture = Tools.Instantiate(parsedTexture.customType);
-                    return customTexture.Parse(parsedTexture, scene, rootUrl);
-                } else if (parsedTexture.mirrorPlane) {
+                if (parsedTexture.mirrorPlane) {
                     var mirrorTexture = new MirrorTexture(parsedTexture.name, parsedTexture.renderTargetSize, scene);
                     mirrorTexture._waitingRenderList = parsedTexture.renderList;
                     mirrorTexture.mirrorPlane = Plane.FromArray(parsedTexture.mirrorPlane);

+ 1 - 1
src/Materials/babylon.fresnelParameters.js

@@ -11,7 +11,7 @@ var BABYLON;
         FresnelParameters.prototype.clone = function () {
             var newFresnelParameters = new FresnelParameters();
             BABYLON.Tools.DeepCopy(this, newFresnelParameters);
-            return new FresnelParameters;
+            return newFresnelParameters;
         };
         FresnelParameters.prototype.serialize = function () {
             var serializationObject = {};

+ 1 - 1
src/Materials/babylon.fresnelParameters.ts

@@ -11,7 +11,7 @@
 
             Tools.DeepCopy(this, newFresnelParameters);
 
-            return new FresnelParameters;
+            return newFresnelParameters;
         }
 
         public serialize(): any {

+ 6 - 3
src/Math/babylon.math.js

@@ -856,9 +856,12 @@ var BABYLON;
             return result;
         };
         Vector3.TransformNormalToRef = function (vector, transformation, result) {
-            result.x = (vector.x * transformation.m[0]) + (vector.y * transformation.m[4]) + (vector.z * transformation.m[8]);
-            result.y = (vector.x * transformation.m[1]) + (vector.y * transformation.m[5]) + (vector.z * transformation.m[9]);
-            result.z = (vector.x * transformation.m[2]) + (vector.y * transformation.m[6]) + (vector.z * transformation.m[10]);
+            var x = (vector.x * transformation.m[0]) + (vector.y * transformation.m[4]) + (vector.z * transformation.m[8]);
+            var y = (vector.x * transformation.m[1]) + (vector.y * transformation.m[5]) + (vector.z * transformation.m[9]);
+            var z = (vector.x * transformation.m[2]) + (vector.y * transformation.m[6]) + (vector.z * transformation.m[10]);
+            result.x = x;
+            result.y = y;
+            result.z = z;
         };
         Vector3.TransformNormalFromFloatsToRef = function (x, y, z, transformation, result) {
             result.x = (x * transformation.m[0]) + (y * transformation.m[4]) + (z * transformation.m[8]);

+ 29 - 0
src/Math/babylon.math.ts

@@ -3116,6 +3116,35 @@
             result.m[14] = temp3 * plane.d;
             result.m[15] = 1.0;
         }
+
+        public static FromXYZAxesToRef(xaxis: Vector3, yaxis: Vector3, zaxis: Vector3, mat: Matrix) {
+            
+            mat.m[0] = xaxis.x;
+            mat.m[1] = xaxis.y;
+            mat.m[2] = xaxis.z;
+
+            mat.m[3] = 0;
+            
+            mat.m[4] = yaxis.x;
+            mat.m[5] = yaxis.y;
+            mat.m[6] = yaxis.z;
+            
+            mat.m[7] = 0;
+            
+            mat.m[8] = zaxis.x;
+            mat.m[9] = zaxis.y;
+            mat.m[10] = zaxis.z;
+            
+            mat.m[11] = 0;
+            
+            mat.m[12] = 0;
+            mat.m[13] = 0;
+            mat.m[14] = 0;
+            
+            mat.m[15] = 1;
+
+        }
+
     }
 
     export class Plane {

+ 6 - 3
src/Sprites/babylon.spriteManager.js

@@ -2,6 +2,7 @@ var BABYLON;
 (function (BABYLON) {
     var SpriteManager = (function () {
         function SpriteManager(name, imgUrl, capacity, cellSize, scene, epsilon, samplingMode) {
+            if (epsilon === void 0) { epsilon = 0.01; }
             if (samplingMode === void 0) { samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE; }
             this.name = name;
             this.sprites = new Array();
@@ -19,15 +20,17 @@ var BABYLON;
             this._spriteTexture = new BABYLON.Texture(imgUrl, scene, true, false, samplingMode);
             this._spriteTexture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
             this._spriteTexture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
-            this._epsilon = epsilon === undefined ? 0.01 : epsilon;
-            if (cellSize.width) {
+            if (cellSize.width && cellSize.height) {
                 this.cellWidth = cellSize.width;
                 this.cellHeight = cellSize.height;
             }
-            else {
+            else if (cellSize !== undefined) {
                 this.cellWidth = cellSize;
                 this.cellHeight = cellSize;
             }
+            else {
+                return;
+            }
             this._scene = scene;
             this._scene.spriteManagers.push(this);
             var indices = [];

+ 6 - 6
src/Sprites/babylon.spriteManager.ts

@@ -43,20 +43,20 @@
             this._spriteTexture = value;
         }
 
-        constructor(public name: string, imgUrl: string, capacity: number, cellSize: any, scene: Scene, epsilon?: number, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
+        constructor(public name: string, imgUrl: string, capacity: number, cellSize: any, scene: Scene, epsilon: number = 0.01, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
             this._capacity = capacity;
             this._spriteTexture = new Texture(imgUrl, scene, true, false, samplingMode);
             this._spriteTexture.wrapU = Texture.CLAMP_ADDRESSMODE;
             this._spriteTexture.wrapV = Texture.CLAMP_ADDRESSMODE;
 
-            this._epsilon = epsilon === undefined ? 0.01 : epsilon;
-
-            if (cellSize.width) {
+            if (cellSize.width && cellSize.height) {
                 this.cellWidth = cellSize.width;
                 this.cellHeight = cellSize.height;
-            } else {
+            } else if(cellSize !== undefined) {
                 this.cellWidth = cellSize;
                 this.cellHeight = cellSize;
+            } else {
+               return;   
             }
 
             this._scene = scene;
@@ -283,4 +283,4 @@
             this.onDisposeObservable.clear();
         }
     }
-} 
+} 

+ 12 - 2
src/babylon.scene.js

@@ -696,7 +696,12 @@ var BABYLON;
                     _this.setPointerOverSprite(null);
                     _this.setPointerOverMesh(pickResult.pickedMesh);
                     if (_this._pointerOverMesh.actionManager && _this._pointerOverMesh.actionManager.hasPointerTriggers) {
-                        canvas.style.cursor = _this.hoverCursor;
+                        if (_this._pointerOverMesh.actionManager.hoverCursor) {
+                            canvas.style.cursor = _this._pointerOverMesh.actionManager.hoverCursor;
+                        }
+                        else {
+                            canvas.style.cursor = _this.hoverCursor;
+                        }
                     }
                     else {
                         canvas.style.cursor = "";
@@ -707,8 +712,13 @@ var BABYLON;
                     // Sprites
                     pickResult = _this.pickSprite(_this._unTranslatedPointerX, _this._unTranslatedPointerY, spritePredicate, false, _this.cameraToUseForPointers);
                     if (pickResult.hit && pickResult.pickedSprite) {
-                        canvas.style.cursor = _this.hoverCursor;
                         _this.setPointerOverSprite(pickResult.pickedSprite);
+                        if (_this._pointerOverSprite.actionManager && _this._pointerOverSprite.actionManager.hoverCursor) {
+                            canvas.style.cursor = _this._pointerOverSprite.actionManager.hoverCursor;
+                        }
+                        else {
+                            canvas.style.cursor = _this.hoverCursor;
+                        }
                     }
                     else {
                         _this.setPointerOverSprite(null);

+ 11 - 3
src/babylon.scene.ts

@@ -819,7 +819,11 @@
                     this.setPointerOverMesh(pickResult.pickedMesh);
 
                     if (this._pointerOverMesh.actionManager && this._pointerOverMesh.actionManager.hasPointerTriggers) {
-                        canvas.style.cursor = this.hoverCursor;
+                        if(this._pointerOverMesh.actionManager.hoverCursor){
+                            canvas.style.cursor = this._pointerOverMesh.actionManager.hoverCursor;
+                        } else {
+                            canvas.style.cursor = this.hoverCursor;
+                        }
                     } else {
                         canvas.style.cursor = "";
                     }
@@ -829,8 +833,12 @@
                     pickResult = this.pickSprite(this._unTranslatedPointerX, this._unTranslatedPointerY, spritePredicate, false, this.cameraToUseForPointers);
 
                     if (pickResult.hit && pickResult.pickedSprite) {
-                        canvas.style.cursor = this.hoverCursor;
                         this.setPointerOverSprite(pickResult.pickedSprite);
+                        if (this._pointerOverSprite.actionManager && this._pointerOverSprite.actionManager.hoverCursor) {
+                            canvas.style.cursor = this._pointerOverSprite.actionManager.hoverCursor;
+                        } else {
+                            canvas.style.cursor = this.hoverCursor;
+                        }
                     } else {
                         this.setPointerOverSprite(null);
                         // Restore pointer
@@ -2156,7 +2164,7 @@
                             (highlightLayer.camera.cameraRigMode !== Camera.RIG_MODE_NONE && highlightLayer.camera._rigCameras.indexOf(camera) > -1))) {
 
                         renderhighlights = true;
-                        
+
                         var renderTarget = (<RenderTargetTexture>(<any>highlightLayer)._mainTexture);
                         if (renderTarget._shouldRender()) {
                             this._renderId++;