@luma.gl/gltf 9.3.0-alpha.4 → 9.3.0-alpha.8

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 (90) hide show
  1. package/dist/dist.dev.js +1305 -327
  2. package/dist/dist.min.js +98 -46
  3. package/dist/gltf/animations/animations.d.ts +16 -4
  4. package/dist/gltf/animations/animations.d.ts.map +1 -1
  5. package/dist/gltf/animations/interpolate.d.ts +4 -3
  6. package/dist/gltf/animations/interpolate.d.ts.map +1 -1
  7. package/dist/gltf/animations/interpolate.js +27 -36
  8. package/dist/gltf/animations/interpolate.js.map +1 -1
  9. package/dist/gltf/create-gltf-model.d.ts +15 -1
  10. package/dist/gltf/create-gltf-model.d.ts.map +1 -1
  11. package/dist/gltf/create-gltf-model.js +154 -48
  12. package/dist/gltf/create-gltf-model.js.map +1 -1
  13. package/dist/gltf/create-scenegraph-from-gltf.d.ts +37 -2
  14. package/dist/gltf/create-scenegraph-from-gltf.d.ts.map +1 -1
  15. package/dist/gltf/create-scenegraph-from-gltf.js +74 -6
  16. package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
  17. package/dist/gltf/gltf-animator.d.ts +26 -0
  18. package/dist/gltf/gltf-animator.d.ts.map +1 -1
  19. package/dist/gltf/gltf-animator.js +22 -19
  20. package/dist/gltf/gltf-animator.js.map +1 -1
  21. package/dist/gltf/gltf-extension-support.d.ts +10 -0
  22. package/dist/gltf/gltf-extension-support.d.ts.map +1 -0
  23. package/dist/gltf/gltf-extension-support.js +173 -0
  24. package/dist/gltf/gltf-extension-support.js.map +1 -0
  25. package/dist/index.cjs +1247 -294
  26. package/dist/index.cjs.map +4 -4
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/parsers/parse-gltf-animations.d.ts +1 -0
  32. package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
  33. package/dist/parsers/parse-gltf-animations.js +73 -28
  34. package/dist/parsers/parse-gltf-animations.js.map +1 -1
  35. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -1
  36. package/dist/parsers/parse-gltf-lights.js +112 -18
  37. package/dist/parsers/parse-gltf-lights.js.map +1 -1
  38. package/dist/parsers/parse-gltf.d.ts +19 -2
  39. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  40. package/dist/parsers/parse-gltf.js +101 -61
  41. package/dist/parsers/parse-gltf.js.map +1 -1
  42. package/dist/parsers/parse-pbr-material.d.ts +115 -2
  43. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  44. package/dist/parsers/parse-pbr-material.js +565 -54
  45. package/dist/parsers/parse-pbr-material.js.map +1 -1
  46. package/dist/pbr/pbr-environment.d.ts +6 -0
  47. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  48. package/dist/pbr/pbr-environment.js +15 -12
  49. package/dist/pbr/pbr-environment.js.map +1 -1
  50. package/dist/pbr/pbr-material.d.ts +13 -3
  51. package/dist/pbr/pbr-material.d.ts.map +1 -1
  52. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts +12 -1
  53. package/dist/webgl-to-webgpu/convert-webgl-attribute.d.ts.map +1 -1
  54. package/dist/webgl-to-webgpu/convert-webgl-attribute.js +3 -0
  55. package/dist/webgl-to-webgpu/convert-webgl-attribute.js.map +1 -1
  56. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts +11 -5
  57. package/dist/webgl-to-webgpu/convert-webgl-sampler.d.ts.map +1 -1
  58. package/dist/webgl-to-webgpu/convert-webgl-sampler.js +16 -12
  59. package/dist/webgl-to-webgpu/convert-webgl-sampler.js.map +1 -1
  60. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts +2 -9
  61. package/dist/webgl-to-webgpu/convert-webgl-topology.d.ts.map +1 -1
  62. package/dist/webgl-to-webgpu/convert-webgl-topology.js +2 -14
  63. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  64. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts +27 -0
  65. package/dist/webgl-to-webgpu/gltf-webgl-constants.d.ts.map +1 -0
  66. package/dist/webgl-to-webgpu/gltf-webgl-constants.js +34 -0
  67. package/dist/webgl-to-webgpu/gltf-webgl-constants.js.map +1 -0
  68. package/package.json +8 -9
  69. package/src/gltf/animations/animations.ts +17 -5
  70. package/src/gltf/animations/interpolate.ts +49 -68
  71. package/src/gltf/create-gltf-model.ts +214 -48
  72. package/src/gltf/create-scenegraph-from-gltf.ts +131 -12
  73. package/src/gltf/gltf-animator.ts +34 -25
  74. package/src/gltf/gltf-extension-support.ts +214 -0
  75. package/src/index.ts +10 -2
  76. package/src/parsers/parse-gltf-animations.ts +94 -33
  77. package/src/parsers/parse-gltf-lights.ts +147 -20
  78. package/src/parsers/parse-gltf.ts +170 -90
  79. package/src/parsers/parse-pbr-material.ts +865 -80
  80. package/src/pbr/pbr-environment.ts +38 -15
  81. package/src/pbr/pbr-material.ts +18 -3
  82. package/src/webgl-to-webgpu/convert-webgl-attribute.ts +12 -1
  83. package/src/webgl-to-webgpu/convert-webgl-sampler.ts +38 -29
  84. package/src/webgl-to-webgpu/convert-webgl-topology.ts +2 -14
  85. package/src/webgl-to-webgpu/gltf-webgl-constants.ts +35 -0
  86. package/dist/utils/deep-copy.d.ts +0 -3
  87. package/dist/utils/deep-copy.d.ts.map +0 -1
  88. package/dist/utils/deep-copy.js +0 -21
  89. package/dist/utils/deep-copy.js.map +0 -1
  90. package/src/utils/deep-copy.ts +0 -22
@@ -1,41 +1,53 @@
1
1
  import {Matrix4} from '@math.gl/core';
2
2
  import type {GLTFNodePostprocessed, GLTFPostprocessed} from '@loaders.gl/gltf';
3
- import type {DirectionalLight, Light, PointLight} from '@luma.gl/shadertools';
3
+ import type {DirectionalLight, Light, PointLight, SpotLight} from '@luma.gl/shadertools';
4
+
5
+ const GLTF_COLOR_FACTOR = 255;
4
6
 
5
7
  /** Parse KHR_lights_punctual extension into luma.gl light definitions */
6
8
  export function parseGLTFLights(gltf: GLTFPostprocessed): Light[] {
7
- const lightDefs = gltf.extensions?.['KHR_lights_punctual']?.['lights'];
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'];
8
13
  if (!lightDefs || !Array.isArray(lightDefs) || lightDefs.length === 0) {
9
14
  return [];
10
15
  }
11
16
 
12
17
  const lights: Light[] = [];
18
+ const parentNodeById = createParentNodeMap(gltf.nodes || []);
19
+ const worldMatrixByNodeId = new Map<string, Matrix4>();
13
20
 
14
21
  for (const node of gltf.nodes || []) {
15
- const nodeLight = node.extensions?.KHR_lights_punctual;
16
- if (!nodeLight || typeof nodeLight.light !== 'number') {
22
+ const lightIndex =
23
+ (node as GLTFNodePostprocessed & {light?: number}).light ??
24
+ node.extensions?.KHR_lights_punctual?.light;
25
+ if (typeof lightIndex !== 'number') {
17
26
  // eslint-disable-next-line no-continue
18
27
  continue;
19
28
  }
20
- const gltfLight = lightDefs[nodeLight.light];
29
+ const gltfLight = lightDefs[lightIndex];
21
30
  if (!gltfLight) {
22
31
  // eslint-disable-next-line no-continue
23
32
  continue;
24
33
  }
25
34
 
26
- const color = (gltfLight.color || [1, 1, 1]) as [number, number, number];
35
+ const color = normalizeGLTFLightColor(
36
+ (gltfLight.color || [1, 1, 1]) as [number, number, number]
37
+ );
27
38
  const intensity = gltfLight.intensity ?? 1;
28
39
  const range = gltfLight.range;
40
+ const worldMatrix = getNodeWorldMatrix(node, parentNodeById, worldMatrixByNodeId);
29
41
 
30
42
  switch (gltfLight.type) {
31
43
  case 'directional':
32
- lights.push(parseDirectionalLight(node, color, intensity));
44
+ lights.push(parseDirectionalLight(worldMatrix, color, intensity));
33
45
  break;
34
46
  case 'point':
35
- lights.push(parsePointLight(node, color, intensity, range));
47
+ lights.push(parsePointLight(worldMatrix, color, intensity, range));
36
48
  break;
37
49
  case 'spot':
38
- lights.push(parsePointLight(node, color, intensity, range));
50
+ lights.push(parseSpotLight(worldMatrix, color, intensity, range, gltfLight.spot));
39
51
  break;
40
52
  default:
41
53
  // Unsupported light type
@@ -46,15 +58,23 @@ export function parseGLTFLights(gltf: GLTFPostprocessed): Light[] {
46
58
  return lights;
47
59
  }
48
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
+ */
49
71
  function parsePointLight(
50
- node: GLTFNodePostprocessed,
72
+ worldMatrix: Matrix4,
51
73
  color: [number, number, number],
52
74
  intensity: number,
53
75
  range?: number
54
76
  ): PointLight {
55
- const position: Readonly<[number, number, number]> = node.translation
56
- ? ([...node.translation] as [number, number, number])
57
- : ([0, 0, 0] as [number, number, number]);
77
+ const position = getLightPosition(worldMatrix);
58
78
 
59
79
  let attenuation: Readonly<[number, number, number]> = [1, 0, 0];
60
80
  if (range !== undefined && range > 0) {
@@ -70,17 +90,15 @@ function parsePointLight(
70
90
  };
71
91
  }
72
92
 
93
+ /**
94
+ * Converts a glTF punctual light attached to a node into a directional light.
95
+ */
73
96
  function parseDirectionalLight(
74
- node: GLTFNodePostprocessed,
97
+ worldMatrix: Matrix4,
75
98
  color: [number, number, number],
76
99
  intensity: number
77
100
  ): DirectionalLight {
78
- let direction: [number, number, number] = [0, 0, -1];
79
-
80
- if (node.rotation) {
81
- const orientation = new Matrix4().fromQuaternion(node.rotation);
82
- direction = orientation.transformDirection([0, 0, -1]) as [number, number, number];
83
- }
101
+ const direction = getLightDirection(worldMatrix);
84
102
 
85
103
  return {
86
104
  type: 'directional',
@@ -89,3 +107,112 @@ function parseDirectionalLight(
89
107
  intensity
90
108
  };
91
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 {type GLTFScenePostprocessed} from '@loaders.gl/gltf/dist/lib/types/gltf-postprocessed-schema';
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,107 +55,166 @@ const defaultOptions: Required<ParseGLTFOptions> = {
41
55
  export function parseGLTF(
42
56
  device: Device,
43
57
  gltf: GLTFPostprocessed,
44
- options_: ParseGLTFOptions = {}
45
- ): GroupNode[] {
46
- const options = {...defaultOptions, ...options_};
47
- const sceneNodes = gltf.scenes.map(gltfScene =>
48
- createScene(device, gltfScene, gltf.nodes, options)
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
- return sceneNodes;
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
- function createScene(
54
- device: Device,
55
- gltfScene: GLTFScenePostprocessed,
56
- gltfNodes: GLTFNodePostprocessed[],
57
- options: Required<ParseGLTFOptions>
58
- ): GroupNode {
59
- const gltfSceneNodes = gltfScene.nodes || [];
60
- const nodes = gltfSceneNodes.map(node => createNode(device, node, gltfNodes, options));
61
- const sceneNode = new GroupNode({
62
- id: gltfScene.name || gltfScene.id,
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
- function createNode(
69
- device: Device,
70
- gltfNode: GLTFNodePostprocessed & {_node?: GroupNode},
71
- gltfNodes: GLTFNodePostprocessed[],
72
- options: Required<ParseGLTFOptions>
73
- ): GroupNode {
74
- if (!gltfNode._node) {
75
- const gltfChildren = gltfNode.children || [];
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
- // Node can have children nodes and meshes at the same time
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
- children.push(createMesh(device, gltfNode.mesh, options));
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
- const node = new GroupNode({
84
- id: gltfNode.name || gltfNode.id,
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
- if (gltfNode.matrix) {
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
- function createMesh(
152
+ /** Creates a `GroupNode` for one glTF node transform. */
153
+ function createNodeForGLTFNode(
117
154
  device: Device,
118
- gltfMesh: GLTFMeshPostprocessed & {_mesh?: GroupNode},
155
+ gltfNode: GLTFNodePostprocessed,
119
156
  options: Required<ParseGLTFOptions>
120
157
  ): GroupNode {
121
- // TODO: avoid changing the gltf
122
- if (!gltfMesh._mesh) {
123
- const gltfPrimitives = gltfMesh.primitives || [];
124
- const primitives = gltfPrimitives.map((gltfPrimitive, i) =>
125
- createPrimitive(device, gltfPrimitive, i, gltfMesh, options)
126
- );
127
- const mesh = new GroupNode({
128
- id: gltfMesh.name || gltfMesh.id,
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
- function createPrimitive(
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
- ): ModelNode {
144
- const id = gltfPrimitive.name || `${gltfMesh.name || gltfMesh.id}-primitive-${i}`;
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}`;
145
218
  const topology = convertGLDrawModeToTopology(gltfPrimitive.mode || 4);
146
219
  const vertexCount = gltfPrimitive.indices
147
220
  ? gltfPrimitive.indices.count
@@ -149,16 +222,17 @@ function createPrimitive(
149
222
 
150
223
  const geometry = createGeometry(id, gltfPrimitive, topology);
151
224
 
152
- const parsedPPBRMaterial = parsePBRMaterial(
153
- device,
154
- gltfPrimitive.material,
155
- geometry.attributes,
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
232
  geometry: createGeometry(id, gltfPrimitive, topology),
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,10 +245,12 @@ 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
  throw new Error('getVertexCount not implemented');
176
251
  }
177
252
 
253
+ /** Converts glTF primitive attributes and indices into a luma.gl `Geometry`. */
178
254
  function createGeometry(id: string, gltfPrimitive: any, topology: PrimitiveTopology): Geometry {
179
255
  const attributes: Record<string, GeometryAttribute> = {};
180
256
  for (const [attributeName, attribute] of Object.entries(gltfPrimitive.attributes)) {
@@ -190,3 +266,7 @@ function createGeometry(id: string, gltfPrimitive: any, topology: PrimitiveTopol
190
266
  attributes
191
267
  });
192
268
  }
269
+
270
+ function getGLTFMaterialId(gltfMaterial: GLTFMaterialPostprocessed, materialIndex: number): string {
271
+ return gltfMaterial.name || gltfMaterial.id || `material-${materialIndex}`;
272
+ }