Parcourir la source

Merge pull request #1332 from nockawa/CanvasScale

Canvas2D: Improvements around Scaling
David Catuhe il y a 9 ans
Parent
commit
b4f169cbb9

Fichier diff supprimé car celui-ci est trop grand
+ 47 - 5
src/Canvas2d/babylon.canvas2d.ts


+ 43 - 40
src/Canvas2d/babylon.ellipse2d.ts

@@ -214,6 +214,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
+         * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -242,43 +243,44 @@
          */
         constructor(settings?: {
 
-            parent            ?: Prim2DBase, 
-            children          ?: Array<Prim2DBase>,
-            id                ?: string,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            rotation          ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            size              ?: Size,
-            width             ?: number,
-            height            ?: number,
-            subdivisions      ?: number,
-            fill              ?: IBrush2D | string,
-            border            ?: IBrush2D | string,
-            borderThickness   ?: number,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+            parent                ?: Prim2DBase, 
+            children              ?: Array<Prim2DBase>,
+            id                    ?: string,
+            position              ?: Vector2,
+            x                     ?: number,
+            y                     ?: number,
+            rotation              ?: number,
+            scale                 ?: number,
+            scaleX                ?: number,
+            scaleY                ?: number,
+            dontInheritParentScale?: boolean,
+            opacity               ?: number,
+            zOrder                ?: number, 
+            origin                ?: Vector2,
+            size                  ?: Size,
+            width                 ?: number,
+            height                ?: number,
+            subdivisions          ?: number,
+            fill                  ?: IBrush2D | string,
+            border                ?: IBrush2D | string,
+            borderThickness       ?: number,
+            isVisible             ?: boolean,
+            isPickable            ?: boolean,
+            isContainer           ?: boolean,
+            childrenFlatZOrder    ?: boolean,
+            marginTop             ?: number | string,
+            marginLeft            ?: number | string,
+            marginRight           ?: number | string,
+            marginBottom          ?: number | string,
+            margin                ?: number | string,
+            marginHAlignment      ?: number,
+            marginVAlignment      ?: number,
+            marginAlignment       ?: string,
+            paddingTop            ?: number | string,
+            paddingLeft           ?: number | string,
+            paddingRight          ?: number | string,
+            paddingBottom         ?: number | string,
+            padding               ?: string,
         }) {
 
             // Avoid checking every time if the object exists
@@ -383,7 +385,6 @@
             return renderCache;
         }
 
-
         protected createInstanceDataParts(): InstanceDataBase[] {
             var res = new Array<InstanceDataBase>();
             if (this.border) {
@@ -402,12 +403,14 @@
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Ellipse2DInstanceData>part;
                 let size = this.actualSize;
-                d.properties = new Vector3(size.width, size.height, this.subdivisions);
+                let s = this.actualScale;
+                d.properties = new Vector3(size.width * s.x, size.height * s.y, this.subdivisions);
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Ellipse2DInstanceData>part;
                 let size = this.actualSize;
-                d.properties = new Vector3(size.width, size.height, this.subdivisions);
+                let s = this.actualScale;
+                d.properties = new Vector3(size.width * s.x, size.height * s.y, this.subdivisions);
             }
             return true;
         }

+ 83 - 63
src/Canvas2d/babylon.group2d.ts

@@ -27,6 +27,13 @@
         public static GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP = 2;
 
         /**
+         * You can specify this behavior to any cached Group2D to indicate that you don't want the cached content to be resized when the Group's actualScale is changing. It will draw the content stretched or shrink which is faster than a resize. This setting is obviously for performance consideration, don't use it if you want the best rendering quality
+         */
+        public static GROUPCACHEBEHAVIOR_NORESIZEONSCALE = 0x100;
+
+        private static GROUPCACHEBEHAVIOR_OPTIONMASK = 0xFF;
+
+        /**
          * Create an Logical or Renderable Group.
          * @param settings a combination of settings, possible ones are
          * - parent: the parent primitive/canvas, must be specified if the primitive is not constructed as a child of another one (i.e. as part of the children array setting)
@@ -35,6 +42,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
+         * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -61,41 +69,42 @@
          */
         constructor(settings?: {
 
-            parent            ?: Prim2DBase,
-            children          ?: Array<Prim2DBase>,
-            id                ?: string,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            trackNode         ?: Node,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            size              ?: Size,
-            width             ?: number,
-            height            ?: number,
-            cacheBehavior     ?: number,
-            layoutEngine      ?: LayoutEngineBase | string,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+            parent                  ?: Prim2DBase,
+            children                ?: Array<Prim2DBase>,
+            id                      ?: string,
+            position                ?: Vector2,
+            x                       ?: number,
+            y                       ?: number,
+            scale                   ?: number,
+            scaleX                  ?: number,
+            scaleY                  ?: number,
+            dontInheritParentScale  ?: boolean,
+            trackNode               ?: Node,
+            opacity                 ?: number,
+            zOrder                  ?: number, 
+            origin                  ?: Vector2,
+            size                    ?: Size,
+            width                   ?: number,
+            height                  ?: number,
+            cacheBehavior           ?: number,
+            layoutEngine            ?: LayoutEngineBase | string,
+            isVisible               ?: boolean,
+            isPickable              ?: boolean,
+            isContainer             ?: boolean,
+            childrenFlatZOrder      ?: boolean,
+            marginTop               ?: number | string,
+            marginLeft              ?: number | string,
+            marginRight             ?: number | string,
+            marginBottom            ?: number | string,
+            margin                  ?: number | string,
+            marginHAlignment        ?: number,
+            marginVAlignment        ?: number,
+            marginAlignment         ?: string,
+            paddingTop              ?: number | string,
+            paddingLeft             ?: number | string,
+            paddingRight            ?: number | string,
+            paddingBottom           ?: number | string,
+            padding                 ?: string,
 
         }) {
             if (settings == null) {
@@ -114,12 +123,17 @@
             }
 
             this._cacheBehavior = (settings.cacheBehavior == null) ? Group2D.GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY : settings.cacheBehavior;
+            let rd = this._renderableData;
+            if (rd) {
+                rd._noResizeOnScale = (this.cacheBehavior & Group2D.GROUPCACHEBEHAVIOR_NORESIZEONSCALE) !== 0;                
+            }
             this.size = size;
             this._viewportPosition = Vector2.Zero();
+            this._viewportSize = Size.Zero();
         }
 
         static _createCachedCanvasGroup(owner: Canvas2D): Group2D {
-            var g = new Group2D({ parent: owner, id: "__cachedCanvasGroup__", position: Vector2.Zero(), origin: Vector2.Zero(), size: null, isVisible: true, isPickable: false });
+            var g = new Group2D({ parent: owner, id: "__cachedCanvasGroup__", position: Vector2.Zero(), origin: Vector2.Zero(), size: null, isVisible: true, isPickable: false, dontInheritParentScale: true });
             return g;
 
         }
@@ -201,7 +215,7 @@
             }
 
             if (this._renderableData) {
-                this._renderableData.dispose(this.owner.engine);
+                this._renderableData.dispose(this.owner);
                 this._renderableData = null;
             }
 
@@ -271,6 +285,7 @@
 
         /**
          * Get/set the Cache Behavior, used in case the Canvas Cache Strategy is set to CACHESTRATEGY_ALLGROUPS. Can be either GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP, GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE or GROUPCACHEBEHAVIOR_FOLLOWCACHESTRATEGY. See their documentation for more information.
+         * GROUPCACHEBEHAVIOR_NORESIZEONSCALE can also be set if you set it at creation time.
          * It is critical to understand than you HAVE TO play with this behavior in order to achieve a good performance/memory ratio. Caching all groups would certainly be the worst strategy of all.
          */
         public get cacheBehavior(): number {
@@ -341,42 +356,32 @@
                 this.updateCachedStatesOf(sortedDirtyList, true);
             }
 
+            let s = this.actualSize;
+            let a = this.actualScale;
+            let sw = Math.ceil(s.width * a.x);
+            let sh = Math.ceil(s.height * a.y);
+
             // 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 prepared again because their transformation must be recompute
             if (!this._isCachedGroup) {
                 // Compute the WebGL viewport's location/size
                 let t = this._globalTransform.getTranslation();
-                let s = this.actualSize.clone();
                 let rs = this.owner._renderingSize;
-                s.height = Math.min(s.height, rs.height - t.y);
-                s.width = Math.min(s.width, rs.width - t.x);
+                sh = Math.min(sh, rs.height - t.y);
+                sw = Math.min(sw, rs.width - t.x);
                 let x = t.x;
                 let y = 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;
-                let vw = s.width;
-                let vh = s.height;
-
-                if (!this._viewportSize) {
-                    this._viewportSize = new Size(vw, vh);
-                } else {
-                    if (this._viewportSize.width !== vw || this._viewportSize.height !== vh) {
-                        context.forceRefreshPrimitive = true;
-                    }
-                    this._viewportSize.width = vw;
-                    this._viewportSize.height = vh;
-                }
             }
 
             // For a cachedGroup we also check of the group's actualSize is changing, if it's the case then the rendering zone will be change so we also have to dirty all primitives to prepare them again.
-            else {
-                let newSize = this.actualSize.clone();
-                if (!newSize.equals(this._viewportSize)) {
-                    context.forceRefreshPrimitive = true;
-                }
-                this._viewportSize = newSize;
+            if (this._viewportSize.width !== sw || this._viewportSize.height !== sh) {
+                context.forceRefreshPrimitive = true;
+                this._viewportSize.width = sw;
+                this._viewportSize.height = sh;
             }
 
             if ((rd._primDirtyList.length > 0) || context.forceRefreshPrimitive) {
@@ -787,6 +792,7 @@
             this._renderableData._renderingScale = scale;
         }
 
+        private static _uV = new Vector2(1, 1);
         private static _s = Size.Zero();
         private _bindCacheTarget() {
             let curWidth: number;
@@ -794,8 +800,17 @@
             let rd = this._renderableData;
             let rs = rd._renderingScale;
 
-            Group2D._s.width  = Math.ceil(this.actualSize.width * rs);
-            Group2D._s.height = Math.ceil(this.actualSize.height * rs);
+            let noResizeScale = rd._noResizeOnScale;
+            let isCanvas = this.parent == null;
+            let scale: Vector2;
+            if (noResizeScale) {
+                scale = isCanvas ? Group2D._uV: this.parent.actualScale;
+            } else {
+                scale = this.actualScale;
+            }
+
+            Group2D._s.width  = Math.ceil(this.actualSize.width * scale.x * rs);
+            Group2D._s.height = Math.ceil(this.actualSize.height * scale.y * rs);
 
             let sizeChanged = !Group2D._s.equals(rd._cacheSize);
 
@@ -827,7 +842,7 @@
             if (sizeChanged) {
                 rd._cacheSize.copyFrom(Group2D._s);
                 rd._cacheNodeUVs = rd._cacheNode.getUVsForCustomSize(rd._cacheSize);
-                this.scale = this._renderableData._renderingScale;
+
                 if (rd._cacheNodeUVsChangedObservable && rd._cacheNodeUVsChangedObservable.hasObservers()) {
                     rd._cacheNodeUVsChangedObservable.notifyObservers(rd._cacheNodeUVs);
                 }
@@ -914,7 +929,7 @@
 
             // All Group cached mode, all groups are renderable/cached, including the Canvas, groups with the behavior DONTCACHE are renderable/not cached, groups with CACHEINPARENT are logical ones
             else if (canvasStrat === Canvas2D.CACHESTRATEGY_ALLGROUPS) {
-                var gcb = this.cacheBehavior;
+                var gcb = this.cacheBehavior & Group2D.GROUPCACHEBEHAVIOR_OPTIONMASK;
                 if ((gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE) || (gcb === Group2D.GROUPCACHEBEHAVIOR_CACHEINPARENTGROUP)) {
                     this._isRenderableGroup = gcb === Group2D.GROUPCACHEBEHAVIOR_DONTCACHEOVERRIDE;
                     this._isCachedGroup = false;
@@ -936,6 +951,7 @@
 
             // If the group is tagged as renderable we add it to the renderable tree
             if (this._isCachedGroup) {
+                this._renderableData._noResizeOnScale = (this.cacheBehavior & Group2D.GROUPCACHEBEHAVIOR_NORESIZEONSCALE) !== 0;
                 let cur = this.parent;
                 while (cur) {
                     if (cur instanceof Group2D && cur._isRenderableGroup) {
@@ -978,9 +994,12 @@
             this._cacheSize = Size.Zero();
             this._useMipMap = false;
             this._anisotropicLevel = 1;
+            this._noResizeOnScale = false;
         }
 
-        dispose(engine: Engine) {
+        dispose(owner: Canvas2D) {
+            let engine = owner.engine;
+
             if (this._cacheRenderSprite) {
                 this._cacheRenderSprite.dispose();
                 this._cacheRenderSprite = null;
@@ -1056,6 +1075,7 @@
         _cacheSize: Size;
         _useMipMap: boolean;
         _anisotropicLevel: number;
+        _noResizeOnScale: boolean;
 
         _transparentListChanged: boolean;
         _transparentPrimitives: Array<TransparentPrimitiveInfo>;

+ 43 - 37
src/Canvas2d/babylon.lines2d.ts

@@ -381,6 +381,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
+         * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -411,43 +412,44 @@
          */
         constructor(points: Vector2[], settings?: {
 
-            parent            ?: Prim2DBase, 
-            children          ?: Array<Prim2DBase>,
-            id                ?: string,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            rotation          ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            fillThickness     ?: number,
-            closed            ?: boolean,
-            startCap          ?: number,
-            endCap            ?: number,
-            fill              ?: IBrush2D | string,
-            border            ?: IBrush2D | string,
-            borderThickness   ?: number,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+            parent                ?: Prim2DBase, 
+            children              ?: Array<Prim2DBase>,
+            id                    ?: string,
+            position              ?: Vector2,
+            x                     ?: number,
+            y                     ?: number,
+            rotation              ?: number,
+            scale                 ?: number,
+            scaleX                ?: number,
+            scaleY                ?: number,
+            dontInheritParentScale?: boolean,
+            opacity               ?: number,
+            zOrder                ?: number, 
+            origin                ?: Vector2,
+            fillThickness         ?: number,
+            closed                ?: boolean,
+            startCap              ?: number,
+            endCap                ?: number,
+            fill                  ?: IBrush2D | string,
+            border                ?: IBrush2D | string,
+            borderThickness       ?: number,
+            isVisible             ?: boolean,
+            isPickable            ?: boolean,
+            isContainer           ?: boolean,
+            childrenFlatZOrder    ?: boolean,
+            marginTop             ?: number | string,
+            marginLeft            ?: number | string,
+            marginRight           ?: number | string,
+            marginBottom          ?: number | string,
+            margin                ?: number | string,
+            marginHAlignment      ?: number,
+            marginVAlignment      ?: number,
+            marginAlignment       ?: string,
+            paddingTop            ?: number | string,
+            paddingLeft           ?: number | string,
+            paddingRight          ?: number | string,
+            paddingBottom         ?: number | string,
+            padding               ?: string,
         }) {
 
             if (!settings) {
@@ -1197,6 +1199,10 @@
             return res;
         }
 
+        protected applyActualScaleOnTransform(): boolean {
+            return true;
+        }
+
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataPart(part)) {
                 return false;

+ 89 - 32
src/Canvas2d/babylon.prim2dBase.ts

@@ -1294,37 +1294,38 @@
         public  static _bigInt = Math.pow(2, 30);
 
         constructor(settings: {
-            parent            ?: Prim2DBase,
-            id                ?: string,
-            children          ?: Array<Prim2DBase>,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            rotation          ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            layoutEngine      ?: LayoutEngineBase | string,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+            parent                  ?: Prim2DBase,
+            id                      ?: string,
+            children                ?: Array<Prim2DBase>,
+            position                ?: Vector2,
+            x                       ?: number,
+            y                       ?: number,
+            rotation                ?: number,
+            scale                   ?: number,
+            scaleX                  ?: number,
+            scaleY                  ?: number,
+            dontInheritParentScale  ?: boolean,
+            opacity                 ?: number,
+            zOrder                  ?: number, 
+            origin                  ?: Vector2,
+            layoutEngine            ?: LayoutEngineBase | string,
+            isVisible               ?: boolean,
+            isPickable              ?: boolean,
+            isContainer             ?: boolean,
+            childrenFlatZOrder      ?: boolean,
+            marginTop               ?: number | string,
+            marginLeft              ?: number | string,
+            marginRight             ?: number | string,
+            marginBottom            ?: number | string,
+            margin                  ?: number | string,
+            marginHAlignment        ?: number,
+            marginVAlignment        ?: number,
+            marginAlignment         ?: string,
+            paddingTop              ?: number | string,
+            paddingLeft             ?: number | string,
+            paddingRight            ?: number | string,
+            paddingBottom           ?: number | string,
+            padding                 ?: string,
         }) {
 
             // Avoid checking every time if the object exists
@@ -1391,6 +1392,8 @@
             this._zOrder = 0;
             this._zMax = 0;
             this._firstZDirtyIndex = Prim2DBase._bigInt;
+            this._actualOpacity = 0;
+            this._actualScale = Vector2.Zero();
             let isPickable = true;
             let isContainer = true;
             if (settings.isPickable !== undefined) {
@@ -1399,7 +1402,10 @@
             if (settings.isContainer !== undefined) {
                 isContainer = settings.isContainer;
             }
-            this._setFlags((isPickable ? SmartPropertyPrim.flagIsPickable : 0) | SmartPropertyPrim.flagBoundingInfoDirty | SmartPropertyPrim.flagActualOpacityDirty | (isContainer ? SmartPropertyPrim.flagIsContainer : 0));
+            if (settings.dontInheritParentScale) {
+                this._setFlags(SmartPropertyPrim.flagDontInheritParentScale);
+            }
+            this._setFlags((isPickable ? SmartPropertyPrim.flagIsPickable : 0) | SmartPropertyPrim.flagBoundingInfoDirty | SmartPropertyPrim.flagActualOpacityDirty | (isContainer ? SmartPropertyPrim.flagIsContainer : 0) | SmartPropertyPrim.flagActualScaleDirty);
 
             if (settings.opacity != null) {
                 this._opacity = settings.opacity;
@@ -1884,6 +1890,8 @@
          */
         public set scale(value: number) {
             this._scale.x = this._scale.y = value;
+            this._setFlags(SmartPropertyPrim.flagActualScaleDirty);
+            this._spreadActualScaleDirty();
         }
 
         public get scale(): number {
@@ -2109,6 +2117,8 @@
          */
         public set scaleX(value: number) {
             this._scale.x = value;
+            this._setFlags(SmartPropertyPrim.flagActualScaleDirty);
+            this._spreadActualScaleDirty();
         }
 
         public get scaleX(): number {
@@ -2121,12 +2131,58 @@
          */
         public set scaleY(value: number) {
             this._scale.y = value;
+            this._setFlags(SmartPropertyPrim.flagActualScaleDirty);
+            this._spreadActualScaleDirty();
         }
 
         public get scaleY(): number {
             return this._scale.y;
         }
 
+        private _spreadActualScaleDirty() {
+            for (let child of this._children) {
+                child._setFlags(SmartPropertyPrim.flagActualScaleDirty);
+                child._spreadActualScaleDirty();
+            }
+        }
+
+        /**
+         * Returns the actual scale of this Primitive, the value is computed from the scale property of this primitive, multiplied by the actualScale of its parent one (if any). The Vector2 object returned contains the scale for both X and Y axis
+         */
+        public get actualScale(): Vector2 {
+            if (this._isFlagSet(SmartPropertyPrim.flagActualScaleDirty)) {
+                let cur = this._isFlagSet(SmartPropertyPrim.flagDontInheritParentScale) ? null : this.parent;
+                let sx = this.scaleX;
+                let sy = this.scaleY;
+                while (cur) {
+                    sx *= cur.scaleX;
+                    sy *= cur.scaleY;
+                    cur = cur._isFlagSet(SmartPropertyPrim.flagDontInheritParentScale) ? null : cur.parent;
+                }
+
+                this._actualScale.copyFromFloats(sx, sy);
+                this._clearFlags(SmartPropertyPrim.flagActualScaleDirty);
+            }
+            return this._actualScale;
+        }
+
+        /**
+         * Get the actual Scale of the X axis, shortcut for this.actualScale.x
+         */
+        public get actualScaleX(): number {
+            return this.actualScale.x;
+        }
+
+        /**
+         * Get the actual Scale of the Y axis, shortcut for this.actualScale.y
+         */
+        public get actualScaleY(): number {
+            return this.actualScale.y;
+        }
+
+        /**
+         * Get the actual opacity level, this property is computed from the opacity property, multiplied by the actualOpacity of its parent (if any)
+         */
         public get actualOpacity(): number {
             if (this._isFlagSet(SmartPropertyPrim.flagActualOpacityDirty)) {
                 let cur = this.parent;
@@ -3208,6 +3264,7 @@
         private _origin: Vector2;
         protected _opacity: number;
         private _actualOpacity: number;
+        private _actualScale : Vector2;
 
         // Stores the step of the parent for which the current global transform was computed
         // If the parent has a new step, it means this prim's global transform must be updated

+ 44 - 40
src/Canvas2d/babylon.rectangle2d.ts

@@ -299,6 +299,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y settings can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
+         * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -325,44 +326,45 @@
          * - paddingBottom: bottom padding, can be a number (will be pixels) or a string (see PrimitiveThickness.fromString)
          * - padding: top, left, right and bottom padding formatted as a single string (see PrimitiveThickness.fromString)
          */
-        constructor(settings  ?: {
-            parent            ?: Prim2DBase, 
-            children          ?: Array<Prim2DBase>,
-            id                ?: string,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            rotation          ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            size              ?: Size,
-            width             ?: number,
-            height            ?: number,
-            roundRadius       ?: number,
-            fill              ?: IBrush2D | string,
-            border            ?: IBrush2D | string,
-            borderThickness   ?: number,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+        constructor(settings      ?: {
+            parent                ?: Prim2DBase, 
+            children              ?: Array<Prim2DBase>,
+            id                    ?: string,
+            position              ?: Vector2,
+            x                     ?: number,
+            y                     ?: number,
+            rotation              ?: number,
+            scale                 ?: number,
+            scaleX                ?: number,
+            scaleY                ?: number,
+            dontInheritParentScale?: boolean,
+            opacity               ?: number,
+            zOrder                ?: number, 
+            origin                ?: Vector2,
+            size                  ?: Size,
+            width                 ?: number,
+            height                ?: number,
+            roundRadius           ?: number,
+            fill                  ?: IBrush2D | string,
+            border                ?: IBrush2D | string,
+            borderThickness       ?: number,
+            isVisible             ?: boolean,
+            isPickable            ?: boolean,
+            isContainer           ?: boolean,
+            childrenFlatZOrder    ?: boolean,
+            marginTop             ?: number | string,
+            marginLeft            ?: number | string,
+            marginRight           ?: number | string,
+            marginBottom          ?: number | string,
+            margin                ?: number | string,
+            marginHAlignment      ?: number,
+            marginVAlignment      ?: number,
+            marginAlignment       ?: string,
+            paddingTop            ?: number | string,
+            paddingLeft           ?: number | string,
+            paddingRight          ?: number | string,
+            paddingBottom         ?: number | string,
+            padding               ?: string,
         }) {
 
             // Avoid checking every time if the object exists
@@ -517,12 +519,14 @@
             if (part.id === Shape2D.SHAPE2D_BORDERPARTID) {
                 let d = <Rectangle2DInstanceData>part;
                 let size = this.actualSize;
-                d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
+                let s = this.actualScale;
+                d.properties = new Vector3(size.width * s.x, size.height * s.y, this.roundRadius || 0);
             }
             else if (part.id === Shape2D.SHAPE2D_FILLPARTID) {
                 let d = <Rectangle2DInstanceData>part;
                 let size = this.actualSize;
-                d.properties = new Vector3(size.width, size.height, this.roundRadius || 0);
+                let s = this.actualScale;
+                d.properties = new Vector3(size.width * s.x, size.height * s.y, this.roundRadius || 0);
             }
             return true;
         }

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

@@ -889,6 +889,10 @@
 
         }
 
+        protected applyActualScaleOnTransform(): boolean {
+            return true;
+        }
+
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!this.isVisible) {
                 return false;
@@ -903,13 +907,16 @@
             return true;
         }
 
+        private static _uV = new Vector2(1, 1);
+
         /**
          * Update the instanceDataBase level properties of a part
          * @param part the part to update
          * @param positionOffset to use in multi part per primitive (e.g. the Text2D has N parts for N letter to display), this give the offset to apply (e.g. the position of the letter from the bottom/left corner of the text).
          */
         protected updateInstanceDataPart(part: InstanceDataBase, positionOffset: Vector2 = null) {
-            let t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);
+            let t = this._globalTransform.multiply(this.renderGroup.invGlobalTransform);    // Compute the transformation into the renderGroup's space
+            let rgScale = this._areSomeFlagsSet(SmartPropertyPrim.flagDontInheritParentScale) ? RenderablePrim2D._uV : this.renderGroup.actualScale;         // We still need to apply the scale of the renderGroup to our rendering, so get it.
             let size = (<Size>this.renderGroup.viewportSize);
             let zBias = this.actualZOffset;
 
@@ -926,11 +933,19 @@
             // 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].
+            // At last we don't forget to apply the actualScale of the Render Group to tx[0] and ty[1] to propagate scaling correctly
             let w = size.width;
             let h = size.height;
             let invZBias = 1 / zBias;
-            let tx = new Vector4(t.m[0] * 2 / w, t.m[4] * 2 / w, 0/*t.m[8]*/, ((t.m[12] + offX) * 2 / w) - 1);
-            let ty = new Vector4(t.m[1] * 2 / h, t.m[5] * 2 / h, 0/*t.m[9]*/, ((t.m[13] + offY) * 2 / h) - 1);
+            let tx = new Vector4(t.m[0] * rgScale.x * 2 / w, t.m[4] * 2 / w, 0/*t.m[8]*/, ((t.m[12] + offX) * rgScale.x * 2 / w) - 1);
+            let ty = new Vector4(t.m[1] * 2 / h, t.m[5] * rgScale.y * 2 / h, 0/*t.m[9]*/, ((t.m[13] + offY) * rgScale.y * 2 / h) - 1);
+
+            if (!this.applyActualScaleOnTransform()) {
+                let las = this.actualScale;
+                tx.x /= las.x;
+                ty.y /= las.y;
+            }
+
             part.transformX = tx;
             part.transformY = ty;
             part.opacity = this.actualOpacity;

+ 4 - 0
src/Canvas2d/babylon.shape2d.ts

@@ -126,6 +126,10 @@
             return cat;
         }
 
+        protected applyActualScaleOnTransform(): boolean {
+            return false;
+        }
+
         protected refreshInstanceDataPart(part: InstanceDataBase): boolean {
             if (!super.refreshInstanceDataPart(part)) {
                 return false;

+ 2 - 0
src/Canvas2d/babylon.smartPropertyPrim.ts

@@ -633,6 +633,8 @@
         public static flagPrimInDirtyList        = 0x0008000;    // set if the primitive is in the primDirtyList
         public static flagIsContainer            = 0x0010000;    // set if the primitive is a container
         public static flagNeedRefresh            = 0x0020000;    // set if the primitive wasn't successful at refresh
+        public static flagActualScaleDirty       = 0x0040000;    // set if the actualScale property needs to be recomputed
+        public static flagDontInheritParentScale = 0x0080000;    // set if the actualScale must not use its parent's scale to be computed
 
         private   _flags             : number;
         private   _externalData      : StringDictionary<Object>;

+ 37 - 35
src/Canvas2d/babylon.sprite2d.ts

@@ -270,6 +270,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
+         * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -298,41 +299,42 @@
          */
         constructor(texture: Texture, settings?: {
 
-            parent            ?: Prim2DBase,
-            children          ?: Array<Prim2DBase>,
-            id                ?: string,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            rotation          ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            spriteSize        ?: Size,
-            spriteLocation    ?: Vector2,
-            spriteScaleFactor ?: Vector2,
-            invertY           ?: boolean,
-            alignToPixel      ?: boolean,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+            parent                ?: Prim2DBase,
+            children              ?: Array<Prim2DBase>,
+            id                    ?: string,
+            position              ?: Vector2,
+            x                     ?: number,
+            y                     ?: number,
+            rotation              ?: number,
+            scale                 ?: number,
+            scaleX                ?: number,
+            scaleY                ?: number,
+            dontInheritParentScale?: boolean,
+            opacity               ?: number,
+            zOrder                ?: number, 
+            origin                ?: Vector2,
+            spriteSize            ?: Size,
+            spriteLocation        ?: Vector2,
+            spriteScaleFactor     ?: Vector2,
+            invertY               ?: boolean,
+            alignToPixel          ?: boolean,
+            isVisible             ?: boolean,
+            isPickable            ?: boolean,
+            isContainer           ?: boolean,
+            childrenFlatZOrder    ?: boolean,
+            marginTop             ?: number | string,
+            marginLeft            ?: number | string,
+            marginRight           ?: number | string,
+            marginBottom          ?: number | string,
+            margin                ?: number | string,
+            marginHAlignment      ?: number,
+            marginVAlignment      ?: number,
+            marginAlignment       ?: string,
+            paddingTop            ?: number | string,
+            paddingLeft           ?: number | string,
+            paddingRight          ?: number | string,
+            paddingBottom         ?: number | string,
+            padding               ?: string,
         }) {
 
             if (!settings) {

+ 37 - 35
src/Canvas2d/babylon.text2d.ts

@@ -263,6 +263,7 @@
          * - position: the X & Y positions relative to its parent. Alternatively the x and y properties can be set. Default is [0;0]
          * - rotation: the initial rotation (in radian) of the primitive. default is 0
          * - scale: the initial scale of the primitive. default is 1. You can alternatively use scaleX &| scaleY to apply non uniform scale
+         * - dontInheritParentScale: if set the parent's scale won't be taken into consideration to compute the actualScale property
          * - opacity: set the overall opacity of the primitive, 1 to be opaque (default), less than 1 to be transparent.
          * - zOrder: override the zOrder with the specified value
          * - origin: define the normalized origin point location, default [0.5;0.5]
@@ -291,41 +292,42 @@
          */
         constructor(text: string, settings?: {
 
-            parent            ?: Prim2DBase, 
-            children          ?: Array<Prim2DBase>,
-            id                ?: string,
-            position          ?: Vector2,
-            x                 ?: number,
-            y                 ?: number,
-            rotation          ?: number,
-            scale             ?: number,
-            scaleX            ?: number,
-            scaleY            ?: number,
-            opacity           ?: number,
-            zOrder            ?: number, 
-            origin            ?: Vector2,
-            fontName          ?: string,
-            fontSuperSample   ?: boolean,
-            defaultFontColor  ?: Color4,
-            size              ?: Size,
-            tabulationSize    ?: number,
-            isVisible         ?: boolean,
-            isPickable        ?: boolean,
-            isContainer       ?: boolean,
-            childrenFlatZOrder?: boolean,
-            marginTop         ?: number | string,
-            marginLeft        ?: number | string,
-            marginRight       ?: number | string,
-            marginBottom      ?: number | string,
-            margin            ?: number | string,
-            marginHAlignment  ?: number,
-            marginVAlignment  ?: number,
-            marginAlignment   ?: string,
-            paddingTop        ?: number | string,
-            paddingLeft       ?: number | string,
-            paddingRight      ?: number | string,
-            paddingBottom     ?: number | string,
-            padding           ?: string,
+            parent                ?: Prim2DBase, 
+            children              ?: Array<Prim2DBase>,
+            id                    ?: string,
+            position              ?: Vector2,
+            x                     ?: number,
+            y                     ?: number,
+            rotation              ?: number,
+            scale                 ?: number,
+            scaleX                ?: number,
+            scaleY                ?: number,
+            dontInheritParentScale?: boolean,
+            opacity               ?: number,
+            zOrder                ?: number, 
+            origin                ?: Vector2,
+            fontName              ?: string,
+            fontSuperSample       ?: boolean,
+            defaultFontColor      ?: Color4,
+            size                  ?: Size,
+            tabulationSize        ?: number,
+            isVisible             ?: boolean,
+            isPickable            ?: boolean,
+            isContainer           ?: boolean,
+            childrenFlatZOrder    ?: boolean,
+            marginTop             ?: number | string,
+            marginLeft            ?: number | string,
+            marginRight           ?: number | string,
+            marginBottom          ?: number | string,
+            margin                ?: number | string,
+            marginHAlignment      ?: number,
+            marginVAlignment      ?: number,
+            marginAlignment       ?: string,
+            paddingTop            ?: number | string,
+            paddingLeft           ?: number | string,
+            paddingRight          ?: number | string,
+            paddingBottom         ?: number | string,
+            padding               ?: string,
         }) {
 
             if (!settings) {