spriteMap.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. import { Engine } from "../Engines/engine";
  2. import { IDisposable, Scene } from "../scene";
  3. import { Nullable } from "../types";
  4. import { Vector2, Vector3 } from "../Maths/math.vector";
  5. import { Texture } from "../Materials/Textures/texture";
  6. import { RawTexture } from "../Materials/Textures/rawTexture";
  7. import { ShaderMaterial } from "../Materials/shaderMaterial";
  8. import { Mesh } from "../Meshes/mesh";
  9. import { PickingInfo } from "../Collisions/pickingInfo";
  10. import { ISpriteJSONSprite, ISpriteJSONAtlas } from "./ISprites";
  11. import { Effect } from "../Materials/effect";
  12. import "../Meshes/Builders/planeBuilder";
  13. import "../Shaders/spriteMap.fragment";
  14. import "../Shaders/spriteMap.vertex";
  15. /**
  16. * Defines the basic options interface of a SpriteMap
  17. */
  18. export interface ISpriteMapOptions{
  19. /**
  20. * Vector2 of the number of cells in the grid.
  21. */
  22. stageSize?: Vector2;
  23. /**
  24. * Vector2 of the size of the output plane in World Units.
  25. */
  26. outputSize?: Vector2;
  27. /**
  28. * Vector3 of the position of the output plane in World Units.
  29. */
  30. outputPosition?: Vector3;
  31. /**
  32. * Vector3 of the rotation of the output plane.
  33. */
  34. outputRotation?: Vector3;
  35. /**
  36. * number of layers that the system will reserve in resources.
  37. */
  38. layerCount?: number;
  39. /**
  40. * number of max animation frames a single cell will reserve in resources.
  41. */
  42. maxAnimationFrames?: number;
  43. /**
  44. * number cell index of the base tile when the system compiles.
  45. */
  46. baseTile?: number;
  47. /**
  48. * boolean flip the sprite after its been repositioned by the framing data.
  49. */
  50. flipU?: boolean;
  51. /**
  52. * Vector3 scalar of the global RGB values of the SpriteMap.
  53. */
  54. colorMultiply?: Vector3;
  55. }
  56. /**
  57. * Defines the IDisposable interface in order to be cleanable from resources.
  58. */
  59. export interface ISpriteMap extends IDisposable {
  60. /**
  61. * String name of the SpriteMap.
  62. */
  63. name: string;
  64. /**
  65. * The JSON Array file from a https://www.codeandweb.com/texturepacker export. Or similar structure.
  66. */
  67. atlasJSON: ISpriteJSONAtlas;
  68. /**
  69. * Texture of the SpriteMap.
  70. */
  71. spriteSheet: Texture;
  72. /**
  73. * The parameters to initialize the SpriteMap with.
  74. */
  75. options: ISpriteMapOptions;
  76. }
  77. /**
  78. * Class used to manage a grid restricted sprite deployment on an Output plane.
  79. */
  80. export class SpriteMap implements ISpriteMap {
  81. /** The Name of the spriteMap */
  82. public name: string;
  83. /** The JSON file with the frame and meta data */
  84. public atlasJSON: ISpriteJSONAtlas;
  85. /** The systems Sprite Sheet Texture */
  86. public spriteSheet: Texture;
  87. /** Arguments passed with the Constructor */
  88. public options: ISpriteMapOptions;
  89. /** Public Sprite Storage array, parsed from atlasJSON */
  90. public sprites: Array<ISpriteJSONSprite>;
  91. /** Returns the Number of Sprites in the System */
  92. public get spriteCount(): number {
  93. return this.sprites.length;
  94. }
  95. /** Returns the Position of Output Plane*/
  96. public get position(): Vector3 {
  97. return this._output.position;
  98. }
  99. /** Returns the Position of Output Plane*/
  100. public set position(v: Vector3) {
  101. this._output.position = v;
  102. }
  103. /** Returns the Rotation of Output Plane*/
  104. public get rotation(): Vector3 {
  105. return this._output.rotation;
  106. }
  107. /** Returns the Rotation of Output Plane*/
  108. public set rotation(v: Vector3) {
  109. this._output.rotation = v;
  110. }
  111. /** Sets the AnimationMap*/
  112. public get animationMap() {
  113. return this._animationMap;
  114. }
  115. /** Sets the AnimationMap*/
  116. public set animationMap(v: RawTexture) {
  117. let buffer = v!._texture!._bufferView;
  118. let am = this._createTileAnimationBuffer(buffer);
  119. this._animationMap.dispose();
  120. this._animationMap = am;
  121. this._material.setTexture("animationMap", this._animationMap);
  122. }
  123. /** Scene that the SpriteMap was created in */
  124. private _scene: Scene;
  125. /** Texture Buffer of Float32 that holds tile frame data*/
  126. private _frameMap: RawTexture;
  127. /** Texture Buffers of Float32 that holds tileMap data*/
  128. private _tileMaps: RawTexture[];
  129. /** Texture Buffer of Float32 that holds Animation Data*/
  130. private _animationMap: RawTexture;
  131. /** Custom ShaderMaterial Central to the System*/
  132. private _material: ShaderMaterial;
  133. /** Custom ShaderMaterial Central to the System*/
  134. private _output: Mesh;
  135. /** Systems Time Ticker*/
  136. private _time: number;
  137. /**
  138. * Creates a new SpriteMap
  139. * @param name defines the SpriteMaps Name
  140. * @param atlasJSON is the JSON file that controls the Sprites Frames and Meta
  141. * @param spriteSheet is the Texture that the Sprites are on.
  142. * @param options a basic deployment configuration
  143. * @param scene The Scene that the map is deployed on
  144. */
  145. constructor(name : string, atlasJSON: ISpriteJSONAtlas, spriteSheet: Texture, options: ISpriteMapOptions, scene : Scene) {
  146. this.name = name;
  147. this.sprites = [];
  148. this.atlasJSON = atlasJSON;
  149. this.sprites = this.atlasJSON["frames"];
  150. this.spriteSheet = spriteSheet;
  151. /**
  152. * Run through the options and set what ever defaults are needed that where not declared.
  153. */
  154. this.options = options;
  155. options.stageSize = options.stageSize || new Vector2(1, 1);
  156. options.outputSize = options.outputSize || options.stageSize;
  157. options.outputPosition = options.outputPosition || Vector3.Zero();
  158. options.outputRotation = options.outputRotation || Vector3.Zero();
  159. options.layerCount = options.layerCount || 1;
  160. options.maxAnimationFrames = options.maxAnimationFrames || 0;
  161. options.baseTile = options.baseTile || 0;
  162. options.flipU = options.flipU || false;
  163. options.colorMultiply = options.colorMultiply || new Vector3(1, 1, 1);
  164. this._scene = scene;
  165. this._frameMap = this._createFrameBuffer();
  166. this._tileMaps = new Array();
  167. for (let i = 0; i < options.layerCount; i++) {
  168. this._tileMaps.push(this._createTileBuffer(null, i));
  169. }
  170. this._animationMap = this._createTileAnimationBuffer(null);
  171. let defines = [];
  172. defines.push("#define LAYERS " + options.layerCount);
  173. if (options.flipU) {
  174. defines.push("#define FLIPU");
  175. }
  176. defines.push(`#define MAX_ANIMATION_FRAMES ${options.maxAnimationFrames}.0`);
  177. let shaderString: string = Effect.ShadersStore["spriteMapPixelShader"];
  178. let layerSampleString: string;
  179. if (this._scene.getEngine().webGLVersion === 1) {
  180. layerSampleString = "";
  181. for (let i = 0; i < options.layerCount; i++) {
  182. layerSampleString += `if (${i} == i) { frameID = texture2D(tileMaps[${i}], (tileID + 0.5) / stageSize, 0.).x; }`;
  183. }
  184. }
  185. else {
  186. layerSampleString = "switch(i) {";
  187. for (let i = 0; i < options.layerCount; i++) {
  188. layerSampleString += "case " + i + " : frameID = texture(tileMaps[" + i + "], (tileID + 0.5) / stageSize, 0.).x;";
  189. layerSampleString += "break;";
  190. }
  191. layerSampleString += "}";
  192. }
  193. Effect.ShadersStore["spriteMap" + this.name + "PixelShader"] = shaderString.replace("#define LAYER_ID_SWITCH", layerSampleString);
  194. this._material = new ShaderMaterial("spriteMap:" + this.name, this._scene, {
  195. vertex: "spriteMap",
  196. fragment: "spriteMap" + this.name,
  197. }, {
  198. defines,
  199. attributes: ["position", "normal", "uv"],
  200. uniforms: [
  201. "worldViewProjection",
  202. "time",
  203. "stageSize",
  204. "outputSize",
  205. "spriteMapSize",
  206. "spriteCount",
  207. "time",
  208. "colorMul",
  209. "mousePosition",
  210. "curTile",
  211. "flipU"
  212. ],
  213. samplers: [
  214. "spriteSheet", "frameMap", "tileMaps", "animationMap"
  215. ],
  216. needAlphaBlending: true
  217. });
  218. this._time = 0;
  219. this._material.setFloat("spriteCount", this.spriteCount);
  220. this._material.setVector2("stageSize", options.stageSize);
  221. this._material.setVector2("outputSize", options.outputSize);
  222. this._material.setTexture("spriteSheet", this.spriteSheet);
  223. this._material.setVector2("spriteMapSize", new Vector2(1, 1));
  224. this._material.setVector3("colorMul", options.colorMultiply);
  225. let tickSave = 0;
  226. const bindSpriteTexture = () => {
  227. if ((this.spriteSheet) && this.spriteSheet.isReady()) {
  228. if (this.spriteSheet._texture) {
  229. this._material.setVector2("spriteMapSize", new Vector2(this.spriteSheet._texture.baseWidth || 1, this.spriteSheet._texture.baseHeight || 1));
  230. return;
  231. }
  232. }
  233. if (tickSave < 100) {
  234. setTimeout(() => {tickSave++; bindSpriteTexture(); }, 100);
  235. }
  236. };
  237. bindSpriteTexture();
  238. this._material.setVector3("colorMul", options.colorMultiply);
  239. this._material.setTexture("frameMap", this._frameMap);
  240. this._material.setTextureArray("tileMaps", this._tileMaps);
  241. this._material.setTexture("animationMap", this._animationMap);
  242. this._material.setFloat("time", this._time);
  243. this._output = Mesh.CreatePlane(name + ":output", 1, scene, true);
  244. this._output.scaling.x = options.outputSize.x;
  245. this._output.scaling.y = options.outputSize.y;
  246. let obfunction = () => {
  247. this._time += this._scene.getEngine().getDeltaTime();
  248. this._material.setFloat("time", this._time);
  249. };
  250. this._scene.onBeforeRenderObservable.add(obfunction);
  251. this._output.material = this._material;
  252. }
  253. /**
  254. * Returns tileID location
  255. * @returns Vector2 the cell position ID
  256. */
  257. public getTileID(): Vector2 {
  258. let p = this.getMousePosition();
  259. p.multiplyInPlace(this.options.stageSize || Vector2.Zero());
  260. p.x = Math.floor(p.x);
  261. p.y = Math.floor(p.y);
  262. return p;
  263. }
  264. /**
  265. * Gets the UV location of the mouse over the SpriteMap.
  266. * @returns Vector2 the UV position of the mouse interaction
  267. */
  268. public getMousePosition(): Vector2 {
  269. let out = this._output;
  270. var pickinfo: Nullable<PickingInfo> = this._scene.pick(this._scene.pointerX, this._scene.pointerY, (mesh) => {
  271. if (mesh !== out) {
  272. return false;
  273. }
  274. return true;
  275. });
  276. if (((!pickinfo) || !pickinfo.hit) || !pickinfo.getTextureCoordinates) {
  277. return new Vector2(-1, -1);
  278. }
  279. let coords = pickinfo.getTextureCoordinates();
  280. if (coords) {
  281. return coords;
  282. }
  283. return new Vector2(-1, -1);
  284. }
  285. /**
  286. * Creates the "frame" texture Buffer
  287. * -------------------------------------
  288. * Structure of frames
  289. * "filename": "Falling-Water-2.png",
  290. * "frame": {"x":69,"y":103,"w":24,"h":32},
  291. * "rotated": true,
  292. * "trimmed": true,
  293. * "spriteSourceSize": {"x":4,"y":0,"w":24,"h":32},
  294. * "sourceSize": {"w":32,"h":32}
  295. * @returns RawTexture of the frameMap
  296. */
  297. private _createFrameBuffer(): RawTexture {
  298. let data = new Array();
  299. //Do two Passes
  300. for (let i = 0; i < this.spriteCount; i++) {
  301. data.push(0, 0, 0, 0); //frame
  302. data.push(0, 0, 0, 0); //spriteSourceSize
  303. data.push(0, 0, 0, 0); //sourceSize, rotated, trimmed
  304. data.push(0, 0, 0, 0); //Keep it pow2 cause I"m cool like that... it helps with sampling accuracy as well. Plus then we have 4 other parameters for future stuff.
  305. }
  306. //Second Pass
  307. for (let i = 0; i < this.spriteCount; i++) {
  308. let f = this.sprites[i]["frame"];
  309. let sss = this.sprites[i]["spriteSourceSize"];
  310. let ss = this.sprites[i]["sourceSize"];
  311. let r = (this.sprites[i]["rotated"]) ? 1 : 0;
  312. let t = (this.sprites[i]["trimmed"]) ? 1 : 0;
  313. //frame
  314. data[i * 4] = f.x;
  315. data[i * 4 + 1] = f.y;
  316. data[i * 4 + 2] = f.w;
  317. data[i * 4 + 3] = f.h;
  318. //spriteSourceSize
  319. data[i * 4 + (this.spriteCount * 4)] = sss.x;
  320. data[i * 4 + 1 + (this.spriteCount * 4)] = sss.y;
  321. data[i * 4 + 3 + (this.spriteCount * 4)] = sss.h;
  322. //sourceSize, rotated, trimmed
  323. data[i * 4 + (this.spriteCount * 8)] = ss.w;
  324. data[i * 4 + 1 + (this.spriteCount * 8)] = ss.h;
  325. data[i * 4 + 2 + (this.spriteCount * 8)] = r;
  326. data[i * 4 + 3 + (this.spriteCount * 8)] = t ;
  327. }
  328. let floatArray = new Float32Array(data);
  329. let t = RawTexture.CreateRGBATexture(
  330. floatArray,
  331. this.spriteCount,
  332. 4,
  333. this._scene,
  334. false,
  335. false,
  336. Texture.NEAREST_NEAREST,
  337. Engine.TEXTURETYPE_FLOAT
  338. );
  339. return t;
  340. }
  341. /**
  342. * Creates the tileMap texture Buffer
  343. * @param buffer normally and array of numbers, or a false to generate from scratch
  344. * @param _layer indicates what layer for a logic trigger dealing with the baseTile. The system uses this
  345. * @returns RawTexture of the tileMap
  346. */
  347. private _createTileBuffer(buffer: any, _layer: number = 0): RawTexture {
  348. let data = new Array();
  349. let _ty = (this.options.stageSize!.y) || 0;
  350. let _tx = (this.options.stageSize!.x) || 0;
  351. if (!buffer) {
  352. let bt = this.options.baseTile;
  353. if (_layer != 0) {
  354. bt = 0;
  355. }
  356. for (let y = 0; y < _ty; y++) {
  357. for (let x = 0; x < _tx * 4; x += 4) {
  358. data.push(bt, 0, 0, 0);
  359. }
  360. }
  361. } else {
  362. data = buffer;
  363. }
  364. let floatArray = new Float32Array(data);
  365. let t = RawTexture.CreateRGBATexture(
  366. floatArray,
  367. _tx,
  368. _ty,
  369. this._scene,
  370. false,
  371. false,
  372. Texture.NEAREST_NEAREST,
  373. Engine.TEXTURETYPE_FLOAT
  374. );
  375. return t;
  376. }
  377. /**
  378. * Modifies the data of the tileMaps
  379. * @param _layer is the ID of the layer you want to edit on the SpriteMap
  380. * @param pos is the iVector2 Coordinates of the Tile
  381. * @param tile The SpriteIndex of the new Tile
  382. */
  383. public changeTiles(_layer: number = 0, pos: Vector2 | Vector2[] , tile: number = 0): void {
  384. let buffer: Nullable<ArrayBufferView>;
  385. buffer = this._tileMaps[_layer]!._texture!._bufferView;
  386. if (buffer === null) {
  387. return;
  388. }
  389. let p = new Array();
  390. if (pos instanceof Vector2) {
  391. p.push(pos);
  392. } else {
  393. p = pos;
  394. }
  395. let _tx = (this.options.stageSize!.x) || 0;
  396. for (let i = 0; i < p.length; i++) {
  397. let _p = p[i];
  398. _p.x = Math.floor(_p.x);
  399. _p.y = Math.floor(_p.y);
  400. let id: number = (_p.x * 4) + (_p.y * (_tx * 4));
  401. (buffer as any)[id] = tile;
  402. }
  403. let t = this._createTileBuffer(buffer);
  404. this._tileMaps[_layer].dispose();
  405. this._tileMaps[_layer] = t;
  406. this._material.setTextureArray("tileMap", this._tileMaps);
  407. }
  408. /**
  409. * Creates the animationMap texture Buffer
  410. * @param buffer normally and array of numbers, or a false to generate from scratch
  411. * @returns RawTexture of the animationMap
  412. */
  413. private _createTileAnimationBuffer(buffer: Nullable<ArrayBufferView>): RawTexture {
  414. let data = new Array();
  415. let floatArray;
  416. if (!buffer) {
  417. for (let i = 0; i < this.spriteCount; i++) {
  418. data.push(0, 0, 0, 0);
  419. let count = 1;
  420. while (count < (this.options.maxAnimationFrames || 4)) {
  421. data.push(0, 0, 0, 0);
  422. count++;
  423. }
  424. }
  425. floatArray = new Float32Array(data);
  426. } else {
  427. floatArray = buffer;
  428. }
  429. let t = RawTexture.CreateRGBATexture(
  430. floatArray,
  431. this.spriteCount,
  432. (this.options.maxAnimationFrames || 4),
  433. this._scene,
  434. false,
  435. false,
  436. Texture.NEAREST_NEAREST,
  437. Engine.TEXTURETYPE_FLOAT
  438. );
  439. return t;
  440. }
  441. /**
  442. * Modifies the data of the animationMap
  443. * @param cellID is the Index of the Sprite
  444. * @param _frame is the target Animation frame
  445. * @param toCell is the Target Index of the next frame of the animation
  446. * @param time is a value between 0-1 that is the trigger for when the frame should change tiles
  447. * @param speed is a global scalar of the time variable on the map.
  448. */
  449. public addAnimationToTile(cellID: number = 0, _frame: number = 0, toCell: number = 0, time: number = 0, speed: number = 1): void {
  450. let buffer: any = this._animationMap!._texture!._bufferView;
  451. let id: number = (cellID * 4) + (this.spriteCount * 4 * _frame);
  452. if (!buffer) {
  453. return;
  454. }
  455. buffer[id] = toCell;
  456. buffer[id + 1 ] = time;
  457. buffer[id + 2 ] = speed;
  458. let t = this._createTileAnimationBuffer(buffer);
  459. this._animationMap.dispose();
  460. this._animationMap = t;
  461. this._material.setTexture("animationMap", this._animationMap);
  462. }
  463. /**
  464. * Exports the .tilemaps file
  465. */
  466. public saveTileMaps(): void {
  467. let maps = "";
  468. for (var i = 0; i < this._tileMaps.length; i++) {
  469. if (i > 0) {maps += "\n\r"; }
  470. maps += this._tileMaps[i]!._texture!._bufferView!.toString();
  471. }
  472. var hiddenElement = document.createElement("a");
  473. hiddenElement.href = "data:octet/stream;charset=utf-8," + encodeURI(maps);
  474. hiddenElement.target = "_blank";
  475. hiddenElement.download = this.name + ".tilemaps";
  476. hiddenElement.click();
  477. hiddenElement.remove();
  478. }
  479. /**
  480. * Imports the .tilemaps file
  481. * @param url of the .tilemaps file
  482. */
  483. public loadTileMaps(url : string) : void {
  484. let xhr = new XMLHttpRequest();
  485. xhr.open("GET", url);
  486. let _lc = this.options!.layerCount || 0;
  487. xhr.onload = () =>
  488. {
  489. let data = xhr.response.split("\n\r");
  490. for (let i = 0; i < _lc; i++) {
  491. let d = (data[i].split(",")).map(Number);
  492. let t = this._createTileBuffer(d);
  493. this._tileMaps[i].dispose();
  494. this._tileMaps[i] = t;
  495. }
  496. this._material.setTextureArray("tileMap", this._tileMaps);
  497. };
  498. xhr.send();
  499. }
  500. /**
  501. * Release associated resources
  502. */
  503. public dispose(): void {
  504. this._output.dispose();
  505. this._material.dispose();
  506. this._animationMap.dispose();
  507. this._tileMaps.forEach((tm) => {
  508. tm.dispose();
  509. });
  510. this._frameMap.dispose();
  511. }
  512. }