123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- 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 TexturePacker
- */
- export interface ITexturePackerOptions{
- /**
- * Custom targets for the channels of a texture packer. Default is all the channels of the Standard Material
- */
- map?: string[];
- /**
- * 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[];
- /**
- * 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[];
- /**
- * number representing the layout style. Defaults to LAYOUT_STRIP
- */
- layout?: number;
- /**
- * number of columns if using custom column count layout(2). This defaults to 4.
- */
- colcount?: number;
- /**
- * flag to update the input meshes to the new packed texture after compilation. Defaults to true.
- */
- updateInputMeshes?: boolean;
- /**
- * boolean flag to dispose all the source textures. Defaults to true.
- */
- 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 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 */
- public static readonly LAYOUT_POWER2 = Constants.LAYOUT_POWER2;
- /** 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: 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.
- * @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 TexturePacker
- */
- 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.
- */
- this.options = options;
- this.options.map = this.options.map || [
- 'ambientTexture',
- 'bumpTexture',
- 'diffuseTexture',
- 'emissiveTexture',
- 'lightmapTexture',
- 'opacityTexture',
- '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 || 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.disposeSources = this.options.disposeSources || true;
- this.options.fillBlanks = 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]
- }
- }
- }
-
- }
|