@forgeax/engine-import 0.1.19 → 0.1.21
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/.tsbuildinfo +1 -1
- package/dist/__tests__/scriptable-pack-texture.unit.test.d.ts +2 -0
- package/dist/__tests__/scriptable-pack-texture.unit.test.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +42 -18
- package/dist/index.mjs.map +1 -1
- package/dist/scriptable-pack-output-producers.d.ts +1 -0
- package/dist/scriptable-pack-output-producers.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/scriptable-pack-product.contract.test.ts +4 -5
- package/src/__tests__/scriptable-pack-production-producers.unit.test.ts +9 -6
- package/src/__tests__/scriptable-pack-texture.unit.test.ts +96 -0
- package/src/index.ts +1 -0
- package/src/scriptable-pack-output-producers.ts +57 -17
|
@@ -2,6 +2,7 @@ import type { ScriptablePackSceneComponent } from '@forgeax/engine-pack/source';
|
|
|
2
2
|
import { type AssetOutputProducer, AssetOutputProducerRegistry } from './scriptable-pack.js';
|
|
3
3
|
export declare const materialAssetOutputProducer: AssetOutputProducer;
|
|
4
4
|
export declare const meshAssetOutputProducer: AssetOutputProducer;
|
|
5
|
+
export declare const textureAssetOutputProducer: AssetOutputProducer;
|
|
5
6
|
export declare function createSceneAssetOutputProducer(sceneComponents?: readonly ScriptablePackSceneComponent[]): AssetOutputProducer;
|
|
6
7
|
export declare function createStandardAssetOutputProducerRegistry(sceneComponents?: readonly ScriptablePackSceneComponent[]): AssetOutputProducerRegistry;
|
|
7
8
|
//# sourceMappingURL=scriptable-pack-output-producers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scriptable-pack-output-producers.d.ts","sourceRoot":"","sources":["../src/scriptable-pack-output-producers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"scriptable-pack-output-producers.d.ts","sourceRoot":"","sources":["../src/scriptable-pack-output-producers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,6BAA6B,CAAC;AAkChF,OAAO,EAEL,KAAK,mBAAmB,EACxB,2BAA2B,EAE5B,MAAM,sBAAsB,CAAC;AAyZ9B,eAAO,MAAM,2BAA2B,qBAIvC,CAAC;AACF,eAAO,MAAM,uBAAuB,qBAA2D,CAAC;AAChG,eAAO,MAAM,0BAA0B,qBAItC,CAAC;AAEF,wBAAgB,8BAA8B,CAC5C,eAAe,GAAE,SAAS,4BAA4B,EAAO,GAC5D,mBAAmB,CAKrB;AAED,wBAAgB,yCAAyC,CACvD,eAAe,GAAE,SAAS,4BAA4B,EAAO,GAC5D,2BAA2B,CAuB7B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-import",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,12 +27,12 @@
|
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@forgeax/engine-ddc": "0.1.
|
|
31
|
-
"@forgeax/engine-ecs": "0.1.
|
|
32
|
-
"@forgeax/engine-geometry": "0.1.
|
|
33
|
-
"@forgeax/engine-pack": "0.1.
|
|
34
|
-
"@forgeax/engine-scene": "0.1.
|
|
35
|
-
"@forgeax/engine-types": "0.1.
|
|
30
|
+
"@forgeax/engine-ddc": "0.1.21",
|
|
31
|
+
"@forgeax/engine-ecs": "0.1.21",
|
|
32
|
+
"@forgeax/engine-geometry": "0.1.21",
|
|
33
|
+
"@forgeax/engine-pack": "0.1.21",
|
|
34
|
+
"@forgeax/engine-scene": "0.1.21",
|
|
35
|
+
"@forgeax/engine-types": "0.1.21",
|
|
36
36
|
"@noble/hashes": "^2.2.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
@@ -21,12 +21,11 @@ describe('ScriptablePack terminal product contract', () => {
|
|
|
21
21
|
ok({
|
|
22
22
|
hero: {
|
|
23
23
|
kind: 'texture',
|
|
24
|
-
width: 1,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
data: new Uint8Array(),
|
|
24
|
+
shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
|
|
25
|
+
format: 'rgba8unorm-srgb',
|
|
26
|
+
data: new Uint8Array(4),
|
|
28
27
|
colorSpace: 'srgb',
|
|
29
|
-
|
|
28
|
+
mips: { kind: 'none' },
|
|
30
29
|
},
|
|
31
30
|
}),
|
|
32
31
|
},
|
|
@@ -89,12 +89,11 @@ const matrixAssets = [
|
|
|
89
89
|
{ kind: 'scene', entities: [] } satisfies SceneAsset,
|
|
90
90
|
{
|
|
91
91
|
kind: 'texture',
|
|
92
|
-
width: 1,
|
|
93
|
-
|
|
94
|
-
format: 'rgba8unorm',
|
|
92
|
+
shape: { viewDimension: '2d', extent: { width: 1, height: 1 } },
|
|
93
|
+
format: 'rgba8unorm-srgb',
|
|
95
94
|
data: new Uint8Array([255, 255, 255, 255]),
|
|
96
95
|
colorSpace: 'srgb',
|
|
97
|
-
|
|
96
|
+
mips: { kind: 'none' },
|
|
98
97
|
} satisfies TextureAsset,
|
|
99
98
|
{
|
|
100
99
|
kind: 'equirect',
|
|
@@ -119,7 +118,11 @@ const matrixAssets = [
|
|
|
119
118
|
atlasHeight: 1,
|
|
120
119
|
},
|
|
121
120
|
} satisfies FontAsset,
|
|
122
|
-
{
|
|
121
|
+
{
|
|
122
|
+
kind: 'render-pipeline',
|
|
123
|
+
pipelineId: 'forgeax::standard',
|
|
124
|
+
renderPath: 'forward',
|
|
125
|
+
} satisfies RenderPipelineAsset,
|
|
123
126
|
{
|
|
124
127
|
kind: 'tileset',
|
|
125
128
|
atlases: [AssetGuid.format(ATLAS_GUID)],
|
|
@@ -179,7 +182,7 @@ describe('standard ScriptablePack output producers', () => {
|
|
|
179
182
|
scene: 'scene-pack/3',
|
|
180
183
|
skeleton: 'ordinary-pod/1',
|
|
181
184
|
skin: 'ordinary-pod/1',
|
|
182
|
-
texture: '
|
|
185
|
+
texture: 'texture-pack/1',
|
|
183
186
|
tileset: 'ordinary-pod/1',
|
|
184
187
|
video: 'ordinary-pod/1',
|
|
185
188
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { AssetGuid } from '@forgeax/engine-pack/guid';
|
|
2
|
+
import type { AssetGuid as AssetGuidType, TextureAsset } from '@forgeax/engine-types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { textureAssetOutputProducer } from '../scriptable-pack-output-producers.js';
|
|
5
|
+
|
|
6
|
+
const GUID = '019ffa97-3000-7000-8000-000000000201';
|
|
7
|
+
const SOURCE_KEY = 'density/volume';
|
|
8
|
+
|
|
9
|
+
function guid(value: string): AssetGuidType {
|
|
10
|
+
const parsed = AssetGuid.parse(value);
|
|
11
|
+
if (!parsed.ok) throw parsed.error;
|
|
12
|
+
return parsed.value;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function texture(shape: TextureAsset['shape'], data: Uint8Array): TextureAsset {
|
|
16
|
+
return {
|
|
17
|
+
kind: 'texture',
|
|
18
|
+
shape,
|
|
19
|
+
format: 'r8unorm',
|
|
20
|
+
colorSpace: 'linear',
|
|
21
|
+
mips: { kind: 'none' },
|
|
22
|
+
data,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('ScriptablePack texture output producer', () => {
|
|
27
|
+
it.each([
|
|
28
|
+
{
|
|
29
|
+
label: 'array',
|
|
30
|
+
shape: { viewDimension: '2d-array' as const, extent: { width: 8, height: 4, layers: 3 } },
|
|
31
|
+
bytes: 8 * 4 * 3,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: 'volume',
|
|
35
|
+
shape: { viewDimension: '3d' as const, extent: { width: 4, height: 4, depth: 4 } },
|
|
36
|
+
bytes: 4 * 4 * 4,
|
|
37
|
+
},
|
|
38
|
+
])('matches source producer facts for a $label logical asset', async ({ shape, bytes }) => {
|
|
39
|
+
const payload = texture(shape, new Uint8Array(bytes).fill(23));
|
|
40
|
+
const result = await textureAssetOutputProducer.produce({
|
|
41
|
+
guid: GUID,
|
|
42
|
+
sourceKey: SOURCE_KEY,
|
|
43
|
+
asset: payload,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(result.ok).toBe(true);
|
|
47
|
+
if (!result.ok) return;
|
|
48
|
+
expect(result.value.payload).toEqual(payload);
|
|
49
|
+
expect(result.value.artifacts.body).toMatchObject({
|
|
50
|
+
mediaType: 'application/x-forgeax-r8',
|
|
51
|
+
assetCodec: { name: 'r8unorm', version: '1' },
|
|
52
|
+
});
|
|
53
|
+
expect(result.value.artifacts.body?.bytes).toEqual(payload.data);
|
|
54
|
+
expect(result.value.refs).toEqual([]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('keeps one publication GUID and rejects slice or layer identity fabrication', async () => {
|
|
58
|
+
const payload = texture(
|
|
59
|
+
{ viewDimension: '3d', extent: { width: 2, height: 2, depth: 2 } },
|
|
60
|
+
new Uint8Array(8),
|
|
61
|
+
);
|
|
62
|
+
const result = await textureAssetOutputProducer.produce({
|
|
63
|
+
guid: AssetGuid.format(guid(GUID)),
|
|
64
|
+
sourceKey: SOURCE_KEY,
|
|
65
|
+
asset: payload,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(result.ok).toBe(true);
|
|
69
|
+
if (!result.ok) return;
|
|
70
|
+
expect((result.value.payload as TextureAsset).shape).toEqual(payload.shape);
|
|
71
|
+
expect(result.value.artifacts.body?.bytes.byteLength).toBe(8);
|
|
72
|
+
expect(result.value.refs).toEqual([]);
|
|
73
|
+
expect(result.value).not.toHaveProperty('sliceGuids');
|
|
74
|
+
expect(result.value).not.toHaveProperty('layerGuids');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('fails fast when the payload data is not a canonical byte stream', async () => {
|
|
78
|
+
const payload = texture(
|
|
79
|
+
{ viewDimension: '3d', extent: { width: 2, height: 2, depth: 2 } },
|
|
80
|
+
new Uint8Array(7),
|
|
81
|
+
);
|
|
82
|
+
const result = await textureAssetOutputProducer.produce({
|
|
83
|
+
guid: GUID,
|
|
84
|
+
sourceKey: SOURCE_KEY,
|
|
85
|
+
asset: payload,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(result.ok).toBe(false);
|
|
89
|
+
if (!result.ok) {
|
|
90
|
+
expect(result.error.code).toBe('import-internal-error');
|
|
91
|
+
expect(result.error.detail).toMatchObject({
|
|
92
|
+
reason: expect.stringContaining('texture data is not canonical'),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -126,6 +126,7 @@ export {
|
|
|
126
126
|
createStandardAssetOutputProducerRegistry,
|
|
127
127
|
materialAssetOutputProducer,
|
|
128
128
|
meshAssetOutputProducer,
|
|
129
|
+
textureAssetOutputProducer,
|
|
129
130
|
} from './scriptable-pack-output-producers.js';
|
|
130
131
|
export {
|
|
131
132
|
createScriptablePackStagedAssetSnapshotSource,
|
|
@@ -23,7 +23,13 @@ import type {
|
|
|
23
23
|
TilesetAsset,
|
|
24
24
|
VideoAsset,
|
|
25
25
|
} from '@forgeax/engine-types';
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
deriveTextureLayout,
|
|
28
|
+
err,
|
|
29
|
+
ImportError,
|
|
30
|
+
MATERIAL_TEXTURE_SLOTS,
|
|
31
|
+
ok,
|
|
32
|
+
} from '@forgeax/engine-types';
|
|
27
33
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
28
34
|
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
29
35
|
import { packMeshBinV4 } from './mesh-bin.js';
|
|
@@ -105,7 +111,9 @@ function materialProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
|
105
111
|
? new Set<string>(MATERIAL_TEXTURE_SLOTS)
|
|
106
112
|
: new Set(
|
|
107
113
|
material.parameters
|
|
108
|
-
.filter(
|
|
114
|
+
.filter(
|
|
115
|
+
(parameter) => parameter.type === 'texture' || parameter.type === 'texture_cube',
|
|
116
|
+
)
|
|
109
117
|
.map((parameter) => parameter.name),
|
|
110
118
|
);
|
|
111
119
|
|
|
@@ -246,23 +254,50 @@ function bytes(value: Uint8Array | Uint8ClampedArray): Uint8Array {
|
|
|
246
254
|
return Uint8Array.from(value);
|
|
247
255
|
}
|
|
248
256
|
|
|
257
|
+
function textureProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
258
|
+
if (input.asset.kind !== 'texture') throw new TypeError('expected TextureAsset');
|
|
259
|
+
const texture = input.asset as TextureAsset;
|
|
260
|
+
const layout = deriveTextureLayout({
|
|
261
|
+
shape: texture.shape,
|
|
262
|
+
format: texture.format,
|
|
263
|
+
mips: texture.mips,
|
|
264
|
+
actualByteLength: texture.data.byteLength,
|
|
265
|
+
order: 'mip-major,image-major,row-major',
|
|
266
|
+
});
|
|
267
|
+
if (!layout.ok) {
|
|
268
|
+
const expectedBytes =
|
|
269
|
+
layout.error.code === 'texture-packing-invalid'
|
|
270
|
+
? layout.error.detail.expectedBytes
|
|
271
|
+
: texture.data.byteLength;
|
|
272
|
+
const actualBytes =
|
|
273
|
+
layout.error.code === 'texture-packing-invalid'
|
|
274
|
+
? layout.error.detail.actualBytes
|
|
275
|
+
: texture.data.byteLength;
|
|
276
|
+
throw new TypeError(
|
|
277
|
+
`texture data is not canonical: expected ${expectedBytes} bytes, got ${actualBytes}`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
payload: texture,
|
|
282
|
+
refs: [],
|
|
283
|
+
artifacts: {
|
|
284
|
+
body: {
|
|
285
|
+
mediaType:
|
|
286
|
+
texture.format === 'r8unorm'
|
|
287
|
+
? 'application/x-forgeax-r8'
|
|
288
|
+
: `application/x-forgeax-${texture.format}`,
|
|
289
|
+
assetCodec: { name: texture.format, version: '1' },
|
|
290
|
+
bytes: bytes(texture.data),
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
249
296
|
function ordinaryPodProduct(input: AssetOutputInput): AssetOutputProduct {
|
|
250
297
|
const asset = input.asset;
|
|
251
298
|
switch (asset.kind) {
|
|
252
|
-
case 'texture':
|
|
253
|
-
|
|
254
|
-
return {
|
|
255
|
-
payload: texture,
|
|
256
|
-
refs: [],
|
|
257
|
-
artifacts: {
|
|
258
|
-
body: {
|
|
259
|
-
mediaType: 'image/raw',
|
|
260
|
-
assetCodec: { name: 'raw-image', version: '1' },
|
|
261
|
-
bytes: bytes(texture.data),
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
};
|
|
265
|
-
}
|
|
299
|
+
case 'texture':
|
|
300
|
+
return textureProduct(input);
|
|
266
301
|
case 'equirect': {
|
|
267
302
|
const equirect = asset as EquirectAsset;
|
|
268
303
|
return {
|
|
@@ -418,6 +453,11 @@ export const materialAssetOutputProducer = createSafeProducer(
|
|
|
418
453
|
materialProduct,
|
|
419
454
|
);
|
|
420
455
|
export const meshAssetOutputProducer = createSafeProducer('mesh', 'mesh-binary/4', meshProduct);
|
|
456
|
+
export const textureAssetOutputProducer = createSafeProducer(
|
|
457
|
+
'texture',
|
|
458
|
+
'texture-pack/1',
|
|
459
|
+
textureProduct,
|
|
460
|
+
);
|
|
421
461
|
|
|
422
462
|
export function createSceneAssetOutputProducer(
|
|
423
463
|
sceneComponents: readonly ScriptablePackSceneComponent[] = [],
|
|
@@ -434,9 +474,9 @@ export function createStandardAssetOutputProducerRegistry(
|
|
|
434
474
|
const registry = new AssetOutputProducerRegistry();
|
|
435
475
|
registry.register(materialAssetOutputProducer);
|
|
436
476
|
registry.register(meshAssetOutputProducer);
|
|
477
|
+
registry.register(textureAssetOutputProducer);
|
|
437
478
|
registry.register(createSceneAssetOutputProducer(sceneComponents));
|
|
438
479
|
for (const kind of [
|
|
439
|
-
'texture',
|
|
440
480
|
'equirect',
|
|
441
481
|
'sampler',
|
|
442
482
|
'font',
|