Просмотр исходного кода

Texture Packer

Migration of all methods from prototype.
Added access to more arugments.
Created new Frame class for the simple frame object data.  - Could prolly get rid of this...
Pryme8 5 лет назад
Родитель
Сommit
c6e8ad7cbb

+ 1 - 1
src/Engines/constants.ts

@@ -245,7 +245,7 @@ export class Constants {
     public static readonly SCALEMODE_NEAREST = 2;
     /** Defines that texture rescaling will use a ceil to find the closer power of 2 size */
     public static readonly SCALEMODE_CEILING = 3;
-    
+
     // Texture Packer Layouts
     /** Defines that texture packer will use a strip mode layout. */
     public static readonly LAYOUT_STRIP = 0;

+ 63 - 0
src/Materials/Textures/Packer/frame.ts

@@ -0,0 +1,63 @@
+//import { Engine } from "../../../Engines/engine";
+//import { Constants } from "../../../Engines/constants";
+//import { AbstractMesh } from "../../../Meshes/abstractMesh";
+//import { VertexBuffer } from "../../../Meshes/buffer";
+//import { Scene } from "../../../scene";
+//import { Material } from "../../material";
+//import { Texture } from "../texture";
+//import { DynamicTexture } from "../dynamicTexture";
+//import { Nullable } from "../../../types";
+import { Vector2 } from "../../../Maths/math.vector";
+
+/**
+ * Defines the basic options interface of a TexturePacker Frame
+ */
+export interface ITexturePackerFrame{
+
+    /**
+	 * The frame ID
+	 */
+    id: number;
+
+    /**
+	 * The frames Scale
+	 */
+    scale: Vector2;
+
+    /**
+	 * The Frames offset
+	 */
+    offset: Vector2;    
+
+}
+
+/**
+ * This is a support class for frame Data on texture packer sets.
+ * @see #TODO ADD THIS
+ */
+export class TexturePackerFrame implements ITexturePackerFrame{
+     /**
+	 * The frame ID
+	 */
+    public id: number;
+
+    /**
+	 * The frames Scale
+	 */
+    public scale: Vector2;
+
+    /**
+	 * The Frames offset
+	 */
+    public offset: Vector2;
+    
+    /**
+    * Initializes a texture package frame.
+    * @returns TexturePackerFrame
+    */
+    constructor(id: number, scale: Vector2, offset: Vector2) {
+        this.id = id;
+        this.scale = scale;
+        this.offset = offset;
+    }    
+}

+ 2 - 1
src/Materials/Textures/Packer/index.ts

@@ -1 +1,2 @@
-export * from "./packer";
+export * from "./packer";
+export * from "./frame";

+ 461 - 26
src/Materials/Textures/Packer/packer.ts

@@ -1,13 +1,19 @@
-import { BaseTexture } from "../../Materials/Textures/baseTexture";
+import { Engine } from "../../../Engines/engine";
 import { Constants } from "../../../Engines/constants";
 import { AbstractMesh } from "../../../Meshes/abstractMesh";
 import { VertexBuffer } from "../../../Meshes/buffer";
 import { Scene } from "../../../scene";
+import { Material } from "../../material";
+import { Texture } from "../texture";
+import { DynamicTexture } from "../dynamicTexture";
+import { Nullable } from "../../../types";
+import { Vector2 } from "../../../Maths/math.vector";
+import { TexturePackerFrame } from "./frame";
 
 /**
- * Defines the basic options interface of a SpriteMap
+ * Defines the basic options interface of a TexturePacker
  */
-export interface IPackerOptions{
+export interface ITexturePackerOptions{
 
     /**
 	 * Custom targets for the channels of a texture packer.  Default is all the channels of the Standard Material
@@ -17,12 +23,12 @@ export interface IPackerOptions{
     /**
 	 * the UV input targets, as a single value for all meshes or an array of values that matches the mesh count.  Defaults to VertexBuffer.UVKind
 	 */
-    uvsIn?: string | string[];
+    uvsIn?: string;// | string[];
 
     /**
 	 * the UV output targets, as a single value for all meshes or an array of values that matches the mesh count.  Defaults to VertexBuffer.UVKind
 	 */
-    uvsOut?: string | string[];
+    uvsOut?: string;// | string[];
 
     /**
 	 * number representing the layout style. Defaults to LAYOUT_STRIP
@@ -42,26 +48,37 @@ export interface IPackerOptions{
     /**
 	* boolean flag to dispose all the source textures.  Defaults to true.
 	*/
-    disposeSource?: boolean;
+    disposeSources?: boolean;
 
     /**
 	 * Fills the blank cells in a set to the customFillColor.  Defaults to true.
 	 */
     fillBlanks?: boolean;
-    
+
     /**
 	 * string value representing the context fill style color.  Defaults to 'black'.
 	 */
     customFillColor?: string;
+    
+    /**
+	 * Width and Height Value of each Frame in the TexturePacker Sets
+	 */
+    frameSize?: number;
+    
+    /**
+	 * Ratio of the value to add padding wise to each cell.  Defaults to 0.0115
+	 */
+    paddingRatio?: number;
+    
 
 }
 
 /**
  * This is a support class that generates a series of packed texture sets.
  * @see #TODO ADD THIS
- */ 
-export class Packer{ 
-   
+ */
+export class TexturePacker{
+
     /** mag = nearest and min = nearest and mip = nearest */
     public static readonly LAYOUT_STRIP = Constants.LAYOUT_STRIP;
     /** mag = nearest and min = linear and mip = nearest */
@@ -69,19 +86,47 @@ export class Packer{
     /** mag = nearest and min = linear and mip = linear */
     public static readonly LAYOUT_COLNUM = Constants.LAYOUT_COLNUM;
 
+    /** The Name of the Texture Package */
+    public name: string;
+   
+   /** The scene scope of the TexturePacker */
+    public scene: Scene;
+
+    /** The Meshes to target */
+    public meshes: AbstractMesh[];
+
     /** Arguments passed with the Constructor */
-    public options: IPackerOptions;
+    public options: ITexturePackerOptions;
 
+    /** The promise that is started upon initialization */
+    public promise: Promise< TexturePacker | string >;
+    
+    /** The Container object for the channel sets that are generated */
+    public sets: object;
+    
+    /** The Container array for the frames that are generated */
+    public frames: TexturePackerFrame[];
+    
+    /** The List of textures to purge from memory after compilation */
+    private _disposeList: Texture[];
+    
+    /** The padding value from Math.ceil(frameSize * paddingRatio) */
+    private _paddingValue: number;
 
     /**
-    * Initializes a texture package series from an array of meshes or a single mesh. 
+    * Initializes a texture package series from an array of meshes or a single mesh.
     * @param name The name of the package
     * @param meshes The target meshes to compose the package from
     * @param options The arguments that texture packer should follow while building.
     * @param scene The scene which the textures are scoped to.
-    * @returns Packer
+    * @returns TexturePacker
     */
-    constructor(name: string, meshes: AbstractMesh | AbstractMesh[], options: IPackerOptions, scene: Scene ){
+    constructor(name: string, meshes: AbstractMesh[], options: ITexturePackerOptions, scene: Scene) {
+
+        this.name = name;
+        this.meshes = meshes;
+        this.scene = scene;
+
         /**
         * Run through the options and set what ever defaults are needed that where not declared.
         */
@@ -96,24 +141,414 @@ export class Packer{
                 'reflectionTexture',
                 'refractionTexture',
                 'specularTexture'
-            ];            
-      
+            ];
+        
         this.options.uvsIn = this.options.uvsIn || VertexBuffer.UVKind;
         this.options.uvsOut = this.options.uvsOut || VertexBuffer.UVKind;
-        this.options.layout = this.options.layout || Packer.LAYOUT_STRIP;
-        
-        if(this.options.layout === Packer.LAYOUT_COLNUM){
+        this.options.layout = this.options.layout || TexturePacker.LAYOUT_STRIP;
+
+        if (this.options.layout === TexturePacker.LAYOUT_COLNUM) {
             this.options.colcount = this.options.colcount || 4;
         }
-        
+
         this.options.updateInputMeshes = this.options.updateInputMeshes || true;
-        this.options.disposeSource = this.options.disposeSource || true;
+        this.options.disposeSources = this.options.disposeSources || true;
         this.options.fillBlanks = this.options.fillBlanks || true;
-        
-        if(this.options.fillBlanks === true){
+
+        if (this.options.fillBlanks === true) {
             this.options.customFillColor = this.options.customFillColor || 'black';
-        }        
+        }
+        
+        this.options.frameSize = this.options.frameSize || 256;
+        this.options.paddingRatio = this.options.paddingRatio || 0.0115;
+        
+        this._paddingValue = Math.ceil(this.options.frameSize * this.options.paddingRatio);
+        
+        //Make it an even padding Number.
+        if (this._paddingValue % 2 !== 0 ) {
+            this._paddingValue++;
+        }
+        
+        /**
+        * Create the promise and then run through the materials on the meshes.
+        */
+        this.promise = new Promise ((resolve, reject) => {
+            console.log("Promise Start");
+            try{
+                let done = 0;
+                const doneCheck = ( mat: Material )=>{
+                    done++;
+                    //Check Status of all Textures on all meshes, till they are ready.
+                    if(this.options.map){
+                        for(let j = 0; j < this.options.map.length; j++){
+                            let index: string = this.options.map[j];
+                            let t: Texture = (mat as any)[index];                                
+                            if(t !== null){
+                                
+                                if(!(this.sets as any)[this.options.map[j]]){
+                                    (this.sets as any)[this.options.map[j]] = true;
+                                }
+
+                                if(this.options.disposeSources){
+                                    this._disposeList.push(t);
+                                }
+                            }                            
+                        }
+
+                        if(done == this.meshes.length){
+                            this._createFrames(resolve);
+                        }
+                    }
+                }
+
+                for(let i = 0; i < this.meshes.length; i++){
+                    let mesh = this.meshes[i];                   
+                    let material: Nullable<Material> | Material = mesh.material;
+                    if(!material){
+                        return new Error('Mesh has no Material Assigned!');
+                    }
+                    
+                    material.forceCompilationAsync(mesh).then(() => {
+                        doneCheck((material as Material));
+                    })
+                    
+                }
+            }catch(e){
+                return reject(e);
+            }    
+        })
+    
+    return
+    }
+    
+    
+    /**
+    * Starts the package process
+    * @param resolve The promises resolution function
+    * @returns TexturePacker
+    */    
+    private _createFrames(resolve: Function){
+        
+        let dtSize = this._calculateSize();
+        let dtUnits = (new Vector2(1,1)).divide(dtSize);
+        let doneCount = 0;
+        let expecting = this._disposeList.length;
+        let meshLength = this.meshes.length;
+
+        let sKeys = Object.keys(this.sets);
+        for(let i = 0; i < sKeys.length; i++){
+            let setName = sKeys[i];
+            
+            let dt = new DynamicTexture(this.name+'.TexturePack.'+setName+'Set', 
+                    {width:dtSize.x, height:dtSize.y},
+                    this.scene,
+                    true, //Generate Mips
+                    Texture.TRILINEAR_SAMPLINGMODE,
+                    Engine.TEXTUREFORMAT_RGBA
+                );
+                
+            let dtx = dt.getContext();
+            dtx.fillStyle = 'rgba(0,0,0,0)';
+            dtx.fillRect(0,0, dtSize.x, dtSize.y) ;
+            dt.update();
+            (this.sets as any)[setName] = dt;           
+        }
+        
+        let baseSize = this.options.frameSize || 256;
+        let padding = this._paddingValue;
+        let tcs = baseSize + (2 * padding);
+        
+        const done = ()=>{
+            console.log("Compilation Done")
+            this._calculateMeshUVFrames(baseSize, padding, dtSize, dtUnits, this.options.updateInputMeshes || false)
+        }       
+
+        //Update the Textures
+        for(let i = 0; i < meshLength; i++){
+            let m = this.meshes[i]
+            let mat = m.material
+
+            //Check if the material has the texture
+            //Create a temporary canvas the same size as 1 frame
+            //Then apply the texture to the center and the 8 offsets
+            //Copy the Context and place in the correct frame on the DT
+
+            for(let j = 0; j < sKeys.length; j++){                
+                let tempTexture = new DynamicTexture('temp', tcs, this.scene, true)
+                let tcx = tempTexture.getContext()
+                
+                //tempTexture.update(false)
+                
+                let offset = this._getFrameOffset(i)
+
+                const updateDt = ()=>{
+                    doneCount++
+                    console.log("Done/To:", doneCount, expecting)                                      
+                    let iDat = tcx.getImageData(0, 0, tcs, tcs)
+                    //Update Set
+                    let dt = (this.sets as any)[setName]
+                    let dtx = dt.getContext()
+                    dtx.putImageData(iDat, dtSize.x * offset.x, dtSize.y * offset.y)
+                    tempTexture.dispose()
+                    dt.update(false)           
+
+                        if(doneCount == expecting){                            
+                            console.log('Done Making Frames')                            
+                            done()
+                            resolve(this)
+                        }
+                }
+                
+                let setName = sKeys[j] || '_blank';
+                if((mat as any)[setName] === null){
+                    console.log("Blank Frame:", i, setName);
+                    tcx.fillStyle = 'rgba(0,0,0,0)';
+                    
+                    if(this.options.fillBlanks){
+                        tcx.fillStyle = (this.options.customFillColor as string);
+                    }
+                    
+                    tcx.fillRect(0,0, tcs, tcs);
+                    tempTexture.update(false);
+                    updateDt();
+                    
+                }else{                
+                    console.log("Generating Frame:", i, setName, offset)                                     
+                    let img = new Image();
+                    img.src = (mat as any)[setName]!.url;
+                    img.onload = ()=>{
+                        tcx.fillStyle = 'rgba(0,0,0,0)'
+                        tcx.fillRect(0,0, tcs, tcs)
+                        tempTexture.update(false)
+                        console.log("Image Loaded")
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding,
+                            padding,
+                            baseSize,
+                            baseSize
+                        )
+                        
+                        //Right
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding+baseSize,
+                            padding,
+                            baseSize,
+                            baseSize
+                        )
+                        //RightBottom
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding+baseSize,
+                            padding+baseSize,
+                            baseSize,
+                            baseSize
+                        )
+                        //Bottom
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding,
+                            padding+baseSize,
+                            baseSize,
+                            baseSize
+                        )
+                        //BottomLeft
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding-baseSize,
+                            padding+baseSize,
+                            baseSize,
+                            baseSize
+                        )
+                        //Left
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding-baseSize,
+                            padding,
+                            baseSize,
+                            baseSize
+                        )
+                        //LeftTop
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding-baseSize,
+                            padding-baseSize,
+                            baseSize,
+                            baseSize
+                        )
+                        //Top
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding,
+                            padding-baseSize,
+                            baseSize,
+                            baseSize
+                        )
+                        //TopRight
+                        tcx.drawImage(
+                            img,
+                            0,
+                            0,
+                            img.width,
+                            img.height,
+                            padding+baseSize,
+                            padding-baseSize,
+                            baseSize,
+                            baseSize
+                        )
+
+                        updateDt()
+                        
+                    }   
+                    
+                }
+            }
+        }       
+    }
+    
+    /**
+    * Calculates the Size of the Channel Sets
+    * @returns Vector2
+    */  
+    private _calculateSize(): Vector2{
+        
+        let meshLength: number = this.meshes.length || 0;
+        let baseSize: number =  this.options.frameSize || 0;
+        let padding: number = this._paddingValue || 0;
+        
+        switch(this.options.layout){
+            case 0 : 
+                //STRIP_LAYOUT
+                return new Vector2(
+                    ( baseSize * meshLength ) + ( 2 * padding * meshLength ),
+                    ( baseSize ) + (2 * padding )
+                );
+            break;
+        }
         
-              
+        return Vector2.Zero();
+    }
+    
+    
+    /**
+    * Calculates the UV data for the frames.
+    * @param baseSize the base frameSize
+    * @param padding the base frame padding
+    * @param dtSize size of the Dynamic Texture for that channel
+    * @param dtUnits is 1/dtSize
+    * @param update flag to update the input meshes
+    * @returns Void
+    */  
+    private _calculateMeshUVFrames(baseSize: number, padding: number, dtSize: Vector2, dtUnits: Vector2, update: boolean){        
+        let meshLength = this.meshes.length;
+        
+        for(let i = 0; i < meshLength; i++){
+            let m = this.meshes[i];
+
+            let scale = new Vector2(
+                baseSize / dtSize.x,
+                baseSize / dtSize.y,
+            );
+            
+            let pOffset: Vector2 = dtUnits.clone().scale(padding);
+            let frameOffset: Vector2 = this._getFrameOffset(i);
+            let offset: Vector2 = frameOffset.add(pOffset);           
+            
+            let frame: TexturePackerFrame = new TexturePackerFrame( i, scale, offset );
+             
+            this.frames.push(
+                frame
+            );
+            
+            //Update Output UVs
+            if(update){
+                this._updateMeshUV(m, i);
+                this._updateTextureRefrences(m);
+            }            
+        }
+    }
+    
+    private _getFrameOffset(index: number): Vector2{
+        let meshLength = this.meshes.length
+        switch(this.options.layout){
+            case 0 : 
+                //STRIP_LAYOUT
+                let xStep = 1/meshLength
+                return new Vector2(
+                    index * xStep,
+                    0
+                )
+            break;
+        }
+        
+        return Vector2.Zero();
+    }
+        
+    private _updateMeshUV(mesh: AbstractMesh, frameID: number): void{
+        let frame: TexturePackerFrame = (this.frames as any)[frameID];
+        let uvIn = mesh.getVerticesData(this.options.uvsIn || VertexBuffer.UVKind)
+        let uvOut = []
+        let toCount = 0;
+        
+        if(uvIn!.length){
+            toCount = uvIn!.length || 0;
+        }
+        
+        for(let i = 0; i < toCount; i+=2){            
+            uvOut.push(
+                ( (uvIn as any)[i] * frame.scale.x ) + frame.offset.x,
+                ( (uvIn as any)[i + 1] * frame.scale.y ) + frame.offset.y
+            )
+        }
+        
+        mesh.setVerticesData(this.options.uvsOut || VertexBuffer.UVKind, uvOut)
     }    
-}
+    
+    private _updateTextureRefrences(m: AbstractMesh): void{
+        let mat = m.material
+        let sKeys = Object.keys(this.sets)
+        for(let i = 0; i < sKeys.length; i++){
+            let setName = sKeys[i]
+            if((mat as any)[setName] !== null){
+                if((mat as any)[setName].dispose){
+                    (mat as any)[setName].dispose()
+                }               
+                (mat as any)[setName] = (this.sets as any)[setName]
+            }
+        }        
+    }    
+    
+}

+ 1 - 0
src/Materials/Textures/index.ts

@@ -20,3 +20,4 @@ export * from "./renderTargetTexture";
 export * from "./texture";
 export * from "./videoTexture";
 export * from "./htmlElementTexture";
+export * from "./Packer/index";