packer.ts 24 KB

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