diff --git a/game/bot.aseprite b/game/bot.aseprite index 31b78edc..de16c1e1 100644 Binary files a/game/bot.aseprite and b/game/bot.aseprite differ diff --git a/game/level.ts b/game/level.ts new file mode 100644 index 00000000..63d8f168 --- /dev/null +++ b/game/level.ts @@ -0,0 +1,141 @@ +// NOTE: It's super not clear how much of this should be in rust vs +// javascript. Dealing with the level code is really nice in javascript +// but there's a bunch of weird stuff that really feels lower level. +import { load_texture } from "./assets"; +import { Texture, print, spr, use_texture } from "./graphics"; +import { load_string } from "./io"; + +// TODO: Use io-ts? YIKES. + +export type Tile = { + px: [number, number]; + src: [number, number]; + f: number; + t: number; + a: number; +}; + +export type TileLayer = { + type: "tile"; + texture: Texture; + grid_size: number; + offset: [number, number]; + tiles: Tile[]; +}; + +export type Layer = TileLayer; + +export type Level = { + world_x: number; + world_y: number; + width: number; + height: number; + layers: Layer[]; +}; + +export type TileSet = { id: number; texture: Texture }; + +export type World = { levels: Level[]; tilesets: Map }; + +async function load_tileset(def: { + uid: number; + relPath: string; +}): Promise { + let relPath = def.relPath as string; + if (relPath.endsWith(".aseprite")) { + // Whoops let's load the export instead? + relPath = relPath.substring(0, relPath.length - 8) + "png"; + } + let texture = await load_texture(relPath); + + print("Loaded tileset", def.uid, "from", relPath, "as ID", texture.id()); + return { id: def.uid, texture }; +} + +function load_level( + tile_sets: Map, + def: { + worldX: number; + worldY: number; + pxWid: number; + pxHei: number; + layerInstances: { + __gridSize: number; + __pxTotalOffsetX: number; + __pxTotalOffsetY: number; + __tilesetDefUid: number; + gridTiles: { + px: [number, number]; + src: [number, number]; + f: number; + t: number; + a: number; + }[]; + }[]; + } +): Level { + return { + world_x: def.worldX, + world_y: def.worldY, + width: def.pxWid, + height: def.pxHei, + layers: def.layerInstances.map((li) => { + const tileset = tile_sets.get(li.__tilesetDefUid); + if (!tileset) { + throw new Error("Unable to find texture!!! " + li.__tilesetDefUid); + } + + return { + type: "tile", + texture: tileset.texture, + grid_size: li.__gridSize, + offset: [li.__pxTotalOffsetX, li.__pxTotalOffsetY], + tiles: li.gridTiles, + }; + }), + }; +} + +export async function load_world(path: string): Promise { + print("Loading map:", path); + const blob = await load_string(path); + const map = JSON.parse(blob); + + const tilesets = new Map(); + let loaded_tilesets = await Promise.all( + map.defs.tilesets + .filter((def: any) => def.relPath != null) + .map((def: any) => load_tileset(def)) + ); + for (const ts of loaded_tilesets) { + tilesets.set(ts.id, ts); + } + + const levels = map.levels.map((l: any) => load_level(tilesets, l)); + return { levels, tilesets }; +} + +export function draw_level( + level: Level, + offset_x: number = 0, + offset_y: number = 0 +) { + for (const layer of level.layers) { + use_texture(layer.texture); + + let [ofx, ofy] = layer.offset; + ofx += offset_x; + ofy += offset_y; + for (const tile of layer.tiles) { + // TODO: Flip and whatnot. + spr( + tile.px[0] + ofx, + tile.px[1] + ofy, + layer.grid_size, + layer.grid_size, + tile.src[0], + tile.src[1] + ); + } + } +} diff --git a/game/main.ts b/game/main.ts index 802378b0..84282c67 100644 --- a/game/main.ts +++ b/game/main.ts @@ -2,8 +2,8 @@ import { cls, print, spr, use_texture, Texture } from "./graphics"; import { load_texture } from "./assets"; import { since_start } from "./time"; import { btn, Button } from "./input"; -// import { load_string } from "./io"; import { new_v2, vadd, vmul, vnorm } from "./vector"; +import { load_world, World, Level, draw_level } from "./level"; /// TODO: Support reload by saving and restoring state from init/update/restore. @@ -11,6 +11,8 @@ import { new_v2, vadd, vmul, vnorm } from "./vector"; let clock = 0; let bot_sprite: Texture | undefined = undefined; +let world: World | undefined = undefined; +let level: Level | undefined = undefined; // Note zelda overworld is 16x8 screens // zelda screen is 16x11 tiles @@ -18,13 +20,6 @@ let bot_sprite: Texture | undefined = undefined; let loaded = false; -async function load_map(path: string) { - // print("Loading map:", path); - // const blob = await load_string(path); - // const map = JSON.parse(blob); - // print("Loaded map:", map); -} - function load_assets() { // Start this load, but then... let texture_load = load_texture("./bot.png").then((n) => { @@ -32,7 +27,16 @@ function load_assets() { bot_sprite = n; }); - let map_load = load_map("./overworld.ldtk"); + let map_load = load_world("./overworld.ldtk").then((w) => { + print("World loaded at", since_start()); + world = w; + + // Assume we start at 0,0 + level = world.levels.find((l) => l.world_x == 0 && l.world_y == 0); + if (!level) { + throw new Error("UNABLE TO FIND LEVEL AT 0,0: CANNOT START"); + } + }); Promise.all([texture_load, map_load]).then(() => { loaded = true; @@ -106,6 +110,10 @@ export function draw() { return; } + if (level != undefined) { + draw_level(level, 0, 0); + } + if (bot_sprite != undefined) { // ...it gets resolved here? use_texture(bot_sprite); diff --git a/game/overworld.aseprite b/game/overworld.aseprite new file mode 100644 index 00000000..b7804aaa Binary files /dev/null and b/game/overworld.aseprite differ diff --git a/game/overworld.ldtk b/game/overworld.ldtk index ec00d190..1c46c27f 100644 --- a/game/overworld.ldtk +++ b/game/overworld.ldtk @@ -11,14 +11,14 @@ "iid": "315bbfe0-1460-11ee-be24-3324961fe10c", "jsonVersion": "1.3.3", "appBuildId": 467698, - "nextUid": 8, + "nextUid": 9, "identifierStyle": "Capitalize", "toc": [], "worldLayout": "GridVania", "worldGridWidth": 320, - "worldGridHeight": 176, + "worldGridHeight": 240, "defaultLevelWidth": 320, - "defaultLevelHeight": 176, + "defaultLevelHeight": 240, "defaultPivotX": 0, "defaultPivotY": 0, "defaultGridSize": 16, @@ -39,39 +39,9 @@ "customCommands": [], "flags": [], "defs": { "layers": [ - { - "__type": "Entities", - "identifier": "Entities", - "type": "Entities", - "uid": 6, - "doc": null, - "uiColor": null, - "gridSize": 16, - "guideGridWid": 0, - "guideGridHei": 0, - "displayOpacity": 1, - "inactiveOpacity": 0.6, - "hideInList": false, - "hideFieldsWhenInactive": true, - "canSelectWhenInactive": true, - "renderInWorldView": true, - "pxOffsetX": 0, - "pxOffsetY": 0, - "parallaxFactorX": 0, - "parallaxFactorY": 0, - "parallaxScaling": true, - "requiredTags": [], - "excludedTags": [], - "intGridValues": [], - "autoRuleGroups": [], - "autoSourceLayerDefUid": null, - "tilesetDefUid": null, - "tilePivotX": 0, - "tilePivotY": 0 - }, { "__type": "Tiles", - "identifier": "World", + "identifier": "Base", "type": "Tiles", "uid": 2, "doc": null, @@ -99,59 +69,28 @@ "tilePivotX": 0, "tilePivotY": 0 } - ], "entities": [ + ], "entities": [], "tilesets": [ { - "identifier": "Entity", - "uid": 4, - "tags": [], - "exportToToc": false, - "doc": null, - "width": 32, - "height": 32, - "resizableX": false, - "resizableY": false, - "minWidth": null, - "maxWidth": null, - "minHeight": null, - "maxHeight": null, - "keepAspectRatio": false, - "tileOpacity": 1, - "fillOpacity": 0.08, - "lineOpacity": 0, - "hollow": false, - "color": "#BE4A2F", - "renderMode": "Tile", - "showName": true, - "tilesetId": 5, - "tileRenderMode": "FitInside", - "tileRect": { "tilesetUid": 5, "x": 32, "y": 128, "w": 16, "h": 16 }, - "nineSliceBorders": [], - "maxCount": 0, - "limitScope": "PerLevel", - "limitBehavior": "MoveLastOne", - "pivotX": 0, - "pivotY": 0, - "fieldDefs": [] - } - ], "tilesets": [ - { - "__cWid": 2, - "__cHei": 2, - "identifier": "World_sprites", + "__cWid": 8, + "__cHei": 8, + "identifier": "Overworld", "uid": 1, - "relPath": "world_sprites.aseprite", + "relPath": "overworld.aseprite", "embedAtlas": null, - "pxWid": 32, - "pxHei": 32, + "pxWid": 128, + "pxHei": 128, "tileGridSize": 16, "spacing": 0, "padding": 0, "tags": [], - "tagsSourceEnumUid": null, - "enumTags": [], + "tagsSourceEnumUid": 8, + "enumTags": [{ "enumValueId": "Collide", "tileIds": [] }], "customData": [], "savedSelections": [], - "cachedPixelData": { "opaqueTiles": "1111", "averageColors": "f6b4f6b4f6b4f6b3" } + "cachedPixelData": { + "opaqueTiles": "1111111100000011000000000000000000000000000000000000000000000000", + "averageColors": "f6b4f6b4f6b4f6b3f6b3f6b3f6b3f6b3000000000000000000000000f6b3f6b3000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } }, { "__cWid": 32, @@ -175,7 +114,7 @@ "averageColors": "00004b344233459b423349a959a9379c688769758ca4bc9489aab9aa58cc58bc42d74d2244ce428f4c7e4fb34abb45564ffe7dda78880000000000000000000069a969a97a99999999989a85998699767a7579667ccc7ccc7bcb7caa7ccc7ccc22d72d2224ce228f2c7e2fb32abb25562ffe000000000000000000000000000059764b97599868ac679a69ab4a84477756787688475347532a932a934a837a83f2b6fb22f3acf15afa6cfc93f899f334fccc000000000000000000000000000059aa49aa59996999699969aa489949995999799a499949992999299948997889a385a823a379a248a749a864a667a223a8880000000000000000000000000000189919991999199939994778166727772889289948993aaa389949a959a959a932b63b2233ad315a395c3c83389933343ccc00000000000000000000000000008aaa8aaa8aaa8aaa8aaa7bbb8aaa7bbb8bcb7aaa8bcb7bcb69aa8aaa8aaa69aa6abb6abb6abb6abb6a226a226a226a2261a661a661a661a600000000000000006c526c426c926c91659b649c66a566a46a7b6a7b667766776aba6abb676367636b746b746b746b74616c616c616c616c8abb8abb8abb8abb00000000000000006ba5579a6689598658875cb66abb9aa989aa98ac7abc6678968a88877c87cba952755823536952475648586354455223599900000000000000000000000000003ec63da76db79dc7554885498969b4377fa29e8289cdb9ce5ade5ade49ce49ce82a68a22839b8259885b8b73855683238aab00000000000000000000000000005d745d867da87e75448c458b86ad76ae68ac679c779b78ce3c9378867ca6adb784858933847a844788498963854584348989000000000000000000000000000057a668b899b8449396534493858364836853697769436667755667776c73498800000000000000000000000000000000000000000000000000000000000000006bba79b87d9679ad776a7b988abc8abc4aceaace4bba4bba6b8c4c9c4cac5b7c000000000000000000000000000000000000000000000000000000000000000059aaada7a9bdcdbd59aaada7a9bdcdbd8cb8a9b98ac889b8aabaacc79ea498bd000000000000000000000000000000000000000000000000000000000000000057ac596b55946abb5abb8ca65d8677ac437b5a3368886934547a595897a57b230000000000000000000000000000000000000000000000000000000000000000799a5c817b9b3a886abb8464676a7a967a857a857977898889882a954a956b950000000000000000000000000000000000000000000000000000000000000000499977997868799579875a6465995a8957a66a735ba53a935969479a576a467700000000000000000000000000000000000000000000000000000000000000005744985596659b747a659a76768a7a5676754777388735665976987794459465000000000000000000000000000000000000000000000000000000000000000088668a66868a9b8577666a4467846987778a7789797a87888b8676667a767ca50000000000000000000000000000000000000000000000000000000000000000449374934c957c9574847a438475a3958695768565956853b9447a777493a493000000000000000000000000000000000000000000000000000000000000000079547a838394689a49547a6357636975786383848997b384655873748974588400000000000000000000000000000000000000000000000000000000000000007da48ca769768b554b976cba3a824a82696259526a758c986963694268478b850000000000000000000000000000000000000000000000000000000000000000696559555579557458598674573353635677575579667a8758538b848a44838b0000000000000000000000000000000000000000000000000000000000000000385437883b95534549555a855877997598772b953b9529a939a95aa84b949a840000000000000000000000000000000000000000000000000000000000000000897687898776878578998485878b789a847b8b6579998a55886998788a879b9700000000000000000000000000000000000000000000000000000000000000006ba97988897469646b987a876a997a987b987955766777765c958a858777867700000000000000000000000000000000000000000000000000000000000000005a747b947b967866a855788928884566578879a98864a579233433343334633400000000000000000000000000000000000000000000000000000000000000006a747b846a844997598669987bb8b8aabaa96ba67cba9854687669864a864b86000000000000000000000000000000000000000000000000000000000000000038ab389b48ab47ac49ab48ac579b48ac49ab38ab58bc4b8659aa5c8457ac586a0000000000000000000000000000000000000000000000000000000000000000299b2999389a379b38893955589a79bc8c9588bc7a8c599a689a5b8558ac597a00000000000000000000000000000000000000000000000000000000000000002888378936773975579b389a579b488938884b74469a465747785b75568b586a000000000000000000000000000000000000000000000000000000000000000038553865285428444755566455763a64356746743779397445674c63469b585a0000000000000000000000000000000000000000000000000000000000000000284437643a7629641555297938874879385438664665355536775a85569a785a00000000000000000000000000000000000000000000000000000000000000005789789b779b6a75668a897b64558555876576798855845694749b74a68a986a000000000000000000000000000000000000000000000000000000000000000047776766678867667799798698768866976685673755387638763b74358b387a00000000000000000000000000000000000000000000000000000000000000005777686569874944498846774677685568646987677778775a456a65ab66ca550000000000000000000000000000000000000000000000000000000000000000355656666656455546455345634558655854aa749854775577737b64777a7a7900000000000000000000000000000000000000000000000000000000000000005955895598546c758c75ba76b88797749b75a98967888789978857888788a78800000000000000000000000000000000000000000000000000000000000000006977897799776a748a749a747987ba97aa998ba8a78bab75a87ab89cbb74b97b000000000000000000000000000000000000000000000000000000000000000059645788598858546a7569996a767a766887649c767476797a54766977667976000000000000000000000000000000000000000000000000000000000000000078887a75796577777a869976987799865777667787668a53857a885a98659546000000000000000000000000000000000000000000000000000000000000000087559877a96586779788b9769866888899877576777879647759a8659888a7440000000000000000000000000000000000000000000000000000000000000000785477887a55747b7585795b7999a9667456878889aa58997888797b56776855000000000000000000000000000000000000000000000000000000000000000048545854617b644557448744537b85565899899a39994a7a58998999a5558988000000000000000000000000000000000000000000000000000000000000000089659744a6559555a55698889486a57aab43a96b9556a665a854a579a744a5550000000000000000000000000000000000000000000000000000000000000000596587556677777777778578876687778974867787668876988897779876a744000000000000000000000000000000000000000000000000000000000000000067536556875448225922415851595456654587459456947b48997a86764585560000000000000000000000000000000000000000000000000000000000000000a854a89989998556a7559766a7779976a975997596749a64968a9779a55595450000000000000000000000000000000000000000000000000000000000000000674487549854885594558445a777a7778373579b5a32675584456975958b9944000000000000000000000000000000000000000000000000000000000000000077449754b674b469b964b658a766a864a777a975a566a754a677a875b777b9650000000000000000000000000000000000000000000000000000000000000000775577547445755676558744697377637766785334556566577859755877887600000000000000000000000000000000000000000000000000000000000000002789287328772a7436793a9457795a84368a3334323364555a757b856aaa9a5500000000000000000000000000000000000000000000000000000000000000005888516b5a3349a95964797778987a5375696a536668796577887a847a74797500000000000000000000000000000000000000000000000000000000000000007b537a53767b6769748775767a9a7988759c768a7b957a847775776478647854000000000000000000000000000000000000000000000000000000000000000098999788988998889b879a869a869a86696565676965667767446854677877880000000000000000000000000000000000000000000000000000000000000000678a77997ba647887a7589999ca59ba889aa9999655667bd6ba979a967bc6c7300000000000000000000000000000000000000000000000000000000000000006aaa6556518566775965485438985888576546854ca547775999699989997a9900000000000000000000000000000000000000000000000000000000000000006678526466335644769c5a7888547a785c4454a658885c946285627b6c54674a000000000000000000000000000000000000000000000000000000000000000033843b33359c337c395c3b853899355653745a33558b536b585b5a7557885445000000000000000000000000000000000000000000000000000000000000000026551566274525664a85486546564656377756664655465545454656516a656700000000000000000000000000000000000000000000000000000000000000004964696468553a86485437443645896588548856895477446a7569547a757954000000000000000000000000000000000000000000000000000000000000000036678566399988993b968b955ba658995566588859645a986ca7796477887ca6000000000000000000000000000000000000000000000000000000000000000019562a554c665c55156a256a468c557b1a8429744a845a83196b285a496b595b00000000000000000000000000000000000000000000000000000000000000001486248645a7549615782578469a5689187629764a875a861a692a694b7a5b79000000000000000000000000000000000000000000000000000000000000000017772777489858881555255546665556199528854884588411122112411251120000000000000000000000000000000000000000000000000000000000000000" } } - ], "enums": [], "externalEnums": [], "levelFields": [] }, + ], "enums": [{ "identifier": "Tile_flag", "uid": 8, "values": [{ "id": "Collide", "tileRect": null, "tileId": -1, "color": 12470831, "__tileSrcRect": null }], "iconTilesetUid": null, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }], "externalEnums": [], "levelFields": [] }, "levels": [ { "identifier": "Level_0", @@ -185,7 +124,7 @@ "worldY": 0, "worldDepth": 0, "pxWid": 320, - "pxHei": 176, + "pxHei": 240, "__bgColor": "#696A79", "bgColor": null, "useAutoIdentifier": true, @@ -199,41 +138,16 @@ "fieldInstances": [], "layerInstances": [ { - "__identifier": "Entities", - "__type": "Entities", - "__cWid": 20, - "__cHei": 11, - "__gridSize": 16, - "__opacity": 1, - "__pxTotalOffsetX": 0, - "__pxTotalOffsetY": 0, - "__tilesetDefUid": null, - "__tilesetRelPath": null, - "iid": "bb797bd0-1460-11ee-9744-6b9bac7727f9", - "levelId": 0, - "layerDefUid": 6, - "pxOffsetX": 0, - "pxOffsetY": 0, - "visible": true, - "optionalRules": [], - "intGridCsv": [], - "autoLayerTiles": [], - "seed": 5582018, - "overrideTilesetUid": null, - "gridTiles": [], - "entityInstances": [] - }, - { - "__identifier": "World", + "__identifier": "Base", "__type": "Tiles", "__cWid": 20, - "__cHei": 11, + "__cHei": 15, "__gridSize": 16, "__opacity": 1, "__pxTotalOffsetX": 0, "__pxTotalOffsetY": 0, "__tilesetDefUid": 1, - "__tilesetRelPath": "world_sprites.aseprite", + "__tilesetRelPath": "overworld.aseprite", "iid": "5f6f1940-1460-11ee-be24-a5eacbfb9439", "levelId": 0, "layerDefUid": 2, @@ -247,225 +161,305 @@ "overrideTilesetUid": null, "gridTiles": [ { "px": [0,0], "src": [16,0], "f": 0, "t": 1, "d": [0], "a": 1 }, - { "px": [16,0], "src": [16,0], "f": 0, "t": 1, "d": [1], "a": 1 }, + { "px": [16,0], "src": [64,0], "f": 0, "t": 4, "d": [1], "a": 1 }, { "px": [32,0], "src": [16,0], "f": 0, "t": 1, "d": [2], "a": 1 }, - { "px": [48,0], "src": [0,16], "f": 0, "t": 2, "d": [3], "a": 1 }, - { "px": [64,0], "src": [16,16], "f": 0, "t": 3, "d": [4], "a": 1 }, - { "px": [80,0], "src": [16,16], "f": 0, "t": 3, "d": [5], "a": 1 }, - { "px": [96,0], "src": [16,16], "f": 0, "t": 3, "d": [6], "a": 1 }, - { "px": [112,0], "src": [0,0], "f": 0, "t": 0, "d": [7], "a": 1 }, - { "px": [128,0], "src": [16,0], "f": 0, "t": 1, "d": [8], "a": 1 }, - { "px": [144,0], "src": [0,0], "f": 0, "t": 0, "d": [9], "a": 1 }, + { "px": [48,0], "src": [16,0], "f": 0, "t": 1, "d": [3], "a": 1 }, + { "px": [64,0], "src": [48,0], "f": 0, "t": 3, "d": [4], "a": 1 }, + { "px": [80,0], "src": [48,0], "f": 0, "t": 3, "d": [5], "a": 1 }, + { "px": [96,0], "src": [48,0], "f": 0, "t": 3, "d": [6], "a": 1 }, + { "px": [112,0], "src": [48,0], "f": 0, "t": 3, "d": [7], "a": 1 }, + { "px": [128,0], "src": [64,0], "f": 0, "t": 4, "d": [8], "a": 1 }, + { "px": [144,0], "src": [16,0], "f": 0, "t": 1, "d": [9], "a": 1 }, { "px": [160,0], "src": [16,0], "f": 0, "t": 1, "d": [10], "a": 1 }, - { "px": [176,0], "src": [0,16], "f": 0, "t": 2, "d": [11], "a": 1 }, + { "px": [176,0], "src": [32,0], "f": 0, "t": 2, "d": [11], "a": 1 }, { "px": [192,0], "src": [16,0], "f": 0, "t": 1, "d": [12], "a": 1 }, { "px": [208,0], "src": [0,0], "f": 0, "t": 0, "d": [13], "a": 1 }, - { "px": [224,0], "src": [0,16], "f": 0, "t": 2, "d": [14], "a": 1 }, - { "px": [240,0], "src": [16,16], "f": 0, "t": 3, "d": [15], "a": 1 }, - { "px": [256,0], "src": [16,16], "f": 0, "t": 3, "d": [16], "a": 1 }, - { "px": [272,0], "src": [16,16], "f": 0, "t": 3, "d": [17], "a": 1 }, + { "px": [224,0], "src": [48,0], "f": 0, "t": 3, "d": [14], "a": 1 }, + { "px": [240,0], "src": [16,0], "f": 0, "t": 1, "d": [15], "a": 1 }, + { "px": [256,0], "src": [80,0], "f": 0, "t": 5, "d": [16], "a": 1 }, + { "px": [272,0], "src": [16,0], "f": 0, "t": 1, "d": [17], "a": 1 }, { "px": [288,0], "src": [0,0], "f": 0, "t": 0, "d": [18], "a": 1 }, { "px": [304,0], "src": [0,0], "f": 0, "t": 0, "d": [19], "a": 1 }, - { "px": [0,16], "src": [16,0], "f": 0, "t": 1, "d": [20], "a": 1 }, - { "px": [16,16], "src": [0,16], "f": 0, "t": 2, "d": [21], "a": 1 }, - { "px": [32,16], "src": [16,0], "f": 0, "t": 1, "d": [22], "a": 1 }, - { "px": [48,16], "src": [16,0], "f": 0, "t": 1, "d": [23], "a": 1 }, - { "px": [64,16], "src": [16,0], "f": 0, "t": 1, "d": [24], "a": 1 }, - { "px": [80,16], "src": [0,16], "f": 0, "t": 2, "d": [25], "a": 1 }, - { "px": [96,16], "src": [16,0], "f": 0, "t": 1, "d": [26], "a": 1 }, - { "px": [112,16], "src": [0,16], "f": 0, "t": 2, "d": [27], "a": 1 }, - { "px": [128,16], "src": [16,0], "f": 0, "t": 1, "d": [28], "a": 1 }, - { "px": [144,16], "src": [16,16], "f": 0, "t": 3, "d": [29], "a": 1 }, - { "px": [160,16], "src": [0,0], "f": 0, "t": 0, "d": [30], "a": 1 }, - { "px": [176,16], "src": [16,0], "f": 0, "t": 1, "d": [31], "a": 1 }, + { "px": [0,16], "src": [48,0], "f": 0, "t": 3, "d": [20], "a": 1 }, + { "px": [16,16], "src": [32,0], "f": 0, "t": 2, "d": [21], "a": 1 }, + { "px": [32,16], "src": [48,0], "f": 0, "t": 3, "d": [22], "a": 1 }, + { "px": [48,16], "src": [64,0], "f": 0, "t": 4, "d": [23], "a": 1 }, + { "px": [64,16], "src": [64,0], "f": 0, "t": 4, "d": [24], "a": 1 }, + { "px": [80,16], "src": [16,0], "f": 0, "t": 1, "d": [25], "a": 1 }, + { "px": [96,16], "src": [64,0], "f": 0, "t": 4, "d": [26], "a": 1 }, + { "px": [112,16], "src": [64,0], "f": 0, "t": 4, "d": [27], "a": 1 }, + { "px": [128,16], "src": [64,0], "f": 0, "t": 4, "d": [28], "a": 1 }, + { "px": [144,16], "src": [48,0], "f": 0, "t": 3, "d": [29], "a": 1 }, + { "px": [160,16], "src": [48,0], "f": 0, "t": 3, "d": [30], "a": 1 }, + { "px": [176,16], "src": [48,0], "f": 0, "t": 3, "d": [31], "a": 1 }, { "px": [192,16], "src": [0,0], "f": 0, "t": 0, "d": [32], "a": 1 }, - { "px": [208,16], "src": [0,16], "f": 0, "t": 2, "d": [33], "a": 1 }, - { "px": [224,16], "src": [16,0], "f": 0, "t": 1, "d": [34], "a": 1 }, - { "px": [240,16], "src": [0,0], "f": 0, "t": 0, "d": [35], "a": 1 }, - { "px": [256,16], "src": [16,0], "f": 0, "t": 1, "d": [36], "a": 1 }, - { "px": [272,16], "src": [0,16], "f": 0, "t": 2, "d": [37], "a": 1 }, - { "px": [288,16], "src": [0,0], "f": 0, "t": 0, "d": [38], "a": 1 }, - { "px": [304,16], "src": [0,16], "f": 0, "t": 2, "d": [39], "a": 1 }, - { "px": [0,32], "src": [0,0], "f": 0, "t": 0, "d": [40], "a": 1 }, - { "px": [16,32], "src": [16,16], "f": 0, "t": 3, "d": [41], "a": 1 }, - { "px": [32,32], "src": [0,16], "f": 0, "t": 2, "d": [42], "a": 1 }, - { "px": [48,32], "src": [16,0], "f": 0, "t": 1, "d": [43], "a": 1 }, - { "px": [64,32], "src": [0,0], "f": 0, "t": 0, "d": [44], "a": 1 }, - { "px": [80,32], "src": [0,0], "f": 0, "t": 0, "d": [45], "a": 1 }, - { "px": [96,32], "src": [0,0], "f": 0, "t": 0, "d": [46], "a": 1 }, - { "px": [112,32], "src": [0,0], "f": 0, "t": 0, "d": [47], "a": 1 }, - { "px": [128,32], "src": [0,16], "f": 0, "t": 2, "d": [48], "a": 1 }, - { "px": [144,32], "src": [16,0], "f": 0, "t": 1, "d": [49], "a": 1 }, - { "px": [160,32], "src": [16,0], "f": 0, "t": 1, "d": [50], "a": 1 }, - { "px": [176,32], "src": [0,0], "f": 0, "t": 0, "d": [51], "a": 1 }, + { "px": [208,16], "src": [0,0], "f": 0, "t": 0, "d": [33], "a": 1 }, + { "px": [224,16], "src": [32,0], "f": 0, "t": 2, "d": [34], "a": 1 }, + { "px": [240,16], "src": [48,0], "f": 0, "t": 3, "d": [35], "a": 1 }, + { "px": [256,16], "src": [0,0], "f": 0, "t": 0, "d": [36], "a": 1 }, + { "px": [272,16], "src": [64,0], "f": 0, "t": 4, "d": [37], "a": 1 }, + { "px": [288,16], "src": [64,0], "f": 0, "t": 4, "d": [38], "a": 1 }, + { "px": [304,16], "src": [32,0], "f": 0, "t": 2, "d": [39], "a": 1 }, + { "px": [0,32], "src": [48,0], "f": 0, "t": 3, "d": [40], "a": 1 }, + { "px": [16,32], "src": [0,0], "f": 0, "t": 0, "d": [41], "a": 1 }, + { "px": [32,32], "src": [80,0], "f": 0, "t": 5, "d": [42], "a": 1 }, + { "px": [48,32], "src": [64,0], "f": 0, "t": 4, "d": [43], "a": 1 }, + { "px": [64,32], "src": [32,0], "f": 0, "t": 2, "d": [44], "a": 1 }, + { "px": [80,32], "src": [32,0], "f": 0, "t": 2, "d": [45], "a": 1 }, + { "px": [96,32], "src": [64,0], "f": 0, "t": 4, "d": [46], "a": 1 }, + { "px": [112,32], "src": [32,0], "f": 0, "t": 2, "d": [47], "a": 1 }, + { "px": [128,32], "src": [64,0], "f": 0, "t": 4, "d": [48], "a": 1 }, + { "px": [144,32], "src": [64,0], "f": 0, "t": 4, "d": [49], "a": 1 }, + { "px": [160,32], "src": [64,0], "f": 0, "t": 4, "d": [50], "a": 1 }, + { "px": [176,32], "src": [48,0], "f": 0, "t": 3, "d": [51], "a": 1 }, { "px": [192,32], "src": [16,0], "f": 0, "t": 1, "d": [52], "a": 1 }, - { "px": [208,32], "src": [0,16], "f": 0, "t": 2, "d": [53], "a": 1 }, - { "px": [224,32], "src": [16,16], "f": 0, "t": 3, "d": [54], "a": 1 }, - { "px": [240,32], "src": [16,0], "f": 0, "t": 1, "d": [55], "a": 1 }, - { "px": [256,32], "src": [16,16], "f": 0, "t": 3, "d": [56], "a": 1 }, - { "px": [272,32], "src": [0,16], "f": 0, "t": 2, "d": [57], "a": 1 }, - { "px": [288,32], "src": [16,16], "f": 0, "t": 3, "d": [58], "a": 1 }, - { "px": [304,32], "src": [0,0], "f": 0, "t": 0, "d": [59], "a": 1 }, - { "px": [0,48], "src": [0,16], "f": 0, "t": 2, "d": [60], "a": 1 }, - { "px": [16,48], "src": [0,0], "f": 0, "t": 0, "d": [61], "a": 1 }, - { "px": [32,48], "src": [16,0], "f": 0, "t": 1, "d": [62], "a": 1 }, - { "px": [48,48], "src": [16,0], "f": 0, "t": 1, "d": [63], "a": 1 }, - { "px": [64,48], "src": [0,0], "f": 0, "t": 0, "d": [64], "a": 1 }, - { "px": [80,48], "src": [0,0], "f": 0, "t": 0, "d": [65], "a": 1 }, - { "px": [96,48], "src": [0,0], "f": 0, "t": 0, "d": [66], "a": 1 }, - { "px": [112,48], "src": [16,0], "f": 0, "t": 1, "d": [67], "a": 1 }, - { "px": [128,48], "src": [16,0], "f": 0, "t": 1, "d": [68], "a": 1 }, - { "px": [144,48], "src": [0,0], "f": 0, "t": 0, "d": [69], "a": 1 }, - { "px": [160,48], "src": [0,16], "f": 0, "t": 2, "d": [70], "a": 1 }, + { "px": [208,32], "src": [32,0], "f": 0, "t": 2, "d": [53], "a": 1 }, + { "px": [224,32], "src": [64,0], "f": 0, "t": 4, "d": [54], "a": 1 }, + { "px": [240,32], "src": [0,0], "f": 0, "t": 0, "d": [55], "a": 1 }, + { "px": [256,32], "src": [32,0], "f": 0, "t": 2, "d": [56], "a": 1 }, + { "px": [272,32], "src": [16,0], "f": 0, "t": 1, "d": [57], "a": 1 }, + { "px": [288,32], "src": [80,0], "f": 0, "t": 5, "d": [58], "a": 1 }, + { "px": [304,32], "src": [48,0], "f": 0, "t": 3, "d": [59], "a": 1 }, + { "px": [0,48], "src": [48,0], "f": 0, "t": 3, "d": [60], "a": 1 }, + { "px": [16,48], "src": [16,0], "f": 0, "t": 1, "d": [61], "a": 1 }, + { "px": [32,48], "src": [48,0], "f": 0, "t": 3, "d": [62], "a": 1 }, + { "px": [48,48], "src": [80,0], "f": 0, "t": 5, "d": [63], "a": 1 }, + { "px": [64,48], "src": [80,0], "f": 0, "t": 5, "d": [64], "a": 1 }, + { "px": [80,48], "src": [48,0], "f": 0, "t": 3, "d": [65], "a": 1 }, + { "px": [96,48], "src": [48,0], "f": 0, "t": 3, "d": [66], "a": 1 }, + { "px": [112,48], "src": [64,0], "f": 0, "t": 4, "d": [67], "a": 1 }, + { "px": [128,48], "src": [32,0], "f": 0, "t": 2, "d": [68], "a": 1 }, + { "px": [144,48], "src": [80,0], "f": 0, "t": 5, "d": [69], "a": 1 }, + { "px": [160,48], "src": [16,0], "f": 0, "t": 1, "d": [70], "a": 1 }, { "px": [176,48], "src": [0,0], "f": 0, "t": 0, "d": [71], "a": 1 }, - { "px": [192,48], "src": [0,0], "f": 0, "t": 0, "d": [72], "a": 1 }, - { "px": [208,48], "src": [16,16], "f": 0, "t": 3, "d": [73], "a": 1 }, + { "px": [192,48], "src": [48,0], "f": 0, "t": 3, "d": [72], "a": 1 }, + { "px": [208,48], "src": [80,0], "f": 0, "t": 5, "d": [73], "a": 1 }, { "px": [224,48], "src": [0,0], "f": 0, "t": 0, "d": [74], "a": 1 }, - { "px": [240,48], "src": [16,16], "f": 0, "t": 3, "d": [75], "a": 1 }, - { "px": [256,48], "src": [16,0], "f": 0, "t": 1, "d": [76], "a": 1 }, + { "px": [240,48], "src": [0,0], "f": 0, "t": 0, "d": [75], "a": 1 }, + { "px": [256,48], "src": [80,0], "f": 0, "t": 5, "d": [76], "a": 1 }, { "px": [272,48], "src": [0,0], "f": 0, "t": 0, "d": [77], "a": 1 }, - { "px": [288,48], "src": [0,0], "f": 0, "t": 0, "d": [78], "a": 1 }, - { "px": [304,48], "src": [16,16], "f": 0, "t": 3, "d": [79], "a": 1 }, - { "px": [0,64], "src": [16,0], "f": 0, "t": 1, "d": [80], "a": 1 }, - { "px": [16,64], "src": [16,0], "f": 0, "t": 1, "d": [81], "a": 1 }, - { "px": [32,64], "src": [0,0], "f": 0, "t": 0, "d": [82], "a": 1 }, - { "px": [48,64], "src": [0,16], "f": 0, "t": 2, "d": [83], "a": 1 }, - { "px": [64,64], "src": [0,16], "f": 0, "t": 2, "d": [84], "a": 1 }, - { "px": [80,64], "src": [16,16], "f": 0, "t": 3, "d": [85], "a": 1 }, - { "px": [96,64], "src": [0,0], "f": 0, "t": 0, "d": [86], "a": 1 }, - { "px": [112,64], "src": [16,0], "f": 0, "t": 1, "d": [87], "a": 1 }, - { "px": [128,64], "src": [0,0], "f": 0, "t": 0, "d": [88], "a": 1 }, - { "px": [144,64], "src": [16,0], "f": 0, "t": 1, "d": [89], "a": 1 }, - { "px": [160,64], "src": [0,16], "f": 0, "t": 2, "d": [90], "a": 1 }, - { "px": [176,64], "src": [16,0], "f": 0, "t": 1, "d": [91], "a": 1 }, - { "px": [192,64], "src": [0,16], "f": 0, "t": 2, "d": [92], "a": 1 }, - { "px": [208,64], "src": [16,0], "f": 0, "t": 1, "d": [93], "a": 1 }, - { "px": [224,64], "src": [16,16], "f": 0, "t": 3, "d": [94], "a": 1 }, - { "px": [240,64], "src": [0,0], "f": 0, "t": 0, "d": [95], "a": 1 }, - { "px": [256,64], "src": [0,16], "f": 0, "t": 2, "d": [96], "a": 1 }, + { "px": [288,48], "src": [32,0], "f": 0, "t": 2, "d": [78], "a": 1 }, + { "px": [304,48], "src": [32,0], "f": 0, "t": 2, "d": [79], "a": 1 }, + { "px": [0,64], "src": [64,0], "f": 0, "t": 4, "d": [80], "a": 1 }, + { "px": [16,64], "src": [48,0], "f": 0, "t": 3, "d": [81], "a": 1 }, + { "px": [32,64], "src": [48,0], "f": 0, "t": 3, "d": [82], "a": 1 }, + { "px": [48,64], "src": [0,0], "f": 0, "t": 0, "d": [83], "a": 1 }, + { "px": [64,64], "src": [48,0], "f": 0, "t": 3, "d": [84], "a": 1 }, + { "px": [80,64], "src": [64,0], "f": 0, "t": 4, "d": [85], "a": 1 }, + { "px": [96,64], "src": [48,0], "f": 0, "t": 3, "d": [86], "a": 1 }, + { "px": [112,64], "src": [80,0], "f": 0, "t": 5, "d": [87], "a": 1 }, + { "px": [128,64], "src": [16,0], "f": 0, "t": 1, "d": [88], "a": 1 }, + { "px": [144,64], "src": [48,0], "f": 0, "t": 3, "d": [89], "a": 1 }, + { "px": [160,64], "src": [0,0], "f": 0, "t": 0, "d": [90], "a": 1 }, + { "px": [176,64], "src": [64,0], "f": 0, "t": 4, "d": [91], "a": 1 }, + { "px": [192,64], "src": [80,0], "f": 0, "t": 5, "d": [92], "a": 1 }, + { "px": [208,64], "src": [0,0], "f": 0, "t": 0, "d": [93], "a": 1 }, + { "px": [224,64], "src": [16,0], "f": 0, "t": 1, "d": [94], "a": 1 }, + { "px": [240,64], "src": [48,0], "f": 0, "t": 3, "d": [95], "a": 1 }, + { "px": [256,64], "src": [64,0], "f": 0, "t": 4, "d": [96], "a": 1 }, { "px": [272,64], "src": [0,0], "f": 0, "t": 0, "d": [97], "a": 1 }, { "px": [288,64], "src": [0,0], "f": 0, "t": 0, "d": [98], "a": 1 }, - { "px": [304,64], "src": [0,0], "f": 0, "t": 0, "d": [99], "a": 1 }, - { "px": [0,80], "src": [0,0], "f": 0, "t": 0, "d": [100], "a": 1 }, - { "px": [16,80], "src": [16,0], "f": 0, "t": 1, "d": [101], "a": 1 }, - { "px": [32,80], "src": [0,0], "f": 0, "t": 0, "d": [102], "a": 1 }, - { "px": [48,80], "src": [16,0], "f": 0, "t": 1, "d": [103], "a": 1 }, - { "px": [64,80], "src": [0,0], "f": 0, "t": 0, "d": [104], "a": 1 }, - { "px": [80,80], "src": [16,0], "f": 0, "t": 1, "d": [105], "a": 1 }, - { "px": [96,80], "src": [0,0], "f": 0, "t": 0, "d": [106], "a": 1 }, - { "px": [112,80], "src": [16,16], "f": 0, "t": 3, "d": [107], "a": 1 }, - { "px": [128,80], "src": [0,0], "f": 0, "t": 0, "d": [108], "a": 1 }, - { "px": [144,80], "src": [16,0], "f": 0, "t": 1, "d": [109], "a": 1 }, - { "px": [160,80], "src": [0,16], "f": 0, "t": 2, "d": [110], "a": 1 }, - { "px": [176,80], "src": [16,0], "f": 0, "t": 1, "d": [111], "a": 1 }, - { "px": [192,80], "src": [0,0], "f": 0, "t": 0, "d": [112], "a": 1 }, - { "px": [208,80], "src": [0,16], "f": 0, "t": 2, "d": [113], "a": 1 }, - { "px": [224,80], "src": [0,0], "f": 0, "t": 0, "d": [114], "a": 1 }, - { "px": [240,80], "src": [0,0], "f": 0, "t": 0, "d": [115], "a": 1 }, - { "px": [256,80], "src": [16,0], "f": 0, "t": 1, "d": [116], "a": 1 }, - { "px": [272,80], "src": [16,0], "f": 0, "t": 1, "d": [117], "a": 1 }, - { "px": [288,80], "src": [16,16], "f": 0, "t": 3, "d": [118], "a": 1 }, - { "px": [304,80], "src": [0,0], "f": 0, "t": 0, "d": [119], "a": 1 }, - { "px": [0,96], "src": [16,0], "f": 0, "t": 1, "d": [120], "a": 1 }, - { "px": [16,96], "src": [16,16], "f": 0, "t": 3, "d": [121], "a": 1 }, - { "px": [32,96], "src": [16,16], "f": 0, "t": 3, "d": [122], "a": 1 }, - { "px": [48,96], "src": [16,0], "f": 0, "t": 1, "d": [123], "a": 1 }, - { "px": [64,96], "src": [0,16], "f": 0, "t": 2, "d": [124], "a": 1 }, - { "px": [80,96], "src": [0,0], "f": 0, "t": 0, "d": [125], "a": 1 }, - { "px": [96,96], "src": [0,16], "f": 0, "t": 2, "d": [126], "a": 1 }, - { "px": [112,96], "src": [0,16], "f": 0, "t": 2, "d": [127], "a": 1 }, - { "px": [128,96], "src": [16,0], "f": 0, "t": 1, "d": [128], "a": 1 }, - { "px": [144,96], "src": [16,16], "f": 0, "t": 3, "d": [129], "a": 1 }, - { "px": [160,96], "src": [16,16], "f": 0, "t": 3, "d": [130], "a": 1 }, - { "px": [176,96], "src": [16,16], "f": 0, "t": 3, "d": [131], "a": 1 }, - { "px": [192,96], "src": [0,0], "f": 0, "t": 0, "d": [132], "a": 1 }, - { "px": [208,96], "src": [16,0], "f": 0, "t": 1, "d": [133], "a": 1 }, - { "px": [224,96], "src": [0,16], "f": 0, "t": 2, "d": [134], "a": 1 }, - { "px": [240,96], "src": [16,16], "f": 0, "t": 3, "d": [135], "a": 1 }, - { "px": [256,96], "src": [16,0], "f": 0, "t": 1, "d": [136], "a": 1 }, - { "px": [272,96], "src": [0,0], "f": 0, "t": 0, "d": [137], "a": 1 }, - { "px": [288,96], "src": [16,16], "f": 0, "t": 3, "d": [138], "a": 1 }, - { "px": [304,96], "src": [16,16], "f": 0, "t": 3, "d": [139], "a": 1 }, - { "px": [0,112], "src": [16,16], "f": 0, "t": 3, "d": [140], "a": 1 }, + { "px": [304,64], "src": [32,0], "f": 0, "t": 2, "d": [99], "a": 1 }, + { "px": [0,80], "src": [48,0], "f": 0, "t": 3, "d": [100], "a": 1 }, + { "px": [16,80], "src": [64,0], "f": 0, "t": 4, "d": [101], "a": 1 }, + { "px": [32,80], "src": [80,0], "f": 0, "t": 5, "d": [102], "a": 1 }, + { "px": [48,80], "src": [64,0], "f": 0, "t": 4, "d": [103], "a": 1 }, + { "px": [64,80], "src": [48,0], "f": 0, "t": 3, "d": [104], "a": 1 }, + { "px": [80,80], "src": [0,0], "f": 0, "t": 0, "d": [105], "a": 1 }, + { "px": [96,80], "src": [48,0], "f": 0, "t": 3, "d": [106], "a": 1 }, + { "px": [112,80], "src": [64,0], "f": 0, "t": 4, "d": [107], "a": 1 }, + { "px": [128,80], "src": [80,0], "f": 0, "t": 5, "d": [108], "a": 1 }, + { "px": [144,80], "src": [80,0], "f": 0, "t": 5, "d": [109], "a": 1 }, + { "px": [160,80], "src": [64,0], "f": 0, "t": 4, "d": [110], "a": 1 }, + { "px": [176,80], "src": [64,0], "f": 0, "t": 4, "d": [111], "a": 1 }, + { "px": [192,80], "src": [64,0], "f": 0, "t": 4, "d": [112], "a": 1 }, + { "px": [208,80], "src": [48,0], "f": 0, "t": 3, "d": [113], "a": 1 }, + { "px": [224,80], "src": [48,0], "f": 0, "t": 3, "d": [114], "a": 1 }, + { "px": [240,80], "src": [16,0], "f": 0, "t": 1, "d": [115], "a": 1 }, + { "px": [256,80], "src": [48,0], "f": 0, "t": 3, "d": [116], "a": 1 }, + { "px": [272,80], "src": [64,0], "f": 0, "t": 4, "d": [117], "a": 1 }, + { "px": [288,80], "src": [80,0], "f": 0, "t": 5, "d": [118], "a": 1 }, + { "px": [304,80], "src": [64,0], "f": 0, "t": 4, "d": [119], "a": 1 }, + { "px": [0,96], "src": [64,0], "f": 0, "t": 4, "d": [120], "a": 1 }, + { "px": [16,96], "src": [48,0], "f": 0, "t": 3, "d": [121], "a": 1 }, + { "px": [32,96], "src": [64,0], "f": 0, "t": 4, "d": [122], "a": 1 }, + { "px": [48,96], "src": [48,0], "f": 0, "t": 3, "d": [123], "a": 1 }, + { "px": [64,96], "src": [0,0], "f": 0, "t": 0, "d": [124], "a": 1 }, + { "px": [80,96], "src": [80,0], "f": 0, "t": 5, "d": [125], "a": 1 }, + { "px": [96,96], "src": [64,0], "f": 0, "t": 4, "d": [126], "a": 1 }, + { "px": [112,96], "src": [0,0], "f": 0, "t": 0, "d": [127], "a": 1 }, + { "px": [128,96], "src": [64,0], "f": 0, "t": 4, "d": [128], "a": 1 }, + { "px": [144,96], "src": [80,0], "f": 0, "t": 5, "d": [129], "a": 1 }, + { "px": [160,96], "src": [48,0], "f": 0, "t": 3, "d": [130], "a": 1 }, + { "px": [176,96], "src": [48,0], "f": 0, "t": 3, "d": [131], "a": 1 }, + { "px": [192,96], "src": [80,0], "f": 0, "t": 5, "d": [132], "a": 1 }, + { "px": [208,96], "src": [32,0], "f": 0, "t": 2, "d": [133], "a": 1 }, + { "px": [224,96], "src": [16,0], "f": 0, "t": 1, "d": [134], "a": 1 }, + { "px": [240,96], "src": [48,0], "f": 0, "t": 3, "d": [135], "a": 1 }, + { "px": [256,96], "src": [0,0], "f": 0, "t": 0, "d": [136], "a": 1 }, + { "px": [272,96], "src": [48,0], "f": 0, "t": 3, "d": [137], "a": 1 }, + { "px": [288,96], "src": [48,0], "f": 0, "t": 3, "d": [138], "a": 1 }, + { "px": [304,96], "src": [80,0], "f": 0, "t": 5, "d": [139], "a": 1 }, + { "px": [0,112], "src": [48,0], "f": 0, "t": 3, "d": [140], "a": 1 }, { "px": [16,112], "src": [16,0], "f": 0, "t": 1, "d": [141], "a": 1 }, - { "px": [32,112], "src": [0,0], "f": 0, "t": 0, "d": [142], "a": 1 }, - { "px": [48,112], "src": [16,0], "f": 0, "t": 1, "d": [143], "a": 1 }, - { "px": [64,112], "src": [0,0], "f": 0, "t": 0, "d": [144], "a": 1 }, - { "px": [80,112], "src": [0,0], "f": 0, "t": 0, "d": [145], "a": 1 }, - { "px": [96,112], "src": [0,16], "f": 0, "t": 2, "d": [146], "a": 1 }, - { "px": [112,112], "src": [16,0], "f": 0, "t": 1, "d": [147], "a": 1 }, + { "px": [32,112], "src": [48,0], "f": 0, "t": 3, "d": [142], "a": 1 }, + { "px": [48,112], "src": [0,0], "f": 0, "t": 0, "d": [143], "a": 1 }, + { "px": [64,112], "src": [48,0], "f": 0, "t": 3, "d": [144], "a": 1 }, + { "px": [80,112], "src": [16,0], "f": 0, "t": 1, "d": [145], "a": 1 }, + { "px": [96,112], "src": [80,0], "f": 0, "t": 5, "d": [146], "a": 1 }, + { "px": [112,112], "src": [32,0], "f": 0, "t": 2, "d": [147], "a": 1 }, { "px": [128,112], "src": [0,0], "f": 0, "t": 0, "d": [148], "a": 1 }, - { "px": [144,112], "src": [16,0], "f": 0, "t": 1, "d": [149], "a": 1 }, - { "px": [160,112], "src": [16,16], "f": 0, "t": 3, "d": [150], "a": 1 }, - { "px": [176,112], "src": [16,16], "f": 0, "t": 3, "d": [151], "a": 1 }, - { "px": [192,112], "src": [0,0], "f": 0, "t": 0, "d": [152], "a": 1 }, - { "px": [208,112], "src": [16,16], "f": 0, "t": 3, "d": [153], "a": 1 }, - { "px": [224,112], "src": [0,0], "f": 0, "t": 0, "d": [154], "a": 1 }, - { "px": [240,112], "src": [16,0], "f": 0, "t": 1, "d": [155], "a": 1 }, - { "px": [256,112], "src": [0,16], "f": 0, "t": 2, "d": [156], "a": 1 }, - { "px": [272,112], "src": [0,0], "f": 0, "t": 0, "d": [157], "a": 1 }, - { "px": [288,112], "src": [16,16], "f": 0, "t": 3, "d": [158], "a": 1 }, - { "px": [304,112], "src": [16,0], "f": 0, "t": 1, "d": [159], "a": 1 }, - { "px": [0,128], "src": [16,0], "f": 0, "t": 1, "d": [160], "a": 1 }, - { "px": [16,128], "src": [0,16], "f": 0, "t": 2, "d": [161], "a": 1 }, - { "px": [32,128], "src": [16,16], "f": 0, "t": 3, "d": [162], "a": 1 }, - { "px": [48,128], "src": [16,16], "f": 0, "t": 3, "d": [163], "a": 1 }, + { "px": [144,112], "src": [32,0], "f": 0, "t": 2, "d": [149], "a": 1 }, + { "px": [160,112], "src": [64,0], "f": 0, "t": 4, "d": [150], "a": 1 }, + { "px": [176,112], "src": [48,0], "f": 0, "t": 3, "d": [151], "a": 1 }, + { "px": [192,112], "src": [16,0], "f": 0, "t": 1, "d": [152], "a": 1 }, + { "px": [208,112], "src": [64,0], "f": 0, "t": 4, "d": [153], "a": 1 }, + { "px": [224,112], "src": [16,0], "f": 0, "t": 1, "d": [154], "a": 1 }, + { "px": [240,112], "src": [80,0], "f": 0, "t": 5, "d": [155], "a": 1 }, + { "px": [256,112], "src": [80,0], "f": 0, "t": 5, "d": [156], "a": 1 }, + { "px": [272,112], "src": [16,0], "f": 0, "t": 1, "d": [157], "a": 1 }, + { "px": [288,112], "src": [48,0], "f": 0, "t": 3, "d": [158], "a": 1 }, + { "px": [304,112], "src": [48,0], "f": 0, "t": 3, "d": [159], "a": 1 }, + { "px": [0,128], "src": [48,0], "f": 0, "t": 3, "d": [160], "a": 1 }, + { "px": [16,128], "src": [48,0], "f": 0, "t": 3, "d": [161], "a": 1 }, + { "px": [32,128], "src": [0,0], "f": 0, "t": 0, "d": [162], "a": 1 }, + { "px": [48,128], "src": [64,0], "f": 0, "t": 4, "d": [163], "a": 1 }, { "px": [64,128], "src": [0,0], "f": 0, "t": 0, "d": [164], "a": 1 }, - { "px": [80,128], "src": [0,16], "f": 0, "t": 2, "d": [165], "a": 1 }, - { "px": [96,128], "src": [16,16], "f": 0, "t": 3, "d": [166], "a": 1 }, - { "px": [112,128], "src": [0,0], "f": 0, "t": 0, "d": [167], "a": 1 }, - { "px": [128,128], "src": [0,0], "f": 0, "t": 0, "d": [168], "a": 1 }, - { "px": [144,128], "src": [16,0], "f": 0, "t": 1, "d": [169], "a": 1 }, - { "px": [160,128], "src": [16,16], "f": 0, "t": 3, "d": [170], "a": 1 }, - { "px": [176,128], "src": [0,0], "f": 0, "t": 0, "d": [171], "a": 1 }, - { "px": [192,128], "src": [0,0], "f": 0, "t": 0, "d": [172], "a": 1 }, - { "px": [208,128], "src": [16,16], "f": 0, "t": 3, "d": [173], "a": 1 }, + { "px": [80,128], "src": [48,0], "f": 0, "t": 3, "d": [165], "a": 1 }, + { "px": [96,128], "src": [80,0], "f": 0, "t": 5, "d": [166], "a": 1 }, + { "px": [112,128], "src": [16,0], "f": 0, "t": 1, "d": [167], "a": 1 }, + { "px": [128,128], "src": [64,0], "f": 0, "t": 4, "d": [168], "a": 1 }, + { "px": [144,128], "src": [32,0], "f": 0, "t": 2, "d": [169], "a": 1 }, + { "px": [160,128], "src": [16,0], "f": 0, "t": 1, "d": [170], "a": 1 }, + { "px": [176,128], "src": [16,0], "f": 0, "t": 1, "d": [171], "a": 1 }, + { "px": [192,128], "src": [48,0], "f": 0, "t": 3, "d": [172], "a": 1 }, + { "px": [208,128], "src": [48,0], "f": 0, "t": 3, "d": [173], "a": 1 }, { "px": [224,128], "src": [16,0], "f": 0, "t": 1, "d": [174], "a": 1 }, - { "px": [240,128], "src": [16,16], "f": 0, "t": 3, "d": [175], "a": 1 }, - { "px": [256,128], "src": [0,16], "f": 0, "t": 2, "d": [176], "a": 1 }, - { "px": [272,128], "src": [16,16], "f": 0, "t": 3, "d": [177], "a": 1 }, - { "px": [288,128], "src": [0,16], "f": 0, "t": 2, "d": [178], "a": 1 }, - { "px": [304,128], "src": [0,0], "f": 0, "t": 0, "d": [179], "a": 1 }, - { "px": [0,144], "src": [0,0], "f": 0, "t": 0, "d": [180], "a": 1 }, - { "px": [16,144], "src": [0,0], "f": 0, "t": 0, "d": [181], "a": 1 }, - { "px": [32,144], "src": [16,0], "f": 0, "t": 1, "d": [182], "a": 1 }, - { "px": [48,144], "src": [0,0], "f": 0, "t": 0, "d": [183], "a": 1 }, - { "px": [64,144], "src": [0,16], "f": 0, "t": 2, "d": [184], "a": 1 }, - { "px": [80,144], "src": [16,16], "f": 0, "t": 3, "d": [185], "a": 1 }, - { "px": [96,144], "src": [16,16], "f": 0, "t": 3, "d": [186], "a": 1 }, - { "px": [112,144], "src": [0,0], "f": 0, "t": 0, "d": [187], "a": 1 }, - { "px": [128,144], "src": [0,16], "f": 0, "t": 2, "d": [188], "a": 1 }, - { "px": [144,144], "src": [16,16], "f": 0, "t": 3, "d": [189], "a": 1 }, - { "px": [160,144], "src": [0,16], "f": 0, "t": 2, "d": [190], "a": 1 }, - { "px": [176,144], "src": [0,16], "f": 0, "t": 2, "d": [191], "a": 1 }, - { "px": [192,144], "src": [0,16], "f": 0, "t": 2, "d": [192], "a": 1 }, - { "px": [208,144], "src": [0,16], "f": 0, "t": 2, "d": [193], "a": 1 }, - { "px": [224,144], "src": [16,16], "f": 0, "t": 3, "d": [194], "a": 1 }, - { "px": [240,144], "src": [0,0], "f": 0, "t": 0, "d": [195], "a": 1 }, - { "px": [256,144], "src": [16,16], "f": 0, "t": 3, "d": [196], "a": 1 }, - { "px": [272,144], "src": [16,16], "f": 0, "t": 3, "d": [197], "a": 1 }, - { "px": [288,144], "src": [0,0], "f": 0, "t": 0, "d": [198], "a": 1 }, - { "px": [304,144], "src": [16,0], "f": 0, "t": 1, "d": [199], "a": 1 }, - { "px": [0,160], "src": [0,16], "f": 0, "t": 2, "d": [200], "a": 1 }, - { "px": [16,160], "src": [16,16], "f": 0, "t": 3, "d": [201], "a": 1 }, - { "px": [32,160], "src": [16,0], "f": 0, "t": 1, "d": [202], "a": 1 }, - { "px": [48,160], "src": [16,0], "f": 0, "t": 1, "d": [203], "a": 1 }, - { "px": [64,160], "src": [0,0], "f": 0, "t": 0, "d": [204], "a": 1 }, - { "px": [80,160], "src": [0,0], "f": 0, "t": 0, "d": [205], "a": 1 }, - { "px": [96,160], "src": [16,16], "f": 0, "t": 3, "d": [206], "a": 1 }, - { "px": [112,160], "src": [0,16], "f": 0, "t": 2, "d": [207], "a": 1 }, - { "px": [128,160], "src": [16,16], "f": 0, "t": 3, "d": [208], "a": 1 }, + { "px": [240,128], "src": [48,0], "f": 0, "t": 3, "d": [175], "a": 1 }, + { "px": [256,128], "src": [80,0], "f": 0, "t": 5, "d": [176], "a": 1 }, + { "px": [272,128], "src": [64,0], "f": 0, "t": 4, "d": [177], "a": 1 }, + { "px": [288,128], "src": [80,0], "f": 0, "t": 5, "d": [178], "a": 1 }, + { "px": [304,128], "src": [32,0], "f": 0, "t": 2, "d": [179], "a": 1 }, + { "px": [0,144], "src": [16,0], "f": 0, "t": 1, "d": [180], "a": 1 }, + { "px": [16,144], "src": [80,0], "f": 0, "t": 5, "d": [181], "a": 1 }, + { "px": [32,144], "src": [48,0], "f": 0, "t": 3, "d": [182], "a": 1 }, + { "px": [48,144], "src": [16,0], "f": 0, "t": 1, "d": [183], "a": 1 }, + { "px": [64,144], "src": [64,0], "f": 0, "t": 4, "d": [184], "a": 1 }, + { "px": [80,144], "src": [32,0], "f": 0, "t": 2, "d": [185], "a": 1 }, + { "px": [96,144], "src": [48,0], "f": 0, "t": 3, "d": [186], "a": 1 }, + { "px": [112,144], "src": [32,0], "f": 0, "t": 2, "d": [187], "a": 1 }, + { "px": [128,144], "src": [64,0], "f": 0, "t": 4, "d": [188], "a": 1 }, + { "px": [144,144], "src": [32,0], "f": 0, "t": 2, "d": [189], "a": 1 }, + { "px": [160,144], "src": [48,0], "f": 0, "t": 3, "d": [190], "a": 1 }, + { "px": [176,144], "src": [16,0], "f": 0, "t": 1, "d": [191], "a": 1 }, + { "px": [192,144], "src": [0,0], "f": 0, "t": 0, "d": [192], "a": 1 }, + { "px": [208,144], "src": [0,0], "f": 0, "t": 0, "d": [193], "a": 1 }, + { "px": [224,144], "src": [64,0], "f": 0, "t": 4, "d": [194], "a": 1 }, + { "px": [240,144], "src": [32,0], "f": 0, "t": 2, "d": [195], "a": 1 }, + { "px": [256,144], "src": [32,0], "f": 0, "t": 2, "d": [196], "a": 1 }, + { "px": [272,144], "src": [0,0], "f": 0, "t": 0, "d": [197], "a": 1 }, + { "px": [288,144], "src": [64,0], "f": 0, "t": 4, "d": [198], "a": 1 }, + { "px": [304,144], "src": [64,0], "f": 0, "t": 4, "d": [199], "a": 1 }, + { "px": [0,160], "src": [32,0], "f": 0, "t": 2, "d": [200], "a": 1 }, + { "px": [16,160], "src": [0,0], "f": 0, "t": 0, "d": [201], "a": 1 }, + { "px": [32,160], "src": [80,0], "f": 0, "t": 5, "d": [202], "a": 1 }, + { "px": [48,160], "src": [64,0], "f": 0, "t": 4, "d": [203], "a": 1 }, + { "px": [64,160], "src": [32,0], "f": 0, "t": 2, "d": [204], "a": 1 }, + { "px": [80,160], "src": [64,0], "f": 0, "t": 4, "d": [205], "a": 1 }, + { "px": [96,160], "src": [0,0], "f": 0, "t": 0, "d": [206], "a": 1 }, + { "px": [112,160], "src": [0,0], "f": 0, "t": 0, "d": [207], "a": 1 }, + { "px": [128,160], "src": [48,0], "f": 0, "t": 3, "d": [208], "a": 1 }, { "px": [144,160], "src": [0,0], "f": 0, "t": 0, "d": [209], "a": 1 }, - { "px": [160,160], "src": [0,16], "f": 0, "t": 2, "d": [210], "a": 1 }, - { "px": [176,160], "src": [0,0], "f": 0, "t": 0, "d": [211], "a": 1 }, - { "px": [192,160], "src": [0,0], "f": 0, "t": 0, "d": [212], "a": 1 }, - { "px": [208,160], "src": [16,0], "f": 0, "t": 1, "d": [213], "a": 1 }, - { "px": [224,160], "src": [0,16], "f": 0, "t": 2, "d": [214], "a": 1 }, - { "px": [240,160], "src": [16,16], "f": 0, "t": 3, "d": [215], "a": 1 }, + { "px": [160,160], "src": [48,0], "f": 0, "t": 3, "d": [210], "a": 1 }, + { "px": [176,160], "src": [16,0], "f": 0, "t": 1, "d": [211], "a": 1 }, + { "px": [192,160], "src": [80,0], "f": 0, "t": 5, "d": [212], "a": 1 }, + { "px": [208,160], "src": [64,0], "f": 0, "t": 4, "d": [213], "a": 1 }, + { "px": [224,160], "src": [16,0], "f": 0, "t": 1, "d": [214], "a": 1 }, + { "px": [240,160], "src": [80,0], "f": 0, "t": 5, "d": [215], "a": 1 }, { "px": [256,160], "src": [16,0], "f": 0, "t": 1, "d": [216], "a": 1 }, - { "px": [272,160], "src": [0,0], "f": 0, "t": 0, "d": [217], "a": 1 }, - { "px": [288,160], "src": [16,16], "f": 0, "t": 3, "d": [218], "a": 1 }, - { "px": [304,160], "src": [16,16], "f": 0, "t": 3, "d": [219], "a": 1 } + { "px": [272,160], "src": [32,0], "f": 0, "t": 2, "d": [217], "a": 1 }, + { "px": [288,160], "src": [64,0], "f": 0, "t": 4, "d": [218], "a": 1 }, + { "px": [304,160], "src": [32,0], "f": 0, "t": 2, "d": [219], "a": 1 }, + { "px": [0,176], "src": [0,0], "f": 0, "t": 0, "d": [220], "a": 1 }, + { "px": [16,176], "src": [64,0], "f": 0, "t": 4, "d": [221], "a": 1 }, + { "px": [32,176], "src": [80,0], "f": 0, "t": 5, "d": [222], "a": 1 }, + { "px": [48,176], "src": [48,0], "f": 0, "t": 3, "d": [223], "a": 1 }, + { "px": [64,176], "src": [48,0], "f": 0, "t": 3, "d": [224], "a": 1 }, + { "px": [80,176], "src": [64,0], "f": 0, "t": 4, "d": [225], "a": 1 }, + { "px": [96,176], "src": [64,0], "f": 0, "t": 4, "d": [226], "a": 1 }, + { "px": [112,176], "src": [64,0], "f": 0, "t": 4, "d": [227], "a": 1 }, + { "px": [128,176], "src": [48,0], "f": 0, "t": 3, "d": [228], "a": 1 }, + { "px": [144,176], "src": [0,0], "f": 0, "t": 0, "d": [229], "a": 1 }, + { "px": [160,176], "src": [32,0], "f": 0, "t": 2, "d": [230], "a": 1 }, + { "px": [176,176], "src": [48,0], "f": 0, "t": 3, "d": [231], "a": 1 }, + { "px": [192,176], "src": [80,0], "f": 0, "t": 5, "d": [232], "a": 1 }, + { "px": [208,176], "src": [48,0], "f": 0, "t": 3, "d": [233], "a": 1 }, + { "px": [224,176], "src": [32,0], "f": 0, "t": 2, "d": [234], "a": 1 }, + { "px": [240,176], "src": [16,0], "f": 0, "t": 1, "d": [235], "a": 1 }, + { "px": [256,176], "src": [32,0], "f": 0, "t": 2, "d": [236], "a": 1 }, + { "px": [272,176], "src": [48,0], "f": 0, "t": 3, "d": [237], "a": 1 }, + { "px": [288,176], "src": [32,0], "f": 0, "t": 2, "d": [238], "a": 1 }, + { "px": [304,176], "src": [32,0], "f": 0, "t": 2, "d": [239], "a": 1 }, + { "px": [0,192], "src": [32,0], "f": 0, "t": 2, "d": [240], "a": 1 }, + { "px": [16,192], "src": [32,0], "f": 0, "t": 2, "d": [241], "a": 1 }, + { "px": [32,192], "src": [64,0], "f": 0, "t": 4, "d": [242], "a": 1 }, + { "px": [48,192], "src": [32,0], "f": 0, "t": 2, "d": [243], "a": 1 }, + { "px": [64,192], "src": [64,0], "f": 0, "t": 4, "d": [244], "a": 1 }, + { "px": [80,192], "src": [64,0], "f": 0, "t": 4, "d": [245], "a": 1 }, + { "px": [96,192], "src": [48,0], "f": 0, "t": 3, "d": [246], "a": 1 }, + { "px": [112,192], "src": [48,0], "f": 0, "t": 3, "d": [247], "a": 1 }, + { "px": [128,192], "src": [32,0], "f": 0, "t": 2, "d": [248], "a": 1 }, + { "px": [144,192], "src": [0,0], "f": 0, "t": 0, "d": [249], "a": 1 }, + { "px": [160,192], "src": [64,0], "f": 0, "t": 4, "d": [250], "a": 1 }, + { "px": [176,192], "src": [64,0], "f": 0, "t": 4, "d": [251], "a": 1 }, + { "px": [192,192], "src": [16,0], "f": 0, "t": 1, "d": [252], "a": 1 }, + { "px": [208,192], "src": [80,0], "f": 0, "t": 5, "d": [253], "a": 1 }, + { "px": [224,192], "src": [0,0], "f": 0, "t": 0, "d": [254], "a": 1 }, + { "px": [240,192], "src": [32,0], "f": 0, "t": 2, "d": [255], "a": 1 }, + { "px": [256,192], "src": [64,0], "f": 0, "t": 4, "d": [256], "a": 1 }, + { "px": [272,192], "src": [32,0], "f": 0, "t": 2, "d": [257], "a": 1 }, + { "px": [288,192], "src": [80,0], "f": 0, "t": 5, "d": [258], "a": 1 }, + { "px": [304,192], "src": [32,0], "f": 0, "t": 2, "d": [259], "a": 1 }, + { "px": [0,208], "src": [16,0], "f": 0, "t": 1, "d": [260], "a": 1 }, + { "px": [16,208], "src": [80,0], "f": 0, "t": 5, "d": [261], "a": 1 }, + { "px": [32,208], "src": [80,0], "f": 0, "t": 5, "d": [262], "a": 1 }, + { "px": [48,208], "src": [32,0], "f": 0, "t": 2, "d": [263], "a": 1 }, + { "px": [64,208], "src": [80,0], "f": 0, "t": 5, "d": [264], "a": 1 }, + { "px": [80,208], "src": [16,0], "f": 0, "t": 1, "d": [265], "a": 1 }, + { "px": [96,208], "src": [48,0], "f": 0, "t": 3, "d": [266], "a": 1 }, + { "px": [112,208], "src": [48,0], "f": 0, "t": 3, "d": [267], "a": 1 }, + { "px": [128,208], "src": [16,0], "f": 0, "t": 1, "d": [268], "a": 1 }, + { "px": [144,208], "src": [48,0], "f": 0, "t": 3, "d": [269], "a": 1 }, + { "px": [160,208], "src": [64,0], "f": 0, "t": 4, "d": [270], "a": 1 }, + { "px": [176,208], "src": [48,0], "f": 0, "t": 3, "d": [271], "a": 1 }, + { "px": [192,208], "src": [48,0], "f": 0, "t": 3, "d": [272], "a": 1 }, + { "px": [208,208], "src": [16,0], "f": 0, "t": 1, "d": [273], "a": 1 }, + { "px": [224,208], "src": [48,0], "f": 0, "t": 3, "d": [274], "a": 1 }, + { "px": [240,208], "src": [32,0], "f": 0, "t": 2, "d": [275], "a": 1 }, + { "px": [256,208], "src": [0,0], "f": 0, "t": 0, "d": [276], "a": 1 }, + { "px": [272,208], "src": [80,0], "f": 0, "t": 5, "d": [277], "a": 1 }, + { "px": [288,208], "src": [64,0], "f": 0, "t": 4, "d": [278], "a": 1 }, + { "px": [304,208], "src": [0,0], "f": 0, "t": 0, "d": [279], "a": 1 }, + { "px": [0,224], "src": [64,0], "f": 0, "t": 4, "d": [280], "a": 1 }, + { "px": [16,224], "src": [48,0], "f": 0, "t": 3, "d": [281], "a": 1 }, + { "px": [32,224], "src": [48,0], "f": 0, "t": 3, "d": [282], "a": 1 }, + { "px": [48,224], "src": [0,0], "f": 0, "t": 0, "d": [283], "a": 1 }, + { "px": [64,224], "src": [16,0], "f": 0, "t": 1, "d": [284], "a": 1 }, + { "px": [80,224], "src": [80,0], "f": 0, "t": 5, "d": [285], "a": 1 }, + { "px": [96,224], "src": [0,0], "f": 0, "t": 0, "d": [286], "a": 1 }, + { "px": [112,224], "src": [80,0], "f": 0, "t": 5, "d": [287], "a": 1 }, + { "px": [128,224], "src": [48,0], "f": 0, "t": 3, "d": [288], "a": 1 }, + { "px": [144,224], "src": [64,0], "f": 0, "t": 4, "d": [289], "a": 1 }, + { "px": [160,224], "src": [0,0], "f": 0, "t": 0, "d": [290], "a": 1 }, + { "px": [176,224], "src": [64,0], "f": 0, "t": 4, "d": [291], "a": 1 }, + { "px": [192,224], "src": [0,0], "f": 0, "t": 0, "d": [292], "a": 1 }, + { "px": [208,224], "src": [16,0], "f": 0, "t": 1, "d": [293], "a": 1 }, + { "px": [224,224], "src": [48,0], "f": 0, "t": 3, "d": [294], "a": 1 }, + { "px": [240,224], "src": [0,0], "f": 0, "t": 0, "d": [295], "a": 1 }, + { "px": [256,224], "src": [64,0], "f": 0, "t": 4, "d": [296], "a": 1 }, + { "px": [272,224], "src": [32,0], "f": 0, "t": 2, "d": [297], "a": 1 }, + { "px": [288,224], "src": [32,0], "f": 0, "t": 2, "d": [298], "a": 1 }, + { "px": [304,224], "src": [16,0], "f": 0, "t": 1, "d": [299], "a": 1 } ], "entityInstances": [] } @@ -475,4 +469,4 @@ ], "worlds": [], "dummyWorldIid": "315be6f0-1460-11ee-be24-1f7d7ad3f341" -} \ No newline at end of file +} diff --git a/game/overworld.png b/game/overworld.png new file mode 100644 index 00000000..0aa560f8 Binary files /dev/null and b/game/overworld.png differ diff --git a/src/lib.rs b/src/lib.rs index 5f0493bd..67280cf2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,39 +58,30 @@ impl ScreenUniforms { } } -struct State { - surface: wgpu::Surface, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - size: winit::dpi::PhysicalSize, - window: Window, - render_pipeline: wgpu::RenderPipeline, - - vertex_buffer: wgpu::Buffer, +#[derive(Debug)] +struct VertexBuffer { + buffer: wgpu::Buffer, + vec: Vec, max_vertices: usize, - - sprite_bind_group_layout: wgpu::BindGroupLayout, - sprite_textures: HashMap, - - write_textures: HashMap, - - screen_uniform: ScreenUniforms, - screen_uniform_buffer: wgpu::Buffer, - screen_uniform_bind_group: wgpu::BindGroup, - - // Garbage - mouse_x: f64, - mouse_y: f64, } -// TUTORIAL FOR BABIES LIKE ME: https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/ +#[derive(Clone, Debug)] +struct VertexBufferHandle { + index: usize, + capacity: usize, +} -impl State { - // Creating some of the wgpu types requires async code +struct WindowAndDevice { + pub window: Window, + // pub instance: wgpu::Instance, + pub surface: wgpu::Surface, + pub adapter: wgpu::Adapter, + pub device: wgpu::Device, + pub queue: wgpu::Queue, +} + +impl WindowAndDevice { async fn new(window: Window) -> Self { - let size = window.inner_size(); - // The instance is a handle to our GPU // Backends::all => Vulkan + Metal + DX12 + Browser WebGPU let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { @@ -131,7 +122,52 @@ impl State { .await .unwrap(); - let surface_caps = surface.get_capabilities(&adapter); + WindowAndDevice { + window, + surface, + adapter, + device, + queue, + } + } +} + +struct State { + surface: wgpu::Surface, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + window: Window, + render_pipeline: wgpu::RenderPipeline, + + vertex_buffers: Vec, + next_free_vertex_buffer: usize, + + sprite_bind_group_layout: wgpu::BindGroupLayout, + sprite_textures: HashMap, + + write_textures: HashMap, + + screen_uniform: ScreenUniforms, + screen_uniform_buffer: wgpu::Buffer, + screen_uniform_bind_group: wgpu::BindGroup, + + // Garbage + mouse_x: f64, + mouse_y: f64, +} + +// TUTORIAL FOR BABIES LIKE ME: https://sotrh.github.io/learn-wgpu/beginner/tutorial2-surface/ + +impl State { + // Creating some of the wgpu types requires async code + fn new(hardware: WindowAndDevice) -> Self { + let size = hardware.window.inner_size(); + + let device = hardware.device; + + let surface_caps = hardware.surface.get_capabilities(&hardware.adapter); // Shader code in this tutorial assumes an sRGB surface // texture. Using a different one will result all the colors coming @@ -152,7 +188,7 @@ impl State { alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], }; - surface.configure(&device, &config); + hardware.surface.configure(&device, &config); let sprite_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { @@ -260,26 +296,17 @@ impl State { multiview: None, }); - let max_vertices: usize = 4096; - let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Vertex Buffer"), - size: (max_vertices * std::mem::size_of::()) - .try_into() - .unwrap(), - mapped_at_creation: false, - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, - }); - Self { - window, - surface, + window: hardware.window, + surface: hardware.surface, device, - queue, + queue: hardware.queue, config, size, render_pipeline: sprite_pipeline, - vertex_buffer, - max_vertices, + vertex_buffers: Vec::new(), + next_free_vertex_buffer: 0, + sprite_bind_group_layout, sprite_textures: HashMap::new(), write_textures: HashMap::new(), @@ -316,260 +343,430 @@ impl State { fn render(&mut self, commands: Vec) -> Result<(), wgpu::SurfaceError> { let _span = span!("context render"); - let output = self.surface.get_current_texture()?; - // Group the commands into passes. + // Reset vertex buffers. + self.next_free_vertex_buffer = 0; + + let mut builder = FrameBuilder::new(self)?; + for command in commands { + builder.handle_command(command); + } + FrameBuilder::complete(builder); + + Ok(()) + } + + fn create_texture(&mut self, id: u32, image: image::DynamicImage, label: Option) { + let texture = texture::Texture::from_image( + &self.device, + &self.queue, + &image, + match &label { + Some(l) => Some(&l), + None => None, + }, + ); + let sprite_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.sprite_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture.sampler), + }, + ], + label: match &label { + Some(l) => Some(&l), + None => None, + }, + }); + + self.sprite_textures.insert(id, sprite_bind_group); + } + + fn create_writeable_texture( + &mut self, + id: u32, + width: u32, + height: u32, + label: Option, + ) { + let texture = texture::Texture::new_writable( + &self.device, + width, + height, + match &label { + Some(l) => Some(&l), + None => None, + }, + ); + let sprite_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.sprite_bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&texture.view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler(&texture.sampler), + }, + ], + label: match &label { + Some(l) => Some(&l), + None => None, + }, + }); + + self.sprite_textures.insert(id, sprite_bind_group); + self.write_textures.insert(id, texture); + } + + fn new_vertex_buffer(&mut self) -> VertexBufferHandle { + if self.next_free_vertex_buffer >= self.vertex_buffers.len() { + let max_vertices: usize = 4096; + let buffer = self.device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Vertex Buffer"), + size: (max_vertices * std::mem::size_of::()) + .try_into() + .unwrap(), + mapped_at_creation: false, + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST, + }); + self.vertex_buffers.push(VertexBuffer { + buffer, + max_vertices, + vec: Vec::with_capacity(max_vertices), + }); + } + + let index = self.next_free_vertex_buffer; + self.next_free_vertex_buffer += 1; + let vb = &mut self.vertex_buffers[index]; + vb.vec.clear(); + VertexBufferHandle { + index, + capacity: vb.max_vertices, + } + } + + fn get_vertex_buffer(&self, handle: &VertexBufferHandle) -> &VertexBuffer { + &self.vertex_buffers[handle.index] + } + + fn get_vertex_buffer_mut(&mut self, handle: &VertexBufferHandle) -> &mut VertexBuffer { + &mut self.vertex_buffers[handle.index] + } + + fn copy_vertex_buffers(&mut self) { + for i in 0..self.next_free_vertex_buffer { + let vb = &self.vertex_buffers[i]; + self.queue + .write_buffer(&vb.buffer, 0, bytemuck::cast_slice(&vb.vec)); + } + } +} + +#[derive(Debug)] +struct DrawCall { + texture_id: Option, + vertex_buffer: VertexBufferHandle, + draw_start: u32, + draw_end: u32, +} + +impl DrawCall { + pub fn new(vertex_buffer: VertexBufferHandle, draw_start: u32) -> Self { + DrawCall { + texture_id: None, + vertex_buffer, + draw_start, + draw_end: draw_start, + } + } + + pub fn new_at_buffer_tail(&self) -> Self { + DrawCall::new(self.vertex_buffer.clone(), self.draw_end) + } + + pub fn switch_textures(&self, id: u32) -> DrawCall { + let mut next = self.new_at_buffer_tail(); + next.texture_id = Some(id); + next + } + + pub fn empty(&self) -> bool { + self.draw_start == self.draw_end + } + + pub fn allocate_capacity(&mut self, capacity: u32) -> Option { + if self.vertex_buffer.capacity >= (self.draw_end + capacity) as usize { + self.draw_end += capacity; + Some(self.vertex_buffer.clone()) + } else { + None + } + } + + // pub fn draw<'a>(&'a self, pass: &'a mut wgpu::RenderPass, state: &'a State) { + // if self.draw_start == self.draw_end { + // return; + // } + + // let texture_id = match self.texture_id { + // Some(id) => id, + // None => return, + // }; + + // let bind_group = state.sprite_textures.get(&texture_id).unwrap(); + // pass.set_bind_group(0, bind_group, &[]); + + // let vb = self.vertex_buffer.borrow(); + // pass.set_bind_group(1, &state.screen_uniform_bind_group, &[]); + // pass.set_vertex_buffer(0, vb.buffer.slice(..)); + // pass.draw(self.draw_start..self.draw_end, 0..1); + // } +} + +struct FrameBuilder<'a> { + state: &'a mut State, + screen_view: Rc, + last_view: Rc, + encoder: wgpu::CommandEncoder, + output: wgpu::SurfaceTexture, + + target: Rc, + color: Option<[f64; 4]>, + draw_calls: Vec, +} + +impl<'a> FrameBuilder<'a> { + fn new(state: &'a mut State) -> Result { + let encoder = state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + let output = state.surface.get_current_texture()?; + let screen_view = Rc::new( output .texture .create_view(&wgpu::TextureViewDescriptor::default()), ); - let mut last_view = screen_view.clone(); + let last_view = screen_view.clone(); - struct Pass { - color: Option<[f64; 4]>, - commands: Vec, - target: Rc, + Ok(FrameBuilder { + state, + screen_view, + last_view: last_view.clone(), + encoder, + output, + + target: last_view, + color: None, + draw_calls: Vec::new(), + }) + } + + fn complete(builder: FrameBuilder<'a>) { + let mut builder = builder; + builder.flush(); + + // At this point the state needs to copy the vertex buffers we know about. + builder.state.copy_vertex_buffers(); + + // Submit will accept anything that implements IntoIter + builder + .state + .queue + .submit(std::iter::once(builder.encoder.finish())); + + builder.output.present(); + } + + fn handle_command(&mut self, command: GraphicsCommand) { + match command { + // ================================================================ + // Pass commands + // ================================================================ + // NOTE: You would expect a "change target" command to be + // followed nearly immediately by a clear command and we + // wind up starting an extra pass in that case which sucks. + GraphicsCommand::Clear(cc) => self.start_pass(Some(cc.color), self.last_view.clone()), + GraphicsCommand::WriteToTexture(id) => { + let texture = self.state.write_textures.get(&id).unwrap(); + self.last_view = Rc::new( + texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()), + ); + self.start_pass(None, self.last_view.clone()); + } + GraphicsCommand::WriteToScreen => { + if !Rc::ptr_eq(&self.last_view, &self.screen_view) { + self.last_view = self.screen_view.clone(); + self.start_pass(None, self.last_view.clone()); + } + } + GraphicsCommand::Print(pc) => println!("{}", pc.text), + GraphicsCommand::Sprite(sc) => self.push_sprite(sc), + GraphicsCommand::UseTexture(id) => self.use_texture(id), + GraphicsCommand::EndFrame => self.flush(), + + // ================================================================ + // Resource commands + // ================================================================ + GraphicsCommand::CreateTexture(ct) => { + self.state.create_texture(ct.id, ct.image, ct.label) + } + GraphicsCommand::CreateWritableTexture { + id, + width, + height, + label, + } => self + .state + .create_writeable_texture(id, width, height, label), } - let mut passes = Vec::new(); - for command in commands { - match command { - GraphicsCommand::Clear(cc) => passes.push(Pass { - color: Some(cc.color), - commands: Vec::new(), - target: last_view.clone(), - }), + } - GraphicsCommand::CreateTexture(ct) => { - let texture = texture::Texture::from_image( - &self.device, - &self.queue, - &ct.image, - match &ct.label { - Some(l) => Some(&l), - None => None, - }, - ); - let sprite_bind_group = - self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.sprite_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: match &ct.label { - Some(l) => Some(&l), - None => None, - }, - }); + fn start_pass(&mut self, color: Option<[f64; 4]>, target: Rc) { + self.flush(); + self.color = color; + self.target = target; + } - self.sprite_textures.insert(ct.id, sprite_bind_group); - } - - GraphicsCommand::CreateWritableTexture { - id, - width, - height, - label, - } => { - let texture = texture::Texture::new_writable( - &self.device, - width, - height, - match &label { - Some(l) => Some(&l), - None => None, - }, - ); - let sprite_bind_group = - self.device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.sprite_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&texture.sampler), - }, - ], - label: match &label { - Some(l) => Some(&l), - None => None, - }, - }); - - self.sprite_textures.insert(id, sprite_bind_group); - self.write_textures.insert(id, texture); - } - - GraphicsCommand::WriteToTexture(id) => { - let texture = self.write_textures.get(&id).unwrap(); - last_view = Rc::new( - texture - .texture - .create_view(&wgpu::TextureViewDescriptor::default()), - ); - let new_pass = match passes.last_mut() { - Some(pass) => { - if pass.commands.is_empty() { - pass.target = last_view.clone(); - false - } else { - true - } - } - None => true, - }; - if new_pass { - passes.push(Pass { - color: None, - commands: vec![], - target: last_view.clone(), - }) + fn use_texture(&mut self, texture_id: u32) { + match self.draw_calls.last_mut() { + Some(call) => match call.texture_id { + Some(id) => { + if id == texture_id { + return; + } else if call.empty() { + call.texture_id = Some(texture_id); + } else { + let next = call.switch_textures(texture_id); + self.draw_calls.push(next); + return; } } + None => { + call.texture_id = Some(texture_id); + } + }, + None => { + let mut call = DrawCall::new(self.state.new_vertex_buffer(), 0); + call.texture_id = Some(texture_id); + self.draw_calls.push(call); + } + } + } - GraphicsCommand::WriteToScreen => { - if !Rc::ptr_eq(&last_view, &screen_view) { - // If I have a pass already I need a new one. - last_view = screen_view.clone(); - if !passes.is_empty() { - passes.push(Pass { - color: None, - commands: vec![], - target: last_view.clone(), + fn get_vertex_buffer(&mut self, required_capacity: u32) -> &mut VertexBuffer { + match self.draw_calls.last_mut() { + Some(call) => match call.allocate_capacity(required_capacity) { + Some(vb) => return self.state.get_vertex_buffer_mut(&vb), + None => {} + }, + None => {} + }; + + let mut call = DrawCall::new(self.state.new_vertex_buffer(), 0); + let vb = call.allocate_capacity(required_capacity).unwrap(); + self.draw_calls.push(call); + self.state.get_vertex_buffer_mut(&vb) + } + + fn push_sprite(&mut self, sc: script::graphics::SpriteCommand) { + let vertex_buffer = self.get_vertex_buffer(6); + + vertex_buffer.vec.push(Vertex { + position: [sc.x, sc.y, 0.0], + tex_coords: [sc.u, sc.v], + }); + vertex_buffer.vec.push(Vertex { + position: [sc.x, sc.y + sc.h, 0.0], + tex_coords: [sc.u, sc.v + sc.sh], + }); + vertex_buffer.vec.push(Vertex { + position: [sc.x + sc.w, sc.y, 0.0], + tex_coords: [sc.u + sc.sw, sc.v], + }); + + vertex_buffer.vec.push(Vertex { + position: [sc.x, sc.y + sc.h, 0.0], + tex_coords: [sc.u, sc.v + sc.sh], + }); + vertex_buffer.vec.push(Vertex { + position: [sc.x + sc.w, sc.y + sc.h, 0.0], + tex_coords: [sc.u + sc.sw, sc.v + sc.sh], + }); + vertex_buffer.vec.push(Vertex { + position: [sc.x + sc.w, sc.y, 0.0], + tex_coords: [sc.u + sc.sw, sc.v], + }); + } + + fn flush(&mut self) { + let first_call = match self.draw_calls.last() { + Some(call) => call.new_at_buffer_tail(), + None => DrawCall::new(self.state.new_vertex_buffer(), 0), + }; + + if self.draw_calls.len() > 0 { + let mut pass = self.encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self.target, + resolve_target: None, + ops: wgpu::Operations { + load: if let Some([r, g, b, a]) = self.color { + wgpu::LoadOp::Clear(wgpu::Color { + r, //0.1, + g, //0.2, + b, + a, }) - } - } - } - - GraphicsCommand::EndFrame => (), - other => match passes.last_mut() { - Some(pass) => pass.commands.push(other), - None => passes.push(Pass { - color: None, - commands: vec![other], - target: last_view.clone(), - }), - }, - } - } - - let mut vertices = Vec::new(); - for pass in passes { - // TODO: It would be great if we could use multiple passes in a - // single encoder but right now because of the dyanmic - // nature of vertices we can't, I think? Because - // queue.write_buffer doesn't actually happen until we call - // submit... - let mut encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &pass.target, - resolve_target: None, - ops: wgpu::Operations { - load: if let Some([r, g, b, a]) = pass.color { - wgpu::LoadOp::Clear(wgpu::Color { - r, //0.1, - g, //0.2, - b, - a, - }) - } else { - wgpu::LoadOp::Load - }, - store: true, + } else { + wgpu::LoadOp::Load }, - })], - depth_stencil_attachment: None, - }); + store: true, + }, + })], + depth_stencil_attachment: None, + }); - let mut texture_id = None; - vertices.clear(); - for command in pass.commands { - match command { - GraphicsCommand::Print(pc) => { - println!("{}", pc.text); - } - GraphicsCommand::Sprite(sc) => { - vertices.push(Vertex { - position: [sc.x, sc.y, 0.0], - tex_coords: [sc.u, sc.v], - }); - vertices.push(Vertex { - position: [sc.x, sc.y + sc.h, 0.0], - tex_coords: [sc.u, sc.v + sc.sh], - }); - vertices.push(Vertex { - position: [sc.x + sc.w, sc.y, 0.0], - tex_coords: [sc.u + sc.sw, sc.v], - }); - - vertices.push(Vertex { - position: [sc.x, sc.y + sc.h, 0.0], - tex_coords: [sc.u, sc.v + sc.sh], - }); - vertices.push(Vertex { - position: [sc.x + sc.w, sc.y + sc.h, 0.0], - tex_coords: [sc.u + sc.sw, sc.v + sc.sh], - }); - vertices.push(Vertex { - position: [sc.x + sc.w, sc.y, 0.0], - tex_coords: [sc.u + sc.sw, sc.v], - }); - } - - GraphicsCommand::UseTexture(id) => texture_id = Some(id), - - GraphicsCommand::CreateTexture(_) => (), // Already handled - GraphicsCommand::CreateWritableTexture { .. } => (), // Already handled - GraphicsCommand::WriteToTexture(_) => (), // Already handled - GraphicsCommand::WriteToScreen => (), // Already handled - GraphicsCommand::Clear(_) => (), // Already handled - GraphicsCommand::EndFrame => (), // Should never appear - } + pass.set_pipeline(&self.state.render_pipeline); + for call in &self.draw_calls { + if call.draw_start == call.draw_end { + continue; } - if let Some(id) = texture_id { - assert!(vertices.len() < self.max_vertices); // ! - self.queue.write_buffer( - &self.vertex_buffer, - 0, - bytemuck::cast_slice(&vertices), - ); - render_pass.set_pipeline(&self.render_pipeline); + let texture_id = match call.texture_id { + Some(id) => id, + None => return, + }; - let bind_group = self.sprite_textures.get(&id).unwrap(); - render_pass.set_bind_group(0, bind_group, &[]); + let bind_group = self.state.sprite_textures.get(&texture_id).unwrap(); + pass.set_bind_group(0, bind_group, &[]); - render_pass.set_bind_group(1, &self.screen_uniform_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.draw(0..(vertices.len() as u32), 0..1); - } + let vb = self.state.get_vertex_buffer(&call.vertex_buffer); + pass.set_bind_group(1, &self.state.screen_uniform_bind_group, &[]); + pass.set_vertex_buffer(0, vb.buffer.slice(..)); + pass.draw(call.draw_start..call.draw_end, 0..1); + + // call.draw(&mut pass, &self.state); } - - // Submit will accept anything that implements IntoIter - self.queue.submit(std::iter::once(encoder.finish())); } - output.present(); - - Ok(()) + self.color = None; + self.draw_calls.clear(); + self.draw_calls.push(first_call); } } @@ -670,11 +867,12 @@ pub async fn run() { let window = WindowBuilder::new().build(&event_loop).unwrap(); let event_loop_proxy = event_loop.create_proxy(); - let state = State::new(window).await; + let hardware = WindowAndDevice::new(window).await; let (sender, reciever) = std::sync::mpsc::channel(); std::thread::spawn(move || { set_thread_name!("game thread"); + let state = State::new(hardware); main_thread(event_loop_proxy, state, reciever); });