packer.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. import { Engine } from "../../../Engines/engine";
  2. import { Constants } from "../../../Engines/constants";
  3. import { AbstractMesh } from "../../../Meshes/abstractMesh";
  4. import { VertexBuffer } from "../../../Meshes/buffer";
  5. import { Scene } from "../../../scene";
  6. import { Material } from "../../material";
  7. import { Texture } from "../texture";
  8. import { DynamicTexture } from "../dynamicTexture";
  9. import { Nullable } from "../../../types";
  10. import { Vector2 } from "../../../Maths/math.vector";
  11. import { TexturePackerFrame } from "./frame";
  12. /**
  13. * Defines the basic options interface of a TexturePacker
  14. */
  15. export interface ITexturePackerOptions{
  16. /**
  17. * Custom targets for the channels of a texture packer. Default is all the channels of the Standard Material
  18. */
  19. map?: string[];
  20. /**
  21. * 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
  22. */
  23. uvsIn?: string;// | string[];
  24. /**
  25. * 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
  26. */
  27. uvsOut?: string;// | string[];
  28. /**
  29. * number representing the layout style. Defaults to LAYOUT_STRIP
  30. */
  31. layout?: number;
  32. /**
  33. * number of columns if using custom column count layout(2). This defaults to 4.
  34. */
  35. colcount?: number;
  36. /**
  37. * flag to update the input meshes to the new packed texture after compilation. Defaults to true.
  38. */
  39. updateInputMeshes?: boolean;
  40. /**
  41. * boolean flag to dispose all the source textures. Defaults to true.
  42. */
  43. disposeSources?: boolean;
  44. /**
  45. * Fills the blank cells in a set to the customFillColor. Defaults to true.
  46. */
  47. fillBlanks?: boolean;
  48. /**
  49. * string value representing the context fill style color. Defaults to 'black'.
  50. */
  51. customFillColor?: string;
  52. /**
  53. * Width and Height Value of each Frame in the TexturePacker Sets
  54. */
  55. frameSize?: number;
  56. /**
  57. * Ratio of the value to add padding wise to each cell. Defaults to 0.0115
  58. */
  59. paddingRatio?: number;
  60. }
  61. /**
  62. * This is a support class that generates a series of packed texture sets.
  63. * @see #TODO ADD THIS
  64. */
  65. export class TexturePacker{
  66. /** mag = nearest and min = nearest and mip = nearest */
  67. public static readonly LAYOUT_STRIP = Constants.LAYOUT_STRIP;
  68. /** mag = nearest and min = linear and mip = nearest */
  69. public static readonly LAYOUT_POWER2 = Constants.LAYOUT_POWER2;
  70. /** mag = nearest and min = linear and mip = linear */
  71. public static readonly LAYOUT_COLNUM = Constants.LAYOUT_COLNUM;
  72. /** The Name of the Texture Package */
  73. public name: string;
  74. /** The scene scope of the TexturePacker */
  75. public scene: Scene;
  76. /** The Meshes to target */
  77. public meshes: AbstractMesh[];
  78. /** Arguments passed with the Constructor */
  79. public options: ITexturePackerOptions;
  80. /** The promise that is started upon initialization */
  81. public promise: Promise< TexturePacker | string >;
  82. /** The Container object for the channel sets that are generated */
  83. public sets: object;
  84. /** The Container array for the frames that are generated */
  85. public frames: TexturePackerFrame[];
  86. /** The List of textures to purge from memory after compilation */
  87. private _disposeList: Texture[];
  88. /** The padding value from Math.ceil(frameSize * paddingRatio) */
  89. private _paddingValue: number;
  90. /**
  91. * Initializes a texture package series from an array of meshes or a single mesh.
  92. * @param name The name of the package
  93. * @param meshes The target meshes to compose the package from
  94. * @param options The arguments that texture packer should follow while building.
  95. * @param scene The scene which the textures are scoped to.
  96. * @returns TexturePacker
  97. */
  98. constructor(name: string, meshes: AbstractMesh[], options: ITexturePackerOptions, scene: Scene) {
  99. this.name = name;
  100. this.meshes = meshes;
  101. this.scene = scene;
  102. /**
  103. * Run through the options and set what ever defaults are needed that where not declared.
  104. */
  105. this.options = options;
  106. this.options.map = this.options.map || [
  107. 'ambientTexture',
  108. 'bumpTexture',
  109. 'diffuseTexture',
  110. 'emissiveTexture',
  111. 'lightmapTexture',
  112. 'opacityTexture',
  113. 'reflectionTexture',
  114. 'refractionTexture',
  115. 'specularTexture'
  116. ];
  117. this.options.uvsIn = this.options.uvsIn || VertexBuffer.UVKind;
  118. this.options.uvsOut = this.options.uvsOut || VertexBuffer.UVKind;
  119. this.options.layout = this.options.layout || TexturePacker.LAYOUT_STRIP;
  120. if (this.options.layout === TexturePacker.LAYOUT_COLNUM) {
  121. this.options.colcount = this.options.colcount || 4;
  122. }
  123. this.options.updateInputMeshes = this.options.updateInputMeshes || true;
  124. this.options.disposeSources = this.options.disposeSources || true;
  125. this.options.fillBlanks = this.options.fillBlanks || true;
  126. if (this.options.fillBlanks === true) {
  127. this.options.customFillColor = this.options.customFillColor || 'black';
  128. }
  129. this.options.frameSize = this.options.frameSize || 256;
  130. this.options.paddingRatio = this.options.paddingRatio || 0.0115;
  131. this._paddingValue = Math.ceil(this.options.frameSize * this.options.paddingRatio);
  132. //Make it an even padding Number.
  133. if (this._paddingValue % 2 !== 0 ) {
  134. this._paddingValue++;
  135. }
  136. /**
  137. * Create the promise and then run through the materials on the meshes.
  138. */
  139. this.promise = new Promise ((resolve, reject) => {
  140. console.log("Promise Start");
  141. try{
  142. let done = 0;
  143. const doneCheck = ( mat: Material )=>{
  144. done++;
  145. //Check Status of all Textures on all meshes, till they are ready.
  146. if(this.options.map){
  147. for(let j = 0; j < this.options.map.length; j++){
  148. let index: string = this.options.map[j];
  149. let t: Texture = (mat as any)[index];
  150. if(t !== null){
  151. if(!(this.sets as any)[this.options.map[j]]){
  152. (this.sets as any)[this.options.map[j]] = true;
  153. }
  154. if(this.options.disposeSources){
  155. this._disposeList.push(t);
  156. }
  157. }
  158. }
  159. if(done == this.meshes.length){
  160. this._createFrames(resolve);
  161. }
  162. }
  163. }
  164. for(let i = 0; i < this.meshes.length; i++){
  165. let mesh = this.meshes[i];
  166. let material: Nullable<Material> | Material = mesh.material;
  167. if(!material){
  168. return new Error('Mesh has no Material Assigned!');
  169. }
  170. material.forceCompilationAsync(mesh).then(() => {
  171. doneCheck((material as Material));
  172. })
  173. }
  174. }catch(e){
  175. return reject(e);
  176. }
  177. })
  178. return
  179. }
  180. /**
  181. * Starts the package process
  182. * @param resolve The promises resolution function
  183. * @returns TexturePacker
  184. */
  185. private _createFrames(resolve: Function){
  186. let dtSize = this._calculateSize();
  187. let dtUnits = (new Vector2(1,1)).divide(dtSize);
  188. let doneCount = 0;
  189. let expecting = this._disposeList.length;
  190. let meshLength = this.meshes.length;
  191. let sKeys = Object.keys(this.sets);
  192. for(let i = 0; i < sKeys.length; i++){
  193. let setName = sKeys[i];
  194. let dt = new DynamicTexture(this.name+'.TexturePack.'+setName+'Set',
  195. {width:dtSize.x, height:dtSize.y},
  196. this.scene,
  197. true, //Generate Mips
  198. Texture.TRILINEAR_SAMPLINGMODE,
  199. Engine.TEXTUREFORMAT_RGBA
  200. );
  201. let dtx = dt.getContext();
  202. dtx.fillStyle = 'rgba(0,0,0,0)';
  203. dtx.fillRect(0,0, dtSize.x, dtSize.y) ;
  204. dt.update();
  205. (this.sets as any)[setName] = dt;
  206. }
  207. let baseSize = this.options.frameSize || 256;
  208. let padding = this._paddingValue;
  209. let tcs = baseSize + (2 * padding);
  210. const done = ()=>{
  211. console.log("Compilation Done")
  212. this._calculateMeshUVFrames(baseSize, padding, dtSize, dtUnits, this.options.updateInputMeshes || false)
  213. }
  214. //Update the Textures
  215. for(let i = 0; i < meshLength; i++){
  216. let m = this.meshes[i]
  217. let mat = m.material
  218. //Check if the material has the texture
  219. //Create a temporary canvas the same size as 1 frame
  220. //Then apply the texture to the center and the 8 offsets
  221. //Copy the Context and place in the correct frame on the DT
  222. for(let j = 0; j < sKeys.length; j++){
  223. let tempTexture = new DynamicTexture('temp', tcs, this.scene, true)
  224. let tcx = tempTexture.getContext()
  225. //tempTexture.update(false)
  226. let offset = this._getFrameOffset(i)
  227. const updateDt = ()=>{
  228. doneCount++
  229. console.log("Done/To:", doneCount, expecting)
  230. let iDat = tcx.getImageData(0, 0, tcs, tcs)
  231. //Update Set
  232. let dt = (this.sets as any)[setName]
  233. let dtx = dt.getContext()
  234. dtx.putImageData(iDat, dtSize.x * offset.x, dtSize.y * offset.y)
  235. tempTexture.dispose()
  236. dt.update(false)
  237. if(doneCount == expecting){
  238. console.log('Done Making Frames')
  239. done()
  240. resolve(this)
  241. }
  242. }
  243. let setName = sKeys[j] || '_blank';
  244. if((mat as any)[setName] === null){
  245. console.log("Blank Frame:", i, setName);
  246. tcx.fillStyle = 'rgba(0,0,0,0)';
  247. if(this.options.fillBlanks){
  248. tcx.fillStyle = (this.options.customFillColor as string);
  249. }
  250. tcx.fillRect(0,0, tcs, tcs);
  251. tempTexture.update(false);
  252. updateDt();
  253. }else{
  254. console.log("Generating Frame:", i, setName, offset)
  255. let img = new Image();
  256. img.src = (mat as any)[setName]!.url;
  257. img.onload = ()=>{
  258. tcx.fillStyle = 'rgba(0,0,0,0)'
  259. tcx.fillRect(0,0, tcs, tcs)
  260. tempTexture.update(false)
  261. console.log("Image Loaded")
  262. tcx.drawImage(
  263. img,
  264. 0,
  265. 0,
  266. img.width,
  267. img.height,
  268. padding,
  269. padding,
  270. baseSize,
  271. baseSize
  272. )
  273. //Right
  274. tcx.drawImage(
  275. img,
  276. 0,
  277. 0,
  278. img.width,
  279. img.height,
  280. padding+baseSize,
  281. padding,
  282. baseSize,
  283. baseSize
  284. )
  285. //RightBottom
  286. tcx.drawImage(
  287. img,
  288. 0,
  289. 0,
  290. img.width,
  291. img.height,
  292. padding+baseSize,
  293. padding+baseSize,
  294. baseSize,
  295. baseSize
  296. )
  297. //Bottom
  298. tcx.drawImage(
  299. img,
  300. 0,
  301. 0,
  302. img.width,
  303. img.height,
  304. padding,
  305. padding+baseSize,
  306. baseSize,
  307. baseSize
  308. )
  309. //BottomLeft
  310. tcx.drawImage(
  311. img,
  312. 0,
  313. 0,
  314. img.width,
  315. img.height,
  316. padding-baseSize,
  317. padding+baseSize,
  318. baseSize,
  319. baseSize
  320. )
  321. //Left
  322. tcx.drawImage(
  323. img,
  324. 0,
  325. 0,
  326. img.width,
  327. img.height,
  328. padding-baseSize,
  329. padding,
  330. baseSize,
  331. baseSize
  332. )
  333. //LeftTop
  334. tcx.drawImage(
  335. img,
  336. 0,
  337. 0,
  338. img.width,
  339. img.height,
  340. padding-baseSize,
  341. padding-baseSize,
  342. baseSize,
  343. baseSize
  344. )
  345. //Top
  346. tcx.drawImage(
  347. img,
  348. 0,
  349. 0,
  350. img.width,
  351. img.height,
  352. padding,
  353. padding-baseSize,
  354. baseSize,
  355. baseSize
  356. )
  357. //TopRight
  358. tcx.drawImage(
  359. img,
  360. 0,
  361. 0,
  362. img.width,
  363. img.height,
  364. padding+baseSize,
  365. padding-baseSize,
  366. baseSize,
  367. baseSize
  368. )
  369. updateDt()
  370. }
  371. }
  372. }
  373. }
  374. }
  375. /**
  376. * Calculates the Size of the Channel Sets
  377. * @returns Vector2
  378. */
  379. private _calculateSize(): Vector2{
  380. let meshLength: number = this.meshes.length || 0;
  381. let baseSize: number = this.options.frameSize || 0;
  382. let padding: number = this._paddingValue || 0;
  383. switch(this.options.layout){
  384. case 0 :
  385. //STRIP_LAYOUT
  386. return new Vector2(
  387. ( baseSize * meshLength ) + ( 2 * padding * meshLength ),
  388. ( baseSize ) + (2 * padding )
  389. );
  390. break;
  391. }
  392. return Vector2.Zero();
  393. }
  394. /**
  395. * Calculates the UV data for the frames.
  396. * @param baseSize the base frameSize
  397. * @param padding the base frame padding
  398. * @param dtSize size of the Dynamic Texture for that channel
  399. * @param dtUnits is 1/dtSize
  400. * @param update flag to update the input meshes
  401. * @returns Void
  402. */
  403. private _calculateMeshUVFrames(baseSize: number, padding: number, dtSize: Vector2, dtUnits: Vector2, update: boolean){
  404. let meshLength = this.meshes.length;
  405. for(let i = 0; i < meshLength; i++){
  406. let m = this.meshes[i];
  407. let scale = new Vector2(
  408. baseSize / dtSize.x,
  409. baseSize / dtSize.y,
  410. );
  411. let pOffset: Vector2 = dtUnits.clone().scale(padding);
  412. let frameOffset: Vector2 = this._getFrameOffset(i);
  413. let offset: Vector2 = frameOffset.add(pOffset);
  414. let frame: TexturePackerFrame = new TexturePackerFrame( i, scale, offset );
  415. this.frames.push(
  416. frame
  417. );
  418. //Update Output UVs
  419. if(update){
  420. this._updateMeshUV(m, i);
  421. this._updateTextureRefrences(m);
  422. }
  423. }
  424. }
  425. private _getFrameOffset(index: number): Vector2{
  426. let meshLength = this.meshes.length
  427. switch(this.options.layout){
  428. case 0 :
  429. //STRIP_LAYOUT
  430. let xStep = 1/meshLength
  431. return new Vector2(
  432. index * xStep,
  433. 0
  434. )
  435. break;
  436. }
  437. return Vector2.Zero();
  438. }
  439. private _updateMeshUV(mesh: AbstractMesh, frameID: number): void{
  440. let frame: TexturePackerFrame = (this.frames as any)[frameID];
  441. let uvIn = mesh.getVerticesData(this.options.uvsIn || VertexBuffer.UVKind)
  442. let uvOut = []
  443. let toCount = 0;
  444. if(uvIn!.length){
  445. toCount = uvIn!.length || 0;
  446. }
  447. for(let i = 0; i < toCount; i+=2){
  448. uvOut.push(
  449. ( (uvIn as any)[i] * frame.scale.x ) + frame.offset.x,
  450. ( (uvIn as any)[i + 1] * frame.scale.y ) + frame.offset.y
  451. )
  452. }
  453. mesh.setVerticesData(this.options.uvsOut || VertexBuffer.UVKind, uvOut)
  454. }
  455. private _updateTextureRefrences(m: AbstractMesh): void{
  456. let mat = m.material
  457. let sKeys = Object.keys(this.sets)
  458. for(let i = 0; i < sKeys.length; i++){
  459. let setName = sKeys[i]
  460. if((mat as any)[setName] !== null){
  461. if((mat as any)[setName].dispose){
  462. (mat as any)[setName].dispose()
  463. }
  464. (mat as any)[setName] = (this.sets as any)[setName]
  465. }
  466. }
  467. }
  468. }