Selaa lähdekoodia

Canvas2D integration

David Catuhe 9 vuotta sitten
vanhempi
commit
eb7b0c7b23

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 26 - 25
dist/preview release/babylon.core.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 1743 - 787
dist/preview release/babylon.d.ts


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 36 - 33
dist/preview release/babylon.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 3979 - 22
dist/preview release/babylon.max.js


Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 36 - 33
dist/preview release/babylon.noworker.js


+ 137 - 102
src/Canvas2d/babylon.bounding2d.js

@@ -1,102 +1,137 @@
-var BABYLON;
-(function (BABYLON) {
-    /**
-     * Stores 2D Bounding Information.
-     * This class handles a circle area and a bounding rectangle one.
-     */
-    var BoundingInfo2D = (function () {
-        function BoundingInfo2D() {
-            this.extent = new BABYLON.Size(0, 0);
-        }
-        /**
-         * Duplicate this instance and return a new one
-         * @return the duplicated instance
-         */
-        BoundingInfo2D.prototype.clone = function () {
-            var r = new BoundingInfo2D();
-            r.radius = this.radius;
-            r.extent = this.extent.clone();
-            return r;
-        };
-        /**
-         * Apply a transformation matrix to this BoundingInfo2D and return a new instance containing the result
-         * @param matrix the transformation matrix to apply
-         * @return the new instance containing the result of the transformation applied on this BoundingInfo2D
-         */
-        BoundingInfo2D.prototype.transform = function (matrix) {
-            var r = new BoundingInfo2D();
-            this.transformToRef(matrix, r);
-            return r;
-        };
-        /**
-         * Compute the union of this BoundingInfo2D with a given one, return a new BoundingInfo2D as a result
-         * @param other the second BoundingInfo2D to compute the union with this one
-         * @return a new instance containing the result of the union
-         */
-        BoundingInfo2D.prototype.union = function (other) {
-            var r = new BoundingInfo2D();
-            this.unionToRef(other, r);
-            return r;
-        };
-        /**
-         * Transform this BoundingInfo2D with a given matrix and store the result in an existing BoundingInfo2D instance.
-         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
-         * @param matrix The matrix to use to compute the transformation
-         * @param result A VALID (i.e. allocated) BoundingInfo2D object where the result will be stored
-         */
-        BoundingInfo2D.prototype.transformToRef = function (matrix, result) {
-            // Extract scale from matrix
-            var xs = BABYLON.MathTools.Sign(matrix.m[0] * matrix.m[1] * matrix.m[2] * matrix.m[3]) < 0 ? -1 : 1;
-            var ys = BABYLON.MathTools.Sign(matrix.m[4] * matrix.m[5] * matrix.m[6] * matrix.m[7]) < 0 ? -1 : 1;
-            var scaleX = xs * Math.sqrt(matrix.m[0] * matrix.m[0] + matrix.m[1] * matrix.m[1] + matrix.m[2] * matrix.m[2]);
-            var scaleY = ys * Math.sqrt(matrix.m[4] * matrix.m[4] + matrix.m[5] * matrix.m[5] + matrix.m[6] * matrix.m[6]);
-            // Get translation
-            var trans = matrix.getTranslation();
-            var transLength = trans.length();
-            if (transLength < BABYLON.Epsilon) {
-                result.radius = this.radius * Math.max(scaleX, scaleY);
-            }
-            else {
-                // Compute the radius vector by applying the transformation matrix manually
-                var rx = (trans.x / transLength) * (transLength + this.radius) * scaleX;
-                var ry = (trans.y / transLength) * (transLength + this.radius) * scaleY;
-                // Store the vector length as the new radius
-                result.radius = Math.sqrt(rx * rx + ry * ry);
-            }
-            // Construct a bounding box based on the extent values
-            var p = new Array(4);
-            p[0] = new BABYLON.Vector2(this.extent.width, this.extent.height);
-            p[1] = new BABYLON.Vector2(this.extent.width, -this.extent.height);
-            p[2] = new BABYLON.Vector2(-this.extent.width, -this.extent.height);
-            p[3] = new BABYLON.Vector2(-this.extent.width, this.extent.height);
-            // Transform the four points of the bounding box with the matrix
-            for (var i = 0; i < 4; i++) {
-                p[i] = BABYLON.Vector2.Transform(p[i], matrix);
-            }
-            // Take the first point as reference
-            var maxW = Math.abs(p[0].x), maxH = Math.abs(p[0].y);
-            // Parse the three others, compare them to the reference and keep the biggest
-            for (var i = 1; i < 4; i++) {
-                maxW = Math.max(Math.abs(p[i].x), maxW);
-                maxH = Math.max(Math.abs(p[i].y), maxH);
-            }
-            // Store the new extent
-            result.extent.width = maxW * scaleX;
-            result.extent.height = maxH * scaleY;
-        };
-        /**
-         * Compute the union of this BoundingInfo2D with another one and store the result in a third valid BoundingInfo2D object
-         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
-         * @param other the second object used to compute the union
-         * @param result a VALID BoundingInfo2D instance (i.e. allocated) where the result will be stored
-         */
-        BoundingInfo2D.prototype.unionToRef = function (other, result) {
-            result.radius = Math.max(this.radius, other.radius);
-            result.extent.width = Math.max(this.extent.width, other.extent.width);
-            result.extent.height = Math.max(this.extent.height, other.extent.height);
-        };
-        return BoundingInfo2D;
-    }());
-    BABYLON.BoundingInfo2D = BoundingInfo2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.bounding2d.js.map
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Stores 2D Bounding Information.
+     * This class handles a circle area and a bounding rectangle one.
+     */
+    var BoundingInfo2D = (function () {
+        function BoundingInfo2D() {
+            this.radius = 0;
+            this.center = BABYLON.Vector2.Zero();
+            this.extent = BABYLON.Vector2.Zero();
+        }
+        BoundingInfo2D.CreateFromSize = function (size) {
+            var r = new BoundingInfo2D();
+            BoundingInfo2D.CreateFromSizeToRef(size, r);
+            return r;
+        };
+        BoundingInfo2D.CreateFromRadius = function (radius) {
+            var r = new BoundingInfo2D();
+            BoundingInfo2D.CreateFromRadiusToRef(radius, r);
+            return r;
+        };
+        BoundingInfo2D.CreateFromPoints = function (points) {
+            var r = new BoundingInfo2D();
+            BoundingInfo2D.CreateFromPointsToRef(points, r);
+            return r;
+        };
+        BoundingInfo2D.CreateFromSizeToRef = function (size, b) {
+            b.center = new BABYLON.Vector2(size.width / 2, size.height / 2);
+            b.extent = b.center.clone();
+            b.radius = b.extent.length();
+        };
+        BoundingInfo2D.CreateFromRadiusToRef = function (radius, b) {
+            b.center = BABYLON.Vector2.Zero();
+            b.extent = new BABYLON.Vector2(radius, radius);
+            b.radius = radius;
+        };
+        BoundingInfo2D.CreateFromPointsToRef = function (points, b) {
+            var xmin = Number.MAX_VALUE, ymin = Number.MAX_VALUE, xmax = Number.MIN_VALUE, ymax = Number.MIN_VALUE;
+            for (var _i = 0; _i < points.length; _i++) {
+                var p = points[_i];
+                xmin = Math.min(p.x, xmin);
+                xmax = Math.max(p.x, xmax);
+                ymin = Math.min(p.y, ymin);
+                ymax = Math.max(p.y, ymax);
+            }
+            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, b);
+        };
+        BoundingInfo2D.CreateFromMinMaxToRef = function (xmin, xmax, ymin, ymax, b) {
+            b.center = new BABYLON.Vector2(xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2);
+            b.extent = new BABYLON.Vector2(xmax - b.center.x, ymax - b.center.y);
+            b.radius = b.extent.length();
+        };
+        /**
+         * Duplicate this instance and return a new one
+         * @return the duplicated instance
+         */
+        BoundingInfo2D.prototype.clone = function () {
+            var r = new BoundingInfo2D();
+            r.center = this.center.clone();
+            r.radius = this.radius;
+            r.extent = this.extent.clone();
+            return r;
+        };
+        BoundingInfo2D.prototype.max = function () {
+            var r = BABYLON.Vector2.Zero();
+            this.maxToRef(r);
+            return r;
+        };
+        BoundingInfo2D.prototype.maxToRef = function (result) {
+            result.x = this.center.x + this.extent.x;
+            result.y = this.center.y + this.extent.y;
+        };
+        /**
+         * Apply a transformation matrix to this BoundingInfo2D and return a new instance containing the result
+         * @param matrix the transformation matrix to apply
+         * @return the new instance containing the result of the transformation applied on this BoundingInfo2D
+         */
+        BoundingInfo2D.prototype.transform = function (matrix, origin) {
+            if (origin === void 0) { origin = null; }
+            var r = new BoundingInfo2D();
+            this.transformToRef(matrix, origin, r);
+            return r;
+        };
+        /**
+         * Compute the union of this BoundingInfo2D with a given one, return a new BoundingInfo2D as a result
+         * @param other the second BoundingInfo2D to compute the union with this one
+         * @return a new instance containing the result of the union
+         */
+        BoundingInfo2D.prototype.union = function (other) {
+            var r = new BoundingInfo2D();
+            this.unionToRef(other, r);
+            return r;
+        };
+        /**
+         * Transform this BoundingInfo2D with a given matrix and store the result in an existing BoundingInfo2D instance.
+         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
+         * @param origin An optional normalized origin to apply before the transformation. 0;0 is top/left, 0.5;0.5 is center, etc.
+         * @param matrix The matrix to use to compute the transformation
+         * @param result A VALID (i.e. allocated) BoundingInfo2D object where the result will be stored
+         */
+        BoundingInfo2D.prototype.transformToRef = function (matrix, origin, result) {
+            // Construct a bounding box based on the extent values
+            var p = new Array(4);
+            p[0] = new BABYLON.Vector2(this.center.x + this.extent.x, this.center.y + this.extent.y);
+            p[1] = new BABYLON.Vector2(this.center.x + this.extent.x, this.center.y - this.extent.y);
+            p[2] = new BABYLON.Vector2(this.center.x - this.extent.x, this.center.y - this.extent.y);
+            p[3] = new BABYLON.Vector2(this.center.x - this.extent.x, this.center.y + this.extent.y);
+            //if (origin) {
+            //    let off = new Vector2((p[0].x - p[2].x) * origin.x, (p[0].y - p[2].y) * origin.y);
+            //    for (let j = 0; j < 4; j++) {
+            //        p[j].subtractInPlace(off);
+            //    }
+            //}
+            // Transform the four points of the bounding box with the matrix
+            for (var i = 0; i < 4; i++) {
+                BABYLON.Vector2.TransformToRef(p[i], matrix, p[i]);
+            }
+            BoundingInfo2D.CreateFromPointsToRef(p, result);
+        };
+        /**
+         * Compute the union of this BoundingInfo2D with another one and store the result in a third valid BoundingInfo2D object
+         * This is a GC friendly version, try to use it as much as possible, specially if your transformation is inside a loop, allocate the result object once for good outside of the loop and use it everytime.
+         * @param other the second object used to compute the union
+         * @param result a VALID BoundingInfo2D instance (i.e. allocated) where the result will be stored
+         */
+        BoundingInfo2D.prototype.unionToRef = function (other, result) {
+            var xmax = Math.max(this.center.x + this.extent.x, other.center.x + other.extent.x);
+            var ymax = Math.max(this.center.y + this.extent.y, other.center.y + other.extent.y);
+            var xmin = Math.min(this.center.x - this.extent.x, other.center.x - other.extent.x);
+            var ymin = Math.min(this.center.y - this.extent.y, other.center.y - other.extent.y);
+            BoundingInfo2D.CreateFromMinMaxToRef(xmin, xmax, ymin, ymax, result);
+        };
+        return BoundingInfo2D;
+    })();
+    BABYLON.BoundingInfo2D = BoundingInfo2D;
+})(BABYLON || (BABYLON = {}));

+ 184 - 113
src/Canvas2d/babylon.brushes2d.js

@@ -1,113 +1,184 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var BABYLON;
-(function (BABYLON) {
-    /**
-     * Base class implemting the ILocable interface.
-     * The particularity of this class is to call the protected onLock() method when the instance is about to be locked for good.
-     */
-    var LockableBase = (function () {
-        function LockableBase() {
-        }
-        LockableBase.prototype.isLocked = function () {
-            return this._isLocked;
-        };
-        LockableBase.prototype.lock = function () {
-            if (this._isLocked) {
-                return true;
-            }
-            this.onLock();
-            this._isLocked = true;
-            return false;
-        };
-        /**
-         * Protected handler that will be called when the instance is about to be locked.
-         */
-        LockableBase.prototype.onLock = function () {
-        };
-        return LockableBase;
-    }());
-    BABYLON.LockableBase = LockableBase;
-    /**
-     * This classs implements a Border that will be drawn with a uniform solid color (i.e. the same color everywhere in the border).
-     */
-    var SolidColorBorder2D = (function (_super) {
-        __extends(SolidColorBorder2D, _super);
-        function SolidColorBorder2D(color, lock) {
-            if (lock === void 0) { lock = false; }
-            _super.call(this);
-            this._color = color;
-            if (lock) {
-                this.lock();
-            }
-        }
-        Object.defineProperty(SolidColorBorder2D.prototype, "color", {
-            /**
-             * The color used by this instance to render
-             * @returns the color object. Note that it's not a clone of the actual object stored in the instance so you MUST NOT modify it, otherwise unexpected behavior might occurs.
-             */
-            get: function () {
-                return this._color;
-            },
-            set: function (value) {
-                if (this.isLocked()) {
-                    return;
-                }
-                this._color = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        /**
-         * Return a unique identifier of the instance, which is simply the hexadecimal representation (CSS Style) of the solid color.
-         */
-        SolidColorBorder2D.prototype.toString = function () {
-            return this._color.toHexString();
-        };
-        return SolidColorBorder2D;
-    }(LockableBase));
-    BABYLON.SolidColorBorder2D = SolidColorBorder2D;
-    /**
-     * This class implements a Fill that will be drawn with a uniform solid color (i.e. the same everywhere inside the primitive).
-     */
-    var SolidColorFill2D = (function (_super) {
-        __extends(SolidColorFill2D, _super);
-        function SolidColorFill2D(color, lock) {
-            if (lock === void 0) { lock = false; }
-            _super.call(this);
-            this._color = color;
-            if (lock) {
-                this.lock();
-            }
-        }
-        Object.defineProperty(SolidColorFill2D.prototype, "color", {
-            /**
-             * The color used by this instance to render
-             * @returns the color object. Note that it's not a clone of the actual object stored in the instance so you MUST NOT modify it, otherwise unexpected behavior might occurs.
-             */
-            get: function () {
-                return this._color;
-            },
-            set: function (value) {
-                if (this.isLocked()) {
-                    return;
-                }
-                this._color = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        /**
-         * Return a unique identifier of the instance, which is simply the hexadecimal representation (CSS Style) of the solid color.
-         */
-        SolidColorFill2D.prototype.toString = function () {
-            return this._color.toHexString();
-        };
-        return SolidColorFill2D;
-    }(LockableBase));
-    BABYLON.SolidColorFill2D = SolidColorFill2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.brushes2d.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Base class implemting the ILocable interface.
+     * The particularity of this class is to call the protected onLock() method when the instance is about to be locked for good.
+     */
+    var LockableBase = (function () {
+        function LockableBase() {
+        }
+        LockableBase.prototype.isLocked = function () {
+            return this._isLocked;
+        };
+        LockableBase.prototype.lock = function () {
+            if (this._isLocked) {
+                return true;
+            }
+            this.onLock();
+            this._isLocked = true;
+            return false;
+        };
+        /**
+         * Protected handler that will be called when the instance is about to be locked.
+         */
+        LockableBase.prototype.onLock = function () {
+        };
+        return LockableBase;
+    })();
+    BABYLON.LockableBase = LockableBase;
+    /**
+     * This classs implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
+     */
+    var SolidColorBrush2D = (function (_super) {
+        __extends(SolidColorBrush2D, _super);
+        function SolidColorBrush2D(color, lock) {
+            if (lock === void 0) { lock = false; }
+            _super.call(this);
+            this._color = color;
+            if (lock) {
+                {
+                    this.lock();
+                }
+            }
+        }
+        SolidColorBrush2D.prototype.isTransparent = function () {
+            return this._color && this._color.a < 1.0;
+        };
+        Object.defineProperty(SolidColorBrush2D.prototype, "color", {
+            /**
+             * The color used by this instance to render
+             * @returns the color object. Note that it's not a clone of the actual object stored in the instance so you MUST NOT modify it, otherwise unexpected behavior might occurs.
+             */
+            get: function () {
+                return this._color;
+            },
+            set: function (value) {
+                if (this.isLocked()) {
+                    return;
+                }
+                this._color = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Return a unique identifier of the instance, which is simply the hexadecimal representation (CSS Style) of the solid color.
+         */
+        SolidColorBrush2D.prototype.toString = function () {
+            return this._color.toHexString();
+        };
+        SolidColorBrush2D = __decorate([
+            BABYLON.className("SolidColorBrush2D")
+        ], SolidColorBrush2D);
+        return SolidColorBrush2D;
+    })(LockableBase);
+    BABYLON.SolidColorBrush2D = SolidColorBrush2D;
+    var GradientColorBrush2D = (function (_super) {
+        __extends(GradientColorBrush2D, _super);
+        function GradientColorBrush2D(color1, color2, translation, rotation, scale, lock) {
+            if (translation === void 0) { translation = BABYLON.Vector2.Zero(); }
+            if (rotation === void 0) { rotation = 0; }
+            if (scale === void 0) { scale = 1; }
+            if (lock === void 0) { lock = false; }
+            _super.call(this);
+            this._color1 = color1;
+            this._color2 = color2;
+            this._translation = translation;
+            this._rotation = rotation;
+            this._scale = scale;
+            if (lock) {
+                this.lock();
+            }
+        }
+        GradientColorBrush2D.prototype.isTransparent = function () {
+            return (this._color1 && this._color1.a < 1.0) || (this._color2 && this._color2.a < 1.0);
+        };
+        Object.defineProperty(GradientColorBrush2D.prototype, "color1", {
+            get: function () {
+                return this._color1;
+            },
+            set: function (value) {
+                if (this.isLocked()) {
+                    return;
+                }
+                this._color1 = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GradientColorBrush2D.prototype, "color2", {
+            get: function () {
+                return this._color2;
+            },
+            set: function (value) {
+                if (this.isLocked()) {
+                    return;
+                }
+                this._color2 = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GradientColorBrush2D.prototype, "translation", {
+            get: function () {
+                return this._translation;
+            },
+            set: function (value) {
+                if (this.isLocked()) {
+                    return;
+                }
+                this._translation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GradientColorBrush2D.prototype, "rotation", {
+            get: function () {
+                return this._rotation;
+            },
+            set: function (value) {
+                if (this.isLocked()) {
+                    return;
+                }
+                this._rotation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GradientColorBrush2D.prototype, "scale", {
+            get: function () {
+                return this._scale;
+            },
+            set: function (value) {
+                if (this.isLocked()) {
+                    return;
+                }
+                this._scale = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        GradientColorBrush2D.prototype.toString = function () {
+            return "C1:" + this._color1 + ";C2:" + this._color2 + ";T:" + this._translation.toString() + ";R:" + this._rotation + ";S:" + this._scale + ";";
+        };
+        GradientColorBrush2D.BuildKey = function (color1, color2, translation, rotation, scale) {
+            return "C1:" + color1 + ";C2:" + color2 + ";T:" + translation.toString() + ";R:" + rotation + ";S:" + scale + ";";
+        };
+        GradientColorBrush2D = __decorate([
+            BABYLON.className("GradientColorBrush2D")
+        ], GradientColorBrush2D);
+        return GradientColorBrush2D;
+    })(LockableBase);
+    BABYLON.GradientColorBrush2D = GradientColorBrush2D;
+})(BABYLON || (BABYLON = {}));

+ 358 - 305
src/Canvas2d/babylon.canvas2d.js

@@ -1,305 +1,358 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var GroupsCacheMap = (function () {
-        function GroupsCacheMap() {
-            this.groupSprites = new Array();
-        }
-        return GroupsCacheMap;
-    }());
-    var Canvas2D = (function (_super) {
-        __extends(Canvas2D, _super);
-        function Canvas2D() {
-            _super.apply(this, arguments);
-            this._mapCounter = 0;
-        }
-        /**
-         * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the top/left corner of the screen.
-         * ScreenSpace Canvas will be drawn in the Viewport as a 2D Layer lying to the top of the 3D Scene. Typically used for traditional UI.
-         * All caching strategies will be available.
-         * @param engine
-         * @param name
-         * @param pos
-         * @param size
-         * @param cachingStrategy
-         */
-        Canvas2D.CreateScreenSpace = function (scene, name, pos, size, cachingStrategy) {
-            if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
-            var c = new Canvas2D();
-            c.setupCanvas(scene, name, size, true, cachingStrategy);
-            c.position = pos;
-            return c;
-        };
-        /**
-         * Create a new 2D WorldSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a world transformation matrix to place it in the world space.
-         * This kind of canvas can't have its Primitives directly drawn in the Viewport, they need to be cached in a bitmap at some point, as a consequence the DONT_CACHE strategy is unavailable. All remaining strategies are supported.
-         * @param engine
-         * @param name
-         * @param transform
-         * @param size
-         * @param cachingStrategy
-         */
-        Canvas2D.CreateWorldSpace = function (scene, name, transform, size, cachingStrategy) {
-            if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
-            if (cachingStrategy === Canvas2D.CACHESTRATEGY_DONTCACHE) {
-                throw new Error("CACHESTRATEGY_DONTCACHE cache Strategy can't be used for WorldSpace Canvas");
-            }
-            var c = new Canvas2D();
-            c.setupCanvas(scene, name, size, false, cachingStrategy);
-            c._worldTransform = transform;
-            return c;
-        };
-        Canvas2D.prototype.setupCanvas = function (scene, name, size, isScreenSpace, cachingstrategy) {
-            if (isScreenSpace === void 0) { isScreenSpace = true; }
-            if (cachingstrategy === void 0) { cachingstrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
-            this._cachingStrategy = cachingstrategy;
-            this._hierarchyLevelZFactor = 100;
-            this._hierarchyLevelMaxSiblingCount = 1000;
-            this._hierarchySiblingZDelta = this._hierarchyLevelZFactor / this._hierarchyLevelMaxSiblingCount;
-            this.setupGroup2D(this, null, name, BABYLON.Vector2.Zero(), size);
-            this._scene = scene;
-            this._engine = scene.getEngine();
-            this._renderingSize = new BABYLON.Size(0, 0);
-            if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
-                this._background = BABYLON.Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
-                this._background.levelVisible = false;
-            }
-            this._isScreeSpace = isScreenSpace;
-        };
-        Object.defineProperty(Canvas2D.prototype, "scene", {
-            /**
-             * Accessor to the Scene that owns the Canvas
-             * @returns The instance of the Scene object
-             */
-            get: function () {
-                return this._scene;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Canvas2D.prototype, "engine", {
-            /**
-             * Accessor to the Engine that drives the Scene used by this Canvas
-             * @returns The instance of the Engine object
-             */
-            get: function () {
-                return this._engine;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Canvas2D.prototype, "cachingStrategy", {
-            /**
-             * Accessor of the Caching Strategy used by this Canvas.
-             * See Canvas2D.CACHESTRATEGY_xxxx static members for more information
-             * @returns the value corresponding to the used strategy.
-             */
-            get: function () {
-                return this._cachingStrategy;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Canvas2D.prototype, "backgroundFill", {
-            /**
-             * Property that defines the fill object used to draw the background of the Canvas.
-             * Note that Canvas with a Caching Strategy of
-             * @returns If the background is not set, null will be returned, otherwise a valid fill object is returned.
-             */
-            get: function () {
-                if (!this._background || !this._background.isVisible) {
-                    return null;
-                }
-                return this._background.fill;
-            },
-            set: function (value) {
-                this.checkBackgroundAvailability();
-                if (value === this._background.fill) {
-                    return;
-                }
-                this._background.fill = value;
-                this._background.isVisible = true;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Canvas2D.prototype, "border", {
-            /**
-             * Property that defines the border object used to draw the background of the Canvas.
-             * @returns If the background is not set, null will be returned, otherwise a valid border object is returned.
-             */
-            get: function () {
-                if (!this._background || !this._background.isVisible) {
-                    return null;
-                }
-                return this._background.border;
-            },
-            set: function (value) {
-                this.checkBackgroundAvailability();
-                if (value === this._background.border) {
-                    return;
-                }
-                this._background.border = value;
-                this._background.isVisible = true;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Canvas2D.prototype.checkBackgroundAvailability = function () {
-            if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
-                throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
-            }
-        };
-        Object.defineProperty(Canvas2D.prototype, "hierarchySiblingZDelta", {
-            /**
-             * Read-only property that return the Z delta to apply for each sibling primitives inside of a given one.
-             * Sibling Primitives are defined in a specific order, the first ones will be draw below the next ones.
-             * This property define the Z value to apply between each sibling Primitive. Current implementation allows 1000 Siblings Primitives per level.
-             * @returns The Z Delta
-             */
-            get: function () {
-                return this._hierarchySiblingZDelta;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        /**
-         * Method that renders the Canvas
-         * @param camera the current camera.
-         */
-        Canvas2D.prototype.render = function (camera) {
-            this._renderingSize.width = this.engine.getRenderWidth();
-            this._renderingSize.height = this.engine.getRenderHeight();
-            var context = new BABYLON.Render2DContext();
-            context.camera = camera;
-            context.parentVisibleState = this.levelVisible;
-            context.parentTransform = BABYLON.Matrix.Identity();
-            context.parentTransformStep = 1;
-            context.forceRefreshPrimitive = false;
-            this.updateGlobalTransVis(context, false);
-            this._prepareGroupRender(context);
-            this._groupRender(context);
-        };
-        /**
-         * Internal method that alloc a cache for the given group.
-         * Caching is made using a collection of MapTexture where many groups have their bitmapt cache stored inside.
-         * @param group The group to allocate the cache of.
-         * @return custom type with the PackedRect instance giving information about the cache location into the texture and also the MapTexture instance that stores the cache.
-         */
-        Canvas2D.prototype._allocateGroupCache = function (group) {
-            // Determine size
-            var size = group.actualSize;
-            size = new BABYLON.Size(Math.ceil(size.width), Math.ceil(size.height));
-            if (!this._groupCacheMaps) {
-                this._groupCacheMaps = new Array();
-            }
-            // Try to find a spot in one of the cached texture
-            var res = null;
-            for (var _i = 0, _a = this._groupCacheMaps; _i < _a.length; _i++) {
-                var g = _a[_i];
-                var node = g.texture.allocateRect(size);
-                if (node) {
-                    res = { node: node, texture: g.texture };
-                    break;
-                }
-            }
-            // Couldn't find a map that could fit the rect, create a new map for it
-            if (!res) {
-                var mapSize = new BABYLON.Size(Canvas2D._groupTextureCacheSize, Canvas2D._groupTextureCacheSize);
-                // Check if the predefined size would fit, other create a custom size using the nearest bigger power of 2
-                if (size.width > mapSize.width || size.height > mapSize.height) {
-                    mapSize.width = Math.pow(2, Math.ceil(Math.log(size.width) / Math.log(2)));
-                    mapSize.height = Math.pow(2, Math.ceil(Math.log(size.height) / Math.log(2)));
-                }
-                g = new GroupsCacheMap();
-                var id = "groupsMapChache" + this._mapCounter + "forCanvas" + this.id;
-                g.texture = new BABYLON.MapTexture(id, this._scene, mapSize);
-                this._groupCacheMaps.push(g);
-                var node = g.texture.allocateRect(size);
-                res = { node: node, texture: g.texture };
-            }
-            // 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
-            var sprite = BABYLON.Sprite2D.Create(this, "__cachedSpriteOfGroup__" + group.id, 10, 10, g.texture, res.node.contentSize, res.node.pos, true);
-            sprite.origin = BABYLON.Vector2.Zero();
-            g.groupSprites.push({ group: group, sprite: sprite });
-            return res;
-        };
-        /**
-         * Get a Solid Color Fill instance matching the given color.
-         * @param color The color to retrieve
-         * @return A shared instance of the SolidColorFill2D class that use the given color
-         */
-        Canvas2D.GetSolidColorFill = function (color) {
-            return Canvas2D._solidColorFills.getOrAddWithFactory(color.toHexString(), function () { return new BABYLON.SolidColorFill2D(color.clone(), true); });
-        };
-        /**
-         * Get a Solid Color Border instance matching the given color.
-         * @param color The color to retrieve
-         * @return A shared instance of the SolidColorBorder2D class that use the given color
-         */
-        Canvas2D.GetSolidColorBorder = function (color) {
-            return Canvas2D._solidColorBorders.getOrAddWithFactory(color.toHexString(), function () { return new BABYLON.SolidColorBorder2D(color.clone(), true); });
-        };
-        /**
-         * Get a Solid Color Fill instance matching the given color expressed as a CSS formatted hexadecimal value.
-         * @param color The color to retrieve
-         * @return A shared instance of the SolidColorFill2D class that use the given color
-         */
-        Canvas2D.GetSolidColorFillFromHex = function (hexValue) {
-            return Canvas2D._solidColorFills.getOrAddWithFactory(hexValue, function () { return new BABYLON.SolidColorFill2D(BABYLON.Color4.FromHexString(hexValue), true); });
-        };
-        /**
-         * Get a Solid Color Border instance matching the given color expressed as a CSS formatted hexadecimal value.
-         * @param color The color to retrieve
-         * @return A shared instance of the SolidColorBorder2D class that use the given color
-         */
-        Canvas2D.GetSolidColorBorderFromHex = function (hexValue) {
-            return Canvas2D._solidColorBorders.getOrAddWithFactory(hexValue, function () { return new BABYLON.SolidColorBorder2D(BABYLON.Color4.FromHexString(hexValue), true); });
-        };
-        /**
-         * In this strategy only the direct children groups of the Canvas will be cached, their whole content (whatever the sub groups they have) into a single bitmap.
-         * This strategy doesn't allow primitives added directly as children of the Canvas.
-         * You typically want to use this strategy of a screenSpace fullscreen canvas: you don't want a bitmap cache taking the whole screen resolution but still want the main contents (say UI in the topLeft and rightBottom for instance) to be efficiently cached.
-         */
-        Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS = 1;
-        /**
-         * In this strategy each group will have its own cache bitmap (except if a given group explicitly defines the DONTCACHEOVERRIDE or CACHEINPARENTGROUP behaviors).
-         * This strategy is typically used if the canvas has some groups that are frequently animated. Unchanged ones will have a steady cache and the others will be refreshed when they change, reducing the redraw operation count to their content only.
-         * When using this strategy, group instances can rely on the DONTCACHEOVERRIDE or CACHEINPARENTGROUP behaviors to minize the amount of cached bitmaps.
-         */
-        Canvas2D.CACHESTRATEGY_ALLGROUPS = 2;
-        /**
-         * In this strategy the whole canvas is cached into a single bitmap containing every primitives it owns, at the exception of the ones that are owned by a group having the DONTCACHEOVERRIDE behavior (these primitives will be directly drawn to the viewport at each render for screenSpace Canvas or be part of the Canvas cache bitmap for worldSpace Canvas).
-         */
-        Canvas2D.CACHESTRATEGY_CANVAS = 3;
-        /**
-         * This strategy is used to recompose/redraw the canvas entierely at each viewport render.
-         * Use this strategy if memory is a concern above rendering performances and/or if the canvas is frequently animated (hence reducing the benefits of caching).
-         * Note that you can't use this strategy for WorldSpace Canvas, they need at least a top level group caching.
-         */
-        Canvas2D.CACHESTRATEGY_DONTCACHE = 4;
-        /**
-         * Define the default size used for both the width and height of a MapTexture to allocate.
-         * Note that some MapTexture might be bigger than this size if the first node to allocate is bigger in width or height
-         */
-        Canvas2D._groupTextureCacheSize = 1024;
-        Canvas2D._solidColorFills = new BABYLON.StringDictionary();
-        Canvas2D._solidColorBorders = new BABYLON.StringDictionary();
-        Canvas2D = __decorate([
-            BABYLON.className("Canvas2D")
-        ], Canvas2D);
-        return Canvas2D;
-    }(BABYLON.Group2D));
-    BABYLON.Canvas2D = Canvas2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.canvas2d.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Canvas2D = (function (_super) {
+        __extends(Canvas2D, _super);
+        function Canvas2D() {
+            _super.apply(this, arguments);
+            this._mapCounter = 0;
+        }
+        /**
+         * Create a new 2D ScreenSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a position relative to the top/left corner of the screen.
+         * ScreenSpace Canvas will be drawn in the Viewport as a 2D Layer lying to the top of the 3D Scene. Typically used for traditional UI.
+         * All caching strategies will be available.
+         * @param engine
+         * @param name
+         * @param pos
+         * @param size
+         * @param cachingStrategy
+         */
+        Canvas2D.CreateScreenSpace = function (scene, name, pos, size, cachingStrategy) {
+            if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
+            var c = new Canvas2D();
+            c.setupCanvas(scene, name, size, true, cachingStrategy);
+            c.position = pos;
+            return c;
+        };
+        /**
+         * Create a new 2D WorldSpace Rendering Canvas, it is a 2D rectangle that has a size (width/height) and a world transformation matrix to place it in the world space.
+         * This kind of canvas can't have its Primitives directly drawn in the Viewport, they need to be cached in a bitmap at some point, as a consequence the DONT_CACHE strategy is unavailable. All remaining strategies are supported.
+         */
+        Canvas2D.CreateWorldSpace = function (scene, name, position, rotation, size, renderScaleFactor, sideOrientation, cachingStrategy) {
+            if (renderScaleFactor === void 0) { renderScaleFactor = 1; }
+            if (cachingStrategy === void 0) { cachingStrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
+            if (cachingStrategy !== Canvas2D.CACHESTRATEGY_CANVAS) {
+                throw new Error("Right now only the CACHESTRATEGY_CANVAS cache Strategy is supported for WorldSpace Canvas. More will come soon!");
+            }
+            //if (cachingStrategy === Canvas2D.CACHESTRATEGY_DONTCACHE) {
+            //    throw new Error("CACHESTRATEGY_DONTCACHE cache Strategy can't be used for WorldSpace Canvas");
+            //}
+            var c = new Canvas2D();
+            c.setupCanvas(scene, name, new BABYLON.Size(size.width * renderScaleFactor, size.height * renderScaleFactor), false, cachingStrategy);
+            var plane = new BABYLON.WorldSpaceCanvas2d(name, scene, c);
+            var vertexData = BABYLON.VertexData.CreatePlane({ width: size.width / 2, height: size.height / 2, sideOrientation: sideOrientation });
+            var mtl = new BABYLON.StandardMaterial(name + "_Material", scene);
+            c.applyCachedTexture(vertexData, mtl);
+            vertexData.applyToMesh(plane, false);
+            mtl.specularColor = new BABYLON.Color3(0, 0, 0);
+            mtl.disableLighting = true;
+            mtl.useAlphaFromDiffuseTexture = true;
+            plane.position = position;
+            plane.rotationQuaternion = rotation;
+            plane.material = mtl;
+            return c;
+        };
+        Canvas2D.prototype.setupCanvas = function (scene, name, size, isScreenSpace, cachingstrategy) {
+            var _this = this;
+            if (isScreenSpace === void 0) { isScreenSpace = true; }
+            if (cachingstrategy === void 0) { cachingstrategy = Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS; }
+            this._cachingStrategy = cachingstrategy;
+            this._depthLevel = 0;
+            this._hierarchyMaxDepth = 100;
+            this._hierarchyLevelZFactor = 1 / this._hierarchyMaxDepth;
+            this._hierarchyLevelMaxSiblingCount = 1000;
+            this._hierarchySiblingZDelta = this._hierarchyLevelZFactor / this._hierarchyLevelMaxSiblingCount;
+            this.setupGroup2D(this, null, name, BABYLON.Vector2.Zero(), size);
+            this._scene = scene;
+            this._engine = scene.getEngine();
+            this._renderingSize = new BABYLON.Size(0, 0);
+            if (cachingstrategy !== Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
+                this._background = BABYLON.Rectangle2D.Create(this, "###CANVAS BACKGROUND###", 0, 0, size.width, size.height);
+                this._background.origin = BABYLON.Vector2.Zero();
+            }
+            this._isScreeSpace = isScreenSpace;
+            if (this._isScreeSpace) {
+                this._afterRenderObserver = this._scene.onAfterRenderObservable.add(function (d, s) {
+                    _this._engine.clear(null, false, true);
+                    _this.render();
+                });
+            }
+            else {
+                this._beforeRenderObserver = this._scene.onBeforeRenderObservable.add(function (d, s) {
+                    _this.render();
+                });
+            }
+            this._supprtInstancedArray = this._engine.getCaps().instancedArrays !== null;
+            //            this._supprtInstancedArray = false; // TODO REMOVE!!!
+        };
+        Canvas2D.prototype.dispose = function () {
+            if (!_super.prototype.dispose.call(this)) {
+                return false;
+            }
+            if (this._beforeRenderObserver) {
+                this._scene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
+                this._beforeRenderObserver = null;
+            }
+            if (this._afterRenderObserver) {
+                this._scene.onAfterRenderObservable.remove(this._afterRenderObserver);
+                this._afterRenderObserver = null;
+            }
+        };
+        Object.defineProperty(Canvas2D.prototype, "scene", {
+            /**
+             * Accessor to the Scene that owns the Canvas
+             * @returns The instance of the Scene object
+             */
+            get: function () {
+                return this._scene;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "engine", {
+            /**
+             * Accessor to the Engine that drives the Scene used by this Canvas
+             * @returns The instance of the Engine object
+             */
+            get: function () {
+                return this._engine;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "cachingStrategy", {
+            /**
+             * Accessor of the Caching Strategy used by this Canvas.
+             * See Canvas2D.CACHESTRATEGY_xxxx static members for more information
+             * @returns the value corresponding to the used strategy.
+             */
+            get: function () {
+                return this._cachingStrategy;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "supportInstancedArray", {
+            get: function () {
+                return this._supprtInstancedArray;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "backgroundFill", {
+            /**
+             * Property that defines the fill object used to draw the background of the Canvas.
+             * Note that Canvas with a Caching Strategy of
+             * @returns If the background is not set, null will be returned, otherwise a valid fill object is returned.
+             */
+            get: function () {
+                if (!this._background || !this._background.isVisible) {
+                    return null;
+                }
+                return this._background.fill;
+            },
+            set: function (value) {
+                this.checkBackgroundAvailability();
+                if (value === this._background.fill) {
+                    return;
+                }
+                this._background.fill = value;
+                this._background.isVisible = true;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "backgroundBorder", {
+            /**
+             * Property that defines the border object used to draw the background of the Canvas.
+             * @returns If the background is not set, null will be returned, otherwise a valid border object is returned.
+             */
+            get: function () {
+                if (!this._background || !this._background.isVisible) {
+                    return null;
+                }
+                return this._background.border;
+            },
+            set: function (value) {
+                this.checkBackgroundAvailability();
+                if (value === this._background.border) {
+                    return;
+                }
+                this._background.border = value;
+                this._background.isVisible = true;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "backgroundRoundRadius", {
+            get: function () {
+                if (!this._background || !this._background.isVisible) {
+                    return null;
+                }
+                return this._background.roundRadius;
+            },
+            set: function (value) {
+                this.checkBackgroundAvailability();
+                if (value === this._background.roundRadius) {
+                    return;
+                }
+                this._background.roundRadius = value;
+                this._background.isVisible = true;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Canvas2D.prototype.checkBackgroundAvailability = function () {
+            if (this._cachingStrategy === Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
+                throw Error("Can't use Canvas Background with the caching strategy TOPLEVELGROUPS");
+            }
+        };
+        Object.defineProperty(Canvas2D.prototype, "hierarchySiblingZDelta", {
+            /**
+             * Read-only property that return the Z delta to apply for each sibling primitives inside of a given one.
+             * Sibling Primitives are defined in a specific order, the first ones will be draw below the next ones.
+             * This property define the Z value to apply between each sibling Primitive. Current implementation allows 1000 Siblings Primitives per level.
+             * @returns The Z Delta
+             */
+            get: function () {
+                return this._hierarchySiblingZDelta;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "hierarchyLevelZFactor", {
+            get: function () {
+                return this._hierarchyLevelZFactor;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Method that renders the Canvas
+         * @param camera the current camera.
+         */
+        Canvas2D.prototype.render = function () {
+            this._renderingSize.width = this.engine.getRenderWidth();
+            this._renderingSize.height = this.engine.getRenderHeight();
+            var context = new BABYLON.Render2DContext();
+            context.forceRefreshPrimitive = false;
+            ++this._globalTransformProcessStep;
+            this.updateGlobalTransVis(false);
+            this._prepareGroupRender(context);
+            this._groupRender(context);
+        };
+        /**
+         * Internal method that alloc a cache for the given group.
+         * Caching is made using a collection of MapTexture where many groups have their bitmapt cache stored inside.
+         * @param group The group to allocate the cache of.
+         * @return custom type with the PackedRect instance giving information about the cache location into the texture and also the MapTexture instance that stores the cache.
+         */
+        Canvas2D.prototype._allocateGroupCache = function (group) {
+            // Determine size
+            var size = group.actualSize;
+            size = new BABYLON.Size(Math.ceil(size.width), Math.ceil(size.height));
+            if (!this._groupCacheMaps) {
+                this._groupCacheMaps = new Array();
+            }
+            // Try to find a spot in one of the cached texture
+            var res = null;
+            for (var _i = 0, _a = this._groupCacheMaps; _i < _a.length; _i++) {
+                var map = _a[_i];
+                var node = map.allocateRect(size);
+                if (node) {
+                    res = { node: node, texture: map };
+                    break;
+                }
+            }
+            // Couldn't find a map that could fit the rect, create a new map for it
+            if (!res) {
+                var mapSize = new BABYLON.Size(Canvas2D._groupTextureCacheSize, Canvas2D._groupTextureCacheSize);
+                // Check if the predefined size would fit, other create a custom size using the nearest bigger power of 2
+                if (size.width > mapSize.width || size.height > mapSize.height) {
+                    mapSize.width = Math.pow(2, Math.ceil(Math.log(size.width) / Math.log(2)));
+                    mapSize.height = Math.pow(2, Math.ceil(Math.log(size.height) / Math.log(2)));
+                }
+                var id = "groupsMapChache" + this._mapCounter + "forCanvas" + this.id;
+                map = new BABYLON.MapTexture(id, this._scene, mapSize);
+                this._groupCacheMaps.push(map);
+                var node = map.allocateRect(size);
+                res = { node: node, texture: map };
+            }
+            // 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
+            // Don't do it in case of the group being a worldspace canvas (because its texture is bound to a WorldSpaceCanvas node)
+            if (group !== this || this._isScreeSpace) {
+                var node = res.node;
+                var sprite = BABYLON.Sprite2D.Create(this, "__cachedSpriteOfGroup__" + group.id, group.position.x, group.position.y, map, node.contentSize, node.pos, false);
+                sprite.origin = BABYLON.Vector2.Zero();
+                res.sprite = sprite;
+            }
+            return res;
+        };
+        /**
+         * Get a Solid Color Brush instance matching the given color.
+         * @param color The color to retrieve
+         * @return A shared instance of the SolidColorBrush2D class that use the given color
+         */
+        Canvas2D.GetSolidColorBrush = function (color) {
+            return Canvas2D._solidColorBrushes.getOrAddWithFactory(color.toHexString(), function () { return new BABYLON.SolidColorBrush2D(color.clone(), true); });
+        };
+        /**
+         * Get a Solid Color Brush instance matching the given color expressed as a CSS formatted hexadecimal value.
+         * @param color The color to retrieve
+         * @return A shared instance of the SolidColorBrush2D class that uses the given color
+         */
+        Canvas2D.GetSolidColorBrushFromHex = function (hexValue) {
+            return Canvas2D._solidColorBrushes.getOrAddWithFactory(hexValue, function () { return new BABYLON.SolidColorBrush2D(BABYLON.Color4.FromHexString(hexValue), true); });
+        };
+        Canvas2D.GetGradientColorBrush = function (color1, color2, translation, rotation, scale) {
+            if (translation === void 0) { translation = BABYLON.Vector2.Zero(); }
+            if (rotation === void 0) { rotation = 0; }
+            if (scale === void 0) { scale = 1; }
+            return Canvas2D._gradientColorBrushes.getOrAddWithFactory(BABYLON.GradientColorBrush2D.BuildKey(color1, color2, translation, rotation, scale), function () { return new BABYLON.GradientColorBrush2D(color1, color2, translation, rotation, scale, true); });
+        };
+        /**
+         * In this strategy only the direct children groups of the Canvas will be cached, their whole content (whatever the sub groups they have) into a single bitmap.
+         * This strategy doesn't allow primitives added directly as children of the Canvas.
+         * You typically want to use this strategy of a screenSpace fullscreen canvas: you don't want a bitmap cache taking the whole screen resolution but still want the main contents (say UI in the topLeft and rightBottom for instance) to be efficiently cached.
+         */
+        Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS = 1;
+        /**
+         * In this strategy each group will have its own cache bitmap (except if a given group explicitly defines the DONTCACHEOVERRIDE or CACHEINPARENTGROUP behaviors).
+         * This strategy is typically used if the canvas has some groups that are frequently animated. Unchanged ones will have a steady cache and the others will be refreshed when they change, reducing the redraw operation count to their content only.
+         * When using this strategy, group instances can rely on the DONTCACHEOVERRIDE or CACHEINPARENTGROUP behaviors to minize the amount of cached bitmaps.
+         */
+        Canvas2D.CACHESTRATEGY_ALLGROUPS = 2;
+        /**
+         * In this strategy the whole canvas is cached into a single bitmap containing every primitives it owns, at the exception of the ones that are owned by a group having the DONTCACHEOVERRIDE behavior (these primitives will be directly drawn to the viewport at each render for screenSpace Canvas or be part of the Canvas cache bitmap for worldSpace Canvas).
+         */
+        Canvas2D.CACHESTRATEGY_CANVAS = 3;
+        /**
+         * This strategy is used to recompose/redraw the canvas entierely at each viewport render.
+         * Use this strategy if memory is a concern above rendering performances and/or if the canvas is frequently animated (hence reducing the benefits of caching).
+         * Note that you can't use this strategy for WorldSpace Canvas, they need at least a top level group caching.
+         */
+        Canvas2D.CACHESTRATEGY_DONTCACHE = 4;
+        /**
+         * Define the default size used for both the width and height of a MapTexture to allocate.
+         * Note that some MapTexture might be bigger than this size if the first node to allocate is bigger in width or height
+         */
+        Canvas2D._groupTextureCacheSize = 1024;
+        Canvas2D._solidColorBrushes = new BABYLON.StringDictionary();
+        Canvas2D._gradientColorBrushes = new BABYLON.StringDictionary();
+        Canvas2D = __decorate([
+            BABYLON.className("Canvas2D")
+        ], Canvas2D);
+        return Canvas2D;
+    })(BABYLON.Group2D);
+    BABYLON.Canvas2D = Canvas2D;
+})(BABYLON || (BABYLON = {}));

+ 370 - 341
src/Canvas2d/babylon.group2d.js

@@ -1,341 +1,370 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var Group2D = (function (_super) {
-        __extends(Group2D, _super);
-        function Group2D() {
-            _super.call(this);
-            this._primDirtyList = new Array();
-            this._childrenRenderableGroups = new Array();
-            this.groupRenderInfo = new BABYLON.StringDictionary();
-        }
-        Group2D.CreateGroup2D = function (parent, id, position, size, cacheBehabior) {
-            if (cacheBehabior === void 0) { cacheBehabior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
-            BABYLON.Prim2DBase.CheckParent(parent);
-            var g = new Group2D();
-            g.setupGroup2D(parent.owner, parent, id, position, size, cacheBehabior);
-            return g;
-        };
-        /**
-         * Create an instance of the Group Primitive.
-         * A group act as a container for many sub primitives, if features:
-         * - Maintain a size, not setting one will determine it based on its content.
-         * - Play an essential role in the rendering pipeline. A group and its content can be cached into a bitmap to enhance rendering performance (at the cost of memory storage in GPU)
-         * @param owner
-         * @param id
-         * @param position
-         * @param size
-         * @param dontcache
-         */
-        Group2D.prototype.setupGroup2D = function (owner, parent, id, position, size, cacheBehavior) {
-            if (cacheBehavior === void 0) { cacheBehavior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
-            this._cacheBehavior = cacheBehavior;
-            this.setupPrim2DBase(owner, parent, id, position);
-            this.size = size;
-            this._viewportPosition = BABYLON.Vector2.Zero();
-        };
-        Object.defineProperty(Group2D.prototype, "isRenderableGroup", {
-            get: function () {
-                return this._isRenderableGroup;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Group2D.prototype, "isCachedGroup", {
-            get: function () {
-                return this._isCachedGroup;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Group2D.prototype, "size", {
-            get: function () {
-                return this._size;
-            },
-            set: function (val) {
-                this._size = val;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Group2D.prototype, "viewportSize", {
-            get: function () {
-                return this._viewportSize;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Group2D.prototype, "actualSize", {
-            get: function () {
-                // Return the size if set by the user
-                if (this._size) {
-                    return this._size;
-                }
-                // Otherwise the size is computed based on the boundingInfo
-                var size = this.boundingInfo.extent.clone();
-                return size;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Group2D.prototype, "cacheBehavior", {
-            get: function () {
-                return this._cacheBehavior;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Group2D.prototype._addPrimToDirtyList = function (prim) {
-            this._primDirtyList.push(prim);
-        };
-        Group2D.prototype.updateLevelBoundingInfo = function () {
-            var size;
-            // If the size is set by the user, the boundingInfo is compute from this value
-            if (this.size) {
-                size = this.size;
-            }
-            else {
-                size = new BABYLON.Size(0, 0);
-            }
-            this._levelBoundingInfo.radius = Math.sqrt(size.width * size.width + size.height * size.height);
-            this._levelBoundingInfo.extent = size.clone();
-        };
-        // Method called only on renderable groups to prepare the rendering
-        Group2D.prototype._prepareGroupRender = function (context) {
-            var childrenContext = this._buildChildContext(context);
-            var sortedDirtyList = null;
-            // Update the Global Transformation and visibility status of the changed primitives
-            if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
-                sortedDirtyList = this._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
-                this.updateGlobalTransVisOf(sortedDirtyList, childrenContext, true);
-            }
-            // Setup the size of the rendering viewport
-            // In non cache mode, we're rendering directly to the rendering canvas, in this case we have to detect if the canvas size changed since the previous iteration, if it's the case all primitives must be preprared again because their transformation must be recompute
-            if (!this._isCachedGroup) {
-                // Compute the WebGL viewport's location/size
-                var t = this._globalTransform.getTranslation();
-                var s = this.actualSize.clone();
-                var rs = this.owner._renderingSize;
-                s.height = Math.min(s.height, rs.height - t.y);
-                s.width = Math.min(s.width, rs.width - t.x);
-                var x = t.x;
-                var y = (rs.height - s.height) - t.y;
-                // The viewport where we're rendering must be the size of the canvas if this one fit in the rendering screen or clipped to the screen dimensions if needed
-                this._viewportPosition.x = x;
-                this._viewportPosition.y = y;
-                var vw = s.width;
-                var vh = s.height;
-                if (!this._viewportSize) {
-                    this._viewportSize = new BABYLON.Size(vw, vh);
-                }
-                else {
-                    if (this._viewportSize.width !== vw || this._viewportSize.height !== vh) {
-                        context.forceRefreshPrimitive = true;
-                    }
-                    this._viewportSize.width = vw;
-                    this._viewportSize.height = vh;
-                }
-            }
-            else {
-                this._viewportSize = this.actualSize;
-            }
-            if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
-                // If the group is cached, set the dirty flag to true because of the incoming changes
-                this._cacheGroupDirty = this._isCachedGroup;
-                // If it's a force refresh, prepare all the children
-                if (context.forceRefreshPrimitive) {
-                    for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
-                        var p = _a[_i];
-                        p._prepareRender(childrenContext);
-                    }
-                }
-                else {
-                    // Each primitive that changed at least once was added into the primDirtyList, we have to sort this level using
-                    //  the hierarchyDepth in order to prepare primitives from top to bottom
-                    if (!sortedDirtyList) {
-                        sortedDirtyList = this._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
-                    }
-                    sortedDirtyList.forEach(function (p) {
-                        // We need to check if prepare is needed because even if the primitive is in the dirtyList, its parent primitive may also have been modified, then prepared, then recurse on its children primitives (this one for instance) if the changes where impacting them.
-                        // For instance: a Rect's position change, the position of its children primitives will also change so a prepare will be call on them. If a child was in the dirtyList we will avoid a second prepare by making this check.
-                        if (p.needPrepare()) {
-                            p._prepareRender(childrenContext);
-                        }
-                    });
-                    // Everything is updated, clear the dirty list
-                    this._primDirtyList.splice(0);
-                }
-            }
-            // A renderable group has a list of direct children that are also renderable groups, we recurse on them to also prepare them
-            this._childrenRenderableGroups.forEach(function (g) {
-                g._prepareGroupRender(childrenContext);
-            });
-        };
-        Group2D.prototype._groupRender = function (context) {
-            var engine = this.owner.engine;
-            var failedCount = 0;
-            // First recurse to children render group to render them (in their cache or on screen)
-            var childrenContext = this._buildChildContext(context);
-            for (var _i = 0, _a = this._childrenRenderableGroups; _i < _a.length; _i++) {
-                var childGroup = _a[_i];
-                childGroup._groupRender(childrenContext);
-            }
-            // Render the primitives if needed: either if we don't cache the content or if the content is cached but has changed
-            if (!this.isCachedGroup || this._cacheGroupDirty) {
-                if (this.isCachedGroup) {
-                    this._bindCacheTarget();
-                }
-                else {
-                    var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
-                }
-                // For each different model of primitive to render
-                this.groupRenderInfo.forEach(function (k, v) {
-                    // If the instances of the model was changed, pack the data
-                    var instanceData = v._instancesData.pack();
-                    // Compute the size the instance buffer should have
-                    var neededSize = v._instancesData.usedElementCount * v._instancesData.stride * 4;
-                    // Check if we have to (re)create the instancesBuffer because there's none or the size doesn't match
-                    if (!v._instancesBuffer || (v._instancesBufferSize !== neededSize)) {
-                        if (v._instancesBuffer) {
-                            engine.deleteInstancesBuffer(v._instancesBuffer);
-                        }
-                        v._instancesBuffer = engine.createInstancesBuffer(neededSize);
-                        v._instancesBufferSize = neededSize;
-                        v._dirtyInstancesData = true;
-                        // Update the WebGL buffer to match the new content of the instances data
-                        engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
-                    }
-                    else if (v._dirtyInstancesData) {
-                        // Update the WebGL buffer to match the new content of the instances data
-                        engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesBuffer);
-                        engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData);
-                        v._dirtyInstancesData = false;
-                    }
-                    // render all the instances of this model, if the render method returns true then our instances are no longer dirty
-                    var renderFailed = !v._modelCache.render(v, context);
-                    // Update dirty flag/related
-                    v._dirtyInstancesData = renderFailed;
-                    failedCount += renderFailed ? 1 : 0;
-                });
-                // The group's content is no longer dirty
-                this._cacheGroupDirty = failedCount !== 0;
-                if (this.isCachedGroup) {
-                    this._unbindCacheTarget();
-                }
-                else {
-                    if (curVP) {
-                        engine.setViewport(curVP);
-                    }
-                }
-            }
-        };
-        Group2D.prototype._bindCacheTarget = function () {
-            // Check if we have to allocate a rendering zone in the global cache texture
-            if (!this._cacheNode) {
-                var res = this.owner._allocateGroupCache(this);
-                this._cacheNode = res.node;
-                this._cacheTexture = res.texture;
-            }
-            var n = this._cacheNode;
-            this._cacheTexture.bindTextureForRect(n);
-        };
-        Group2D.prototype._unbindCacheTarget = function () {
-            if (this._cacheTexture) {
-                this._cacheTexture.unbindTexture();
-            }
-        };
-        Group2D.prototype.detectGroupStates = function () {
-            var isCanvas = this instanceof BABYLON.Canvas2D;
-            var canvasStrat = this.owner.cachingStrategy;
-            // In Don't Cache mode, only the canvas is renderable, all the other groups are logical. There are not a single cached group.
-            if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_DONTCACHE) {
-                this._isRenderableGroup = isCanvas;
-                this._isCachedGroup = false;
-            }
-            else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_CANVAS) {
-                if (isCanvas) {
-                    this._isRenderableGroup = true;
-                    this._isCachedGroup = true;
-                }
-                else {
-                    this._isRenderableGroup = false;
-                    this._isCachedGroup = false;
-                }
-            }
-            else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
-                if (isCanvas) {
-                    this._isRenderableGroup = true;
-                    this._isCachedGroup = false;
-                }
-                else {
-                    if (this.hierarchyDepth === 1) {
-                        this._isRenderableGroup = true;
-                        this._isCachedGroup = true;
-                    }
-                    else {
-                        this._isRenderableGroup = false;
-                        this._isCachedGroup = false;
-                    }
-                }
-            }
-            else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_ALLGROUPS) {
-                var gcb = this.cacheBehavior;
-                if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
-                    this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
-                    this._isCachedGroup = false;
-                }
-                if (gcb === Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
-                    this._isRenderableGroup = true;
-                    this._isCachedGroup = true;
-                }
-            }
-            // If the group is tagged as renderable we add it to the renderable tree
-            if (this._isCachedGroup) {
-                var cur = this.parent;
-                while (cur) {
-                    if (cur instanceof Group2D && cur._isRenderableGroup) {
-                        cur._childrenRenderableGroups.push(this);
-                        break;
-                    }
-                    cur = cur.parent;
-                }
-            }
-        };
-        Group2D.GROUP2D_PROPCOUNT = BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 10;
-        /**
-         * Default behavior, the group will use the caching strategy defined at the Canvas Level
-         */
-        Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY = 0;
-        /**
-         * When used, this group's content won't be cached, no matter which strategy used.
-         * If the group is part of a WorldSpace Canvas, its content will be drawn in the Canvas cache bitmap.
-         */
-        Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE = 1;
-        /**
-         * When used, the group's content will be cached in the nearest cached parent group/canvas
-         */
-        Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP = 2;
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return Group2D.sizeProperty = pi; }, false, true)
-        ], Group2D.prototype, "size", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 2, function (pi) { return Group2D.actualSizeProperty = pi; })
-        ], Group2D.prototype, "actualSize", null);
-        Group2D = __decorate([
-            BABYLON.className("Group2D")
-        ], Group2D);
-        return Group2D;
-    }(BABYLON.Prim2DBase));
-    BABYLON.Group2D = Group2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.group2d.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Group2D = (function (_super) {
+        __extends(Group2D, _super);
+        function Group2D() {
+            _super.call(this);
+            this._primDirtyList = new Array();
+            this._childrenRenderableGroups = new Array();
+            this.groupRenderInfo = new BABYLON.StringDictionary();
+        }
+        Group2D.CreateGroup2D = function (parent, id, position, size, cacheBehabior) {
+            if (cacheBehabior === void 0) { cacheBehabior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var g = new Group2D();
+            g.setupGroup2D(parent.owner, parent, id, position, size, cacheBehabior);
+            return g;
+        };
+        Group2D.prototype.applyCachedTexture = function (vertexData, material) {
+            this._bindCacheTarget();
+            var uv = vertexData.uvs;
+            var nodeuv = this._cacheNode.UVs;
+            for (var i = 0; i < 4; i++) {
+                uv[i * 2 + 0] = nodeuv[i].x;
+                uv[i * 2 + 1] = nodeuv[i].y;
+            }
+            material.diffuseTexture = this._cacheTexture;
+            material.emissiveColor = new BABYLON.Color3(1, 1, 1);
+            this._cacheTexture.hasAlpha = true;
+            this._unbindCacheTarget();
+        };
+        /**
+         * Create an instance of the Group Primitive.
+         * A group act as a container for many sub primitives, if features:
+         * - Maintain a size, not setting one will determine it based on its content.
+         * - Play an essential role in the rendering pipeline. A group and its content can be cached into a bitmap to enhance rendering performance (at the cost of memory storage in GPU)
+         * @param owner
+         * @param id
+         * @param position
+         * @param size
+         * @param dontcache
+         */
+        Group2D.prototype.setupGroup2D = function (owner, parent, id, position, size, cacheBehavior) {
+            if (cacheBehavior === void 0) { cacheBehavior = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY; }
+            this._cacheBehavior = cacheBehavior;
+            this.setupPrim2DBase(owner, parent, id, position);
+            this.size = size;
+            this._viewportPosition = BABYLON.Vector2.Zero();
+        };
+        Object.defineProperty(Group2D.prototype, "isRenderableGroup", {
+            get: function () {
+                return this._isRenderableGroup;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Group2D.prototype, "isCachedGroup", {
+            get: function () {
+                return this._isCachedGroup;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Group2D.prototype, "size", {
+            get: function () {
+                return this._size;
+            },
+            set: function (val) {
+                this._size = val;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Group2D.prototype, "viewportSize", {
+            get: function () {
+                return this._viewportSize;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Group2D.prototype, "actualSize", {
+            get: function () {
+                // Return the size if set by the user
+                if (this._size) {
+                    return this._size;
+                }
+                // Otherwise the size is computed based on the boundingInfo
+                var m = this.boundingInfo.max();
+                return new BABYLON.Size(m.x, m.y);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Group2D.prototype, "cacheBehavior", {
+            get: function () {
+                return this._cacheBehavior;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Group2D.prototype._addPrimToDirtyList = function (prim) {
+            this._primDirtyList.push(prim);
+        };
+        Group2D.prototype.updateLevelBoundingInfo = function () {
+            var size;
+            // If the size is set by the user, the boundingInfo is computed from this value
+            if (this.size) {
+                size = this.size;
+            }
+            else {
+                size = new BABYLON.Size(0, 0);
+            }
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(size, this._levelBoundingInfo);
+        };
+        // Method called only on renderable groups to prepare the rendering
+        Group2D.prototype._prepareGroupRender = function (context) {
+            var sortedDirtyList = null;
+            // Update the Global Transformation and visibility status of the changed primitives
+            if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
+                sortedDirtyList = this._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
+                this.updateGlobalTransVisOf(sortedDirtyList, true);
+            }
+            // Setup the size of the rendering viewport
+            // In non cache mode, we're rendering directly to the rendering canvas, in this case we have to detect if the canvas size changed since the previous iteration, if it's the case all primitives must be preprared again because their transformation must be recompute
+            if (!this._isCachedGroup) {
+                // Compute the WebGL viewport's location/size
+                var t = this._globalTransform.getTranslation();
+                var s = this.actualSize.clone();
+                var rs = this.owner._renderingSize;
+                s.height = Math.min(s.height, rs.height - t.y);
+                s.width = Math.min(s.width, rs.width - t.x);
+                var x = t.x;
+                var y = (rs.height - s.height) - t.y;
+                // The viewport where we're rendering must be the size of the canvas if this one fit in the rendering screen or clipped to the screen dimensions if needed
+                this._viewportPosition.x = x;
+                this._viewportPosition.y = y;
+                var vw = s.width;
+                var vh = s.height;
+                if (!this._viewportSize) {
+                    this._viewportSize = new BABYLON.Size(vw, vh);
+                }
+                else {
+                    if (this._viewportSize.width !== vw || this._viewportSize.height !== vh) {
+                        context.forceRefreshPrimitive = true;
+                    }
+                    this._viewportSize.width = vw;
+                    this._viewportSize.height = vh;
+                }
+            }
+            else {
+                this._viewportSize = this.actualSize;
+            }
+            if ((this._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
+                // If the group is cached, set the dirty flag to true because of the incoming changes
+                this._cacheGroupDirty = this._isCachedGroup;
+                // If it's a force refresh, prepare all the children
+                if (context.forceRefreshPrimitive) {
+                    for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
+                        var p = _a[_i];
+                        p._prepareRender(context);
+                    }
+                }
+                else {
+                    // Each primitive that changed at least once was added into the primDirtyList, we have to sort this level using
+                    //  the hierarchyDepth in order to prepare primitives from top to bottom
+                    if (!sortedDirtyList) {
+                        sortedDirtyList = this._primDirtyList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
+                    }
+                    sortedDirtyList.forEach(function (p) {
+                        // We need to check if prepare is needed because even if the primitive is in the dirtyList, its parent primitive may also have been modified, then prepared, then recurse on its children primitives (this one for instance) if the changes where impacting them.
+                        // For instance: a Rect's position change, the position of its children primitives will also change so a prepare will be call on them. If a child was in the dirtyList we will avoid a second prepare by making this check.
+                        if (p.needPrepare()) {
+                            p._prepareRender(context);
+                        }
+                    });
+                    // Everything is updated, clear the dirty list
+                    this._primDirtyList.splice(0);
+                }
+            }
+            // A renderable group has a list of direct children that are also renderable groups, we recurse on them to also prepare them
+            this._childrenRenderableGroups.forEach(function (g) {
+                g._prepareGroupRender(context);
+            });
+        };
+        Group2D.prototype._groupRender = function (context) {
+            var engine = this.owner.engine;
+            var failedCount = 0;
+            // First recurse to children render group to render them (in their cache or on screen)
+            for (var _i = 0, _a = this._childrenRenderableGroups; _i < _a.length; _i++) {
+                var childGroup = _a[_i];
+                childGroup._groupRender(context);
+            }
+            // Render the primitives if needed: either if we don't cache the content or if the content is cached but has changed
+            if (!this.isCachedGroup || this._cacheGroupDirty) {
+                if (this.isCachedGroup) {
+                    this._bindCacheTarget();
+                }
+                else {
+                    var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
+                }
+                // For each different model of primitive to render
+                this.groupRenderInfo.forEach(function (k, v) {
+                    for (var i = 0; i < v._instancesPartsData.length; i++) {
+                        // If the instances of the model was changed, pack the data
+                        var instanceData_1 = v._instancesPartsData[i].pack();
+                        // Compute the size the instance buffer should have
+                        var neededSize = v._instancesPartsData[i].usedElementCount * v._instancesPartsData[i].stride * 4;
+                        // Check if we have to (re)create the instancesBuffer because there's none or the size doesn't match
+                        if (!v._instancesPartsBuffer[i] || (v._instancesPartsBufferSize[i] !== neededSize)) {
+                            if (v._instancesPartsBuffer[i]) {
+                                engine.deleteInstancesBuffer(v._instancesPartsBuffer[i]);
+                            }
+                            v._instancesPartsBuffer[i] = engine.createInstancesBuffer(neededSize);
+                            v._instancesPartsBufferSize[i] = neededSize;
+                            v._dirtyInstancesData = true;
+                            // Update the WebGL buffer to match the new content of the instances data
+                            engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData_1);
+                        }
+                        else if (v._dirtyInstancesData) {
+                            // Update the WebGL buffer to match the new content of the instances data
+                            engine._gl.bindBuffer(engine._gl.ARRAY_BUFFER, v._instancesPartsBuffer[i]);
+                            engine._gl.bufferSubData(engine._gl.ARRAY_BUFFER, 0, instanceData_1);
+                            v._dirtyInstancesData = false;
+                        }
+                    }
+                    // render all the instances of this model, if the render method returns true then our instances are no longer dirty
+                    var renderFailed = !v._modelCache.render(v, context);
+                    // Update dirty flag/related
+                    v._dirtyInstancesData = renderFailed;
+                    failedCount += renderFailed ? 1 : 0;
+                });
+                // The group's content is no longer dirty
+                this._cacheGroupDirty = failedCount !== 0;
+                if (this.isCachedGroup) {
+                    this._unbindCacheTarget();
+                }
+                else {
+                    if (curVP) {
+                        engine.setViewport(curVP);
+                    }
+                }
+            }
+        };
+        Group2D.prototype._bindCacheTarget = function () {
+            // Check if we have to allocate a rendering zone in the global cache texture
+            if (!this._cacheNode) {
+                var res = this.owner._allocateGroupCache(this);
+                this._cacheNode = res.node;
+                this._cacheTexture = res.texture;
+                this._cacheRenderSprite = res.sprite;
+            }
+            var n = this._cacheNode;
+            this._cacheTexture.bindTextureForRect(n, true);
+        };
+        Group2D.prototype._unbindCacheTarget = function () {
+            if (this._cacheTexture) {
+                this._cacheTexture.unbindTexture();
+            }
+        };
+        Group2D.prototype.handleGroupChanged = function (prop) {
+            // This method is only for cachedGroup
+            if (!this.isCachedGroup || !this._cacheRenderSprite) {
+                return;
+            }
+            // For now we only support these property changes
+            // TODO: add more! :)
+            if (prop.id === BABYLON.Prim2DBase.positionProperty.id) {
+                this._cacheRenderSprite.position = this.position.clone();
+            }
+            else if (prop.id === BABYLON.Prim2DBase.rotationProperty.id) {
+                this._cacheRenderSprite.rotation = this.rotation;
+            }
+            else if (prop.id === BABYLON.Prim2DBase.scaleProperty.id) {
+                this._cacheRenderSprite.scale = this.scale;
+            }
+        };
+        Group2D.prototype.detectGroupStates = function () {
+            var isCanvas = this instanceof BABYLON.Canvas2D;
+            var canvasStrat = this.owner.cachingStrategy;
+            // In Don't Cache mode, only the canvas is renderable, all the other groups are logical. There are not a single cached group.
+            if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_DONTCACHE) {
+                this._isRenderableGroup = isCanvas;
+                this._isCachedGroup = false;
+            }
+            else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_CANVAS) {
+                if (isCanvas) {
+                    this._isRenderableGroup = true;
+                    this._isCachedGroup = true;
+                }
+                else {
+                    this._isRenderableGroup = false;
+                    this._isCachedGroup = false;
+                }
+            }
+            else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) {
+                if (isCanvas) {
+                    this._isRenderableGroup = true;
+                    this._isCachedGroup = false;
+                }
+                else {
+                    if (this.hierarchyDepth === 1) {
+                        this._isRenderableGroup = true;
+                        this._isCachedGroup = true;
+                    }
+                    else {
+                        this._isRenderableGroup = false;
+                        this._isCachedGroup = false;
+                    }
+                }
+            }
+            else if (canvasStrat === BABYLON.Canvas2D.CACHESTRATEGY_ALLGROUPS) {
+                var gcb = this.cacheBehavior;
+                if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
+                    this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
+                    this._isCachedGroup = false;
+                }
+                if (gcb === Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY) {
+                    this._isRenderableGroup = true;
+                    this._isCachedGroup = true;
+                }
+            }
+            // If the group is tagged as renderable we add it to the renderable tree
+            if (this._isCachedGroup) {
+                var cur = this.parent;
+                while (cur) {
+                    if (cur instanceof Group2D && cur._isRenderableGroup) {
+                        cur._childrenRenderableGroups.push(this);
+                        break;
+                    }
+                    cur = cur.parent;
+                }
+            }
+        };
+        Group2D.GROUP2D_PROPCOUNT = BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
+        /**
+           * Default behavior, the group will use the caching strategy defined at the Canvas Level
+           */
+        Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY = 0;
+        /**
+         * When used, this group's content won't be cached, no matter which strategy used.
+         * If the group is part of a WorldSpace Canvas, its content will be drawn in the Canvas cache bitmap.
+         */
+        Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE = 1;
+        /**
+         * When used, the group's content will be cached in the nearest cached parent group/canvas
+         */
+        Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP = 2;
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return Group2D.sizeProperty = pi; }, false, true)
+        ], Group2D.prototype, "size", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 2, function (pi) { return Group2D.actualSizeProperty = pi; })
+        ], Group2D.prototype, "actualSize", null);
+        Group2D = __decorate([
+            BABYLON.className("Group2D")
+        ], Group2D);
+        return Group2D;
+    })(BABYLON.Prim2DBase);
+    BABYLON.Group2D = Group2D;
+})(BABYLON || (BABYLON = {}));

+ 133 - 54
src/Canvas2d/babylon.modelRenderCache.js

@@ -1,54 +1,133 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var BABYLON;
-(function (BABYLON) {
-    var GroupInstanceInfo = (function () {
-        function GroupInstanceInfo(owner, classTreeInfo, cache) {
-            this._owner = owner;
-            this._classTreeInfo = classTreeInfo;
-            this._modelCache = cache;
-        }
-        return GroupInstanceInfo;
-    }());
-    BABYLON.GroupInstanceInfo = GroupInstanceInfo;
-    var ModelRenderCacheBase = (function () {
-        function ModelRenderCacheBase() {
-        }
-        /**
-         * Render the model instances
-         * @param instanceInfo
-         * @param context
-         * @return must return true is the rendering succeed, false if the rendering couldn't be done (asset's not yet ready, like Effect)
-         */
-        ModelRenderCacheBase.prototype.render = function (instanceInfo, context) {
-            return true;
-        };
-        return ModelRenderCacheBase;
-    }());
-    BABYLON.ModelRenderCacheBase = ModelRenderCacheBase;
-    var ModelRenderCache = (function (_super) {
-        __extends(ModelRenderCache, _super);
-        function ModelRenderCache() {
-            _super.call(this);
-            this._nextKey = 1;
-            this._instancesData = new BABYLON.StringDictionary();
-        }
-        ModelRenderCache.prototype.addInstanceData = function (data) {
-            var key = this._nextKey.toString();
-            if (!this._instancesData.add(key, data)) {
-                throw Error("Key: " + key + " is already allocated");
-            }
-            ++this._nextKey;
-            return key;
-        };
-        ModelRenderCache.prototype.removeInstanceData = function (key) {
-            this._instancesData.remove(key);
-        };
-        return ModelRenderCache;
-    }(ModelRenderCacheBase));
-    BABYLON.ModelRenderCache = ModelRenderCache;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.modelRenderCache.js.map
+var BABYLON;
+(function (BABYLON) {
+    var GroupInstanceInfo = (function () {
+        function GroupInstanceInfo(owner, cache) {
+            this._owner = owner;
+            this._modelCache = cache;
+            this._instancesPartsData = new Array();
+            this._instancesPartsBuffer = new Array();
+            this._instancesPartsBufferSize = new Array();
+            this._partIndexFromId = new BABYLON.StringDictionary();
+            this._instancesPartsUsedShaderCategories = new Array();
+        }
+        return GroupInstanceInfo;
+    })();
+    BABYLON.GroupInstanceInfo = GroupInstanceInfo;
+    var ModelRenderCache = (function () {
+        function ModelRenderCache(modelKey, isTransparent) {
+            this._modelKey = modelKey;
+            this._isTransparent = isTransparent;
+            this._nextKey = 1;
+            this._instancesData = new BABYLON.StringDictionary();
+        }
+        /**
+         * Render the model instances
+         * @param instanceInfo
+         * @param context
+         * @return must return true is the rendering succeed, false if the rendering couldn't be done (asset's not yet ready, like Effect)
+         */
+        ModelRenderCache.prototype.render = function (instanceInfo, context) {
+            return true;
+        };
+        ModelRenderCache.prototype.addInstanceDataParts = function (data) {
+            var key = this._nextKey.toString();
+            if (!this._instancesData.add(key, data)) {
+                throw Error("Key: " + key + " is already allocated");
+            }
+            ++this._nextKey;
+            return key;
+        };
+        ModelRenderCache.prototype.removeInstanceData = function (key) {
+            this._instancesData.remove(key);
+        };
+        ModelRenderCache.prototype.getPartIndexFromId = function (partId) {
+            for (var i = 0; i < this._partIdList.length; i++) {
+                if (this._partIdList[i] === partId) {
+                    return i;
+                }
+            }
+            return null;
+        };
+        ModelRenderCache.prototype.loadInstancingAttributes = function (partId, effect) {
+            var i = this.getPartIndexFromId(partId);
+            if (i === null) {
+                return null;
+            }
+            var ci = this._partsClassInfo[i];
+            var categories = this._partsUsedCategories[i];
+            var res = ci.classContent.getInstancingAttributeInfos(effect, categories);
+            return res;
+        };
+        ModelRenderCache.prototype.setupUniforms = function (effect, partIndex, data, elementCount) {
+            var offset = (this._partsDataStride[partIndex] / 4) * elementCount;
+            var pci = this._partsClassInfo[partIndex];
+            var self = this;
+            pci.fullContent.forEach(function (k, v) {
+                if (!v.category || self._partsUsedCategories[partIndex].indexOf(v.category) !== 1) {
+                    switch (v.dataType) {
+                        case 4 /* float */:
+                            {
+                                var attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                                effect.setFloat(v.attributeName, data.buffer[offset + attribOffset]);
+                                break;
+                            }
+                        case 0 /* Vector2 */:
+                            {
+                                var attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                                ModelRenderCache.v2.x = data.buffer[offset + attribOffset + 0];
+                                ModelRenderCache.v2.y = data.buffer[offset + attribOffset + 1];
+                                effect.setVector2(v.attributeName, ModelRenderCache.v2);
+                                break;
+                            }
+                        case 5 /* Color3 */:
+                        case 1 /* Vector3 */:
+                            {
+                                var attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                                ModelRenderCache.v3.x = data.buffer[offset + attribOffset + 0];
+                                ModelRenderCache.v3.y = data.buffer[offset + attribOffset + 1];
+                                ModelRenderCache.v3.z = data.buffer[offset + attribOffset + 2];
+                                effect.setVector3(v.attributeName, ModelRenderCache.v3);
+                                break;
+                            }
+                        case 6 /* Color4 */:
+                        case 2 /* Vector4 */:
+                            {
+                                var attribOffset = v.instanceOffset.get(self._partsJoinedUsedCategories[partIndex]);
+                                ModelRenderCache.v4.x = data.buffer[offset + attribOffset + 0];
+                                ModelRenderCache.v4.y = data.buffer[offset + attribOffset + 1];
+                                ModelRenderCache.v4.z = data.buffer[offset + attribOffset + 2];
+                                ModelRenderCache.v4.w = data.buffer[offset + attribOffset + 3];
+                                effect.setVector4(v.attributeName, ModelRenderCache.v4);
+                                break;
+                            }
+                        default:
+                    }
+                }
+            });
+        };
+        Object.defineProperty(ModelRenderCache.prototype, "isTransparent", {
+            get: function () {
+                return this._isTransparent;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        //setupUniformsLocation(effect: Effect, uniforms: string[], partId: number) {
+        //    let i = this.getPartIndexFromId(partId);
+        //    if (i === null) {
+        //        return null;
+        //    }
+        //    let pci = this._partsClassInfo[i];
+        //    pci.fullContent.forEach((k, v) => {
+        //        if (uniforms.indexOf(v.attributeName) !== -1) {
+        //            v.uniformLocation = effect.getUniform(v.attributeName);
+        //        }
+        //    });
+        //}
+        ModelRenderCache.v2 = BABYLON.Vector2.Zero();
+        ModelRenderCache.v3 = BABYLON.Vector3.Zero();
+        ModelRenderCache.v4 = BABYLON.Vector4.Zero();
+        return ModelRenderCache;
+    })();
+    BABYLON.ModelRenderCache = ModelRenderCache;
+})(BABYLON || (BABYLON = {}));

+ 351 - 360
src/Canvas2d/babylon.prim2dBase.js

@@ -1,360 +1,351 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var Render2DContext = (function () {
-        function Render2DContext() {
-        }
-        return Render2DContext;
-    }());
-    BABYLON.Render2DContext = Render2DContext;
-    var Prim2DBase = (function (_super) {
-        __extends(Prim2DBase, _super);
-        function Prim2DBase() {
-            _super.apply(this, arguments);
-        }
-        Prim2DBase.prototype.setupPrim2DBase = function (owner, parent, id, position, isVisible) {
-            if (isVisible === void 0) { isVisible = true; }
-            if (!(this instanceof BABYLON.Group2D) && !(this instanceof BABYLON.Sprite2D && id !== null && id.indexOf("__cachedSpriteOfGroup__") === 0) && (owner.cachingStrategy === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) && (parent === owner)) {
-                throw new Error("Can't create a primitive with the canvas as direct parent when the caching strategy is TOPLEVELGROUPS. You need to create a Group below the canvas and use it as the parent for the primitive");
-            }
-            this.setupSmartPropertyPrim();
-            this._boundingInfoDirty = true;
-            this._boundingInfo = new BABYLON.BoundingInfo2D();
-            this._owner = owner;
-            this._parent = parent;
-            if (parent != null) {
-                this._hierarchyDepth = parent._hierarchyDepth + 1;
-                this._renderGroup = this.parent.traverseUp(function (p) { return p instanceof BABYLON.Group2D && p.isRenderableGroup; });
-                parent.addChild(this);
-            }
-            else {
-                this._hierarchyDepth = 0;
-                this._renderGroup = null;
-            }
-            this._id = id;
-            this.propertyChanged = new BABYLON.Observable();
-            this._children = new Array();
-            this._parentTranformStep = 0;
-            this._globalTransformStep = 0;
-            if (this instanceof BABYLON.Group2D) {
-                var group = this;
-                group.detectGroupStates();
-            }
-            this.position = position;
-            this.rotation = 0;
-            this.scale = 1;
-            this.levelVisible = isVisible;
-            this.origin = new BABYLON.Vector2(0.5, 0.5);
-        };
-        Prim2DBase.prototype.traverseUp = function (predicate) {
-            var p = this;
-            while (p != null) {
-                if (predicate(p)) {
-                    return p;
-                }
-                p = p._parent;
-            }
-            return null;
-        };
-        Object.defineProperty(Prim2DBase.prototype, "owner", {
-            get: function () {
-                return this._owner;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "parent", {
-            get: function () {
-                return this._parent;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "id", {
-            get: function () {
-                return this._id;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "position", {
-            get: function () {
-                return this._position;
-            },
-            set: function (value) {
-                this._position = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "rotation", {
-            get: function () {
-                return this._rotation;
-            },
-            set: function (value) {
-                this._rotation = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "scale", {
-            get: function () {
-                return this._scale;
-            },
-            set: function (value) {
-                this._scale = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "origin", {
-            get: function () {
-                return this._origin;
-            },
-            set: function (value) {
-                this._origin = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "levelVisible", {
-            get: function () {
-                return this._levelVisible;
-            },
-            set: function (value) {
-                this._levelVisible = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "isVisible", {
-            get: function () {
-                return this._isVisible;
-            },
-            set: function (value) {
-                this._isVisible = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "zOrder", {
-            get: function () {
-                return this._zOrder;
-            },
-            set: function (value) {
-                this._zOrder = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "hierarchyDepth", {
-            get: function () {
-                return this._hierarchyDepth;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "renderGroup", {
-            get: function () {
-                return this._renderGroup;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "globalTransform", {
-            get: function () {
-                return this._globalTransform;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "invGlobalTransform", {
-            get: function () {
-                return this._invGlobalTransform;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Prim2DBase.prototype, "boundingInfo", {
-            get: function () {
-                if (this._boundingInfoDirty) {
-                    this._boundingInfo = this.levelBoundingInfo.clone();
-                    var bi = this._boundingInfo;
-                    var localTransform = new BABYLON.Matrix();
-                    if (this.parent) {
-                        this.globalTransform.multiplyToRef(BABYLON.Matrix.Invert(this.parent.globalTransform), localTransform);
-                    }
-                    else {
-                        localTransform = this.globalTransform;
-                    }
-                    var invLocalTransform = BABYLON.Matrix.Invert(localTransform);
-                    this.levelBoundingInfo.transformToRef(localTransform, bi);
-                    var tps = new BABYLON.BoundingInfo2D();
-                    for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
-                        var curChild = _a[_i];
-                        curChild.boundingInfo.transformToRef(curChild.globalTransform.multiply(invLocalTransform), tps);
-                        bi.unionToRef(tps, bi);
-                    }
-                    this._boundingInfoDirty = false;
-                }
-                return this._boundingInfo;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Prim2DBase.prototype.moveChild = function (child, previous) {
-            if (child.parent !== this) {
-                return false;
-            }
-            var prevOffset, nextOffset;
-            var childIndex = this._children.indexOf(child);
-            var prevIndex = previous ? this._children.indexOf(previous) : -1;
-            // Move to first position
-            if (!previous) {
-                prevOffset = 0;
-                nextOffset = this._children[1]._siblingDepthOffset;
-            }
-            else {
-                prevOffset = this._children[prevIndex]._siblingDepthOffset;
-                nextOffset = this._children[prevIndex + 1]._siblingDepthOffset;
-            }
-            child._siblingDepthOffset = (nextOffset - prevOffset) / 2;
-            this._children.splice(prevIndex + 1, 0, this._children.splice(childIndex, 1)[0]);
-        };
-        Prim2DBase.prototype.addChild = function (child) {
-            child._siblingDepthOffset = (this._children.length + 1) * this.owner.hierarchySiblingZDelta;
-            this._children.push(child);
-        };
-        Prim2DBase.prototype.getActualZOffset = function () {
-            return this._zOrder || this._siblingDepthOffset;
-        };
-        Prim2DBase.prototype.onPrimBecomesDirty = function () {
-            if (this._renderGroup) {
-                //if (this instanceof Group2D) {
-                //    var group: any= this;
-                //    if (group.isRenderableGroup) {
-                //        return;
-                //    }
-                //}
-                this._renderGroup._addPrimToDirtyList(this);
-            }
-        };
-        Prim2DBase.prototype.needPrepare = function () {
-            return this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformPreviousStep !== this._globalTransformStep);
-        };
-        Prim2DBase.prototype._buildChildContext = function (context) {
-            var childContext = new Render2DContext();
-            childContext.camera = context.camera;
-            childContext.parentVisibleState = context.parentVisibleState && this.levelVisible;
-            childContext.parentTransform = this._globalTransform;
-            childContext.parentTransformStep = this._globalTransformStep;
-            childContext.forceRefreshPrimitive = context.forceRefreshPrimitive;
-            return childContext;
-        };
-        Prim2DBase.prototype._prepareRender = function (context) {
-            this._prepareRenderPre(context);
-            this._prepareRenderPost(context);
-        };
-        Prim2DBase.prototype._prepareRenderPre = function (context) {
-        };
-        Prim2DBase.prototype._prepareRenderPost = function (context) {
-            // Don't recurse if it's a renderable group, the content will be processed by the group itself
-            if (this instanceof BABYLON.Group2D) {
-                var self = this;
-                if (self.isRenderableGroup) {
-                    return;
-                }
-            }
-            // Check if we need to recurse the prepare to children primitives
-            //  - must have children
-            //  - the global transform of this level have changed, or
-            //  - the visible state of primitive has changed
-            if (this._children.length > 0 && ((this._globalTransformPreviousStep !== this._globalTransformStep) ||
-                this.checkPropertiesDirty(Prim2DBase.isVisibleProperty.flagId))) {
-                var childContext = this._buildChildContext(context);
-                this._children.forEach(function (c) {
-                    // As usual stop the recursion if we meet a renderable group
-                    if (!(c instanceof BABYLON.Group2D && c.isRenderableGroup)) {
-                        c._prepareRender(childContext);
-                    }
-                });
-            }
-            // Finally reset the dirty flags as we've processed everything
-            this._modelDirty = false;
-            this._instanceDirtyFlags = 0;
-        };
-        Prim2DBase.CheckParent = function (parent) {
-            if (!parent) {
-                throw new Error("A Primitive needs a valid Parent, it can be any kind of Primitives based types, even the Canvas (with the exception that only Group2D can be direct child of a Canvas if the cache strategy used is TOPLEVELGROUPS)");
-            }
-        };
-        Prim2DBase.prototype.updateGlobalTransVisOf = function (list, context, recurse) {
-            for (var _i = 0, list_1 = list; _i < list_1.length; _i++) {
-                var cur = list_1[_i];
-                cur.updateGlobalTransVis(context, recurse);
-            }
-        };
-        Prim2DBase.prototype.updateGlobalTransVis = function (context, recurse) {
-            this._globalTransformPreviousStep = this._globalTransformStep;
-            this.isVisible = context.parentVisibleState && this.levelVisible;
-            // Detect if nothing changed
-            var tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
-            if ((context.parentTransformStep === this._parentTranformStep) && !this.checkPropertiesDirty(tflags)) {
-                return;
-            }
-            var rot = BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 0, 1), this._rotation);
-            var local = BABYLON.Matrix.Compose(new BABYLON.Vector3(this._scale, this._scale, this._scale), rot, new BABYLON.Vector3(this._position.x, this._position.y, 0));
-            this._globalTransform = context.parentTransform.multiply(local);
-            this._invGlobalTransform = BABYLON.Matrix.Invert(this._globalTransform);
-            ++this._globalTransformStep;
-            this._parentTranformStep = context.parentTransformStep;
-            this.clearPropertiesDirty(tflags);
-            if (recurse) {
-                var childrenContext = this._buildChildContext(context);
-                for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
-                    var child = _a[_i];
-                    // Stop the recursion if we meet a renderable group
-                    child.updateGlobalTransVis(childrenContext, !(child instanceof BABYLON.Group2D && child.isRenderableGroup));
-                }
-            }
-        };
-        Prim2DBase.PRIM2DBASE_PROPCOUNT = 10;
-        __decorate([
-            BABYLON.instanceLevelProperty(1, function (pi) { return Prim2DBase.positionProperty = pi; }, false, true)
-        ], Prim2DBase.prototype, "position", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(2, function (pi) { return Prim2DBase.rotationProperty = pi; }, false, true)
-        ], Prim2DBase.prototype, "rotation", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(3, function (pi) { return Prim2DBase.scaleProperty = pi; }, false, true)
-        ], Prim2DBase.prototype, "scale", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(4, function (pi) { return Prim2DBase.originProperty = pi; }, false, true)
-        ], Prim2DBase.prototype, "origin", null);
-        __decorate([
-            BABYLON.dynamicLevelProperty(5, function (pi) { return Prim2DBase.levelVisibleProperty = pi; })
-        ], Prim2DBase.prototype, "levelVisible", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(6, function (pi) { return Prim2DBase.isVisibleProperty = pi; })
-        ], Prim2DBase.prototype, "isVisible", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(7, function (pi) { return Prim2DBase.zOrderProperty = pi; })
-        ], Prim2DBase.prototype, "zOrder", null);
-        Prim2DBase = __decorate([
-            BABYLON.className("Prim2DBase")
-        ], Prim2DBase);
-        return Prim2DBase;
-    }(BABYLON.SmartPropertyPrim));
-    BABYLON.Prim2DBase = Prim2DBase;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.prim2dBase.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Render2DContext = (function () {
+        function Render2DContext() {
+        }
+        return Render2DContext;
+    })();
+    BABYLON.Render2DContext = Render2DContext;
+    var Prim2DBase = (function (_super) {
+        __extends(Prim2DBase, _super);
+        function Prim2DBase() {
+            _super.apply(this, arguments);
+        }
+        Prim2DBase.prototype.setupPrim2DBase = function (owner, parent, id, position, isVisible) {
+            if (isVisible === void 0) { isVisible = true; }
+            if (!(this instanceof BABYLON.Group2D) && !(this instanceof BABYLON.Sprite2D && id !== null && id.indexOf("__cachedSpriteOfGroup__") === 0) && (owner.cachingStrategy === BABYLON.Canvas2D.CACHESTRATEGY_TOPLEVELGROUPS) && (parent === owner)) {
+                throw new Error("Can't create a primitive with the canvas as direct parent when the caching strategy is TOPLEVELGROUPS. You need to create a Group below the canvas and use it as the parent for the primitive");
+            }
+            this.setupSmartPropertyPrim();
+            this._boundingInfoDirty = true;
+            this._boundingInfo = new BABYLON.BoundingInfo2D();
+            this._owner = owner;
+            this._parent = parent;
+            if (parent != null) {
+                this._hierarchyDepth = parent._hierarchyDepth + 1;
+                this._renderGroup = this.parent.traverseUp(function (p) { return p instanceof BABYLON.Group2D && p.isRenderableGroup; });
+                parent.addChild(this);
+            }
+            else {
+                this._hierarchyDepth = 0;
+                this._renderGroup = null;
+            }
+            this._id = id;
+            this.propertyChanged = new BABYLON.Observable();
+            this._children = new Array();
+            this._globalTransformProcessStep = 0;
+            this._globalTransformStep = 0;
+            if (this instanceof BABYLON.Group2D) {
+                var group = this;
+                group.detectGroupStates();
+            }
+            this.position = position;
+            this.rotation = 0;
+            this.scale = 1;
+            this.levelVisible = isVisible;
+            this.origin = new BABYLON.Vector2(0.5, 0.5);
+        };
+        Prim2DBase.prototype.traverseUp = function (predicate) {
+            var p = this;
+            while (p != null) {
+                if (predicate(p)) {
+                    return p;
+                }
+                p = p._parent;
+            }
+            return null;
+        };
+        Object.defineProperty(Prim2DBase.prototype, "owner", {
+            get: function () {
+                return this._owner;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "parent", {
+            get: function () {
+                return this._parent;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "id", {
+            get: function () {
+                return this._id;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "position", {
+            get: function () {
+                return this._position;
+            },
+            set: function (value) {
+                this._position = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "rotation", {
+            get: function () {
+                return this._rotation;
+            },
+            set: function (value) {
+                this._rotation = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "scale", {
+            get: function () {
+                return this._scale;
+            },
+            set: function (value) {
+                this._scale = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "origin", {
+            /**
+             * The origin defines the normalized coordinate of the center of the primitive, from the top/left corner.
+             * The origin is used only to compute transformation of the primitive, it has no meaning in the primitive local frame of reference
+             * For instance:
+             * 0,0 means the center is top/left
+             * 0.5,0.5 means the center is at the center of the primtive
+             * 0,1 means the center is bottom/left
+             * @returns The normalized center.
+             */
+            get: function () {
+                return this._origin;
+            },
+            set: function (value) {
+                this._origin = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "levelVisible", {
+            get: function () {
+                return this._levelVisible;
+            },
+            set: function (value) {
+                this._levelVisible = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "isVisible", {
+            get: function () {
+                return this._isVisible;
+            },
+            set: function (value) {
+                this._isVisible = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "zOrder", {
+            get: function () {
+                return this._zOrder;
+            },
+            set: function (value) {
+                this._zOrder = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "hierarchyDepth", {
+            get: function () {
+                return this._hierarchyDepth;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "renderGroup", {
+            get: function () {
+                return this._renderGroup;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "globalTransform", {
+            get: function () {
+                return this._globalTransform;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "invGlobalTransform", {
+            get: function () {
+                return this._invGlobalTransform;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "boundingInfo", {
+            get: function () {
+                if (this._boundingInfoDirty) {
+                    this._boundingInfo = this.levelBoundingInfo.clone();
+                    var bi = this._boundingInfo;
+                    var tps = new BABYLON.BoundingInfo2D();
+                    for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
+                        var curChild = _a[_i];
+                        var t = curChild.globalTransform.multiply(this.invGlobalTransform);
+                        curChild.boundingInfo.transformToRef(t, curChild.origin, tps);
+                        bi.unionToRef(tps, bi);
+                    }
+                    this._boundingInfoDirty = false;
+                }
+                return this._boundingInfo;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Prim2DBase.prototype.moveChild = function (child, previous) {
+            if (child.parent !== this) {
+                return false;
+            }
+            var prevOffset, nextOffset;
+            var childIndex = this._children.indexOf(child);
+            var prevIndex = previous ? this._children.indexOf(previous) : -1;
+            // Move to first position
+            if (!previous) {
+                prevOffset = 1;
+                nextOffset = this._children[1]._siblingDepthOffset;
+            }
+            else {
+                prevOffset = this._children[prevIndex]._siblingDepthOffset;
+                nextOffset = this._children[prevIndex + 1]._siblingDepthOffset;
+            }
+            child._siblingDepthOffset = (nextOffset - prevOffset) / 2;
+            this._children.splice(prevIndex + 1, 0, this._children.splice(childIndex, 1)[0]);
+        };
+        Prim2DBase.prototype.addChild = function (child) {
+            child._siblingDepthOffset = (this._children.length + 1) * this.owner.hierarchySiblingZDelta;
+            child._depthLevel = this._depthLevel + 1;
+            child._hierarchyDepthOffset = child._depthLevel * this.owner.hierarchyLevelZFactor;
+            this._children.push(child);
+        };
+        Prim2DBase.prototype.getActualZOffset = function () {
+            return this._zOrder || 1 - (this._siblingDepthOffset + this._hierarchyDepthOffset);
+        };
+        Prim2DBase.prototype.onPrimBecomesDirty = function () {
+            if (this._renderGroup) {
+                this._renderGroup._addPrimToDirtyList(this);
+            }
+        };
+        Prim2DBase.prototype.needPrepare = function () {
+            return this._modelDirty || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep);
+        };
+        Prim2DBase.prototype._prepareRender = function (context) {
+            this._prepareRenderPre(context);
+            this._prepareRenderPost(context);
+        };
+        Prim2DBase.prototype._prepareRenderPre = function (context) {
+        };
+        Prim2DBase.prototype._prepareRenderPost = function (context) {
+            // Don't recurse if it's a renderable group, the content will be processed by the group itself
+            if (this instanceof BABYLON.Group2D) {
+                var self = this;
+                if (self.isRenderableGroup) {
+                    return;
+                }
+            }
+            // Check if we need to recurse the prepare to children primitives
+            //  - must have children
+            //  - the global transform of this level have changed, or
+            //  - the visible state of primitive has changed
+            if (this._children.length > 0 && ((this._globalTransformProcessStep !== this._globalTransformStep) ||
+                this.checkPropertiesDirty(Prim2DBase.isVisibleProperty.flagId))) {
+                this._children.forEach(function (c) {
+                    // As usual stop the recursion if we meet a renderable group
+                    if (!(c instanceof BABYLON.Group2D && c.isRenderableGroup)) {
+                        c._prepareRender(context);
+                    }
+                });
+            }
+            // Finally reset the dirty flags as we've processed everything
+            this._modelDirty = false;
+            this._instanceDirtyFlags = 0;
+        };
+        Prim2DBase.CheckParent = function (parent) {
+            if (!parent) {
+                throw new Error("A Primitive needs a valid Parent, it can be any kind of Primitives based types, even the Canvas (with the exception that only Group2D can be direct child of a Canvas if the cache strategy used is TOPLEVELGROUPS)");
+            }
+        };
+        Prim2DBase.prototype.updateGlobalTransVisOf = function (list, recurse) {
+            for (var _i = 0; _i < list.length; _i++) {
+                var cur = list[_i];
+                cur.updateGlobalTransVis(recurse);
+            }
+        };
+        Prim2DBase.prototype.updateGlobalTransVis = function (recurse) {
+            // Check if the parent is synced
+            if (this._parent && this._parent._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
+                this._parent.updateGlobalTransVis(false);
+            }
+            // Check if we must update this prim
+            if (this === this.owner || this._globalTransformProcessStep !== this.owner._globalTransformProcessStep) {
+                this.isVisible = (!this._parent || this._parent.isVisible) && this.levelVisible;
+                // Detect if either the parent or this node changed
+                var tflags = Prim2DBase.positionProperty.flagId | Prim2DBase.rotationProperty.flagId | Prim2DBase.scaleProperty.flagId;
+                if ((this._parent && this._parent._globalTransformStep !== this._parentTransformStep) || this.checkPropertiesDirty(tflags)) {
+                    var rot = BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 0, 1), this._rotation);
+                    var local = BABYLON.Matrix.Compose(new BABYLON.Vector3(this._scale, this._scale, this._scale), rot, new BABYLON.Vector3(this._position.x, this._position.y, 0));
+                    this._globalTransform = this._parent ? local.multiply(this._parent._globalTransform) : local;
+                    this._invGlobalTransform = BABYLON.Matrix.Invert(this._globalTransform);
+                    this._globalTransformStep = this.owner._globalTransformProcessStep + 1;
+                    this._parentTransformStep = this._parent ? this._parent._globalTransformStep : 0;
+                    this.clearPropertiesDirty(tflags);
+                }
+                this._globalTransformProcessStep = this.owner._globalTransformProcessStep;
+            }
+            if (recurse) {
+                for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
+                    var child = _a[_i];
+                    // Stop the recursion if we meet a renderable group
+                    child.updateGlobalTransVis(!(child instanceof BABYLON.Group2D && child.isRenderableGroup));
+                }
+            }
+        };
+        Prim2DBase.PRIM2DBASE_PROPCOUNT = 10;
+        __decorate([
+            BABYLON.instanceLevelProperty(1, function (pi) { return Prim2DBase.positionProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "position", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(2, function (pi) { return Prim2DBase.rotationProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "rotation", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(3, function (pi) { return Prim2DBase.scaleProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "scale", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(4, function (pi) { return Prim2DBase.originProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "origin", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(5, function (pi) { return Prim2DBase.levelVisibleProperty = pi; })
+        ], Prim2DBase.prototype, "levelVisible", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(6, function (pi) { return Prim2DBase.isVisibleProperty = pi; })
+        ], Prim2DBase.prototype, "isVisible", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(7, function (pi) { return Prim2DBase.zOrderProperty = pi; })
+        ], Prim2DBase.prototype, "zOrder", null);
+        Prim2DBase = __decorate([
+            BABYLON.className("Prim2DBase")
+        ], Prim2DBase);
+        return Prim2DBase;
+    })(BABYLON.SmartPropertyPrim);
+    BABYLON.Prim2DBase = Prim2DBase;
+})(BABYLON || (BABYLON = {}));

+ 271 - 174
src/Canvas2d/babylon.rectangle2d.js

@@ -1,174 +1,271 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var Rectangle2DRenderCache = (function (_super) {
-        __extends(Rectangle2DRenderCache, _super);
-        function Rectangle2DRenderCache() {
-            _super.apply(this, arguments);
-        }
-        Rectangle2DRenderCache.prototype.render = function (instanceInfo, context) {
-            // Do nothing if the shader is still loading/preparing
-            if (!this.effect.isReady()) {
-                return false;
-            }
-            // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
-            if (!this.instancingAttributes) {
-                this.instancingAttributes = instanceInfo._classTreeInfo.classContent.getInstancingAttributeInfos(this.effect);
-            }
-            var engine = instanceInfo._owner.owner.engine;
-            engine.enableEffect(this.effect);
-            engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effect);
-            engine.updateAndBindInstancesBuffer(instanceInfo._instancesBuffer, null, this.instancingAttributes);
-            engine.draw(true, 0, Rectangle2D.roundSubdivisions * 4 * 3, instanceInfo._instancesData.usedElementCount);
-            engine.unBindInstancesBuffer(instanceInfo._instancesBuffer, this.instancingAttributes);
-            return true;
-        };
-        return Rectangle2DRenderCache;
-    }(BABYLON.ModelRenderCache));
-    BABYLON.Rectangle2DRenderCache = Rectangle2DRenderCache;
-    var Rectangle2DInstanceData = (function (_super) {
-        __extends(Rectangle2DInstanceData, _super);
-        function Rectangle2DInstanceData() {
-            _super.apply(this, arguments);
-        }
-        Object.defineProperty(Rectangle2DInstanceData.prototype, "properties", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        __decorate([
-            BABYLON.instanceData()
-        ], Rectangle2DInstanceData.prototype, "properties", null);
-        return Rectangle2DInstanceData;
-    }(BABYLON.InstanceDataBase));
-    BABYLON.Rectangle2DInstanceData = Rectangle2DInstanceData;
-    var Rectangle2D = (function (_super) {
-        __extends(Rectangle2D, _super);
-        function Rectangle2D() {
-            _super.apply(this, arguments);
-        }
-        Object.defineProperty(Rectangle2D.prototype, "size", {
-            get: function () {
-                return this._size;
-            },
-            set: function (value) {
-                this._size = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Rectangle2D.prototype, "notRounded", {
-            get: function () {
-                return this._notRounded;
-            },
-            set: function (value) {
-                this._notRounded = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Rectangle2D.prototype, "roundRadius", {
-            get: function () {
-                return this._roundRadius;
-            },
-            set: function (value) {
-                this._roundRadius = value;
-                this.notRounded = value === 0;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Rectangle2D.prototype.updateLevelBoundingInfo = function () {
-            this._levelBoundingInfo.radius = Math.sqrt(this.size.width * this.size.width + this.size.height * this.size.height);
-            this._levelBoundingInfo.extent = this.size.clone();
-        };
-        Rectangle2D.prototype.setupRectangle2D = function (owner, parent, id, position, size, roundRadius, fill, border) {
-            if (roundRadius === void 0) { roundRadius = 0; }
-            this.setupRenderablePrim2D(owner, parent, id, position, true, fill, border);
-            this.size = size;
-            this.notRounded = !roundRadius;
-            this.roundRadius = roundRadius;
-        };
-        Rectangle2D.Create = function (parent, id, x, y, width, height, fill, border) {
-            BABYLON.Prim2DBase.CheckParent(parent);
-            var rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), new BABYLON.Size(width, height), null);
-            rect.fill = fill || BABYLON.Canvas2D.GetSolidColorFillFromHex("#FFFFFFFF");
-            rect.border = border;
-            return rect;
-        };
-        Rectangle2D.CreateRounded = function (parent, id, x, y, width, height, roundRadius, fill, border) {
-            if (roundRadius === void 0) { roundRadius = 0; }
-            BABYLON.Prim2DBase.CheckParent(parent);
-            var rect = new Rectangle2D();
-            rect.setupRectangle2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), new BABYLON.Size(width, height), roundRadius);
-            rect.fill = fill || BABYLON.Canvas2D.GetSolidColorFillFromHex("#FFFFFFFF");
-            rect.border = border;
-            return rect;
-        };
-        Rectangle2D.prototype.createModelRenderCache = function () {
-            var renderCache = new Rectangle2DRenderCache();
-            var engine = this.owner.engine;
-            // Need to create vb/ib for the fill part?
-            if (this.fill) {
-                var vbSize = ((this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4) + 1;
-                var vb = new Float32Array(vbSize);
-                for (var i = 0; i < vbSize; i++) {
-                    vb[i] = i;
-                }
-                renderCache.fillVB = engine.createVertexBuffer(vb);
-                var triCount = vbSize - 1;
-                var ib = new Float32Array(triCount * 3);
-                for (var i = 0; i < triCount; i++) {
-                    ib[i * 3 + 0] = 0;
-                    ib[i * 3 + 1] = i + 1;
-                    ib[i * 3 + 2] = i + 2;
-                }
-                ib[triCount * 3 - 1] = 1;
-                renderCache.fillIB = engine.createIndexBuffer(ib);
-                renderCache.effect = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ["index", "zBias", "transformX", "transformY", "origin", "properties"], [], [], "");
-            }
-            return renderCache;
-        };
-        Rectangle2D.prototype.createInstanceData = function () {
-            return new Rectangle2DInstanceData();
-        };
-        Rectangle2D.prototype.refreshInstanceData = function () {
-            if (!_super.prototype.refreshInstanceData.call(this)) {
-                return false;
-            }
-            var d = this._instanceData;
-            var size = this.size;
-            d.properties = new BABYLON.Vector3(size.width, size.height, this.roundRadius || 0);
-            return true;
-        };
-        Rectangle2D.roundSubdivisions = 16;
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, function (pi) { return Rectangle2D.sizeProperty = pi; }, false, true)
-        ], Rectangle2D.prototype, "size", null);
-        __decorate([
-            BABYLON.modelLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, function (pi) { return Rectangle2D.notRoundedProperty = pi; })
-        ], Rectangle2D.prototype, "notRounded", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, function (pi) { return Rectangle2D.roundRadiusProperty = pi; })
-        ], Rectangle2D.prototype, "roundRadius", null);
-        Rectangle2D = __decorate([
-            BABYLON.className("Rectangle2D")
-        ], Rectangle2D);
-        return Rectangle2D;
-    }(BABYLON.RenderablePrim2D));
-    BABYLON.Rectangle2D = Rectangle2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.rectangle2d.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Rectangle2DRenderCache = (function (_super) {
+        __extends(Rectangle2DRenderCache, _super);
+        function Rectangle2DRenderCache(modelKey, isTransparent) {
+            _super.call(this, modelKey, isTransparent);
+        }
+        Rectangle2DRenderCache.prototype.render = function (instanceInfo, context) {
+            // Do nothing if the shader is still loading/preparing
+            if ((this.effectFill && !this.effectFill.isReady()) || (this.effectBorder && !this.effectBorder.isReady())) {
+                return false;
+            }
+            var engine = instanceInfo._owner.owner.engine;
+            var depthFunction = 0;
+            if (this.effectFill && this.effectBorder) {
+                depthFunction = engine.getDepthFunction();
+                engine.setDepthFunctionToLessOrEqual();
+            }
+            var cur;
+            if (this.isTransparent) {
+                cur = engine.getAlphaMode();
+                engine.setAlphaMode(BABYLON.Engine.ALPHA_COMBINE);
+            }
+            if (this.effectFill) {
+                var partIndex = instanceInfo._partIndexFromId.get(BABYLON.Shape2D.SHAPE2D_FILLPARTID.toString());
+                engine.enableEffect(this.effectFill);
+                engine.bindBuffers(this.fillVB, this.fillIB, [1], 4, this.effectFill);
+                var count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingFillAttributes) {
+                        // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
+                        this.instancingFillAttributes = this.loadInstancingAttributes(BABYLON.Shape2D.SHAPE2D_FILLPARTID, this.effectFill);
+                    }
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingFillAttributes);
+                    engine.draw(true, 0, this.fillIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingFillAttributes);
+                }
+                else {
+                    for (var i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectFill, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.fillIndicesCount);
+                    }
+                }
+            }
+            if (this.effectBorder) {
+                var partIndex = instanceInfo._partIndexFromId.get(BABYLON.Shape2D.SHAPE2D_BORDERPARTID.toString());
+                engine.enableEffect(this.effectBorder);
+                engine.bindBuffers(this.borderVB, this.borderIB, [1], 4, this.effectBorder);
+                var count = instanceInfo._instancesPartsData[partIndex].usedElementCount;
+                if (instanceInfo._owner.owner.supportInstancedArray) {
+                    if (!this.instancingBorderAttributes) {
+                        this.instancingBorderAttributes = this.loadInstancingAttributes(BABYLON.Shape2D.SHAPE2D_BORDERPARTID, this.effectBorder);
+                    }
+                    engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], null, this.instancingBorderAttributes);
+                    engine.draw(true, 0, this.borderIndicesCount, count);
+                    engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[partIndex], this.instancingBorderAttributes);
+                }
+                else {
+                    for (var i = 0; i < count; i++) {
+                        this.setupUniforms(this.effectBorder, partIndex, instanceInfo._instancesPartsData[partIndex], i);
+                        engine.draw(true, 0, this.borderIndicesCount);
+                    }
+                }
+            }
+            if (this.isTransparent) {
+                engine.setAlphaMode(cur);
+            }
+            if (this.effectFill && this.effectBorder) {
+                engine.setDepthFunction(depthFunction);
+            }
+            return true;
+        };
+        return Rectangle2DRenderCache;
+    })(BABYLON.ModelRenderCache);
+    BABYLON.Rectangle2DRenderCache = Rectangle2DRenderCache;
+    var Rectangle2DInstanceData = (function (_super) {
+        __extends(Rectangle2DInstanceData, _super);
+        function Rectangle2DInstanceData(partId) {
+            _super.call(this, partId, 1);
+        }
+        Object.defineProperty(Rectangle2DInstanceData.prototype, "properties", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.instanceData()
+        ], Rectangle2DInstanceData.prototype, "properties", null);
+        return Rectangle2DInstanceData;
+    })(BABYLON.Shape2DInstanceData);
+    BABYLON.Rectangle2DInstanceData = Rectangle2DInstanceData;
+    var Rectangle2D = (function (_super) {
+        __extends(Rectangle2D, _super);
+        function Rectangle2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Rectangle2D.prototype, "size", {
+            get: function () {
+                return this._size;
+            },
+            set: function (value) {
+                this._size = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Rectangle2D.prototype, "notRounded", {
+            get: function () {
+                return this._notRounded;
+            },
+            set: function (value) {
+                this._notRounded = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Rectangle2D.prototype, "roundRadius", {
+            get: function () {
+                return this._roundRadius;
+            },
+            set: function (value) {
+                this._roundRadius = value;
+                this.notRounded = value === 0;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Rectangle2D.prototype.updateLevelBoundingInfo = function () {
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo);
+        };
+        Rectangle2D.prototype.setupRectangle2D = function (owner, parent, id, position, size, roundRadius, fill, border, borderThickness) {
+            if (roundRadius === void 0) { roundRadius = 0; }
+            if (borderThickness === void 0) { borderThickness = 1; }
+            this.setupShape2D(owner, parent, id, position, true, fill, border, borderThickness);
+            this.size = size;
+            this.notRounded = !roundRadius;
+            this.roundRadius = roundRadius;
+        };
+        Rectangle2D.Create = function (parent, id, x, y, width, height, fill, border) {
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var rect = new Rectangle2D();
+            rect.setupRectangle2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), new BABYLON.Size(width, height), null);
+            rect.fill = fill;
+            rect.border = border;
+            return rect;
+        };
+        Rectangle2D.CreateRounded = function (parent, id, x, y, width, height, roundRadius, fill, border) {
+            if (roundRadius === void 0) { roundRadius = 0; }
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var rect = new Rectangle2D();
+            rect.setupRectangle2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), new BABYLON.Size(width, height), roundRadius);
+            rect.fill = fill || BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            rect.border = border;
+            return rect;
+        };
+        Rectangle2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
+            var renderCache = new Rectangle2DRenderCache(modelKey, isTransparent);
+            return renderCache;
+        };
+        Rectangle2D.prototype.setupModelRenderCache = function (modelRenderCache) {
+            var renderCache = modelRenderCache;
+            var engine = this.owner.engine;
+            // Need to create webgl resources for fill part?
+            if (this.fill) {
+                var vbSize = ((this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4) + 1;
+                var vb = new Float32Array(vbSize);
+                for (var i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.fillVB = engine.createVertexBuffer(vb);
+                var triCount = vbSize - 1;
+                var ib = new Float32Array(triCount * 3);
+                for (var i = 0; i < triCount; i++) {
+                    ib[i * 3 + 0] = 0;
+                    ib[i * 3 + 2] = i + 1;
+                    ib[i * 3 + 1] = i + 2;
+                }
+                ib[triCount * 3 - 2] = 1;
+                renderCache.fillIB = engine.createIndexBuffer(ib);
+                renderCache.fillIndicesCount = triCount * 3;
+                var ei = this.getDataPartEffectInfo(BABYLON.Shape2D.SHAPE2D_FILLPARTID, ["index"]);
+                renderCache.effectFill = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, ei.uniforms, [], ei.defines, null, function (e) {
+                    //                    renderCache.setupUniformsLocation(e, ei.uniforms, Shape2D.SHAPE2D_FILLPARTID);
+                });
+            }
+            // Need to create webgl resource for border part?
+            if (this.border) {
+                var vbSize = (this.notRounded ? 1 : Rectangle2D.roundSubdivisions) * 4 * 2;
+                var vb = new Float32Array(vbSize);
+                for (var i = 0; i < vbSize; i++) {
+                    vb[i] = i;
+                }
+                renderCache.borderVB = engine.createVertexBuffer(vb);
+                var triCount = vbSize;
+                var rs = triCount / 2;
+                var ib = new Float32Array(triCount * 3);
+                for (var i = 0; i < rs; i++) {
+                    var r0 = i;
+                    var r1 = (i + 1) % rs;
+                    ib[i * 6 + 0] = rs + r1;
+                    ib[i * 6 + 1] = rs + r0;
+                    ib[i * 6 + 2] = r0;
+                    ib[i * 6 + 3] = r1;
+                    ib[i * 6 + 4] = rs + r1;
+                    ib[i * 6 + 5] = r0;
+                }
+                renderCache.borderIB = engine.createIndexBuffer(ib);
+                renderCache.borderIndicesCount = triCount * 3;
+                var ei = this.getDataPartEffectInfo(BABYLON.Shape2D.SHAPE2D_BORDERPARTID, ["index"]);
+                renderCache.effectBorder = engine.createEffect({ vertex: "rect2d", fragment: "rect2d" }, ei.attributes, ei.uniforms, [], ei.defines, null, function (e) {
+                    //                    renderCache.setupUniformsLocation(e, ei.uniforms, Shape2D.SHAPE2D_BORDERPARTID);
+                });
+            }
+            return renderCache;
+        };
+        Rectangle2D.prototype.createInstanceDataParts = function () {
+            var res = new Array();
+            if (this.border) {
+                res.push(new Rectangle2DInstanceData(BABYLON.Shape2D.SHAPE2D_BORDERPARTID));
+            }
+            if (this.fill) {
+                res.push(new Rectangle2DInstanceData(BABYLON.Shape2D.SHAPE2D_FILLPARTID));
+            }
+            return res;
+        };
+        Rectangle2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
+                return false;
+            }
+            if (part.id === BABYLON.Shape2D.SHAPE2D_BORDERPARTID) {
+                var d = part;
+                var size = this.size;
+                d.properties = new BABYLON.Vector3(size.width, size.height, this.roundRadius || 0);
+            }
+            else if (part.id === BABYLON.Shape2D.SHAPE2D_FILLPARTID) {
+                var d = part;
+                var size = this.size;
+                d.properties = new BABYLON.Vector3(size.width, size.height, this.roundRadius || 0);
+            }
+            return true;
+        };
+        Rectangle2D.roundSubdivisions = 16;
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 1, function (pi) { return Rectangle2D.sizeProperty = pi; }, false, true)
+        ], Rectangle2D.prototype, "size", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 2, function (pi) { return Rectangle2D.notRoundedProperty = pi; })
+        ], Rectangle2D.prototype, "notRounded", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 3, function (pi) { return Rectangle2D.roundRadiusProperty = pi; })
+        ], Rectangle2D.prototype, "roundRadius", null);
+        Rectangle2D = __decorate([
+            BABYLON.className("Rectangle2D")
+        ], Rectangle2D);
+        return Rectangle2D;
+    })(BABYLON.Shape2D);
+    BABYLON.Rectangle2D = Rectangle2D;
+})(BABYLON || (BABYLON = {}));

+ 498 - 372
src/Canvas2d/babylon.renderablePrim2d.js

@@ -1,372 +1,498 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var InstanceClassInfo = (function () {
-        function InstanceClassInfo(base) {
-            this._baseInfo = base;
-            this._nextOffset = 0;
-            this._attributes = new Array();
-        }
-        InstanceClassInfo.prototype.mapProperty = function (propInfo) {
-            propInfo.instanceOffset = (this._baseInfo ? this._baseInfo._nextOffset : 0) + this._nextOffset;
-            this._nextOffset += (propInfo.size / 4);
-            this._attributes.push(propInfo);
-        };
-        InstanceClassInfo.prototype.getInstancingAttributeInfos = function (effect) {
-            var res = new Array();
-            var curInfo = this;
-            while (curInfo) {
-                for (var _i = 0, _a = curInfo._attributes; _i < _a.length; _i++) {
-                    var attrib = _a[_i];
-                    var index = effect.getAttributeLocationByName(attrib.attributeName);
-                    var iai = new BABYLON.InstancingAttributeInfo();
-                    iai.index = index;
-                    iai.attributeSize = attrib.size / 4; // attrib.size is in byte and we need to store in "component" (i.e float is 1, vec3 is 3)
-                    iai.offset = attrib.instanceOffset * 4; // attrub.instanceOffset is in float, iai.offset must be in bytes
-                    res.push(iai);
-                }
-                curInfo = curInfo._baseInfo;
-            }
-            return res;
-        };
-        return InstanceClassInfo;
-    }());
-    BABYLON.InstanceClassInfo = InstanceClassInfo;
-    var InstancePropInfo = (function () {
-        function InstancePropInfo() {
-        }
-        InstancePropInfo.prototype.setSize = function (val) {
-            if (val instanceof BABYLON.Vector2) {
-                this.size = 8;
-                this.dataType = 0 /* Vector2 */;
-                return;
-            }
-            if (val instanceof BABYLON.Vector3) {
-                this.size = 12;
-                this.dataType = 1 /* Vector3 */;
-                return;
-            }
-            if (val instanceof BABYLON.Vector4) {
-                this.size = 16;
-                this.dataType = 2 /* Vector4 */;
-                return;
-            }
-            if (val instanceof BABYLON.Matrix) {
-                throw new Error("Matrix type is not supported by WebGL Instance Buffer, you have to use four Vector4 properties instead");
-            }
-            if (typeof (val) === "number") {
-                this.size = 4;
-                this.dataType = 4 /* float */;
-                return;
-            }
-            if (val instanceof BABYLON.Color3) {
-                this.size = 12;
-                this.dataType = 5 /* Color3 */;
-                return;
-            }
-            if (val instanceof BABYLON.Color4) {
-                this.size = 16;
-                this.dataType = 6 /* Color4 */;
-                return;
-            }
-        };
-        InstancePropInfo.prototype.writeData = function (array, offset, val) {
-            switch (this.dataType) {
-                case 0 /* Vector2 */:
-                    {
-                        var v = val;
-                        array[offset + 0] = v.x;
-                        array[offset + 1] = v.y;
-                        break;
-                    }
-                case 1 /* Vector3 */:
-                    {
-                        var v = val;
-                        array[offset + 0] = v.x;
-                        array[offset + 1] = v.y;
-                        array[offset + 2] = v.z;
-                        break;
-                    }
-                case 2 /* Vector4 */:
-                    {
-                        var v = val;
-                        array[offset + 0] = v.x;
-                        array[offset + 1] = v.y;
-                        array[offset + 2] = v.z;
-                        array[offset + 3] = v.w;
-                        break;
-                    }
-                case 5 /* Color3 */:
-                    {
-                        var v = val;
-                        array[offset + 0] = v.r;
-                        array[offset + 1] = v.g;
-                        array[offset + 2] = v.b;
-                        break;
-                    }
-                case 6 /* Color4 */:
-                    {
-                        var v = val;
-                        array[offset + 0] = v.r;
-                        array[offset + 1] = v.g;
-                        array[offset + 2] = v.b;
-                        array[offset + 3] = v.a;
-                        break;
-                    }
-                case 4 /* float */:
-                    {
-                        var v = val;
-                        array[offset] = v;
-                        break;
-                    }
-                case 3 /* Matrix */:
-                    {
-                        var v = val;
-                        for (var i = 0; i < 16; i++) {
-                            array[offset + i] = v.m[i];
-                        }
-                        break;
-                    }
-            }
-        };
-        return InstancePropInfo;
-    }());
-    BABYLON.InstancePropInfo = InstancePropInfo;
-    function instanceData(name) {
-        return function (target, propName, descriptor) {
-            var dic = BABYLON.ClassTreeInfo.getOrRegister(target, function (base) { return new InstanceClassInfo(base); });
-            var node = dic.getLevelOf(target);
-            name = name || propName;
-            var info = node.levelContent.get(name);
-            if (info) {
-                throw new Error("The ID " + name + " is already taken by another instance data");
-            }
-            info = new InstancePropInfo();
-            info.attributeName = name;
-            node.levelContent.add(name, info);
-            descriptor.get = function () {
-                return null;
-            };
-            descriptor.set = function (val) {
-                if (!info.size) {
-                    info.setSize(val);
-                    node.classContent.mapProperty(info);
-                }
-                var obj = this;
-                if (obj._dataBuffer) {
-                    info.writeData(obj._dataBuffer.buffer, obj._dataElement.offset + info.instanceOffset, val);
-                }
-            };
-        };
-    }
-    BABYLON.instanceData = instanceData;
-    var InstanceDataBase = (function () {
-        function InstanceDataBase() {
-        }
-        Object.defineProperty(InstanceDataBase.prototype, "zBias", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(InstanceDataBase.prototype, "transformX", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(InstanceDataBase.prototype, "transformY", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(InstanceDataBase.prototype, "origin", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        InstanceDataBase.prototype.getClassTreeInfo = function () {
-            if (!this._typeInfo) {
-                this._typeInfo = BABYLON.ClassTreeInfo.get(Object.getPrototypeOf(this));
-            }
-            return this._typeInfo;
-        };
-        __decorate([
-            instanceData()
-        ], InstanceDataBase.prototype, "zBias", null);
-        __decorate([
-            instanceData()
-        ], InstanceDataBase.prototype, "transformX", null);
-        __decorate([
-            instanceData()
-        ], InstanceDataBase.prototype, "transformY", null);
-        __decorate([
-            instanceData()
-        ], InstanceDataBase.prototype, "origin", null);
-        return InstanceDataBase;
-    }());
-    BABYLON.InstanceDataBase = InstanceDataBase;
-    var RenderablePrim2D = (function (_super) {
-        __extends(RenderablePrim2D, _super);
-        function RenderablePrim2D() {
-            _super.apply(this, arguments);
-        }
-        RenderablePrim2D.prototype.setupRenderablePrim2D = function (owner, parent, id, position, isVisible, fill, border) {
-            this.setupPrim2DBase(owner, parent, id, position);
-            this._isTransparent = false;
-        };
-        Object.defineProperty(RenderablePrim2D.prototype, "border", {
-            get: function () {
-                return this._border;
-            },
-            set: function (value) {
-                if (value === this._border) {
-                    return;
-                }
-                this._border = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(RenderablePrim2D.prototype, "fill", {
-            get: function () {
-                return this._fill;
-            },
-            set: function (value) {
-                if (value === this._fill) {
-                    return;
-                }
-                this._fill = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        RenderablePrim2D.prototype._prepareRenderPre = function (context) {
-            var _this = this;
-            _super.prototype._prepareRenderPre.call(this, context);
-            // If the model changed and we have already an instance, we must remove this instance from the obsolete model
-            if (this._modelDirty && this._modelRenderInstanceID) {
-                this._modelRenderCache.removeInstanceData(this._modelRenderInstanceID);
-                this._modelRenderInstanceID = null;
-            }
-            // Need to create the model?
-            if (!this._modelRenderCache || this._modelDirty) {
-                this._modelRenderCache = BABYLON.SmartPropertyPrim.GetOrAddModelCache(this.modelKey, function (key) { return _this.createModelRenderCache(); });
-                this._modelDirty = false;
-            }
-            // Need to create the instance?
-            var gii;
-            var newInstance = false;
-            if (!this._modelRenderInstanceID) {
-                newInstance = true;
-                var id = this.createInstanceData();
-                this._instanceData = id;
-                var cti_1 = id.getClassTreeInfo();
-                if (!cti_1.classContent.instanceDataStride) {
-                    // Make sure the instance is visible other the properties won't be set and their size/offset wont be computed
-                    var curVisible = this.isVisible;
-                    this.isVisible = true;
-                    // We manually trigger refreshInstanceData for the only sake of evaluating each isntance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
-                    this.refreshInstanceData();
-                    this.isVisible = curVisible;
-                    var size = 0;
-                    cti_1.fullContent.forEach(function (k, v) {
-                        if (!v.size) {
-                            console.log("ERROR: Couldn't detect the size of the Property " + v.attributeName + " from type " + BABYLON.Tools.getClassName(cti_1.type) + ". Property is ignored.");
-                        }
-                        else {
-                            size += v.size;
-                        }
-                    });
-                    cti_1.classContent.instanceDataStride = size;
-                }
-                gii = this.renderGroup.groupRenderInfo.getOrAddWithFactory(this.modelKey, function (k) { return new BABYLON.GroupInstanceInfo(_this.renderGroup, cti_1, _this._modelRenderCache); });
-                if (!gii._instancesData) {
-                    // instanceDataStride's unit is byte but DynamicFloatArray is float32, so div by four to get the correct number
-                    gii._instancesData = new BABYLON.DynamicFloatArray(cti_1.classContent.instanceDataStride / 4, 50);
-                }
-                id._dataBuffer = gii._instancesData;
-                id._dataElement = id._dataBuffer.allocElement();
-                this._modelRenderInstanceID = this._modelRenderCache.addInstanceData(this._instanceData);
-            }
-            if (context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformPreviousStep !== this._globalTransformStep)) {
-                // Will return false if the instance should not be rendered (not visible or other any reasons)
-                if (!this.refreshInstanceData()) {
-                    // Free the data element
-                    if (this._instanceData._dataElement) {
-                        this._instanceData._dataBuffer.freeElement(this._instanceData._dataElement);
-                        this._instanceData._dataElement = null;
-                    }
-                }
-                this._instanceDirtyFlags = 0;
-                if (!gii) {
-                    gii = this.renderGroup.groupRenderInfo.get(this.modelKey);
-                }
-                gii._dirtyInstancesData = true;
-            }
-        };
-        RenderablePrim2D.prototype.createModelRenderCache = function () {
-            return null;
-        };
-        RenderablePrim2D.prototype.createInstanceData = function () {
-            return null;
-        };
-        RenderablePrim2D.prototype.refreshInstanceData = function () {
-            var d = this._instanceData;
-            if (!this.isVisible) {
-                return false;
-            }
-            d.isVisible = this.isVisible;
-            var t = this.renderGroup.invGlobalTransform.multiply(this._globalTransform);
-            var size = this.renderGroup.viewportSize;
-            var zBias = this.getActualZOffset();
-            // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner
-            // Current coordinates are expressed in renderGroup coordinates ([0, renderGroup.actualSize.width|height]) with 0,0 being at the left/top corner
-            // RenderGroup Width and Height are multiplied by zBias because the VertexShader will multiply X and Y by W, which is 1/zBias. Has we divide our coordinate by these Width/Height, we will also divide by the zBias to compensate the operation made by the VertexShader.
-            // So for X: 
-            //  - tx.x = value * 2 / width: is to switch from [0, renderGroup.width] to [0, 2]
-            //  - tx.w = (value * 2 / width) - 1: w stores the translation in renderGroup coordinates so (value * 2 / width) to switch to a clip space translation value. - 1 is to offset the overall [0;2] to [-1;1]. Don't forget it's -(1/zBias) and not -1 because everything need to be scaled by 1/zBias.
-            // Same thing for Y, except the "* -2" instead of "* 2" to switch the origin from top to bottom (has expected by the clip space)
-            var w = size.width * zBias;
-            var h = size.height * zBias;
-            var invZBias = 1 / zBias;
-            var tx = new BABYLON.Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, t.m[8], (t.m[12] * 2 / w) - (invZBias));
-            var ty = new BABYLON.Vector4(t.m[1] * -2 / h, t.m[5] * -2 / h, t.m[9], ((t.m[13] * 2 / h) - (invZBias)) * -1);
-            d.transformX = tx;
-            d.transformY = ty;
-            d.origin = this.origin;
-            // Stores zBias and it's inverse value because that's needed to compute the clip space W coordinate (which is 1/Z, so 1/zBias)
-            d.zBias = new BABYLON.Vector2(zBias, invZBias);
-            return true;
-        };
-        RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT = BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 10;
-        __decorate([
-            BABYLON.modelLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return RenderablePrim2D.borderProperty = pi; }, true)
-        ], RenderablePrim2D.prototype, "border", null);
-        __decorate([
-            BABYLON.modelLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 2, function (pi) { return RenderablePrim2D.fillProperty = pi; }, true)
-        ], RenderablePrim2D.prototype, "fill", null);
-        RenderablePrim2D = __decorate([
-            BABYLON.className("RenderablePrim2D")
-        ], RenderablePrim2D);
-        return RenderablePrim2D;
-    }(BABYLON.Prim2DBase));
-    BABYLON.RenderablePrim2D = RenderablePrim2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.renderablePrim2d.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var InstanceClassInfo = (function () {
+        function InstanceClassInfo(base) {
+            this._baseInfo = base;
+            this._nextOffset = new BABYLON.StringDictionary();
+            this._attributes = new Array();
+        }
+        InstanceClassInfo.prototype.mapProperty = function (propInfo, push) {
+            var curOff = this._nextOffset.getOrAdd(InstanceClassInfo._CurCategories, 0);
+            propInfo.instanceOffset.add(InstanceClassInfo._CurCategories, this._getBaseOffset(InstanceClassInfo._CurCategories) + curOff);
+            //console.log(`[${InstanceClassInfo._CurCategories}] New PropInfo. Category: ${propInfo.category}, Name: ${propInfo.attributeName}, Offset: ${propInfo.instanceOffset.get(InstanceClassInfo._CurCategories)}, Size: ${propInfo.size / 4}`);
+            this._nextOffset.set(InstanceClassInfo._CurCategories, curOff + (propInfo.size / 4));
+            if (push) {
+                this._attributes.push(propInfo);
+            }
+        };
+        InstanceClassInfo.prototype.getInstancingAttributeInfos = function (effect, categories) {
+            var catInline = categories.join(";");
+            var res = new Array();
+            var curInfo = this;
+            while (curInfo) {
+                for (var _i = 0, _a = curInfo._attributes; _i < _a.length; _i++) {
+                    var attrib = _a[_i];
+                    // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
+                    if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
+                        var index = effect.getAttributeLocationByName(attrib.attributeName);
+                        var iai = new BABYLON.InstancingAttributeInfo();
+                        iai.index = index;
+                        iai.attributeSize = attrib.size / 4; // attrib.size is in byte and we need to store in "component" (i.e float is 1, vec3 is 3)
+                        iai.offset = attrib.instanceOffset.get(catInline) * 4; // attrib.instanceOffset is in float, iai.offset must be in bytes
+                        iai.attributeName = attrib.attributeName;
+                        res.push(iai);
+                    }
+                }
+                curInfo = curInfo._baseInfo;
+            }
+            return res;
+        };
+        InstanceClassInfo.prototype.getShaderAttributes = function (categories) {
+            var res = new Array();
+            var curInfo = this;
+            while (curInfo) {
+                for (var _i = 0, _a = curInfo._attributes; _i < _a.length; _i++) {
+                    var attrib = _a[_i];
+                    // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
+                    if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
+                        res.push(attrib.attributeName);
+                    }
+                }
+                curInfo = curInfo._baseInfo;
+            }
+            return res;
+        };
+        InstanceClassInfo.prototype._getBaseOffset = function (categories) {
+            var curOffset = 0;
+            var curBase = this._baseInfo;
+            while (curBase) {
+                curOffset += curBase._nextOffset.getOrAdd(categories, 0);
+                curBase = curBase._baseInfo;
+            }
+            return curOffset;
+        };
+        return InstanceClassInfo;
+    })();
+    BABYLON.InstanceClassInfo = InstanceClassInfo;
+    var InstancePropInfo = (function () {
+        //uniformLocation: WebGLUniformLocation;
+        function InstancePropInfo() {
+            this.instanceOffset = new BABYLON.StringDictionary();
+        }
+        InstancePropInfo.prototype.setSize = function (val) {
+            if (val instanceof BABYLON.Vector2) {
+                this.size = 8;
+                this.dataType = 0 /* Vector2 */;
+                return;
+            }
+            if (val instanceof BABYLON.Vector3) {
+                this.size = 12;
+                this.dataType = 1 /* Vector3 */;
+                return;
+            }
+            if (val instanceof BABYLON.Vector4) {
+                this.size = 16;
+                this.dataType = 2 /* Vector4 */;
+                return;
+            }
+            if (val instanceof BABYLON.Matrix) {
+                throw new Error("Matrix type is not supported by WebGL Instance Buffer, you have to use four Vector4 properties instead");
+            }
+            if (typeof (val) === "number") {
+                this.size = 4;
+                this.dataType = 4 /* float */;
+                return;
+            }
+            if (val instanceof BABYLON.Color3) {
+                this.size = 12;
+                this.dataType = 5 /* Color3 */;
+                return;
+            }
+            if (val instanceof BABYLON.Color4) {
+                this.size = 16;
+                this.dataType = 6 /* Color4 */;
+                return;
+            }
+            return;
+        };
+        InstancePropInfo.prototype.writeData = function (array, offset, val) {
+            switch (this.dataType) {
+                case 0 /* Vector2 */:
+                    {
+                        var v = val;
+                        array[offset + 0] = v.x;
+                        array[offset + 1] = v.y;
+                        break;
+                    }
+                case 1 /* Vector3 */:
+                    {
+                        var v = val;
+                        array[offset + 0] = v.x;
+                        array[offset + 1] = v.y;
+                        array[offset + 2] = v.z;
+                        break;
+                    }
+                case 2 /* Vector4 */:
+                    {
+                        var v = val;
+                        array[offset + 0] = v.x;
+                        array[offset + 1] = v.y;
+                        array[offset + 2] = v.z;
+                        array[offset + 3] = v.w;
+                        break;
+                    }
+                case 5 /* Color3 */:
+                    {
+                        var v = val;
+                        array[offset + 0] = v.r;
+                        array[offset + 1] = v.g;
+                        array[offset + 2] = v.b;
+                        break;
+                    }
+                case 6 /* Color4 */:
+                    {
+                        var v = val;
+                        array[offset + 0] = v.r;
+                        array[offset + 1] = v.g;
+                        array[offset + 2] = v.b;
+                        array[offset + 3] = v.a;
+                        break;
+                    }
+                case 4 /* float */:
+                    {
+                        var v = val;
+                        array[offset] = v;
+                        break;
+                    }
+                case 3 /* Matrix */:
+                    {
+                        var v = val;
+                        for (var i = 0; i < 16; i++) {
+                            array[offset + i] = v.m[i];
+                        }
+                        break;
+                    }
+            }
+        };
+        return InstancePropInfo;
+    })();
+    BABYLON.InstancePropInfo = InstancePropInfo;
+    function instanceData(category, shaderAttributeName) {
+        return function (target, propName, descriptor) {
+            var dic = BABYLON.ClassTreeInfo.getOrRegister(target, function (base) { return new InstanceClassInfo(base); });
+            var node = dic.getLevelOf(target);
+            var instanceDataName = propName;
+            shaderAttributeName = shaderAttributeName || instanceDataName;
+            var info = node.levelContent.get(instanceDataName);
+            if (info) {
+                throw new Error("The ID " + instanceDataName + " is already taken by another instance data");
+            }
+            info = new InstancePropInfo();
+            info.attributeName = shaderAttributeName;
+            info.category = category || null;
+            node.levelContent.add(instanceDataName, info);
+            descriptor.get = function () {
+                return null;
+            };
+            descriptor.set = function (val) {
+                if (!info.size) {
+                    info.setSize(val);
+                    node.classContent.mapProperty(info, true);
+                }
+                else if (!info.instanceOffset.contains(InstanceClassInfo._CurCategories)) {
+                    node.classContent.mapProperty(info, false);
+                }
+                var obj = this;
+                if (obj.dataBuffer && obj.dataElements) {
+                    var offset = obj.dataElements[obj.curElement].offset + info.instanceOffset.get(InstanceClassInfo._CurCategories);
+                    info.writeData(obj.dataBuffer.buffer, offset, val);
+                }
+            };
+        };
+    }
+    BABYLON.instanceData = instanceData;
+    var InstanceDataBase = (function () {
+        function InstanceDataBase(partId, dataElementCount) {
+            this.id = partId;
+            this.curElement = 0;
+            this.dataElementCount = dataElementCount;
+        }
+        Object.defineProperty(InstanceDataBase.prototype, "zBias", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(InstanceDataBase.prototype, "transformX", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(InstanceDataBase.prototype, "transformY", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(InstanceDataBase.prototype, "origin", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        InstanceDataBase.prototype.getClassTreeInfo = function () {
+            if (!this.typeInfo) {
+                this.typeInfo = BABYLON.ClassTreeInfo.get(Object.getPrototypeOf(this));
+            }
+            return this.typeInfo;
+        };
+        InstanceDataBase.prototype.allocElements = function () {
+            var res = new Array(this.dataElementCount);
+            for (var i = 0; i < this.dataElementCount; i++) {
+                res[i] = this.dataBuffer.allocElement();
+            }
+            this.dataElements = res;
+        };
+        InstanceDataBase.prototype.freeElements = function () {
+            for (var _i = 0, _a = this.dataElements; _i < _a.length; _i++) {
+                var ei = _a[_i];
+                this.dataBuffer.freeElement(ei);
+            }
+            this.dataElements = null;
+        };
+        __decorate([
+            instanceData()
+        ], InstanceDataBase.prototype, "zBias", null);
+        __decorate([
+            instanceData()
+        ], InstanceDataBase.prototype, "transformX", null);
+        __decorate([
+            instanceData()
+        ], InstanceDataBase.prototype, "transformY", null);
+        __decorate([
+            instanceData()
+        ], InstanceDataBase.prototype, "origin", null);
+        return InstanceDataBase;
+    })();
+    BABYLON.InstanceDataBase = InstanceDataBase;
+    var RenderablePrim2D = (function (_super) {
+        __extends(RenderablePrim2D, _super);
+        function RenderablePrim2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(RenderablePrim2D.prototype, "isTransparent", {
+            get: function () {
+                return this._isTransparent;
+            },
+            set: function (value) {
+                this._isTransparent = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        RenderablePrim2D.prototype.setupRenderablePrim2D = function (owner, parent, id, position, isVisible) {
+            this.setupPrim2DBase(owner, parent, id, position);
+            this._isTransparent = false;
+        };
+        RenderablePrim2D.prototype._prepareRenderPre = function (context) {
+            var _this = this;
+            _super.prototype._prepareRenderPre.call(this, context);
+            // If the model changed and we have already an instance, we must remove this instance from the obsolete model
+            if (this._modelDirty && this._modelRenderInstanceID) {
+                this._modelRenderCache.removeInstanceData(this._modelRenderInstanceID);
+                this._modelRenderInstanceID = null;
+            }
+            // Need to create the model?
+            var setupModelRenderCache = false;
+            if (!this._modelRenderCache || this._modelDirty) {
+                this._modelRenderCache = BABYLON.SmartPropertyPrim.GetOrAddModelCache(this.modelKey, function (key) {
+                    var mrc = _this.createModelRenderCache(key, _this.isTransparent);
+                    setupModelRenderCache = true;
+                    return mrc;
+                });
+                this._modelDirty = false;
+            }
+            // Need to create the instance?
+            var gii;
+            var newInstance = false;
+            if (!this._modelRenderInstanceID) {
+                newInstance = true;
+                var parts = this.createInstanceDataParts();
+                this._instanceDataParts = parts;
+                if (!this._modelRenderCache._partsDataStride) {
+                    var ctiArray = new Array();
+                    var dataStrides = new Array();
+                    var usedCatList = new Array();
+                    var partIdList = new Array();
+                    var joinedUsedCatList = new Array();
+                    for (var _i = 0; _i < parts.length; _i++) {
+                        var dataPart = parts[_i];
+                        var cat = this.getUsedShaderCategories(dataPart);
+                        var cti = dataPart.getClassTreeInfo();
+                        // Make sure the instance is visible other the properties won't be set and their size/offset wont be computed
+                        var curVisible = this.isVisible;
+                        this.isVisible = true;
+                        // We manually trigger refreshInstanceData for the only sake of evaluating each isntance property size and offset in the instance data, this can only be made at runtime. Once it's done we have all the information to create the instance data buffer.
+                        //console.log("Build Prop Layout for " + Tools.getClassName(this._instanceDataParts[0]));
+                        var joinCat = cat.join(";");
+                        joinedUsedCatList.push(joinCat);
+                        InstanceClassInfo._CurCategories = joinCat;
+                        this.refreshInstanceDataPart(dataPart);
+                        this.isVisible = curVisible;
+                        var size = 0;
+                        for (var index = 0; index < cti.fullContent.count; index++) {
+                            var v = cti.fullContent[index];
+                            if (!v.category || cat.indexOf(v.category) !== -1) {
+                                if (!v.size) {
+                                    console.log("ERROR: Couldn't detect the size of the Property " + v.attributeName + " from type " + BABYLON.Tools.getClassName(cti.type) + ". Property is ignored.");
+                                }
+                                else {
+                                    size += v.size;
+                                }
+                            }
+                        }
+                        dataStrides.push(size);
+                        usedCatList.push(cat);
+                        ctiArray.push(cti);
+                        partIdList.push(dataPart.id);
+                    }
+                    this._modelRenderCache._partsDataStride = dataStrides;
+                    this._modelRenderCache._partsUsedCategories = usedCatList;
+                    this._modelRenderCache._partsJoinedUsedCategories = joinedUsedCatList;
+                    this._modelRenderCache._partsClassInfo = ctiArray;
+                    this._modelRenderCache._partIdList = partIdList;
+                }
+                gii = this.renderGroup.groupRenderInfo.getOrAddWithFactory(this.modelKey, function (k) { return new BABYLON.GroupInstanceInfo(_this.renderGroup, _this._modelRenderCache); });
+                // First time init of the GroupInstanceInfo
+                if (gii._instancesPartsData.length === 0) {
+                    for (var j = 0; j < this._modelRenderCache._partsDataStride.length; j++) {
+                        var stride = this._modelRenderCache._partsDataStride[j];
+                        gii._instancesPartsData.push(new BABYLON.DynamicFloatArray(stride / 4, 50));
+                        gii._partIndexFromId.add(this._modelRenderCache._partIdList[j].toString(), j);
+                        for (var _a = 0, _b = this._instanceDataParts; _a < _b.length; _a++) {
+                            var part = _b[_a];
+                            gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())] = this.getUsedShaderCategories(part).join(";");
+                        }
+                    }
+                }
+                for (var i = 0; i < parts.length; i++) {
+                    var part = parts[i];
+                    part.dataBuffer = gii._instancesPartsData[i];
+                    part.allocElements();
+                }
+                this._modelRenderInstanceID = this._modelRenderCache.addInstanceDataParts(this._instanceDataParts);
+            }
+            if (setupModelRenderCache) {
+                this.setupModelRenderCache(this._modelRenderCache);
+            }
+            if (context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep)) {
+                if (!gii) {
+                    gii = this.renderGroup.groupRenderInfo.get(this.modelKey);
+                }
+                for (var _c = 0, _d = this._instanceDataParts; _c < _d.length; _c++) {
+                    var part = _d[_c];
+                    var cat = this.getUsedShaderCategories(part);
+                    InstanceClassInfo._CurCategories = gii._instancesPartsUsedShaderCategories[gii._partIndexFromId.get(part.id.toString())];
+                    // Will return false if the instance should not be rendered (not visible or other any reasons)
+                    if (!this.refreshInstanceDataPart(part)) {
+                        // Free the data element
+                        if (part.dataElements) {
+                            part.freeElements();
+                        }
+                    }
+                }
+                this._instanceDirtyFlags = 0;
+                gii._dirtyInstancesData = true;
+            }
+        };
+        RenderablePrim2D.prototype.getDataPartEffectInfo = function (dataPartId, vertexBufferAttributes) {
+            var dataPart = BABYLON.Tools.first(this._instanceDataParts, function (i) { return i.id === dataPartId; });
+            if (!dataPart) {
+                return null;
+            }
+            var instancedArray = this.owner.supportInstancedArray;
+            var cti = dataPart.getClassTreeInfo();
+            var categories = this.getUsedShaderCategories(dataPart);
+            var att = cti.classContent.getShaderAttributes(categories);
+            var defines = "";
+            categories.forEach(function (c) { defines += "#define " + c + "\n"; });
+            if (instancedArray) {
+                defines += "#define Instanced\n";
+            }
+            return { attributes: instancedArray ? vertexBufferAttributes.concat(att) : vertexBufferAttributes, uniforms: instancedArray ? [] : att, defines: defines };
+        };
+        Object.defineProperty(RenderablePrim2D.prototype, "modelRenderCache", {
+            get: function () {
+                return this._modelRenderCache;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        RenderablePrim2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
+            return null;
+        };
+        RenderablePrim2D.prototype.setupModelRenderCache = function (modelRenderCache) {
+        };
+        RenderablePrim2D.prototype.createInstanceDataParts = function () {
+            return null;
+        };
+        RenderablePrim2D.prototype.getUsedShaderCategories = function (dataPart) {
+            return [];
+        };
+        RenderablePrim2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!this.isVisible) {
+                return false;
+            }
+            part.isVisible = this.isVisible;
+            // Which means, if there's only one data element, we're update it from this method, otherwise it is the responsability of the derived class to call updateInstanceDataPart as many times as needed, properly (look at Text2D's implementation for more information)
+            if (part.dataElementCount === 1) {
+                this.updateInstanceDataPart(part);
+            }
+            return true;
+        };
+        RenderablePrim2D.prototype.updateInstanceDataPart = function (part, positionOffset) {
+            if (positionOffset === void 0) { positionOffset = null; }
+            var t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);
+            var size = this.renderGroup.viewportSize;
+            var zBias = this.getActualZOffset();
+            var offX = 0;
+            var offY = 0;
+            // If there's an offset, apply the global transformation matrix on it to get a global offset
+            if (positionOffset) {
+                offX = positionOffset.x * t.m[0] + positionOffset.y * t.m[4];
+                offY = positionOffset.x * t.m[1] + positionOffset.y * t.m[5];
+            }
+            // Have to convert the coordinates to clip space which is ranged between [-1;1] on X and Y axis, with 0,0 being the left/bottom corner
+            // Current coordinates are expressed in renderGroup coordinates ([0, renderGroup.actualSize.width|height]) with 0,0 being at the left/top corner
+            // RenderGroup Width and Height are multiplied by zBias because the VertexShader will multiply X and Y by W, which is 1/zBias. As we divide our coordinate by these Width/Height, we will also divide by the zBias to compensate the operation made by the VertexShader.
+            // So for X: 
+            //  - tx.x = value * 2 / width: is to switch from [0, renderGroup.width] to [0, 2]
+            //  - tx.w = (value * 2 / width) - 1: w stores the translation in renderGroup coordinates so (value * 2 / width) to switch to a clip space translation value. - 1 is to offset the overall [0;2] to [-1;1]. Don't forget it's -(1/zBias) and not -1 because everything need to be scaled by 1/zBias.
+            var w = size.width * zBias;
+            var h = size.height * zBias;
+            var invZBias = 1 / zBias;
+            var tx = new BABYLON.Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, 0 /*t.m[8]*/, ((t.m[12] + offX) * 2 / w) - (invZBias));
+            var ty = new BABYLON.Vector4(t.m[1] * 2 / h, t.m[5] * 2 / h, 0 /*t.m[9]*/, ((t.m[13] + offY) * 2 / h) - (invZBias));
+            part.transformX = tx;
+            part.transformY = ty;
+            part.origin = this.origin;
+            // Stores zBias and it's inverse value because that's needed to compute the clip space W coordinate (which is 1/Z, so 1/zBias)
+            part.zBias = new BABYLON.Vector2(zBias, invZBias);
+        };
+        RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT = BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 5;
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return RenderablePrim2D.isTransparentProperty = pi; })
+        ], RenderablePrim2D.prototype, "isTransparent", null);
+        RenderablePrim2D = __decorate([
+            BABYLON.className("RenderablePrim2D")
+        ], RenderablePrim2D);
+        return RenderablePrim2D;
+    })(BABYLON.Prim2DBase);
+    BABYLON.RenderablePrim2D = RenderablePrim2D;
+})(BABYLON || (BABYLON = {}));

+ 5 - 2
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -356,7 +356,9 @@
                         this.isVisible = curVisible;
 
                         let size = 0;
-                        cti.fullContent.forEach((k, v) => {
+
+                        for (var index = 0; index < cti.fullContent.count; index++) {
+                            var v = cti.fullContent[index];
                             if (!v.category || cat.indexOf(v.category) !== -1) {
                                 if (!v.size) {
                                     console.log(`ERROR: Couldn't detect the size of the Property ${v.attributeName} from type ${Tools.getClassName(cti.type)}. Property is ignored.`);
@@ -364,7 +366,8 @@
                                     size += v.size;
                                 }
                             }
-                        });
+                        }
+
                         dataStrides.push(size);
                         usedCatList.push(cat);
                         ctiArray.push(cti);

+ 249 - 0
src/Canvas2d/babylon.shape2d.js

@@ -0,0 +1,249 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Shape2D = (function (_super) {
+        __extends(Shape2D, _super);
+        function Shape2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Shape2D.prototype, "border", {
+            get: function () {
+                return this._border;
+            },
+            set: function (value) {
+                this._border = value;
+                this._updateTransparencyStatus();
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2D.prototype, "fill", {
+            get: function () {
+                return this._fill;
+            },
+            set: function (value) {
+                this._fill = value;
+                this._updateTransparencyStatus();
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2D.prototype, "borderThickness", {
+            get: function () {
+                return this._borderThickness;
+            },
+            set: function (value) {
+                this._borderThickness = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Shape2D.prototype.setupShape2D = function (owner, parent, id, position, isVisible, fill, border, borderThickness) {
+            if (borderThickness === void 0) { borderThickness = 1.0; }
+            this.setupRenderablePrim2D(owner, parent, id, position, isVisible);
+            this.border = border;
+            this.fill = fill;
+            this.borderThickness = borderThickness;
+        };
+        Shape2D.prototype.getUsedShaderCategories = function (dataPart) {
+            var cat = _super.prototype.getUsedShaderCategories.call(this, dataPart);
+            // Fill Part
+            if (dataPart.id === Shape2D.SHAPE2D_FILLPARTID) {
+                var fill = this.fill;
+                if (fill instanceof BABYLON.SolidColorBrush2D) {
+                    cat.push(Shape2D.SHAPE2D_CATEGORY_FILLSOLID);
+                }
+                if (fill instanceof BABYLON.GradientColorBrush2D) {
+                    cat.push(Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT);
+                }
+            }
+            // Border Part
+            if (dataPart.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                cat.push(Shape2D.SHAPE2D_CATEGORY_BORDER);
+                var border = this.border;
+                if (border instanceof BABYLON.SolidColorBrush2D) {
+                    cat.push(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID);
+                }
+                if (border instanceof BABYLON.GradientColorBrush2D) {
+                    cat.push(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT);
+                }
+            }
+            return cat;
+        };
+        Shape2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
+                return false;
+            }
+            // Fill Part
+            if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
+                var d = part;
+                if (this.fill) {
+                    var fill = this.fill;
+                    if (fill instanceof BABYLON.SolidColorBrush2D) {
+                        d.fillSolidColor = fill.color;
+                    }
+                    else if (fill instanceof BABYLON.GradientColorBrush2D) {
+                        d.fillGradientColor1 = fill.color1;
+                        d.fillGradientColor2 = fill.color2;
+                        var t = BABYLON.Matrix.Compose(new BABYLON.Vector3(fill.scale, fill.scale, fill.scale), BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 0, 1), fill.rotation), new BABYLON.Vector3(fill.translation.x, fill.translation.y, 0));
+                        var ty = new BABYLON.Vector4(t.m[1], t.m[5], t.m[9], t.m[13]);
+                        d.fillGradientTY = ty;
+                    }
+                }
+            }
+            else if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
+                var d = part;
+                if (this.border) {
+                    d.borderThickness = this.borderThickness;
+                    var border = this.border;
+                    if (border instanceof BABYLON.SolidColorBrush2D) {
+                        d.borderSolidColor = border.color;
+                    }
+                    else if (border instanceof BABYLON.GradientColorBrush2D) {
+                        d.borderGradientColor1 = border.color1;
+                        d.borderGradientColor2 = border.color2;
+                        var t = BABYLON.Matrix.Compose(new BABYLON.Vector3(border.scale, border.scale, border.scale), BABYLON.Quaternion.RotationAxis(new BABYLON.Vector3(0, 0, 1), border.rotation), new BABYLON.Vector3(border.translation.x, border.translation.y, 0));
+                        var ty = new BABYLON.Vector4(t.m[1], t.m[5], t.m[9], t.m[13]);
+                        d.borderGradientTY = ty;
+                    }
+                }
+            }
+            return true;
+        };
+        Shape2D.prototype._updateTransparencyStatus = function () {
+            this.isTransparent = (this._border && this._border.isTransparent()) || (this._fill && this._fill.isTransparent());
+        };
+        Shape2D.SHAPE2D_BORDERPARTID = 1;
+        Shape2D.SHAPE2D_FILLPARTID = 2;
+        Shape2D.SHAPE2D_CATEGORY_BORDER = "Border";
+        Shape2D.SHAPE2D_CATEGORY_BORDERSOLID = "BorderSolid";
+        Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT = "BorderGradient";
+        Shape2D.SHAPE2D_CATEGORY_FILLSOLID = "FillSolid";
+        Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT = "FillGradient";
+        Shape2D.SHAPE2D_PROPCOUNT = BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5;
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, function (pi) { return Shape2D.borderProperty = pi; }, true)
+        ], Shape2D.prototype, "border", null);
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, function (pi) { return Shape2D.fillProperty = pi; }, true)
+        ], Shape2D.prototype, "fill", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, function (pi) { return Shape2D.borderThicknessProperty = pi; })
+        ], Shape2D.prototype, "borderThickness", null);
+        Shape2D = __decorate([
+            BABYLON.className("Shape2D")
+        ], Shape2D);
+        return Shape2D;
+    })(BABYLON.RenderablePrim2D);
+    BABYLON.Shape2D = Shape2D;
+    var Shape2DInstanceData = (function (_super) {
+        __extends(Shape2DInstanceData, _super);
+        function Shape2DInstanceData() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Shape2DInstanceData.prototype, "fillSolidColor", {
+            // FILL ATTRIBUTES
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "fillGradientColor1", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "fillGradientColor2", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "fillGradientTY", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "borderThickness", {
+            // BORDER ATTRIBUTES
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "borderSolidColor", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "borderGradientColor1", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "borderGradientColor2", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Shape2DInstanceData.prototype, "borderGradientTY", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_FILLSOLID)
+        ], Shape2DInstanceData.prototype, "fillSolidColor", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT)
+        ], Shape2DInstanceData.prototype, "fillGradientColor1", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT)
+        ], Shape2DInstanceData.prototype, "fillGradientColor2", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_FILLGRADIENT)
+        ], Shape2DInstanceData.prototype, "fillGradientTY", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_BORDER)
+        ], Shape2DInstanceData.prototype, "borderThickness", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERSOLID)
+        ], Shape2DInstanceData.prototype, "borderSolidColor", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
+        ], Shape2DInstanceData.prototype, "borderGradientColor1", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
+        ], Shape2DInstanceData.prototype, "borderGradientColor2", null);
+        __decorate([
+            BABYLON.instanceData(Shape2D.SHAPE2D_CATEGORY_BORDERGRADIENT)
+        ], Shape2DInstanceData.prototype, "borderGradientTY", null);
+        return Shape2DInstanceData;
+    })(BABYLON.InstanceDataBase);
+    BABYLON.Shape2DInstanceData = Shape2DInstanceData;
+})(BABYLON || (BABYLON = {}));

+ 366 - 344
src/Canvas2d/babylon.smartPropertyPrim.js

@@ -1,344 +1,366 @@
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var Prim2DClassInfo = (function () {
-        function Prim2DClassInfo() {
-        }
-        return Prim2DClassInfo;
-    }());
-    BABYLON.Prim2DClassInfo = Prim2DClassInfo;
-    var Prim2DPropInfo = (function () {
-        function Prim2DPropInfo() {
-        }
-        Prim2DPropInfo.PROPKIND_MODEL = 1;
-        Prim2DPropInfo.PROPKIND_INSTANCE = 2;
-        Prim2DPropInfo.PROPKIND_DYNAMIC = 3;
-        return Prim2DPropInfo;
-    }());
-    BABYLON.Prim2DPropInfo = Prim2DPropInfo;
-    var PropertyChangedInfo = (function () {
-        function PropertyChangedInfo() {
-        }
-        return PropertyChangedInfo;
-    }());
-    BABYLON.PropertyChangedInfo = PropertyChangedInfo;
-    var ClassTreeInfo = (function () {
-        function ClassTreeInfo(baseClass, type, classContentFactory) {
-            this._baseClass = baseClass;
-            this._type = type;
-            this._subClasses = new Array();
-            this._levelContent = new BABYLON.StringDictionary();
-            this._classContentFactory = classContentFactory;
-        }
-        Object.defineProperty(ClassTreeInfo.prototype, "classContent", {
-            get: function () {
-                if (!this._classContent) {
-                    this._classContent = this._classContentFactory(this._baseClass ? this._baseClass.classContent : null);
-                }
-                return this._classContent;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(ClassTreeInfo.prototype, "type", {
-            get: function () {
-                return this._type;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(ClassTreeInfo.prototype, "levelContent", {
-            get: function () {
-                return this._levelContent;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(ClassTreeInfo.prototype, "fullContent", {
-            get: function () {
-                if (!this._fullContent) {
-                    var dic_1 = new BABYLON.StringDictionary();
-                    var curLevel = this;
-                    while (curLevel) {
-                        curLevel.levelContent.forEach(function (k, v) { return dic_1.add(k, v); });
-                        curLevel = curLevel._baseClass;
-                    }
-                    this._fullContent = dic_1;
-                }
-                return this._fullContent;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        ClassTreeInfo.prototype.getLevelOf = function (type) {
-            // Are we already there?
-            if (type === this._type) {
-                return this;
-            }
-            var baseProto = Object.getPrototypeOf(type);
-            return this.getOrAddType(baseProto, type);
-            //// If type is a class, this will get the base class proto, if type is an instance of a class, this will get the proto of the class
-            //let baseTypeName = Tools.getClassName(baseProto);
-            //// If both name are equal we only switch from instance to class, we need to get the next proto in the hierarchy to get the base class
-            //if (baseTypeName === typeName) {
-            //    baseTypeName = Tools.getClassName(Object.getPrototypeOf(baseProto));
-            //}
-            //return this.getOrAddType(baseTypeName, typeName);
-        };
-        ClassTreeInfo.prototype.getOrAddType = function (baseType, type) {
-            // Are we at the level corresponding to the baseType?
-            // If so, get or add the level we're looking for
-            if (baseType === this._type) {
-                for (var _i = 0, _a = this._subClasses; _i < _a.length; _i++) {
-                    var subType = _a[_i];
-                    if (subType.type === type) {
-                        return subType.node;
-                    }
-                }
-                var node = new ClassTreeInfo(this, type, this._classContentFactory);
-                var info = { type: type, node: node };
-                this._subClasses.push(info);
-                return info.node;
-            }
-            // Recurse down to keep looking for the node corresponding to the baseTypeName
-            for (var _b = 0, _c = this._subClasses; _b < _c.length; _b++) {
-                var subType = _c[_b];
-                var info = subType.node.getOrAddType(baseType, type);
-                if (info) {
-                    return info;
-                }
-            }
-            return null;
-        };
-        ClassTreeInfo.get = function (type) {
-            var dic = type["__classTreeInfo"];
-            if (!dic) {
-                return null;
-            }
-            return dic.getLevelOf(type);
-        };
-        ClassTreeInfo.getOrRegister = function (type, classContentFactory) {
-            var dic = type["__classTreeInfo"];
-            if (!dic) {
-                dic = new ClassTreeInfo(null, type, classContentFactory);
-                type["__classTreeInfo"] = dic;
-            }
-            return dic;
-        };
-        return ClassTreeInfo;
-    }());
-    BABYLON.ClassTreeInfo = ClassTreeInfo;
-    var SmartPropertyPrim = (function () {
-        function SmartPropertyPrim() {
-        }
-        SmartPropertyPrim.prototype.setupSmartPropertyPrim = function () {
-            this._modelKey = null;
-            this._modelDirty = false;
-            this._levelBoundingInfoDirty = false;
-            this._instanceDirtyFlags = 0;
-            this._levelBoundingInfo = new BABYLON.BoundingInfo2D();
-        };
-        SmartPropertyPrim.prototype.dispose = function () {
-        };
-        Object.defineProperty(SmartPropertyPrim.prototype, "modelKey", {
-            get: function () {
-                var _this = this;
-                // No need to compute it?
-                if (!this._modelDirty && this._modelKey) {
-                    return this._modelKey;
-                }
-                var modelKey = "Class:" + BABYLON.Tools.getClassName(this) + ";";
-                var propDic = this.propDic;
-                propDic.forEach(function (k, v) {
-                    if (v.kind === Prim2DPropInfo.PROPKIND_MODEL) {
-                        var propVal = _this[v.name];
-                        modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? BABYLON.Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
-                    }
-                });
-                this._modelDirty = false;
-                this._modelKey = modelKey;
-                return modelKey;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        SmartPropertyPrim.GetOrAddModelCache = function (key, factory) {
-            return SmartPropertyPrim.ModelCache.getOrAddWithFactory(key, factory);
-        };
-        Object.defineProperty(SmartPropertyPrim.prototype, "propDic", {
-            get: function () {
-                if (!this._propInfo) {
-                    var cti = ClassTreeInfo.get(Object.getPrototypeOf(this));
-                    if (!cti) {
-                        throw new Error("Can't access the propDic member in class definition, is this class SmartPropertyPrim based?");
-                    }
-                    this._propInfo = cti.fullContent;
-                }
-                return this._propInfo;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        SmartPropertyPrim._createPropInfo = function (target, propName, propId, dirtyBoundingInfo, typeLevelCompare, kind) {
-            var dic = ClassTreeInfo.getOrRegister(target, function () { return new Prim2DClassInfo(); });
-            var node = dic.getLevelOf(target);
-            var propInfo = node.levelContent.get(propId.toString());
-            if (propInfo) {
-                throw new Error("The ID " + propId + " is already taken by another property declaration named: " + propInfo.name);
-            }
-            // Create, setup and add the PropInfo object to our prop dictionary
-            propInfo = new Prim2DPropInfo();
-            propInfo.id = propId;
-            propInfo.flagId = Math.pow(2, propId);
-            propInfo.kind = kind;
-            propInfo.name = propName;
-            propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
-            propInfo.typeLevelCompare = typeLevelCompare;
-            node.levelContent.add(propId.toString(), propInfo);
-            return propInfo;
-        };
-        SmartPropertyPrim._checkUnchanged = function (curValue, newValue) {
-            // Nothing to nothing: nothign to do!
-            if ((curValue === null && newValue === null) || (curValue === undefined && newValue === undefined)) {
-                return true;
-            }
-            // Check value unchanged
-            if ((curValue != null) && (newValue != null)) {
-                if (typeof (curValue.equals) == "function") {
-                    if (curValue.equals(newValue)) {
-                        return true;
-                    }
-                }
-                else {
-                    if (curValue === newValue) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        };
-        SmartPropertyPrim.prototype._handlePropChanged = function (curValue, newValue, propName, propInfo, typeLevelCompare) {
-            // Trigger propery changed
-            var info = SmartPropertyPrim.propChangedInfo;
-            info.oldValue = curValue;
-            info.newValue = newValue;
-            info.propertyName = propName;
-            var propMask = propInfo.flagId;
-            this.propertyChanged.notifyObservers(info, propMask);
-            // Check if we need to dirty only if the type change and make the test
-            var skipDirty = false;
-            if (typeLevelCompare && curValue != null && newValue != null) {
-                var cvProto = curValue.__proto__;
-                var nvProto = newValue.__proto__;
-                skipDirty = (cvProto === nvProto);
-            }
-            // Set the dirty flags
-            if (!skipDirty) {
-                if (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL) {
-                    if ((this._instanceDirtyFlags === 0) && (!this._modelDirty)) {
-                        this.onPrimBecomesDirty();
-                    }
-                    this._modelDirty = true;
-                }
-                else if (propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) {
-                    if ((this._instanceDirtyFlags === 0) && (!this._modelDirty)) {
-                        this.onPrimBecomesDirty();
-                    }
-                    this._instanceDirtyFlags |= propMask;
-                }
-            }
-        };
-        SmartPropertyPrim.prototype.checkPropertiesDirty = function (flags) {
-            return (this._instanceDirtyFlags & flags) !== 0;
-        };
-        SmartPropertyPrim.prototype.clearPropertiesDirty = function (flags) {
-            this._instanceDirtyFlags &= ~flags;
-            return this._instanceDirtyFlags;
-        };
-        Object.defineProperty(SmartPropertyPrim.prototype, "levelBoundingInfo", {
-            get: function () {
-                if (this._levelBoundingInfoDirty) {
-                    this.updateLevelBoundingInfo();
-                    this._levelBoundingInfoDirty = false;
-                }
-                return this._levelBoundingInfo;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        SmartPropertyPrim.prototype.updateLevelBoundingInfo = function () {
-        };
-        SmartPropertyPrim.prototype.onPrimBecomesDirty = function () {
-        };
-        SmartPropertyPrim._hookProperty = function (propId, piStore, typeLevelCompare, dirtyBoundingInfo, kind) {
-            return function (target, propName, descriptor) {
-                var propInfo = SmartPropertyPrim._createPropInfo(target, propName, propId, dirtyBoundingInfo, typeLevelCompare, kind);
-                if (piStore) {
-                    piStore(propInfo);
-                }
-                var getter = descriptor.get, setter = descriptor.set;
-                // Overload the property setter implementation to add our own logic
-                descriptor.set = function (val) {
-                    var curVal = getter.call(this);
-                    if (SmartPropertyPrim._checkUnchanged(curVal, val)) {
-                        return;
-                    }
-                    // Cast the object we're working one
-                    var prim = this;
-                    // Change the value
-                    setter.call(this, val);
-                    // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
-                    if (propInfo.dirtyBoundingInfo) {
-                        prim._levelBoundingInfoDirty = true;
-                        // Escalade the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
-                        if (prim instanceof BABYLON.Prim2DBase) {
-                            var curprim = prim.parent;
-                            while (curprim) {
-                                curprim._boundingInfoDirty = true;
-                                if (curprim instanceof BABYLON.Group2D) {
-                                    if (curprim.isRenderableGroup) {
-                                        break;
-                                    }
-                                }
-                                curprim = curprim.parent;
-                            }
-                        }
-                    }
-                    // Notify change, dirty flags update
-                    prim._handlePropChanged(curVal, val, propName, propInfo, typeLevelCompare);
-                };
-            };
-        };
-        SmartPropertyPrim.ModelCache = new BABYLON.StringDictionary();
-        SmartPropertyPrim.propChangedInfo = new PropertyChangedInfo();
-        SmartPropertyPrim = __decorate([
-            BABYLON.className("SmartPropertyPrim")
-        ], SmartPropertyPrim);
-        return SmartPropertyPrim;
-    }());
-    BABYLON.SmartPropertyPrim = SmartPropertyPrim;
-    function modelLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
-        if (typeLevelCompare === void 0) { typeLevelCompare = false; }
-        if (dirtyBoundingInfo === void 0) { dirtyBoundingInfo = false; }
-        return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_MODEL);
-    }
-    BABYLON.modelLevelProperty = modelLevelProperty;
-    function instanceLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
-        if (typeLevelCompare === void 0) { typeLevelCompare = false; }
-        if (dirtyBoundingInfo === void 0) { dirtyBoundingInfo = false; }
-        return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_INSTANCE);
-    }
-    BABYLON.instanceLevelProperty = instanceLevelProperty;
-    function dynamicLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
-        if (typeLevelCompare === void 0) { typeLevelCompare = false; }
-        if (dirtyBoundingInfo === void 0) { dirtyBoundingInfo = false; }
-        return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_DYNAMIC);
-    }
-    BABYLON.dynamicLevelProperty = dynamicLevelProperty;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.smartPropertyPrim.js.map
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Prim2DClassInfo = (function () {
+        function Prim2DClassInfo() {
+        }
+        return Prim2DClassInfo;
+    })();
+    BABYLON.Prim2DClassInfo = Prim2DClassInfo;
+    var Prim2DPropInfo = (function () {
+        function Prim2DPropInfo() {
+        }
+        Prim2DPropInfo.PROPKIND_MODEL = 1;
+        Prim2DPropInfo.PROPKIND_INSTANCE = 2;
+        Prim2DPropInfo.PROPKIND_DYNAMIC = 3;
+        return Prim2DPropInfo;
+    })();
+    BABYLON.Prim2DPropInfo = Prim2DPropInfo;
+    var PropertyChangedInfo = (function () {
+        function PropertyChangedInfo() {
+        }
+        return PropertyChangedInfo;
+    })();
+    BABYLON.PropertyChangedInfo = PropertyChangedInfo;
+    var ClassTreeInfo = (function () {
+        function ClassTreeInfo(baseClass, type, classContentFactory) {
+            this._baseClass = baseClass;
+            this._type = type;
+            this._subClasses = new Array();
+            this._levelContent = new BABYLON.StringDictionary();
+            this._classContentFactory = classContentFactory;
+        }
+        Object.defineProperty(ClassTreeInfo.prototype, "classContent", {
+            get: function () {
+                if (!this._classContent) {
+                    this._classContent = this._classContentFactory(this._baseClass ? this._baseClass.classContent : null);
+                }
+                return this._classContent;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ClassTreeInfo.prototype, "type", {
+            get: function () {
+                return this._type;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ClassTreeInfo.prototype, "levelContent", {
+            get: function () {
+                return this._levelContent;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ClassTreeInfo.prototype, "fullContent", {
+            get: function () {
+                if (!this._fullContent) {
+                    var dic = new BABYLON.StringDictionary();
+                    var curLevel = this;
+                    while (curLevel) {
+                        curLevel.levelContent.forEach(function (k, v) { return dic.add(k, v); });
+                        curLevel = curLevel._baseClass;
+                    }
+                    this._fullContent = dic;
+                }
+                return this._fullContent;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ClassTreeInfo.prototype.getLevelOf = function (type) {
+            // Are we already there?
+            if (type === this._type) {
+                return this;
+            }
+            var baseProto = Object.getPrototypeOf(type);
+            var curProtoContent = this.getOrAddType(Object.getPrototypeOf(baseProto), baseProto);
+            if (!curProtoContent) {
+                this.getLevelOf(baseProto);
+            }
+            return this.getOrAddType(baseProto, type);
+        };
+        ClassTreeInfo.prototype.getOrAddType = function (baseType, type) {
+            // Are we at the level corresponding to the baseType?
+            // If so, get or add the level we're looking for
+            if (baseType === this._type) {
+                for (var _i = 0, _a = this._subClasses; _i < _a.length; _i++) {
+                    var subType = _a[_i];
+                    if (subType.type === type) {
+                        return subType.node;
+                    }
+                }
+                var node = new ClassTreeInfo(this, type, this._classContentFactory);
+                var info = { type: type, node: node };
+                this._subClasses.push(info);
+                return info.node;
+            }
+            // Recurse down to keep looking for the node corresponding to the baseTypeName
+            for (var _b = 0, _c = this._subClasses; _b < _c.length; _b++) {
+                var subType = _c[_b];
+                var info = subType.node.getOrAddType(baseType, type);
+                if (info) {
+                    return info;
+                }
+            }
+            return null;
+        };
+        ClassTreeInfo.get = function (type) {
+            var dic = type["__classTreeInfo"];
+            if (!dic) {
+                return null;
+            }
+            return dic.getLevelOf(type);
+        };
+        ClassTreeInfo.getOrRegister = function (type, classContentFactory) {
+            var dic = type["__classTreeInfo"];
+            if (!dic) {
+                dic = new ClassTreeInfo(null, type, classContentFactory);
+                type["__classTreeInfo"] = dic;
+            }
+            return dic;
+        };
+        return ClassTreeInfo;
+    })();
+    BABYLON.ClassTreeInfo = ClassTreeInfo;
+    var SmartPropertyPrim = (function () {
+        function SmartPropertyPrim() {
+        }
+        SmartPropertyPrim.prototype.setupSmartPropertyPrim = function () {
+            this._modelKey = null;
+            this._modelDirty = false;
+            this._levelBoundingInfoDirty = false;
+            this._instanceDirtyFlags = 0;
+            this._isDisposed = false;
+            this._levelBoundingInfo = new BABYLON.BoundingInfo2D();
+        };
+        Object.defineProperty(SmartPropertyPrim.prototype, "isDisposed", {
+            get: function () {
+                return this._isDisposed;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        SmartPropertyPrim.prototype.dispose = function () {
+            if (this.isDisposed) {
+                return false;
+            }
+            this._isDisposed = true;
+            return true;
+        };
+        Object.defineProperty(SmartPropertyPrim.prototype, "modelKey", {
+            get: function () {
+                var _this = this;
+                // No need to compute it?
+                if (!this._modelDirty && this._modelKey) {
+                    return this._modelKey;
+                }
+                var modelKey = "Class:" + BABYLON.Tools.getClassName(this) + ";";
+                var propDic = this.propDic;
+                propDic.forEach(function (k, v) {
+                    if (v.kind === Prim2DPropInfo.PROPKIND_MODEL) {
+                        var propVal = _this[v.name];
+                        modelKey += v.name + ":" + ((propVal != null) ? ((v.typeLevelCompare) ? BABYLON.Tools.getClassName(propVal) : propVal.toString()) : "[null]") + ";";
+                    }
+                });
+                this._modelDirty = false;
+                this._modelKey = modelKey;
+                return modelKey;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(SmartPropertyPrim.prototype, "isDirty", {
+            get: function () {
+                return (this._instanceDirtyFlags !== 0) || this._modelDirty;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        SmartPropertyPrim.GetOrAddModelCache = function (key, factory) {
+            return SmartPropertyPrim.ModelCache.getOrAddWithFactory(key, factory);
+        };
+        Object.defineProperty(SmartPropertyPrim.prototype, "propDic", {
+            get: function () {
+                if (!this._propInfo) {
+                    var cti = ClassTreeInfo.get(Object.getPrototypeOf(this));
+                    if (!cti) {
+                        throw new Error("Can't access the propDic member in class definition, is this class SmartPropertyPrim based?");
+                    }
+                    this._propInfo = cti.fullContent;
+                }
+                return this._propInfo;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        SmartPropertyPrim._createPropInfo = function (target, propName, propId, dirtyBoundingInfo, typeLevelCompare, kind) {
+            var dic = ClassTreeInfo.getOrRegister(target, function () { return new Prim2DClassInfo(); });
+            var node = dic.getLevelOf(target);
+            var propInfo = node.levelContent.get(propId.toString());
+            if (propInfo) {
+                throw new Error("The ID " + propId + " is already taken by another property declaration named: " + propInfo.name);
+            }
+            // Create, setup and add the PropInfo object to our prop dictionary
+            propInfo = new Prim2DPropInfo();
+            propInfo.id = propId;
+            propInfo.flagId = Math.pow(2, propId);
+            propInfo.kind = kind;
+            propInfo.name = propName;
+            propInfo.dirtyBoundingInfo = dirtyBoundingInfo;
+            propInfo.typeLevelCompare = typeLevelCompare;
+            node.levelContent.add(propId.toString(), propInfo);
+            return propInfo;
+        };
+        SmartPropertyPrim._checkUnchanged = function (curValue, newValue) {
+            // Nothing to nothing: nothign to do!
+            if ((curValue === null && newValue === null) || (curValue === undefined && newValue === undefined)) {
+                return true;
+            }
+            // Check value unchanged
+            if ((curValue != null) && (newValue != null)) {
+                if (typeof (curValue.equals) == "function") {
+                    if (curValue.equals(newValue)) {
+                        return true;
+                    }
+                }
+                else {
+                    if (curValue === newValue) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        };
+        SmartPropertyPrim.prototype._handlePropChanged = function (curValue, newValue, propName, propInfo, typeLevelCompare) {
+            // Trigger propery changed
+            var info = SmartPropertyPrim.propChangedInfo;
+            info.oldValue = curValue;
+            info.newValue = newValue;
+            info.propertyName = propName;
+            var propMask = propInfo.flagId;
+            this.propertyChanged.notifyObservers(info, propMask);
+            // If the property belong to a group, check if it's a cached one, and dirty its render sprite accordingly
+            if (this instanceof BABYLON.Group2D) {
+                this.handleGroupChanged(propInfo);
+            }
+            // Check if we need to dirty only if the type change and make the test
+            var skipDirty = false;
+            if (typeLevelCompare && curValue != null && newValue != null) {
+                var cvProto = curValue.__proto__;
+                var nvProto = newValue.__proto__;
+                skipDirty = (cvProto === nvProto);
+            }
+            // Set the dirty flags
+            if (!skipDirty) {
+                if (propInfo.kind === Prim2DPropInfo.PROPKIND_MODEL) {
+                    if (!this.isDirty) {
+                        this.onPrimBecomesDirty();
+                    }
+                    this._modelDirty = true;
+                }
+                else if (propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) {
+                    if (!this.isDirty) {
+                        this.onPrimBecomesDirty();
+                    }
+                    this._instanceDirtyFlags |= propMask;
+                }
+            }
+        };
+        SmartPropertyPrim.prototype.handleGroupChanged = function (prop) {
+        };
+        SmartPropertyPrim.prototype.checkPropertiesDirty = function (flags) {
+            return (this._instanceDirtyFlags & flags) !== 0;
+        };
+        SmartPropertyPrim.prototype.clearPropertiesDirty = function (flags) {
+            this._instanceDirtyFlags &= ~flags;
+            return this._instanceDirtyFlags;
+        };
+        Object.defineProperty(SmartPropertyPrim.prototype, "levelBoundingInfo", {
+            get: function () {
+                if (this._levelBoundingInfoDirty) {
+                    this.updateLevelBoundingInfo();
+                    this._levelBoundingInfoDirty = false;
+                }
+                return this._levelBoundingInfo;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        SmartPropertyPrim.prototype.updateLevelBoundingInfo = function () {
+        };
+        SmartPropertyPrim.prototype.onPrimBecomesDirty = function () {
+        };
+        SmartPropertyPrim._hookProperty = function (propId, piStore, typeLevelCompare, dirtyBoundingInfo, kind) {
+            return function (target, propName, descriptor) {
+                var propInfo = SmartPropertyPrim._createPropInfo(target, propName, propId, dirtyBoundingInfo, typeLevelCompare, kind);
+                if (piStore) {
+                    piStore(propInfo);
+                }
+                var getter = descriptor.get, setter = descriptor.set;
+                // Overload the property setter implementation to add our own logic
+                descriptor.set = function (val) {
+                    var curVal = getter.call(this);
+                    if (SmartPropertyPrim._checkUnchanged(curVal, val)) {
+                        return;
+                    }
+                    // Cast the object we're working one
+                    var prim = this;
+                    // Change the value
+                    setter.call(this, val);
+                    // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
+                    if (propInfo.dirtyBoundingInfo) {
+                        prim._levelBoundingInfoDirty = true;
+                        // Escalade the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
+                        if (prim instanceof BABYLON.Prim2DBase) {
+                            var curprim = prim.parent;
+                            while (curprim) {
+                                curprim._boundingInfoDirty = true;
+                                if (curprim instanceof BABYLON.Group2D) {
+                                    if (curprim.isRenderableGroup) {
+                                        break;
+                                    }
+                                }
+                                curprim = curprim.parent;
+                            }
+                        }
+                    }
+                    // Notify change, dirty flags update
+                    prim._handlePropChanged(curVal, val, propName, propInfo, typeLevelCompare);
+                };
+            };
+        };
+        SmartPropertyPrim.ModelCache = new BABYLON.StringDictionary();
+        SmartPropertyPrim.propChangedInfo = new PropertyChangedInfo();
+        SmartPropertyPrim = __decorate([
+            BABYLON.className("SmartPropertyPrim")
+        ], SmartPropertyPrim);
+        return SmartPropertyPrim;
+    })();
+    BABYLON.SmartPropertyPrim = SmartPropertyPrim;
+    function modelLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
+        if (typeLevelCompare === void 0) { typeLevelCompare = false; }
+        if (dirtyBoundingInfo === void 0) { dirtyBoundingInfo = false; }
+        return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_MODEL);
+    }
+    BABYLON.modelLevelProperty = modelLevelProperty;
+    function instanceLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
+        if (typeLevelCompare === void 0) { typeLevelCompare = false; }
+        if (dirtyBoundingInfo === void 0) { dirtyBoundingInfo = false; }
+        return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_INSTANCE);
+    }
+    BABYLON.instanceLevelProperty = instanceLevelProperty;
+    function dynamicLevelProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo) {
+        if (typeLevelCompare === void 0) { typeLevelCompare = false; }
+        if (dirtyBoundingInfo === void 0) { dirtyBoundingInfo = false; }
+        return SmartPropertyPrim._hookProperty(propId, piStore, typeLevelCompare, dirtyBoundingInfo, Prim2DPropInfo.PROPKIND_DYNAMIC);
+    }
+    BABYLON.dynamicLevelProperty = dynamicLevelProperty;
+})(BABYLON || (BABYLON = {}));

+ 256 - 233
src/Canvas2d/babylon.sprite2d.js

@@ -1,233 +1,256 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
-    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
-    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
-    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
-    return c > 3 && r && Object.defineProperty(target, key, r), r;
-};
-var BABYLON;
-(function (BABYLON) {
-    var Sprite2DRenderCache = (function (_super) {
-        __extends(Sprite2DRenderCache, _super);
-        function Sprite2DRenderCache() {
-            _super.apply(this, arguments);
-        }
-        Sprite2DRenderCache.prototype.render = function (instanceInfo, context) {
-            // Do nothing if the shader is still loading/preparing
-            if (!this.effect.isReady() || !this.texture.isReady()) {
-                return false;
-            }
-            // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
-            if (!this.instancingAttributes) {
-                this.instancingAttributes = instanceInfo._classTreeInfo.classContent.getInstancingAttributeInfos(this.effect);
-            }
-            var engine = instanceInfo._owner.owner.engine;
-            engine.enableEffect(this.effect);
-            this.effect.setTexture("diffuseSampler", this.texture);
-            engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
-            engine.updateAndBindInstancesBuffer(instanceInfo._instancesBuffer, null, this.instancingAttributes);
-            engine.draw(true, 0, 6, instanceInfo._instancesData.usedElementCount);
-            engine.unBindInstancesBuffer(instanceInfo._instancesBuffer, this.instancingAttributes);
-            return true;
-        };
-        return Sprite2DRenderCache;
-    }(BABYLON.ModelRenderCache));
-    BABYLON.Sprite2DRenderCache = Sprite2DRenderCache;
-    var Sprite2DInstanceData = (function (_super) {
-        __extends(Sprite2DInstanceData, _super);
-        function Sprite2DInstanceData() {
-            _super.apply(this, arguments);
-        }
-        Object.defineProperty(Sprite2DInstanceData.prototype, "topLeftUV", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2DInstanceData.prototype, "sizeUV", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2DInstanceData.prototype, "textureSize", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2DInstanceData.prototype, "frame", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2DInstanceData.prototype, "invertY", {
-            get: function () {
-                return null;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        __decorate([
-            BABYLON.instanceData()
-        ], Sprite2DInstanceData.prototype, "topLeftUV", null);
-        __decorate([
-            BABYLON.instanceData()
-        ], Sprite2DInstanceData.prototype, "sizeUV", null);
-        __decorate([
-            BABYLON.instanceData()
-        ], Sprite2DInstanceData.prototype, "textureSize", null);
-        __decorate([
-            BABYLON.instanceData()
-        ], Sprite2DInstanceData.prototype, "frame", null);
-        __decorate([
-            BABYLON.instanceData()
-        ], Sprite2DInstanceData.prototype, "invertY", null);
-        return Sprite2DInstanceData;
-    }(BABYLON.InstanceDataBase));
-    BABYLON.Sprite2DInstanceData = Sprite2DInstanceData;
-    var Sprite2D = (function (_super) {
-        __extends(Sprite2D, _super);
-        function Sprite2D() {
-            _super.apply(this, arguments);
-        }
-        Object.defineProperty(Sprite2D.prototype, "texture", {
-            get: function () {
-                return this._texture;
-            },
-            set: function (value) {
-                this._texture = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2D.prototype, "spriteSize", {
-            get: function () {
-                return this._size;
-            },
-            set: function (value) {
-                this._size = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2D.prototype, "spriteLocation", {
-            get: function () {
-                return this._location;
-            },
-            set: function (value) {
-                this._location = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2D.prototype, "spriteFrame", {
-            get: function () {
-                return this._spriteFrame;
-            },
-            set: function (value) {
-                this._spriteFrame = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Object.defineProperty(Sprite2D.prototype, "invertY", {
-            get: function () {
-                return this._invertY;
-            },
-            set: function (value) {
-                this._invertY = value;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        Sprite2D.prototype.updateLevelBoundingInfo = function () {
-            this._levelBoundingInfo.radius = Math.sqrt(this.spriteSize.width * this.spriteSize.width + this.spriteSize.height * this.spriteSize.height);
-            this._levelBoundingInfo.extent = this.spriteSize.clone();
-        };
-        Sprite2D.prototype.setupSprite2D = function (owner, parent, id, position, texture, spriteSize, spriteLocation, invertY) {
-            this.setupRenderablePrim2D(owner, parent, id, position, true, null, null);
-            this.texture = texture;
-            this.spriteSize = spriteSize;
-            this.spriteLocation = spriteLocation;
-            this.spriteFrame = 0;
-            this.invertY = invertY;
-        };
-        Sprite2D.Create = function (parent, id, x, y, texture, spriteSize, spriteLocation, invertY) {
-            if (invertY === void 0) { invertY = false; }
-            BABYLON.Prim2DBase.CheckParent(parent);
-            var sprite = new Sprite2D();
-            sprite.setupSprite2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), texture, spriteSize, spriteLocation, invertY);
-            return sprite;
-        };
-        Sprite2D.prototype.createModelRenderCache = function () {
-            var renderCache = new Sprite2DRenderCache();
-            var engine = this.owner.engine;
-            var vb = new Float32Array(4);
-            for (var i = 0; i < 4; i++) {
-                vb[i] = i;
-            }
-            renderCache.vb = engine.createVertexBuffer(vb);
-            var ib = new Float32Array(6);
-            ib[0] = 0;
-            ib[1] = 1;
-            ib[2] = 2;
-            ib[3] = 0;
-            ib[4] = 2;
-            ib[5] = 3;
-            renderCache.ib = engine.createIndexBuffer(ib);
-            renderCache.texture = this.texture;
-            renderCache.effect = engine.createEffect({ vertex: "sprite2d", fragment: "sprite2d" }, ["index", "zBias", "transformX", "transformY", "topLeftUV", "sizeUV", "origin", "textureSize", "frame", "invertY"], [], ["diffuseSampler"], "");
-            return renderCache;
-        };
-        Sprite2D.prototype.createInstanceData = function () {
-            return new Sprite2DInstanceData();
-        };
-        Sprite2D.prototype.refreshInstanceData = function () {
-            if (!_super.prototype.refreshInstanceData.call(this)) {
-                return false;
-            }
-            var d = this._instanceData;
-            var ts = this.texture.getSize();
-            var sl = this.spriteLocation;
-            var ss = this.spriteSize;
-            d.topLeftUV = new BABYLON.Vector2(sl.x / ts.width, sl.y / ts.height);
-            var suv = new BABYLON.Vector2(ss.width / ts.width, ss.height / ts.height);
-            d.sizeUV = suv;
-            d.frame = this.spriteFrame;
-            d.textureSize = new BABYLON.Vector2(ts.width, ts.height);
-            d.invertY = this.invertY ? 1 : 0;
-            return true;
-        };
-        __decorate([
-            BABYLON.modelLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, function (pi) { return Sprite2D.textureProperty = pi; })
-        ], Sprite2D.prototype, "texture", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, function (pi) { return Sprite2D.spriteSizeProperty = pi; }, false, true)
-        ], Sprite2D.prototype, "spriteSize", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, function (pi) { return Sprite2D.spriteLocationProperty = pi; })
-        ], Sprite2D.prototype, "spriteLocation", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, function (pi) { return Sprite2D.spriteFrameProperty = pi; })
-        ], Sprite2D.prototype, "spriteFrame", null);
-        __decorate([
-            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, function (pi) { return Sprite2D.invertYProperty = pi; })
-        ], Sprite2D.prototype, "invertY", null);
-        Sprite2D = __decorate([
-            BABYLON.className("Sprite2D")
-        ], Sprite2D);
-        return Sprite2D;
-    }(BABYLON.RenderablePrim2D));
-    BABYLON.Sprite2D = Sprite2D;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.sprite2d.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Sprite2DRenderCache = (function (_super) {
+        __extends(Sprite2DRenderCache, _super);
+        function Sprite2DRenderCache() {
+            _super.apply(this, arguments);
+        }
+        Sprite2DRenderCache.prototype.render = function (instanceInfo, context) {
+            // Do nothing if the shader is still loading/preparing
+            if (!this.effect.isReady() || !this.texture.isReady()) {
+                return false;
+            }
+            // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
+            var engine = instanceInfo._owner.owner.engine;
+            engine.enableEffect(this.effect);
+            this.effect.setTexture("diffuseSampler", this.texture);
+            engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
+            var cur = engine.getAlphaMode();
+            engine.setAlphaMode(BABYLON.Engine.ALPHA_COMBINE);
+            var count = instanceInfo._instancesPartsData[0].usedElementCount;
+            if (instanceInfo._owner.owner.supportInstancedArray) {
+                if (!this.instancingAttributes) {
+                    this.instancingAttributes = this.loadInstancingAttributes(Sprite2D.SPRITE2D_MAINPARTID, this.effect);
+                }
+                engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
+                engine.draw(true, 0, 6, count);
+                engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes);
+            }
+            else {
+                for (var i = 0; i < count; i++) {
+                    this.setupUniforms(this.effect, 0, instanceInfo._instancesPartsData[0], i);
+                    engine.draw(true, 0, 6);
+                }
+            }
+            engine.setAlphaMode(cur);
+            return true;
+        };
+        return Sprite2DRenderCache;
+    })(BABYLON.ModelRenderCache);
+    BABYLON.Sprite2DRenderCache = Sprite2DRenderCache;
+    var Sprite2DInstanceData = (function (_super) {
+        __extends(Sprite2DInstanceData, _super);
+        function Sprite2DInstanceData(partId) {
+            _super.call(this, partId, 1);
+        }
+        Object.defineProperty(Sprite2DInstanceData.prototype, "topLeftUV", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2DInstanceData.prototype, "sizeUV", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2DInstanceData.prototype, "textureSize", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2DInstanceData.prototype, "frame", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2DInstanceData.prototype, "invertY", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.instanceData()
+        ], Sprite2DInstanceData.prototype, "topLeftUV", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Sprite2DInstanceData.prototype, "sizeUV", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Sprite2DInstanceData.prototype, "textureSize", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Sprite2DInstanceData.prototype, "frame", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Sprite2DInstanceData.prototype, "invertY", null);
+        return Sprite2DInstanceData;
+    })(BABYLON.InstanceDataBase);
+    BABYLON.Sprite2DInstanceData = Sprite2DInstanceData;
+    var Sprite2D = (function (_super) {
+        __extends(Sprite2D, _super);
+        function Sprite2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Sprite2D.prototype, "texture", {
+            get: function () {
+                return this._texture;
+            },
+            set: function (value) {
+                this._texture = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2D.prototype, "spriteSize", {
+            get: function () {
+                return this._size;
+            },
+            set: function (value) {
+                this._size = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2D.prototype, "spriteLocation", {
+            get: function () {
+                return this._location;
+            },
+            set: function (value) {
+                this._location = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2D.prototype, "spriteFrame", {
+            get: function () {
+                return this._spriteFrame;
+            },
+            set: function (value) {
+                this._spriteFrame = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Sprite2D.prototype, "invertY", {
+            get: function () {
+                return this._invertY;
+            },
+            set: function (value) {
+                this._invertY = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Sprite2D.prototype.updateLevelBoundingInfo = function () {
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo);
+        };
+        Sprite2D.prototype.setupSprite2D = function (owner, parent, id, position, texture, spriteSize, spriteLocation, invertY) {
+            this.setupRenderablePrim2D(owner, parent, id, position, true);
+            this.texture = texture;
+            this.texture.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this.texture.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this.spriteSize = spriteSize;
+            this.spriteLocation = spriteLocation;
+            this.spriteFrame = 0;
+            this.invertY = invertY;
+            this._isTransparent = true;
+        };
+        Sprite2D.Create = function (parent, id, x, y, texture, spriteSize, spriteLocation, invertY) {
+            if (invertY === void 0) { invertY = false; }
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var sprite = new Sprite2D();
+            sprite.setupSprite2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), texture, spriteSize, spriteLocation, invertY);
+            return sprite;
+        };
+        Sprite2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
+            var renderCache = new Sprite2DRenderCache(modelKey, isTransparent);
+            return renderCache;
+        };
+        Sprite2D.prototype.setupModelRenderCache = function (modelRenderCache) {
+            var renderCache = modelRenderCache;
+            var engine = this.owner.engine;
+            var vb = new Float32Array(4);
+            for (var i = 0; i < 4; i++) {
+                vb[i] = i;
+            }
+            renderCache.vb = engine.createVertexBuffer(vb);
+            var ib = new Float32Array(6);
+            ib[0] = 0;
+            ib[1] = 2;
+            ib[2] = 1;
+            ib[3] = 0;
+            ib[4] = 3;
+            ib[5] = 2;
+            renderCache.ib = engine.createIndexBuffer(ib);
+            renderCache.texture = this.texture;
+            var ei = this.getDataPartEffectInfo(Sprite2D.SPRITE2D_MAINPARTID, ["index"]);
+            renderCache.effect = engine.createEffect({ vertex: "sprite2d", fragment: "sprite2d" }, ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, function (e) {
+                //                renderCache.setupUniformsLocation(e, ei.uniforms, Sprite2D.SPRITE2D_MAINPARTID);
+            });
+            return renderCache;
+        };
+        Sprite2D.prototype.createInstanceDataParts = function () {
+            return [new Sprite2DInstanceData(Sprite2D.SPRITE2D_MAINPARTID)];
+        };
+        Sprite2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
+                return false;
+            }
+            if (part.id === Sprite2D.SPRITE2D_MAINPARTID) {
+                var d = this._instanceDataParts[0];
+                var ts = this.texture.getSize();
+                var sl = this.spriteLocation;
+                var ss = this.spriteSize;
+                d.topLeftUV = new BABYLON.Vector2(sl.x / ts.width, sl.y / ts.height);
+                var suv = new BABYLON.Vector2(ss.width / ts.width, ss.height / ts.height);
+                d.sizeUV = suv;
+                d.frame = this.spriteFrame;
+                d.textureSize = new BABYLON.Vector2(ts.width, ts.height);
+                d.invertY = this.invertY ? 1 : 0;
+            }
+            return true;
+        };
+        Sprite2D.SPRITE2D_MAINPARTID = 1;
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, function (pi) { return Sprite2D.textureProperty = pi; })
+        ], Sprite2D.prototype, "texture", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, function (pi) { return Sprite2D.spriteSizeProperty = pi; }, false, true)
+        ], Sprite2D.prototype, "spriteSize", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, function (pi) { return Sprite2D.spriteLocationProperty = pi; })
+        ], Sprite2D.prototype, "spriteLocation", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, function (pi) { return Sprite2D.spriteFrameProperty = pi; })
+        ], Sprite2D.prototype, "spriteFrame", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, function (pi) { return Sprite2D.invertYProperty = pi; })
+        ], Sprite2D.prototype, "invertY", null);
+        Sprite2D = __decorate([
+            BABYLON.className("Sprite2D")
+        ], Sprite2D);
+        return Sprite2D;
+    })(BABYLON.RenderablePrim2D);
+    BABYLON.Sprite2D = Sprite2D;
+})(BABYLON || (BABYLON = {}));

+ 345 - 0
src/Canvas2d/babylon.text2d.js

@@ -0,0 +1,345 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Text2DRenderCache = (function (_super) {
+        __extends(Text2DRenderCache, _super);
+        function Text2DRenderCache() {
+            _super.apply(this, arguments);
+        }
+        Text2DRenderCache.prototype.render = function (instanceInfo, context) {
+            // Do nothing if the shader is still loading/preparing
+            if (!this.effect.isReady() || !this.fontTexture.isReady()) {
+                return false;
+            }
+            // Compute the offset locations of the attributes in the vertexshader that will be mapped to the instance buffer data
+            if (!this.instancingAttributes) {
+                this.instancingAttributes = this.loadInstancingAttributes(Text2D.TEXT2D_MAINPARTID, this.effect);
+            }
+            var engine = instanceInfo._owner.owner.engine;
+            this.fontTexture.update();
+            engine.enableEffect(this.effect);
+            this.effect.setTexture("diffuseSampler", this.fontTexture);
+            engine.bindBuffers(this.vb, this.ib, [1], 4, this.effect);
+            var cur = engine.getAlphaMode();
+            engine.setAlphaMode(BABYLON.Engine.ALPHA_COMBINE);
+            var count = instanceInfo._instancesPartsData[0].usedElementCount;
+            if (instanceInfo._owner.owner.supportInstancedArray) {
+                engine.updateAndBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], null, this.instancingAttributes);
+                engine.draw(true, 0, 6, count);
+                engine.unBindInstancesBuffer(instanceInfo._instancesPartsBuffer[0], this.instancingAttributes);
+            }
+            else {
+                for (var i = 0; i < count; i++) {
+                    this.setupUniforms(this.effect, 0, instanceInfo._instancesPartsData[0], i);
+                    engine.draw(true, 0, 6);
+                }
+            }
+            engine.setAlphaMode(cur);
+            return true;
+        };
+        return Text2DRenderCache;
+    })(BABYLON.ModelRenderCache);
+    BABYLON.Text2DRenderCache = Text2DRenderCache;
+    var Text2DInstanceData = (function (_super) {
+        __extends(Text2DInstanceData, _super);
+        function Text2DInstanceData(partId, dataElementCount) {
+            _super.call(this, partId, dataElementCount);
+        }
+        Object.defineProperty(Text2DInstanceData.prototype, "topLeftUV", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2DInstanceData.prototype, "sizeUV", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2DInstanceData.prototype, "textureSize", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2DInstanceData.prototype, "color", {
+            get: function () {
+                return null;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.instanceData()
+        ], Text2DInstanceData.prototype, "topLeftUV", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Text2DInstanceData.prototype, "sizeUV", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Text2DInstanceData.prototype, "textureSize", null);
+        __decorate([
+            BABYLON.instanceData()
+        ], Text2DInstanceData.prototype, "color", null);
+        return Text2DInstanceData;
+    })(BABYLON.InstanceDataBase);
+    BABYLON.Text2DInstanceData = Text2DInstanceData;
+    var Text2D = (function (_super) {
+        __extends(Text2D, _super);
+        function Text2D() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Text2D.prototype, "fontName", {
+            get: function () {
+                return this._fontName;
+            },
+            set: function (value) {
+                if (this._fontName) {
+                    throw new Error("Font Name change is not supported right now.");
+                }
+                this._fontName = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "defaultFontColor", {
+            get: function () {
+                return this._defaultFontColor;
+            },
+            set: function (value) {
+                this._defaultFontColor = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "text", {
+            get: function () {
+                return this._text;
+            },
+            set: function (value) {
+                this._text = value;
+                this._actualAreaSize = null; // A change of text will reset the Actual Area Size which will be recomputed next time it's used
+                this._updateCharCount();
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "areaSize", {
+            get: function () {
+                return this._areaSize;
+            },
+            set: function (value) {
+                this._areaSize = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "vAlign", {
+            get: function () {
+                return this._vAlign;
+            },
+            set: function (value) {
+                this._vAlign = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "hAlign", {
+            get: function () {
+                return this._hAlign;
+            },
+            set: function (value) {
+                this._hAlign = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "actualAreaSize", {
+            get: function () {
+                if (this.areaSize) {
+                    return this.areaSize;
+                }
+                if (this._actualAreaSize) {
+                    return this._actualAreaSize;
+                }
+                this._actualAreaSize = this.fontTexture.measureText(this._text, this._tabulationSize);
+                return this._actualAreaSize;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Text2D.prototype, "fontTexture", {
+            get: function () {
+                if (this._fontTexture) {
+                    return this._fontTexture;
+                }
+                this._fontTexture = BABYLON.FontTexture.GetCachedFontTexture(this.owner.scene, this.fontName);
+                return this._fontTexture;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Text2D.prototype.dispose = function () {
+            if (!_super.prototype.dispose.call(this)) {
+                return false;
+            }
+            if (this._fontTexture) {
+                BABYLON.FontTexture.ReleaseCachedFontTexture(this.owner.scene, this.fontName);
+                this._fontTexture = null;
+            }
+            return true;
+        };
+        Text2D.prototype.updateLevelBoundingInfo = function () {
+            BABYLON.BoundingInfo2D.CreateFromSizeToRef(this.actualAreaSize, this._levelBoundingInfo);
+        };
+        Text2D.prototype.setupText2D = function (owner, parent, id, position, fontName, text, areaSize, defaultFontColor, vAlign, hAlign, tabulationSize) {
+            this.setupRenderablePrim2D(owner, parent, id, position, true);
+            this.fontName = fontName;
+            this.defaultFontColor = defaultFontColor;
+            this.text = text;
+            this.areaSize = areaSize;
+            this.vAlign = vAlign;
+            this.hAlign = hAlign;
+            this._tabulationSize = tabulationSize;
+            this._isTransparent = true;
+            this.origin = BABYLON.Vector2.Zero();
+        };
+        Text2D.Create = function (parent, id, x, y, fontName, text, defaultFontColor, areaSize, vAlign, hAlign, tabulationSize) {
+            if (vAlign === void 0) { vAlign = Text2D.TEXT2D_VALIGN_TOP; }
+            if (hAlign === void 0) { hAlign = Text2D.TEXT2D_HALIGN_LEFT; }
+            if (tabulationSize === void 0) { tabulationSize = 4; }
+            BABYLON.Prim2DBase.CheckParent(parent);
+            var text2d = new Text2D();
+            text2d.setupText2D(parent.owner, parent, id, new BABYLON.Vector2(x, y), fontName, text, areaSize, defaultFontColor || new BABYLON.Color4(0, 0, 0, 1), vAlign, hAlign, tabulationSize);
+            return text2d;
+        };
+        Text2D.prototype.createModelRenderCache = function (modelKey, isTransparent) {
+            var renderCache = new Text2DRenderCache(modelKey, isTransparent);
+            return renderCache;
+        };
+        Text2D.prototype.setupModelRenderCache = function (modelRenderCache) {
+            var renderCache = modelRenderCache;
+            var engine = this.owner.engine;
+            renderCache.fontTexture = this.fontTexture;
+            var vb = new Float32Array(4);
+            for (var i = 0; i < 4; i++) {
+                vb[i] = i;
+            }
+            renderCache.vb = engine.createVertexBuffer(vb);
+            var ib = new Float32Array(6);
+            ib[0] = 0;
+            ib[1] = 2;
+            ib[2] = 1;
+            ib[3] = 0;
+            ib[4] = 3;
+            ib[5] = 2;
+            renderCache.ib = engine.createIndexBuffer(ib);
+            // Effects
+            var ei = this.getDataPartEffectInfo(Text2D.TEXT2D_MAINPARTID, ["index"]);
+            renderCache.effect = engine.createEffect("text2d", ei.attributes, ei.uniforms, ["diffuseSampler"], ei.defines, null, function (e) {
+                //                renderCache.setupUniformsLocation(e, ei.uniforms, Text2D.TEXT2D_MAINPARTID);
+            });
+            return renderCache;
+        };
+        Text2D.prototype.createInstanceDataParts = function () {
+            return [new Text2DInstanceData(Text2D.TEXT2D_MAINPARTID, this._charCount)];
+        };
+        Text2D.prototype.refreshInstanceDataPart = function (part) {
+            if (!_super.prototype.refreshInstanceDataPart.call(this, part)) {
+                return false;
+            }
+            if (part.id === Text2D.TEXT2D_MAINPARTID) {
+                var d = part;
+                var texture = this.fontTexture;
+                var ts = texture.getSize();
+                var offset = BABYLON.Vector2.Zero();
+                var charxpos = 0;
+                d.curElement = 0;
+                for (var _i = 0, _a = this.text; _i < _a.length; _i++) {
+                    var char = _a[_i];
+                    // Line feed
+                    if (char === "\n") {
+                        offset.x = 0;
+                        offset.y += texture.lineHeight;
+                    }
+                    // Tabulation ?
+                    if (char === "\t") {
+                        var nextPos = charxpos + this._tabulationSize;
+                        nextPos = nextPos - (nextPos % this._tabulationSize);
+                        offset.x += (nextPos - charxpos) * texture.spaceWidth;
+                        charxpos = nextPos;
+                        continue;
+                    }
+                    if (char < " ") {
+                        continue;
+                    }
+                    this.updateInstanceDataPart(d, offset);
+                    var ci = texture.getChar(char);
+                    offset.x += ci.charWidth;
+                    d.topLeftUV = ci.topLeftUV;
+                    var suv = ci.bottomRightUV.subtract(ci.topLeftUV);
+                    d.sizeUV = suv;
+                    d.textureSize = new BABYLON.Vector2(ts.width, ts.height);
+                    d.color = this.defaultFontColor;
+                    ++d.curElement;
+                }
+            }
+            return true;
+        };
+        Text2D.prototype._updateCharCount = function () {
+            var count = 0;
+            for (var _i = 0, _a = this._text; _i < _a.length; _i++) {
+                var char = _a[_i];
+                if (char === "\r" || char === "\n" || char === "\t" || char < " ") {
+                    continue;
+                }
+                ++count;
+            }
+            this._charCount = count;
+        };
+        Text2D.TEXT2D_MAINPARTID = 1;
+        Text2D.TEXT2D_VALIGN_TOP = 1;
+        Text2D.TEXT2D_VALIGN_CENTER = 2;
+        Text2D.TEXT2D_VALIGN_BOTTOM = 3;
+        Text2D.TEXT2D_HALIGN_LEFT = 1;
+        Text2D.TEXT2D_HALIGN_CENTER = 2;
+        Text2D.TEXT2D_HALIGN_RIGHT = 3;
+        __decorate([
+            BABYLON.modelLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, function (pi) { return Text2D.fontProperty = pi; }, false, true)
+        ], Text2D.prototype, "fontName", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, function (pi) { return Text2D.defaultFontColorProperty = pi; })
+        ], Text2D.prototype, "defaultFontColor", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, function (pi) { return Text2D.textProperty = pi; }, false, true)
+        ], Text2D.prototype, "text", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, function (pi) { return Text2D.areaSizeProperty = pi; })
+        ], Text2D.prototype, "areaSize", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, function (pi) { return Text2D.vAlignProperty = pi; })
+        ], Text2D.prototype, "vAlign", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 6, function (pi) { return Text2D.hAlignProperty = pi; })
+        ], Text2D.prototype, "hAlign", null);
+        Text2D = __decorate([
+            BABYLON.className("Text2D")
+        ], Text2D);
+        return Text2D;
+    })(BABYLON.RenderablePrim2D);
+    BABYLON.Text2D = Text2D;
+})(BABYLON || (BABYLON = {}));

+ 17 - 0
src/Canvas2d/babylon.worldSpaceCanvas2d.js

@@ -0,0 +1,17 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    var WorldSpaceCanvas2d = (function (_super) {
+        __extends(WorldSpaceCanvas2d, _super);
+        function WorldSpaceCanvas2d(name, scene, canvas) {
+            _super.call(this, name, scene);
+            this._canvas = canvas;
+        }
+        return WorldSpaceCanvas2d;
+    })(BABYLON.Mesh);
+    BABYLON.WorldSpaceCanvas2d = WorldSpaceCanvas2d;
+})(BABYLON || (BABYLON = {}));

+ 3 - 0
src/Materials/Textures/babylon.baseTexture.js

@@ -23,6 +23,9 @@ var BABYLON;
             this._scene = scene;
             this._scene.textures.push(this);
         }
+        BaseTexture.prototype.toString = function () {
+            return this.name;
+        };
         BaseTexture.prototype.getScene = function () {
             return this._scene;
         };

+ 251 - 0
src/Materials/Textures/babylon.fontTexture.js

@@ -0,0 +1,251 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * This class given information about a given character.
+     */
+    var CharInfo = (function () {
+        function CharInfo() {
+        }
+        return CharInfo;
+    })();
+    BABYLON.CharInfo = CharInfo;
+    var FontTexture = (function (_super) {
+        __extends(FontTexture, _super);
+        /**
+         * Create a new instance of the FontTexture class
+         * @param name the name of the texture
+         * @param font the font to use, use the W3C CSS notation
+         * @param scene the scene that owns the texture
+         * @param maxCharCount the approximative maximum count of characters that could fit in the texture. This is an approximation because most of the fonts are proportional (each char has its own Width). The 'W' character's width is used to compute the size of the texture based on the given maxCharCount
+         * @param samplingMode the texture sampling mode
+         */
+        function FontTexture(name, font, scene, maxCharCount, samplingMode) {
+            if (maxCharCount === void 0) { maxCharCount = 200; }
+            if (samplingMode === void 0) { samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE; }
+            _super.call(this, null, scene, true, false, samplingMode);
+            this._charInfos = {};
+            this._curCharCount = 0;
+            this._lastUpdateCharCount = -1;
+            this._usedCounter = 1;
+            this.name = name;
+            this.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            // First canvas creation to determine the size of the texture to create
+            this._canvas = document.createElement("canvas");
+            this._context = this._canvas.getContext("2d");
+            this._context.font = font;
+            this._context.fillStyle = "white";
+            var res = this.getFontHeight(font);
+            this._lineHeight = res.height;
+            this._offset = res.offset - 1;
+            var maxCharWidth = this._context.measureText("W").width;
+            this._spaceWidth = this._context.measureText(" ").width;
+            // This is an approximate size, but should always be able to fit at least the maxCharCount
+            var totalEstSurface = this._lineHeight * maxCharWidth * 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();
+            // 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;
+            this._canvas.height = textureSize.height;
+            this._context = this._canvas.getContext("2d");
+            this._context.textBaseline = "top";
+            this._context.font = font;
+            this._context.fillStyle = "white";
+            this._context.imageSmoothingEnabled = false;
+            this._currentFreePosition = BABYLON.Vector2.Zero();
+            // Add the basic ASCII based characters
+            for (var i = 0x20; i < 0x7F; i++) {
+                var c = String.fromCharCode(i);
+                this.getChar(c);
+            }
+            this.update();
+        }
+        Object.defineProperty(FontTexture.prototype, "spaceWidth", {
+            get: function () {
+                return this._spaceWidth;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(FontTexture.prototype, "lineHeight", {
+            get: function () {
+                return this._lineHeight;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        FontTexture.GetCachedFontTexture = function (scene, fontName) {
+            var s = scene;
+            if (!s.__fontTextureCache__) {
+                s.__fontTextureCache__ = new BABYLON.StringDictionary();
+            }
+            var dic = s.__fontTextureCache__;
+            var lfn = fontName.toLocaleLowerCase();
+            var ft = dic.get(lfn);
+            if (ft) {
+                ++ft._usedCounter;
+                return ft;
+            }
+            ft = new FontTexture(null, lfn, scene);
+            dic.add(lfn, ft);
+            return ft;
+        };
+        FontTexture.ReleaseCachedFontTexture = function (scene, fontName) {
+            var s = scene;
+            var dic = s.__fontTextureCache__;
+            if (!dic) {
+                return;
+            }
+            var lfn = fontName.toLocaleLowerCase();
+            var font = dic.get(lfn);
+            if (--font._usedCounter === 0) {
+                dic.remove(lfn);
+                font.dispose();
+            }
+        };
+        /**
+         * Make sure the given char is present in the font map.
+         * @param char the character to get or add
+         * @return the CharInfo instance corresponding to the given character
+         */
+        FontTexture.prototype.getChar = function (char) {
+            if (char.length !== 1) {
+                return null;
+            }
+            var info = this._charInfos[char];
+            if (info) {
+                return info;
+            }
+            info = new CharInfo();
+            var measure = this._context.measureText(char);
+            var textureSize = this.getSize();
+            // we reached the end of the current line?
+            var xMargin = 2;
+            var yMargin = 2;
+            var width = measure.width;
+            if (this._currentFreePosition.x + width + xMargin > textureSize.width) {
+                this._currentFreePosition.x = 0;
+                this._currentFreePosition.y += this._lineHeight + yMargin; // +2 for safety marging
+                // 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 - 0.5, this._currentFreePosition.y - this._offset - 0.5);
+            // Fill the CharInfo object
+            info.topLeftUV = new BABYLON.Vector2(this._currentFreePosition.x / textureSize.width, this._currentFreePosition.y / textureSize.height);
+            info.bottomRightUV = new BABYLON.Vector2(info.topLeftUV.x + (width / textureSize.width), info.topLeftUV.y + ((this._lineHeight + 1) / textureSize.height));
+            info.charWidth = width;
+            // Add the info structure
+            this._charInfos[char] = info;
+            this._curCharCount++;
+            // Set the next position
+            this._currentFreePosition.x += width + xMargin;
+            return info;
+        };
+        FontTexture.prototype.measureText = function (text, tabulationSize) {
+            if (tabulationSize === void 0) { tabulationSize = 4; }
+            var maxWidth = 0;
+            var curWidth = 0;
+            var lineCount = 1;
+            var charxpos = 0;
+            // Parse each char of the string
+            for (var _i = 0; _i < text.length; _i++) {
+                var char = text[_i];
+                // Next line feed?
+                if (char === "\n") {
+                    maxWidth = Math.max(maxWidth, curWidth);
+                    charxpos = 0;
+                    curWidth = 0;
+                    ++lineCount;
+                    continue;
+                }
+                // Tabulation ?
+                if (char === "\t") {
+                    var nextPos = charxpos + tabulationSize;
+                    nextPos = nextPos - (nextPos % tabulationSize);
+                    curWidth += (nextPos - charxpos) * this.spaceWidth;
+                    charxpos = nextPos;
+                    continue;
+                }
+                if (char < " ") {
+                    continue;
+                }
+                curWidth += this.getChar(char).charWidth;
+                ++charxpos;
+            }
+            maxWidth = Math.max(maxWidth, curWidth);
+            return new BABYLON.Size(maxWidth, lineCount * this._lineHeight);
+        };
+        // More info here: https://videlais.com/2014/03/16/the-many-and-varied-problems-with-measuring-font-height-for-html5-canvas/
+        FontTexture.prototype.getFontHeight = function (font) {
+            var fontDraw = document.createElement("canvas");
+            var ctx = fontDraw.getContext('2d');
+            ctx.fillRect(0, 0, fontDraw.width, fontDraw.height);
+            ctx.textBaseline = 'top';
+            ctx.fillStyle = 'white';
+            ctx.font = font;
+            ctx.fillText('jH|', 0, 0);
+            var pixels = ctx.getImageData(0, 0, fontDraw.width, fontDraw.height).data;
+            var start = -1;
+            var end = -1;
+            for (var row = 0; row < fontDraw.height; row++) {
+                for (var column = 0; column < fontDraw.width; column++) {
+                    var index = (row * fontDraw.width + column) * 4;
+                    if (pixels[index] === 0) {
+                        if (column === fontDraw.width - 1 && start !== -1) {
+                            end = row;
+                            row = fontDraw.height;
+                            break;
+                        }
+                        continue;
+                    }
+                    else {
+                        if (start === -1) {
+                            start = row;
+                        }
+                        break;
+                    }
+                }
+            }
+            return { height: end - start, offset: start - 1 };
+        };
+        Object.defineProperty(FontTexture.prototype, "canRescale", {
+            get: function () {
+                return false;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        FontTexture.prototype.getContext = function () {
+            return this._context;
+        };
+        /**
+         * Call this method when you've call getChar() at least one time, this will update the texture if needed.
+         * Don't be afraid to call it, if no new character was added, this method simply does nothing.
+         */
+        FontTexture.prototype.update = function () {
+            // Update only if there's new char added since the previous update
+            if (this._lastUpdateCharCount < this._curCharCount) {
+                this.getScene().getEngine().updateDynamicTexture(this._texture, this._canvas, false, true);
+                this._lastUpdateCharCount = this._curCharCount;
+            }
+        };
+        // cloning should be prohibited, there's no point to duplicate this texture at all
+        FontTexture.prototype.clone = function () {
+            return null;
+        };
+        return FontTexture;
+    })(BABYLON.Texture);
+    BABYLON.FontTexture = FontTexture;
+})(BABYLON || (BABYLON = {}));

+ 97 - 95
src/Materials/Textures/babylon.mapTexture.js

@@ -1,95 +1,97 @@
-var __extends = (this && this.__extends) || function (d, b) {
-    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
-    function __() { this.constructor = d; }
-    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-};
-var BABYLON;
-(function (BABYLON) {
-    var MapTexture = (function (_super) {
-        __extends(MapTexture, _super);
-        function MapTexture(name, scene, size, samplingMode) {
-            if (samplingMode === void 0) { samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE; }
-            _super.call(this, null, scene, true, false, samplingMode);
-            this.name = name;
-            this._size = size;
-            this.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
-            this.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
-            // Create the rectPackMap that will allocate portion of the texture
-            this._rectPackingMap = new BABYLON.RectPackingMap(new BABYLON.Size(size.width, size.height));
-            // Create the texture that will store the content
-            this._texture = scene.getEngine().createRenderTargetTexture(size, { generateMipMaps: !this.noMipmap, type: BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT });
-        }
-        /**
-         * Allocate a rectangle of a given size in the texture map
-         * @param size the size of the rectangle to allocation
-         * @return the PackedRect instance corresponding to the allocated rect or null is there was not enough space to allocate it.
-         */
-        MapTexture.prototype.allocateRect = function (size) {
-            return this._rectPackingMap.addRect(size);
-        };
-        /**
-         * Free a given rectangle from the texture map
-         * @param rectInfo the instance corresponding to the rect to free.
-         */
-        MapTexture.prototype.freeRect = function (rectInfo) {
-            if (rectInfo) {
-                rectInfo.freeContent();
-            }
-        };
-        Object.defineProperty(MapTexture.prototype, "freeSpace", {
-            /**
-             * Return the avaible space in the range of [O;1]. 0 being not space left at all, 1 being an empty texture map.
-             * This is the cumulated space, not the biggest available surface. Due to fragmentation you may not allocate a rect corresponding to this surface.
-             * @returns {}
-             */
-            get: function () {
-                return this._rectPackingMap.freeSpace;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        /**
-         * Bind the texture to the rendering engine to render in the zone of a given rectangle.
-         * Use this method when you want to render into the texture map with a clipspace set to the location and size of the given rect.
-         * Don't forget to call unbindTexture when you're done rendering
-         * @param rect the zone to render to
-         */
-        MapTexture.prototype.bindTextureForRect = function (rect) {
-            var engine = this.getScene().getEngine();
-            engine.bindFramebuffer(this._texture);
-            this._replacedViewport = engine.setDirectViewport(rect.pos.x, rect.pos.y, rect.contentSize.width, rect.contentSize.height);
-        };
-        /**
-         * Unbind the texture map from the rendering engine.
-         * Call this method when you're done rendering. A previous call to bindTextureForRect has to be made.
-         * @param dumpForDebug if set to true the content of the texture map will be dumped to a picture file that will be sent to the internet browser.
-         */
-        MapTexture.prototype.unbindTexture = function (dumpForDebug) {
-            // Dump ?
-            if (dumpForDebug) {
-                BABYLON.Tools.DumpFramebuffer(this._size.width, this._size.height, this.getScene().getEngine());
-            }
-            var engine = this.getScene().getEngine();
-            if (this._replacedViewport) {
-                engine.setViewport(this._replacedViewport);
-                this._replacedViewport = null;
-            }
-            engine.unBindFramebuffer(this._texture);
-        };
-        Object.defineProperty(MapTexture.prototype, "canRescale", {
-            get: function () {
-                return false;
-            },
-            enumerable: true,
-            configurable: true
-        });
-        // Note, I don't know what behevior this method should have: clone the underlying texture/rectPackingMap or just reference them?
-        // Anyway, there's not much point to use this method for this kind of texture I guess
-        MapTexture.prototype.clone = function () {
-            return null;
-        };
-        return MapTexture;
-    }(BABYLON.Texture));
-    BABYLON.MapTexture = MapTexture;
-})(BABYLON || (BABYLON = {}));
-//# sourceMappingURL=babylon.mapTexture.js.map
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    var MapTexture = (function (_super) {
+        __extends(MapTexture, _super);
+        function MapTexture(name, scene, size, samplingMode) {
+            if (samplingMode === void 0) { samplingMode = BABYLON.Texture.TRILINEAR_SAMPLINGMODE; }
+            _super.call(this, null, scene, true, false, samplingMode);
+            this.name = name;
+            this._size = size;
+            this.wrapU = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            this.wrapV = BABYLON.Texture.CLAMP_ADDRESSMODE;
+            // Create the rectPackMap that will allocate portion of the texture
+            this._rectPackingMap = new BABYLON.RectPackingMap(new BABYLON.Size(size.width, size.height));
+            // Create the texture that will store the content
+            this._texture = scene.getEngine().createRenderTargetTexture(size, { generateMipMaps: !this.noMipmap, type: BABYLON.Engine.TEXTURETYPE_UNSIGNED_INT });
+        }
+        /**
+         * Allocate a rectangle of a given size in the texture map
+         * @param size the size of the rectangle to allocation
+         * @return the PackedRect instance corresponding to the allocated rect or null is there was not enough space to allocate it.
+         */
+        MapTexture.prototype.allocateRect = function (size) {
+            return this._rectPackingMap.addRect(size);
+        };
+        /**
+         * Free a given rectangle from the texture map
+         * @param rectInfo the instance corresponding to the rect to free.
+         */
+        MapTexture.prototype.freeRect = function (rectInfo) {
+            if (rectInfo) {
+                rectInfo.freeContent();
+            }
+        };
+        Object.defineProperty(MapTexture.prototype, "freeSpace", {
+            /**
+             * Return the avaible space in the range of [O;1]. 0 being not space left at all, 1 being an empty texture map.
+             * This is the cumulated space, not the biggest available surface. Due to fragmentation you may not allocate a rect corresponding to this surface.
+             * @returns {}
+             */
+            get: function () {
+                return this._rectPackingMap.freeSpace;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Bind the texture to the rendering engine to render in the zone of a given rectangle.
+         * Use this method when you want to render into the texture map with a clipspace set to the location and size of the given rect.
+         * Don't forget to call unbindTexture when you're done rendering
+         * @param rect the zone to render to
+         */
+        MapTexture.prototype.bindTextureForRect = function (rect, clear) {
+            var engine = this.getScene().getEngine();
+            engine.bindFramebuffer(this._texture);
+            this._replacedViewport = engine.setDirectViewport(rect.pos.x, rect.pos.y, rect.contentSize.width, rect.contentSize.height);
+            if (clear) {
+                engine.clear(new BABYLON.Color4(0, 0, 0, 0), true, true);
+            }
+        };
+        /**
+         * Unbind the texture map from the rendering engine.
+         * Call this method when you're done rendering. A previous call to bindTextureForRect has to be made.
+         * @param dumpForDebug if set to true the content of the texture map will be dumped to a picture file that will be sent to the internet browser.
+         */
+        MapTexture.prototype.unbindTexture = function (dumpForDebug) {
+            // Dump ?
+            if (dumpForDebug) {
+                BABYLON.Tools.DumpFramebuffer(this._size.width, this._size.height, this.getScene().getEngine());
+            }
+            var engine = this.getScene().getEngine();
+            if (this._replacedViewport) {
+                engine.setViewport(this._replacedViewport);
+                this._replacedViewport = null;
+            }
+            engine.unBindFramebuffer(this._texture);
+        };
+        Object.defineProperty(MapTexture.prototype, "canRescale", {
+            get: function () {
+                return false;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        // Note, I don't know what behevior this method should have: clone the underlying texture/rectPackingMap or just reference them?
+        // Anyway, there's not much point to use this method for this kind of texture I guess
+        MapTexture.prototype.clone = function () {
+            return null;
+        };
+        return MapTexture;
+    })(BABYLON.Texture);
+    BABYLON.MapTexture = MapTexture;
+})(BABYLON || (BABYLON = {}));

+ 8 - 0
src/Materials/Textures/babylon.texture.js

@@ -59,6 +59,13 @@ var BABYLON;
                 });
             }
         }
+        Object.defineProperty(Texture.prototype, "noMipmap", {
+            get: function () {
+                return this._noMipmap;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Texture.prototype.delayLoad = function () {
             if (this.delayLoadState !== BABYLON.Engine.DELAYLOADSTATE_NOTLOADED) {
                 return;
@@ -76,6 +83,7 @@ var BABYLON;
             if (!this._texture) {
                 return;
             }
+            this._samplingMode = samplingMode;
             this.getScene().getEngine().updateTextureSamplingMode(samplingMode, this._texture);
         };
         Texture.prototype._prepareRowForTextureGeneration = function (x, y, z, t) {

+ 7 - 1
src/Math/babylon.math.js

@@ -483,9 +483,15 @@ var BABYLON;
             return new Vector2(x, y);
         };
         Vector2.Transform = function (vector, transformation) {
+            var r = Vector2.Zero();
+            Vector2.TransformToRef(vector, transformation, r);
+            return r;
+        };
+        Vector2.TransformToRef = function (vector, transformation, result) {
             var x = (vector.x * transformation.m[0]) + (vector.y * transformation.m[4]) + transformation.m[12];
             var y = (vector.x * transformation.m[1]) + (vector.y * transformation.m[5]) + transformation.m[13];
-            return new Vector2(x, y);
+            result.x = x;
+            result.y = y;
         };
         Vector2.Distance = function (value1, value2) {
             return Math.sqrt(Vector2.DistanceSquared(value1, value2));

+ 10 - 3
src/Tools/babylon.stringDictionary.js

@@ -48,9 +48,9 @@ var BABYLON;
          * @return the value corresponding to the key
          */
         StringDictionary.prototype.getOrAdd = function (key, val) {
-            var val = this.get(key);
-            if (val !== undefined) {
-                return val;
+            var curVal = this.get(key);
+            if (curVal !== undefined) {
+                return curVal;
             }
             this.add(key, val);
             return val;
@@ -77,6 +77,13 @@ var BABYLON;
             ++this._count;
             return true;
         };
+        StringDictionary.prototype.set = function (key, value) {
+            if (this._data[key] === undefined) {
+                return false;
+            }
+            this._data[key] = value;
+            return true;
+        };
         /**
          * Remove a key/value from the dictionary.
          * @param key the key to remove

+ 38 - 5
src/Tools/babylon.tools.js

@@ -588,11 +588,6 @@ var BABYLON;
             }
             return false;
         };
-        Tools.getClassName = function (obj) {
-            var funcNameRegex = /function (.{1,})\(/;
-            var results = (funcNameRegex).exec((obj).constructor.toString());
-            return (results && results.length > 1) ? results[1] : "";
-        };
         Object.defineProperty(Tools, "NoneLogLevel", {
             get: function () {
                 return Tools._NoneLogLevel;
@@ -790,6 +785,32 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        /**
+         * This method will return the name of the class used to create the instance of the given object.
+         * It will works only on Javascript basic data types (number, string, ...) and instance of class declared with the @className decorator.
+         * @param object the object to get the class name from
+         * @return the name of the class, will be "object" for a custom data type not using the @className decorator
+         */
+        Tools.getClassName = function (object, isType) {
+            if (isType === void 0) { isType = false; }
+            var name = null;
+            if (object instanceof Object) {
+                var classObj = isType ? object : Object.getPrototypeOf(object);
+                name = classObj.constructor["__bjsclassName__"];
+            }
+            if (!name) {
+                name = typeof object;
+            }
+            return name;
+        };
+        Tools.first = function (array, predicate) {
+            for (var _i = 0; _i < array.length; _i++) {
+                var el = array[_i];
+                if (predicate(el)) {
+                    return el;
+                }
+            }
+        };
         Tools.BaseUrl = "";
         Tools.CorsBehavior = "anonymous";
         Tools.UseFallbackTexture = true;
@@ -814,6 +835,18 @@ var BABYLON;
     })();
     BABYLON.Tools = Tools;
     /**
+     * Use this className as a decorator on a given class definition to add it a name.
+     * You can then use the Tools.getClassName(obj) on an instance to retrieve its class name.
+     * This method is the only way to get it done in all cases, even if the .js file declaring the class is minified
+     * @param name
+     */
+    function className(name) {
+        return function (target) {
+            target["__bjsclassName__"] = name;
+        };
+    }
+    BABYLON.className = className;
+    /**
      * An implementation of a loop for asynchronous functions.
      */
     var AsyncLoop = (function () {

+ 60 - 11
src/babylon.engine.js

@@ -99,6 +99,12 @@ var BABYLON;
             partialLoad(files[index], index, loadedImages, scene, onfinish);
         }
     };
+    var InstancingAttributeInfo = (function () {
+        function InstancingAttributeInfo() {
+        }
+        return InstancingAttributeInfo;
+    })();
+    BABYLON.InstancingAttributeInfo = InstancingAttributeInfo;
     var EngineCapabilities = (function () {
         function EngineCapabilities() {
         }
@@ -479,6 +485,12 @@ var BABYLON;
         Engine.prototype.resetDrawCalls = function () {
             this._drawCalls = 0;
         };
+        Engine.prototype.getDepthFunction = function () {
+            return this._depthCullingState.depthFunc;
+        };
+        Engine.prototype.setDepthFunction = function (depthFunc) {
+            this._depthCullingState.depthFunc = depthFunc;
+        };
         Engine.prototype.setDepthFunctionToGreater = function () {
             this._depthCullingState.depthFunc = this._gl.GREATER;
         };
@@ -591,9 +603,16 @@ var BABYLON;
             this._cachedViewport = viewport;
             this._gl.viewport(x * width, y * height, width * viewport.width, height * viewport.height);
         };
+        /**
+         * Directly set the WebGL Viewport
+         * The x, y, width & height are directly passed to the WebGL call
+         * @return the current viewport Object (if any) that is being replaced by this call. You can restore this viewport later on to go back to the original state.
+         */
         Engine.prototype.setDirectViewport = function (x, y, width, height) {
+            var currentViewport = this._cachedViewport;
             this._cachedViewport = null;
             this._gl.viewport(x, y, width, height);
+            return currentViewport;
         };
         Engine.prototype.beginFrame = function () {
             this._measureFps();
@@ -807,20 +826,46 @@ var BABYLON;
         };
         Engine.prototype.updateAndBindInstancesBuffer = function (instancesBuffer, data, offsetLocations) {
             this._gl.bindBuffer(this._gl.ARRAY_BUFFER, instancesBuffer);
-            this._gl.bufferSubData(this._gl.ARRAY_BUFFER, 0, data);
-            for (var index = 0; index < 4; index++) {
-                var offsetLocation = offsetLocations[index];
-                this._gl.enableVertexAttribArray(offsetLocation);
-                this._gl.vertexAttribPointer(offsetLocation, 4, this._gl.FLOAT, false, 64, index * 16);
-                this._caps.instancedArrays.vertexAttribDivisorANGLE(offsetLocation, 1);
+            if (data) {
+                this._gl.bufferSubData(this._gl.ARRAY_BUFFER, 0, data);
+            }
+            if (offsetLocations[0].index !== undefined) {
+                var stride = 0;
+                for (var i = 0; i < offsetLocations.length; i++) {
+                    var ai = offsetLocations[i];
+                    stride += ai.attributeSize * 4;
+                }
+                for (var i = 0; i < offsetLocations.length; i++) {
+                    var ai = offsetLocations[i];
+                    this._gl.enableVertexAttribArray(ai.index);
+                    this._gl.vertexAttribPointer(ai.index, ai.attributeSize, ai.attribyteType || this._gl.FLOAT, ai.normalized || false, stride, ai.offset);
+                    this._caps.instancedArrays.vertexAttribDivisorANGLE(ai.index, 1);
+                }
+            }
+            else {
+                for (var index = 0; index < 4; index++) {
+                    var offsetLocation = offsetLocations[index];
+                    this._gl.enableVertexAttribArray(offsetLocation);
+                    this._gl.vertexAttribPointer(offsetLocation, 4, this._gl.FLOAT, false, 64, index * 16);
+                    this._caps.instancedArrays.vertexAttribDivisorANGLE(offsetLocation, 1);
+                }
             }
         };
         Engine.prototype.unBindInstancesBuffer = function (instancesBuffer, offsetLocations) {
             this._gl.bindBuffer(this._gl.ARRAY_BUFFER, instancesBuffer);
-            for (var index = 0; index < 4; index++) {
-                var offsetLocation = offsetLocations[index];
-                this._gl.disableVertexAttribArray(offsetLocation);
-                this._caps.instancedArrays.vertexAttribDivisorANGLE(offsetLocation, 0);
+            if (offsetLocations[0].index !== undefined) {
+                for (var i = 0; i < offsetLocations.length; i++) {
+                    var ai = offsetLocations[i];
+                    this._gl.disableVertexAttribArray(ai.index);
+                    this._caps.instancedArrays.vertexAttribDivisorANGLE(ai.index, 0);
+                }
+            }
+            else {
+                for (var index = 0; index < 4; index++) {
+                    var offsetLocation = offsetLocations[index];
+                    this._gl.disableVertexAttribArray(offsetLocation);
+                    this._caps.instancedArrays.vertexAttribDivisorANGLE(offsetLocation, 0);
+                }
             }
         };
         Engine.prototype.applyStates = function () {
@@ -1338,9 +1383,13 @@ var BABYLON;
                 this._gl.bindTexture(this._gl.TEXTURE_2D, null);
             }
         };
-        Engine.prototype.updateDynamicTexture = function (texture, canvas, invertY) {
+        Engine.prototype.updateDynamicTexture = function (texture, canvas, invertY, premulAlpha) {
+            if (premulAlpha === void 0) { premulAlpha = false; }
             this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY ? 1 : 0);
+            if (premulAlpha) {
+                this._gl.pixelStorei(this._gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
+            }
             this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, this._gl.RGBA, this._gl.UNSIGNED_BYTE, canvas);
             if (texture.generateMipMaps) {
                 this._gl.generateMipmap(this._gl.TEXTURE_2D);