@flighthq/scene3d-formats 0.2.1-next.840.857425c
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/awd2Inflate.d.ts +4 -0
- package/dist/awd2Inflate.d.ts.map +1 -0
- package/dist/awd2Inflate.js +249 -0
- package/dist/awd2Inflate.js.map +1 -0
- package/dist/awd2Parse.d.ts +7 -0
- package/dist/awd2Parse.d.ts.map +1 -0
- package/dist/awd2Parse.js +1533 -0
- package/dist/awd2Parse.js.map +1 -0
- package/dist/awd2Schema.d.ts +44 -0
- package/dist/awd2Schema.d.ts.map +1 -0
- package/dist/awd2Schema.js +67 -0
- package/dist/awd2Schema.js.map +1 -0
- package/dist/gltfParse.d.ts +9 -0
- package/dist/gltfParse.d.ts.map +1 -0
- package/dist/gltfParse.js +1456 -0
- package/dist/gltfParse.js.map +1 -0
- package/dist/gltfPunctualLights.d.ts +3 -0
- package/dist/gltfPunctualLights.d.ts.map +1 -0
- package/dist/gltfPunctualLights.js +103 -0
- package/dist/gltfPunctualLights.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/md2Parse.d.ts +5 -0
- package/dist/md2Parse.d.ts.map +1 -0
- package/dist/md2Parse.js +408 -0
- package/dist/md2Parse.js.map +1 -0
- package/dist/md2Schema.d.ts +12 -0
- package/dist/md2Schema.d.ts.map +1 -0
- package/dist/md2Schema.js +193 -0
- package/dist/md2Schema.js.map +1 -0
- package/dist/md5AnimParse.d.ts +3 -0
- package/dist/md5AnimParse.d.ts.map +1 -0
- package/dist/md5AnimParse.js +385 -0
- package/dist/md5AnimParse.js.map +1 -0
- package/dist/md5Parse.d.ts +6 -0
- package/dist/md5Parse.d.ts.map +1 -0
- package/dist/md5Parse.js +635 -0
- package/dist/md5Parse.js.map +1 -0
- package/dist/mtlParse.d.ts +3 -0
- package/dist/mtlParse.d.ts.map +1 -0
- package/dist/mtlParse.js +256 -0
- package/dist/mtlParse.js.map +1 -0
- package/dist/objParse.d.ts +5 -0
- package/dist/objParse.d.ts.map +1 -0
- package/dist/objParse.js +464 -0
- package/dist/objParse.js.map +1 -0
- package/dist/sceneSkeleton.d.ts +3 -0
- package/dist/sceneSkeleton.d.ts.map +1 -0
- package/dist/sceneSkeleton.js +34 -0
- package/dist/sceneSkeleton.js.map +1 -0
- package/dist/shared.d.ts +18 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +143 -0
- package/dist/shared.js.map +1 -0
- package/dist/threeDsParse.d.ts +4 -0
- package/dist/threeDsParse.d.ts.map +1 -0
- package/dist/threeDsParse.js +718 -0
- package/dist/threeDsParse.js.map +1 -0
- package/package.json +55 -0
- package/src/awd2Inflate.test.ts +118 -0
- package/src/awd2Parse.test.ts +2431 -0
- package/src/gltfParse.test.ts +2339 -0
- package/src/gltfPunctualLights.test.ts +184 -0
- package/src/gltfTreeShaking.test.ts +45 -0
- package/src/md2Parse.test.ts +1033 -0
- package/src/md5AnimParse.test.ts +673 -0
- package/src/md5Parse.test.ts +1094 -0
- package/src/mtlParse.test.ts +244 -0
- package/src/objParse.test.ts +690 -0
- package/src/sceneSkeleton.test.ts +35 -0
- package/src/shared.test.ts +210 -0
- package/src/threeDsParse.test.ts +1089 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { DirectionalLight, ImportDiagnostic, PointLight, SpotLight, GltfDocument } from '@flighthq/types';
|
|
2
|
+
import { DirectionalLightKind, ImportDiagnosticSeverity, PointLightKind, SpotLightKind } from '@flighthq/types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { parseGltf } from './gltfParse';
|
|
6
|
+
import { GltfPunctualLightsExtensionHandler } from './gltfPunctualLights';
|
|
7
|
+
|
|
8
|
+
function findLightDiagnostic(diagnostics: readonly ImportDiagnostic[], kind: string): ImportDiagnostic | undefined {
|
|
9
|
+
return diagnostics.find((diagnostic) => diagnostic.kind === kind);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A KHR_lights_punctual glTF document with one light and one node referencing it — the fixture the
|
|
13
|
+
// per-light diagnostic tests vary.
|
|
14
|
+
function makeLightGltf(light: unknown): GltfDocument {
|
|
15
|
+
return {
|
|
16
|
+
asset: { version: '2.0' },
|
|
17
|
+
extensions: { KHR_lights_punctual: { lights: [light] } },
|
|
18
|
+
nodes: [{ extensions: { KHR_lights_punctual: { light: 0 } } }],
|
|
19
|
+
scenes: [{ nodes: [0] }],
|
|
20
|
+
} as GltfDocument;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('GltfPunctualLightsExtensionHandler', () => {
|
|
24
|
+
it('individually realizes placed directional, point, and spot lights', () => {
|
|
25
|
+
const source: GltfDocument = {
|
|
26
|
+
asset: { version: '2.0' },
|
|
27
|
+
extensions: {
|
|
28
|
+
KHR_lights_punctual: {
|
|
29
|
+
lights: [
|
|
30
|
+
{ color: [1, 0, 0], intensity: 2, name: 'sun', type: 'directional' },
|
|
31
|
+
{ intensity: 3, range: 10, type: 'point' },
|
|
32
|
+
{ spot: { innerConeAngle: 0.25, outerConeAngle: 0.5 }, type: 'spot' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
extensionsRequired: ['KHR_lights_punctual'],
|
|
37
|
+
nodes: [
|
|
38
|
+
{ extensions: { KHR_lights_punctual: { light: 0 } }, translation: [1, 2, 3] },
|
|
39
|
+
{ extensions: { KHR_lights_punctual: { light: 1 } } },
|
|
40
|
+
{ extensions: { KHR_lights_punctual: { light: 2 } } },
|
|
41
|
+
],
|
|
42
|
+
scenes: [{ nodes: [0, 1, 2] }],
|
|
43
|
+
};
|
|
44
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
45
|
+
const document = parseGltf(source, diagnostics, {
|
|
46
|
+
extensionHandlers: [GltfPunctualLightsExtensionHandler],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(diagnostics).toEqual([]);
|
|
50
|
+
expect(document.lights).toHaveLength(3);
|
|
51
|
+
const directional = document.lights[0].descriptor as DirectionalLight;
|
|
52
|
+
expect(directional.kind).toBe(DirectionalLightKind);
|
|
53
|
+
expect(directional.intensity).toBe(2);
|
|
54
|
+
expect(directional.color).toBe(0xff0000ff);
|
|
55
|
+
expect(directional.direction).toMatchObject({ x: 0, y: 0, z: -1 });
|
|
56
|
+
expect(document.lights[0]).toMatchObject({ name: 'sun', node: 0 });
|
|
57
|
+
expect(document.lights[0].transform.position).toMatchObject({ x: 1, y: 2, z: 3 });
|
|
58
|
+
|
|
59
|
+
const point = document.lights[1].descriptor as PointLight;
|
|
60
|
+
expect(point.kind).toBe(PointLightKind);
|
|
61
|
+
expect(point.intensity).toBe(3);
|
|
62
|
+
expect(point.range).toBe(10);
|
|
63
|
+
|
|
64
|
+
const spot = document.lights[2].descriptor as SpotLight;
|
|
65
|
+
expect(spot.kind).toBe(SpotLightKind);
|
|
66
|
+
expect(spot.innerConeCos).toBeCloseTo(Math.cos(0.25));
|
|
67
|
+
expect(spot.outerConeCos).toBeCloseTo(Math.cos(0.5));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('is absent unless the caller imports and supplies it', () => {
|
|
71
|
+
const source: GltfDocument = {
|
|
72
|
+
asset: { version: '2.0' },
|
|
73
|
+
extensions: { KHR_lights_punctual: { lights: [{ type: 'point' }] } },
|
|
74
|
+
nodes: [{ extensions: { KHR_lights_punctual: { light: 0 } } }],
|
|
75
|
+
scenes: [{ nodes: [0] }],
|
|
76
|
+
};
|
|
77
|
+
expect(parseGltf(source).lights).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('drops and reports gltf.light-missing for a node referencing a missing light', () => {
|
|
81
|
+
const source: GltfDocument = {
|
|
82
|
+
asset: { version: '2.0' },
|
|
83
|
+
extensions: { KHR_lights_punctual: { lights: [] } },
|
|
84
|
+
nodes: [{ extensions: { KHR_lights_punctual: { light: 5 } } }],
|
|
85
|
+
scenes: [{ nodes: [0] }],
|
|
86
|
+
} as GltfDocument;
|
|
87
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
88
|
+
const document = parseGltf(source, diagnostics, { extensionHandlers: [GltfPunctualLightsExtensionHandler] });
|
|
89
|
+
expect(document.lights).toHaveLength(0);
|
|
90
|
+
const crumb = findLightDiagnostic(diagnostics, 'gltf.light-missing');
|
|
91
|
+
expect(crumb).toBeDefined();
|
|
92
|
+
expect(crumb!.severity).toBe(ImportDiagnosticSeverity.Drop);
|
|
93
|
+
expect(crumb!.origin).toBe('buildGltfPunctualLight');
|
|
94
|
+
expect(crumb!.detail?.count).toBe(1);
|
|
95
|
+
expect(crumb!.detail?.firstLight).toBe(5);
|
|
96
|
+
expect(crumb!.detail?.firstNode).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('drops the light and reports gltf.light-negative-intensity', () => {
|
|
100
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
101
|
+
const document = parseGltf(makeLightGltf({ intensity: -1, type: 'point' }), diagnostics, {
|
|
102
|
+
extensionHandlers: [GltfPunctualLightsExtensionHandler],
|
|
103
|
+
});
|
|
104
|
+
// Drop (not Recover): the light is omitted — no clamp/substitute — so the document carries no light.
|
|
105
|
+
expect(document.lights).toHaveLength(0);
|
|
106
|
+
const crumb = findLightDiagnostic(diagnostics, 'gltf.light-negative-intensity');
|
|
107
|
+
expect(crumb).toBeDefined();
|
|
108
|
+
expect(crumb!.severity).toBe(ImportDiagnosticSeverity.Drop);
|
|
109
|
+
expect(crumb!.origin).toBe('buildGltfPunctualLight');
|
|
110
|
+
expect(crumb!.detail?.count).toBe(1);
|
|
111
|
+
expect(crumb!.detail?.firstLight).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('drops the light and reports gltf.light-non-positive-range', () => {
|
|
115
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
116
|
+
const document = parseGltf(makeLightGltf({ range: 0, type: 'point' }), diagnostics, {
|
|
117
|
+
extensionHandlers: [GltfPunctualLightsExtensionHandler],
|
|
118
|
+
});
|
|
119
|
+
expect(document.lights).toHaveLength(0);
|
|
120
|
+
const crumb = findLightDiagnostic(diagnostics, 'gltf.light-non-positive-range');
|
|
121
|
+
expect(crumb).toBeDefined();
|
|
122
|
+
expect(crumb!.severity).toBe(ImportDiagnosticSeverity.Drop);
|
|
123
|
+
expect(crumb!.origin).toBe('buildGltfPunctualLight');
|
|
124
|
+
expect(crumb!.detail?.firstLight).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('drops the light and reports gltf.light-invalid-spot-cone', () => {
|
|
128
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
129
|
+
const document = parseGltf(
|
|
130
|
+
makeLightGltf({ spot: { innerConeAngle: 0.6, outerConeAngle: 0.3 }, type: 'spot' }),
|
|
131
|
+
diagnostics,
|
|
132
|
+
{
|
|
133
|
+
extensionHandlers: [GltfPunctualLightsExtensionHandler],
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
expect(document.lights).toHaveLength(0);
|
|
137
|
+
const crumb = findLightDiagnostic(diagnostics, 'gltf.light-invalid-spot-cone');
|
|
138
|
+
expect(crumb).toBeDefined();
|
|
139
|
+
expect(crumb!.severity).toBe(ImportDiagnosticSeverity.Drop);
|
|
140
|
+
expect(crumb!.origin).toBe('buildGltfPunctualLight');
|
|
141
|
+
expect(crumb!.detail?.firstLight).toBe(0);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('skips and reports gltf.light-unsupported-type', () => {
|
|
145
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
146
|
+
const document = parseGltf(makeLightGltf({ type: 'area' }), diagnostics, {
|
|
147
|
+
extensionHandlers: [GltfPunctualLightsExtensionHandler],
|
|
148
|
+
});
|
|
149
|
+
// Skip: a recognized light type Flight does not realize (area) — the light is not produced.
|
|
150
|
+
expect(document.lights).toHaveLength(0);
|
|
151
|
+
const crumb = findLightDiagnostic(diagnostics, 'gltf.light-unsupported-type');
|
|
152
|
+
expect(crumb).toBeDefined();
|
|
153
|
+
expect(crumb!.severity).toBe(ImportDiagnosticSeverity.Skip);
|
|
154
|
+
expect(crumb!.origin).toBe('buildGltfPunctualLight');
|
|
155
|
+
expect(crumb!.detail?.firstLight).toBe(0);
|
|
156
|
+
expect(crumb!.detail?.firstType).toBe('area');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('aggregates repeated light-missing drops across nodes into one crumb with a count', () => {
|
|
160
|
+
const source: GltfDocument = {
|
|
161
|
+
asset: { version: '2.0' },
|
|
162
|
+
extensions: { KHR_lights_punctual: { lights: [] } },
|
|
163
|
+
nodes: [
|
|
164
|
+
{ extensions: { KHR_lights_punctual: { light: 3 } } },
|
|
165
|
+
{ extensions: { KHR_lights_punctual: { light: 4 } } },
|
|
166
|
+
],
|
|
167
|
+
scenes: [{ nodes: [0, 1] }],
|
|
168
|
+
} as GltfDocument;
|
|
169
|
+
const diagnostics: ImportDiagnostic[] = [];
|
|
170
|
+
parseGltf(source, diagnostics, { extensionHandlers: [GltfPunctualLightsExtensionHandler] });
|
|
171
|
+
const matching = diagnostics.filter((d) => d.kind === 'gltf.light-missing');
|
|
172
|
+
expect(matching).toHaveLength(1);
|
|
173
|
+
expect(matching[0].detail?.count).toBe(2);
|
|
174
|
+
expect(matching[0].detail?.firstLight).toBe(3);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('emits no diagnostics when no collector array is supplied', () => {
|
|
178
|
+
expect(() =>
|
|
179
|
+
parseGltf(makeLightGltf({ intensity: -1, type: 'area' }), undefined, {
|
|
180
|
+
extensionHandlers: [GltfPunctualLightsExtensionHandler],
|
|
181
|
+
}),
|
|
182
|
+
).not.toThrow();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
|
|
3
|
+
import { build } from 'esbuild';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
const resolveDir = getFileUrlDirectory(import.meta.url);
|
|
7
|
+
|
|
8
|
+
async function bundleRootExport(name: string): Promise<string> {
|
|
9
|
+
const result = await build({
|
|
10
|
+
bundle: true,
|
|
11
|
+
format: 'esm',
|
|
12
|
+
logLevel: 'silent',
|
|
13
|
+
minify: true,
|
|
14
|
+
packages: 'external',
|
|
15
|
+
stdin: {
|
|
16
|
+
contents: `export { ${name} } from './index.ts';`,
|
|
17
|
+
resolveDir,
|
|
18
|
+
sourcefile: `${name}.ts`,
|
|
19
|
+
},
|
|
20
|
+
treeShaking: true,
|
|
21
|
+
write: false,
|
|
22
|
+
});
|
|
23
|
+
return result.outputFiles[0].text;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('glTF extension tree shaking', () => {
|
|
27
|
+
it('keeps the core parser independent of optional punctual-light realization', async () => {
|
|
28
|
+
const output = await bundleRootExport('parseGltf');
|
|
29
|
+
expect(output).not.toContain('@flighthq/lighting');
|
|
30
|
+
expect(output).not.toContain('KHR_lights_punctual');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('includes punctual-light realization only when its named handler is imported', async () => {
|
|
34
|
+
const output = await bundleRootExport('GltfPunctualLightsExtensionHandler');
|
|
35
|
+
expect(output).toContain('DirectionalLight');
|
|
36
|
+
expect(output).toContain('KHR_lights_punctual');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function getFileUrlDirectory(url: string): string {
|
|
41
|
+
const directory = new URL('.', url);
|
|
42
|
+
const pathname = decodeURIComponent(directory.pathname);
|
|
43
|
+
if (directory.hostname !== '') return `//${directory.hostname}${pathname}`;
|
|
44
|
+
return /^\/[A-Za-z]:\//.test(pathname) ? pathname.slice(1) : pathname;
|
|
45
|
+
}
|