@aura3d/assets 1.4.0 → 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.
- package/package.json +38 -5
- package/AGENTS.md +0 -46
- package/src/AdvancedAssetCorpus.ts +0 -454
- package/src/AssetBundleCacheFixtures.ts +0 -290
- package/src/AssetCache.ts +0 -76
- package/src/AssetCompatibility.ts +0 -224
- package/src/AssetCorpus.ts +0 -329
- package/src/AssetDependencyGraph.ts +0 -83
- package/src/AssetHandle.ts +0 -74
- package/src/AssetImportPreflight.ts +0 -179
- package/src/AssetInspection.ts +0 -706
- package/src/AssetLoader.ts +0 -36
- package/src/AssetManager.ts +0 -211
- package/src/AssetRegistry.ts +0 -44
- package/src/AssetRenderDefaults.ts +0 -36
- package/src/AudioLoader.ts +0 -41
- package/src/BlenderExportValidation.ts +0 -227
- package/src/CarConceptMaterialStability.ts +0 -1405
- package/src/EXRLoader.ts +0 -1
- package/src/ExternalParityGLTFCorpus.ts +0 -65
- package/src/GLTFAnimationRuntime.ts +0 -1416
- package/src/GLTFAutoFit.ts +0 -133
- package/src/GLTFCompressionDecoders.ts +0 -190
- package/src/GLTFExtensionSupport.ts +0 -138
- package/src/GLTFLoader.ts +0 -4060
- package/src/GLTFRenderResources.ts +0 -2270
- package/src/HDRLoader.ts +0 -1
- package/src/ImageLoader.ts +0 -52
- package/src/ImportPipeline.ts +0 -148
- package/src/KTX2BasisTextureTranscoder.ts +0 -225
- package/src/LoadContext.ts +0 -60
- package/src/MaterialLoader.ts +0 -166
- package/src/MeshOptimization.ts +0 -93
- package/src/OBJLoader.ts +0 -551
- package/src/SceneAnalysisFixtures.ts +0 -438
- package/src/SceneLoader.ts +0 -62
- package/src/ShaderLoader.ts +0 -30
- package/src/TextureLoader.ts +0 -30
- package/src/TexturePipeline.ts +0 -96
- package/src/TextureStreaming.ts +0 -20
- package/src/WorkerAssetJobs.ts +0 -56
- package/src/advanced-gallery/index.ts +0 -18
- package/src/asset-corpus/GLTFSceneLoader.ts +0 -9
- package/src/asset-corpus/ProductionAssetCorpus.ts +0 -230
- package/src/asset-corpus/ProductionAssetPipeline.ts +0 -5
- package/src/asset-corpus/ProductionGLTFRenderPipeline.ts +0 -182
- package/src/asset-corpus/ProductionHDRTextureLoader.ts +0 -2
- package/src/asset-corpus/ProductionKTX2TextureLoader.ts +0 -2
- package/src/asset-corpus/ProductionTextureLoader.ts +0 -2
- package/src/asset-corpus/index.ts +0 -29
- package/src/browser-index.ts +0 -229
- package/src/createRenderableScene.ts +0 -128
- package/src/index.ts +0 -325
- package/src/loadRenderableAsset.ts +0 -67
- package/src/loaders/CubeTextureLoader.ts +0 -8
- package/src/loaders/EXRLoader.ts +0 -10
- package/src/loaders/HDRLoader.ts +0 -7
- package/src/loaders/KTX2Loader.ts +0 -7
- package/src/loaders/LoaderDiagnostics.ts +0 -36
- package/src/loaders/MTLLoader.ts +0 -20
- package/src/loaders/OBJLoader.ts +0 -24
- package/src/loaders/TextureLoader.ts +0 -14
- package/src/loaders/ThreeCompatGLTFLoader.ts +0 -19
- package/src/loaders/index.ts +0 -14
- package/src/threejs-compatibility/ThreeCompatAssetProvenance.ts +0 -51
- package/src/threejs-compatibility/ThreeCompatAssetRegistry.ts +0 -61
- package/src/threejs-example-parity/index.ts +0 -262
- package/tests/assets.test.ts +0 -393
- package/tsconfig.json +0 -7
package/tests/assets.test.ts
DELETED
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
AssetManager,
|
|
5
|
-
computeAutoFitTransform,
|
|
6
|
-
GLTFLoader,
|
|
7
|
-
normalizeSkinWeights,
|
|
8
|
-
OBJLoader,
|
|
9
|
-
resolveGLTFClipName,
|
|
10
|
-
type AssetLoader
|
|
11
|
-
} from "../src/index";
|
|
12
|
-
|
|
13
|
-
test("normalizeSkinWeights collapses degenerate (zero-sum) weights to a rigid binding", () => {
|
|
14
|
-
const stats = { degenerate: 0, rescaled: 0 };
|
|
15
|
-
assert.deepEqual(normalizeSkinWeights([0, 0, 0, 0], stats), [1, 0, 0, 0]);
|
|
16
|
-
assert.equal(stats.degenerate, 1);
|
|
17
|
-
assert.equal(stats.rescaled, 0);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("normalizeSkinWeights rescales denormalized weights to sum 1 (FLOAT)", () => {
|
|
21
|
-
const stats = { degenerate: 0, rescaled: 0 };
|
|
22
|
-
const result = normalizeSkinWeights([2, 2, 0, 0], stats);
|
|
23
|
-
assert.deepEqual(result, [0.5, 0.5, 0, 0]);
|
|
24
|
-
assert.equal(stats.rescaled, 1);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("normalizeSkinWeights rescales integer-encoded weights regardless of componentType scale", () => {
|
|
28
|
-
// UNSIGNED_BYTE-style raw values (sum 255) and UNSIGNED_SHORT-style (sum 65535)
|
|
29
|
-
// both normalize by proportion to the same result.
|
|
30
|
-
assert.deepEqual(normalizeSkinWeights([255, 0, 0, 0]), [1, 0, 0, 0]);
|
|
31
|
-
assert.deepEqual(normalizeSkinWeights([32767, 32768, 0, 0]).map((value) => Math.round(value * 100) / 100), [0.5, 0.5, 0, 0]);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test("normalizeSkinWeights clamps negative and non-finite components to zero", () => {
|
|
35
|
-
assert.deepEqual(normalizeSkinWeights([1, -1, Number.NaN, Number.POSITIVE_INFINITY]), [1, 0, 0, 0]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("normalizeSkinWeights leaves already-normalized weights untouched", () => {
|
|
39
|
-
const stats = { degenerate: 0, rescaled: 0 };
|
|
40
|
-
assert.deepEqual(normalizeSkinWeights([0.6, 0.4, 0, 0], stats), [0.6, 0.4, 0, 0]);
|
|
41
|
-
assert.equal(stats.degenerate, 0);
|
|
42
|
-
assert.equal(stats.rescaled, 0);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("resolveGLTFClipName matches exact (case/format-insensitive) first", () => {
|
|
46
|
-
assert.equal(resolveGLTFClipName("Idle", ["Walk", "idle", "Run"]), "idle");
|
|
47
|
-
assert.equal(resolveGLTFClipName("walk-cycle", ["Run", "Walk_Cycle"]), "Walk_Cycle");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("resolveGLTFClipName resolves via synonym groups", () => {
|
|
51
|
-
assert.equal(resolveGLTFClipName("idle", ["Loops", "Sprint"]), "Loops");
|
|
52
|
-
assert.equal(resolveGLTFClipName("run", ["Loops", "Sprint"]), "Sprint");
|
|
53
|
-
assert.equal(resolveGLTFClipName("wave", ["Greet", "Static"]), "Greet");
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("resolveGLTFClipName falls back to substring then first clip", () => {
|
|
57
|
-
assert.equal(resolveGLTFClipName("walk", ["WalkForward", "Run"]), "WalkForward");
|
|
58
|
-
assert.equal(resolveGLTFClipName("nonexistent", ["Take 001", "Other"]), "Take 001");
|
|
59
|
-
assert.equal(resolveGLTFClipName("anything", []), undefined);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("computeAutoFitTransform scales tall and tiny models to the target height", () => {
|
|
63
|
-
const big = computeAutoFitTransform({ min: [-900, 0, -900], max: [900, 1821, 900] }, { targetHeight: 1.6 });
|
|
64
|
-
assert.ok(Math.abs(big.fittedHeight - 1.6) < 1e-6);
|
|
65
|
-
assert.ok(big.scale < 0.01);
|
|
66
|
-
|
|
67
|
-
const tiny = computeAutoFitTransform({ min: [-0.5, 0, -0.5], max: [0.5, 1, 0.5] });
|
|
68
|
-
assert.ok(Math.abs(tiny.fittedHeight - 1.6) < 1e-6);
|
|
69
|
-
assert.ok(tiny.scale > 1);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("computeAutoFitTransform recenters feet to y=0 and centers x/z", () => {
|
|
73
|
-
const fit = computeAutoFitTransform({ min: [2, 4, 6], max: [4, 6, 8] }, { targetHeight: 2 });
|
|
74
|
-
// height 2 -> scale 1; center (3,5,7), floor y=4
|
|
75
|
-
assert.equal(fit.scale, 1);
|
|
76
|
-
assert.deepEqual(fit.translation, [-3, -4, -7]);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("computeAutoFitTransform applies z-up correction", () => {
|
|
80
|
-
const fit = computeAutoFitTransform({ min: [-1, -1, 0], max: [1, 1, 4] }, { upAxis: "z", targetHeight: 4 });
|
|
81
|
-
assert.equal(fit.upAxisCorrection, "z-to-y");
|
|
82
|
-
// after z->y rotation the source z-extent (4) becomes height -> scale 1
|
|
83
|
-
assert.ok(Math.abs(fit.fittedHeight - 4) < 1e-6);
|
|
84
|
-
assert.notDeepEqual(fit.rotation, [0, 0, 0, 1]);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("AssetManager shares duplicate in-flight loads and releases cached handles", async () => {
|
|
88
|
-
let loads = 0;
|
|
89
|
-
let disposes = 0;
|
|
90
|
-
const loader: AssetLoader<{ readonly text: string }> = {
|
|
91
|
-
type: "text",
|
|
92
|
-
canLoad: () => true,
|
|
93
|
-
async load(request) {
|
|
94
|
-
loads += 1;
|
|
95
|
-
await Promise.resolve();
|
|
96
|
-
return { text: request.url };
|
|
97
|
-
},
|
|
98
|
-
dispose() {
|
|
99
|
-
disposes += 1;
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const assets = new AssetManager({ baseUrl: "https://example.test/assets/" });
|
|
104
|
-
assets.register(loader);
|
|
105
|
-
|
|
106
|
-
const [a, b] = await Promise.all([assets.load("hero.txt", { type: "text" }), assets.load("hero.txt", { type: "text" })]);
|
|
107
|
-
assert.equal(a, b);
|
|
108
|
-
assert.equal(loads, 1);
|
|
109
|
-
assert.equal(a.refCount, 2);
|
|
110
|
-
|
|
111
|
-
await assets.release(a);
|
|
112
|
-
assert.equal(disposes, 0);
|
|
113
|
-
await assets.release(b);
|
|
114
|
-
assert.equal(disposes, 1);
|
|
115
|
-
assert.throws(() => a.value, /disposed/);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("AssetManager retries after failed load instead of poisoning cache", async () => {
|
|
119
|
-
let attempts = 0;
|
|
120
|
-
const loader: AssetLoader<string> = {
|
|
121
|
-
type: "text",
|
|
122
|
-
canLoad: () => true,
|
|
123
|
-
load() {
|
|
124
|
-
attempts += 1;
|
|
125
|
-
if (attempts === 1) {
|
|
126
|
-
throw new Error("boom");
|
|
127
|
-
}
|
|
128
|
-
return "ok";
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const assets = new AssetManager();
|
|
133
|
-
assets.register(loader);
|
|
134
|
-
|
|
135
|
-
await assert.rejects(() => assets.load("retry.txt", { type: "text" }), /Failed to load asset/);
|
|
136
|
-
const handle = await assets.load<string>("retry.txt", { type: "text" });
|
|
137
|
-
assert.equal(handle.value, "ok");
|
|
138
|
-
assert.equal(attempts, 2);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("AssetManager releases dependencies through the dependency graph", async () => {
|
|
142
|
-
const disposed: string[] = [];
|
|
143
|
-
const leaf: AssetLoader<string> = {
|
|
144
|
-
type: "leaf",
|
|
145
|
-
canLoad: (request) => request.url.endsWith(".leaf"),
|
|
146
|
-
load: (request) => request.url,
|
|
147
|
-
dispose: (value) => {
|
|
148
|
-
disposed.push(value);
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
const parent: AssetLoader<string> = {
|
|
152
|
-
type: "parent",
|
|
153
|
-
canLoad: (request) => request.url.endsWith(".parent"),
|
|
154
|
-
dependencies: () => ["child.leaf"],
|
|
155
|
-
load: (request) => request.url,
|
|
156
|
-
dispose: (value) => {
|
|
157
|
-
disposed.push(value);
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
const assets = new AssetManager();
|
|
161
|
-
assets.register(leaf);
|
|
162
|
-
assets.register(parent);
|
|
163
|
-
|
|
164
|
-
const handle = await assets.load("root.parent", { type: "parent" });
|
|
165
|
-
await assets.release(handle);
|
|
166
|
-
|
|
167
|
-
assert.deepEqual(disposed.sort(), ["child.leaf", "root.parent"]);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("GLTFLoader loads a triangle fixture into geometry and scene data", async () => {
|
|
171
|
-
const buffer = Buffer.alloc(44);
|
|
172
|
-
new Float32Array(buffer.buffer, buffer.byteOffset, 9).set([-0.5, -0.5, 0, 0.5, -0.5, 0, 0, 0.5, 0]);
|
|
173
|
-
new Uint16Array(buffer.buffer, buffer.byteOffset + 36, 3).set([0, 1, 2]);
|
|
174
|
-
const gltf = {
|
|
175
|
-
asset: { version: "2.0" },
|
|
176
|
-
buffers: [{ uri: `data:application/octet-stream;base64,${buffer.toString("base64")}`, byteLength: 44 }],
|
|
177
|
-
bufferViews: [
|
|
178
|
-
{ buffer: 0, byteOffset: 0, byteLength: 36 },
|
|
179
|
-
{ buffer: 0, byteOffset: 36, byteLength: 6 }
|
|
180
|
-
],
|
|
181
|
-
accessors: [
|
|
182
|
-
{ bufferView: 0, componentType: 5126, count: 3, type: "VEC3" },
|
|
183
|
-
{ bufferView: 1, componentType: 5123, count: 3, type: "SCALAR" }
|
|
184
|
-
],
|
|
185
|
-
materials: [{ name: "mat" }],
|
|
186
|
-
meshes: [{ name: "tri", primitives: [{ attributes: { POSITION: 0 }, indices: 1, material: 0 }] }],
|
|
187
|
-
nodes: [{ name: "triangle", mesh: 0 }],
|
|
188
|
-
scenes: [{ nodes: [0] }],
|
|
189
|
-
scene: 0
|
|
190
|
-
};
|
|
191
|
-
const url = `data:model/gltf+json,${encodeURIComponent(JSON.stringify(gltf))}`;
|
|
192
|
-
const asset = await new GLTFLoader().load({ url }, { throwIfAborted: () => undefined } as never);
|
|
193
|
-
|
|
194
|
-
assert.equal(asset.meshes.length, 1);
|
|
195
|
-
assert.equal(asset.meshes[0]?.geometry.vertexCount, 3);
|
|
196
|
-
assert.equal(asset.meshes[0]?.positions.length, 3);
|
|
197
|
-
assert.deepEqual(asset.meshes[0]?.indices, [0, 1, 2]);
|
|
198
|
-
assert.equal(asset.createScene().collectRenderables().length, 1);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("GLTFLoader loads a binary GLB fixture with a BIN chunk", async () => {
|
|
202
|
-
const binary = Buffer.alloc(44);
|
|
203
|
-
new Float32Array(binary.buffer, binary.byteOffset, 9).set([-1, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
204
|
-
new Uint16Array(binary.buffer, binary.byteOffset + 36, 3).set([0, 1, 2]);
|
|
205
|
-
const gltf = {
|
|
206
|
-
asset: { version: "2.0" },
|
|
207
|
-
buffers: [{ byteLength: 44 }],
|
|
208
|
-
bufferViews: [
|
|
209
|
-
{ buffer: 0, byteOffset: 0, byteLength: 36 },
|
|
210
|
-
{ buffer: 0, byteOffset: 36, byteLength: 6 }
|
|
211
|
-
],
|
|
212
|
-
accessors: [
|
|
213
|
-
{ bufferView: 0, componentType: 5126, count: 3, type: "VEC3" },
|
|
214
|
-
{ bufferView: 1, componentType: 5123, count: 3, type: "SCALAR" }
|
|
215
|
-
],
|
|
216
|
-
meshes: [{ name: "glb-triangle", primitives: [{ attributes: { POSITION: 0 }, indices: 1 }] }],
|
|
217
|
-
nodes: [{ name: "glb-node", mesh: 0 }],
|
|
218
|
-
scenes: [{ nodes: [0] }]
|
|
219
|
-
};
|
|
220
|
-
const url = `data:model/gltf-binary;base64,${createGLB(gltf, binary).toString("base64")}`;
|
|
221
|
-
|
|
222
|
-
const asset = await new GLTFLoader().load({ url }, { throwIfAborted: () => undefined } as never);
|
|
223
|
-
|
|
224
|
-
assert.equal(asset.meshes[0]?.name, "glb-triangle");
|
|
225
|
-
assert.equal(asset.meshes[0]?.geometry.indexCount, 3);
|
|
226
|
-
assert.deepEqual(asset.meshes[0]?.positions[2], [0, 1, 0]);
|
|
227
|
-
assert.equal(asset.createScene().findByName("glb-node").length, 1);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("OBJLoader parses bounded geometry into the glTF render-resource path", async () => {
|
|
231
|
-
const obj = [
|
|
232
|
-
"v -0.5 -0.5 0",
|
|
233
|
-
"v 0.5 -0.5 0",
|
|
234
|
-
"v 0.5 0.5 0",
|
|
235
|
-
"v -0.5 0.5 0",
|
|
236
|
-
"vt 0 0",
|
|
237
|
-
"vt 1 0",
|
|
238
|
-
"vt 1 1",
|
|
239
|
-
"vt 0 1",
|
|
240
|
-
"f 1/1 2/2 3/3 4/4"
|
|
241
|
-
].join("\n");
|
|
242
|
-
const url = `data:text/plain,${encodeURIComponent(obj)}`;
|
|
243
|
-
const asset = await new OBJLoader().load({ url, type: "obj" }, { throwIfAborted: () => undefined } as never);
|
|
244
|
-
|
|
245
|
-
assert.equal(asset.url, url);
|
|
246
|
-
assert.equal(asset.meshes.length, 1);
|
|
247
|
-
assert.equal(asset.meshes[0]?.geometry.vertexCount, 6);
|
|
248
|
-
assert.equal(asset.meshes[0]?.geometry.indexCount, 6);
|
|
249
|
-
assert.equal(asset.meshes[0]?.texcoords.length, 6);
|
|
250
|
-
assert.equal(asset.meshes[0]?.normals.length, 6);
|
|
251
|
-
assert.deepEqual(asset.meshes[0]?.geometry.bounds.min, [-0.5, -0.5, 0]);
|
|
252
|
-
assert.deepEqual(asset.meshes[0]?.geometry.bounds.max, [0.5, 0.5, 0]);
|
|
253
|
-
assert(asset.loaderDiagnostics.features.includes("obj-native-import"));
|
|
254
|
-
assert(asset.loaderDiagnostics.features.includes("obj-generated-normals"));
|
|
255
|
-
assert.equal(asset.createScene().collectRenderables().length, 1);
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("OBJLoader preserves mtllib/usemtl material groups through glTF primitives", async () => {
|
|
259
|
-
const obj = [
|
|
260
|
-
"mtllib sample.mtl",
|
|
261
|
-
"v -0.5 -0.5 0",
|
|
262
|
-
"v 0.5 -0.5 0",
|
|
263
|
-
"v 0.5 0.5 0",
|
|
264
|
-
"v -0.5 0.5 0",
|
|
265
|
-
"usemtl amber",
|
|
266
|
-
"f 1 2 3",
|
|
267
|
-
"usemtl blue",
|
|
268
|
-
"f 1 3 4"
|
|
269
|
-
].join("\n");
|
|
270
|
-
const mtl = [
|
|
271
|
-
"newmtl amber",
|
|
272
|
-
"Kd 1.0 0.62 0.14",
|
|
273
|
-
"Ns 64",
|
|
274
|
-
"newmtl blue",
|
|
275
|
-
"Kd 0.1 0.35 0.9",
|
|
276
|
-
"d 0.75"
|
|
277
|
-
].join("\n");
|
|
278
|
-
const url = `data:text/plain,${encodeURIComponent(obj)}`;
|
|
279
|
-
const request = { url, type: "obj", materialLibraries: { "sample.mtl": mtl } };
|
|
280
|
-
const asset = await new OBJLoader().load(request, { throwIfAborted: () => undefined } as never);
|
|
281
|
-
|
|
282
|
-
assert.equal(asset.materials.length, 2);
|
|
283
|
-
assert.deepEqual(asset.materials.map((material) => material.name), ["amber", "blue"]);
|
|
284
|
-
assert.deepEqual(asset.materials[0]?.baseColorFactor, [1, 0.62, 0.14, 1]);
|
|
285
|
-
assert.deepEqual(asset.materials[1]?.baseColorFactor, [0.1, 0.35, 0.9, 0.75]);
|
|
286
|
-
assert.equal(asset.materials[1]?.alphaMode, "BLEND");
|
|
287
|
-
assert.equal(asset.meshes.length, 2);
|
|
288
|
-
assert.deepEqual(asset.meshes.map((mesh) => mesh.material), ["amber", "blue"]);
|
|
289
|
-
assert(asset.loaderDiagnostics.features.includes("obj-mtllib"));
|
|
290
|
-
assert(asset.loaderDiagnostics.features.includes("obj-multi-material"));
|
|
291
|
-
assert.equal(asset.createScene().collectRenderables().length, 2);
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
test("GLTFLoader extracts material texture metadata and embedded image bytes from GLB", async () => {
|
|
295
|
-
const meshBytes = Buffer.alloc(68);
|
|
296
|
-
new Float32Array(meshBytes.buffer, meshBytes.byteOffset, 9).set([-1, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
297
|
-
new Uint16Array(meshBytes.buffer, meshBytes.byteOffset + 36, 3).set([0, 1, 2]);
|
|
298
|
-
new Float32Array(meshBytes.buffer, meshBytes.byteOffset + 44, 6).set([0, 0, 1, 0, 0.5, 1]);
|
|
299
|
-
const imageBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
300
|
-
const binary = Buffer.concat([meshBytes, imageBytes]);
|
|
301
|
-
const gltf = {
|
|
302
|
-
asset: { version: "2.0" },
|
|
303
|
-
buffers: [{ byteLength: binary.byteLength }],
|
|
304
|
-
bufferViews: [
|
|
305
|
-
{ buffer: 0, byteOffset: 0, byteLength: 36 },
|
|
306
|
-
{ buffer: 0, byteOffset: 36, byteLength: 6 },
|
|
307
|
-
{ buffer: 0, byteOffset: 44, byteLength: 24 },
|
|
308
|
-
{ buffer: 0, byteOffset: 68, byteLength: imageBytes.byteLength }
|
|
309
|
-
],
|
|
310
|
-
accessors: [
|
|
311
|
-
{ bufferView: 0, componentType: 5126, count: 3, type: "VEC3" },
|
|
312
|
-
{ bufferView: 1, componentType: 5123, count: 3, type: "SCALAR" },
|
|
313
|
-
{ bufferView: 2, componentType: 5126, count: 3, type: "VEC2" }
|
|
314
|
-
],
|
|
315
|
-
images: [{ name: "embedded-base-color", bufferView: 3, mimeType: "image/png" }],
|
|
316
|
-
samplers: [{ name: "linear-repeat", magFilter: 9729, minFilter: 9729, wrapS: 10497, wrapT: 10497 }],
|
|
317
|
-
textures: [{ name: "base-color-texture", source: 0, sampler: 0 }],
|
|
318
|
-
materials: [
|
|
319
|
-
{
|
|
320
|
-
name: "pbr-material",
|
|
321
|
-
pbrMetallicRoughness: {
|
|
322
|
-
baseColorFactor: [0.25, 0.5, 0.75, 1],
|
|
323
|
-
baseColorTexture: { index: 0, texCoord: 1 },
|
|
324
|
-
metallicFactor: 0.2,
|
|
325
|
-
roughnessFactor: 0.8
|
|
326
|
-
},
|
|
327
|
-
doubleSided: true
|
|
328
|
-
}
|
|
329
|
-
],
|
|
330
|
-
meshes: [{ name: "textured-triangle", primitives: [{ attributes: { POSITION: 0, TEXCOORD_1: 2 }, indices: 1, material: 0 }] }],
|
|
331
|
-
nodes: [{ name: "textured-node", mesh: 0 }],
|
|
332
|
-
scenes: [{ nodes: [0] }]
|
|
333
|
-
};
|
|
334
|
-
const url = `data:model/gltf-binary;base64,${createGLB(gltf, binary).toString("base64")}`;
|
|
335
|
-
|
|
336
|
-
const asset = await new GLTFLoader().load({ url }, { throwIfAborted: () => undefined } as never);
|
|
337
|
-
|
|
338
|
-
assert.equal(asset.images[0]?.mimeType, "image/png");
|
|
339
|
-
assert.deepEqual([...new Uint8Array(asset.images[0]?.data ?? new ArrayBuffer(0))], [...imageBytes]);
|
|
340
|
-
assert.deepEqual(asset.materials[0]?.baseColorTexture, { texture: 0, image: 0, texCoord: 1 });
|
|
341
|
-
assert.equal(asset.meshes[0]?.material, "pbr-material");
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
test("GLTFLoader rejects missing material texture references", async () => {
|
|
345
|
-
const buffer = Buffer.alloc(36);
|
|
346
|
-
new Float32Array(buffer.buffer, buffer.byteOffset, 9).set([-1, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
347
|
-
const gltf = {
|
|
348
|
-
asset: { version: "2.0" },
|
|
349
|
-
buffers: [{ uri: `data:application/octet-stream;base64,${buffer.toString("base64")}`, byteLength: 36 }],
|
|
350
|
-
bufferViews: [{ buffer: 0, byteOffset: 0, byteLength: 36 }],
|
|
351
|
-
accessors: [{ bufferView: 0, componentType: 5126, count: 3, type: "VEC3" }],
|
|
352
|
-
materials: [{ pbrMetallicRoughness: { baseColorTexture: { index: 4 } } }],
|
|
353
|
-
meshes: [{ primitives: [{ attributes: { POSITION: 0 }, material: 0 }] }]
|
|
354
|
-
};
|
|
355
|
-
const url = `data:model/gltf+json,${encodeURIComponent(JSON.stringify(gltf))}`;
|
|
356
|
-
|
|
357
|
-
await assert.rejects(
|
|
358
|
-
() => new GLTFLoader().load({ url }, { throwIfAborted: () => undefined } as never),
|
|
359
|
-
/baseColorTexture references missing texture 4/
|
|
360
|
-
);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
function createGLB(json: unknown, binary: Buffer): Buffer {
|
|
364
|
-
const jsonBytes = pad4(Buffer.from(JSON.stringify(json), "utf8"), 0x20);
|
|
365
|
-
const binaryBytes = pad4(binary, 0);
|
|
366
|
-
const totalLength = 12 + 8 + jsonBytes.byteLength + 8 + binaryBytes.byteLength;
|
|
367
|
-
const glb = Buffer.alloc(totalLength);
|
|
368
|
-
let offset = 0;
|
|
369
|
-
glb.writeUInt32LE(0x46546c67, offset);
|
|
370
|
-
offset += 4;
|
|
371
|
-
glb.writeUInt32LE(2, offset);
|
|
372
|
-
offset += 4;
|
|
373
|
-
glb.writeUInt32LE(totalLength, offset);
|
|
374
|
-
offset += 4;
|
|
375
|
-
glb.writeUInt32LE(jsonBytes.byteLength, offset);
|
|
376
|
-
offset += 4;
|
|
377
|
-
glb.writeUInt32LE(0x4e4f534a, offset);
|
|
378
|
-
offset += 4;
|
|
379
|
-
jsonBytes.copy(glb, offset);
|
|
380
|
-
offset += jsonBytes.byteLength;
|
|
381
|
-
glb.writeUInt32LE(binaryBytes.byteLength, offset);
|
|
382
|
-
offset += 4;
|
|
383
|
-
glb.writeUInt32LE(0x004e4942, offset);
|
|
384
|
-
offset += 4;
|
|
385
|
-
binaryBytes.copy(glb, offset);
|
|
386
|
-
return glb;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
function pad4(buffer: Buffer, fill: number): Buffer {
|
|
390
|
-
const remainder = buffer.byteLength % 4;
|
|
391
|
-
if (remainder === 0) return buffer;
|
|
392
|
-
return Buffer.concat([buffer, Buffer.alloc(4 - remainder, fill)]);
|
|
393
|
-
}
|