@galfus/gltf-loader 0.0.0-stage → 0.31.0-alpha
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/README.md +93 -2
- package/package.json +43 -4
- package/src/animation.ts +160 -0
- package/src/bounds.ts +160 -0
- package/src/decoders/resolver.ts +131 -0
- package/src/factory.ts +303 -0
- package/src/index.ts +185 -0
- package/src/instance.ts +126 -0
- package/src/parser.ts +334 -0
- package/src/resources.ts +391 -0
- package/src/scene.ts +354 -0
- package/src/types.ts +270 -0
- package/src/zip.ts +173 -0
package/src/scene.ts
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene graph construction, ECS entity spawning, hierarchy resolution, and GPU instancing.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Model3DComponent, Realm3D } from '@galfus/engine';
|
|
6
|
+
import { mat4, quat, vec3 } from '@galfus/engine';
|
|
7
|
+
import type {
|
|
8
|
+
ModelImportOptions,
|
|
9
|
+
ModelInstantiateOptions,
|
|
10
|
+
ModelNodeDesc
|
|
11
|
+
} from '@galfus/model-loader-types';
|
|
12
|
+
import { readAccessor } from './parser';
|
|
13
|
+
import type { UploadedGltfResources } from './resources';
|
|
14
|
+
import type { GlTF, GlTFNode } from './types';
|
|
15
|
+
|
|
16
|
+
export interface SpawnedSceneResult {
|
|
17
|
+
rootEntityId: number;
|
|
18
|
+
entities: number[];
|
|
19
|
+
nodeEntityMap: Map<number, number>; // gltfNodeIndex -> ecsEntityId
|
|
20
|
+
primitiveEntities: Map<number, number[]>;
|
|
21
|
+
instancedModelIds: number[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks whether a glTF node passes the user-provided import filter.
|
|
26
|
+
*/
|
|
27
|
+
function shouldImportNode(nodeDesc: ModelNodeDesc, options?: ModelImportOptions): boolean {
|
|
28
|
+
if (!options?.filter) return true;
|
|
29
|
+
if (typeof options.filter === 'function') {
|
|
30
|
+
return options.filter(nodeDesc);
|
|
31
|
+
}
|
|
32
|
+
if (typeof options.filter === 'string') {
|
|
33
|
+
return nodeDesc.name === options.filter;
|
|
34
|
+
}
|
|
35
|
+
if (options.filter instanceof RegExp) {
|
|
36
|
+
return options.filter.test(nodeDesc.name ?? '');
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Spawns a complete glTF scene graph or sub-tree into the ECS and GPU.
|
|
43
|
+
*
|
|
44
|
+
* @param realm The target Realm3D.
|
|
45
|
+
* @param gltf The root glTF descriptor.
|
|
46
|
+
* @param buffers Array of resolved binary buffers.
|
|
47
|
+
* @param resources Pre-uploaded GPU geometries, materials, and textures.
|
|
48
|
+
* @param instantiateOptions Spatial offsets and optional parent hierarchy attachment.
|
|
49
|
+
* @param importOptions Filtering rules, node renamers, and camera/light toggles.
|
|
50
|
+
* @param rootNodeIndices Optional specific node indices to instantiate (for sub-factories).
|
|
51
|
+
* @returns Object holding the root entity, entity collections, and node mappings.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const result = spawnSceneGraph(realm, gltf, buffers, resources, options);
|
|
56
|
+
* console.log(`Spawned ${result.entities.length} entities in ECS`);
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function spawnSceneGraph(
|
|
60
|
+
realm: Realm3D,
|
|
61
|
+
gltf: GlTF,
|
|
62
|
+
buffers: Uint8Array[],
|
|
63
|
+
resources: UploadedGltfResources,
|
|
64
|
+
instantiateOptions?: ModelInstantiateOptions,
|
|
65
|
+
importOptions?: ModelImportOptions,
|
|
66
|
+
rootNodeIndices?: number[]
|
|
67
|
+
): SpawnedSceneResult {
|
|
68
|
+
const nodeCount = gltf.nodes?.length ?? 0;
|
|
69
|
+
const nodeEntityMap = new Map<number, number>();
|
|
70
|
+
const entities: number[] = [];
|
|
71
|
+
const instancedModelIds: number[] = [];
|
|
72
|
+
const primitiveEntities = new Map<number, number[]>();
|
|
73
|
+
|
|
74
|
+
// Determine root nodes: either user-specified or from active glTF scene
|
|
75
|
+
let candidateRoots: number[] = [];
|
|
76
|
+
if (rootNodeIndices && rootNodeIndices.length > 0) {
|
|
77
|
+
candidateRoots = rootNodeIndices;
|
|
78
|
+
} else {
|
|
79
|
+
const activeSceneIdx = gltf.scene ?? 0;
|
|
80
|
+
const activeScene = gltf.scenes?.[activeSceneIdx];
|
|
81
|
+
candidateRoots = activeScene?.nodes ?? (nodeCount > 0 ? [0] : []);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 1. Create a root container entity for this instance
|
|
85
|
+
const rootEntityId = realm.createSpatialNode({
|
|
86
|
+
position: instantiateOptions?.transform?.position ?? [0, 0, 0],
|
|
87
|
+
rotation: instantiateOptions?.transform?.rotation ?? [0, 0, 0, 1],
|
|
88
|
+
scale: instantiateOptions?.transform?.scale ?? [1, 1, 1]
|
|
89
|
+
});
|
|
90
|
+
entities.push(rootEntityId);
|
|
91
|
+
realm.setEntityName(rootEntityId, 'GltfInstance_Root');
|
|
92
|
+
|
|
93
|
+
if (instantiateOptions?.parentEntity !== undefined) {
|
|
94
|
+
realm.hierarchy.attach(rootEntityId, instantiateOptions.parentEntity);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Fallback default material if a primitive omits material definition
|
|
98
|
+
let defaultMaterialId: number | undefined;
|
|
99
|
+
function getDefaultMaterial(): number {
|
|
100
|
+
if (defaultMaterialId === undefined) {
|
|
101
|
+
defaultMaterialId = realm.createMaterial({ baseColor: [1, 1, 1, 1] }).materialId;
|
|
102
|
+
}
|
|
103
|
+
return defaultMaterialId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Helper to extract TRS from glTF node
|
|
107
|
+
function getNodeTRS(node: GlTFNode): {
|
|
108
|
+
position: [number, number, number];
|
|
109
|
+
rotation: [number, number, number, number];
|
|
110
|
+
scale: [number, number, number];
|
|
111
|
+
} {
|
|
112
|
+
if (node.matrix) {
|
|
113
|
+
// Decompose 4x4 column-major matrix
|
|
114
|
+
const m = node.matrix;
|
|
115
|
+
const pos: [number, number, number] = [m[12] ?? 0, m[13] ?? 0, m[14] ?? 0];
|
|
116
|
+
const scl: [number, number, number] = [
|
|
117
|
+
Math.hypot(m[0] ?? 1, m[1] ?? 0, m[2] ?? 0),
|
|
118
|
+
Math.hypot(m[4] ?? 0, m[5] ?? 1, m[6] ?? 0),
|
|
119
|
+
Math.hypot(m[8] ?? 0, m[9] ?? 0, m[10] ?? 1)
|
|
120
|
+
];
|
|
121
|
+
// Orientation from rotation matrix
|
|
122
|
+
const rot: [number, number, number, number] = [0, 0, 0, 1];
|
|
123
|
+
quat.fromMat4(rot as any, m as any);
|
|
124
|
+
return { position: pos, rotation: rot, scale: scl };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
position: node.translation ?? [0, 0, 0],
|
|
129
|
+
rotation: node.rotation ?? [0, 0, 0, 1],
|
|
130
|
+
scale: node.scale ?? [1, 1, 1]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 2. Instantiate Nodes as ECS entities (or GPU Instanced Models)
|
|
135
|
+
for (let nIdx = 0; nIdx < nodeCount; nIdx++) {
|
|
136
|
+
const node = gltf.nodes![nIdx]!;
|
|
137
|
+
const nodeDesc: ModelNodeDesc = {
|
|
138
|
+
index: nIdx,
|
|
139
|
+
name: node.name,
|
|
140
|
+
hasMesh: node.mesh !== undefined,
|
|
141
|
+
hasSkin: node.skin !== undefined,
|
|
142
|
+
hasCamera: node.camera !== undefined,
|
|
143
|
+
hasLight: node.extensions?.['KHR_lights_punctual'] !== undefined,
|
|
144
|
+
extras:
|
|
145
|
+
typeof node.extras === 'object' && node.extras !== null
|
|
146
|
+
? (node.extras as Record<string, unknown>)
|
|
147
|
+
: undefined
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
if (!shouldImportNode(nodeDesc, importOptions)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const normalizedName = importOptions?.renameNode
|
|
155
|
+
? importOptions.renameNode(node.name ?? `Node_${nIdx}`, nodeDesc)
|
|
156
|
+
: (node.name ?? `Node_${nIdx}`);
|
|
157
|
+
|
|
158
|
+
// A) Check for Hardware GPU Instancing (EXT_mesh_gpu_instancing)
|
|
159
|
+
const gpuInstancingExt = node.extensions?.EXT_mesh_gpu_instancing;
|
|
160
|
+
if (gpuInstancingExt && node.mesh !== undefined) {
|
|
161
|
+
const mesh = gltf.meshes?.[node.mesh];
|
|
162
|
+
if (mesh) {
|
|
163
|
+
const transAcc = gpuInstancingExt.attributes['TRANSLATION'];
|
|
164
|
+
const rotAcc = gpuInstancingExt.attributes['ROTATION'];
|
|
165
|
+
const scaleAcc = gpuInstancingExt.attributes['SCALE'];
|
|
166
|
+
|
|
167
|
+
const translations =
|
|
168
|
+
transAcc !== undefined
|
|
169
|
+
? (readAccessor(gltf, buffers, transAcc) as Float32Array)
|
|
170
|
+
: undefined;
|
|
171
|
+
const rotations =
|
|
172
|
+
rotAcc !== undefined ? (readAccessor(gltf, buffers, rotAcc) as Float32Array) : undefined;
|
|
173
|
+
const scales =
|
|
174
|
+
scaleAcc !== undefined
|
|
175
|
+
? (readAccessor(gltf, buffers, scaleAcc) as Float32Array)
|
|
176
|
+
: undefined;
|
|
177
|
+
|
|
178
|
+
const count = translations
|
|
179
|
+
? translations.length / 3
|
|
180
|
+
: rotations
|
|
181
|
+
? rotations.length / 4
|
|
182
|
+
: scales
|
|
183
|
+
? scales.length / 3
|
|
184
|
+
: 0;
|
|
185
|
+
// 32 floats per instance record required by Galfus Core
|
|
186
|
+
const instanceData = new Float32Array(count * 32);
|
|
187
|
+
|
|
188
|
+
const tempMat = mat4.create();
|
|
189
|
+
const tempPos = vec3.create();
|
|
190
|
+
const tempRot = quat.create();
|
|
191
|
+
const tempScale = vec3.create();
|
|
192
|
+
|
|
193
|
+
for (let i = 0; i < count; i++) {
|
|
194
|
+
tempPos[0] = translations ? translations[i * 3 + 0]! : 0;
|
|
195
|
+
tempPos[1] = translations ? translations[i * 3 + 1]! : 0;
|
|
196
|
+
tempPos[2] = translations ? translations[i * 3 + 2]! : 0;
|
|
197
|
+
|
|
198
|
+
if (rotations) {
|
|
199
|
+
tempRot[0] = rotations[i * 4 + 0]!;
|
|
200
|
+
tempRot[1] = rotations[i * 4 + 1]!;
|
|
201
|
+
tempRot[2] = rotations[i * 4 + 2]!;
|
|
202
|
+
tempRot[3] = rotations[i * 4 + 3]!;
|
|
203
|
+
} else {
|
|
204
|
+
quat.identity(tempRot);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (scales) {
|
|
208
|
+
tempScale[0] = scales[i * 3 + 0]!;
|
|
209
|
+
tempScale[1] = scales[i * 3 + 1]!;
|
|
210
|
+
tempScale[2] = scales[i * 3 + 2]!;
|
|
211
|
+
} else {
|
|
212
|
+
vec3.set(tempScale, 1, 1, 1);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
mat4.fromRotationTranslationScale(tempMat, tempRot, tempPos, tempScale);
|
|
216
|
+
|
|
217
|
+
const offset = i * 32;
|
|
218
|
+
for (let m = 0; m < 16; m++) {
|
|
219
|
+
instanceData[offset + m] = tempMat[m]!;
|
|
220
|
+
}
|
|
221
|
+
// Color (default white)
|
|
222
|
+
instanceData[offset + 16] = 1;
|
|
223
|
+
instanceData[offset + 17] = 1;
|
|
224
|
+
instanceData[offset + 18] = 1;
|
|
225
|
+
instanceData[offset + 19] = 1;
|
|
226
|
+
// Emissive (default black)
|
|
227
|
+
instanceData[offset + 20] = 0;
|
|
228
|
+
instanceData[offset + 21] = 0;
|
|
229
|
+
instanceData[offset + 22] = 0;
|
|
230
|
+
instanceData[offset + 23] = 0;
|
|
231
|
+
// PBR params (metallic, roughness, etc.)
|
|
232
|
+
instanceData[offset + 24] = 1;
|
|
233
|
+
instanceData[offset + 25] = 1;
|
|
234
|
+
instanceData[offset + 26] = 0;
|
|
235
|
+
instanceData[offset + 27] = 0;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for (let primIdx = 0; primIdx < mesh.primitives.length; primIdx++) {
|
|
239
|
+
const prim = mesh.primitives[primIdx]!;
|
|
240
|
+
const key = `${node.mesh}_${primIdx}`;
|
|
241
|
+
const geometryId = resources.geometryMap.get(key);
|
|
242
|
+
const materialId =
|
|
243
|
+
(prim.material !== undefined ? resources.materialMap.get(prim.material) : undefined) ??
|
|
244
|
+
getDefaultMaterial();
|
|
245
|
+
|
|
246
|
+
if (geometryId !== undefined) {
|
|
247
|
+
const instancedEntityId = realm.createInstancedModel({
|
|
248
|
+
geometryId,
|
|
249
|
+
materialId
|
|
250
|
+
});
|
|
251
|
+
realm.setInstancedModelData(instancedEntityId, instanceData);
|
|
252
|
+
instancedModelIds.push(instancedEntityId);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// B) Standard ECS Model Entity
|
|
260
|
+
const trs = getNodeTRS(node);
|
|
261
|
+
const entityId = realm.createSpatialNode(trs);
|
|
262
|
+
entities.push(entityId);
|
|
263
|
+
nodeEntityMap.set(nIdx, entityId);
|
|
264
|
+
|
|
265
|
+
realm.setEntityName(entityId, normalizedName);
|
|
266
|
+
|
|
267
|
+
// Attach metadata extras if present
|
|
268
|
+
if (nodeDesc.extras) {
|
|
269
|
+
for (const [k, v] of Object.entries(nodeDesc.extras)) {
|
|
270
|
+
realm.setMetadataKV(entityId, k, v);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Attach Renderable Mesh if present
|
|
275
|
+
if (node.mesh !== undefined) {
|
|
276
|
+
const mesh = gltf.meshes?.[node.mesh];
|
|
277
|
+
if (mesh) {
|
|
278
|
+
for (let primIdx = 0; primIdx < mesh.primitives.length; primIdx++) {
|
|
279
|
+
const prim = mesh.primitives[primIdx]!;
|
|
280
|
+
const key = `${node.mesh}_${primIdx}`;
|
|
281
|
+
const geometryId = resources.geometryMap.get(key);
|
|
282
|
+
const materialId =
|
|
283
|
+
(prim.material !== undefined ? resources.materialMap.get(prim.material) : undefined) ??
|
|
284
|
+
getDefaultMaterial();
|
|
285
|
+
|
|
286
|
+
if (geometryId !== undefined) {
|
|
287
|
+
let primitiveEntityId = entityId;
|
|
288
|
+
if (primIdx === 0) {
|
|
289
|
+
realm.createMesh(entityId, {
|
|
290
|
+
geometryId,
|
|
291
|
+
materialId
|
|
292
|
+
});
|
|
293
|
+
} else {
|
|
294
|
+
// Sub-primitive: spawn a child spatial node attached to the primary entity
|
|
295
|
+
const subEntityId = realm.createSpatialNode();
|
|
296
|
+
primitiveEntityId = subEntityId;
|
|
297
|
+
realm.setEntityName(subEntityId, `${normalizedName}_prim${primIdx}`);
|
|
298
|
+
realm.createMesh(subEntityId, {
|
|
299
|
+
geometryId,
|
|
300
|
+
materialId
|
|
301
|
+
});
|
|
302
|
+
realm.hierarchy.attach(subEntityId, entityId);
|
|
303
|
+
entities.push(subEntityId);
|
|
304
|
+
}
|
|
305
|
+
const mapped = primitiveEntities.get(nIdx) ?? [];
|
|
306
|
+
mapped.push(primitiveEntityId);
|
|
307
|
+
primitiveEntities.set(nIdx, mapped);
|
|
308
|
+
if (prim.targets?.length) {
|
|
309
|
+
const weights =
|
|
310
|
+
node.weights ?? mesh.weights ?? new Array(prim.targets.length).fill(0);
|
|
311
|
+
const model = realm.world.getComponent<Model3DComponent>(
|
|
312
|
+
primitiveEntityId,
|
|
313
|
+
'Model3D'
|
|
314
|
+
);
|
|
315
|
+
if (model) realm.setModelMorphWeights(model.modelId, new Float32Array(weights));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// 3. Connect Hierarchy Relationships via Anchor HierarchyCoordinator
|
|
324
|
+
for (let nIdx = 0; nIdx < nodeCount; nIdx++) {
|
|
325
|
+
const parentEntityId = nodeEntityMap.get(nIdx);
|
|
326
|
+
if (parentEntityId === undefined) continue;
|
|
327
|
+
|
|
328
|
+
const node = gltf.nodes![nIdx]!;
|
|
329
|
+
if (node.children) {
|
|
330
|
+
for (const childIdx of node.children) {
|
|
331
|
+
const childEntityId = nodeEntityMap.get(childIdx);
|
|
332
|
+
if (childEntityId !== undefined) {
|
|
333
|
+
realm.hierarchy.attach(childEntityId, parentEntityId);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 4. Attach Candidate Roots to Instance Root Container
|
|
340
|
+
for (const rootIdx of candidateRoots) {
|
|
341
|
+
const rootChildEntityId = nodeEntityMap.get(rootIdx);
|
|
342
|
+
if (rootChildEntityId !== undefined) {
|
|
343
|
+
realm.hierarchy.attach(rootChildEntityId, rootEntityId);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
rootEntityId,
|
|
349
|
+
entities,
|
|
350
|
+
nodeEntityMap,
|
|
351
|
+
primitiveEntities,
|
|
352
|
+
instancedModelIds
|
|
353
|
+
};
|
|
354
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* glTF 2.0 canonical schema specifications and extension types.
|
|
3
|
+
*
|
|
4
|
+
* Implements the official Khronos glTF 2.0 standard:
|
|
5
|
+
* https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const GLTF_COMPONENT_TYPE = {
|
|
9
|
+
BYTE: 5120,
|
|
10
|
+
UNSIGNED_BYTE: 5121,
|
|
11
|
+
SHORT: 5122,
|
|
12
|
+
UNSIGNED_SHORT: 5123,
|
|
13
|
+
UNSIGNED_INT: 5125,
|
|
14
|
+
FLOAT: 5126
|
|
15
|
+
} as const;
|
|
16
|
+
|
|
17
|
+
export type GlTFComponentType = (typeof GLTF_COMPONENT_TYPE)[keyof typeof GLTF_COMPONENT_TYPE];
|
|
18
|
+
|
|
19
|
+
export type GlTFAccessorType = 'SCALAR' | 'VEC2' | 'VEC3' | 'VEC4' | 'MAT2' | 'MAT3' | 'MAT4';
|
|
20
|
+
|
|
21
|
+
export interface GlTFAccessor {
|
|
22
|
+
bufferView?: number;
|
|
23
|
+
byteOffset?: number;
|
|
24
|
+
componentType: GlTFComponentType;
|
|
25
|
+
normalized?: boolean;
|
|
26
|
+
count: number;
|
|
27
|
+
type: GlTFAccessorType;
|
|
28
|
+
max?: number[];
|
|
29
|
+
min?: number[];
|
|
30
|
+
sparse?: {
|
|
31
|
+
count: number;
|
|
32
|
+
indices: { bufferView: number; byteOffset?: number; componentType: GlTFComponentType };
|
|
33
|
+
values: { bufferView: number; byteOffset?: number };
|
|
34
|
+
};
|
|
35
|
+
name?: string;
|
|
36
|
+
extensions?: Record<string, unknown>;
|
|
37
|
+
extras?: unknown;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface GlTFBufferView {
|
|
41
|
+
buffer: number;
|
|
42
|
+
byteOffset?: number;
|
|
43
|
+
byteLength: number;
|
|
44
|
+
byteStride?: number;
|
|
45
|
+
target?: number;
|
|
46
|
+
name?: string;
|
|
47
|
+
extensions?: {
|
|
48
|
+
EXT_meshopt_compression?: {
|
|
49
|
+
buffer: number;
|
|
50
|
+
byteOffset?: number;
|
|
51
|
+
byteLength: number;
|
|
52
|
+
byteStride: number;
|
|
53
|
+
count: number;
|
|
54
|
+
mode: 'ATTRIBUTES' | 'TRIANGLES' | 'INDICES';
|
|
55
|
+
filter?: 'NONE' | 'OCTAHEDRAL' | 'QUATERNION' | 'EXPONENTIAL';
|
|
56
|
+
};
|
|
57
|
+
} & Record<string, unknown>;
|
|
58
|
+
extras?: unknown;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface GlTFBuffer {
|
|
62
|
+
uri?: string;
|
|
63
|
+
byteLength: number;
|
|
64
|
+
name?: string;
|
|
65
|
+
extensions?: Record<string, unknown>;
|
|
66
|
+
extras?: unknown;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface GlTFPrimitive {
|
|
70
|
+
attributes: Record<string, number>;
|
|
71
|
+
indices?: number;
|
|
72
|
+
material?: number;
|
|
73
|
+
mode?: number;
|
|
74
|
+
targets?: Array<Record<string, number>>;
|
|
75
|
+
extensions?: {
|
|
76
|
+
KHR_draco_mesh_compression?: {
|
|
77
|
+
bufferView: number;
|
|
78
|
+
attributes: Record<string, number>;
|
|
79
|
+
};
|
|
80
|
+
} & Record<string, unknown>;
|
|
81
|
+
extras?: unknown;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface GlTFMesh {
|
|
85
|
+
primitives: GlTFPrimitive[];
|
|
86
|
+
weights?: number[];
|
|
87
|
+
name?: string;
|
|
88
|
+
extensions?: Record<string, unknown>;
|
|
89
|
+
extras?: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface GlTFTextureInfo {
|
|
93
|
+
index: number;
|
|
94
|
+
texCoord?: number;
|
|
95
|
+
extensions?: Record<string, unknown>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface GlTFMaterialNormalTextureInfo extends GlTFTextureInfo {
|
|
99
|
+
scale?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface GlTFMaterialOcclusionTextureInfo extends GlTFTextureInfo {
|
|
103
|
+
strength?: number;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface GlTFKHRMaterialsPbrSpecularGlossiness {
|
|
107
|
+
diffuseFactor?: [number, number, number, number];
|
|
108
|
+
diffuseTexture?: GlTFTextureInfo;
|
|
109
|
+
specularFactor?: [number, number, number];
|
|
110
|
+
glossinessFactor?: number;
|
|
111
|
+
specularGlossinessTexture?: GlTFTextureInfo;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface GlTFPbrMetallicRoughness {
|
|
115
|
+
baseColorFactor?: [number, number, number, number];
|
|
116
|
+
baseColorTexture?: GlTFTextureInfo;
|
|
117
|
+
metallicFactor?: number;
|
|
118
|
+
roughnessFactor?: number;
|
|
119
|
+
metallicRoughnessTexture?: GlTFTextureInfo;
|
|
120
|
+
extensions?: Record<string, unknown>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface GlTFMaterial {
|
|
124
|
+
name?: string;
|
|
125
|
+
pbrMetallicRoughness?: GlTFPbrMetallicRoughness;
|
|
126
|
+
normalTexture?: GlTFMaterialNormalTextureInfo;
|
|
127
|
+
occlusionTexture?: GlTFMaterialOcclusionTextureInfo;
|
|
128
|
+
emissiveTexture?: GlTFTextureInfo;
|
|
129
|
+
emissiveFactor?: [number, number, number];
|
|
130
|
+
alphaMode?: 'OPAQUE' | 'MASK' | 'BLEND';
|
|
131
|
+
alphaCutoff?: number;
|
|
132
|
+
doubleSided?: boolean;
|
|
133
|
+
extensions?: {
|
|
134
|
+
KHR_materials_pbrSpecularGlossiness?: GlTFKHRMaterialsPbrSpecularGlossiness;
|
|
135
|
+
KHR_materials_ior?: { ior?: number };
|
|
136
|
+
} & Record<string, unknown>;
|
|
137
|
+
extras?: unknown;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export type GltfMaterialAlphaModeResolver = (
|
|
141
|
+
material: GlTFMaterial,
|
|
142
|
+
index: number,
|
|
143
|
+
baseColorImage?: Uint8Array
|
|
144
|
+
) => GlTFMaterial['alphaMode'];
|
|
145
|
+
|
|
146
|
+
export interface GlTFTexture {
|
|
147
|
+
sampler?: number;
|
|
148
|
+
source?: number;
|
|
149
|
+
name?: string;
|
|
150
|
+
extensions?: {
|
|
151
|
+
KHR_texture_basisu?: {
|
|
152
|
+
source: number;
|
|
153
|
+
};
|
|
154
|
+
} & Record<string, unknown>;
|
|
155
|
+
extras?: unknown;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface GlTFImage {
|
|
159
|
+
uri?: string;
|
|
160
|
+
mimeType?: string;
|
|
161
|
+
bufferView?: number;
|
|
162
|
+
name?: string;
|
|
163
|
+
extensions?: Record<string, unknown>;
|
|
164
|
+
extras?: unknown;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface GlTFNode {
|
|
168
|
+
camera?: number;
|
|
169
|
+
children?: number[];
|
|
170
|
+
skin?: number;
|
|
171
|
+
matrix?: [
|
|
172
|
+
number,
|
|
173
|
+
number,
|
|
174
|
+
number,
|
|
175
|
+
number,
|
|
176
|
+
number,
|
|
177
|
+
number,
|
|
178
|
+
number,
|
|
179
|
+
number,
|
|
180
|
+
number,
|
|
181
|
+
number,
|
|
182
|
+
number,
|
|
183
|
+
number,
|
|
184
|
+
number,
|
|
185
|
+
number,
|
|
186
|
+
number,
|
|
187
|
+
number
|
|
188
|
+
];
|
|
189
|
+
mesh?: number;
|
|
190
|
+
rotation?: [number, number, number, number];
|
|
191
|
+
scale?: [number, number, number];
|
|
192
|
+
translation?: [number, number, number];
|
|
193
|
+
weights?: number[];
|
|
194
|
+
name?: string;
|
|
195
|
+
extensions?: {
|
|
196
|
+
EXT_mesh_gpu_instancing?: {
|
|
197
|
+
attributes: Record<string, number>;
|
|
198
|
+
};
|
|
199
|
+
} & Record<string, unknown>;
|
|
200
|
+
extras?: unknown;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface GlTFSkin {
|
|
204
|
+
inverseBindMatrices?: number;
|
|
205
|
+
skeleton?: number;
|
|
206
|
+
joints: number[];
|
|
207
|
+
name?: string;
|
|
208
|
+
extensions?: Record<string, unknown>;
|
|
209
|
+
extras?: unknown;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface GlTFAnimationChannel {
|
|
213
|
+
sampler: number;
|
|
214
|
+
target: {
|
|
215
|
+
node?: number;
|
|
216
|
+
path: 'translation' | 'rotation' | 'scale' | 'weights';
|
|
217
|
+
extensions?: Record<string, unknown>;
|
|
218
|
+
};
|
|
219
|
+
extensions?: Record<string, unknown>;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export interface GlTFAnimationSampler {
|
|
223
|
+
input: number;
|
|
224
|
+
interpolation?: 'LINEAR' | 'STEP' | 'CUBICSPLINE';
|
|
225
|
+
output: number;
|
|
226
|
+
extensions?: Record<string, unknown>;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export interface GlTFAnimation {
|
|
230
|
+
name?: string;
|
|
231
|
+
channels: GlTFAnimationChannel[];
|
|
232
|
+
samplers: GlTFAnimationSampler[];
|
|
233
|
+
extensions?: Record<string, unknown>;
|
|
234
|
+
extras?: unknown;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export interface GlTFScene {
|
|
238
|
+
nodes?: number[];
|
|
239
|
+
name?: string;
|
|
240
|
+
extensions?: Record<string, unknown>;
|
|
241
|
+
extras?: unknown;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface GlTFAsset {
|
|
245
|
+
version: string;
|
|
246
|
+
generator?: string;
|
|
247
|
+
minVersion?: string;
|
|
248
|
+
extensions?: Record<string, unknown>;
|
|
249
|
+
extras?: unknown;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface GlTF {
|
|
253
|
+
asset: GlTFAsset;
|
|
254
|
+
scene?: number;
|
|
255
|
+
scenes?: GlTFScene[];
|
|
256
|
+
nodes?: GlTFNode[];
|
|
257
|
+
meshes?: GlTFMesh[];
|
|
258
|
+
materials?: GlTFMaterial[];
|
|
259
|
+
textures?: GlTFTexture[];
|
|
260
|
+
images?: GlTFImage[];
|
|
261
|
+
accessors?: GlTFAccessor[];
|
|
262
|
+
bufferViews?: GlTFBufferView[];
|
|
263
|
+
buffers?: GlTFBuffer[];
|
|
264
|
+
skins?: GlTFSkin[];
|
|
265
|
+
animations?: GlTFAnimation[];
|
|
266
|
+
extensionsUsed?: string[];
|
|
267
|
+
extensionsRequired?: string[];
|
|
268
|
+
extensions?: Record<string, unknown>;
|
|
269
|
+
extras?: unknown;
|
|
270
|
+
}
|