@luma.gl/gltf 9.2.6 → 9.3.0-alpha.11
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/dist.dev.js +4064 -1967
- package/dist/dist.min.js +117 -46
- package/dist/gltf/animations/animations.d.ts +57 -5
- package/dist/gltf/animations/animations.d.ts.map +1 -1
- package/dist/gltf/animations/interpolate.d.ts +6 -3
- package/dist/gltf/animations/interpolate.d.ts.map +1 -1
- package/dist/gltf/animations/interpolate.js +47 -51
- package/dist/gltf/animations/interpolate.js.map +1 -1
- package/dist/gltf/create-gltf-model.d.ts +15 -1
- package/dist/gltf/create-gltf-model.d.ts.map +1 -1
- package/dist/gltf/create-gltf-model.js +168 -43
- package/dist/gltf/create-gltf-model.js.map +1 -1
- package/dist/gltf/create-scenegraph-from-gltf.d.ts +39 -2
- package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
- package/dist/gltf/create-scenegraph-from-gltf.js +76 -6
- package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
- package/dist/gltf/gltf-animator.d.ts +37 -0
- package/dist/gltf/gltf-animator.d.ts.map +1 -1
- package/dist/gltf/gltf-animator.js +112 -17
- package/dist/gltf/gltf-animator.js.map +1 -1
- package/dist/gltf/gltf-extension-support.d.ts +13 -0
- package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
- package/dist/gltf/gltf-extension-support.js +178 -0
- package/dist/gltf/gltf-extension-support.js.map +1 -0
- package/dist/index.cjs +1806 -298
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/parsers/parse-gltf-animations.d.ts +1 -0
- package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
- package/dist/parsers/parse-gltf-animations.js +373 -27
- package/dist/parsers/parse-gltf-animations.js.map +1 -1
- package/dist/parsers/parse-gltf-lights.d.ts +5 -0
- package/dist/parsers/parse-gltf-lights.d.ts.map +1 -0
- package/dist/parsers/parse-gltf-lights.js +163 -0
- package/dist/parsers/parse-gltf-lights.js.map +1 -0
- package/dist/parsers/parse-gltf.d.ts +19 -2
- package/dist/parsers/parse-gltf.d.ts.map +1 -1
- package/dist/parsers/parse-gltf.js +120 -67
- package/dist/parsers/parse-gltf.js.map +1 -1
- package/dist/parsers/parse-pbr-material.d.ts +115 -2
- package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
- package/dist/parsers/parse-pbr-material.js +602 -53
- package/dist/parsers/parse-pbr-material.js.map +1 -1
- package/dist/pbr/pbr-environment.d.ts +10 -4
- package/dist/pbr/pbr-environment.d.ts.map +1 -1
- package/dist/pbr/pbr-environment.js +18 -15
- package/dist/pbr/pbr-environment.js.map +1 -1
- package/dist/pbr/pbr-material.d.ts +13 -3
- package/dist/pbr/pbr-material.d.ts.map +1 -1
- package/dist/pbr/texture-transform.d.ts +24 -0
- package/dist/pbr/texture-transform.d.ts.map +1 -0
- package/dist/pbr/texture-transform.js +98 -0
- package/dist/pbr/texture-transform.js.map +1 -0
- package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
- package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
- package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
- package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
- package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
- package/dist/webgl-to-webgpu/convert-webgl-topology.js +3 -15
- package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
- package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
- package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
- package/package.json +8 -9
- package/src/gltf/animations/animations.ts +88 -6
- package/src/gltf/animations/interpolate.ts +84 -96
- package/src/gltf/create-gltf-model.ts +233 -48
- package/src/gltf/create-scenegraph-from-gltf.ts +134 -11
- package/src/gltf/gltf-animator.ts +198 -20
- package/src/gltf/gltf-extension-support.ts +226 -0
- package/src/index.ts +11 -2
- package/src/parsers/parse-gltf-animations.ts +533 -32
- package/src/parsers/parse-gltf-lights.ts +218 -0
- package/src/parsers/parse-gltf.ts +189 -96
- package/src/parsers/parse-pbr-material.ts +974 -79
- package/src/pbr/pbr-environment.ts +44 -21
- package/src/pbr/pbr-material.ts +18 -3
- package/src/pbr/texture-transform.ts +263 -0
- package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
- package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
- package/src/webgl-to-webgpu/convert-webgl-topology.ts +3 -15
- package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
- package/dist/utils/deep-copy.d.ts +0 -3
- package/dist/utils/deep-copy.d.ts.map +0 -1
- package/dist/utils/deep-copy.js +0 -21
- package/dist/utils/deep-copy.js.map +0 -1
- package/src/utils/deep-copy.ts +0 -22
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {Matrix4} from '@math.gl/core';
|
|
2
|
+
import type {GLTFNodePostprocessed, GLTFPostprocessed} from '@loaders.gl/gltf';
|
|
3
|
+
import type {DirectionalLight, Light, PointLight, SpotLight} from '@luma.gl/shadertools';
|
|
4
|
+
|
|
5
|
+
const GLTF_COLOR_FACTOR = 255;
|
|
6
|
+
|
|
7
|
+
/** Parse KHR_lights_punctual extension into luma.gl light definitions */
|
|
8
|
+
export function parseGLTFLights(gltf: GLTFPostprocessed): Light[] {
|
|
9
|
+
const lightDefs =
|
|
10
|
+
// `postProcessGLTF()` moves KHR_lights_punctual into `gltf.lights`.
|
|
11
|
+
(gltf as GLTFPostprocessed & {lights?: any[]}).lights ||
|
|
12
|
+
gltf.extensions?.['KHR_lights_punctual']?.['lights'];
|
|
13
|
+
if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const lights: Light[] = [];
|
|
18
|
+
const parentNodeById = createParentNodeMap(gltf.nodes || []);
|
|
19
|
+
const worldMatrixByNodeId = new Map<string, Matrix4>();
|
|
20
|
+
|
|
21
|
+
for (const node of gltf.nodes || []) {
|
|
22
|
+
const lightIndex =
|
|
23
|
+
(node as GLTFNodePostprocessed & {light?: number}).light ??
|
|
24
|
+
node.extensions?.KHR_lights_punctual?.light;
|
|
25
|
+
if (typeof lightIndex !== 'number') {
|
|
26
|
+
// eslint-disable-next-line no-continue
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const gltfLight = lightDefs[lightIndex];
|
|
30
|
+
if (!gltfLight) {
|
|
31
|
+
// eslint-disable-next-line no-continue
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const color = normalizeGLTFLightColor(
|
|
36
|
+
(gltfLight.color || [1, 1, 1]) as [number, number, number]
|
|
37
|
+
);
|
|
38
|
+
const intensity = gltfLight.intensity ?? 1;
|
|
39
|
+
const range = gltfLight.range;
|
|
40
|
+
const worldMatrix = getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId);
|
|
41
|
+
|
|
42
|
+
switch (gltfLight.type) {
|
|
43
|
+
case 'directional':
|
|
44
|
+
lights.push(parseDirectionalLight(worldMatrix, color, intensity));
|
|
45
|
+
break;
|
|
46
|
+
case 'point':
|
|
47
|
+
lights.push(parsePointLight(worldMatrix, color, intensity, range));
|
|
48
|
+
break;
|
|
49
|
+
case 'spot':
|
|
50
|
+
lights.push(parseSpotLight(worldMatrix, color, intensity, range, gltfLight.spot));
|
|
51
|
+
break;
|
|
52
|
+
default:
|
|
53
|
+
// Unsupported light type
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return lights;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Converts glTF colors from the 0-1 spec range to luma.gl's 0-255 light convention.
|
|
63
|
+
*/
|
|
64
|
+
function normalizeGLTFLightColor(color: [number, number, number]): [number, number, number] {
|
|
65
|
+
return color.map(component => component * GLTF_COLOR_FACTOR) as [number, number, number];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Converts a glTF punctual light attached to a node into a point light.
|
|
70
|
+
*/
|
|
71
|
+
function parsePointLight(
|
|
72
|
+
worldMatrix: Matrix4,
|
|
73
|
+
color: [number, number, number],
|
|
74
|
+
intensity: number,
|
|
75
|
+
range?: number
|
|
76
|
+
): PointLight {
|
|
77
|
+
const position = getLightPosition(worldMatrix);
|
|
78
|
+
|
|
79
|
+
let attenuation: Readonly<[number, number, number]> = [1, 0, 0];
|
|
80
|
+
if (range !== undefined && range > 0) {
|
|
81
|
+
attenuation = [1, 0, 1 / (range * range)] as [number, number, number];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
type: 'point',
|
|
86
|
+
position,
|
|
87
|
+
color,
|
|
88
|
+
intensity,
|
|
89
|
+
attenuation
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Converts a glTF punctual light attached to a node into a directional light.
|
|
95
|
+
*/
|
|
96
|
+
function parseDirectionalLight(
|
|
97
|
+
worldMatrix: Matrix4,
|
|
98
|
+
color: [number, number, number],
|
|
99
|
+
intensity: number
|
|
100
|
+
): DirectionalLight {
|
|
101
|
+
const direction = getLightDirection(worldMatrix);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
type: 'directional',
|
|
105
|
+
direction,
|
|
106
|
+
color,
|
|
107
|
+
intensity
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Converts a glTF punctual light attached to a node into a spot light.
|
|
113
|
+
*/
|
|
114
|
+
function parseSpotLight(
|
|
115
|
+
worldMatrix: Matrix4,
|
|
116
|
+
color: [number, number, number],
|
|
117
|
+
intensity: number,
|
|
118
|
+
range?: number,
|
|
119
|
+
spot: {innerConeAngle?: number; outerConeAngle?: number} = {}
|
|
120
|
+
): SpotLight {
|
|
121
|
+
const position = getLightPosition(worldMatrix);
|
|
122
|
+
const direction = getLightDirection(worldMatrix);
|
|
123
|
+
|
|
124
|
+
let attenuation: Readonly<[number, number, number]> = [1, 0, 0];
|
|
125
|
+
if (range !== undefined && range > 0) {
|
|
126
|
+
attenuation = [1, 0, 1 / (range * range)] as [number, number, number];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
type: 'spot',
|
|
131
|
+
position,
|
|
132
|
+
direction,
|
|
133
|
+
color,
|
|
134
|
+
intensity,
|
|
135
|
+
attenuation,
|
|
136
|
+
innerConeAngle: spot.innerConeAngle ?? 0,
|
|
137
|
+
outerConeAngle: spot.outerConeAngle ?? Math.PI / 4
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Builds a parent lookup so punctual lights can be resolved in world space.
|
|
143
|
+
*/
|
|
144
|
+
function createParentNodeMap(nodes: GLTFNodePostprocessed[]): Map<string, GLTFNodePostprocessed> {
|
|
145
|
+
const parentNodeById = new Map<string, GLTFNodePostprocessed>();
|
|
146
|
+
|
|
147
|
+
for (const node of nodes) {
|
|
148
|
+
for (const childNode of node.children || []) {
|
|
149
|
+
parentNodeById.set(childNode.id, node);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return parentNodeById;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Resolves a glTF node's world matrix from its local transform and parent chain.
|
|
158
|
+
*/
|
|
159
|
+
function getNodeWorldMatrix(
|
|
160
|
+
node: GLTFNodePostprocessed,
|
|
161
|
+
parentNodeById: Map<string, GLTFNodePostprocessed>,
|
|
162
|
+
worldMatrixByNodeId: Map<string, Matrix4>
|
|
163
|
+
): Matrix4 {
|
|
164
|
+
const cachedWorldMatrix = worldMatrixByNodeId.get(node.id);
|
|
165
|
+
if (cachedWorldMatrix) {
|
|
166
|
+
return cachedWorldMatrix;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const localMatrix = getNodeLocalMatrix(node);
|
|
170
|
+
const parentNode = parentNodeById.get(node.id);
|
|
171
|
+
const worldMatrix = parentNode
|
|
172
|
+
? new Matrix4(
|
|
173
|
+
getNodeWorldMatrix(parentNode, parentNodeById, worldMatrixByNodeId)
|
|
174
|
+
).multiplyRight(localMatrix)
|
|
175
|
+
: localMatrix;
|
|
176
|
+
|
|
177
|
+
worldMatrixByNodeId.set(node.id, worldMatrix);
|
|
178
|
+
return worldMatrix;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolves a glTF node's local matrix from its matrix or TRS components.
|
|
183
|
+
*/
|
|
184
|
+
function getNodeLocalMatrix(node: GLTFNodePostprocessed): Matrix4 {
|
|
185
|
+
if (node.matrix) {
|
|
186
|
+
return new Matrix4(node.matrix);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const matrix = new Matrix4();
|
|
190
|
+
|
|
191
|
+
if (node.translation) {
|
|
192
|
+
matrix.translate(node.translation);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (node.rotation) {
|
|
196
|
+
matrix.multiplyRight(new Matrix4().fromQuaternion(node.rotation));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (node.scale) {
|
|
200
|
+
matrix.scale(node.scale);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return matrix;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Resolves the world-space position of a glTF light node.
|
|
208
|
+
*/
|
|
209
|
+
function getLightPosition(worldMatrix: Matrix4): [number, number, number] {
|
|
210
|
+
return worldMatrix.transformAsPoint([0, 0, 0]) as [number, number, number];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Resolves the world-space forward direction of a glTF light node.
|
|
215
|
+
*/
|
|
216
|
+
function getLightDirection(worldMatrix: Matrix4): [number, number, number] {
|
|
217
|
+
return worldMatrix.transformDirection([0, 0, -1]) as [number, number, number];
|
|
218
|
+
}
|
|
@@ -3,26 +3,40 @@
|
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import {Device, type PrimitiveTopology} from '@luma.gl/core';
|
|
6
|
-
import {Geometry, GeometryAttribute, GroupNode, ModelNode, type ModelProps} from '@luma.gl/engine';
|
|
7
|
-
import {Matrix4} from '@math.gl/core';
|
|
8
6
|
import {
|
|
7
|
+
Geometry,
|
|
8
|
+
GeometryAttribute,
|
|
9
|
+
GroupNode,
|
|
10
|
+
Material,
|
|
11
|
+
MaterialFactory,
|
|
12
|
+
ModelNode,
|
|
13
|
+
type ModelProps
|
|
14
|
+
} from '@luma.gl/engine';
|
|
15
|
+
import {
|
|
16
|
+
type GLTFMaterialPostprocessed,
|
|
9
17
|
type GLTFMeshPostprocessed,
|
|
10
18
|
type GLTFNodePostprocessed,
|
|
11
19
|
type GLTFPostprocessed
|
|
12
20
|
} from '@loaders.gl/gltf';
|
|
13
|
-
import {
|
|
21
|
+
import {pbrMaterial} from '@luma.gl/shadertools';
|
|
14
22
|
|
|
15
23
|
import {type PBREnvironment} from '../pbr/pbr-environment';
|
|
16
24
|
import {convertGLDrawModeToTopology} from '../webgl-to-webgpu/convert-webgl-topology';
|
|
17
|
-
import {createGLTFModel} from '../gltf/create-gltf-model';
|
|
25
|
+
import {createGLTFMaterial, createGLTFModel} from '../gltf/create-gltf-model';
|
|
18
26
|
|
|
19
27
|
import {parsePBRMaterial} from './parse-pbr-material';
|
|
20
28
|
|
|
29
|
+
/** Options that influence how a post-processed glTF is turned into a luma.gl scenegraph. */
|
|
21
30
|
export type ParseGLTFOptions = {
|
|
31
|
+
/** Additional model props applied to each generated primitive model. */
|
|
22
32
|
modelOptions?: Partial<ModelProps>;
|
|
33
|
+
/** Enables shader-level PBR debug output. */
|
|
23
34
|
pbrDebug?: boolean;
|
|
35
|
+
/** Optional image-based lighting environment. */
|
|
24
36
|
imageBasedLightingEnvironment?: PBREnvironment;
|
|
37
|
+
/** Enables punctual light extraction. */
|
|
25
38
|
lights?: boolean;
|
|
39
|
+
/** Enables tangent usage when available. */
|
|
26
40
|
useTangents?: boolean;
|
|
27
41
|
};
|
|
28
42
|
|
|
@@ -41,124 +55,184 @@ const defaultOptions: Required<ParseGLTFOptions> = {
|
|
|
41
55
|
export function parseGLTF(
|
|
42
56
|
device: Device,
|
|
43
57
|
gltf: GLTFPostprocessed,
|
|
44
|
-
|
|
45
|
-
):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
options: ParseGLTFOptions = {}
|
|
59
|
+
): {
|
|
60
|
+
/** Scene roots generated from `gltf.scenes`. */
|
|
61
|
+
scenes: GroupNode[];
|
|
62
|
+
/** Materials aligned with the source `gltf.materials` array. */
|
|
63
|
+
materials: Material[];
|
|
64
|
+
/** Map from glTF mesh ids to generated mesh group nodes. */
|
|
65
|
+
gltfMeshIdToNodeMap: Map<string, GroupNode>;
|
|
66
|
+
/** Map from glTF node indices to generated scenegraph nodes. */
|
|
67
|
+
gltfNodeIndexToNodeMap: Map<number, GroupNode>;
|
|
68
|
+
/** Map from glTF node ids to generated scenegraph nodes. */
|
|
69
|
+
gltfNodeIdToNodeMap: Map<string, GroupNode>;
|
|
70
|
+
} {
|
|
71
|
+
const combinedOptions = {...defaultOptions, ...options};
|
|
72
|
+
const materialFactory = new MaterialFactory(device, {modules: [pbrMaterial]});
|
|
73
|
+
const materials = (gltf.materials || []).map((gltfMaterial, materialIndex) =>
|
|
74
|
+
createGLTFMaterial(device, {
|
|
75
|
+
id: getGLTFMaterialId(gltfMaterial, materialIndex),
|
|
76
|
+
parsedPPBRMaterial: parsePBRMaterial(
|
|
77
|
+
device,
|
|
78
|
+
gltfMaterial as any,
|
|
79
|
+
{},
|
|
80
|
+
{
|
|
81
|
+
...combinedOptions,
|
|
82
|
+
gltf,
|
|
83
|
+
validateAttributes: false
|
|
84
|
+
}
|
|
85
|
+
),
|
|
86
|
+
materialFactory
|
|
87
|
+
})
|
|
49
88
|
);
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
const gltfMaterialIdToMaterialMap = new Map<string, Material>();
|
|
90
|
+
(gltf.materials || []).forEach((gltfMaterial, materialIndex) => {
|
|
91
|
+
gltfMaterialIdToMaterialMap.set(gltfMaterial.id, materials[materialIndex]);
|
|
92
|
+
});
|
|
52
93
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
children: nodes
|
|
94
|
+
const gltfMeshIdToNodeMap = new Map<string, GroupNode>();
|
|
95
|
+
gltf.meshes.forEach((gltfMesh, idx) => {
|
|
96
|
+
const newMesh = createNodeForGLTFMesh(
|
|
97
|
+
device,
|
|
98
|
+
gltfMesh,
|
|
99
|
+
gltf,
|
|
100
|
+
gltfMaterialIdToMaterialMap,
|
|
101
|
+
combinedOptions
|
|
102
|
+
);
|
|
103
|
+
gltfMeshIdToNodeMap.set(gltfMesh.id, newMesh);
|
|
64
104
|
});
|
|
65
|
-
return sceneNode;
|
|
66
|
-
}
|
|
67
105
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const children = gltfChildren.map(child => createNode(device, child, gltfNodes, options));
|
|
106
|
+
const gltfNodeIndexToNodeMap = new Map<number, GroupNode>();
|
|
107
|
+
const gltfNodeIdToNodeMap = new Map<string, GroupNode>();
|
|
108
|
+
// Step 1/2: Generate a GroupNode for each gltf node. (1:1 mapping).
|
|
109
|
+
gltf.nodes.forEach((gltfNode, idx) => {
|
|
110
|
+
const newNode = createNodeForGLTFNode(device, gltfNode, combinedOptions);
|
|
111
|
+
gltfNodeIndexToNodeMap.set(idx, newNode);
|
|
112
|
+
gltfNodeIdToNodeMap.set(gltfNode.id, newNode);
|
|
113
|
+
});
|
|
77
114
|
|
|
78
|
-
|
|
115
|
+
// Step 2/2: Go though each gltf node and attach the children.
|
|
116
|
+
// This guarantees that each gltf node will have exactly one luma GroupNode.
|
|
117
|
+
gltf.nodes.forEach((gltfNode, idx) => {
|
|
118
|
+
gltfNodeIndexToNodeMap.get(idx)!.add(
|
|
119
|
+
(gltfNode.children ?? []).map(({id}) => {
|
|
120
|
+
const child = gltfNodeIdToNodeMap.get(id);
|
|
121
|
+
if (!child) throw new Error(`Cannot find child ${id} of node ${idx}`);
|
|
122
|
+
return child;
|
|
123
|
+
})
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// Nodes can have children nodes and one optional child mesh at the same time.
|
|
79
127
|
if (gltfNode.mesh) {
|
|
80
|
-
|
|
128
|
+
const mesh = gltfMeshIdToNodeMap.get(gltfNode.mesh.id);
|
|
129
|
+
if (!mesh) {
|
|
130
|
+
throw new Error(`Cannot find mesh child ${gltfNode.mesh.id} of node ${idx}`);
|
|
131
|
+
}
|
|
132
|
+
gltfNodeIndexToNodeMap.get(idx)!.add(mesh);
|
|
81
133
|
}
|
|
134
|
+
});
|
|
82
135
|
|
|
83
|
-
|
|
84
|
-
|
|
136
|
+
const scenes = gltf.scenes.map(gltfScene => {
|
|
137
|
+
const children = (gltfScene.nodes || []).map(({id}) => {
|
|
138
|
+
const child = gltfNodeIdToNodeMap.get(id);
|
|
139
|
+
if (!child)
|
|
140
|
+
throw new Error(`Cannot find child ${id} of scene ${gltfScene.name || gltfScene.id}`);
|
|
141
|
+
return child;
|
|
142
|
+
});
|
|
143
|
+
return new GroupNode({
|
|
144
|
+
id: gltfScene.name || gltfScene.id,
|
|
85
145
|
children
|
|
86
146
|
});
|
|
147
|
+
});
|
|
87
148
|
|
|
88
|
-
|
|
89
|
-
node.setMatrix(gltfNode.matrix);
|
|
90
|
-
} else {
|
|
91
|
-
node.matrix.identity();
|
|
92
|
-
|
|
93
|
-
if (gltfNode.translation) {
|
|
94
|
-
node.matrix.translate(gltfNode.translation);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (gltfNode.rotation) {
|
|
98
|
-
const rotationMatrix = new Matrix4().fromQuaternion(gltfNode.rotation);
|
|
99
|
-
node.matrix.multiplyRight(rotationMatrix);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (gltfNode.scale) {
|
|
103
|
-
node.matrix.scale(gltfNode.scale);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
gltfNode._node = node;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Copy _node so that gltf-animator can access
|
|
110
|
-
const topLevelNode = gltfNodes.find(node => node.id === gltfNode.id) as any;
|
|
111
|
-
topLevelNode._node = gltfNode._node;
|
|
112
|
-
|
|
113
|
-
return gltfNode._node;
|
|
149
|
+
return {scenes, materials, gltfMeshIdToNodeMap, gltfNodeIdToNodeMap, gltfNodeIndexToNodeMap};
|
|
114
150
|
}
|
|
115
151
|
|
|
116
|
-
|
|
152
|
+
/** Creates a `GroupNode` for one glTF node transform. */
|
|
153
|
+
function createNodeForGLTFNode(
|
|
117
154
|
device: Device,
|
|
118
|
-
|
|
155
|
+
gltfNode: GLTFNodePostprocessed,
|
|
119
156
|
options: Required<ParseGLTFOptions>
|
|
120
157
|
): GroupNode {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
children: primitives
|
|
130
|
-
});
|
|
131
|
-
gltfMesh._mesh = mesh;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return gltfMesh._mesh;
|
|
158
|
+
return new GroupNode({
|
|
159
|
+
id: gltfNode.name || gltfNode.id,
|
|
160
|
+
children: [],
|
|
161
|
+
matrix: gltfNode.matrix,
|
|
162
|
+
position: gltfNode.translation,
|
|
163
|
+
rotation: gltfNode.rotation,
|
|
164
|
+
scale: gltfNode.scale
|
|
165
|
+
});
|
|
135
166
|
}
|
|
136
167
|
|
|
137
|
-
|
|
168
|
+
/** Creates a mesh group node containing one model node per glTF primitive. */
|
|
169
|
+
function createNodeForGLTFMesh(
|
|
138
170
|
device: Device,
|
|
139
|
-
gltfPrimitive: any,
|
|
140
|
-
i: number,
|
|
141
171
|
gltfMesh: GLTFMeshPostprocessed,
|
|
172
|
+
gltf: GLTFPostprocessed,
|
|
173
|
+
gltfMaterialIdToMaterialMap: Map<string, Material>,
|
|
142
174
|
options: Required<ParseGLTFOptions>
|
|
143
|
-
):
|
|
144
|
-
const
|
|
145
|
-
const
|
|
175
|
+
): GroupNode {
|
|
176
|
+
const gltfPrimitives = gltfMesh.primitives || [];
|
|
177
|
+
const primitives = gltfPrimitives.map((gltfPrimitive, i) =>
|
|
178
|
+
createNodeForGLTFPrimitive({
|
|
179
|
+
device,
|
|
180
|
+
gltfPrimitive,
|
|
181
|
+
primitiveIndex: i,
|
|
182
|
+
gltfMesh,
|
|
183
|
+
gltf,
|
|
184
|
+
gltfMaterialIdToMaterialMap,
|
|
185
|
+
options
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
const mesh = new GroupNode({
|
|
189
|
+
id: gltfMesh.name || gltfMesh.id,
|
|
190
|
+
children: primitives
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return mesh;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Input options for creating one renderable glTF primitive model node. */
|
|
197
|
+
type CreateNodeForGLTFPrimitiveOptions = {
|
|
198
|
+
device: Device;
|
|
199
|
+
gltfPrimitive: any;
|
|
200
|
+
primitiveIndex: number;
|
|
201
|
+
gltfMesh: GLTFMeshPostprocessed;
|
|
202
|
+
gltf: GLTFPostprocessed;
|
|
203
|
+
gltfMaterialIdToMaterialMap: Map<string, Material>;
|
|
204
|
+
options: Required<ParseGLTFOptions>;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/** Creates a renderable model node for one glTF primitive. */
|
|
208
|
+
function createNodeForGLTFPrimitive({
|
|
209
|
+
device,
|
|
210
|
+
gltfPrimitive,
|
|
211
|
+
primitiveIndex,
|
|
212
|
+
gltfMesh,
|
|
213
|
+
gltf,
|
|
214
|
+
gltfMaterialIdToMaterialMap,
|
|
215
|
+
options
|
|
216
|
+
}: CreateNodeForGLTFPrimitiveOptions): ModelNode {
|
|
217
|
+
const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${primitiveIndex}`;
|
|
218
|
+
const topology = convertGLDrawModeToTopology(gltfPrimitive.mode ?? 4);
|
|
146
219
|
const vertexCount = gltfPrimitive.indices
|
|
147
220
|
? gltfPrimitive.indices.count
|
|
148
221
|
: getVertexCount(gltfPrimitive.attributes);
|
|
149
222
|
|
|
150
223
|
const geometry = createGeometry(id, gltfPrimitive, topology);
|
|
151
224
|
|
|
152
|
-
const parsedPPBRMaterial = parsePBRMaterial(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
options
|
|
157
|
-
);
|
|
225
|
+
const parsedPPBRMaterial = parsePBRMaterial(device, gltfPrimitive.material, geometry.attributes, {
|
|
226
|
+
...options,
|
|
227
|
+
gltf
|
|
228
|
+
});
|
|
158
229
|
|
|
159
230
|
const modelNode = createGLTFModel(device, {
|
|
160
231
|
id,
|
|
161
|
-
geometry
|
|
232
|
+
geometry,
|
|
233
|
+
material: gltfPrimitive.material
|
|
234
|
+
? gltfMaterialIdToMaterialMap.get(gltfPrimitive.material.id) || null
|
|
235
|
+
: null,
|
|
162
236
|
parsedPPBRMaterial,
|
|
163
237
|
modelOptions: options.modelOptions,
|
|
164
238
|
vertexCount
|
|
@@ -171,22 +245,41 @@ function createPrimitive(
|
|
|
171
245
|
return modelNode;
|
|
172
246
|
}
|
|
173
247
|
|
|
248
|
+
/** Computes the vertex count for a primitive without indices. */
|
|
174
249
|
function getVertexCount(attributes: any) {
|
|
175
|
-
|
|
250
|
+
let vertexCount = Infinity;
|
|
251
|
+
for (const attribute of Object.values(attributes)) {
|
|
252
|
+
if (attribute) {
|
|
253
|
+
const {value, size, components} = attribute as any;
|
|
254
|
+
const attributeSize = size ?? components;
|
|
255
|
+
if (value?.length !== undefined && attributeSize >= 1) {
|
|
256
|
+
vertexCount = Math.min(vertexCount, value.length / attributeSize);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (!Number.isFinite(vertexCount)) {
|
|
261
|
+
throw new Error('Could not determine vertex count from attributes');
|
|
262
|
+
}
|
|
263
|
+
return vertexCount;
|
|
176
264
|
}
|
|
177
265
|
|
|
266
|
+
/** Converts glTF primitive attributes and indices into a luma.gl `Geometry`. */
|
|
178
267
|
function createGeometry(id: string, gltfPrimitive: any, topology: PrimitiveTopology): Geometry {
|
|
179
268
|
const attributes: Record<string, GeometryAttribute> = {};
|
|
180
269
|
for (const [attributeName, attribute] of Object.entries(gltfPrimitive.attributes)) {
|
|
181
|
-
const {components, size, value} = attribute as GeometryAttribute;
|
|
270
|
+
const {components, size, value, normalized} = attribute as GeometryAttribute;
|
|
182
271
|
|
|
183
|
-
attributes[attributeName] = {size: size ?? components, value};
|
|
272
|
+
attributes[attributeName] = {size: size ?? components, value, normalized};
|
|
184
273
|
}
|
|
185
274
|
|
|
186
275
|
return new Geometry({
|
|
187
276
|
id,
|
|
188
277
|
topology,
|
|
189
|
-
indices: gltfPrimitive.indices
|
|
278
|
+
indices: gltfPrimitive.indices?.value,
|
|
190
279
|
attributes
|
|
191
280
|
});
|
|
192
281
|
}
|
|
282
|
+
|
|
283
|
+
function getGLTFMaterialId(gltfMaterial: GLTFMaterialPostprocessed, materialIndex: number): string {
|
|
284
|
+
return gltfMaterial.name || gltfMaterial.id || `material-${materialIndex}`;
|
|
285
|
+
}
|