@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.
@@ -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;AA4BhF,OAAO,EAEL,KAAK,mBAAmB,EACxB,2BAA2B,EAE5B,MAAM,sBAAsB,CAAC;AA4X9B,eAAO,MAAM,2BAA2B,qBAIvC,CAAC;AACF,eAAO,MAAM,uBAAuB,qBAA2D,CAAC;AAEhG,wBAAgB,8BAA8B,CAC5C,eAAe,GAAE,SAAS,4BAA4B,EAAO,GAC5D,mBAAmB,CAKrB;AAED,wBAAgB,yCAAyC,CACvD,eAAe,GAAE,SAAS,4BAA4B,EAAO,GAC5D,2BAA2B,CAuB7B"}
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.19",
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.19",
31
- "@forgeax/engine-ecs": "0.1.19",
32
- "@forgeax/engine-geometry": "0.1.19",
33
- "@forgeax/engine-pack": "0.1.19",
34
- "@forgeax/engine-scene": "0.1.19",
35
- "@forgeax/engine-types": "0.1.19",
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
- height: 1,
26
- format: 'rgba8unorm',
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
- mipmap: false,
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
- height: 1,
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
- mipmap: false,
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
- { kind: 'render-pipeline', pipelineId: 'forgeax::urp' } satisfies RenderPipelineAsset,
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: 'ordinary-pod/1',
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 { err, ImportError, MATERIAL_TEXTURE_SLOTS, ok } from '@forgeax/engine-types';
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((parameter) => parameter.type === 'texture')
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
- const texture = asset as TextureAsset;
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',