@flighthq/scene3d-resources 0.3.0-next.906.07cea63 → 0.3.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.
Files changed (108) hide show
  1. package/dist/anisotropyPbrScene3DMaterialTextures.d.ts +3 -0
  2. package/dist/anisotropyPbrScene3DMaterialTextures.d.ts.map +1 -0
  3. package/dist/anisotropyPbrScene3DMaterialTextures.js +10 -0
  4. package/dist/anisotropyPbrScene3DMaterialTextures.js.map +1 -0
  5. package/dist/awd2Load.d.ts.map +1 -1
  6. package/dist/awd2Load.js +3 -2
  7. package/dist/awd2Load.js.map +1 -1
  8. package/dist/clearcoatPbrScene3DMaterialTextures.d.ts +3 -0
  9. package/dist/clearcoatPbrScene3DMaterialTextures.d.ts.map +1 -0
  10. package/dist/clearcoatPbrScene3DMaterialTextures.js +14 -0
  11. package/dist/clearcoatPbrScene3DMaterialTextures.js.map +1 -0
  12. package/dist/contract.d.ts +10 -1
  13. package/dist/contract.d.ts.map +1 -1
  14. package/dist/contract.js +10 -1
  15. package/dist/contract.js.map +1 -1
  16. package/dist/enableScene3DResourceFailureGuards.d.ts.map +1 -1
  17. package/dist/enableScene3DResourceFailureGuards.js +2 -2
  18. package/dist/enableScene3DResourceFailureGuards.js.map +1 -1
  19. package/dist/explainScene3DResourceCoverage.d.ts +4 -0
  20. package/dist/explainScene3DResourceCoverage.d.ts.map +1 -0
  21. package/dist/explainScene3DResourceCoverage.js +42 -0
  22. package/dist/explainScene3DResourceCoverage.js.map +1 -0
  23. package/dist/getScene3DResourceTextures.d.ts +3 -2
  24. package/dist/getScene3DResourceTextures.d.ts.map +1 -1
  25. package/dist/getScene3DResourceTextures.js +23 -27
  26. package/dist/getScene3DResourceTextures.js.map +1 -1
  27. package/dist/imageResourceFetch.d.ts +2 -2
  28. package/dist/imageResourceFetch.d.ts.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/iridescencePbrScene3DMaterialTextures.d.ts +3 -0
  34. package/dist/iridescencePbrScene3DMaterialTextures.d.ts.map +1 -0
  35. package/dist/iridescencePbrScene3DMaterialTextures.js +12 -0
  36. package/dist/iridescencePbrScene3DMaterialTextures.js.map +1 -0
  37. package/dist/loadScene3DResources.d.ts +4 -0
  38. package/dist/loadScene3DResources.d.ts.map +1 -0
  39. package/dist/{resolveScene3DResourcesAndWait.js → loadScene3DResources.js} +11 -21
  40. package/dist/loadScene3DResources.js.map +1 -0
  41. package/dist/resolveScene3DResources.d.ts +4 -3
  42. package/dist/resolveScene3DResources.d.ts.map +1 -1
  43. package/dist/resolveScene3DResources.js +49 -26
  44. package/dist/resolveScene3DResources.js.map +1 -1
  45. package/dist/revealScene3DResourcesOnResolve.d.ts +2 -2
  46. package/dist/revealScene3DResourcesOnResolve.d.ts.map +1 -1
  47. package/dist/revealScene3DResourcesOnResolve.js +9 -6
  48. package/dist/revealScene3DResourcesOnResolve.js.map +1 -1
  49. package/dist/sceneMaterialTextureRegistry.d.ts +6 -2
  50. package/dist/sceneMaterialTextureRegistry.d.ts.map +1 -1
  51. package/dist/sceneMaterialTextureRegistry.js +31 -8
  52. package/dist/sceneMaterialTextureRegistry.js.map +1 -1
  53. package/dist/sceneResourceRecovery.d.ts +2 -4
  54. package/dist/sceneResourceRecovery.d.ts.map +1 -1
  55. package/dist/sceneResourceRecovery.js +9 -28
  56. package/dist/sceneResourceRecovery.js.map +1 -1
  57. package/dist/sceneResourceResolver.d.ts.map +1 -1
  58. package/dist/sceneResourceResolver.js +6 -2
  59. package/dist/sceneResourceResolver.js.map +1 -1
  60. package/dist/shadedScene3DMaterialTextures.d.ts +3 -0
  61. package/dist/shadedScene3DMaterialTextures.d.ts.map +1 -0
  62. package/dist/shadedScene3DMaterialTextures.js +30 -0
  63. package/dist/shadedScene3DMaterialTextures.js.map +1 -0
  64. package/dist/sheenPbrScene3DMaterialTextures.d.ts +3 -0
  65. package/dist/sheenPbrScene3DMaterialTextures.d.ts.map +1 -0
  66. package/dist/sheenPbrScene3DMaterialTextures.js +12 -0
  67. package/dist/sheenPbrScene3DMaterialTextures.js.map +1 -0
  68. package/dist/specularPbrScene3DMaterialTextures.d.ts +3 -0
  69. package/dist/specularPbrScene3DMaterialTextures.d.ts.map +1 -0
  70. package/dist/specularPbrScene3DMaterialTextures.js +12 -0
  71. package/dist/specularPbrScene3DMaterialTextures.js.map +1 -0
  72. package/dist/transmissionVolumePbrScene3DMaterialTextures.d.ts +3 -0
  73. package/dist/transmissionVolumePbrScene3DMaterialTextures.d.ts.map +1 -0
  74. package/dist/transmissionVolumePbrScene3DMaterialTextures.js +12 -0
  75. package/dist/transmissionVolumePbrScene3DMaterialTextures.js.map +1 -0
  76. package/dist/wrappedDiffusePbrScene3DMaterialTextures.d.ts +3 -0
  77. package/dist/wrappedDiffusePbrScene3DMaterialTextures.d.ts.map +1 -0
  78. package/dist/wrappedDiffusePbrScene3DMaterialTextures.js +12 -0
  79. package/dist/wrappedDiffusePbrScene3DMaterialTextures.js.map +1 -0
  80. package/package.json +16 -13
  81. package/src/anisotropyPbrScene3DMaterialTextures.test.ts +26 -0
  82. package/src/awd2Load.test.ts +2 -2
  83. package/src/clearcoatPbrScene3DMaterialTextures.test.ts +36 -0
  84. package/src/enableScene3DResourceFailureGuards.test.ts +9 -8
  85. package/src/explainScene3DResourceCoverage.test.ts +115 -0
  86. package/src/getScene3DResourceTextures.test.ts +65 -38
  87. package/src/gltfLoad.test.ts +2 -2
  88. package/src/imageResourceFetch.test.ts +3 -3
  89. package/src/iridescencePbrScene3DMaterialTextures.test.ts +34 -0
  90. package/src/{resolveScene3DResourcesAndWait.test.ts → loadScene3DResources.test.ts} +40 -18
  91. package/src/md2Load.test.ts +2 -2
  92. package/src/md5Load.test.ts +2 -2
  93. package/src/objLoad.test.ts +2 -2
  94. package/src/resolveScene3DResources.test.ts +136 -53
  95. package/src/revealScene3DResourcesOnResolve.test.ts +39 -21
  96. package/src/sceneMaterialTextureRegistry.test.ts +118 -26
  97. package/src/sceneResourceRecovery.test.ts +13 -47
  98. package/src/sceneResourceResolver.test.ts +14 -11
  99. package/src/sceneResourceResolverTreeShaking.test.ts +1 -1
  100. package/src/shadedScene3DMaterialTextures.test.ts +52 -0
  101. package/src/sheenPbrScene3DMaterialTextures.test.ts +29 -0
  102. package/src/specularPbrScene3DMaterialTextures.test.ts +29 -0
  103. package/src/threeDsLoad.test.ts +2 -2
  104. package/src/transmissionVolumePbrScene3DMaterialTextures.test.ts +34 -0
  105. package/src/wrappedDiffusePbrScene3DMaterialTextures.test.ts +34 -0
  106. package/dist/resolveScene3DResourcesAndWait.d.ts +0 -4
  107. package/dist/resolveScene3DResourcesAndWait.d.ts.map +0 -1
  108. package/dist/resolveScene3DResourcesAndWait.js.map +0 -1
@@ -0,0 +1,12 @@
1
+ import { WrappedDiffusePbrExtensionKind } from '@flighthq/types/contract';
2
+ import { registerScene3DPbrExtensionTextures } from './sceneMaterialTextureRegistry';
3
+ export function registerWrappedDiffusePbrScene3DMaterialTextures(registry) {
4
+ registerScene3DPbrExtensionTextures(registry, WrappedDiffusePbrExtensionKind, (extension, out) => {
5
+ const wrappedDiffuse = extension;
6
+ if (wrappedDiffuse.wrappedDiffuseMap !== null)
7
+ out.push(wrappedDiffuse.wrappedDiffuseMap);
8
+ if (wrappedDiffuse.thicknessMap !== null)
9
+ out.push(wrappedDiffuse.thicknessMap);
10
+ });
11
+ }
12
+ //# sourceMappingURL=wrappedDiffusePbrScene3DMaterialTextures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrappedDiffusePbrScene3DMaterialTextures.js","sourceRoot":"","sources":["../src/wrappedDiffusePbrScene3DMaterialTextures.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,EAAE,mCAAmC,EAAE,MAAM,gCAAgC,CAAC;AAErF,MAAM,UAAU,gDAAgD,CAAC,QAAwC;IACvG,mCAAmC,CAAC,QAAQ,EAAE,8BAA8B,EAAE,CAAC,SAAS,EAAE,GAAG,EAAQ,EAAE;QACrG,MAAM,cAAc,GAAG,SAAiD,CAAC;QACzE,IAAI,cAAc,CAAC,iBAAiB,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,iBAAiB,CAAC,CAAC;QAC1F,IAAI,cAAc,CAAC,YAAY,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,8 @@
1
1
  {
2
2
  "name": "@flighthq/scene3d-resources",
3
- "version": "0.3.0-next.906.07cea63",
3
+ "version": "0.3.0",
4
+ "author": "Joshua Granick and other contributors",
5
+ "license": "MIT",
4
6
  "repository": {
5
7
  "type": "git",
6
8
  "url": "https://github.com/flighthq/flight.git",
@@ -36,22 +38,23 @@
36
38
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
37
39
  },
38
40
  "dependencies": {
39
- "@flighthq/entity": "0.3.0-next.906.07cea63",
40
- "@flighthq/image": "0.3.0-next.906.07cea63",
41
- "@flighthq/loader": "0.3.0-next.906.07cea63",
42
- "@flighthq/log": "0.3.0-next.906.07cea63",
43
- "@flighthq/net": "0.3.0-next.906.07cea63",
44
- "@flighthq/node": "0.3.0-next.906.07cea63",
45
- "@flighthq/scene3d": "0.3.0-next.906.07cea63",
46
- "@flighthq/scene3d-formats": "0.3.0-next.906.07cea63",
47
- "@flighthq/signals": "0.3.0-next.906.07cea63",
48
- "@flighthq/tween": "0.3.0-next.906.07cea63",
49
- "@flighthq/types": "0.3.0-next.906.07cea63"
41
+ "@flighthq/entity": "0.3.0",
42
+ "@flighthq/image": "0.3.0",
43
+ "@flighthq/loader": "0.3.0",
44
+ "@flighthq/log": "0.3.0",
45
+ "@flighthq/net": "0.3.0",
46
+ "@flighthq/node": "0.3.0",
47
+ "@flighthq/scene3d": "0.3.0",
48
+ "@flighthq/scene3d-formats": "0.3.0",
49
+ "@flighthq/signals": "0.3.0",
50
+ "@flighthq/texture": "0.3.0",
51
+ "@flighthq/tween": "0.3.0",
52
+ "@flighthq/types": "0.3.0"
50
53
  },
51
54
  "devDependencies": {
52
55
  "@flighthq/materials": "*",
53
56
  "@flighthq/mesh": "*",
54
- "@flighthq/texture": "*",
57
+ "@flighthq/shading": "*",
55
58
  "typescript": "^5.3.0"
56
59
  },
57
60
  "description": "Renderer-neutral scene-document URL acquisition and caller-driven resolution of scene texture references into live ImageResources",
@@ -0,0 +1,26 @@
1
+ import { createAnisotropyPbrExtension, createExtendedPbrMaterial } from '@flighthq/materials/contract';
2
+ import { createTexture } from '@flighthq/texture/contract';
3
+ import type { Texture } from '@flighthq/types/contract';
4
+
5
+ import { registerAnisotropyPbrScene3DMaterialTextures } from './anisotropyPbrScene3DMaterialTextures';
6
+ import {
7
+ createScene3DMaterialTextureRegistry,
8
+ getScene3DMaterialTextures,
9
+ registerExtendedPbrScene3DMaterialTextures,
10
+ } from './sceneMaterialTextureRegistry';
11
+
12
+ describe('registerAnisotropyPbrScene3DMaterialTextures', () => {
13
+ it('lists the anisotropy map through the generic Extended PBR material lister', () => {
14
+ const registry = createScene3DMaterialTextureRegistry();
15
+ registerExtendedPbrScene3DMaterialTextures(registry);
16
+ registerAnisotropyPbrScene3DMaterialTextures(registry);
17
+ const map = createTexture();
18
+ const out: Texture[] = [];
19
+ getScene3DMaterialTextures(
20
+ registry,
21
+ createExtendedPbrMaterial({ extensions: [createAnisotropyPbrExtension({ anisotropyMap: map })] }),
22
+ out,
23
+ );
24
+ expect(out).toEqual([map]);
25
+ });
26
+ });
@@ -39,8 +39,8 @@ beforeAll(async () => {
39
39
  });
40
40
 
41
41
  afterAll(() => {
42
- vi.doUnmock('@flighthq/net');
43
- vi.doUnmock('@flighthq/scene3d-formats');
42
+ vi.doUnmock('@flighthq/net/contract');
43
+ vi.doUnmock('@flighthq/scene3d-formats/contract');
44
44
  vi.resetModules();
45
45
  });
46
46
 
@@ -0,0 +1,36 @@
1
+ import { createClearcoatPbrExtension, createExtendedPbrMaterial } from '@flighthq/materials/contract';
2
+ import { createTexture } from '@flighthq/texture/contract';
3
+ import type { Texture } from '@flighthq/types/contract';
4
+
5
+ import { registerClearcoatPbrScene3DMaterialTextures } from './clearcoatPbrScene3DMaterialTextures';
6
+ import {
7
+ createScene3DMaterialTextureRegistry,
8
+ getScene3DMaterialTextures,
9
+ registerExtendedPbrScene3DMaterialTextures,
10
+ } from './sceneMaterialTextureRegistry';
11
+
12
+ describe('registerClearcoatPbrScene3DMaterialTextures', () => {
13
+ it('lists all clearcoat maps in descriptor order', () => {
14
+ const registry = createScene3DMaterialTextureRegistry();
15
+ registerExtendedPbrScene3DMaterialTextures(registry);
16
+ registerClearcoatPbrScene3DMaterialTextures(registry);
17
+ const factor = createTexture();
18
+ const normal = createTexture();
19
+ const roughness = createTexture();
20
+ const out: Texture[] = [];
21
+ getScene3DMaterialTextures(
22
+ registry,
23
+ createExtendedPbrMaterial({
24
+ extensions: [
25
+ createClearcoatPbrExtension({
26
+ clearcoatMap: factor,
27
+ clearcoatNormalMap: normal,
28
+ clearcoatRoughnessMap: roughness,
29
+ }),
30
+ ],
31
+ }),
32
+ out,
33
+ );
34
+ expect(out).toEqual([factor, normal, roughness]);
35
+ });
36
+ });
@@ -63,9 +63,10 @@ describe('enableScene3DResourceFailureGuards', () => {
63
63
  addLogSink(sink.sink);
64
64
  const resolver = createScene3DResourceResolver();
65
65
  const signals = enableScene3DResourceSignals(resolver);
66
- const texture = createTexture({ resource: failedRef() });
66
+ const ref = failedRef();
67
+ const texture = createTexture({ resource: ref });
67
68
  const dispose = enableScene3DResourceFailureGuards(resolver);
68
- const event = { ref: texture.resource!, texture };
69
+ const event = { ref, texture };
69
70
 
70
71
  expect(() => emitSignal(signals.onResourceFailed, event)).not.toThrow();
71
72
  emitSignal(signals.onResourceFailed, event);
@@ -76,13 +77,12 @@ describe('enableScene3DResourceFailureGuards', () => {
76
77
  failureKind: ImageResourceFailureKind.Error,
77
78
  failureMessage: 'bad image',
78
79
  failureName: 'CodecError',
79
- message:
80
- 'resolveScene3DResources: image resource resolution failed — call retryFailedScene3DResources to request it again',
80
+ message: 'Scene3D resource acquisition failed — call retryFailedScene3DResources to request it again',
81
81
  });
82
- expect(texture.resource?.state).toBe(ResourceResolutionState.Failed);
82
+ expect(ref.state).toBe(ResourceResolutionState.Failed);
83
83
 
84
84
  emitSignal(signals.onResourceResolved, event);
85
- texture.resource!.failure = {
85
+ ref.failure = {
86
86
  kind: ImageResourceFailureKind.Unavailable,
87
87
  message: 'still unavailable',
88
88
  name: null,
@@ -102,11 +102,12 @@ describe('enableScene3DResourceFailureGuards', () => {
102
102
  addLogSink(sink.sink);
103
103
  const resolver = createScene3DResourceResolver();
104
104
  const signals = enableScene3DResourceSignals(resolver);
105
- const texture = createTexture({ resource: failedRef() });
105
+ const ref = failedRef();
106
+ const texture = createTexture({ resource: ref });
106
107
  const firstDispose = enableScene3DResourceFailureGuards(resolver);
107
108
  const secondDispose = enableScene3DResourceFailureGuards(resolver);
108
109
 
109
- emitSignal(signals.onResourceFailed, { ref: texture.resource!, texture });
110
+ emitSignal(signals.onResourceFailed, { ref, texture });
110
111
  expect(getMemoryLogSinkEntries(sink)).toHaveLength(1);
111
112
  secondDispose();
112
113
  expect(areScene3DResourceFailureGuardsEnabled(resolver)).toBe(false);
@@ -0,0 +1,115 @@
1
+ import { createBlinnPhongMaterial, createUnlitMaterial } from '@flighthq/materials/contract';
2
+ import { createBoxMeshGeometry } from '@flighthq/mesh/contract';
3
+ import { addNodeChild } from '@flighthq/node/contract';
4
+ import { createMesh, createScene3D, createScene3DKindUsage, getScene3DKindUsage } from '@flighthq/scene3d/contract';
5
+ import { createShadedMaterial } from '@flighthq/shading/contract';
6
+ import type { Material, SceneCoverageEntry, Scene3DKindUsage } from '@flighthq/types/contract';
7
+ import { RenderRegistry, SceneCoverage } from '@flighthq/types/contract';
8
+ import { describe, expect, it } from 'vitest';
9
+
10
+ import { explainScene3DResourceCoverage, hasScene3DResourceCoverage } from './explainScene3DResourceCoverage';
11
+ import { createBuiltInScene3DResourceResolver, createScene3DResourceResolver } from './sceneResourceResolver';
12
+ import { registerShadedScene3DMaterialTextures } from './shadedScene3DMaterialTextures';
13
+
14
+ function usageOf(...materials: Material[]): Scene3DKindUsage {
15
+ const scene = createScene3D();
16
+ for (const material of materials) {
17
+ addNodeChild(scene.root, createMesh(createBoxMeshGeometry(), [material]));
18
+ }
19
+ const usage = createScene3DKindUsage();
20
+ getScene3DKindUsage(usage, scene);
21
+ return usage;
22
+ }
23
+
24
+ function gaps(resolver: Parameters<typeof explainScene3DResourceCoverage>[1], usage: Scene3DKindUsage) {
25
+ const out: SceneCoverageEntry[] = [];
26
+ explainScene3DResourceCoverage(out, resolver, usage);
27
+ return out;
28
+ }
29
+
30
+ describe('explainScene3DResourceCoverage', () => {
31
+ it('reports a bare resolver as missing every material kind, since its registry starts empty', () => {
32
+ const found = gaps(createScene3DResourceResolver(), usageOf(createUnlitMaterial()));
33
+ expect(found).toEqual([
34
+ { coverage: SceneCoverage.Missing, kind: 'UnlitMaterial', registry: RenderRegistry.MaterialTextureLister },
35
+ ]);
36
+ });
37
+
38
+ it('reports a covered kind as Satisfied rather than omitting it', () => {
39
+ // The manifest lists every requirement, so a caller can render a checklist and tell "covered" from
40
+ // "never asked about". Omitting covered kinds would make those two indistinguishable.
41
+ expect(gaps(createBuiltInScene3DResourceResolver(), usageOf(createUnlitMaterial()))).toEqual([
42
+ { coverage: SceneCoverage.Satisfied, kind: 'UnlitMaterial', registry: RenderRegistry.MaterialTextureLister },
43
+ ]);
44
+ });
45
+
46
+ it('reports ShadedMaterial against the built-in assembly, which does not wire its lister', () => {
47
+ // The gap this query exists to surface: shading ships registerShadedScene3DMaterialTextures, but
48
+ // createBuiltInScene3DResourceResolver covers only the surface-PBR families, so an AWD2 document
49
+ // loaded through the built-in assembly has no lister for its materials.
50
+ expect(gaps(createBuiltInScene3DResourceResolver(), usageOf(createShadedMaterial()))).toContainEqual({
51
+ coverage: SceneCoverage.Missing,
52
+ kind: 'ShadedMaterial',
53
+ registry: RenderRegistry.MaterialTextureLister,
54
+ });
55
+ });
56
+
57
+ it('stops reporting once the named door for that family is called', () => {
58
+ const resolver = createBuiltInScene3DResourceResolver();
59
+ registerShadedScene3DMaterialTextures(resolver.registry);
60
+ expect(gaps(resolver, usageOf(createShadedMaterial()))).toEqual([
61
+ { coverage: SceneCoverage.Satisfied, kind: 'ShadedMaterial', registry: RenderRegistry.MaterialTextureLister },
62
+ ]);
63
+ });
64
+
65
+ it('reports a family that ships no lister at all, so the omission is visible rather than inferred', () => {
66
+ // BlinnPhongMaterial carries four texture slots and has no lister anywhere in the SDK.
67
+ expect(gaps(createBuiltInScene3DResourceResolver(), usageOf(createBlinnPhongMaterial()))).toContainEqual({
68
+ coverage: SceneCoverage.Missing,
69
+ kind: 'BlinnPhongMaterial',
70
+ registry: RenderRegistry.MaterialTextureLister,
71
+ });
72
+ });
73
+
74
+ it('clears out, so a repeated call does not accumulate', () => {
75
+ const resolver = createScene3DResourceResolver();
76
+ const usage = usageOf(createUnlitMaterial());
77
+ const out: SceneCoverageEntry[] = [];
78
+ explainScene3DResourceCoverage(out, resolver, usage);
79
+ const first = out.length;
80
+ explainScene3DResourceCoverage(out, resolver, usage);
81
+ expect(out).toHaveLength(first);
82
+ });
83
+ });
84
+
85
+ describe('hasScene3DResourceCoverage', () => {
86
+ it('is false while any material kind is undescribed', () => {
87
+ expect(hasScene3DResourceCoverage(createScene3DResourceResolver(), usageOf(createUnlitMaterial()))).toBe(false);
88
+ });
89
+
90
+ it('agrees with the explain tier on the same resolver, so the two can never disagree', () => {
91
+ const resolver = createBuiltInScene3DResourceResolver();
92
+ const usage = usageOf(createUnlitMaterial(), createShadedMaterial());
93
+ const out: SceneCoverageEntry[] = [];
94
+ explainScene3DResourceCoverage(out, resolver, usage);
95
+ expect(hasScene3DResourceCoverage(resolver, usage)).toBe(out.length === 0);
96
+ });
97
+
98
+ it('stays true when the manifest is all Satisfied entries, which are not shortfalls', () => {
99
+ // The invariant the manifest widening could have broken: explain now appends entries for covered
100
+ // kinds too, and the predicate must keep counting only real gaps.
101
+ const resolver = createBuiltInScene3DResourceResolver();
102
+ const usage = usageOf(createUnlitMaterial());
103
+ const out: SceneCoverageEntry[] = [];
104
+ explainScene3DResourceCoverage(out, resolver, usage);
105
+ expect(out.length).toBeGreaterThan(0);
106
+ expect(out.every((e) => e.coverage === SceneCoverage.Satisfied)).toBe(true);
107
+ expect(hasScene3DResourceCoverage(resolver, usage)).toBe(true);
108
+ });
109
+
110
+ it('is true for an empty scene, which names no material kinds', () => {
111
+ const usage = createScene3DKindUsage();
112
+ getScene3DKindUsage(usage, createScene3D());
113
+ expect(hasScene3DResourceCoverage(createScene3DResourceResolver(), usage)).toBe(true);
114
+ });
115
+ });
@@ -1,17 +1,13 @@
1
- import { createStandardPbrMaterial, createUnlitMaterial } from '@flighthq/materials/contract';
1
+ import { createBlinnPhongMaterial, createStandardPbrMaterial, createUnlitMaterial } from '@flighthq/materials/contract';
2
2
  import { createBoxMeshGeometry } from '@flighthq/mesh/contract';
3
3
  import { addNodeChild } from '@flighthq/node/contract';
4
4
  import { createMesh, createScene3D } from '@flighthq/scene3d/contract';
5
5
  import { createTexture } from '@flighthq/texture/contract';
6
6
  import type { ImageResourceReference, Texture } from '@flighthq/types/contract';
7
- import { ResourceResolutionState, ImageResourceReferenceKind } from '@flighthq/types/contract';
7
+ import { ImageResourceReferenceKind, ResourceResolutionState } from '@flighthq/types/contract';
8
8
  import { describe, expect, it } from 'vitest';
9
9
 
10
- import { getScene3DResourceTextures } from './getScene3DResourceTextures';
11
- import {
12
- createScene3DMaterialTextureRegistry,
13
- registerBuiltInScene3DMaterialTextures,
14
- } from './sceneMaterialTextureRegistry';
10
+ import { getScene3DResourceTextures, getScene3DTextureResourceReference } from './getScene3DResourceTextures';
15
11
 
16
12
  function embeddedRef(state: ResourceResolutionState = ResourceResolutionState.Unresolved): ImageResourceReference {
17
13
  return {
@@ -23,41 +19,32 @@ function embeddedRef(state: ResourceResolutionState = ResourceResolutionState.Un
23
19
  };
24
20
  }
25
21
 
26
- function registry() {
27
- const r = createScene3DMaterialTextureRegistry();
28
- registerBuiltInScene3DMaterialTextures(r);
29
- return r;
30
- }
31
-
32
22
  describe('getScene3DResourceTextures', () => {
33
23
  it('finds pending textures on the root mesh and descendants', () => {
34
- const rootMap = createTexture({ resource: embeddedRef() });
35
- const childMap = createTexture({ resource: embeddedRef() });
24
+ const rootRef = embeddedRef();
25
+ const childRef = embeddedRef();
26
+ const rootMap = createTexture({ resource: rootRef });
27
+ const childMap = createTexture({ resource: childRef });
36
28
  const root = createMesh(createBoxMeshGeometry(), [createUnlitMaterial({ baseColorMap: rootMap })]);
37
29
  const child = createMesh(createBoxMeshGeometry(), [createUnlitMaterial({ baseColorMap: childMap })]);
38
30
  addNodeChild(root, child);
31
+ const scene = createScene3D();
32
+ scene.resources.push(rootRef, childRef);
33
+ addNodeChild(scene.root, root);
39
34
 
40
35
  const out: Texture[] = [];
41
- getScene3DResourceTextures(root, registry(), out);
36
+ getScene3DResourceTextures(out, scene);
42
37
  expect(out).toContain(rootMap);
43
38
  expect(out).toContain(childMap);
44
39
  expect(out).toHaveLength(2);
45
- });
46
-
47
- it('walks a group scene root that is not itself a mesh', () => {
48
- const map = createTexture({ resource: embeddedRef() });
49
- const scene = createScene3D();
50
- const mesh = createMesh(createBoxMeshGeometry(), [createUnlitMaterial({ baseColorMap: map })]);
51
- addNodeChild(scene.root, mesh);
52
-
53
- const out: Texture[] = [];
54
- getScene3DResourceTextures(scene.root, registry(), out);
55
- expect(out).toEqual([map]);
40
+ expect(getScene3DTextureResourceReference(scene, rootMap)).toBe(rootRef);
56
41
  });
57
42
 
58
43
  it('dedupes a texture shared across meshes and materials', () => {
59
- const shared = createTexture({ resource: embeddedRef() });
44
+ const ref = embeddedRef();
45
+ const shared = createTexture({ resource: ref });
60
46
  const scene = createScene3D();
47
+ scene.resources.push(ref);
61
48
  const a = createMesh(createBoxMeshGeometry(), [
62
49
  createUnlitMaterial({ baseColorMap: shared }),
63
50
  createStandardPbrMaterial({ baseColorMap: shared }),
@@ -67,35 +54,75 @@ describe('getScene3DResourceTextures', () => {
67
54
  addNodeChild(scene.root, b);
68
55
 
69
56
  const out: Texture[] = [];
70
- getScene3DResourceTextures(scene.root, registry(), out);
57
+ getScene3DResourceTextures(out, scene);
71
58
  expect(out).toEqual([shared]);
72
59
  });
73
60
 
74
- it('ignores a texture that carries no resource ref', () => {
61
+ it('ignores a texture that carries no resource ref, since nothing needs fetching for it', () => {
75
62
  const bound = createTexture();
76
- const pending = createTexture({ resource: embeddedRef() });
63
+ const ref = embeddedRef();
64
+ const pending = createTexture({ resource: ref });
77
65
  const mesh = createMesh(createBoxMeshGeometry(), [
78
66
  createStandardPbrMaterial({ baseColorMap: bound, normalMap: pending }),
79
67
  ]);
80
68
 
69
+ const scene = createScene3D();
70
+ scene.resources.push(ref);
71
+ addNodeChild(scene.root, mesh);
81
72
  const out: Texture[] = [];
82
- getScene3DResourceTextures(mesh, registry(), out);
73
+ getScene3DResourceTextures(out, scene);
83
74
  expect(out).toEqual([pending]);
84
75
  });
85
76
 
86
- it('skips null material slots', () => {
87
- const map = createTexture({ resource: embeddedRef() });
88
- const mesh = createMesh(createBoxMeshGeometry(), [null, createUnlitMaterial({ baseColorMap: map })]);
77
+ it('finds a texture on any material kind, with nothing registered', () => {
78
+ // The defect this closes: discovery used to run through a per-kind lister registry, so a kind with
79
+ // no lister — BlinnPhongMaterial here, and every custom kind — was undiscoverable. The map was
80
+ // never requested and the model rendered untextured in silence. Now the resource's own back-edge
81
+ // answers, so no registration exists to forget.
82
+ const ref = embeddedRef();
83
+ const map = createTexture({ resource: ref });
84
+ const scene = createScene3D();
85
+ scene.resources.push(ref);
86
+ addNodeChild(scene.root, createMesh(createBoxMeshGeometry(), [createBlinnPhongMaterial({ specularMap: map })]));
87
+
89
88
  const out: Texture[] = [];
90
- getScene3DResourceTextures(mesh, registry(), out);
89
+ getScene3DResourceTextures(out, scene);
91
90
  expect(out).toEqual([map]);
92
91
  });
93
92
 
93
+ it('returns a texture whose material hangs off no mesh, which is the cost of dropping the registry', () => {
94
+ // Honest about the trade: the lister walk used to narrow the set by mesh attachment, so an orphan
95
+ // material's map was not fetched. That narrowing only ever held while every kind in the scene was
96
+ // describable, and it cost a registry the caller had to know to populate. The residue is one
97
+ // over-fetch for a document carrying materials it never attaches.
98
+ const attachedRef = embeddedRef();
99
+ const orphanRef = embeddedRef();
100
+ const attached = createTexture({ resource: attachedRef });
101
+ const orphan = createTexture({ resource: orphanRef });
102
+ const scene = createScene3D();
103
+ scene.resources.push(attachedRef, orphanRef);
104
+ addNodeChild(scene.root, createMesh(createBoxMeshGeometry(), [createUnlitMaterial({ baseColorMap: attached })]));
105
+
106
+ const out: Texture[] = [];
107
+ getScene3DResourceTextures(out, scene);
108
+ expect(out).toEqual([attached, orphan]);
109
+ });
110
+
94
111
  it('clears out before filling it', () => {
95
- const map = createTexture({ resource: embeddedRef() });
112
+ const ref = embeddedRef();
113
+ const map = createTexture({ resource: ref });
96
114
  const mesh = createMesh(createBoxMeshGeometry(), [createUnlitMaterial({ baseColorMap: map })]);
115
+ const scene = createScene3D();
116
+ scene.resources.push(ref);
117
+ addNodeChild(scene.root, mesh);
97
118
  const out: Texture[] = [createTexture(), createTexture()];
98
- getScene3DResourceTextures(mesh, registry(), out);
119
+ getScene3DResourceTextures(out, scene);
99
120
  expect(out).toEqual([map]);
100
121
  });
101
122
  });
123
+
124
+ describe('getScene3DTextureResourceReference', () => {
125
+ it('returns null for a texture outside the scene resource sidecar', () => {
126
+ expect(getScene3DTextureResourceReference(createScene3D(), createTexture())).toBeNull();
127
+ });
128
+ });
@@ -42,8 +42,8 @@ beforeAll(async () => {
42
42
  });
43
43
 
44
44
  afterAll(() => {
45
- vi.doUnmock('@flighthq/net');
46
- vi.doUnmock('@flighthq/scene3d-formats');
45
+ vi.doUnmock('@flighthq/net/contract');
46
+ vi.doUnmock('@flighthq/scene3d-formats/contract');
47
47
  vi.resetModules();
48
48
  });
49
49
 
@@ -1,12 +1,12 @@
1
1
  import type * as ImageModule from '@flighthq/image/contract';
2
- import type { ExternalImageResourceReference, ImageResource } from '@flighthq/types/contract';
2
+ import type { ExternalImageResourceReference, Image } from '@flighthq/types/contract';
3
3
  import { ResourceResolutionState, ImageResourceReferenceKind } from '@flighthq/types/contract';
4
4
  import type { Mock } from 'vitest';
5
5
  import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';
6
6
 
7
7
  import type * as ImageResourceFetchModule from './imageResourceFetch';
8
8
 
9
- const fakeImage = { height: 1, width: 1 } as unknown as ImageResource;
9
+ const fakeImage = { height: 1, width: 1 } as unknown as Image;
10
10
  type LoadImageResourceFromUrl = typeof ImageModule.loadImageResourceFromUrl;
11
11
 
12
12
  let fetchWebImageResource: typeof ImageResourceFetchModule.fetchWebImageResource;
@@ -35,7 +35,7 @@ beforeAll(async () => {
35
35
  });
36
36
 
37
37
  afterAll(() => {
38
- vi.doUnmock('@flighthq/image');
38
+ vi.doUnmock('@flighthq/image/contract');
39
39
  vi.resetModules();
40
40
  });
41
41
 
@@ -0,0 +1,34 @@
1
+ import { createExtendedPbrMaterial, createIridescencePbrExtension } from '@flighthq/materials/contract';
2
+ import { createTexture } from '@flighthq/texture/contract';
3
+ import type { Texture } from '@flighthq/types/contract';
4
+
5
+ import { registerIridescencePbrScene3DMaterialTextures } from './iridescencePbrScene3DMaterialTextures';
6
+ import {
7
+ createScene3DMaterialTextureRegistry,
8
+ getScene3DMaterialTextures,
9
+ registerExtendedPbrScene3DMaterialTextures,
10
+ } from './sceneMaterialTextureRegistry';
11
+
12
+ describe('registerIridescencePbrScene3DMaterialTextures', () => {
13
+ it('lists the iridescence factor and thickness maps', () => {
14
+ const registry = createScene3DMaterialTextureRegistry();
15
+ registerExtendedPbrScene3DMaterialTextures(registry);
16
+ registerIridescencePbrScene3DMaterialTextures(registry);
17
+ const factor = createTexture();
18
+ const thickness = createTexture();
19
+ const out: Texture[] = [];
20
+ getScene3DMaterialTextures(
21
+ registry,
22
+ createExtendedPbrMaterial({
23
+ extensions: [
24
+ createIridescencePbrExtension({
25
+ iridescenceMap: factor,
26
+ iridescenceThicknessMap: thickness,
27
+ }),
28
+ ],
29
+ }),
30
+ out,
31
+ );
32
+ expect(out).toEqual([factor, thickness]);
33
+ });
34
+ });