@flighthq/tileset 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/tileset.d.ts +4 -0
- package/dist/tileset.d.ts.map +1 -0
- package/dist/tileset.js +34 -0
- package/dist/tileset.js.map +1 -0
- package/dist/tilesetFrom.d.ts +8 -0
- package/dist/tilesetFrom.d.ts.map +1 -0
- package/dist/tilesetFrom.js +30 -0
- package/dist/tilesetFrom.js.map +1 -0
- package/package.json +40 -0
- package/src/tileset.test.ts +123 -0
- package/src/tilesetFrom.test.ts +131 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tileset.d.ts","sourceRoot":"","sources":["../src/tileset.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAM/C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAazD;AAED,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAU7D"}
|
package/dist/tileset.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createEntity } from '@flighthq/entity';
|
|
2
|
+
import { createTextureAtlasRegion, setTextureAtlasRegion } from '@flighthq/textureatlas';
|
|
3
|
+
// Fills or refreshes the atlas region array with one region per tile in the grid, using the
|
|
4
|
+
// tileset's tileWidth/tileHeight, margin (border padding), and spacing (inter-tile gap).
|
|
5
|
+
// Existing region objects are reused in place when the capacity matches — zero new allocations
|
|
6
|
+
// for repeated calls on an already-populated tileset.
|
|
7
|
+
export function buildTilesetRegions(target) {
|
|
8
|
+
const { atlas, rows, columns, tileWidth, tileHeight, margin, spacing } = target;
|
|
9
|
+
if (atlas === null)
|
|
10
|
+
return;
|
|
11
|
+
let i = 0;
|
|
12
|
+
for (let row = 0; row < rows; row++) {
|
|
13
|
+
for (let column = 0; column < columns; column++) {
|
|
14
|
+
if (i >= atlas.regions.length)
|
|
15
|
+
atlas.regions.push(createTextureAtlasRegion());
|
|
16
|
+
const x = margin + column * (tileWidth + spacing);
|
|
17
|
+
const y = margin + row * (tileHeight + spacing);
|
|
18
|
+
setTextureAtlasRegion(atlas.regions[i], x, y, tileWidth, tileHeight);
|
|
19
|
+
i++;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function createTileset(obj) {
|
|
24
|
+
return createEntity({
|
|
25
|
+
atlas: obj?.atlas ?? null,
|
|
26
|
+
columns: obj?.columns ?? 0,
|
|
27
|
+
margin: obj?.margin ?? 0,
|
|
28
|
+
rows: obj?.rows ?? 0,
|
|
29
|
+
spacing: obj?.spacing ?? 0,
|
|
30
|
+
tileHeight: obj?.tileHeight ?? 0,
|
|
31
|
+
tileWidth: obj?.tileWidth ?? 0,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=tileset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tileset.js","sourceRoot":"","sources":["../src/tileset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAGzF,4FAA4F;AAC5F,yFAAyF;AACzF,+FAA+F;AAC/F,sDAAsD;AACtD,MAAM,UAAU,mBAAmB,CAAC,MAAe;IACjD,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAChF,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO;IAC3B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;QACpC,KAAK,IAAI,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;YAChD,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM;gBAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,wBAAwB,EAAE,CAAC,CAAC;YAC9E,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,CAAC;YAClD,MAAM,CAAC,GAAG,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC;YAChD,qBAAqB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;YACrE,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAsB;IAClD,OAAO,YAAY,CAAC;QAClB,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,IAAI;QACzB,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,CAAC;QACxB,IAAI,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC;QACpB,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,CAAC;QAC1B,UAAU,EAAE,GAAG,EAAE,UAAU,IAAI,CAAC;QAChC,SAAS,EAAE,GAAG,EAAE,SAAS,IAAI,CAAC;KAC/B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ImageResource, TextureAtlas, Tileset } from '@flighthq/types';
|
|
2
|
+
export declare function createTilesetFromAtlas(atlas: TextureAtlas, tileWidth: number, tileHeight: number, margin?: number, spacing?: number): Tileset;
|
|
3
|
+
export declare function createTilesetFromImageResource(resource: ImageResource, tileWidth: number, tileHeight: number, margin?: number, spacing?: number): Tileset;
|
|
4
|
+
export declare function loadTilesetFromBase64(base64: string, mimeType: string, tileWidth: number, tileHeight: number, signal?: AbortSignal): Promise<Tileset>;
|
|
5
|
+
export declare function loadTilesetFromBlob(blob: Blob, tileWidth: number, tileHeight: number, signal?: AbortSignal): Promise<Tileset>;
|
|
6
|
+
export declare function loadTilesetFromBytes(bytes: Uint8Array, tileWidth: number, tileHeight: number, mimeType?: string, signal?: AbortSignal): Promise<Tileset>;
|
|
7
|
+
export declare function loadTilesetFromUrl(url: string, tileWidth: number, tileHeight: number, crossOrigin?: string, signal?: AbortSignal): Promise<Tileset>;
|
|
8
|
+
//# sourceMappingURL=tilesetFrom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tilesetFrom.d.ts","sourceRoot":"","sources":["../src/tilesetFrom.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAO5E,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAU,EAClB,OAAO,GAAE,MAAU,GAClB,OAAO,CAST;AAED,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,aAAa,EACvB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,MAAU,EAClB,OAAO,GAAE,MAAU,GAClB,OAAO,CAET;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAMlB;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { loadImageResourceFromBase64, loadImageResourceFromBlob, loadImageResourceFromBytes, loadImageResourceFromUrl, } from '@flighthq/image';
|
|
2
|
+
import { createTextureAtlas } from '@flighthq/textureatlas';
|
|
3
|
+
import { buildTilesetRegions, createTileset } from './tileset';
|
|
4
|
+
// Derives rows/columns from the atlas image size, honoring margin and inter-tile spacing.
|
|
5
|
+
// `margin` is the number of pixels of padding between the tile grid and the image edge.
|
|
6
|
+
// `spacing` is the number of pixels between adjacent tiles (inner gap).
|
|
7
|
+
export function createTilesetFromAtlas(atlas, tileWidth, tileHeight, margin = 0, spacing = 0) {
|
|
8
|
+
const image = atlas.image;
|
|
9
|
+
const columns = image !== null && tileWidth > 0 ? Math.floor((image.width - margin * 2 + spacing) / (tileWidth + spacing)) : 0;
|
|
10
|
+
const rows = image !== null && tileHeight > 0 ? Math.floor((image.height - margin * 2 + spacing) / (tileHeight + spacing)) : 0;
|
|
11
|
+
const tileset = createTileset({ atlas, columns, margin, rows, spacing, tileHeight, tileWidth });
|
|
12
|
+
buildTilesetRegions(tileset);
|
|
13
|
+
return tileset;
|
|
14
|
+
}
|
|
15
|
+
export function createTilesetFromImageResource(resource, tileWidth, tileHeight, margin = 0, spacing = 0) {
|
|
16
|
+
return createTilesetFromAtlas(createTextureAtlas({ image: resource }), tileWidth, tileHeight, margin, spacing);
|
|
17
|
+
}
|
|
18
|
+
export async function loadTilesetFromBase64(base64, mimeType, tileWidth, tileHeight, signal) {
|
|
19
|
+
return createTilesetFromImageResource(await loadImageResourceFromBase64(base64, mimeType, signal), tileWidth, tileHeight);
|
|
20
|
+
}
|
|
21
|
+
export async function loadTilesetFromBlob(blob, tileWidth, tileHeight, signal) {
|
|
22
|
+
return createTilesetFromImageResource(await loadImageResourceFromBlob(blob, signal), tileWidth, tileHeight);
|
|
23
|
+
}
|
|
24
|
+
export async function loadTilesetFromBytes(bytes, tileWidth, tileHeight, mimeType, signal) {
|
|
25
|
+
return createTilesetFromImageResource(await loadImageResourceFromBytes(bytes, mimeType, signal), tileWidth, tileHeight);
|
|
26
|
+
}
|
|
27
|
+
export async function loadTilesetFromUrl(url, tileWidth, tileHeight, crossOrigin, signal) {
|
|
28
|
+
return createTilesetFromImageResource(await loadImageResourceFromUrl(url, crossOrigin, signal), tileWidth, tileHeight);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=tilesetFrom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tilesetFrom.js","sourceRoot":"","sources":["../src/tilesetFrom.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAG5D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/D,0FAA0F;AAC1F,wFAAwF;AACxF,wEAAwE;AACxE,MAAM,UAAU,sBAAsB,CACpC,KAAmB,EACnB,SAAiB,EACjB,UAAkB,EAClB,SAAiB,CAAC,EAClB,UAAkB,CAAC;IAEnB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAC1B,MAAM,OAAO,GACX,KAAK,KAAK,IAAI,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjH,MAAM,IAAI,GACR,KAAK,KAAK,IAAI,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpH,MAAM,OAAO,GAAG,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC,CAAC;IAChG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,QAAuB,EACvB,SAAiB,EACjB,UAAkB,EAClB,SAAiB,CAAC,EAClB,UAAkB,CAAC;IAEnB,OAAO,sBAAsB,CAAC,kBAAkB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACjH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,MAAc,EACd,QAAgB,EAChB,SAAiB,EACjB,UAAkB,EAClB,MAAoB;IAEpB,OAAO,8BAA8B,CACnC,MAAM,2BAA2B,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,EAC3D,SAAS,EACT,UAAU,CACX,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAU,EACV,SAAiB,EACjB,UAAkB,EAClB,MAAoB;IAEpB,OAAO,8BAA8B,CAAC,MAAM,yBAAyB,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC9G,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,KAAiB,EACjB,SAAiB,EACjB,UAAkB,EAClB,QAAiB,EACjB,MAAoB;IAEpB,OAAO,8BAA8B,CACnC,MAAM,0BAA0B,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EACzD,SAAS,EACT,UAAU,CACX,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAAW,EACX,SAAiB,EACjB,UAAkB,EAClB,WAAoB,EACpB,MAAoB;IAEpB,OAAO,8BAA8B,CACnC,MAAM,wBAAwB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,CAAC,EACxD,SAAS,EACT,UAAU,CACX,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/tileset",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/entity": "0.1.0",
|
|
31
|
+
"@flighthq/image": "0.1.0",
|
|
32
|
+
"@flighthq/textureatlas": "0.1.0",
|
|
33
|
+
"@flighthq/types": "0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.3.0"
|
|
37
|
+
},
|
|
38
|
+
"description": "Tilesets: uniform-grid texture atlases and constructors from images",
|
|
39
|
+
"sideEffects": false
|
|
40
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createImageResourceFromImageElement } from '@flighthq/image';
|
|
2
|
+
import { createTextureAtlas } from '@flighthq/textureatlas';
|
|
3
|
+
import type { TextureAtlas, Tileset } from '@flighthq/types';
|
|
4
|
+
|
|
5
|
+
import { buildTilesetRegions, createTileset } from './tileset';
|
|
6
|
+
|
|
7
|
+
describe('buildTilesetRegions', () => {
|
|
8
|
+
it('advances y by tileHeight for each row', () => {
|
|
9
|
+
const source = createImageResourceFromImageElement({ width: 32, height: 64 } as HTMLImageElement);
|
|
10
|
+
const atlas = createTextureAtlas({ image: source });
|
|
11
|
+
const tileset = createTileset({ atlas, columns: 1, rows: 2, tileWidth: 32, tileHeight: 32 });
|
|
12
|
+
buildTilesetRegions(tileset);
|
|
13
|
+
// region 0: column=0, row=0 → x=0, y=0
|
|
14
|
+
expect(atlas.regions[0].x).toBe(0);
|
|
15
|
+
expect(atlas.regions[0].y).toBe(0);
|
|
16
|
+
// region 1: column=0, row=1 → x=0, y=32
|
|
17
|
+
expect(atlas.regions[1].x).toBe(0);
|
|
18
|
+
expect(atlas.regions[1].y).toBe(32);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('does nothing when atlas is null', () => {
|
|
22
|
+
const tileset = createTileset({ columns: 2, rows: 2, tileWidth: 32, tileHeight: 32 });
|
|
23
|
+
expect(buildTilesetRegions(tileset)).toBeUndefined();
|
|
24
|
+
expect(tileset.atlas).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('honors margin offset for each region', () => {
|
|
28
|
+
const source = createImageResourceFromImageElement({ width: 68, height: 34 } as HTMLImageElement);
|
|
29
|
+
const atlas = createTextureAtlas({ image: source });
|
|
30
|
+
const tileset = createTileset({ atlas, columns: 2, rows: 1, tileWidth: 32, tileHeight: 32, margin: 2 });
|
|
31
|
+
buildTilesetRegions(tileset);
|
|
32
|
+
// margin=2: first tile starts at x=2, second at x=2+32=34
|
|
33
|
+
expect(atlas.regions[0].x).toBe(2);
|
|
34
|
+
expect(atlas.regions[0].y).toBe(2);
|
|
35
|
+
expect(atlas.regions[1].x).toBe(34);
|
|
36
|
+
expect(atlas.regions[1].y).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('honors spacing gap between tiles', () => {
|
|
40
|
+
const source = createImageResourceFromImageElement({ width: 66, height: 32 } as HTMLImageElement);
|
|
41
|
+
const atlas = createTextureAtlas({ image: source });
|
|
42
|
+
// spacing=2: two 32x32 tiles with a 2px gap between them
|
|
43
|
+
const tileset = createTileset({ atlas, columns: 2, rows: 1, tileWidth: 32, tileHeight: 32, spacing: 2 });
|
|
44
|
+
buildTilesetRegions(tileset);
|
|
45
|
+
// region 0: x=0, region 1: x=32+2=34
|
|
46
|
+
expect(atlas.regions[0].x).toBe(0);
|
|
47
|
+
expect(atlas.regions[1].x).toBe(34);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('positions regions at (column * tileWidth, row * tileHeight)', () => {
|
|
51
|
+
const source = createImageResourceFromImageElement({ width: 64, height: 32 } as HTMLImageElement);
|
|
52
|
+
const atlas = createTextureAtlas({ image: source });
|
|
53
|
+
const tileset = createTileset({ atlas, columns: 2, rows: 1, tileWidth: 32, tileHeight: 32 });
|
|
54
|
+
buildTilesetRegions(tileset);
|
|
55
|
+
// region 0: column=0, row=0 → x=0, y=0
|
|
56
|
+
expect(atlas.regions[0].x).toBe(0);
|
|
57
|
+
expect(atlas.regions[0].y).toBe(0);
|
|
58
|
+
// region 1: column=1, row=0 → x=32, y=0
|
|
59
|
+
expect(atlas.regions[1].x).toBe(32);
|
|
60
|
+
expect(atlas.regions[1].y).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('reuses existing region objects when capacity is already available', () => {
|
|
64
|
+
const source = createImageResourceFromImageElement({ width: 64, height: 32 } as HTMLImageElement);
|
|
65
|
+
const atlas = createTextureAtlas({ image: source });
|
|
66
|
+
const firstRegion = {} as TextureAtlas['regions'][number];
|
|
67
|
+
const secondRegion = {} as TextureAtlas['regions'][number];
|
|
68
|
+
atlas.regions.push(firstRegion, secondRegion);
|
|
69
|
+
const tileset = createTileset({ atlas, columns: 2, rows: 1, tileWidth: 32, tileHeight: 32 });
|
|
70
|
+
|
|
71
|
+
buildTilesetRegions(tileset);
|
|
72
|
+
|
|
73
|
+
expect(atlas.regions).toHaveLength(2);
|
|
74
|
+
expect(atlas.regions[0]).toBe(firstRegion);
|
|
75
|
+
expect(atlas.regions[1]).toBe(secondRegion);
|
|
76
|
+
expect(atlas.regions[0].x).toBe(0);
|
|
77
|
+
expect(atlas.regions[1].x).toBe(32);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('createTileset', () => {
|
|
82
|
+
let tileset: Tileset;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
tileset = createTileset();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('allows pre-defined values', () => {
|
|
89
|
+
const base = {
|
|
90
|
+
atlas: {} as TextureAtlas,
|
|
91
|
+
tileWidth: 10,
|
|
92
|
+
tileHeight: 20,
|
|
93
|
+
rows: 1,
|
|
94
|
+
columns: 2,
|
|
95
|
+
margin: 4,
|
|
96
|
+
spacing: 2,
|
|
97
|
+
};
|
|
98
|
+
const obj = createTileset(base);
|
|
99
|
+
expect(obj.atlas).toStrictEqual(base.atlas);
|
|
100
|
+
expect(obj.tileWidth).toStrictEqual(base.tileWidth);
|
|
101
|
+
expect(obj.tileHeight).toStrictEqual(base.tileHeight);
|
|
102
|
+
expect(obj.rows).toStrictEqual(base.rows);
|
|
103
|
+
expect(obj.columns).toStrictEqual(base.columns);
|
|
104
|
+
expect(obj.margin).toStrictEqual(base.margin);
|
|
105
|
+
expect(obj.spacing).toStrictEqual(base.spacing);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('initializes default values', () => {
|
|
109
|
+
expect(tileset.atlas).toBeNull();
|
|
110
|
+
expect(tileset.tileWidth).toStrictEqual(0);
|
|
111
|
+
expect(tileset.tileHeight).toStrictEqual(0);
|
|
112
|
+
expect(tileset.rows).toStrictEqual(0);
|
|
113
|
+
expect(tileset.columns).toStrictEqual(0);
|
|
114
|
+
expect(tileset.margin).toStrictEqual(0);
|
|
115
|
+
expect(tileset.spacing).toStrictEqual(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('returns a new object for better hidden-class performance', () => {
|
|
119
|
+
const base = {};
|
|
120
|
+
const obj = createTileset(base);
|
|
121
|
+
expect(obj).not.toStrictEqual(base);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createImageResourceFromImageElement } from '@flighthq/image';
|
|
2
|
+
import { createTextureAtlas } from '@flighthq/textureatlas';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createTilesetFromAtlas,
|
|
6
|
+
createTilesetFromImageResource,
|
|
7
|
+
loadTilesetFromBase64,
|
|
8
|
+
loadTilesetFromBlob,
|
|
9
|
+
loadTilesetFromBytes,
|
|
10
|
+
loadTilesetFromUrl,
|
|
11
|
+
} from './tilesetFrom';
|
|
12
|
+
|
|
13
|
+
function makeAtlas(width: number, height: number) {
|
|
14
|
+
const source = createImageResourceFromImageElement({ width, height } as HTMLImageElement);
|
|
15
|
+
return createTextureAtlas({ image: source });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Stub img.decode() so async load functions resolve immediately in jsdom.
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
HTMLImageElement.prototype.decode = vi.fn().mockResolvedValue(undefined);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
delete (HTMLImageElement.prototype as Partial<HTMLImageElement>).decode;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
describe('createTilesetFromAtlas', () => {
|
|
29
|
+
it('accounts for margin and spacing when computing rows and columns', () => {
|
|
30
|
+
// 2px margin on each side, 2px spacing between tiles: (70 - 4 + 2) / (32 + 2) = 68/34 = 2
|
|
31
|
+
const atlas = makeAtlas(70, 70);
|
|
32
|
+
const tileset = createTilesetFromAtlas(atlas, 32, 32, 2, 2);
|
|
33
|
+
expect(tileset.columns).toBe(2);
|
|
34
|
+
expect(tileset.rows).toBe(2);
|
|
35
|
+
expect(tileset.margin).toBe(2);
|
|
36
|
+
expect(tileset.spacing).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('derives rows and columns from atlas image dimensions', () => {
|
|
40
|
+
const atlas = makeAtlas(64, 32);
|
|
41
|
+
const tileset = createTilesetFromAtlas(atlas, 32, 32);
|
|
42
|
+
|
|
43
|
+
expect(tileset.columns).toBe(2);
|
|
44
|
+
expect(tileset.rows).toBe(1);
|
|
45
|
+
expect(tileset.tileWidth).toBe(32);
|
|
46
|
+
expect(tileset.tileHeight).toBe(32);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('initializes atlas regions for each tile', () => {
|
|
50
|
+
const atlas = makeAtlas(64, 32);
|
|
51
|
+
createTilesetFromAtlas(atlas, 32, 32);
|
|
52
|
+
|
|
53
|
+
expect(atlas.regions).toHaveLength(2);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('uses the provided atlas', () => {
|
|
57
|
+
const atlas = makeAtlas(32, 32);
|
|
58
|
+
const tileset = createTilesetFromAtlas(atlas, 32, 32);
|
|
59
|
+
|
|
60
|
+
expect(tileset.atlas).toBe(atlas);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('yields zero rows and columns when atlas image is null', () => {
|
|
64
|
+
const atlas = createTextureAtlas();
|
|
65
|
+
const tileset = createTilesetFromAtlas(atlas, 32, 32);
|
|
66
|
+
|
|
67
|
+
expect(tileset.rows).toBe(0);
|
|
68
|
+
expect(tileset.columns).toBe(0);
|
|
69
|
+
expect(atlas.regions).toHaveLength(0);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('createTilesetFromImageResource', () => {
|
|
74
|
+
it('creates an atlas and tileset from an ImageResource', () => {
|
|
75
|
+
const source = createImageResourceFromImageElement({ width: 128, height: 64 } as HTMLImageElement);
|
|
76
|
+
const tileset = createTilesetFromImageResource(source, 32, 32);
|
|
77
|
+
|
|
78
|
+
expect(tileset.columns).toBe(4);
|
|
79
|
+
expect(tileset.rows).toBe(2);
|
|
80
|
+
expect(tileset.atlas?.image).toBe(source);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('initializes regions for each tile', () => {
|
|
84
|
+
const source = createImageResourceFromImageElement({ width: 64, height: 32 } as HTMLImageElement);
|
|
85
|
+
const tileset = createTilesetFromImageResource(source, 32, 32);
|
|
86
|
+
|
|
87
|
+
expect(tileset.atlas?.regions).toHaveLength(2);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('loadTilesetFromBase64', () => {
|
|
92
|
+
it('resolves to a Tileset with the correct tile dimensions', async () => {
|
|
93
|
+
const tileset = await loadTilesetFromBase64('abc123', 'image/png', 16, 16);
|
|
94
|
+
expect(tileset.tileWidth).toBe(16);
|
|
95
|
+
expect(tileset.tileHeight).toBe(16);
|
|
96
|
+
expect(tileset.atlas?.image?.source).toBeInstanceOf(HTMLImageElement);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('loadTilesetFromBlob', () => {
|
|
101
|
+
it('resolves to a Tileset with a non-null atlas image', async () => {
|
|
102
|
+
const blob = new Blob([], { type: 'image/png' });
|
|
103
|
+
const tileset = await loadTilesetFromBlob(blob, 32, 32);
|
|
104
|
+
expect(tileset.atlas?.image?.source).toBeInstanceOf(HTMLImageElement);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('loadTilesetFromBytes', () => {
|
|
109
|
+
it('resolves to a Tileset with tileWidth and tileHeight set', async () => {
|
|
110
|
+
const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
111
|
+
const tileset = await loadTilesetFromBytes(bytes, 32, 16);
|
|
112
|
+
|
|
113
|
+
expect(tileset.tileWidth).toBe(32);
|
|
114
|
+
expect(tileset.tileHeight).toBe(16);
|
|
115
|
+
expect(tileset.atlas?.image?.source).toBeInstanceOf(HTMLImageElement);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('throws when mime type cannot be detected', async () => {
|
|
119
|
+
const bytes = new Uint8Array(16);
|
|
120
|
+
await expect(loadTilesetFromBytes(bytes, 32, 32)).rejects.toThrow('Unable to determine image type');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('loadTilesetFromUrl', () => {
|
|
125
|
+
it('resolves to a Tileset with the correct tile dimensions', async () => {
|
|
126
|
+
const tileset = await loadTilesetFromUrl('data:image/png;base64,abc', 32, 32);
|
|
127
|
+
expect(tileset.tileWidth).toBe(32);
|
|
128
|
+
expect(tileset.tileHeight).toBe(32);
|
|
129
|
+
expect(tileset.atlas?.image?.source).toBeInstanceOf(HTMLImageElement);
|
|
130
|
+
});
|
|
131
|
+
});
|