Преглед изворни кода

Alignment/LayoutEngine WIP

nockawa пре 9 година
родитељ
комит
c22290aae6

+ 33 - 7
src/Canvas2d/babylon.canvas2d.ts

@@ -64,7 +64,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        static CreateScreenSpace(scene: Scene, options: { id?: string, x?: number, y?: number, position?: Vector2, origin?: Vector2, width?: number, height?: number, size?: Size, cachingStrategy?: number, enableInteraction?: boolean, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, hAlignment?: number, vAlignment?: number }): Canvas2D {
+        static CreateScreenSpace(scene: Scene, options: { id?: string, x?: number, y?: number, position?: Vector2, origin?: Vector2, width?: number, height?: number, size?: Size, cachingStrategy?: number, enableInteraction?: boolean, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, hAlignment?: number, vAlignment?: number }): Canvas2D {
             let c = new Canvas2D();
 
             if (!options) {
@@ -74,7 +74,7 @@
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
                 let size = (!options.size && !options.width && !options.height) ? null : (options.size || (new Size(options.width || 0, options.height || 0)));
 
-                c.setupCanvas(scene, options.id || null, size, 1, true, options.cachingStrategy || Canvas2D.CACHESTRATEGY_DONTCACHE, options.enableInteraction || true, options.origin || Vector2.Zero(), options.isVisible || true, options.marginTop, options.marginLeft, options.marginRight, options.marginBottom, options.hAlignment || Prim2DBase.HAlignLeft, options.vAlignment || Prim2DBase.VAlignTop);
+                c.setupCanvas(scene, options.id || null, size, 1, true, options.cachingStrategy || Canvas2D.CACHESTRATEGY_DONTCACHE, options.enableInteraction || true, options.origin || Vector2.Zero(), options.isVisible || true, options.marginTop, options.marginLeft, options.marginRight, options.marginBottom, options.hAlignment || PrimitiveAlignment.AlignLeft, options.vAlignment || PrimitiveAlignment.AlignTop);
                 c.position = pos;
             }
 
@@ -145,7 +145,7 @@
             return c;
         }
 
-        protected setupCanvas(scene: Scene, name: string, size: Size, renderScaleFactor: number, isScreenSpace: boolean, cachingstrategy: number, enableInteraction: boolean, origin: Vector2, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, hAlign: number, vAlign: number) {
+        protected setupCanvas(scene: Scene, name: string, size: Size, renderScaleFactor: number, isScreenSpace: boolean, cachingstrategy: number, enableInteraction: boolean, origin: Vector2, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, hAlign: number, vAlign: number) {
             let engine = scene.getEngine();
             this._fitRenderingDevice = !size;
             if (!size) {
@@ -439,6 +439,11 @@
                 return;
             }
 
+            // A little safe guard, it might happens than the event is triggered before the first render and nothing is computed, this simple check will make sure everything will be fine
+            if (!this._globalTransform) {
+                this.updateCachedStates(true);
+            }
+
             let ii = Canvas2D._interInfo;
             ii.pickPosition.x = mouseLocalPos.x;
             ii.pickPosition.y = mouseLocalPos.y;
@@ -578,6 +583,11 @@
 
         private _triggerActionManager(prim: Prim2DBase, ppi: PrimitivePointerInfo, mask: number, eventData) {
 
+            // A little safe guard, it might happens than the event is triggered before the first render and nothing is computed, this simple check will make sure everything will be fine
+            if (!this._globalTransform) {
+                this.updateCachedStates(true);
+            }
+
             // Process Trigger related to PointerDown
             if ((mask & PrimitivePointerInfo.PointerDown) !== 0) {
                 // On pointer down, record the current position and time to be able to trick PickTrigger and LongPressTrigger
@@ -890,20 +900,36 @@
                 return;
             }
 
-            this._renderingSize.width = this.engine.getRenderWidth();
-            this._renderingSize.height = this.engine.getRenderHeight();
+            // Detect a change of rendering size
+            let renderingSizeChanged = false;
+            let newWidth = this.engine.getRenderWidth();
+            if (newWidth !== this._renderingSize.width) {
+                renderingSizeChanged = true;
+            }
+            this._renderingSize.width = newWidth;
 
-            if (this._fitRenderingDevice) {
+            let newHeight = this.engine.getRenderHeight();
+            if (newHeight !== this._renderingSize.height) {
+                renderingSizeChanged = true;
+            }
+            this._renderingSize.height = newHeight;
+
+
+            // If the canvas fit the rendering size and it changed, update
+            if (renderingSizeChanged && this._fitRenderingDevice) {
                 this.size = this._renderingSize;
                 if (this._background) {
                     this._background.size = this.size;
                 }
+
+                // Dirty the Layout at the Canvas level to recompute as the size changed
+                this._setFlags(SmartPropertyPrim.flagLayoutDirty);
             }
 
             var context = new PrepareRender2DContext();
 
             ++this._globalTransformProcessStep;
-            this.updateGlobalTransVis(false);
+            this.updateCachedStates(false);
 
             this._prepareGroupRender(context);
 

+ 65 - 0
src/Canvas2d/babylon.canvas2dLayoutEngine.ts

@@ -0,0 +1,65 @@
+module BABYLON {
+
+    @className("LayoutEngineBase")
+    /**
+     * This is the base class you have to extend in order to implement your own Layout Engine.
+     * Note that for performance reason, each different Layout Engine type must be instanced only once, good practice is through a static `Singleton`property defined in the type itself.
+     * If data has to be associated to a given primitive you can use the SmartPropertyPrim.addExternalData API to do it.
+     */
+    export class LayoutEngineBase {
+        public updateLayout(prim: Prim2DBase) {
+        }
+
+        public get isChildPositionAllowed(): boolean {
+            return false;
+        }
+    }
+
+    @className("CanvasLayoutEngine")
+    export class CanvasLayoutEngine extends LayoutEngineBase {
+        public static Singleton: CanvasLayoutEngine = new CanvasLayoutEngine();
+
+        // A very simple (no) layout computing...
+        // The Canvas and its direct children gets the Canvas' size as Layout Area
+        // Indirect children have their Layout Area to the actualSize (margin area) of their parent
+        public updateLayout(prim: Prim2DBase) {
+
+            // If this prim is layoutDiry we update  its layoutArea and also the one of its direct children
+            // Then we recurse on each child where their respective layoutDirty will also be test, and so on.
+            if (prim._isFlagSet(SmartPropertyPrim.flagLayoutDirty)) {
+                this._doUpdate(prim);
+                prim._clearFlags(SmartPropertyPrim.flagLayoutDirty);
+
+                // Recurse
+                for (let child of prim.children) {
+                    this._doUpdate(child);
+                    child._clearFlags(SmartPropertyPrim.flagLayoutDirty);
+                    this.updateLayout(child);
+                }
+            }
+
+        }
+
+        private _doUpdate(prim: Prim2DBase) {
+            // Canvas ?
+            if (prim instanceof Canvas2D) {
+                prim._layoutArea = prim.actualSize;
+            }
+
+            // Direct child of Canvas ?
+            else if (prim.parent instanceof Canvas2D) {
+                prim._layoutArea = prim.owner.actualSize;
+            }
+
+            // Indirect child of Canvas
+            else {
+                prim._layoutArea = prim.parent.contentArea;
+            }
+        }
+
+        get isChildPositionAllowed(): boolean {
+            return true;
+        }
+    }
+
+}

+ 17 - 19
src/Canvas2d/babylon.ellipse2d.ts

@@ -166,20 +166,19 @@
     @className("Ellipse2D")
     export class Ellipse2D extends Shape2D {
 
-        public static sizeProperty: Prim2DPropInfo;
+        public static acutalSizeProperty: Prim2DPropInfo;
         public static subdivisionsProperty: Prim2DPropInfo;
 
+        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Ellipse2D.acutalSizeProperty = pi, false, true)
         public get actualSize(): Size {
-            return this.size;
-        }
-
-        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Ellipse2D.sizeProperty = pi, false, true)
-        public get size(): Size {
+            if (this._actualSize) {
+                return this._actualSize;
+            }
             return this._size;
         }
 
-        public set size(value: Size) {
-            this._size = value;
+        public set actualSize(value: Size) {
+            this._actualSize = value;
         }
 
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Ellipse2D.subdivisionsProperty = pi)
@@ -203,7 +202,7 @@
             BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        protected setupEllipse2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, subdivisions: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
             this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.size = size;
             this.subdivisions = subdivisions;
@@ -226,7 +225,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, subdivisions?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Ellipse2D {
+        public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, subdivisions?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, vAlignment?: number, hAlignment?: number}): Ellipse2D {
             Prim2DBase.CheckParent(parent);
 
             let ellipse = new Ellipse2D();
@@ -256,12 +255,12 @@
                     options.border || null,
                     (options.borderThickness == null) ? 1 : options.borderThickness,
                     (options.isVisible == null) ? true : options.isVisible,
-                    options.marginTop || null,
-                    options.marginLeft || null,
-                    options.marginRight || null,
-                    options.marginBottom || null,
-                    options.vAlignment || null,
-                    options.hAlignment || null);
+                    options.marginTop,
+                    options.marginLeft,
+                    options.marginRight,
+                    options.marginBottom,
+                    options.vAlignment,
+                    options.hAlignment);
             }
 
             return ellipse;
@@ -368,18 +367,17 @@
             }
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Ellipse2DInstanceData>part;
-                let size = this.size;
+                let size = this.actualSize;
                 d.properties = new Vector3(size.width, size.height, this.subdivisions);
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Ellipse2DInstanceData>part;
-                let size = this.size;
+                let size = this.actualSize;
                 d.properties = new Vector3(size.width, size.height, this.subdivisions);
             }
             return true;
         }
 
-        private _size: Size;
         private _subdivisions: number;
     }
 }

+ 13 - 15
src/Canvas2d/babylon.group2d.ts

@@ -43,7 +43,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        static CreateGroup2D(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, cacheBehavior?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Group2D {
+        static CreateGroup2D(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, cacheBehavior?: number, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, vAlignment?: number, hAlignment?: number}): Group2D {
             Prim2DBase.CheckParent(parent);
 
 
@@ -69,8 +69,8 @@
                     options.marginLeft,
                     options.marginRight,
                     options.marginBottom,
-                    options.hAlignment || Prim2DBase.HAlignLeft,
-                    options.vAlignment || Prim2DBase.VAlignTop);
+                    options.hAlignment || PrimitiveAlignment.AlignLeft,
+                    options.vAlignment || PrimitiveAlignment.AlignTop);
             }
        
             return g;
@@ -138,7 +138,7 @@
             return true;
         }
 
-        protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, isVisible: boolean, cacheBehavior: number, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, hAlign: number, vAlign: number) {
+        protected setupGroup2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, isVisible: boolean, cacheBehavior: number, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, hAlign: number, vAlign: number) {
             this._cacheBehavior = cacheBehavior;
             this.setupPrim2DBase(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom , hAlign, vAlign);
             this.size = size;
@@ -196,7 +196,7 @@
                 actualSize = new Size(Math.ceil(m.x), Math.ceil(m.y));
             }
 
-            // Compare the size with the one we previously had, if it differ we set the property dirty and trigger a GroupChanged to synchronize a displaySprite (if any)
+            // Compare the size with the one we previously had, if it differs we set the property dirty and trigger a GroupChanged to synchronize a displaySprite (if any)
             if (!actualSize.equals(this._actualSize)) {
                 this._instanceDirtyFlags |= Group2D.actualSizeProperty.flagId;
                 this._actualSize = actualSize;
@@ -219,7 +219,7 @@
         }
 
         public _renderCachedCanvas() {
-            this.updateGlobalTransVis(true);
+            this.updateCachedStates(true);
             let context = new PrepareRender2DContext();
             this._prepareGroupRender(context);
             this._groupRender();
@@ -252,7 +252,7 @@
             // Update the Global Transformation and visibility status of the changed primitives
             if ((this._renderableData._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
                 sortedDirtyList = this._renderableData._primDirtyList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
-                this.updateGlobalTransVisOf(sortedDirtyList, true);
+                this.updateCachedStatesOf(sortedDirtyList, true);
             }
 
             // Setup the size of the rendering viewport
@@ -446,7 +446,7 @@
             }
 
             // Sort all the primitive from their depth, max (bottom) to min (top)
-            rd._transparentPrimitives.sort((a, b) => b._primitive.getActualZOffset() - a._primitive.getActualZOffset());
+            rd._transparentPrimitives.sort((a, b) => b._primitive.actualZOffset - a._primitive.actualZOffset);
 
             let checkAndAddPrimInSegment = (seg: TransparentSegment, tpiI: number): boolean => {
                 let tpi = rd._transparentPrimitives[tpiI];
@@ -456,7 +456,7 @@
                     return false;
                 }
 
-                let tpiZ = tpi._primitive.getActualZOffset();
+                let tpiZ = tpi._primitive.actualZOffset;
 
                 // We've made it so far, the tpi can be part of the segment, add it
                 tpi._transparentSegment = seg;
@@ -494,7 +494,7 @@
                     let ts = new TransparentSegment();
                     ts.groupInsanceInfo = tpi._groupInstanceInfo;
                     let prim = tpi._primitive;
-                    ts.startZ = prim.getActualZOffset();
+                    ts.startZ = prim.actualZOffset;
                     ts.startDataIndex = prim._getFirstIndexInDataBuffer();
                     ts.endDataIndex = prim._getLastIndexInDataBuffer() + 1; // Make it exclusive, more natural to use in a for loop
                     ts.endZ = ts.startZ;
@@ -685,7 +685,7 @@
             } else if (prop.id === Prim2DBase.originProperty.id) {
                 rd._cacheRenderSprite.origin = this.origin.clone();
             } else if (prop.id === Group2D.actualSizeProperty.id) {
-                rd._cacheRenderSprite.spriteSize = this.actualSize.clone();
+                rd._cacheRenderSprite.size = this.actualSize.clone();
                 //console.log(`[${this._globalTransformProcessStep}] Sync Sprite ${this.id}, width: ${this.actualSize.width}, height: ${this.actualSize.height}`);
             }
         }
@@ -762,8 +762,6 @@
         protected _isRenderableGroup: boolean;
         protected _isCachedGroup: boolean;
         private _cacheGroupDirty: boolean;
-        private _size: Size;
-        private _actualSize: Size;
         private _cacheBehavior: number;
         private _viewportPosition: Vector2;
         private _viewportSize: Size;
@@ -839,8 +837,8 @@
 
         updateSmallestZChangedPrim(tpi: TransparentPrimitiveInfo) {
             if (tpi._primitive) {
-                let newZ = tpi._primitive.getActualZOffset();
-                let curZ = this._firstChangedPrim ? this._firstChangedPrim._primitive.getActualZOffset() : Number.MIN_VALUE;
+                let newZ = tpi._primitive.actualZOffset;
+                let curZ = this._firstChangedPrim ? this._firstChangedPrim._primitive.actualZOffset : Number.MIN_VALUE;
                 if (newZ > curZ) {
                     this._firstChangedPrim = tpi;
                 }

+ 16 - 17
src/Canvas2d/babylon.lines2d.ts

@@ -184,10 +184,6 @@
         public static startCapProperty: Prim2DPropInfo;
         public static endCapProperty: Prim2DPropInfo;
 
-        public get actualSize(): Size {
-            return this.size;
-        }
-
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Lines2D.pointsProperty = pi)
         public get points(): Vector2[] {
             return this._points;
@@ -195,7 +191,7 @@
 
         public set points(value: Vector2[]) {
             this._points = value;
-            this._levelBoundingInfoDirty = true;
+            this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
         }
 
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Lines2D.fillThicknessProperty = pi)
@@ -298,9 +294,13 @@
             return false;
         }
 
-        protected get size(): Size {
-            return this._size;
-        }
+        //public  get size(): Size {
+        //    return this._size;
+        //}
+
+        //public set size(value: Size) {
+        //    // TODO implement lines size
+        //}
 
         protected get boundingMin(): Vector2 {
             return this._boundingMin;
@@ -325,7 +325,7 @@
             BoundingInfo2D.CreateFromMinMaxToRef(this._boundingMin.x, this._boundingMax.x, this._boundingMin.y, this._boundingMax.y, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        protected setupLines2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, points: Vector2[], fillThickness: number, startCap: number, endCap: number, fill: IBrush2D, border: IBrush2D, borderThickness: number, closed: boolean, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
             this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.fillThickness = fillThickness;
             this.startCap = startCap;
@@ -357,7 +357,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, points: Vector2[], options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, fillThickness?: number, closed?: boolean, startCap?: number, endCap?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number }): Lines2D {
+        public static Create(parent: Prim2DBase, points: Vector2[], options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, fillThickness?: number, closed?: boolean, startCap?: number, endCap?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, vAlignment?: number, hAlignment?: number }): Lines2D {
             Prim2DBase.CheckParent(parent);
 
             let lines = new Lines2D();
@@ -389,12 +389,12 @@
                     (options.borderThickness == null) ? 1 : options.borderThickness,
                     (options.closed == null) ? false : options.closed,
                     (options.isVisible == null) ? true : options.isVisible,
-                    options.marginTop || null,
-                    options.marginLeft || null,
-                    options.marginRight || null,
-                    options.marginBottom || null,
-                    options.vAlignment || null,
-                    options.hAlignment || null);
+                    options.marginTop,
+                    options.marginLeft,
+                    options.marginRight,
+                    options.marginBottom,
+                    options.vAlignment,
+                    options.hAlignment);
             }
 
             return lines;
@@ -1084,7 +1084,6 @@
 
         private _boundingMin: Vector2;
         private _boundingMax: Vector2;
-        private _size: Size;
         private _contour: Vector2[];
         private _startCapContour: number[];
         private _startCapTriIndices: number[];

Разлика између датотеке није приказан због своје велике величине
+ 1161 - 164
src/Canvas2d/babylon.prim2dBase.ts


+ 38 - 18
src/Canvas2d/babylon.rectangle2d.ts

@@ -167,15 +167,11 @@
     @className("Rectangle2D")
     export class Rectangle2D extends Shape2D {
 
-        public static sizeProperty: Prim2DPropInfo;
+        public static actualSizeProperty: Prim2DPropInfo;
         public static notRoundedProperty: Prim2DPropInfo;
         public static roundRadiusProperty: Prim2DPropInfo;
 
-        public get actualSize(): Size {
-            return this.size;
-        }
-
-        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.sizeProperty = pi, false, true)
+//        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.sizeProperty = pi, false, true)
         public get size(): Size {
             return this._size;
         }
@@ -184,6 +180,18 @@
             this._size = value;
         }
 
+        @instanceLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 1, pi => Rectangle2D.actualSizeProperty = pi, false, true)
+        public get actualSize(): Size {
+            if (this._actualSize) {
+                return this._actualSize;
+            }
+            return this._size;
+        }
+
+        public set actualSize(value: Size) {
+            this._actualSize = value;
+        }
+
         @modelLevelProperty(Shape2D.SHAPE2D_PROPCOUNT + 2, pi => Rectangle2D.notRoundedProperty = pi)
         public get notRounded(): boolean {
             return this._notRounded;
@@ -201,6 +209,7 @@
         public set roundRadius(value: number) {
             this._roundRadius = value;
             this.notRounded = value === 0;
+            this._positioningDirty();
         }
 
         private static _i0 = Vector2.Zero();
@@ -284,7 +293,7 @@
             BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, roundRadius, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        protected setupRectangle2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, size: Size, roundRadius, fill: IBrush2D, border: IBrush2D, borderThickness: number, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
             this.setupShape2D(owner, parent, id, position, origin, isVisible, fill, border, borderThickness, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.size = size;
             this.notRounded = !roundRadius;
@@ -308,7 +317,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, roundRadius?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Rectangle2D {
+        public static Create(parent: Prim2DBase, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, size?: Size, width?: number, height?: number, roundRadius?: number, fill?: IBrush2D, border?: IBrush2D, borderThickness?: number, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, vAlignment?: number, hAlignment?: number}): Rectangle2D {
             Prim2DBase.CheckParent(parent);
 
             let rect = new Rectangle2D();
@@ -317,7 +326,7 @@
                 rect.setupRectangle2D(parent.owner, parent, null, Vector2.Zero(), null, new Size(10, 10), 0, Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF"), null, 1, true, null, null, null, null, null, null);
             } else {
                 let pos = options.position || new Vector2(options.x || 0, options.y || 0);
-                let size = options.size || (new Size(options.width || 10, options.height || 10));
+                let size = options.size || (new Size((options.width === null) ? null : (options.width || 10), (options.height === null) ? null : (options.height || 10)));
                 let fill = options.fill===undefined ? Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF") : options.fill;
 
                 rect.setupRectangle2D
@@ -333,12 +342,12 @@
                     options.border || null,
                     (options.borderThickness==null) ? 1 : options.borderThickness,
                     options.isVisible || true,
-                    options.marginTop || null,
-                    options.marginLeft || null,
-                    options.marginRight || null,
-                    options.marginBottom || null,
-                    options.vAlignment || null,
-                    options.hAlignment || null);
+                    options.marginTop,
+                    options.marginLeft,
+                    options.marginRight,
+                    options.marginBottom,
+                    options.vAlignment,
+                    options.hAlignment);
             }
             return rect;
         }
@@ -428,6 +437,18 @@
             return renderCache;
         }
 
+        // We override this method because if there's a roundRadius set, we will reduce the initial Content Area to make sure the computed area won't intersect with the shape contour. The formula is simple: we shrink the incoming size by the amount of the roundRadius
+        protected _getInitialContentAreaToRef(primSize: Size, initialContentPosition: Vector2, initialContentArea: Size) {
+            // Fall back to default implementation if there's no round Radius
+            if (this._notRounded) {
+                super._getInitialContentAreaToRef(primSize, initialContentPosition, initialContentArea);
+            } else {
+                let rr = (this.roundRadius - (this.roundRadius/Math.sqrt(2))) * 1.3;
+                initialContentPosition.x = initialContentPosition.y = rr;
+                initialContentArea.width = primSize.width - (rr * 2);
+                initialContentArea.height = primSize.height - (rr * 2);
+            }
+        }
 
         protected createInstanceDataParts(): InstanceDataBase[] {
             var res = new Array<InstanceDataBase>();
@@ -446,18 +467,17 @@
             }
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Rectangle2DInstanceData>part;
-                let size = this.size;
+                let size = this.actualSize;
                 d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Rectangle2DInstanceData>part;
-                let size = this.size;
+                let size = this.actualSize;
                 d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
             }
             return true;
         }
 
-        private _size: Size;
         private _notRounded: boolean;
         private _roundRadius: number;
     }

+ 9 - 9
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -353,7 +353,7 @@
             this._isTransparent = value;
         }
 
-        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, hAlign: number, vAlign: number) {
+        setupRenderablePrim2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, hAlign: number, vAlign: number) {
             this.setupPrim2DBase(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlign, vAlign);
             this._isTransparent = false;
             this._isAlphaTest = false;
@@ -389,14 +389,14 @@
             super._prepareRenderPre(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) {
+            if (this._isFlagSet(SmartPropertyPrim.flagModelDirty) && this._modelRenderInstanceID) {
                 this._modelRenderCache.removeInstanceData(this._modelRenderInstanceID);
                 this._modelRenderInstanceID = null;
             }
 
             // Need to create the model?
             let setupModelRenderCache = false;
-            if (!this._modelRenderCache || this._modelDirty) {
+            if (!this._modelRenderCache || this._isFlagSet(SmartPropertyPrim.flagModelDirty)) {
                 setupModelRenderCache = this._createModelRenderCache();
             }
 
@@ -418,7 +418,7 @@
             // At this stage we have everything correctly initialized, ModelRenderCache is setup, Model Instance data are good too, they have allocated elements in the Instanced DynamicFloatArray.
 
             // The last thing to do is check if the instanced related data must be updated because a InstanceLevel property had changed or the primitive visibility changed.
-            if (this._visibilityChanged || context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep)) {
+            if (this._isFlagSet(SmartPropertyPrim.flagVisibilityChanged) || context.forceRefreshPrimitive || newInstance || (this._instanceDirtyFlags !== 0) || (this._globalTransformProcessStep !== this._globalTransformStep)) {
                 this._updateInstanceDataParts(gii);
             }
         }
@@ -434,7 +434,7 @@
                 setupModelRenderCache = true;
                 return mrc;
             });
-            this._modelDirty = false;
+            this._clearFlags(SmartPropertyPrim.flagModelDirty);
 
             // if this is still false it means the MRC already exists, so we add a reference to it
             if (!setupModelRenderCache) {
@@ -555,7 +555,7 @@
             // Handle changes related to ZOffset
             if (this.isTransparent) {
                 // Handle visibility change, which is also triggered when the primitive just got created
-                if (this._visibilityChanged) {
+                if (this._isFlagSet(SmartPropertyPrim.flagVisibilityChanged)) {
                     if (this.isVisible) {
                         if (!this._transparentPrimitiveInfo) {
                             // Add the primitive to the list of transparent ones in the group that render is
@@ -573,7 +573,7 @@
             // For each Instance Data part, refresh it to update the data in the DynamicFloatArray
             for (let part of this._instanceDataParts) {
                 // Check if we need to allocate data elements (hidden prim which becomes visible again)
-                if (this._visibilityChanged && !part.dataElements) {
+                if (this._isFlagSet(SmartPropertyPrim.flagVisibilityChanged) && !part.dataElements) {
                     part.allocElements();
                 }
 
@@ -598,7 +598,7 @@
                 gii.opaqueDirty = true;
             }
 
-            this._visibilityChanged = false;    // Reset the flag as we've handled the case            
+            this._clearFlags(SmartPropertyPrim.flagVisibilityChanged);    // Reset the flag as we've handled the case            
         }
 
         public _getFirstIndexInDataBuffer(): number {
@@ -764,7 +764,7 @@
         protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null, customSize: Size = null) {
             let t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);
             let size = (<Size>this.renderGroup.viewportSize);
-            let zBias = this.getActualZOffset();
+            let zBias = this.actualZOffset;
 
             let offX = 0;
             let offY = 0;

+ 2 - 2
src/Canvas2d/babylon.shape2d.ts

@@ -44,9 +44,9 @@
             this._borderThickness = value;
         }
 
-        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        setupShape2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, isVisible: boolean, fill: IBrush2D, border: IBrush2D, borderThickness: number, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
 
-            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment || Prim2DBase.HAlignLeft, vAlignment || Prim2DBase.VAlignTop);
+            this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment || PrimitiveAlignment.AlignLeft, vAlignment || PrimitiveAlignment.AlignTop);
             this.border = border;
             this.fill = fill;
             this.borderThickness = borderThickness;

+ 109 - 19
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -136,11 +136,9 @@
     export class SmartPropertyPrim implements IPropertyChanged {
 
         protected setupSmartPropertyPrim() {
+            this._flags = 0;
             this._modelKey = null;
-            this._modelDirty = false;
-            this._levelBoundingInfoDirty = false;
             this._instanceDirtyFlags = 0;
-            this._isDisposed = false;
             this._levelBoundingInfo = new BoundingInfo2D();
             this.animations = new Array<Animation>();
         }
@@ -156,7 +154,7 @@
          * @returns true if the object is dispose, false otherwise.
          */
         public get isDisposed(): boolean {
-            return this._isDisposed;
+            return this._isFlagSet(SmartPropertyPrim.flagIsDisposed);
         }
 
         /**
@@ -171,7 +169,7 @@
             // Don't set to null, it may upset somebody...
             this.animations.splice(0);
 
-            this._isDisposed = true;
+            this._setFlags(SmartPropertyPrim.flagIsDisposed);
             return true;
         }
 
@@ -196,7 +194,7 @@
         public get modelKey(): string {
 
             // No need to compute it?
-            if (!this._modelDirty && this._modelKey) {
+            if (!this._isFlagSet(SmartPropertyPrim.flagModelDirty) && this._modelKey) {
                 return this._modelKey;
             }
 
@@ -220,7 +218,7 @@
                 }
             });
 
-            this._modelDirty = false;
+            this._clearFlags(SmartPropertyPrim.flagModelDirty);
             this._modelKey = modelKey;
 
             return modelKey;
@@ -231,7 +229,7 @@
          * @returns true is dirty, false otherwise
          */
         public get isDirty(): boolean {
-            return (this._instanceDirtyFlags !== 0) || this._modelDirty;
+            return (this._instanceDirtyFlags !== 0) || this._areSomeFlagsSet(SmartPropertyPrim.flagModelDirty | SmartPropertyPrim.flagPositioningDirty | SmartPropertyPrim.flagLayoutDirty);
         }
 
         /**
@@ -318,7 +316,7 @@
         private _handlePropChanged<T>(curValue: T, newValue: T, propName: string, propInfo: Prim2DPropInfo, typeLevelCompare: boolean) {
             // If the property change also dirty the boundingInfo, update the boundingInfo dirty flags
             if (propInfo.dirtyBoundingInfo) {
-                this._levelBoundingInfoDirty = true;
+                this._setFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
 
                 // Escalate the dirty flag in the instance hierarchy, stop when a renderable group is found or at the end
                 if (this instanceof Prim2DBase) {
@@ -365,7 +363,7 @@
                     if (!this.isDirty) {
                         this.onPrimBecomesDirty();
                     }
-                    this._modelDirty = true;
+                    this._setFlags(SmartPropertyPrim.flagModelDirty);
                 } else if ((propInfo.kind === Prim2DPropInfo.PROPKIND_INSTANCE) || (propInfo.kind === Prim2DPropInfo.PROPKIND_DYNAMIC)) {
                     if (!this.isDirty) {
                         this.onPrimBecomesDirty();
@@ -407,9 +405,9 @@
          * @returns {} 
          */
         public get levelBoundingInfo(): BoundingInfo2D {
-            if (this._levelBoundingInfoDirty) {
+            if (this._isFlagSet(SmartPropertyPrim.flagLevelBoundingInfoDirty)) {
                 this.updateLevelBoundingInfo();
-                this._levelBoundingInfoDirty = false;
+                this._clearFlags(SmartPropertyPrim.flagLevelBoundingInfoDirty);
             }
             return this._levelBoundingInfo;
         }
@@ -462,13 +460,105 @@
             }
         }
 
-        private _modelKey; string;
-        private _propInfo: StringDictionary<Prim2DPropInfo>;
-        private _isDisposed: boolean;
-        protected _levelBoundingInfoDirty: boolean;
-        protected _levelBoundingInfo: BoundingInfo2D;
-        protected _boundingInfo: BoundingInfo2D;
-        protected _modelDirty: boolean;
+        /**
+         * Add an externally attached data from its key.
+         * This method call will fail and return false, if such key already exists.
+         * If you don't care and just want to get the data no matter what, use the more convenient getOrAddExternalDataWithFactory() method.
+         * @param key the unique key that identifies the data
+         * @param data the data object to associate to the key for this Engine instance
+         * @return true if no such key were already present and the data was added successfully, false otherwise
+         */
+        public addExternalData<T>(key: string, data: T): boolean {
+            if (!this._externalData) {
+                this._externalData = new StringDictionary<Object>();
+            }
+            return this._externalData.add(key, data);
+        }
+
+        /**
+         * Get an externally attached data from its key
+         * @param key the unique key that identifies the data
+         * @return the associated data, if present (can be null), or undefined if not present
+         */
+        public getExternalData<T>(key: string): T {
+            if (!this._externalData) {
+                return null;
+            }
+            return <T>this._externalData.get(key);
+        }
+
+        /**
+         * Get an externally attached data from its key, create it using a factory if it's not already present
+         * @param key the unique key that identifies the data
+         * @param factory the factory that will be called to create the instance if and only if it doesn't exists
+         * @return the associated data, can be null if the factory returned null.
+         */
+        public getOrAddExternalDataWithFactory<T>(key: string, factory: (k: string) => T): T {
+            if (!this._externalData) {
+                this._externalData = new StringDictionary<Object>();
+            }
+            return <T>this._externalData.getOrAddWithFactory(key, factory);
+        }
+
+        /**
+         * Remove an externally attached data from the Engine instance
+         * @param key the unique key that identifies the data
+         * @return true if the data was successfully removed, false if it doesn't exist
+         */
+        public removeExternalData(key): boolean {
+            if (!this._externalData) {
+                return false;
+            }
+            return this._externalData.remove(key);
+        }
+
+        public _isFlagSet(flag: number): boolean {
+            return (this._flags & flag) !== 0;
+        }
+
+        public _areAllFlagsSet(flags: number): boolean {
+            return (this._flags & flags) === flags;
+        }
+
+        public _areSomeFlagsSet(flags: number): boolean {
+            return (this._flags & flags) !== 0;
+        }
+
+        public _clearFlags(flags: number) {
+            this._flags &= ~flags;
+        }
+
+        public _setFlags(flags: number): number {
+            let cur = this._flags;
+            this._flags |= flags;
+            return cur;
+        }
+
+        public _changeFlags(flags: number, state: boolean) {
+            if (state) {
+                this._flags |= flags;
+            } else {
+                this._flags &= ~flags;
+            }
+        }
+
+        public static flagIsDisposed             = 0x0000001;    // set if the object is already disposed
+        public static flagLevelBoundingInfoDirty = 0x0000002;    // set if the primitive's level bounding box (not including children) is dirty
+        public static flagModelDirty             = 0x0000004;    // set if the model must be changed
+        public static flagLayoutDirty            = 0x0000008;    // set if the layout must be computed
+        public static flagLevelVisible           = 0x0000010;    // set if the primitive is set as visible for its level only
+        public static flagBoundingInfoDirty      = 0x0000020;    // set if the primitive's overall bounding box (including children) is dirty
+        public static flagIsPickable             = 0x0000040;    // set if the primitive can be picked during interaction
+        public static flagIsVisible              = 0x0000080;    // set if the primitive is concretely visible (use the levelVisible of parents)
+        public static flagVisibilityChanged      = 0x0000100;    // set if there was a transition between visible/hidden status
+        public static flagPositioningDirty       = 0x0000200;    // set if the primitive positioning must be computed
+
+        private   _flags             : number;
+        private   _externalData      : StringDictionary<Object>;
+        private   _modelKey          : string;
+        private   _propInfo          : StringDictionary<Prim2DPropInfo>;
+        protected _levelBoundingInfo : BoundingInfo2D;
+        protected _boundingInfo      : BoundingInfo2D;
         protected _instanceDirtyFlags: number;
     }
 

+ 20 - 22
src/Canvas2d/babylon.sprite2d.ts

@@ -121,7 +121,7 @@
         static SPRITE2D_MAINPARTID = 1;
 
         public static textureProperty: Prim2DPropInfo;
-        public static spriteSizeProperty: Prim2DPropInfo;
+        public static actualSizeProperty: Prim2DPropInfo;
         public static spriteLocationProperty: Prim2DPropInfo;
         public static spriteFrameProperty: Prim2DPropInfo;
         public static invertYProperty: Prim2DPropInfo;
@@ -136,17 +136,16 @@
             this._texture = value;
         }
 
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Sprite2D.actualSizeProperty = pi, false, true)
         public get actualSize(): Size {
-            return this.spriteSize;
-        }
-
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 2, pi => Sprite2D.spriteSizeProperty = pi, false, true)
-        public get spriteSize(): Size {
+            if (this._actualSize) {
+                return this._actualSize;
+            }
             return this._size;
         }
 
-        public set spriteSize(value: Size) {
-            this._size = value;
+        public set actualSize(value: Size) {
+            this._actualSize = value;
         }
 
         @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, pi => Sprite2D.spriteLocationProperty = pi)
@@ -185,7 +184,7 @@
         }
 
         protected updateLevelBoundingInfo() {
-            BoundingInfo2D.CreateFromSizeToRef(this.spriteSize, this._levelBoundingInfo, this.origin);
+            BoundingInfo2D.CreateFromSizeToRef(this.size, this._levelBoundingInfo, this.origin);
         }
 
         public getAnimatables(): IAnimatable[] {
@@ -202,21 +201,21 @@
             return true;
         }
 
-        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean, alignToPixel: boolean, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        protected setupSprite2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, texture: Texture, spriteSize: Size, spriteLocation: Vector2, invertY: boolean, alignToPixel: boolean, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
             this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
             this.texture = texture;
             this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this.spriteSize = spriteSize || null;
+            this.size = spriteSize || null;
             this.spriteLocation = spriteLocation || new Vector2(0,0);
             this.spriteFrame = 0;
             this.invertY = invertY;
             this.alignToPixel = alignToPixel;
             this._isTransparent = true;
 
-            if (!this.spriteSize) {
+            if (!this.size) {
                 var s = texture.getSize();
-                this.spriteSize = new Size(s.width, s.height);
+                this.size = new Size(s.width, s.height);
             }
         }
 
@@ -237,7 +236,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean, alignToPixel?: boolean, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, vAlignment?: number, hAlignment?: number}): Sprite2D {
+        public static Create(parent: Prim2DBase, texture: Texture, options: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, spriteSize?: Size, spriteLocation?: Vector2, invertY?: boolean, alignToPixel?: boolean, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, vAlignment?: number, hAlignment?: number}): Sprite2D {
             Prim2DBase.CheckParent(parent);
 
             let sprite = new Sprite2D();
@@ -257,12 +256,12 @@
                     (options.invertY == null) ? false : options.invertY,
                     (options.alignToPixel == null) ? true : options.alignToPixel,
                     (options.isVisible == null) ? true : options.isVisible,
-                    options.marginTop || null,
-                    options.marginLeft || null,
-                    options.marginRight || null,
-                    options.marginBottom || null,
-                    options.vAlignment || null,
-                    options.hAlignment || null
+                    options.marginTop,
+                    options.marginLeft,
+                    options.marginRight,
+                    options.marginBottom,
+                    options.vAlignment,
+                    options.hAlignment
                 );
             }
 
@@ -331,7 +330,7 @@
                 let d = <Sprite2DInstanceData>this._instanceDataParts[0];
                 let ts = this.texture.getBaseSize();
                 let sl = this.spriteLocation;
-                let ss = this.spriteSize;
+                let ss = this.actualSize;
                 d.topLeftUV = new Vector2(sl.x / ts.width, sl.y / ts.height);
                 let suv = new Vector2(ss.width / ts.width, ss.height / ts.height);
                 d.sizeUV = suv;
@@ -347,7 +346,6 @@
         }
 
         private _texture: Texture;
-        private _size: Size;
         private _location: Vector2;
         private _spriteFrame: number;
         private _invertY: boolean;

+ 17 - 19
src/Canvas2d/babylon.text2d.ts

@@ -120,7 +120,7 @@
         public static fontProperty: Prim2DPropInfo;
         public static defaultFontColorProperty: Prim2DPropInfo;
         public static textProperty: Prim2DPropInfo;
-        public static areaSizeProperty: Prim2DPropInfo;
+        public static sizeProperty: Prim2DPropInfo;
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Text2D.fontProperty = pi, false, true)
         public get fontName(): string {
@@ -154,18 +154,18 @@
             this._updateCharCount();
         }
 
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Text2D.areaSizeProperty = pi)
-        public get areaSize(): Size {
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Text2D.sizeProperty = pi)
+        public get size(): Size {
             return this._areaSize;
         }
 
-        public set areaSize(value: Size) {
+        public set size(value: Size) {
             this._areaSize = value;
         }
 
         public get actualSize(): Size {
-            if (this.areaSize) {
-                return this.areaSize;
+            if (this.size) {
+                return this.size;
             }
 
             if (this._actualSize) {
@@ -203,13 +203,13 @@
             BoundingInfo2D.CreateFromSizeToRef(this.actualSize, this._levelBoundingInfo, this.origin);
         }
 
-        protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, tabulationSize: number, isVisible: boolean, marginTop: number, marginLeft: number, marginRight: number, marginBottom: number, vAlignment: number, hAlignment: number) {
+        protected setupText2D(owner: Canvas2D, parent: Prim2DBase, id: string, position: Vector2, origin: Vector2, fontName: string, text: string, areaSize: Size, defaultFontColor: Color4, tabulationSize: number, isVisible: boolean, marginTop: number | string, marginLeft: number | string, marginRight: number | string, marginBottom: number | string, vAlignment: number, hAlignment: number) {
             this.setupRenderablePrim2D(owner, parent, id, position, origin, isVisible, marginTop, marginLeft, marginRight, marginBottom, hAlignment, vAlignment);
 
             this.fontName = fontName;
             this.defaultFontColor = defaultFontColor;
             this.text = text;
-            this.areaSize = areaSize;
+            this.size = areaSize;
             this._tabulationSize = tabulationSize;
             this.isAlphaTest = true;
         }
@@ -231,7 +231,7 @@
          *  - hAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          *  - vAlighment: define horizontal alignment of the Canvas, alignment is optional, default value null: no alignment.
          */
-        public static Create(parent: Prim2DBase, text: string, options?: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, fontName?: string, defaultFontColor?: Color4, areaSize?: Size, tabulationSize?: number, isVisible?: boolean, marginTop?: number, marginLeft?: number, marginRight?: number, marginBottom?: number, hAlignment?: number, vAlignment?: number}): Text2D {
+        public static Create(parent: Prim2DBase, text: string, options?: { id?: string, position?: Vector2, x?: number, y?: number, origin?: Vector2, fontName?: string, defaultFontColor?: Color4, size?: Size, tabulationSize?: number, isVisible?: boolean, marginTop?: number | string, marginLeft?: number | string, marginRight?: number | string, marginBottom?: number | string, hAlignment?: number, vAlignment?: number}): Text2D {
             Prim2DBase.CheckParent(parent);
 
             let text2d = new Text2D();
@@ -248,16 +248,16 @@
                     options.origin || null,
                     options.fontName || "12pt Arial",
                     text,
-                    options.areaSize,
+                    options.size,
                     options.defaultFontColor || new Color4(1, 1, 1, 1),
                     (options.tabulationSize==null) ? 4 : options.tabulationSize,
                     (options.isVisible==null) ? true : options.isVisible,
-                    options.marginTop || null,
-                    options.marginLeft || null,
-                    options.marginRight || null,
-                    options.marginBottom || null,
-                    options.vAlignment || null,
-                    options.hAlignment || null);
+                    options.marginTop,
+                    options.marginLeft,
+                    options.marginRight,
+                    options.marginBottom,
+                    options.vAlignment,
+                    options.hAlignment);
             }
             return text2d;
         }
@@ -399,9 +399,7 @@
         private _defaultFontColor: Color4;
         private _text: string;
         private _areaSize: Size;
-        private _actualSize: Size;
-        private _vAlign: number;
-        private _hAlign: number;
+
     }
 
 

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

@@ -1707,6 +1707,11 @@
             return hash;
         }
 
+        public copyFrom(src: Size) {
+            this.width  = src.width;
+            this.height = src.height;
+        }
+
         public clone(): Size {
             return new Size(this.width, this.height);
         }

+ 1 - 1
src/Shaders/text2d.vertex.fx

@@ -52,7 +52,7 @@ void main(void) {
 
 	vColor = color;
 	vec4 pos;
-	pos.xy = (pos2.xy * sizeUV * textureSize) - origin;
+	pos.xy = floor((pos2.xy * sizeUV * textureSize) - origin);	// Align on target pixel to avoid bad interpolation
 	pos.z = 1.0;
 	pos.w = 1.0;
 	gl_Position = vec4(dot(pos, transformX), dot(pos, transformY), zBias.x, 1);