Browse Source

Canvas2D: adding Scale9 and AtlasPicture features

nockawa 8 năm trước cách đây
mục cha
commit
2b48fe72e3

+ 2 - 1
Tools/Gulp/config.json

@@ -479,6 +479,7 @@
         "../../canvas2D/src/Engine/babylon.rectangle2d.ts",
         "../../canvas2D/src/Engine/babylon.ellipse2d.ts",
         "../../canvas2D/src/Engine/babylon.sprite2d.ts",
+        "../../canvas2D/src/Engine/babylon.atlasPicture.ts",
         "../../canvas2D/src/Engine/babylon.text2d.ts",
         "../../canvas2D/src/Engine/babylon.lines2d.ts",
         "../../canvas2D/src/Engine/babylon.canvas2d.ts",
@@ -491,7 +492,7 @@
         "../../canvas2D/src/GUI/babylon.gui.label.ts",
         "../../canvas2D/src/GUI/babylon.gui.button.ts",
         "../../canvas2D/src/lib.d.ts"
-        ],
+      ],
       "shaderFiles": [
           "../../canvas2D/src/shaders/**.fx", "!../../canvas2D/src/shaders/**.js.fx"
       ],

+ 302 - 0
canvas2D/src/Engine/babylon.atlasPicture.ts

@@ -0,0 +1,302 @@
+module BABYLON {
+    /**
+     * Interface to create your own Loader of Atlas Data file.
+     * Call the AtlasPictureInfoFactory.addLoader to addd your loader instance
+     */
+    export interface IAtlasLoader {
+        loadFile(content: any): { api: AtlasPictureInfo, errorMsg: string, errorCode: number };
+    }
+
+    // Proxy Class for the TexturePacker's JSON Array data format
+    interface IFrame {
+        filename: string;
+        frame: { x: number, y: number, w: number, h: number };
+        rotated: boolean;
+        trimmed: boolean;
+        srpiteSourceSize: { x: number, y: number, w: number, h: number };
+        sourceSize: { x: number, y: number, w: number, h: number };
+    }
+
+    // Proxy Class for the TexturePacker's JSON Array data format
+    interface IMeta {
+        app: string;
+        version: string;
+        image: string;
+        format: string;
+        size: { w: number, h: number }
+        scale: string;
+        smartupdate: string;
+    }
+
+    // Loader class for the TexturePacker's JSON Array data format
+    class JSONArrayLoader implements IAtlasLoader {
+
+        loadFile(content): { api: AtlasPictureInfo, errorMsg: string, errorCode: number } {
+            let errorMsg: string = null;
+            let errorCode: number = 0;
+            let root = null;
+            let api: AtlasPictureInfo = null;
+            try {
+                try {
+                    root = JSON.parse(content);
+                    let frames = <Array<IFrame>>root.frames;
+                    let meta = <IMeta>root.meta;
+
+                    api = new AtlasPictureInfo();
+                    api.atlasSize = new Size(meta.size.w, meta.size.h);
+                    api.subPictures = new StringDictionary<AtlasSubPictureInfo>();
+
+                    for (let f of frames) {
+                        let aspi = new AtlasSubPictureInfo();
+                        aspi.name = f.filename;
+                        aspi.location = new Vector2(f.frame.x, api.atlasSize.height - (f.frame.y + f.frame.h));
+                        aspi.size = new Size(f.frame.w, f.frame.h);
+
+                        api.subPictures.add(aspi.name, aspi);
+                    }
+
+                } catch (ex1) {
+                    errorMsg = "Invalid JSON file";
+                    errorCode = -1;
+                }                 
+            } catch (ex2) {
+                errorMsg = "Unknown Exception: " + ex2;
+                errorCode = -2;
+            } 
+            return { api: api, errorMsg: errorMsg, errorCode: errorCode };
+        }
+    }
+
+    /**
+     * This class will contains information about a sub picture present in an Atlas Picture.
+     */
+    export class AtlasSubPictureInfo {
+        /**
+         * Name of the SubPicture, generally the filename of the initial picture file.
+         */
+        name: string;
+
+        /**
+         * Location of the bottom/left corner of the sub picture from the bottom/left corner the Atlas Picture
+         */
+        location: Vector2;
+
+        /**
+         * Size in pixel of the sub picture
+         */
+        size: Size;
+    }
+
+    /**
+     * This class represent an Atlas Picture, it contains the information of all the sub pictures and the Texture that stores the bitmap.
+     * You get an instance of this class using methods of the AtlasPictureInfoFactory
+     */
+    export class AtlasPictureInfo {
+        /**
+         * Creates many sprite from the Atlas Picture
+         * @param filterCallback a predicate if true is returned then the corresponding sub picture will be used to create a sprite.
+         * The Predicate has many parameters:
+         *  - index: just an index incremented at each sub picture submitted for Sprite creation
+         *  - name: the sub picture's name
+         *  - aspi: the AtlasSubPictureInfo corresponding to the submitted sub picture
+         *  - settings: the Sprite2D creation settings, you can alter this JSON object but BEWARE, the alterations will be kept for subsequent Sprite2D creations!
+         * @param spriteSettings The Sprite2D settings to use for Sprite creation, this JSON object will be passed to the filterCallback for you to alter it, if needed.
+         */
+        createSprites(filterCallback: (index: number, name: string, aspi: AtlasSubPictureInfo, settings: any) => boolean,
+            spriteSettings: {
+                parent?: Prim2DBase,
+                position?: Vector2,
+                x?: number,
+                y?: number,
+                rotation?: number,
+                size?: Size,
+                scale?: number,
+                scaleX?: number,
+                scaleY?: number,
+                dontInheritParentScale?: boolean,
+                opacity?: number,
+                zOrder?: number,
+                origin?: Vector2,
+                scale9?: Vector4,
+                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,
+            }): Array<Sprite2D> {
+
+            let res = new Array<Sprite2D>();
+
+            let index = 0;
+            this.subPictures.forEach((k, v) => {
+                if (!filterCallback || filterCallback(index++, k, v, spriteSettings)) {
+                    let s = this.createSprite(k, spriteSettings);
+                    if (s) {
+                        res.push(s);
+                    }
+                }
+            });
+            return res;
+        }
+
+        /**
+         * Create one Sprite from a sub picture
+         * @param subPictureName the name of the sub picture to use
+         * @param spriteSettings the Sprite2D settings to use for the Sprite instance creation
+         */
+        createSprite(subPictureName: string, spriteSettings: {
+            parent?: Prim2DBase,
+            position?: Vector2,
+            x?: number,
+            y?: number,
+            rotation?: number,
+            size?: Size,
+            scale?: number,
+            scaleX?: number,
+            scaleY?: number,
+            dontInheritParentScale?: boolean,
+            opacity?: number,
+            zOrder?: number,
+            origin?: Vector2,
+            scale9?: Vector4,
+            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,
+        }): Sprite2D {
+            let spi = this.subPictures.get(subPictureName);
+            if (!spi) {
+                return null;
+            }
+            if (!spriteSettings) {
+                spriteSettings = {};
+            }
+            let s = <any>spriteSettings;
+            s.id = subPictureName;
+            s.spriteLocation = spi.location;
+            s.spriteSize = spi.size;
+
+            let sprite = new Sprite2D(this.texture, spriteSettings);
+            return sprite;
+        }
+
+        /**
+         * Size of the Atlas Picture
+         */
+        atlasSize: Size;
+
+        /**
+         * String Dictionary of all the sub pictures, the key is the sub picture's name, the value is the info object
+         */
+        subPictures: StringDictionary<AtlasSubPictureInfo>;
+
+        /**
+         * The Texture associated to the Atlas Picture info
+         */
+        texture: Texture;
+    }
+
+    /**
+     * This if the Factory class containing static method to create Atlas Pictures Info objects or add new loaders
+     */
+    export class AtlasPictureInfoFactory {
+        /**
+         * Add a custom loader
+         * @param fileExtension must be the file extension (without the dot) of the file that is loaded by this loader (e.g.: json)
+         * @param plugin the instance of the loader
+         */
+        public static addLoader(fileExtension: string, plugin: IAtlasLoader) {
+            AtlasPictureInfoFactory._initialize();
+
+            let a = AtlasPictureInfoFactory.plugins.getOrAddWithFactory(fileExtension.toLocaleLowerCase(), () => new Array<IAtlasLoader>());
+            a.push(plugin);
+        }
+
+        /**
+         * Load an Atlas Picture Info object from a data file at a given url and with a given texture
+         * @param texture the texture containing the atlas bitmap
+         * @param url the URL of the Atlas Info data file
+         * @param loaded a callback that will be called when the AtlasPictureInfo object will be loaded and ready
+         * @param error a callback that will be called in case of error
+         */
+        public static loadFromUrl(texture: Texture, url: string, loaded: (api: AtlasPictureInfo) => void, error: (msg: string, code: number) => void = null) {
+            AtlasPictureInfoFactory._initialize();
+
+            var xhr = new XMLHttpRequest();
+            xhr.onreadystatechange = () => {
+                if (xhr.readyState === XMLHttpRequest.DONE) {
+                    if (xhr.status === 200) {
+                        let ext = url.split('.').pop().split(/\#|\?/)[0];
+                        let plugins = AtlasPictureInfoFactory.plugins.get(ext.toLocaleLowerCase());
+                        if (!plugins) {
+                            if (error) {
+                                error("couldn't find a plugin for this file extension", -1);
+                            }
+                            return;
+                        }
+                        for (let p of plugins) {
+                            let ret = p.loadFile(xhr.response);
+                            if (ret) {
+                                if (ret.api && loaded) {
+                                    ret.api.texture = texture;
+                                    loaded(ret.api);
+                                } else if (error) {
+                                    error(ret.errorMsg, ret.errorCode);
+                                }
+                                break;
+                            }
+                        }
+                    } else {
+                        if (error) {
+                            error("Couldn't load file through HTTP Request, HTTP Status " + xhr.status, xhr.status);
+                        }
+                    }
+                }
+            }
+            xhr.open("GET", url, true);
+            xhr.send();
+            return null;
+        }
+
+        private static _initialize() {
+            if (AtlasPictureInfoFactory.plugins !== null) {
+                return;
+            }
+
+            AtlasPictureInfoFactory.plugins = new StringDictionary<Array<IAtlasLoader>>();
+            AtlasPictureInfoFactory.addLoader("json", new JSONArrayLoader());
+
+        }
+
+        private static plugins: StringDictionary<Array<IAtlasLoader>> = null;
+    }
+}

+ 7 - 1
canvas2D/src/Engine/babylon.prim2dBase.ts

@@ -1943,7 +1943,10 @@
          */
         @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 6, pi => Prim2DBase.sizeProperty = pi, false, true)
         public get size(): Size {
+            return this.internalGetSize();
+        }
 
+        protected internalGetSize(): Size {
             if (!this._size || this._size.width == null || this._size.height == null) {
 
                 if (Prim2DBase.boundinbBoxReentrency) {
@@ -1961,11 +1964,14 @@
                 return this._boundingSize;
 
             }
-
             return this._size;
         }
 
         public set size(value: Size) {
+            this.internalSetSize(value);
+        }
+
+        protected internalSetSize(value: Size) {
             this._size = value;
         }
 

+ 3 - 0
canvas2D/src/Engine/babylon.renderablePrim2d.ts

@@ -27,6 +27,9 @@
                     // Only map if there's no category assigned to the instance data or if there's a category and it's in the given list
                     if (!attrib.category || categories.indexOf(attrib.category) !== -1) {
                         let index = effect.getAttributeLocationByName(attrib.attributeName);
+                        if (index === - 1) {
+                            throw new Error(`Attribute ${attrib.attributeName} was not found in Effect: ${effect.name}. It's certainly no longer used in the Effect's Shaders`);
+                        }
                         let iai = new InstancingAttributeInfo();
                         iai.index = index;
                         iai.attributeSize = attrib.size / 4; // attrib.size is in byte and we need to store in "component" (i.e float is 1, vec3 is 3)

+ 163 - 93
canvas2D/src/Engine/babylon.sprite2d.ts

@@ -85,51 +85,6 @@
         }
     }
 
-    export class Sprite2DInstanceData extends InstanceDataBase {
-        constructor(partId: number) {
-            super(partId, 1);
-        }
-
-        @instanceData()
-        get topLeftUV(): Vector2 {
-            return null;
-        }
-        set topLeftUV(value: Vector2) {
-        }
-
-        @instanceData()
-        get sizeUV(): Vector2 {
-            return null;
-        }
-        set sizeUV(value: Vector2) {
-        }
-
-        @instanceData()
-        get scaleFactor(): Vector2 {
-            return null;
-        }
-        set scaleFactor(value: Vector2) {
-        }
-
-        @instanceData()
-        get textureSize(): Vector2 {
-            return null;
-        }
-        set textureSize(value: Vector2) {
-        }
-
-        // 3 floats being:
-        // - x: frame number to display
-        // - y: invertY setting
-        // - z: alignToPixel setting
-        @instanceData()
-        get properties(): Vector3 {
-            return null;
-        }
-        set properties(value: Vector3) {
-        }
-    }
-
     @className("Sprite2D", "BABYLON")
     /**
      * Primitive that displays a Sprite/Picture
@@ -137,13 +92,16 @@
     export class Sprite2D extends RenderablePrim2D {
         static SPRITE2D_MAINPARTID = 1;
 
+        static SHAPE2D_CATEGORY_SCALE9 = "Scale9";
+
         public static textureProperty: Prim2DPropInfo;
         public static useAlphaFromTextureProperty: Prim2DPropInfo;
         public static actualSizeProperty: Prim2DPropInfo;
+        public static spriteSizeProperty: Prim2DPropInfo;
         public static spriteLocationProperty: Prim2DPropInfo;
         public static spriteFrameProperty: Prim2DPropInfo;
         public static invertYProperty: Prim2DPropInfo;
-        public static spriteScaleFactorProperty: Prim2DPropInfo;
+        public static spriteScale9Property: Prim2DPropInfo;
 
         @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 1, pi => Sprite2D.textureProperty = pi)
         /**
@@ -174,6 +132,19 @@
             this._updateRenderMode();
         }
 
+        public get size(): Size {
+            if (this._size == null) {
+                return this.spriteSize;
+            }
+            return this.internalGetSize();
+        }
+
+        public set size(value: Size) {
+            this._useSize = value != null;
+            this.internalSetSize(value);
+            this._updateSpriteScaleFactor();
+        }
+
         @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, pi => Sprite2D.actualSizeProperty = pi, false, true)
         /**
          * Get/set the actual size of the sprite to display
@@ -189,19 +160,32 @@
             this._actualSize = value;
         }
 
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Sprite2D.spriteLocationProperty = pi)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, pi => Sprite2D.spriteSizeProperty = pi)
+        /**
+         * Get/set the sprite location (in pixels) in the texture
+         */
+        public get spriteSize(): Size {
+            return this._spriteSize;
+        }
+
+        public set spriteSize(value: Size) {
+            this._spriteSize = value;
+            this._updateSpriteScaleFactor();
+        }
+
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, pi => Sprite2D.spriteLocationProperty = pi)
         /**
          * Get/set the sprite location (in pixels) in the texture
          */
         public get spriteLocation(): Vector2 {
-            return this._location;
+            return this._spriteLocation;
         }
 
         public set spriteLocation(value: Vector2) {
-            this._location = value;
+            this._spriteLocation = value;
         }
 
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 5, pi => Sprite2D.spriteFrameProperty = pi)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 6, pi => Sprite2D.spriteFrameProperty = pi)
         /**
          * Get/set the sprite frame to display.
          * The frame number is just an offset applied horizontally, based on the sprite's width. it does not wrap, all the frames must be on the same line.
@@ -214,7 +198,7 @@
             this._spriteFrame = value;
         }
 
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 6, pi => Sprite2D.invertYProperty = pi)
+        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 7, pi => Sprite2D.invertYProperty = pi)
         /**
          * Get/set if the sprite texture coordinates should be inverted on the Y axis
          */
@@ -226,42 +210,25 @@
             this._invertY = value;
         }
 
-        @instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 7, pi => Sprite2D.spriteScaleFactorProperty = pi)
+        @modelLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 8, pi => Sprite2D.spriteScale9Property = pi)
         /**
-         * Get/set the sprite location (in pixels) in the texture
+         * Get/set the texture that contains the sprite to display
          */
-        public get spriteScaleFactor(): Vector2 {
-            return this._spriteScaleFactor;
+        public get isScale9(): boolean {
+            return this._scale9!==null;
         }
 
-        public set spriteScaleFactor(value: Vector2) {
-            this._spriteScaleFactor = value;
-        }
+        //@instanceLevelProperty(RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 7, pi => Sprite2D.spriteScaleFactorProperty = pi)
+        ///**
+        // * Get/set the sprite location (in pixels) in the texture
+        // */
+        //public get spriteScaleFactor(): Vector2 {
+        //    return this._spriteScaleFactor;
+        //}
 
-        /**
-         * Sets the scale of the sprite using a BABYLON.Size(w,h).
-         * Keeps proportion by taking the maximum of the two scale for x and y.
-         * @param {Size} size Size(width,height)
-         */
-        public scaleToSize(size: Size) {
-            var baseSize = this.size;
-            if (baseSize == null || !this.texture.isReady()) {
-                // we're probably at initiation of the scene, size is not set
-                if (this.texture.isReady()) {
-                    baseSize = <Size>this.texture.getBaseSize();
-                }
-                else {
-                    // the texture is not ready, wait for it to load before calling scaleToSize again
-                    var thisObject = <Sprite2D>this;
-                    this.texture.onLoadObservable.add(function () {
-                            thisObject.scaleToSize(size); 
-                        });
-                    return;
-                }
-            }
-            
-            this.scale = Math.max(size.height / baseSize.height, size.width / baseSize.width);
-        }
+        //public set spriteScaleFactor(value: Vector2) {
+        //    this._spriteScaleFactor = value;
+        //}
 
         /**
          * Get/set if the sprite rendering should be aligned to the target rendering device pixel or not
@@ -295,6 +262,10 @@
             return true;
         }
 
+        public get isSizeAuto(): boolean {
+            return this.size == null;
+        }
+
         /**
          * Create an 2D Sprite primitive
          * @param texture the texture that stores the sprite to render
@@ -305,13 +276,15 @@
          * - 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
+         * - size: the size of the sprite displayed in the canvas, if not specified the spriteSize will be used
          * - 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]
-         * - spriteSize: the size of the sprite (in pixels), if null the size of the given texture will be used, default is null.
+         * - spriteSize: the size of the sprite (in pixels) as it is stored in the texture, if null the size of the given texture will be used, default is null.
          * - spriteLocation: the location (in pixels) in the texture of the top/left corner of the Sprite to display, default is null (0,0)
-         * - spriteScaleFactor: say you want to display a sprite twice as big as its bitmap which is 64,64, you set the spriteSize to 128,128 and have to set the spriteScaleFactory to 0.5,0.5 in order to address only the 64,64 pixels of the bitmaps. Default is 1,1.
+         * - spriteScaleFactor: DEPRECATED. Old behavior: say you want to display a sprite twice as big as its bitmap which is 64,64, you set the spriteSize to 128,128 and have to set the spriteScaleFactory to 0.5,0.5 in order to address only the 64,64 pixels of the bitmaps. Default is 1,1.
+         * - scale9: draw the sprite as a Scale9 sprite, see http://yannickloriot.com/2013/03/9-patch-technique-in-cocos2d/ for more info. x, y, w, z are left, bottom, right, top coordinate of the resizable box
          * - invertY: if true the texture Y will be inverted, default is false.
          * - alignToPixel: if true the sprite's texels will be aligned to the rendering viewport pixels, ensuring the best rendering quality but slow animations won't be done as smooth as if you set false. If false a texel could lies between two pixels, being blended by the texture sampling mode you choose, the rendering result won't be as good, but very slow animation will be overall better looking. Default is true: content will be aligned.
          * - isVisible: true if the sprite must be visible, false for hidden. Default is true.
@@ -341,6 +314,7 @@
             x                     ?: number,
             y                     ?: number,
             rotation              ?: number,
+            size                  ?: Size,
             scale                 ?: number,
             scaleX                ?: number,
             scaleY                ?: number,
@@ -351,6 +325,7 @@
             spriteSize            ?: Size,
             spriteLocation        ?: Vector2,
             spriteScaleFactor     ?: Vector2,
+            scale9                ?: Vector4,
             invertY               ?: boolean,
             alignToPixel          ?: boolean,
             isVisible             ?: boolean,
@@ -381,31 +356,37 @@
             this.texture = texture;
             this.texture.wrapU = Texture.CLAMP_ADDRESSMODE;
             this.texture.wrapV = Texture.CLAMP_ADDRESSMODE;
-            this.size = (settings.spriteSize!=null) ? settings.spriteSize.clone() : null;
+            this._useSize = false;
+            this.spriteSize = (settings.spriteSize!=null) ? settings.spriteSize.clone() : null;
             this.spriteLocation = (settings.spriteLocation!=null) ? settings.spriteLocation.clone() : new Vector2(0, 0);
-            this.spriteScaleFactor = (settings.spriteScaleFactor!=null) ? settings.spriteScaleFactor : new Vector2(1, 1);
+            if (settings.size != null) {
+                this.size = settings.size;
+            }
             this.spriteFrame = 0;
             this.invertY = (settings.invertY == null) ? false : settings.invertY;
             this.alignToPixel = (settings.alignToPixel == null) ? true : settings.alignToPixel;
             this.useAlphaFromTexture = true;
+            this._scale9 = (settings.scale9 != null) ? settings.scale9.clone() : null;
 
             // If the user doesn't set a size, we'll use the texture's one, but if the texture is not loading, we HAVE to set a temporary dummy size otherwise the positioning engine will switch the marginAlignement to stretch/stretch, and WE DON'T WANT THAT.
             // The fucking delayed texture sprite bug is fixed!
             if (settings.spriteSize == null) {
-                this.size = new Size(10, 10);
+                this.spriteSize = new Size(10, 10);
             }
 
             if (settings.spriteSize == null || !texture.isReady()) {
                 if (texture.isReady()) {
                     let s = texture.getBaseSize();
-                    this.size = new Size(s.width, s.height);
+                    this.spriteSize = new Size(s.width, s.height);
+                    this._updateSpriteScaleFactor();
                 } else {
 
                     texture.onLoadObservable.add(() => {
                         if (settings.spriteSize == null) {
                             let s = texture.getBaseSize();
-                        this.size = new Size(s.width, s.height);
+                            this.spriteSize = new Size(s.width, s.height);
                         }
+                        this._updateSpriteScaleFactor();
                         this._positioningDirty();
                         this._setLayoutDirty();
                         this._instanceDirtyFlags |= Prim2DBase.originProperty.flagId | Sprite2D.textureProperty.flagId;  // To make sure the sprite is issued again for render
@@ -453,6 +434,18 @@
             return renderCache;
         }
 
+        protected getUsedShaderCategories(dataPart: InstanceDataBase): string[] {
+            var cat = super.getUsedShaderCategories(dataPart);
+
+            if (dataPart.id === Sprite2D.SPRITE2D_MAINPARTID) {
+                let useScale9 = this._scale9 != null;
+                if (useScale9) {
+                    cat.push(Sprite2D.SHAPE2D_CATEGORY_SCALE9);
+                }
+            }
+            return cat;
+        }
+
         protected createInstanceDataParts(): InstanceDataBase[] {
             return [new Sprite2DInstanceData(Sprite2D.SPRITE2D_MAINPARTID)];
         }
@@ -487,11 +480,14 @@
                     d.properties = Vector3.Zero();
                     d.textureSize = Vector2.Zero();
                     d.scaleFactor = Vector2.Zero();
+                    if (this.isScale9) {
+                        d.scale9 = Vector4.Zero();
+                    }
                 } else {
                     let ts = this.texture.getBaseSize();
+                    let ss = this.spriteSize;
                     let sl = this.spriteLocation;
-                    let ss = this.actualSize;
-                    let ssf = this.spriteScaleFactor;
+                    let ssf = this.actualScale;
                     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;
@@ -503,6 +499,12 @@
                     d.properties = Sprite2D._prop;
 
                     d.textureSize = new Vector2(ts.width, ts.height);
+
+                    let scale9 = this._scale9;
+                    if (scale9 != null) {
+                        let normalizedScale9 = new Vector4(scale9.x * suv.x / ss.width, scale9.y * suv.y / ss.height, scale9.z * suv.x / ss.width, scale9.w * suv.y / ss.height);
+                        d.scale9 = normalizedScale9;
+                    }
                 }
             }
             return true;
@@ -525,13 +527,81 @@
             return this.texture!=null && this.texture.hasAlpha && this.useAlphaFromTexture;
         }
 
+        private _updateSpriteScaleFactor() {
+            if (!this._useSize) {
+                return;
+            }
+
+            let sS = this.spriteSize;
+            let s = this.size;
+            if (s == null || sS == null) {
+                return;
+            }
+            this.scaleX = s.width / sS.width;
+            this.scaleY = s.height / sS.height;
+        }
+
         private _texture: Texture;
         private _oldTextureHasAlpha: boolean;
         private _useAlphaFromTexture: boolean;
-        private _location: Vector2;
-        private _spriteScaleFactor: Vector2;
+        private _useSize: boolean;
+        private _spriteLocation: Vector2;
+        private _spriteSize: Size;
         private _spriteFrame: number;
+        private _scale9: Vector4;
         private _invertY: boolean;
         private _alignToPixel: boolean;
     }
+
+    export class Sprite2DInstanceData extends InstanceDataBase {
+        constructor(partId: number) {
+            super(partId, 1);
+        }
+
+        @instanceData()
+        get topLeftUV(): Vector2 {
+            return null;
+        }
+        set topLeftUV(value: Vector2) {
+        }
+
+        @instanceData()
+        get sizeUV(): Vector2 {
+            return null;
+        }
+        set sizeUV(value: Vector2) {
+        }
+
+        @instanceData(Sprite2D.SHAPE2D_CATEGORY_SCALE9)
+        get scaleFactor(): Vector2 {
+            return null;
+        }
+        set scaleFactor(value: Vector2) {
+        }
+
+        @instanceData()
+        get textureSize(): Vector2 {
+            return null;
+        }
+        set textureSize(value: Vector2) {
+        }
+
+        // 3 floats being:
+        // - x: frame number to display
+        // - y: invertY setting
+        // - z: alignToPixel setting
+        @instanceData()
+        get properties(): Vector3 {
+            return null;
+        }
+        set properties(value: Vector3) {
+        }
+
+        @instanceData(Sprite2D.SHAPE2D_CATEGORY_SCALE9)
+        get scale9(): Vector4 {
+            return null;
+        }
+        set scale9(value: Vector4) {
+        }
+    }
 }

+ 51 - 1
canvas2D/src/shaders/sprite2d.fragment.fx

@@ -1,11 +1,61 @@
 varying vec2 vUV;
 varying float vOpacity;
+
+#ifdef Scale9
+varying vec2 vTopLeftUV;
+varying vec2 vBottomRightUV;
+varying vec4 vScale9;
+varying vec2 vScaleFactor;
+#endif
+
 uniform bool alphaTest;
 uniform sampler2D diffuseSampler;
 
 void main(void) {
-	vec4 color = texture2D(diffuseSampler, vUV);
+	
+	vec2 uv = vUV;
+
+#ifdef Scale9
+
+	vec2 sizeUV = vBottomRightUV - vTopLeftUV;
+
+	// Compute Horizontal (U) Coordinate
+	float leftPartUV = vTopLeftUV.x + (vScale9.x / vScaleFactor.x);
+	float rightPartUV = vTopLeftUV.x + sizeUV.x - ((sizeUV.x - vScale9.z) / vScaleFactor.x);
+
+	if (vUV.x < leftPartUV) {
+		uv.x = vTopLeftUV.x + ((vUV.x- vTopLeftUV.x) * vScaleFactor.x);
+	}
+
+	else if (vUV.x > rightPartUV) {
+		uv.x = vTopLeftUV.x + vScale9.z + ((vUV.x - rightPartUV) * vScaleFactor.x);
+	}
+
+	else {
+		float r = (vUV.x - leftPartUV) / (rightPartUV - leftPartUV);
+		uv.x = vTopLeftUV.x + vScale9.x + ((vScale9.z-vScale9.x) * r);
+	}
+
+	// Compute Vertical (V) Coordinate
+	float topPartUV = (vTopLeftUV.y + (vScale9.y / vScaleFactor.y));
+	float bottomPartUV = (vTopLeftUV.y + sizeUV.y - ((sizeUV.y - vScale9.w) / vScaleFactor.y));
+
+	if (vUV.y < topPartUV) {
+		uv.y = vTopLeftUV.y + ((vUV.y - vTopLeftUV.y) * vScaleFactor.y);
+	}
+
+	else if (vUV.y > bottomPartUV) {
+		uv.y = vTopLeftUV.y + vScale9.w + ((vUV.y - bottomPartUV) * vScaleFactor.y);
+	}
+
+	else {
+		float r = (vUV.y - topPartUV) / (bottomPartUV - topPartUV);
+		uv.y = vTopLeftUV.y + vScale9.y + ((vScale9.w - vScale9.y) * r);
+	}
+
+#endif
 
+	vec4 color = texture2D(diffuseSampler, uv);
 	if (alphaTest)
 	{
 		if (color.a < 0.95) {

+ 31 - 8
canvas2D/src/shaders/sprite2d.vertex.fx

@@ -10,12 +10,18 @@ attribute float index;
 
 att vec2 topLeftUV;
 att vec2 sizeUV;
+#ifdef Scale9
 att vec2 scaleFactor;
+#endif
 att vec2 textureSize;
 
 // x: frame, y: invertY, z: alignToPixel
 att vec3 properties;
 
+#ifdef Scale9
+att vec4 scale9;
+#endif
+
 att vec2 zBias;
 att vec4 transformX;
 att vec4 transformY;
@@ -27,14 +33,17 @@ att float opacity;
 varying vec2 vUV;
 varying float vOpacity;
 
+#ifdef Scale9
+varying vec2 vTopLeftUV;
+varying vec2 vBottomRightUV;
+varying vec4 vScale9;
+varying vec2 vScaleFactor;
+#endif
+
 void main(void) {
 
 	vec2 pos2;
 
-	//vec2 off = vec2(1.0 / textureSize.x, 1.0 / textureSize.y);
-	vec2 off = vec2(0.0, 0.0);
-	vec2 sfSizeUV = sizeUV * scaleFactor;
-
 	float frame = properties.x;
 	float invertY = properties.y;
 	float alignToPixel = properties.z;
@@ -42,25 +51,25 @@ void main(void) {
 	// Left/Top
 	if (index == 0.0) {
 		pos2 = vec2(0.0, 0.0);
-		vUV = vec2(topLeftUV.x + (frame*sfSizeUV.x) + off.x, topLeftUV.y - off.y);
+		vUV = vec2(topLeftUV.x + (frame*sizeUV.x), topLeftUV.y);
 	}
 
 	// Left/Bottom
 	else if (index == 1.0) {
 		pos2 = vec2(0.0,  1.0);
-		vUV = vec2(topLeftUV.x + (frame*sfSizeUV.x) + off.x, (topLeftUV.y + sfSizeUV.y));
+		vUV = vec2(topLeftUV.x + (frame*sizeUV.x), (topLeftUV.y + sizeUV.y));
 	}
 
 	// Right/Bottom
 	else if (index == 2.0) {
 		pos2 = vec2( 1.0,  1.0);
-		vUV = vec2(topLeftUV.x + sfSizeUV.x + (frame*sfSizeUV.x), (topLeftUV.y + sfSizeUV.y));
+		vUV = vec2(topLeftUV.x + sizeUV.x + (frame*sizeUV.x), (topLeftUV.y + sizeUV.y));
 	}
 
 	// Right/Top
 	else if (index == 3.0) {
 		pos2 = vec2( 1.0, 0.0);
-		vUV = vec2(topLeftUV.x + sfSizeUV.x + (frame*sfSizeUV.x), topLeftUV.y - off.y);
+		vUV = vec2(topLeftUV.x + sizeUV.x + (frame*sizeUV.x), topLeftUV.y);
 	}
 
 	if (invertY == 1.0) {
@@ -75,6 +84,20 @@ void main(void) {
 		pos.xy = pos2.xy * sizeUV * textureSize;
 	}
 
+#ifdef Scale9
+	if (invertY == 1.0) {
+		vTopLeftUV = vec2(topLeftUV.x, 1.0 - (topLeftUV.y + sizeUV.y));
+		vBottomRightUV = vec2(topLeftUV.x + sizeUV.x, 1.0 - topLeftUV.y);
+		vScale9 = vec4(scale9.x, sizeUV.y - scale9.w, scale9.z, sizeUV.y - scale9.y);
+	}
+	else {
+		vTopLeftUV = topLeftUV;
+		vBottomRightUV = vec2(topLeftUV.x, topLeftUV.y + sizeUV.y);
+		vScale9 = scale9;
+	}
+	vScaleFactor = scaleFactor;
+#endif
+
 	vOpacity = opacity;
 	pos.z = 1.0;
 	pos.w = 1.0;

+ 185 - 20
dist/preview release/canvas2D/babylon.canvas2d.d.ts

@@ -1674,6 +1674,8 @@ declare module BABYLON {
          * Use this property to set a new Size object, otherwise to change only the width/height use Prim2DBase.width or height properties.
          */
         size: Size;
+        protected internalGetSize(): Size;
+        protected internalSetSize(value: Size): void;
         /**
          * Direct access to the size.width value of the primitive
          * Use this property when you only want to change one component of the size property
@@ -2728,36 +2730,26 @@ declare module BABYLON {
         render(instanceInfo: GroupInstanceInfo, context: Render2DContext): boolean;
         dispose(): boolean;
     }
-    class Sprite2DInstanceData extends InstanceDataBase {
-        constructor(partId: number);
-        topLeftUV: Vector2;
-        sizeUV: Vector2;
-        scaleFactor: Vector2;
-        textureSize: Vector2;
-        properties: Vector3;
-    }
     class Sprite2D extends RenderablePrim2D {
         static SPRITE2D_MAINPARTID: number;
+        static SHAPE2D_CATEGORY_SCALE9: string;
         static textureProperty: Prim2DPropInfo;
         static useAlphaFromTextureProperty: Prim2DPropInfo;
         static actualSizeProperty: Prim2DPropInfo;
+        static spriteSizeProperty: Prim2DPropInfo;
         static spriteLocationProperty: Prim2DPropInfo;
         static spriteFrameProperty: Prim2DPropInfo;
         static invertYProperty: Prim2DPropInfo;
-        static spriteScaleFactorProperty: Prim2DPropInfo;
+        static spriteScale9Property: Prim2DPropInfo;
         texture: Texture;
         useAlphaFromTexture: boolean;
+        size: Size;
         actualSize: Size;
+        spriteSize: Size;
         spriteLocation: Vector2;
         spriteFrame: number;
         invertY: boolean;
-        spriteScaleFactor: Vector2;
-        /**
-         * Sets the scale of the sprite using a BABYLON.Size(w,h).
-         * Keeps proportion by taking the maximum of the two scale for x and y.
-         * @param {Size} size Size(width,height)
-         */
-        scaleToSize(size: Size): void;
+        readonly isScale9: boolean;
         /**
          * Get/set if the sprite rendering should be aligned to the target rendering device pixel or not
          */
@@ -2768,6 +2760,7 @@ declare module BABYLON {
          */
         getAnimatables(): IAnimatable[];
         protected levelIntersect(intersectInfo: IntersectInfo2D): boolean;
+        readonly isSizeAuto: boolean;
         /**
          * Create an 2D Sprite primitive
          * @param texture the texture that stores the sprite to render
@@ -2778,13 +2771,15 @@ declare module BABYLON {
          * - 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
+         * - size: the size of the sprite displayed in the canvas, if not specified the spriteSize will be used
          * - 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]
-         * - spriteSize: the size of the sprite (in pixels), if null the size of the given texture will be used, default is null.
+         * - spriteSize: the size of the sprite (in pixels) as it is stored in the texture, if null the size of the given texture will be used, default is null.
          * - spriteLocation: the location (in pixels) in the texture of the top/left corner of the Sprite to display, default is null (0,0)
-         * - spriteScaleFactor: say you want to display a sprite twice as big as its bitmap which is 64,64, you set the spriteSize to 128,128 and have to set the spriteScaleFactory to 0.5,0.5 in order to address only the 64,64 pixels of the bitmaps. Default is 1,1.
+         * - spriteScaleFactor: DEPRECATED. Old behavior: say you want to display a sprite twice as big as its bitmap which is 64,64, you set the spriteSize to 128,128 and have to set the spriteScaleFactory to 0.5,0.5 in order to address only the 64,64 pixels of the bitmaps. Default is 1,1.
+         * - scale9: draw the sprite as a Scale9 sprite, see http://yannickloriot.com/2013/03/9-patch-technique-in-cocos2d/ for more info. x, y, w, z are left, bottom, right, top coordinate of the resizable box
          * - invertY: if true the texture Y will be inverted, default is false.
          * - alignToPixel: if true the sprite's texels will be aligned to the rendering viewport pixels, ensuring the best rendering quality but slow animations won't be done as smooth as if you set false. If false a texel could lies between two pixels, being blended by the texture sampling mode you choose, the rendering result won't be as good, but very slow animation will be overall better looking. Default is true: content will be aligned.
          * - isVisible: true if the sprite must be visible, false for hidden. Default is true.
@@ -2813,6 +2808,7 @@ declare module BABYLON {
             x?: number;
             y?: number;
             rotation?: number;
+            size?: Size;
             scale?: number;
             scaleX?: number;
             scaleY?: number;
@@ -2823,6 +2819,7 @@ declare module BABYLON {
             spriteSize?: Size;
             spriteLocation?: Vector2;
             spriteScaleFactor?: Vector2;
+            scale9?: Vector4;
             invertY?: boolean;
             alignToPixel?: boolean;
             isVisible?: boolean;
@@ -2845,6 +2842,7 @@ declare module BABYLON {
         });
         protected createModelRenderCache(modelKey: string): ModelRenderCache;
         protected setupModelRenderCache(modelRenderCache: ModelRenderCache): Sprite2DRenderCache;
+        protected getUsedShaderCategories(dataPart: InstanceDataBase): string[];
         protected createInstanceDataParts(): InstanceDataBase[];
         private static _prop;
         private static layoutConstructMode;
@@ -2854,15 +2852,182 @@ declare module BABYLON {
         protected _mustUpdateInstance(): boolean;
         protected _useTextureAlpha(): boolean;
         protected _shouldUseAlphaFromTexture(): boolean;
+        private _updateSpriteScaleFactor();
         private _texture;
         private _oldTextureHasAlpha;
         private _useAlphaFromTexture;
-        private _location;
-        private _spriteScaleFactor;
+        private _useSize;
+        private _spriteLocation;
+        private _spriteSize;
         private _spriteFrame;
+        private _scale9;
         private _invertY;
         private _alignToPixel;
     }
+    class Sprite2DInstanceData extends InstanceDataBase {
+        constructor(partId: number);
+        topLeftUV: Vector2;
+        sizeUV: Vector2;
+        scaleFactor: Vector2;
+        textureSize: Vector2;
+        properties: Vector3;
+        scale9: Vector4;
+    }
+}
+
+declare module BABYLON {
+    /**
+     * Interface to create your own Loader of Atlas Data file.
+     * Call the AtlasPictureInfoFactory.addLoader to addd your loader instance
+     */
+    interface IAtlasLoader {
+        loadFile(content: any): {
+            api: AtlasPictureInfo;
+            errorMsg: string;
+            errorCode: number;
+        };
+    }
+    /**
+     * This class will contains information about a sub picture present in an Atlas Picture.
+     */
+    class AtlasSubPictureInfo {
+        /**
+         * Name of the SubPicture, generally the filename of the initial picture file.
+         */
+        name: string;
+        /**
+         * Location of the bottom/left corner of the sub picture from the bottom/left corner the Atlas Picture
+         */
+        location: Vector2;
+        /**
+         * Size in pixel of the sub picture
+         */
+        size: Size;
+    }
+    /**
+     * This class represent an Atlas Picture, it contains the information of all the sub pictures and the Texture that stores the bitmap.
+     * You get an instance of this class using methods of the AtlasPictureInfoFactory
+     */
+    class AtlasPictureInfo {
+        /**
+         * Creates many sprite from the Atlas Picture
+         * @param filterCallback a predicate if true is returned then the corresponding sub picture will be used to create a sprite.
+         * The Predicate has many parameters:
+         *  - index: just an index incremented at each sub picture submitted for Sprite creation
+         *  - name: the sub picture's name
+         *  - aspi: the AtlasSubPictureInfo corresponding to the submitted sub picture
+         *  - settings: the Sprite2D creation settings, you can alter this JSON object but BEWARE, the alterations will be kept for subsequent Sprite2D creations!
+         * @param spriteSettings The Sprite2D settings to use for Sprite creation, this JSON object will be passed to the filterCallback for you to alter it, if needed.
+         */
+        createSprites(filterCallback: (index: number, name: string, aspi: AtlasSubPictureInfo, settings: any) => boolean, spriteSettings: {
+            parent?: Prim2DBase;
+            position?: Vector2;
+            x?: number;
+            y?: number;
+            rotation?: number;
+            size?: Size;
+            scale?: number;
+            scaleX?: number;
+            scaleY?: number;
+            dontInheritParentScale?: boolean;
+            opacity?: number;
+            zOrder?: number;
+            origin?: Vector2;
+            scale9?: Vector4;
+            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;
+        }): Array<Sprite2D>;
+        /**
+         * Create one Sprite from a sub picture
+         * @param subPictureName the name of the sub picture to use
+         * @param spriteSettings the Sprite2D settings to use for the Sprite instance creation
+         */
+        createSprite(subPictureName: string, spriteSettings: {
+            parent?: Prim2DBase;
+            position?: Vector2;
+            x?: number;
+            y?: number;
+            rotation?: number;
+            size?: Size;
+            scale?: number;
+            scaleX?: number;
+            scaleY?: number;
+            dontInheritParentScale?: boolean;
+            opacity?: number;
+            zOrder?: number;
+            origin?: Vector2;
+            scale9?: Vector4;
+            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;
+        }): Sprite2D;
+        /**
+         * Size of the Atlas Picture
+         */
+        atlasSize: Size;
+        /**
+         * String Dictionary of all the sub pictures, the key is the sub picture's name, the value is the info object
+         */
+        subPictures: StringDictionary<AtlasSubPictureInfo>;
+        /**
+         * The Texture associated to the Atlas Picture info
+         */
+        texture: Texture;
+    }
+    /**
+     * This if the Factory class containing static method to create Atlas Pictures Info objects or add new loaders
+     */
+    class AtlasPictureInfoFactory {
+        /**
+         * Add a custom loader
+         * @param fileExtension must be the file extension (without the dot) of the file that is loaded by this loader (e.g.: json)
+         * @param plugin the instance of the loader
+         */
+        static addLoader(fileExtension: string, plugin: IAtlasLoader): void;
+        /**
+         * Load an Atlas Picture Info object from a data file at a given url and with a given texture
+         * @param texture the texture containing the atlas bitmap
+         * @param url the URL of the Atlas Info data file
+         * @param loaded a callback that will be called when the AtlasPictureInfo object will be loaded and ready
+         * @param error a callback that will be called in case of error
+         */
+        static loadFromUrl(texture: Texture, url: string, loaded: (api: AtlasPictureInfo) => void, error?: (msg: string, code: number) => void): any;
+        private static _initialize();
+        private static plugins;
+    }
 }
 
 declare module BABYLON {

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 408 - 141
dist/preview release/canvas2D/babylon.canvas2d.js


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 10 - 10
dist/preview release/canvas2D/babylon.canvas2d.min.js