浏览代码

- Changed the Tools.getClassName() implementation as the old one couldn't work with classes defined in a minified .js file. You know have to use the class decorator @className on all class definition you whish to call getClassName() upon.
- Engine.setDirectViewport() now return the Viewport object that is replaced to let the user restore it afterward.
- MapTexture bind/unbindTedxtureForRect() saves/restores the Viewport already set.
- SmartPropertyPrim implementation is changed to no longer rely on the class' name but on the type's prototype instead to make it works in all situations.
- Canvas' static methods are now named starting with an upper case
- Classes definition of the Canvas2D are now using the @className decorator.
- In file documentation added

nockawa 9 年之前
父节点
当前提交
c1009a2c12

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

@@ -0,0 +1,102 @@
+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

+ 40 - 0
src/Canvas2d/babylon.bounding2d.ts

@@ -1,12 +1,30 @@
 module BABYLON {
+
+    /**
+     * Stores 2D Bounding Information.
+     * This class handles a circle area and a bounding rectangle one.
+     */
     export class BoundingInfo2D {
+
+        /**
+         * The radius of the bounding circle, from the origin of the bounded object
+         */
         public radius: number;
+
+        /**
+         * The extent of the bounding rectangle, from the orgini of the bounded object.
+         * This is an absolute value in both X and Y of the vector which describe the right/top corner of the rectangle, you can easily reconstruct the whole rectangle by negating X &| Y.
+         */
         public extent: Size;
 
         constructor() {
             this.extent = new Size(0, 0);
         }
 
+        /**
+         * Duplicate this instance and return a new one
+         * @return the duplicated instance
+         */
         public clone(): BoundingInfo2D {
             let r = new BoundingInfo2D();
             r.radius = this.radius;
@@ -14,18 +32,34 @@
             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
+         */
         public transform(matrix: Matrix): BoundingInfo2D {
             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
+         */
         public union(other: BoundingInfo2D): BoundingInfo2D {
             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
+         */
         public transformToRef(matrix: Matrix, result: BoundingInfo2D) {
             // Extract scale from matrix
             let xs = MathTools.Sign(matrix.m[0] * matrix.m[1] * matrix.m[2] * matrix.m[3]) < 0 ? -1 : 1;
@@ -74,6 +108,12 @@
             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
+         */
         public unionToRef(other: BoundingInfo2D, result: BoundingInfo2D) {
             result.radius = Math.max(this.radius, other.radius);
             result.extent.width = Math.max(this.extent.width, other.extent.width);

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

@@ -0,0 +1,113 @@
+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

+ 66 - 7
src/Canvas2d/babylon.brushes2d.ts

@@ -1,17 +1,52 @@
 module BABYLON {
+    /**
+     * This interface is used to implement a lockable instance pattern.
+     * Classes that implements it may be locked at any time, making their content immutable from now on.
+     * You also can query if a given instance is locked or not.
+     * This allow instances to be shared among several 'consumers'.
+     */
+    export interface ILockable {
+        /**
+         * Query the lock state
+         * @returns returns true if the object is locked and immutable, false if it's not
+         */
+        isLocked(): boolean;
+
+        /**
+         * A call to this method will definitely lock the instance, making its content immutable
+         * @returns the previous lock state of the object. so if true is returned the object  were already locked and this method does nothing, if false is returned it means the object wasn't locked and this call locked it for good.
+         */
+        lock(): boolean;
+    }
+
+    /**
+     * This interface defines the IBorder2D contract.
+     * Classes implementing a new type of 2D Border must implement this interface
+     */
     export interface IBorder2D extends ILockable {
+        /**
+         * It is critical for each instance of a given Border2D type to return a unique string that identifies it because the Border instance will certainly be part of the computed ModelKey for a given Primitive
+         * @returns A string identifier that uniquely identify the instance
+         */
         toString(): string;
     }
 
+    /**
+     * This interface defines the IFill2D contract.
+     * Classes implementing a new type of 2D Fill must implement this interface
+     */
     export interface IFill2D extends ILockable {
+        /**
+         * It is critical for each instance of a given Fill2D type to return a unique string that identifies it because the Fill instance will certainly be part of the computed ModelKey for a given Primitive
+         * @returns A string identifier that uniquely identify the instance
+         */
         toString(): string;
     }
 
-    export interface ILockable {
-        isLocked(): boolean;
-        lock();
-    }
-
+    /**
+     * 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.
+     */
     export class LockableBase implements ILockable {
         isLocked(): boolean {
             return this._isLocked;
@@ -19,20 +54,27 @@
 
         private _isLocked: boolean;
 
-        lock() {
+        lock(): boolean {
             if (this._isLocked) {
-                return;
+                return true;
             }
 
             this.onLock();
             this._isLocked = true;
+            return false;
         }
 
+        /**
+         * Protected handler that will be called when the instance is about to be locked.
+         */
         protected onLock() {
 
         }
     }
 
+    /**
+     * This classs implements a Border that will be drawn with a uniform solid color (i.e. the same color everywhere in the border).
+     */
     export class SolidColorBorder2D extends LockableBase implements IBorder2D {
         constructor(color: Color4, lock: boolean = false) {
             super();
@@ -42,6 +84,10 @@
             }
         }
 
+        /**
+         * 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.
+         */
         public get color(): Color4 {
             return this._color;
         }
@@ -54,12 +100,18 @@
             this._color = value;
         }
 
+        /**
+         * Return a unique identifier of the instance, which is simply the hexadecimal representation (CSS Style) of the solid color.
+         */
         public toString(): string {
             return this._color.toHexString();
         }
         private _color: Color4;
     }
 
+    /**
+     * This class implements a Fill that will be drawn with a uniform solid color (i.e. the same everywhere inside the primitive).
+     */
     export class SolidColorFill2D extends LockableBase implements IFill2D {
         constructor(color: Color4, lock: boolean = false) {
             super();
@@ -69,6 +121,10 @@
             }
         }
 
+        /**
+         * 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.
+         */
         public get color(): Color4 {
             return this._color;
         }
@@ -81,6 +137,9 @@
             this._color = value;
         }
 
+        /**
+         * Return a unique identifier of the instance, which is simply the hexadecimal representation (CSS Style) of the solid color.
+         */
         public toString(): string {
             return this._color.toHexString();
         }

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

@@ -0,0 +1,305 @@
+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

+ 68 - 4
src/Canvas2d/babylon.canvas2d.ts

@@ -7,6 +7,7 @@
         groupSprites: Array<{ group: Group2D, sprite: Sprite2D }>;
     }
 
+    @className("Canvas2D")
     export class Canvas2D extends Group2D {
         /**
          * 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.
@@ -92,18 +93,36 @@
             this._isScreeSpace = isScreenSpace;
         }
 
+        /**
+         * Accessor to the Scene that owns the Canvas
+         * @returns The instance of the Scene object
+         */
         public get scene(): Scene {
             return this._scene;
         }
 
+        /**
+         * Accessor to the Engine that drives the Scene used by this Canvas
+         * @returns The instance of the Engine object
+         */
         public get engine(): Engine {
             return this._engine;
         }
 
+        /**
+         * 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.
+         */
         public get cachingStrategy(): number {
             return this._cachingStrategy;
         }
 
+        /**
+         * 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.
+         */
         public get backgroundFill(): IFill2D {
             if (!this._background || !this._background.isVisible) {
                 return null;
@@ -122,6 +141,10 @@
             this._background.isVisible = true;
         }
 
+        /**
+         * 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.
+         */
         public get border(): IBorder2D {
             if (!this._background || !this._background.isVisible) {
                 return null;
@@ -146,9 +169,16 @@
             }
         }
 
+        /**
+         * 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
+         */
         public get hierarchySiblingZDelta(): number {
             return this._hierarchySiblingZDelta;
         }
+
         private _mapCounter = 0;
         private _background: Rectangle2D;
         private _scene: Scene;
@@ -162,6 +192,10 @@
         private _groupCacheMaps: GroupsCacheMap[];
         public _renderingSize: Size;
 
+        /**
+         * Method that renders the Canvas
+         * @param camera the current camera.
+         */
         public render(camera: Camera) {
             this._renderingSize.width = this.engine.getRenderWidth();
             this._renderingSize.height = this.engine.getRenderHeight();
@@ -179,6 +213,12 @@
             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.
+         */
         public _allocateGroupCache(group: Group2D): { node: PackedRect, texture: MapTexture } {
             // Determine size
             let size = group.actualSize;
@@ -223,21 +263,45 @@
             return res;
         }
 
+        /**
+         * 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
+         */
         private static _groupTextureCacheSize = 1024;
 
-        public static getSolidColorFill(color: Color4): IFill2D {
+        /**
+         * 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
+         */
+        public static GetSolidColorFill(color: Color4): IFill2D {
             return Canvas2D._solidColorFills.getOrAddWithFactory(color.toHexString(), () => new SolidColorFill2D(color.clone(), true));
         }
 
-        public static getSolidColorBorder(color: Color4): IBorder2D {
+        /**
+         * 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
+         */
+        public static GetSolidColorBorder(color: Color4): IBorder2D {
             return Canvas2D._solidColorBorders.getOrAddWithFactory(color.toHexString(), () => new SolidColorBorder2D(color.clone(), true));
         }
 
-        public static getSolidColorFillFromHex(hexValue: string): IFill2D {
+        /**
+         * 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
+         */
+        public static GetSolidColorFillFromHex(hexValue: string): IFill2D {
             return Canvas2D._solidColorFills.getOrAddWithFactory(hexValue, () => new SolidColorFill2D(Color4.FromHexString(hexValue), true));
         }
 
-        public static getSolidColorBorderFromHex(hexValue: string): IBorder2D {
+        /**
+         * 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
+         */
+        public static GetSolidColorBorderFromHex(hexValue: string): IBorder2D {
             return Canvas2D._solidColorBorders.getOrAddWithFactory(hexValue, () => new SolidColorBorder2D(Color4.FromHexString(hexValue), true));
         }
 

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

@@ -0,0 +1,341 @@
+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

+ 7 - 6
src/Canvas2d/babylon.group2d.ts

@@ -1,4 +1,5 @@
 module BABYLON {
+    @className("Group2D")
     export class Group2D extends Prim2DBase {
         static GROUP2D_PROPCOUNT: number = Prim2DBase.PRIM2DBASE_PROPCOUNT + 10;
 
@@ -25,11 +26,7 @@
             this.groupRenderInfo = new StringDictionary<GroupInstanceInfo>();
         }
 
-        static CreateGroup2D(parent: Prim2DBase,
-            id: string,
-            position: Vector2,
-            size?: Size,
-            cacheBehabior: number = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY): Group2D {
+        static CreateGroup2D(parent: Prim2DBase, id: string, position: Vector2, size?: Size, cacheBehabior: number = Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY): Group2D {
             Prim2DBase.CheckParent(parent);
             var g = new Group2D();
             g.setupGroup2D(parent.owner, parent, id, position, size, cacheBehabior);
@@ -218,7 +215,7 @@
                 if (this.isCachedGroup) {
                     this._bindCacheTarget();
                 } else {
-                    engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
+                    var curVP = engine.setDirectViewport(this._viewportPosition.x, this._viewportPosition.y, this._viewportSize.width, this._viewportSize.height);
                 }
 
                 // For each different model of primitive to render
@@ -261,6 +258,10 @@
 
                 if (this.isCachedGroup) {
                     this._unbindCacheTarget();
+                } else {
+                    if (curVP) {
+                        engine.setViewport(curVP);
+                    }
                 }
             }
         }

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

@@ -0,0 +1,54 @@
+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

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

@@ -0,0 +1,360 @@
+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

+ 1 - 0
src/Canvas2d/babylon.prim2dBase.ts

@@ -7,6 +7,7 @@
         forceRefreshPrimitive: boolean;
     }
 
+    @className("Prim2DBase")
     export class Prim2DBase extends SmartPropertyPrim {
         static PRIM2DBASE_PROPCOUNT: number = 10;
 

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

@@ -0,0 +1,174 @@
+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

+ 3 - 2
src/Canvas2d/babylon.rectangle2d.ts

@@ -40,6 +40,7 @@
         }
     }
 
+    @className("Rectangle2D")
     export class Rectangle2D extends RenderablePrim2D<Rectangle2DInstanceData> {
 
         public static sizeProperty: Prim2DPropInfo;
@@ -91,7 +92,7 @@
 
             let rect = new Rectangle2D();
             rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), null);
-            rect.fill = fill || Canvas2D.getSolidColorFillFromHex("#FFFFFFFF");
+            rect.fill = fill || Canvas2D.GetSolidColorFillFromHex("#FFFFFFFF");
             rect.border = border;
             return rect;
         }
@@ -101,7 +102,7 @@
 
             let rect = new Rectangle2D();
             rect.setupRectangle2D(parent.owner, parent, id, new Vector2(x, y), new Size(width, height), roundRadius);
-            rect.fill = fill || Canvas2D.getSolidColorFillFromHex("#FFFFFFFF");
+            rect.fill = fill || Canvas2D.GetSolidColorFillFromHex("#FFFFFFFF");
             rect.border = border;
             return rect;
         }

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

@@ -0,0 +1,372 @@
+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

+ 8 - 3
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -200,14 +200,19 @@
         }
 
         getClassTreeInfo(): ClassTreeInfo<InstanceClassInfo, InstancePropInfo> {
-            return ClassTreeInfo.get<InstanceClassInfo, InstancePropInfo>(this);
+            if (!this._typeInfo) {
+                this._typeInfo = ClassTreeInfo.get<InstanceClassInfo, InstancePropInfo>(Object.getPrototypeOf(this));
+            }
+            return this._typeInfo;
         }
 
         _dataElement: DynamicFloatArrayElementInfo;
         _dataBuffer: DynamicFloatArray;
+        _typeInfo: ClassTreeInfo<InstanceClassInfo, InstancePropInfo>;
     }
 
-   export class RenderablePrim2D<TInstData extends InstanceDataBase> extends Prim2DBase {
+    @className("RenderablePrim2D")
+    export class RenderablePrim2D<TInstData extends InstanceDataBase> extends Prim2DBase {
         static RENDERABLEPRIM2D_PROPCOUNT: number = Prim2DBase.PRIM2DBASE_PROPCOUNT + 10;
 
         public static borderProperty: Prim2DPropInfo;
@@ -279,7 +284,7 @@
                     var size = 0;
                     cti.fullContent.forEach((k, v) => {
                         if (!v.size) {
-                            console.log(`ERROR: Couldn't detect the size of the Property ${v.attributeName} from type ${cti.typeName}. Property is ignored.`);
+                            console.log(`ERROR: Couldn't detect the size of the Property ${v.attributeName} from type ${Tools.getClassName(cti.type)}. Property is ignored.`);
                         } else {
                             size += v.size;
                         }

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

@@ -0,0 +1,344 @@
+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

+ 52 - 32
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -27,10 +27,10 @@
     }
 
     export class ClassTreeInfo<TClass, TProp>{
-        constructor(baseClass: ClassTreeInfo<TClass, TProp>, typeName: string, classContentFactory: (base: TClass) => TClass) {
+        constructor(baseClass: ClassTreeInfo<TClass, TProp>, type: Object, classContentFactory: (base: TClass) => TClass) {
             this._baseClass = baseClass;
-            this._typeName = typeName;
-            this._subClasses = new StringDictionary<ClassTreeInfo<TClass, TProp>>();
+            this._type = type;
+            this._subClasses = new Array<{ type: Object, node: ClassTreeInfo<TClass, TProp> }>();
             this._levelContent = new StringDictionary<TProp>();
             this._classContentFactory = classContentFactory;
         }
@@ -42,8 +42,8 @@
             return this._classContent;
         }
 
-        get typeName(): string {
-            return this._typeName;
+        get type(): Object {
+            return this._type;
         }
 
         get levelContent(): StringDictionary<TProp> {
@@ -66,62 +66,77 @@
         }
 
         getLevelOf(type: Object): ClassTreeInfo<TClass, TProp> {
-            let typeName = Tools.getClassName(type);
-
             // Are we already there?
-            if (typeName === this._typeName) {
+            if (type === this._type) {
                 return this;
             }
 
             let baseProto = Object.getPrototypeOf(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);
+            return this.getOrAddType(baseProto, type);
 
-            // 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);
+            //// 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);
         }
 
-        getOrAddType(baseTypeName, typeName: string): ClassTreeInfo<TClass, TProp> {
+        getOrAddType(baseType: Object, type: Object): ClassTreeInfo<TClass, TProp> {
 
             // Are we at the level corresponding to the baseType?
             // If so, get or add the level we're looking for
-            if (baseTypeName === this._typeName) {
-                return this._subClasses.getOrAddWithFactory(typeName, k => new ClassTreeInfo<TClass, TProp>(this, typeName, this._classContentFactory));
+            if (baseType === this._type) {
+                for (let subType of this._subClasses) {
+                    if (subType.type === type) {
+                        return subType.node;
+                    }
+                }
+                let node = new ClassTreeInfo<TClass, TProp>(this, type, this._classContentFactory);
+                let info = { type: type, node: node};
+                this._subClasses.push(info);
+                return info.node;
             }
 
             // Recurse down to keep looking for the node corresponding to the baseTypeName
-            return this._subClasses.first<ClassTreeInfo<TClass, TProp>>((key, val) => val.getOrAddType(baseTypeName, typeName));
+            for (let subType of this._subClasses) {
+                let info = subType.node.getOrAddType(baseType, type);
+                if (info) {
+                    return info;
+                }
+            }
+            return null;
         }
 
-        static get<TClass, TProp>(target: Object): ClassTreeInfo<TClass, TProp> {
-            let dic = <ClassTreeInfo<TClass, TProp>>target["__classTreeInfo"];
+        static get<TClass, TProp>(type: Object): ClassTreeInfo<TClass, TProp> {
+            let dic = <ClassTreeInfo<TClass, TProp>>type["__classTreeInfo"];
             if (!dic) {
                 return null;
             }
-            return dic.getLevelOf(target);
+            return dic.getLevelOf(type);
         }
 
-        static getOrRegister<TClass, TProp>(target: Object, classContentFactory: (base: TClass) => TClass): ClassTreeInfo<TClass, TProp> {
-            let dic = <ClassTreeInfo<TClass, TProp>>target["__classTreeInfo"];
+        static getOrRegister<TClass, TProp>(type: Object, classContentFactory: (base: TClass) => TClass): ClassTreeInfo<TClass, TProp> {
+            let dic = <ClassTreeInfo<TClass, TProp>>type["__classTreeInfo"];
             if (!dic) {
-                dic = new ClassTreeInfo<TClass, TProp>(null, Tools.getClassName(target), classContentFactory);
-                target["__classTreeInfo"] = dic;
+                dic = new ClassTreeInfo<TClass, TProp>(null, type, classContentFactory);
+                type["__classTreeInfo"] = dic;
             }
             return dic;
         }
 
-        private _typeName: string;
+        private _type: Object;
         private _classContent: TClass;
         private _baseClass: ClassTreeInfo<TClass, TProp>;
-        private _subClasses: StringDictionary<ClassTreeInfo<TClass, TProp>>;
+        private _subClasses: Array<{type: Object, node: ClassTreeInfo<TClass, TProp>}>;
         private _levelContent: StringDictionary<TProp>;
         private _fullContent: StringDictionary<TProp>;
         private _classContentFactory: (base: TClass) => TClass;
     }
 
+    @className("SmartPropertyPrim")
     export class SmartPropertyPrim implements IPropertyChanged {
 
         protected setupSmartPropertyPrim() {
@@ -167,11 +182,15 @@
         protected static ModelCache: StringDictionary<ModelRenderCacheBase> = new StringDictionary<ModelRenderCacheBase>();
 
         private get propDic(): StringDictionary<Prim2DPropInfo> {
-            let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(this);
-            if (!cti) {
-                throw new Error("Can't access the propDic member in class definition, is this class SmartPropertyPrim based?");
+            if (!this._propInfo) {
+                let cti = ClassTreeInfo.get<Prim2DClassInfo, Prim2DPropInfo>(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 cti.fullContent;
+
+            return this._propInfo;
         }
 
         private static _createPropInfo(target: Object, propName: string, propId: number, dirtyBoundingInfo: boolean, typeLevelCompare: boolean, kind: number): Prim2DPropInfo {
@@ -330,6 +349,7 @@
         }
 
         private _modelKey; string;
+        private _propInfo: StringDictionary<Prim2DPropInfo>;
         private _levelBoundingInfoDirty: boolean;
         protected _levelBoundingInfo: BoundingInfo2D;
         protected _boundingInfo: BoundingInfo2D;

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

@@ -0,0 +1,233 @@
+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

+ 1 - 0
src/Canvas2d/babylon.sprite2d.ts

@@ -62,6 +62,7 @@
         }
     }
 
+    @className("Sprite2D")
     export class Sprite2D extends RenderablePrim2D<Sprite2DInstanceData> {
 
         public static textureProperty: Prim2DPropInfo;

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

@@ -0,0 +1,95 @@
+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

+ 9 - 2
src/Materials/Textures/babylon.mapTexture.ts

@@ -4,6 +4,8 @@
         private _rectPackingMap: RectPackingMap;
         private _size: ISize;
 
+        private _replacedViewport: Viewport;
+
         constructor(name: string, scene: Scene, size: ISize, samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
             super(null, scene, true, false, samplingMode);
 
@@ -56,8 +58,7 @@
         public bindTextureForRect(rect: PackedRect) {
             let engine = this.getScene().getEngine();
             engine.bindFramebuffer(this._texture);
-//            engine.clear(new Color4(0,1,1,0), true, true);
-            engine.setDirectViewport(rect.pos.x, rect.pos.y, rect.contentSize.width, rect.contentSize.height);
+            this._replacedViewport = engine.setDirectViewport(rect.pos.x, rect.pos.y, rect.contentSize.width, rect.contentSize.height);
         }
 
         /**
@@ -72,6 +73,12 @@
             }
 
             let engine = this.getScene().getEngine();
+
+            if (this._replacedViewport) {
+                engine.setViewport(this._replacedViewport);
+                this._replacedViewport = null;
+            }
+
             engine.unBindFramebuffer(this._texture);
         }
 

文件差异内容过多而无法显示
+ 926 - 900
src/Tools/babylon.tools.js


+ 32 - 6
src/Tools/babylon.tools.ts

@@ -693,12 +693,6 @@
             return false;
         }
 
-        public static getClassName(obj): string {
-            var funcNameRegex = /function (.{1,})\(/;
-            var results = (funcNameRegex).exec((obj).constructor.toString());
-            return (results && results.length > 1) ? results[1] : "";
-        }
-
         // Logs
         private static _NoneLogLevel = 0;
         private static _MessageLogLevel = 1;
@@ -907,6 +901,38 @@
 
             return new Date().getTime();
         }
+
+        /**
+         * 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
+         */
+        public static getClassName(object, isType: boolean = false): string {
+            let name = null;
+            if (object instanceof Object) {
+                let classObj = isType ? object :  Object.getPrototypeOf(object);
+                name = classObj.constructor["__bjsclassName__"];
+            }
+            if (!name) {
+                name = typeof object;
+            }
+
+            return name;
+        }
+
+    }
+
+    /**
+     * 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
+     */
+    export function className(name: string): (target: Object) => void {
+        return (target: Object) => {
+            target["__bjsclassName__"] = name;
+        }
     }
 
     /**

文件差异内容过多而无法显示
+ 1996 - 1952
src/babylon.engine.js


+ 9 - 1
src/babylon.engine.ts

@@ -723,10 +723,18 @@
             this._gl.viewport(x * width, y * height, width * viewport.width, height * viewport.height);
         }
 
-        public setDirectViewport(x: number, y: number, width: number, height: number): void {
+        /**
+         * 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.
+         */
+        public setDirectViewport(x: number, y: number, width: number, height: number): Viewport {
+            let currentViewport = this._cachedViewport;
             this._cachedViewport = null;
 
             this._gl.viewport(x, y, width, height);
+
+            return currentViewport;
         }
 
         public beginFrame(): void {