@jgengine/assets 0.5.0 → 0.6.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/CHANGELOG.md +24 -0
- package/dist/catalogs/build.js +4 -7
- package/dist/cli/pull.js +9 -2
- package/dist/dims.d.ts +3 -0
- package/dist/dims.js +143 -0
- package/dist/download.d.ts +6 -0
- package/dist/download.js +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/indexGen.d.ts +2 -2
- package/dist/indexGen.js +15 -5
- package/dist/manifest.d.ts +4 -0
- package/dist/sources/kenney.js +4 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -15,6 +15,30 @@ the latest and surface the migration steps.
|
|
|
15
15
|
|
|
16
16
|
_Nothing yet._
|
|
17
17
|
|
|
18
|
+
## 0.6.0
|
|
19
|
+
|
|
20
|
+
An additive release: **every 0.5.0 API is unchanged**, so upgrading is only a
|
|
21
|
+
version bump. The headline is a whole outdoor-world layer — composable
|
|
22
|
+
`environment(...)` descriptors, renderer-free query primitives so gameplay reads
|
|
23
|
+
the same world the shell renders, a standalone rigid-body physics sim, and the
|
|
24
|
+
`@jgengine/shell` renderers that draw it all.
|
|
25
|
+
|
|
26
|
+
### Migrate
|
|
27
|
+
|
|
28
|
+
- Bump every `@jgengine/*` dependency to `^0.6.0` (the eight packages version in lockstep).
|
|
29
|
+
- No code change is required — 0.6.0 adds surface, it doesn't move or remove any.
|
|
30
|
+
- Optional: describe an outdoor scene once with `environment({ terrain, weather, vegetation, water, structures })` from `@jgengine/core/world/features`. `@jgengine/shell`'s `EnvironmentScene` renders it (terrain, rain/snow, grass, ocean, buildings), and gameplay reads the same world through the renderer-free primitives — `resolveTerrainField(...)` for ground-snap/collision, `windField(...)` for sway/sailing, `waterSurface(...)` for buoyancy, `scatter(...)` for prop/spawn placement, `buildingIndex(...)` for placement avoidance — no three.js needed.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- `@jgengine/core/world` query primitives — pure, renderer-free world sampling so gameplay and rendering read one source of truth. `world/terrain` (`TerrainField`, `noiseField`, `resolveTerrainField`, `resolveGroundStep` for slope-limited movement), `world/wind` (`windField` — one wind source for weather sway, grass, sailing, fire spread), `world/water` (`waterSurface` / `waterSurfaceFromDescriptor` — CPU Gerstner matching the ocean shader, for buoyancy and shorelines), `world/scatter` (`scatter` — seeded, overlap-aware point distribution for vegetation/props/spawns), `world/buildings` (`generateBuilding`, `generateBuildingDistrict`) and `world/buildingIndex` (`buildingIndex` — `at`/`within`/`nearest`/`isInside`/`blockers` over a district).
|
|
35
|
+
- `@jgengine/core/world/features` composable outdoor descriptors — `environment(...)` plus `terrain()`, `rain()`, `snow()`, `grass()`, `ocean()`, `building()`.
|
|
36
|
+
- `@jgengine/core/world/regions` (`createRegionField` — blend content-agnostic biomes by nearest selector, adding `tint`/`water`/`fog`/`speedMultiplier`/opaque `data`; extends `TerrainField` so it ground-snaps too) and `@jgengine/core/world/scatterItems` (`scatterItems` — region-driven content scatter: density per region, grounded, above-water/slope-aware, with `pickWeighted` for weighted rolls).
|
|
37
|
+
- `@jgengine/core/physics/physicsWorld` — standalone fixed-capacity rigid-body sim (SoA buffers, spatial-hash broadphase, sleeping) for many colliding dynamic bodies (piles, debris, stress scenes). Distinct from the `defineGame` `physics: { gravity }` character-controller config.
|
|
38
|
+
- `ctx.time` simulation clock (`@jgengine/core/time/simClock`) — `onTick`'s `dt` is now **game time**, so `rate * dt` decay/regen/AI obeys pause and fast-forward for free without per-system wiring. Configure with `defineGame({ time: { scale?, speeds?, dayLength?, start?, startPaused? } })` (all optional; default is real-time 1:1). `ctx.time` exposes `pause`/`play`/`toggle`/`setSpeed`/`cycleSpeed` + `snapshot()`/`calendar()`; `useGameClock()` binds it in React.
|
|
39
|
+
- `@jgengine/shell` environment/terrain/water/weather/structures renderers — `EnvironmentScene` mounts an `environment()` descriptor as R3F renderers; `GrassField`, `ProceduralGround`, `Ocean`, `RainField`, `SnowField`, `LightningStrike`, `GeneratedBuilding`, and `world/InstancedBodies` (renders `PhysicsWorld` bodies).
|
|
40
|
+
- `@jgengine/react/liveBind` — `useFrameBind` drives a DOM/SVG element from a per-frame value without re-rendering React (HUDs bound to live engine state never re-render or lag).
|
|
41
|
+
|
|
18
42
|
## 0.5.0
|
|
19
43
|
|
|
20
44
|
An additive release: **every 0.4.0 API is unchanged**, so upgrading is only a
|
package/dist/catalogs/build.js
CHANGED
|
@@ -11,26 +11,23 @@ export function buildCatalog(options = {}) {
|
|
|
11
11
|
const includeSingles = options.includeSingles ?? true;
|
|
12
12
|
const sourceFilter = options.sources === undefined ? null : new Set(options.sources);
|
|
13
13
|
const catalog = createAssetCatalog();
|
|
14
|
-
const urlById = new Map();
|
|
15
14
|
for (const entry of generatedIndex) {
|
|
16
15
|
if (sourceFilter !== null && !sourceFilter.has(entry.source))
|
|
17
16
|
continue;
|
|
18
17
|
const url = entryUrl(basePath, entry);
|
|
19
|
-
|
|
20
|
-
catalog.register(entry.id, { url });
|
|
18
|
+
catalog.register(entry.id, entry.dims === undefined ? { url } : { url, dims: entry.dims });
|
|
21
19
|
}
|
|
22
20
|
if (includeSingles) {
|
|
23
21
|
for (const single of singles) {
|
|
24
|
-
urlById.set(single.id, single.url);
|
|
25
22
|
catalog.register(single.id, { url: single.url });
|
|
26
23
|
}
|
|
27
24
|
}
|
|
28
25
|
if (includeAliases) {
|
|
29
26
|
for (const alias of aliases) {
|
|
30
|
-
const
|
|
31
|
-
if (
|
|
27
|
+
const ref = catalog.resolve(alias.target);
|
|
28
|
+
if (ref === null)
|
|
32
29
|
continue;
|
|
33
|
-
catalog.register(alias.key, { url });
|
|
30
|
+
catalog.register(alias.key, ref.dims === undefined ? { url: ref.url } : { url: ref.url, dims: ref.dims });
|
|
34
31
|
}
|
|
35
32
|
}
|
|
36
33
|
return catalog;
|
package/dist/cli/pull.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { downloadArchive, extractGlbs, resolveArchiveUrl, sha256Hex } from "../download.js";
|
|
5
|
+
import { downloadArchive, extractGlbs, extractTextures, resolveArchiveUrl, sha256Hex } from "../download.js";
|
|
6
6
|
import { generatedIndex } from "../generated/index.js";
|
|
7
7
|
import { reindex } from "../indexGen.js";
|
|
8
8
|
import { isScrapeDownload } from "../manifest.js";
|
|
@@ -71,7 +71,14 @@ async function cmdPull(argv) {
|
|
|
71
71
|
mkdirSync(outDir, { recursive: true });
|
|
72
72
|
for (const glb of glbs)
|
|
73
73
|
writeFileSync(join(outDir, glb.file), glb.bytes);
|
|
74
|
-
|
|
74
|
+
const textures = extractTextures(archive);
|
|
75
|
+
for (const texture of textures) {
|
|
76
|
+
const dest = join(outDir, texture.file);
|
|
77
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
78
|
+
writeFileSync(dest, texture.bytes);
|
|
79
|
+
}
|
|
80
|
+
const textureNote = textures.length > 0 ? ` + ${textures.length} texture(s)` : "";
|
|
81
|
+
console.log(`pulled ${glbs.length} models${textureNote} -> ${outDir}`);
|
|
75
82
|
}
|
|
76
83
|
function readSingles() {
|
|
77
84
|
if (!existsSync(singlesJson))
|
package/dist/dims.d.ts
ADDED
package/dist/dims.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
const IDENTITY = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
2
|
+
const GLB_MAGIC = 0x46546c67;
|
|
3
|
+
const CHUNK_JSON = 0x4e4f534a;
|
|
4
|
+
function multiply(a, b) {
|
|
5
|
+
const out = new Array(16);
|
|
6
|
+
for (let col = 0; col < 4; col += 1) {
|
|
7
|
+
for (let row = 0; row < 4; row += 1) {
|
|
8
|
+
let sum = 0;
|
|
9
|
+
for (let k = 0; k < 4; k += 1)
|
|
10
|
+
sum += a[k * 4 + row] * b[col * 4 + k];
|
|
11
|
+
out[col * 4 + row] = sum;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
function fromTRS(node) {
|
|
17
|
+
if (node.matrix !== undefined && node.matrix.length === 16)
|
|
18
|
+
return [...node.matrix];
|
|
19
|
+
const [tx, ty, tz] = node.translation ?? [0, 0, 0];
|
|
20
|
+
const [qx, qy, qz, qw] = node.rotation ?? [0, 0, 0, 1];
|
|
21
|
+
const [sx, sy, sz] = node.scale ?? [1, 1, 1];
|
|
22
|
+
const x2 = qx + qx;
|
|
23
|
+
const y2 = qy + qy;
|
|
24
|
+
const z2 = qz + qz;
|
|
25
|
+
const xx = qx * x2;
|
|
26
|
+
const xy = qx * y2;
|
|
27
|
+
const xz = qx * z2;
|
|
28
|
+
const yy = qy * y2;
|
|
29
|
+
const yz = qy * z2;
|
|
30
|
+
const zz = qz * z2;
|
|
31
|
+
const wx = qw * x2;
|
|
32
|
+
const wy = qw * y2;
|
|
33
|
+
const wz = qw * z2;
|
|
34
|
+
return [
|
|
35
|
+
(1 - (yy + zz)) * sx,
|
|
36
|
+
(xy + wz) * sx,
|
|
37
|
+
(xz - wy) * sx,
|
|
38
|
+
0,
|
|
39
|
+
(xy - wz) * sy,
|
|
40
|
+
(1 - (xx + zz)) * sy,
|
|
41
|
+
(yz + wx) * sy,
|
|
42
|
+
0,
|
|
43
|
+
(xz + wy) * sz,
|
|
44
|
+
(yz - wx) * sz,
|
|
45
|
+
(1 - (xx + yy)) * sz,
|
|
46
|
+
0,
|
|
47
|
+
tx,
|
|
48
|
+
ty,
|
|
49
|
+
tz,
|
|
50
|
+
1,
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
function transformPoint(m, x, y, z) {
|
|
54
|
+
return [
|
|
55
|
+
m[0] * x + m[4] * y + m[8] * z + m[12],
|
|
56
|
+
m[1] * x + m[5] * y + m[9] * z + m[13],
|
|
57
|
+
m[2] * x + m[6] * y + m[10] * z + m[14],
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function readJsonChunk(bytes) {
|
|
61
|
+
if (bytes.byteLength < 20)
|
|
62
|
+
return null;
|
|
63
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
64
|
+
if (view.getUint32(0, true) !== GLB_MAGIC)
|
|
65
|
+
return null;
|
|
66
|
+
const chunkLength = view.getUint32(12, true);
|
|
67
|
+
const chunkType = view.getUint32(16, true);
|
|
68
|
+
if (chunkType !== CHUNK_JSON)
|
|
69
|
+
return null;
|
|
70
|
+
const start = 20;
|
|
71
|
+
const end = start + chunkLength;
|
|
72
|
+
if (end > bytes.byteLength)
|
|
73
|
+
return null;
|
|
74
|
+
const json = new TextDecoder().decode(bytes.subarray(start, end));
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(json);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function accumulate(bounds, x, y, z) {
|
|
83
|
+
if (x < bounds.min[0])
|
|
84
|
+
bounds.min[0] = x;
|
|
85
|
+
if (y < bounds.min[1])
|
|
86
|
+
bounds.min[1] = y;
|
|
87
|
+
if (z < bounds.min[2])
|
|
88
|
+
bounds.min[2] = z;
|
|
89
|
+
if (x > bounds.max[0])
|
|
90
|
+
bounds.max[0] = x;
|
|
91
|
+
if (y > bounds.max[1])
|
|
92
|
+
bounds.max[1] = y;
|
|
93
|
+
if (z > bounds.max[2])
|
|
94
|
+
bounds.max[2] = z;
|
|
95
|
+
}
|
|
96
|
+
function walk(gltf, nodeIndex, parent, bounds) {
|
|
97
|
+
const node = gltf.nodes?.[nodeIndex];
|
|
98
|
+
if (node === undefined)
|
|
99
|
+
return;
|
|
100
|
+
const world = multiply(parent, fromTRS(node));
|
|
101
|
+
if (node.mesh !== undefined) {
|
|
102
|
+
const mesh = gltf.meshes?.[node.mesh];
|
|
103
|
+
for (const primitive of mesh?.primitives ?? []) {
|
|
104
|
+
const accessorIndex = primitive.attributes?.POSITION;
|
|
105
|
+
if (accessorIndex === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
const accessor = gltf.accessors?.[accessorIndex];
|
|
108
|
+
const min = accessor?.min;
|
|
109
|
+
const max = accessor?.max;
|
|
110
|
+
if (min === undefined || max === undefined || min.length < 3 || max.length < 3)
|
|
111
|
+
continue;
|
|
112
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
113
|
+
const cx = (corner & 1) === 0 ? min[0] : max[0];
|
|
114
|
+
const cy = (corner & 2) === 0 ? min[1] : max[1];
|
|
115
|
+
const cz = (corner & 4) === 0 ? min[2] : max[2];
|
|
116
|
+
const [wx, wy, wz] = transformPoint(world, cx, cy, cz);
|
|
117
|
+
accumulate(bounds, wx, wy, wz);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const child of node.children ?? [])
|
|
122
|
+
walk(gltf, child, world, bounds);
|
|
123
|
+
}
|
|
124
|
+
export function readGlbDims(bytes) {
|
|
125
|
+
const gltf = readJsonChunk(bytes);
|
|
126
|
+
if (gltf === null)
|
|
127
|
+
return null;
|
|
128
|
+
const sceneIndex = gltf.scene ?? 0;
|
|
129
|
+
const roots = gltf.scenes?.[sceneIndex]?.nodes ?? gltf.nodes?.map((_, index) => index) ?? [];
|
|
130
|
+
const bounds = {
|
|
131
|
+
min: [Infinity, Infinity, Infinity],
|
|
132
|
+
max: [-Infinity, -Infinity, -Infinity],
|
|
133
|
+
};
|
|
134
|
+
for (const root of roots)
|
|
135
|
+
walk(gltf, root, IDENTITY, bounds);
|
|
136
|
+
if (!Number.isFinite(bounds.min[0]) || !Number.isFinite(bounds.max[0]))
|
|
137
|
+
return null;
|
|
138
|
+
return {
|
|
139
|
+
footprint: { w: bounds.max[0] - bounds.min[0], d: bounds.max[2] - bounds.min[2] },
|
|
140
|
+
center: { x: (bounds.min[0] + bounds.max[0]) / 2, z: (bounds.min[2] + bounds.max[2]) / 2 },
|
|
141
|
+
minY: bounds.min[1],
|
|
142
|
+
};
|
|
143
|
+
}
|
package/dist/download.d.ts
CHANGED
|
@@ -3,8 +3,14 @@ export interface ExtractedGlb {
|
|
|
3
3
|
file: string;
|
|
4
4
|
bytes: Uint8Array;
|
|
5
5
|
}
|
|
6
|
+
export interface ExtractedTexture {
|
|
7
|
+
/** Path relative to the pack output dir, e.g. "Textures/colormap.png". */
|
|
8
|
+
file: string;
|
|
9
|
+
bytes: Uint8Array;
|
|
10
|
+
}
|
|
6
11
|
export declare function findArchiveUrl(html: string, pageUrl: string): string | null;
|
|
7
12
|
export declare function resolveArchiveUrl(source: AssetSource): Promise<string>;
|
|
8
13
|
export declare function downloadArchive(url: string): Promise<Uint8Array>;
|
|
9
14
|
export declare function extractGlbs(archive: Uint8Array): ExtractedGlb[];
|
|
15
|
+
export declare function extractTextures(archive: Uint8Array): ExtractedTexture[];
|
|
10
16
|
export declare function sha256Hex(bytes: Uint8Array): Promise<string>;
|
package/dist/download.js
CHANGED
|
@@ -60,6 +60,22 @@ export function extractGlbs(archive) {
|
|
|
60
60
|
}
|
|
61
61
|
return Array.from(byName, ([file, bytes]) => ({ file, bytes })).sort((a, b) => a.file.localeCompare(b.file));
|
|
62
62
|
}
|
|
63
|
+
const TEXTURE_ENTRY = /(?:^|\/)(Textures\/[^/]+)$/i;
|
|
64
|
+
export function extractTextures(archive) {
|
|
65
|
+
const entries = unzipSync(archive, {
|
|
66
|
+
filter: (file) => TEXTURE_ENTRY.test(file.name),
|
|
67
|
+
});
|
|
68
|
+
const byRel = new Map();
|
|
69
|
+
for (const [path, bytes] of Object.entries(entries)) {
|
|
70
|
+
const match = path.match(TEXTURE_ENTRY);
|
|
71
|
+
if (match === null)
|
|
72
|
+
continue;
|
|
73
|
+
const rel = match[1];
|
|
74
|
+
if (!byRel.has(rel))
|
|
75
|
+
byRel.set(rel, bytes);
|
|
76
|
+
}
|
|
77
|
+
return Array.from(byRel, ([file, bytes]) => ({ file, bytes })).sort((a, b) => a.file.localeCompare(b.file));
|
|
78
|
+
}
|
|
63
79
|
export async function sha256Hex(bytes) {
|
|
64
80
|
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
65
81
|
new Uint8Array(buffer).set(bytes);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { sources, sourceById, kenneySources, quaterniusSources, kaykitSources }
|
|
|
3
3
|
export { singles } from "./singles.js";
|
|
4
4
|
export { aliases } from "./aliases.js";
|
|
5
5
|
export { generatedIndex, generatedBySource } from "./generated/index.js";
|
|
6
|
+
export { readGlbDims } from "./dims.js";
|
|
6
7
|
export { buildCatalog, entryUrl, type BuildCatalogOptions } from "./catalogs/build.js";
|
|
7
8
|
export { createStarterCatalog } from "./catalogs/starter.js";
|
|
8
9
|
export { verifyManifest, type VerifyResult } from "./verify.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { sources, sourceById, kenneySources, quaterniusSources, kaykitSources }
|
|
|
3
3
|
export { singles } from "./singles.js";
|
|
4
4
|
export { aliases } from "./aliases.js";
|
|
5
5
|
export { generatedIndex, generatedBySource } from "./generated/index.js";
|
|
6
|
+
export { readGlbDims } from "./dims.js";
|
|
6
7
|
export { buildCatalog, entryUrl } from "./catalogs/build.js";
|
|
7
8
|
export { createStarterCatalog } from "./catalogs/starter.js";
|
|
8
9
|
export { verifyManifest } from "./verify.js";
|
package/dist/indexGen.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AssetSource, IndexEntry } from "./manifest.js";
|
|
1
|
+
import type { AssetSource, IndexEntry, ModelDims } from "./manifest.js";
|
|
2
2
|
export declare function keyFromFile(file: string): string;
|
|
3
|
-
export declare function entryForFile(source: AssetSource, file: string): IndexEntry;
|
|
3
|
+
export declare function entryForFile(source: AssetSource, file: string, dims?: ModelDims): IndexEntry;
|
|
4
4
|
export declare function indexSourceDir(source: AssetSource, dir: string): IndexEntry[];
|
|
5
5
|
export interface ReindexResult {
|
|
6
6
|
perSource: {
|
package/dist/indexGen.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { readGlbDims } from "./dims.js";
|
|
3
4
|
import { sourceById } from "./sources/index.js";
|
|
4
5
|
export function keyFromFile(file) {
|
|
5
6
|
return file.replace(/\.glb$/i, "");
|
|
6
7
|
}
|
|
7
|
-
export function entryForFile(source, file) {
|
|
8
|
+
export function entryForFile(source, file, dims) {
|
|
8
9
|
return {
|
|
9
10
|
id: `${source.id}/${keyFromFile(file)}`,
|
|
10
11
|
source: source.id,
|
|
11
12
|
categories: source.categories,
|
|
12
13
|
file,
|
|
14
|
+
...(dims === undefined ? {} : { dims }),
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
function collectGlbFiles(dir) {
|
|
@@ -20,19 +22,27 @@ function collectGlbFiles(dir) {
|
|
|
20
22
|
found.push(...collectGlbFiles(full));
|
|
21
23
|
}
|
|
22
24
|
else if (/\.glb$/i.test(name)) {
|
|
23
|
-
found.push(name);
|
|
25
|
+
found.push({ file: name, full });
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
return found;
|
|
27
29
|
}
|
|
30
|
+
function measureDims(full) {
|
|
31
|
+
try {
|
|
32
|
+
return readGlbDims(readFileSync(full)) ?? undefined;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
28
38
|
export function indexSourceDir(source, dir) {
|
|
29
39
|
const seen = new Set();
|
|
30
40
|
const entries = [];
|
|
31
|
-
for (const file of collectGlbFiles(dir).sort((a, b) => a.localeCompare(b))) {
|
|
41
|
+
for (const { file, full } of collectGlbFiles(dir).sort((a, b) => a.file.localeCompare(b.file))) {
|
|
32
42
|
if (seen.has(file))
|
|
33
43
|
continue;
|
|
34
44
|
seen.add(file);
|
|
35
|
-
entries.push(entryForFile(source, file));
|
|
45
|
+
entries.push(entryForFile(source, file, measureDims(full)));
|
|
36
46
|
}
|
|
37
47
|
return entries;
|
|
38
48
|
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ModelDims } from "@jgengine/core/scene/assetCatalog";
|
|
2
|
+
export type { ModelDims };
|
|
1
3
|
export type AssetProvider = "kenney" | "quaternius" | "kaykit" | "polypizza" | "itch" | "custom";
|
|
2
4
|
export interface PinnedDownload {
|
|
3
5
|
url: string;
|
|
@@ -23,6 +25,8 @@ export interface IndexEntry {
|
|
|
23
25
|
source: string;
|
|
24
26
|
categories: readonly string[];
|
|
25
27
|
file: string;
|
|
28
|
+
/** Footprint/center/minY measured from the GLB at reindex; absent when the model could not be read. */
|
|
29
|
+
dims?: ModelDims;
|
|
26
30
|
}
|
|
27
31
|
export interface AssetAlias {
|
|
28
32
|
key: string;
|
package/dist/sources/kenney.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const KENNEY_PACKS = [
|
|
2
|
+
{ id: "kenney-furniture", slug: "furniture-kit", title: "Furniture Kit", categories: ["furniture", "interior", "prop"] },
|
|
3
|
+
{ id: "kenney-mini-characters", slug: "mini-characters", title: "Mini Characters", categories: ["character", "rigged"] },
|
|
2
4
|
{ id: "kenney-nature", slug: "nature-kit", title: "Nature Kit", categories: ["nature", "environment", "prop"] },
|
|
3
5
|
{ id: "kenney-castle", slug: "castle-kit", title: "Castle Kit", categories: ["building", "medieval", "environment"] },
|
|
4
6
|
{ id: "kenney-city-commercial", slug: "city-kit-commercial", title: "City Kit (Commercial)", categories: ["building", "city", "environment"] },
|
|
@@ -19,6 +21,8 @@ const KENNEY_PACKS = [
|
|
|
19
21
|
{ id: "kenney-tower-defense", slug: "tower-defense-kit", title: "Tower Defense Kit", categories: ["tower", "environment", "prop"] },
|
|
20
22
|
{ id: "kenney-food", slug: "food-kit", title: "Food Kit", categories: ["food", "prop"] },
|
|
21
23
|
{ id: "kenney-blaster", slug: "blaster-kit", title: "Blaster Kit", categories: ["weapon", "prop"] },
|
|
24
|
+
{ id: "kenney-furniture", slug: "furniture-kit", title: "Furniture Kit", categories: ["furniture", "interior", "prop"] },
|
|
25
|
+
{ id: "kenney-mini-characters", slug: "mini-characters", title: "Mini Characters", categories: ["character", "prop"] },
|
|
22
26
|
];
|
|
23
27
|
export const kenneySources = KENNEY_PACKS.map((pack) => ({
|
|
24
28
|
id: pack.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jgengine/assets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Self-generating, license-verified index of thousands of CC0 3D models for JGengine. Sources fetch from providers' own CDNs at pull time; the npm tarball ships only the typed index and CLI (no GLB bytes).",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test": "bun test src"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@jgengine/core": "^0.
|
|
36
|
+
"@jgengine/core": "^0.6.0",
|
|
37
37
|
"fflate": "^0.8.2"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|