@aura3d/assets 1.3.3 → 1.4.1

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 (73) hide show
  1. package/dist/GLTFLoader.js +1 -0
  2. package/dist/GLTFLoader.js.map +1 -1
  3. package/dist/GLTFRenderResources.d.ts.map +1 -1
  4. package/dist/GLTFRenderResources.js +1 -0
  5. package/dist/GLTFRenderResources.js.map +1 -1
  6. package/package.json +38 -5
  7. package/src/AdvancedAssetCorpus.ts +0 -454
  8. package/src/AssetBundleCacheFixtures.ts +0 -290
  9. package/src/AssetCache.ts +0 -76
  10. package/src/AssetCompatibility.ts +0 -224
  11. package/src/AssetCorpus.ts +0 -329
  12. package/src/AssetDependencyGraph.ts +0 -83
  13. package/src/AssetHandle.ts +0 -74
  14. package/src/AssetImportPreflight.ts +0 -179
  15. package/src/AssetInspection.ts +0 -706
  16. package/src/AssetLoader.ts +0 -36
  17. package/src/AssetManager.ts +0 -211
  18. package/src/AssetRegistry.ts +0 -44
  19. package/src/AssetRenderDefaults.ts +0 -36
  20. package/src/AudioLoader.ts +0 -41
  21. package/src/BlenderExportValidation.ts +0 -227
  22. package/src/CarConceptMaterialStability.ts +0 -1405
  23. package/src/EXRLoader.ts +0 -1
  24. package/src/ExternalParityGLTFCorpus.ts +0 -65
  25. package/src/GLTFAnimationRuntime.ts +0 -1416
  26. package/src/GLTFAutoFit.ts +0 -133
  27. package/src/GLTFCompressionDecoders.ts +0 -190
  28. package/src/GLTFExtensionSupport.ts +0 -138
  29. package/src/GLTFLoader.ts +0 -4059
  30. package/src/GLTFRenderResources.ts +0 -2269
  31. package/src/HDRLoader.ts +0 -1
  32. package/src/ImageLoader.ts +0 -52
  33. package/src/ImportPipeline.ts +0 -148
  34. package/src/KTX2BasisTextureTranscoder.ts +0 -225
  35. package/src/LoadContext.ts +0 -60
  36. package/src/MaterialLoader.ts +0 -166
  37. package/src/MeshOptimization.ts +0 -93
  38. package/src/OBJLoader.ts +0 -551
  39. package/src/SceneAnalysisFixtures.ts +0 -438
  40. package/src/SceneLoader.ts +0 -62
  41. package/src/ShaderLoader.ts +0 -30
  42. package/src/TextureLoader.ts +0 -30
  43. package/src/TexturePipeline.ts +0 -96
  44. package/src/TextureStreaming.ts +0 -20
  45. package/src/WorkerAssetJobs.ts +0 -56
  46. package/src/advanced-gallery/index.ts +0 -18
  47. package/src/asset-corpus/GLTFSceneLoader.ts +0 -9
  48. package/src/asset-corpus/ProductionAssetCorpus.ts +0 -230
  49. package/src/asset-corpus/ProductionAssetPipeline.ts +0 -5
  50. package/src/asset-corpus/ProductionGLTFRenderPipeline.ts +0 -182
  51. package/src/asset-corpus/ProductionHDRTextureLoader.ts +0 -2
  52. package/src/asset-corpus/ProductionKTX2TextureLoader.ts +0 -2
  53. package/src/asset-corpus/ProductionTextureLoader.ts +0 -2
  54. package/src/asset-corpus/index.ts +0 -29
  55. package/src/browser-index.ts +0 -229
  56. package/src/createRenderableScene.ts +0 -128
  57. package/src/index.ts +0 -325
  58. package/src/loadRenderableAsset.ts +0 -67
  59. package/src/loaders/CubeTextureLoader.ts +0 -8
  60. package/src/loaders/EXRLoader.ts +0 -10
  61. package/src/loaders/HDRLoader.ts +0 -7
  62. package/src/loaders/KTX2Loader.ts +0 -7
  63. package/src/loaders/LoaderDiagnostics.ts +0 -36
  64. package/src/loaders/MTLLoader.ts +0 -20
  65. package/src/loaders/OBJLoader.ts +0 -24
  66. package/src/loaders/TextureLoader.ts +0 -14
  67. package/src/loaders/ThreeCompatGLTFLoader.ts +0 -19
  68. package/src/loaders/index.ts +0 -14
  69. package/src/threejs-compatibility/ThreeCompatAssetProvenance.ts +0 -51
  70. package/src/threejs-compatibility/ThreeCompatAssetRegistry.ts +0 -61
  71. package/src/threejs-example-parity/index.ts +0 -262
  72. package/tests/assets.test.ts +0 -393
  73. package/tsconfig.json +0 -7
@@ -1,329 +0,0 @@
1
- export type GLTFCorpusSchemaVersion = "gltf-corpus";
2
- export type GLTFCorpusAssetFormat = "gltf" | "glb";
3
- export type GLTFCorpusExpectedStatus = "pass" | "warn" | "expected-fail";
4
- export type AssetDiagnosticSeverity = "info" | "warning" | "error";
5
-
6
- export interface AssetDiagnostic {
7
- readonly code: string;
8
- readonly severity: AssetDiagnosticSeverity;
9
- readonly message: string;
10
- readonly nextAction: string;
11
- readonly assetId?: string;
12
- }
13
-
14
- export interface AssetImportSettings {
15
- readonly colorSpace: "asset" | "srgb" | "linear";
16
- readonly mipmaps: "generate" | "preserve" | "none";
17
- readonly compression: "auto" | "prefer-source" | "none";
18
- readonly scale: number;
19
- readonly normals: "preserve" | "generate-if-missing" | "require";
20
- readonly tangents: "preserve" | "generate-if-missing" | "require";
21
- readonly animationImport: "all" | "first-clip" | "none";
22
- readonly materialVariants: "import" | "ignore";
23
- }
24
-
25
- export interface GLTFCorpusSource {
26
- readonly repository: string;
27
- readonly revision: string;
28
- readonly path: string;
29
- readonly uri: string;
30
- readonly sha256: string;
31
- }
32
-
33
- export interface GLTFCorpusAsset {
34
- readonly id: string;
35
- readonly name: string;
36
- readonly source: GLTFCorpusSource;
37
- readonly format: GLTFCorpusAssetFormat;
38
- readonly tags: readonly string[];
39
- readonly license: string;
40
- readonly expectedStatus: GLTFCorpusExpectedStatus;
41
- readonly expectedDiagnostics?: readonly AssetDiagnostic[];
42
- readonly importSettings?: Partial<AssetImportSettings>;
43
- }
44
-
45
- export interface GLTFCorpusManifest {
46
- readonly schemaVersion: GLTFCorpusSchemaVersion;
47
- readonly generatedFrom: {
48
- readonly name: string;
49
- readonly repository: string;
50
- readonly revision: string;
51
- };
52
- readonly assets: readonly GLTFCorpusAsset[];
53
- }
54
-
55
- export interface GLTFCorpusAssetReport {
56
- readonly id: string;
57
- readonly name: string;
58
- readonly format: GLTFCorpusAssetFormat;
59
- readonly expectedStatus: GLTFCorpusExpectedStatus;
60
- readonly sourceUri: string;
61
- readonly sourceRevision: string;
62
- readonly sourceSha256: string;
63
- readonly tags: readonly string[];
64
- readonly importSettings: AssetImportSettings;
65
- readonly diagnostics: readonly AssetDiagnostic[];
66
- }
67
-
68
- export interface GLTFCorpusReport {
69
- readonly schemaVersion: "gltf-corpus-report";
70
- readonly generatedAt: string;
71
- readonly sourceManifest: {
72
- readonly schemaVersion: GLTFCorpusSchemaVersion;
73
- readonly sourceName: string;
74
- readonly sourceRepository: string;
75
- readonly sourceRevision: string;
76
- readonly assetCount: number;
77
- };
78
- readonly summary: {
79
- readonly pass: number;
80
- readonly warn: number;
81
- readonly expectedFail: number;
82
- };
83
- readonly assets: readonly GLTFCorpusAssetReport[];
84
- }
85
-
86
- export interface GLTFCorpusValidationResult {
87
- readonly ok: boolean;
88
- readonly diagnostics: readonly AssetDiagnostic[];
89
- readonly manifest?: GLTFCorpusManifest;
90
- }
91
-
92
- export const DEFAULT_ASSET_IMPORT_SETTINGS: AssetImportSettings = {
93
- colorSpace: "asset",
94
- mipmaps: "generate",
95
- compression: "auto",
96
- scale: 1,
97
- normals: "generate-if-missing",
98
- tangents: "generate-if-missing",
99
- animationImport: "all",
100
- materialVariants: "import"
101
- };
102
-
103
- const HASH_PATTERN = /^[a-f0-9]{64}$/;
104
- const REVISION_PATTERN = /^[a-f0-9]{40}$/;
105
- const ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
106
-
107
- export function normalizeAssetImportSettings(settings: Partial<AssetImportSettings> = {}): AssetImportSettings {
108
- const normalized: AssetImportSettings = {
109
- ...DEFAULT_ASSET_IMPORT_SETTINGS,
110
- ...settings
111
- };
112
-
113
- validateChoice(normalized.colorSpace, ["asset", "srgb", "linear"], "colorSpace");
114
- validateChoice(normalized.mipmaps, ["generate", "preserve", "none"], "mipmaps");
115
- validateChoice(normalized.compression, ["auto", "prefer-source", "none"], "compression");
116
- validateChoice(normalized.normals, ["preserve", "generate-if-missing", "require"], "normals");
117
- validateChoice(normalized.tangents, ["preserve", "generate-if-missing", "require"], "tangents");
118
- validateChoice(normalized.animationImport, ["all", "first-clip", "none"], "animationImport");
119
- validateChoice(normalized.materialVariants, ["import", "ignore"], "materialVariants");
120
- if (!Number.isFinite(normalized.scale) || normalized.scale <= 0) {
121
- throw new Error("Asset import setting scale must be a positive finite number");
122
- }
123
-
124
- return normalized;
125
- }
126
-
127
- export function validateGLTFCorpusManifest(input: unknown): GLTFCorpusValidationResult {
128
- const diagnostics: AssetDiagnostic[] = [];
129
-
130
- if (!isRecord(input)) {
131
- return {
132
- ok: false,
133
- diagnostics: [diagnostic("ASSET_CORPUS_MANIFEST_TYPE", "error", "glTF corpus manifest must be an object.", "Load a JSON object with schemaVersion and assets.")]
134
- };
135
- }
136
-
137
- if (input.schemaVersion !== "gltf-corpus") {
138
- diagnostics.push(diagnostic("ASSET_CORPUS_SCHEMA_VERSION", "error", "glTF corpus manifest schemaVersion must be gltf-corpus.", "Regenerate the corpus manifest with the legacy schema."));
139
- }
140
-
141
- const generatedFrom = isRecord(input.generatedFrom) ? input.generatedFrom : undefined;
142
- if (!generatedFrom) {
143
- diagnostics.push(diagnostic("ASSET_CORPUS_SOURCE_MISSING", "error", "glTF corpus manifest must identify the upstream source.", "Add generatedFrom with name, repository, and pinned revision."));
144
- } else {
145
- validateText(generatedFrom.name, "ASSET_CORPUS_SOURCE_NAME", "generatedFrom.name", diagnostics);
146
- validateRepository(generatedFrom.repository, "ASSET_CORPUS_SOURCE_REPOSITORY", "generatedFrom.repository", diagnostics);
147
- validateRevision(generatedFrom.revision, "ASSET_CORPUS_SOURCE_REVISION", "generatedFrom.revision", diagnostics);
148
- }
149
-
150
- if (!Array.isArray(input.assets) || input.assets.length === 0) {
151
- diagnostics.push(diagnostic("ASSET_CORPUS_EMPTY", "error", "glTF corpus manifest must contain at least one asset.", "Add pinned external glTF or GLB assets to assets."));
152
- return { ok: false, diagnostics };
153
- }
154
-
155
- const seen = new Set<string>();
156
- for (const [index, value] of input.assets.entries()) {
157
- validateAssetEntry(value, index, seen, diagnostics);
158
- }
159
-
160
- return {
161
- ok: diagnostics.every((entry) => entry.severity !== "error"),
162
- diagnostics,
163
- manifest: diagnostics.some((entry) => entry.severity === "error") ? undefined : input as unknown as GLTFCorpusManifest
164
- };
165
- }
166
-
167
- export function assertValidGLTFCorpusManifest(input: unknown): GLTFCorpusManifest {
168
- const result = validateGLTFCorpusManifest(input);
169
- if (!result.ok || !result.manifest) {
170
- const details = result.diagnostics.map((entry) => `${entry.code}: ${entry.message}`).join("; ");
171
- throw new Error(`Invalid glTF corpus manifest: ${details}`);
172
- }
173
- return result.manifest;
174
- }
175
-
176
- export function createGLTFCorpusReport(manifest: GLTFCorpusManifest, generatedAt = new Date().toISOString()): GLTFCorpusReport {
177
- const validated = assertValidGLTFCorpusManifest(manifest);
178
- const assets = validated.assets.map((asset) => ({
179
- id: asset.id,
180
- name: asset.name,
181
- format: asset.format,
182
- expectedStatus: asset.expectedStatus,
183
- sourceUri: asset.source.uri,
184
- sourceRevision: asset.source.revision,
185
- sourceSha256: asset.source.sha256,
186
- tags: [...asset.tags].sort(),
187
- importSettings: normalizeAssetImportSettings(asset.importSettings),
188
- diagnostics: diagnosticsForAsset(asset)
189
- }));
190
-
191
- return {
192
- schemaVersion: "gltf-corpus-report",
193
- generatedAt,
194
- sourceManifest: {
195
- schemaVersion: validated.schemaVersion,
196
- sourceName: validated.generatedFrom.name,
197
- sourceRepository: validated.generatedFrom.repository,
198
- sourceRevision: validated.generatedFrom.revision,
199
- assetCount: validated.assets.length
200
- },
201
- summary: {
202
- pass: assets.filter((asset) => asset.expectedStatus === "pass").length,
203
- warn: assets.filter((asset) => asset.expectedStatus === "warn").length,
204
- expectedFail: assets.filter((asset) => asset.expectedStatus === "expected-fail").length
205
- },
206
- assets
207
- };
208
- }
209
-
210
- function diagnosticsForAsset(asset: GLTFCorpusAsset): readonly AssetDiagnostic[] {
211
- return (asset.expectedDiagnostics ?? []).map((entry) => ({
212
- ...entry,
213
- assetId: entry.assetId ?? asset.id
214
- }));
215
- }
216
-
217
- function validateAssetEntry(value: unknown, index: number, seen: Set<string>, diagnostics: AssetDiagnostic[]): void {
218
- if (!isRecord(value)) {
219
- diagnostics.push(diagnostic("ASSET_CORPUS_ASSET_TYPE", "error", `Asset entry ${index} must be an object.`, "Replace the entry with an asset object."));
220
- return;
221
- }
222
-
223
- const assetId = typeof value.id === "string" ? value.id : undefined;
224
- if (!assetId || !ID_PATTERN.test(assetId)) {
225
- diagnostics.push(diagnostic("ASSET_CORPUS_ASSET_ID", "error", `Asset entry ${index} id must be kebab-case ASCII.`, "Use a stable lowercase id such as box-textured.", assetId));
226
- } else if (seen.has(assetId)) {
227
- diagnostics.push(diagnostic("ASSET_CORPUS_DUPLICATE_ID", "error", `Asset id ${assetId} is duplicated.`, "Keep corpus asset ids unique.", assetId));
228
- } else {
229
- seen.add(assetId);
230
- }
231
-
232
- validateText(value.name, "ASSET_CORPUS_ASSET_NAME", `asset ${assetId ?? index} name`, diagnostics, assetId);
233
- validateChoice(value.format, ["gltf", "glb"], "format", diagnostics, assetId);
234
- validateChoice(value.expectedStatus, ["pass", "warn", "expected-fail"], "expectedStatus", diagnostics, assetId);
235
- validateText(value.license, "ASSET_CORPUS_ASSET_LICENSE", `asset ${assetId ?? index} license`, diagnostics, assetId);
236
- validateTags(value.tags, assetId, diagnostics);
237
- validateSource(value.source, assetId, diagnostics);
238
-
239
- if (value.expectedStatus === "expected-fail" && (!Array.isArray(value.expectedDiagnostics) || value.expectedDiagnostics.length === 0)) {
240
- diagnostics.push(diagnostic("ASSET_CORPUS_EXPECTED_FAIL_DIAGNOSTIC", "error", `Asset ${assetId ?? index} is expected-fail but has no expected diagnostics.`, "Add a typed diagnostic with nextAction for the expected failure.", assetId));
241
- }
242
- if (value.expectedDiagnostics !== undefined) {
243
- validateExpectedDiagnostics(value.expectedDiagnostics, assetId, diagnostics);
244
- }
245
- if (value.importSettings !== undefined) {
246
- try {
247
- normalizeAssetImportSettings(value.importSettings as Partial<AssetImportSettings>);
248
- } catch (error) {
249
- diagnostics.push(diagnostic("ASSET_CORPUS_IMPORT_SETTINGS", "error", error instanceof Error ? error.message : String(error), "Fix the asset importSettings object.", assetId));
250
- }
251
- }
252
- }
253
-
254
- function validateSource(value: unknown, assetId: string | undefined, diagnostics: AssetDiagnostic[]): void {
255
- if (!isRecord(value)) {
256
- diagnostics.push(diagnostic("ASSET_CORPUS_SOURCE_TYPE", "error", `Asset ${assetId ?? "unknown"} source must be an object.`, "Add source repository, revision, path, uri, and sha256.", assetId));
257
- return;
258
- }
259
-
260
- validateRepository(value.repository, "ASSET_CORPUS_SOURCE_REPOSITORY", "source.repository", diagnostics, assetId);
261
- validateRevision(value.revision, "ASSET_CORPUS_SOURCE_REVISION", "source.revision", diagnostics, assetId);
262
- validateText(value.path, "ASSET_CORPUS_SOURCE_PATH", "source.path", diagnostics, assetId);
263
- validateText(value.uri, "ASSET_CORPUS_SOURCE_URI", "source.uri", diagnostics, assetId);
264
- if (typeof value.uri === "string" && /\/(?:main|master|latest)\//i.test(value.uri)) {
265
- diagnostics.push(diagnostic("ASSET_CORPUS_SOURCE_UNPINNED_URI", "error", `Asset ${assetId ?? "unknown"} source URI is not pinned to a revision.`, "Use a URI that contains the exact repository commit hash.", assetId));
266
- }
267
- if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256)) {
268
- diagnostics.push(diagnostic("ASSET_CORPUS_SOURCE_HASH", "error", `Asset ${assetId ?? "unknown"} source sha256 must be a lowercase 64-character hex digest.`, "Record the SHA-256 of the root glTF/GLB file.", assetId));
269
- }
270
- }
271
-
272
- function validateExpectedDiagnostics(value: unknown, assetId: string | undefined, diagnostics: AssetDiagnostic[]): void {
273
- if (!Array.isArray(value)) {
274
- diagnostics.push(diagnostic("ASSET_CORPUS_DIAGNOSTICS_TYPE", "error", `Asset ${assetId ?? "unknown"} expectedDiagnostics must be an array.`, "Use an array of diagnostic objects.", assetId));
275
- return;
276
- }
277
- for (const [index, entry] of value.entries()) {
278
- if (!isRecord(entry)) {
279
- diagnostics.push(diagnostic("ASSET_CORPUS_DIAGNOSTIC_TYPE", "error", `Diagnostic ${index} for asset ${assetId ?? "unknown"} must be an object.`, "Use diagnostic objects with code, severity, message, and nextAction.", assetId));
280
- continue;
281
- }
282
- validateText(entry.code, "ASSET_CORPUS_DIAGNOSTIC_CODE", "diagnostic.code", diagnostics, assetId);
283
- validateChoice(entry.severity, ["info", "warning", "error"], "diagnostic.severity", diagnostics, assetId);
284
- validateText(entry.message, "ASSET_CORPUS_DIAGNOSTIC_MESSAGE", "diagnostic.message", diagnostics, assetId);
285
- validateText(entry.nextAction, "ASSET_CORPUS_DIAGNOSTIC_NEXT_ACTION", "diagnostic.nextAction", diagnostics, assetId);
286
- }
287
- }
288
-
289
- function validateTags(value: unknown, assetId: string | undefined, diagnostics: AssetDiagnostic[]): void {
290
- if (!Array.isArray(value) || value.length === 0 || value.some((tag) => typeof tag !== "string" || tag.trim().length === 0)) {
291
- diagnostics.push(diagnostic("ASSET_CORPUS_TAGS", "error", `Asset ${assetId ?? "unknown"} must have at least one non-empty tag.`, "Add tags describing coverage such as core, texture, animation, or extension.", assetId));
292
- }
293
- }
294
-
295
- function validateRepository(value: unknown, code: string, label: string, diagnostics: AssetDiagnostic[], assetId?: string): void {
296
- if (typeof value !== "string" || !/^https:\/\/github\.com\/[^/]+\/[^/]+$/.test(value)) {
297
- diagnostics.push(diagnostic(code, "error", `${label} must be a GitHub repository URL.`, "Use an https://github.com/owner/repo URL.", assetId));
298
- }
299
- }
300
-
301
- function validateRevision(value: unknown, code: string, label: string, diagnostics: AssetDiagnostic[], assetId?: string): void {
302
- if (typeof value !== "string" || !REVISION_PATTERN.test(value)) {
303
- diagnostics.push(diagnostic(code, "error", `${label} must be a 40-character commit SHA.`, "Pin the corpus source to an exact commit.", assetId));
304
- }
305
- }
306
-
307
- function validateText(value: unknown, code: string, label: string, diagnostics: AssetDiagnostic[], assetId?: string): void {
308
- if (typeof value !== "string" || value.trim().length === 0) {
309
- diagnostics.push(diagnostic(code, "error", `${label} must be a non-empty string.`, `Provide ${label}.`, assetId));
310
- }
311
- }
312
-
313
- function validateChoice(value: unknown, choices: readonly string[], label: string, diagnostics?: AssetDiagnostic[], assetId?: string): void {
314
- if (typeof value === "string" && choices.includes(value)) return;
315
- const message = `${label} must be one of: ${choices.join(", ")}`;
316
- if (diagnostics) {
317
- diagnostics.push(diagnostic("ASSET_CORPUS_ENUM", "error", message, `Use a supported ${label} value.`, assetId));
318
- return;
319
- }
320
- throw new Error(message);
321
- }
322
-
323
- function diagnostic(code: string, severity: AssetDiagnosticSeverity, message: string, nextAction: string, assetId?: string): AssetDiagnostic {
324
- return { code, severity, message, nextAction, ...(assetId ? { assetId } : {}) };
325
- }
326
-
327
- function isRecord(value: unknown): value is Record<string, unknown> {
328
- return typeof value === "object" && value !== null;
329
- }
@@ -1,83 +0,0 @@
1
- export class AssetDependencyGraph {
2
- private readonly outgoing = new Map<string, Set<string>>();
3
- private readonly incoming = new Map<string, Set<string>>();
4
-
5
- addNode(id: string): void {
6
- this.outgoing.get(id) ?? this.outgoing.set(id, new Set());
7
- this.incoming.get(id) ?? this.incoming.set(id, new Set());
8
- }
9
-
10
- addDependency(ownerId: string, dependencyId: string): void {
11
- this.addNode(ownerId);
12
- this.addNode(dependencyId);
13
-
14
- if (ownerId === dependencyId || this.dependsOn(dependencyId, ownerId)) {
15
- throw new Error(`Asset dependency cycle detected: ${ownerId} -> ${dependencyId}`);
16
- }
17
-
18
- this.outgoing.get(ownerId)?.add(dependencyId);
19
- this.incoming.get(dependencyId)?.add(ownerId);
20
- }
21
-
22
- dependenciesOf(id: string): readonly string[] {
23
- return [...(this.outgoing.get(id) ?? [])];
24
- }
25
-
26
- dependentsOf(id: string): readonly string[] {
27
- return [...(this.incoming.get(id) ?? [])];
28
- }
29
-
30
- remove(id: string): void {
31
- for (const dependency of this.dependenciesOf(id)) {
32
- this.incoming.get(dependency)?.delete(id);
33
- }
34
-
35
- for (const dependent of this.dependentsOf(id)) {
36
- this.outgoing.get(dependent)?.delete(id);
37
- }
38
-
39
- this.outgoing.delete(id);
40
- this.incoming.delete(id);
41
- }
42
-
43
- releaseOrder(rootId: string): readonly string[] {
44
- const visited = new Set<string>();
45
- const order: string[] = [];
46
-
47
- const visit = (id: string): void => {
48
- if (visited.has(id)) {
49
- return;
50
- }
51
-
52
- visited.add(id);
53
- for (const dependency of this.dependenciesOf(id)) {
54
- visit(dependency);
55
- }
56
- order.push(id);
57
- };
58
-
59
- visit(rootId);
60
- return order;
61
- }
62
-
63
- private dependsOn(startId: string, targetId: string): boolean {
64
- const seen = new Set<string>();
65
- const stack = [startId];
66
-
67
- while (stack.length > 0) {
68
- const current = stack.pop();
69
- if (!current || seen.has(current)) {
70
- continue;
71
- }
72
-
73
- if (current === targetId) {
74
- return true;
75
- }
76
-
77
- seen.add(current);
78
- stack.push(...this.dependenciesOf(current));
79
- }
80
-
81
- return false;
82
- }
83
- }
@@ -1,74 +0,0 @@
1
- export type AssetHandleStatus = "ready" | "disposed";
2
-
3
- export interface AssetHandleOptions<T> {
4
- readonly id: string;
5
- readonly url: string;
6
- readonly type: string;
7
- readonly value: T;
8
- readonly dispose?: (value: T) => void | Promise<void>;
9
- }
10
-
11
- export class AssetHandle<T> {
12
- readonly id: string;
13
- readonly url: string;
14
- readonly type: string;
15
-
16
- private readonly valueRef: T;
17
- private readonly disposer?: (value: T) => void | Promise<void>;
18
- private retainCount = 1;
19
- private currentStatus: AssetHandleStatus = "ready";
20
-
21
- constructor(options: AssetHandleOptions<T>) {
22
- this.id = options.id;
23
- this.url = options.url;
24
- this.type = options.type;
25
- this.valueRef = options.value;
26
- this.disposer = options.dispose;
27
- }
28
-
29
- get status(): AssetHandleStatus {
30
- return this.currentStatus;
31
- }
32
-
33
- get refCount(): number {
34
- return this.retainCount;
35
- }
36
-
37
- get disposed(): boolean {
38
- return this.currentStatus === "disposed";
39
- }
40
-
41
- get value(): T {
42
- this.assertAlive();
43
- return this.valueRef;
44
- }
45
-
46
- retain(): this {
47
- this.assertAlive();
48
- this.retainCount += 1;
49
- return this;
50
- }
51
-
52
- async release(): Promise<number> {
53
- this.assertAlive();
54
- this.retainCount -= 1;
55
-
56
- if (this.retainCount < 0) {
57
- this.retainCount = 0;
58
- throw new Error(`Asset handle ${this.id} was released too many times`);
59
- }
60
-
61
- if (this.retainCount === 0) {
62
- this.currentStatus = "disposed";
63
- await this.disposer?.(this.valueRef);
64
- }
65
-
66
- return this.retainCount;
67
- }
68
-
69
- assertAlive(): void {
70
- if (this.currentStatus === "disposed") {
71
- throw new Error(`Asset handle ${this.id} for ${this.url} has been disposed`);
72
- }
73
- }
74
- }
@@ -1,179 +0,0 @@
1
- export type AssetImportPreflightProfile = "high-quality" | "balanced" | "web" | "mobile";
2
-
3
- export type AssetImportPreflightFormat =
4
- | "gltf"
5
- | "glb"
6
- | "fbx"
7
- | "usd"
8
- | "usdz"
9
- | "obj"
10
- | "dae"
11
- | "unknown";
12
-
13
- export type AssetImportPreflightStatus = "supported" | "convert-required" | "blocked";
14
-
15
- export interface AssetImportPreflightOptions {
16
- readonly profile?: AssetImportPreflightProfile;
17
- readonly fileBytes?: number;
18
- }
19
-
20
- export interface AssetImportPreflightSettings {
21
- readonly texture: {
22
- readonly maxSize: number;
23
- readonly compression: "prefer-source" | "bc3" | "etc2" | "basis";
24
- readonly generateMipmaps: boolean;
25
- readonly colorSpace: "srgb";
26
- };
27
- readonly mesh: {
28
- readonly optimize: boolean;
29
- readonly generateNormals: boolean;
30
- readonly generateTangents: boolean;
31
- readonly weldVertices: boolean;
32
- readonly targetTriangleCount?: number;
33
- };
34
- }
35
-
36
- export interface AssetImportPreflightReport {
37
- readonly source: "origin-master-asset-importer-adapted";
38
- readonly url: string;
39
- readonly format: AssetImportPreflightFormat;
40
- readonly profile: AssetImportPreflightProfile;
41
- readonly status: AssetImportPreflightStatus;
42
- readonly supportedByCurrentLoader: boolean;
43
- readonly fileBytes?: number;
44
- readonly settings: AssetImportPreflightSettings;
45
- readonly stages: readonly string[];
46
- readonly diagnostics: readonly {
47
- readonly code: string;
48
- readonly severity: "info" | "warning" | "error";
49
- readonly message: string;
50
- readonly nextAction: string;
51
- }[];
52
- readonly claimBoundary: string;
53
- }
54
-
55
- const NATIVE_SUPPORTED_FORMATS = new Set<AssetImportPreflightFormat>(["gltf", "glb", "obj"]);
56
-
57
- const EXTENSION_FORMATS: Record<string, AssetImportPreflightFormat> = {
58
- ".gltf": "gltf",
59
- ".glb": "glb",
60
- ".fbx": "fbx",
61
- ".usd": "usd",
62
- ".usda": "usd",
63
- ".usdc": "usd",
64
- ".usdz": "usdz",
65
- ".obj": "obj",
66
- ".dae": "dae"
67
- };
68
-
69
- export function createAssetImportPreflightReport(
70
- url: string,
71
- options: AssetImportPreflightOptions = {}
72
- ): AssetImportPreflightReport {
73
- const trimmed = url.trim();
74
- if (!trimmed) {
75
- throw new Error("Asset import preflight URL is required.");
76
- }
77
- if (options.fileBytes !== undefined && (!Number.isFinite(options.fileBytes) || options.fileBytes < 0)) {
78
- throw new Error("Asset import preflight fileBytes must be a non-negative finite number.");
79
- }
80
- const profile = options.profile ?? "balanced";
81
- const format = detectAssetImportFormat(trimmed);
82
- const supportedByCurrentLoader = NATIVE_SUPPORTED_FORMATS.has(format);
83
- const status = supportedByCurrentLoader ? "supported" : format === "unknown" ? "blocked" : "convert-required";
84
- const diagnostics = preflightDiagnostics(format, status);
85
- return {
86
- source: "origin-master-asset-importer-adapted",
87
- url: trimmed,
88
- format,
89
- profile,
90
- status,
91
- supportedByCurrentLoader,
92
- ...(options.fileBytes !== undefined ? { fileBytes: Math.round(options.fileBytes) } : {}),
93
- settings: settingsForProfile(profile),
94
- stages: stagesFor(format, status),
95
- diagnostics,
96
- claimBoundary: "This preflight adapts old import-profile ideas for the current glTF-first pipeline. glTF/GLB are production corpus formats and OBJ has a bounded native geometry-only loader; native FBX/USD/USDZ/DAE import and Unity/Unreal/DCC pipeline parity remain blocked."
97
- };
98
- }
99
-
100
- export function detectAssetImportFormat(url: string): AssetImportPreflightFormat {
101
- const path = url.split(/[?#]/, 1)[0] ?? url;
102
- const lower = path.toLowerCase();
103
- const extension = Object.keys(EXTENSION_FORMATS)
104
- .sort((left, right) => right.length - left.length)
105
- .find((candidate) => lower.endsWith(candidate));
106
- return extension ? EXTENSION_FORMATS[extension] ?? "unknown" : "unknown";
107
- }
108
-
109
- function settingsForProfile(profile: AssetImportPreflightProfile): AssetImportPreflightSettings {
110
- switch (profile) {
111
- case "high-quality":
112
- return {
113
- texture: { maxSize: 4096, compression: "prefer-source", generateMipmaps: true, colorSpace: "srgb" },
114
- mesh: { optimize: true, generateNormals: true, generateTangents: true, weldVertices: true }
115
- };
116
- case "web":
117
- return {
118
- texture: { maxSize: 2048, compression: "basis", generateMipmaps: true, colorSpace: "srgb" },
119
- mesh: { optimize: true, generateNormals: true, generateTangents: true, weldVertices: true, targetTriangleCount: 60_000 }
120
- };
121
- case "mobile":
122
- return {
123
- texture: { maxSize: 1024, compression: "etc2", generateMipmaps: true, colorSpace: "srgb" },
124
- mesh: { optimize: true, generateNormals: true, generateTangents: false, weldVertices: true, targetTriangleCount: 25_000 }
125
- };
126
- case "balanced":
127
- return {
128
- texture: { maxSize: 2048, compression: "bc3", generateMipmaps: true, colorSpace: "srgb" },
129
- mesh: { optimize: true, generateNormals: true, generateTangents: true, weldVertices: true, targetTriangleCount: 100_000 }
130
- };
131
- }
132
- }
133
-
134
- function stagesFor(format: AssetImportPreflightFormat, status: AssetImportPreflightStatus): readonly string[] {
135
- const common = ["identify-format", "normalize-import-settings", "dependency-scan"];
136
- if (status === "supported") {
137
- if (format === "obj") {
138
- return [...common, "obj-geometry-parse", "obj-to-gltf-render-resource-path", "mesh-optimization", "render-resource-validation"];
139
- }
140
- return [...common, "gltf-load", "texture-decode", "mesh-optimization", "render-resource-validation"];
141
- }
142
- if (status === "convert-required") {
143
- return [...common, `${format}-external-conversion-required`, "gltf-reimport-required", "render-resource-validation-blocked"];
144
- }
145
- return [...common, "unsupported-format-blocked"];
146
- }
147
-
148
- function preflightDiagnostics(format: AssetImportPreflightFormat, status: AssetImportPreflightStatus): AssetImportPreflightReport["diagnostics"] {
149
- if (status === "supported") {
150
- if (format === "obj") {
151
- return [{
152
- code: "ASSET_IMPORT_OBJ_NATIVE_BOUNDED",
153
- severity: "info",
154
- message: "Current asset pipeline can parse OBJ geometry natively through the bounded OBJLoader and then reuse the glTF render-resource path.",
155
- nextAction: "Use OBJ only for geometry-only import evidence; convert materials, animation, skinning, and scene metadata through glTF/GLB before parity claims."
156
- }];
157
- }
158
- return [{
159
- code: "ASSET_IMPORT_GLTF_SUPPORTED",
160
- severity: "info",
161
- message: "Current asset pipeline can load this format through the glTF loader.",
162
- nextAction: "Run the normal GLTFLoader/import pipeline and browser visual evidence."
163
- }];
164
- }
165
- if (status === "convert-required") {
166
- return [{
167
- code: "ASSET_IMPORT_EXTERNAL_CONVERSION_REQUIRED",
168
- severity: "warning",
169
- message: `${format.toUpperCase()} is recognized for preflight only and must be converted to glTF/GLB before current rendering evidence is valid.`,
170
- nextAction: "Convert through a real DCC/export tool, attach conversion provenance, then run the glTF browser corpus and compatibility reports."
171
- }];
172
- }
173
- return [{
174
- code: "ASSET_IMPORT_FORMAT_BLOCKED",
175
- severity: "error",
176
- message: "The asset extension is not recognized by the current import preflight.",
177
- nextAction: "Add a narrow preflight mapping and conversion workflow before claiming support."
178
- }];
179
- }