@luma.gl/gltf 9.3.0-alpha.9 → 9.3.0

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 (64) hide show
  1. package/dist/dist.dev.js +4396 -3081
  2. package/dist/dist.min.js +33 -14
  3. package/dist/gltf/animations/animations.d.ts +43 -3
  4. package/dist/gltf/animations/animations.d.ts.map +1 -1
  5. package/dist/gltf/animations/interpolate.d.ts +2 -0
  6. package/dist/gltf/animations/interpolate.d.ts.map +1 -1
  7. package/dist/gltf/animations/interpolate.js +27 -22
  8. package/dist/gltf/animations/interpolate.js.map +1 -1
  9. package/dist/gltf/create-gltf-model.d.ts.map +1 -1
  10. package/dist/gltf/create-gltf-model.js +29 -10
  11. package/dist/gltf/create-gltf-model.js.map +1 -1
  12. package/dist/gltf/create-scenegraph-from-gltf.js +2 -2
  13. package/dist/gltf/create-scenegraph-from-gltf.js.map +1 -1
  14. package/dist/gltf/gltf-animator.d.ts +12 -1
  15. package/dist/gltf/gltf-animator.d.ts.map +1 -1
  16. package/dist/gltf/gltf-animator.js +98 -6
  17. package/dist/gltf/gltf-animator.js.map +1 -1
  18. package/dist/gltf/gltf-extension-support.d.ts +3 -0
  19. package/dist/gltf/gltf-extension-support.d.ts.map +1 -1
  20. package/dist/gltf/gltf-extension-support.js +10 -5
  21. package/dist/gltf/gltf-extension-support.js.map +1 -1
  22. package/dist/index.cjs +763 -272
  23. package/dist/index.cjs.map +4 -4
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/parsers/parse-gltf-animations.d.ts.map +1 -1
  28. package/dist/parsers/parse-gltf-animations.js +319 -18
  29. package/dist/parsers/parse-gltf-animations.js.map +1 -1
  30. package/dist/parsers/parse-gltf-lights.d.ts +6 -2
  31. package/dist/parsers/parse-gltf-lights.d.ts.map +1 -1
  32. package/dist/parsers/parse-gltf-lights.js +9 -6
  33. package/dist/parsers/parse-gltf-lights.js.map +1 -1
  34. package/dist/parsers/parse-gltf.d.ts +2 -0
  35. package/dist/parsers/parse-gltf.d.ts.map +1 -1
  36. package/dist/parsers/parse-gltf.js +21 -7
  37. package/dist/parsers/parse-gltf.js.map +1 -1
  38. package/dist/parsers/parse-pbr-material.d.ts.map +1 -1
  39. package/dist/parsers/parse-pbr-material.js +114 -81
  40. package/dist/parsers/parse-pbr-material.js.map +1 -1
  41. package/dist/pbr/pbr-environment.d.ts.map +1 -1
  42. package/dist/pbr/pbr-environment.js +7 -3
  43. package/dist/pbr/pbr-environment.js.map +1 -1
  44. package/dist/pbr/texture-transform.d.ts +24 -0
  45. package/dist/pbr/texture-transform.d.ts.map +1 -0
  46. package/dist/pbr/texture-transform.js +98 -0
  47. package/dist/pbr/texture-transform.js.map +1 -0
  48. package/dist/webgl-to-webgpu/convert-webgl-topology.js +1 -1
  49. package/dist/webgl-to-webgpu/convert-webgl-topology.js.map +1 -1
  50. package/package.json +5 -5
  51. package/src/gltf/animations/animations.ts +73 -3
  52. package/src/gltf/animations/interpolate.ts +50 -43
  53. package/src/gltf/create-gltf-model.ts +29 -10
  54. package/src/gltf/create-scenegraph-from-gltf.ts +2 -2
  55. package/src/gltf/gltf-animator.ts +177 -8
  56. package/src/gltf/gltf-extension-support.ts +17 -5
  57. package/src/index.ts +1 -1
  58. package/src/parsers/parse-gltf-animations.ts +461 -21
  59. package/src/parsers/parse-gltf-lights.ts +27 -8
  60. package/src/parsers/parse-gltf.ts +23 -7
  61. package/src/parsers/parse-pbr-material.ts +184 -79
  62. package/src/pbr/pbr-environment.ts +10 -3
  63. package/src/pbr/texture-transform.ts +263 -0
  64. package/src/webgl-to-webgpu/convert-webgl-topology.ts +1 -1
@@ -3,9 +3,20 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {log} from '@luma.gl/core';
6
- import {GroupNode} from '@luma.gl/engine';
7
- import {GLTFAnimation} from './animations/animations';
8
- import {interpolate} from './animations/interpolate';
6
+ import {GroupNode, Material} from '@luma.gl/engine';
7
+ import {
8
+ GLTFAnimation,
9
+ GLTFMaterialAnimationChannel,
10
+ GLTFMaterialAnimationProperty,
11
+ GLTFTextureTransformAnimationChannel
12
+ } from './animations/animations';
13
+ import {evaluateSampler, interpolate} from './animations/interpolate';
14
+ import {
15
+ getTextureTransformDeltaMatrix,
16
+ getTextureTransformSlotDefinition,
17
+ type PBRTextureTransform,
18
+ type PBRTextureTransformSlot
19
+ } from '../pbr/texture-transform';
9
20
 
10
21
  /** Construction props for a single glTF animation controller. */
11
22
  type GLTFSingleAnimatorProps = {
@@ -13,6 +24,8 @@ type GLTFSingleAnimatorProps = {
13
24
  animation: GLTFAnimation;
14
25
  /** Mapping from glTF node ids to scenegraph nodes. */
15
26
  gltfNodeIdToNodeMap: Map<string, GroupNode>;
27
+ /** Materials aligned with the source glTF materials array. */
28
+ materials?: Material[];
16
29
  /** Start time in seconds. */
17
30
  startTime?: number;
18
31
  /** Whether playback is active. */
@@ -27,19 +40,36 @@ class GLTFSingleAnimator {
27
40
  animation: GLTFAnimation;
28
41
  /** Target scenegraph lookup table. */
29
42
  gltfNodeIdToNodeMap: Map<string, GroupNode>;
43
+ /** Materials aligned with the source glTF materials array. */
44
+ materials: Material[];
30
45
  /** Playback start time in seconds. */
31
46
  startTime: number = 0;
32
47
  /** Whether playback is currently enabled. */
33
48
  playing: boolean = true;
34
49
  /** Playback speed multiplier. */
35
50
  speed: number = 1;
51
+ /** Mutable runtime texture-transform state for animated material slots. */
52
+ materialTextureTransformState = new Map<
53
+ Material,
54
+ Partial<Record<PBRTextureTransformSlot, PBRTextureTransform>>
55
+ >();
36
56
 
37
57
  /** Creates a single-animation controller. */
38
58
  constructor(props: GLTFSingleAnimatorProps) {
39
59
  this.animation = props.animation;
40
60
  this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
61
+ this.materials = props.materials || [];
41
62
  this.animation.name ||= 'unnamed';
42
63
  Object.assign(this, props);
64
+
65
+ if (
66
+ this.animation.channels.some(channel => channel.type !== 'node') &&
67
+ !this.materials.length
68
+ ) {
69
+ throw new Error(
70
+ `Animation ${this.animation.name} targets materials, but GLTFAnimator was created without a materials array`
71
+ );
72
+ }
43
73
  }
44
74
 
45
75
  /** Advances the animation to the supplied wall-clock time in milliseconds. */
@@ -51,13 +81,38 @@ class GLTFSingleAnimator {
51
81
  const absTime = timeMs / 1000;
52
82
  const time = (absTime - this.startTime) * this.speed;
53
83
 
54
- this.animation.channels.forEach(({sampler, targetNodeId, path}) => {
55
- const targetNode = this.gltfNodeIdToNodeMap.get(targetNodeId);
56
- if (!targetNode) {
57
- throw new Error(`Cannot find animation target node ${targetNodeId}`);
84
+ this.animation.channels.forEach(channel => {
85
+ if (channel.type === 'node') {
86
+ const {sampler, targetNodeId, path} = channel;
87
+ const targetNode = this.gltfNodeIdToNodeMap.get(targetNodeId);
88
+ if (!targetNode) {
89
+ throw new Error(`Cannot find animation target node ${targetNodeId}`);
90
+ }
91
+
92
+ interpolate(time, sampler, targetNode, path);
93
+ return;
58
94
  }
59
95
 
60
- interpolate(time, sampler, targetNode, path);
96
+ const material = this.materials[channel.targetMaterialIndex];
97
+ if (!material) {
98
+ throw new Error(
99
+ `Cannot find animation target material ${channel.targetMaterialIndex} for ${channel.pointer}`
100
+ );
101
+ }
102
+
103
+ const value = evaluateSampler(time, channel.sampler);
104
+ if (value) {
105
+ if (channel.type === 'material') {
106
+ applyMaterialAnimationValue(material, channel, value);
107
+ } else {
108
+ applyTextureTransformAnimationValue(
109
+ material,
110
+ channel,
111
+ value,
112
+ this.materialTextureTransformState
113
+ );
114
+ }
115
+ }
61
116
  });
62
117
  }
63
118
  }
@@ -68,6 +123,8 @@ export type GLTFAnimatorProps = {
68
123
  animations: GLTFAnimation[];
69
124
  /** Mapping from glTF node ids to scenegraph nodes. */
70
125
  gltfNodeIdToNodeMap: Map<string, GroupNode>;
126
+ /** Materials aligned with the source glTF materials array. */
127
+ materials?: Material[];
71
128
  };
72
129
 
73
130
  /** Coordinates playback of every animation found in a glTF scene. */
@@ -81,6 +138,7 @@ export class GLTFAnimator {
81
138
  const name = animation.name || `Animation-${index}`;
82
139
  return new GLTFSingleAnimator({
83
140
  gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
141
+ materials: props.materials,
84
142
  animation: {name, channels: animation.channels}
85
143
  });
86
144
  });
@@ -102,3 +160,114 @@ export class GLTFAnimator {
102
160
  return this.animations;
103
161
  }
104
162
  }
163
+
164
+ function applyMaterialAnimationValue(
165
+ material: Material,
166
+ channel: GLTFMaterialAnimationChannel,
167
+ value: number[]
168
+ ): void {
169
+ const pbrMaterial =
170
+ channel.component !== undefined
171
+ ? {
172
+ [channel.property]: updateMaterialArrayComponent(
173
+ getCurrentMaterialValue(material, channel.property),
174
+ channel.component,
175
+ value[0]
176
+ )
177
+ }
178
+ : {
179
+ [channel.property]: value.length === 1 ? value[0] : value
180
+ };
181
+
182
+ material.setProps({pbrMaterial});
183
+ }
184
+
185
+ function getCurrentMaterialValue(
186
+ material: Material,
187
+ property: GLTFMaterialAnimationProperty
188
+ ): number[] {
189
+ const uniformValues = material.shaderInputs.getUniformValues() as Record<string, any>;
190
+ const currentValue = uniformValues['pbrMaterial']?.[property];
191
+ return Array.isArray(currentValue) ? [...currentValue] : [];
192
+ }
193
+
194
+ function updateMaterialArrayComponent(
195
+ currentValue: number[],
196
+ component: number,
197
+ nextValue: number
198
+ ): number[] {
199
+ const updatedValue = [...currentValue];
200
+ updatedValue[component] = nextValue;
201
+ return updatedValue;
202
+ }
203
+
204
+ function applyTextureTransformAnimationValue(
205
+ material: Material,
206
+ channel: GLTFTextureTransformAnimationChannel,
207
+ value: number[],
208
+ materialTextureTransformState: Map<
209
+ Material,
210
+ Partial<Record<PBRTextureTransformSlot, PBRTextureTransform>>
211
+ >
212
+ ): void {
213
+ const slotDefinition = getTextureTransformSlotDefinition(channel.textureSlot);
214
+ const currentTransform = getCurrentTextureTransform(
215
+ materialTextureTransformState,
216
+ material,
217
+ channel
218
+ );
219
+
220
+ switch (channel.path) {
221
+ case 'offset':
222
+ if (channel.component !== undefined) {
223
+ currentTransform.offset[channel.component] = value[0];
224
+ } else {
225
+ currentTransform.offset = [value[0], value[1]];
226
+ }
227
+ break;
228
+
229
+ case 'rotation':
230
+ currentTransform.rotation = value[0];
231
+ break;
232
+
233
+ case 'scale':
234
+ if (channel.component !== undefined) {
235
+ currentTransform.scale[channel.component] = value[0];
236
+ } else {
237
+ currentTransform.scale = [value[0], value[1]];
238
+ }
239
+ break;
240
+ }
241
+
242
+ material.setProps({
243
+ pbrMaterial: {
244
+ [slotDefinition.uvTransformUniform]: getTextureTransformDeltaMatrix(
245
+ channel.baseTransform,
246
+ currentTransform
247
+ )
248
+ }
249
+ });
250
+ }
251
+
252
+ function getCurrentTextureTransform(
253
+ materialTextureTransformState: Map<
254
+ Material,
255
+ Partial<Record<PBRTextureTransformSlot, PBRTextureTransform>>
256
+ >,
257
+ material: Material,
258
+ channel: GLTFTextureTransformAnimationChannel
259
+ ): PBRTextureTransform {
260
+ const materialState = materialTextureTransformState.get(material) || {};
261
+ let textureTransformState = materialState[channel.textureSlot];
262
+ if (!textureTransformState) {
263
+ textureTransformState = {
264
+ offset: [...channel.baseTransform.offset] as [number, number],
265
+ rotation: channel.baseTransform.rotation,
266
+ scale: [...channel.baseTransform.scale] as [number, number]
267
+ };
268
+ materialState[channel.textureSlot] = textureTransformState;
269
+ materialTextureTransformState.set(material, materialState);
270
+ }
271
+
272
+ return textureTransformState;
273
+ }
@@ -121,8 +121,9 @@ const GLTF_EXTENSION_SUPPORT_REGISTRY: Record<string, GLTFExtensionSupportDefini
121
121
  comment: 'Node-visibility animations and toggles are not mapped onto runtime scenegraph state.'
122
122
  },
123
123
  KHR_animation_pointer: {
124
- supportLevel: 'none',
125
- comment: 'Animation pointers are not mapped onto runtime scenegraph updates.'
124
+ supportLevel: 'parsed-and-wired',
125
+ comment:
126
+ 'Selected node TRS, material factor, and KHR_texture_transform offset/rotation/scale pointers are wired to runtime updates; unsupported targets are skipped.'
126
127
  },
127
128
  KHR_materials_diffuse_transmission: {
128
129
  supportLevel: 'none',
@@ -171,7 +172,9 @@ export function getGLTFExtensionSupport(
171
172
  extensionName,
172
173
  {
173
174
  extensionName,
174
- supported: extensionSupportDefinition.supportLevel === 'built-in',
175
+ supported:
176
+ extensionSupportDefinition.supportLevel === 'built-in' ||
177
+ extensionSupportDefinition.supportLevel === 'parsed-and-wired',
175
178
  supportLevel: extensionSupportDefinition.supportLevel,
176
179
  comment: extensionSupportDefinition.comment
177
180
  }
@@ -182,6 +185,12 @@ export function getGLTFExtensionSupport(
182
185
  return new Map(extensionSupportEntries);
183
186
  }
184
187
 
188
+ export function getRegisteredGLTFExtensionSupport(
189
+ extensionName: string
190
+ ): GLTFExtensionSupportDefinition | null {
191
+ return GLTF_EXTENSION_SUPPORT_REGISTRY[extensionName] || null;
192
+ }
193
+
185
194
  function collectGLTFExtensionNames(gltf: GLTFPostprocessed): Set<string> {
186
195
  const gltfWithRemovedExtensions = gltf as GLTFPostprocessedWithRemovedExtensions;
187
196
  const extensionNames = new Set<string>();
@@ -191,12 +200,15 @@ function collectGLTFExtensionNames(gltf: GLTFPostprocessed): Set<string> {
191
200
  addExtensionNames(extensionNames, gltfWithRemovedExtensions.extensionsRemoved);
192
201
  addExtensionNames(extensionNames, Object.keys(gltf.extensions || {}));
193
202
 
194
- if (gltfWithRemovedExtensions.lights?.length || gltf.nodes.some(node => 'light' in node)) {
203
+ if (
204
+ gltfWithRemovedExtensions.lights?.length ||
205
+ (gltf.nodes || []).some(node => 'light' in node)
206
+ ) {
195
207
  extensionNames.add('KHR_lights_punctual');
196
208
  }
197
209
 
198
210
  if (
199
- gltf.materials.some(material => {
211
+ (gltf.materials || []).some(material => {
200
212
  const gltfMaterial = material as typeof material & {unlit?: boolean};
201
213
  return gltfMaterial.unlit || gltfMaterial.extensions?.KHR_materials_unlit;
202
214
  })
package/src/index.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  export {loadPBREnvironment, type PBREnvironment} from './pbr/pbr-environment';
4
4
  export {type ParsedPBRMaterial} from './pbr/pbr-material';
5
5
  export {parsePBRMaterial, type ParsePBRMaterialOptions} from './parsers/parse-pbr-material';
6
- export {parseGLTFLights} from './parsers/parse-gltf-lights';
6
+ export {parseGLTFLights, type ParseGLTFLightsOptions} from './parsers/parse-gltf-lights';
7
7
 
8
8
  // glTF Scenegraph Instantiator
9
9
  export {