@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
@@ -2,32 +2,47 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
- import {GLTFNodePostprocessed} from '@loaders.gl/gltf';
6
5
  import {log} from '@luma.gl/core';
7
6
  import {GroupNode} from '@luma.gl/engine';
8
- import {Matrix4} from '@math.gl/core';
9
7
  import {GLTFAnimation} from './animations/animations';
10
8
  import {interpolate} from './animations/interpolate';
11
9
 
10
+ /** Construction props for a single glTF animation controller. */
12
11
  type GLTFSingleAnimatorProps = {
12
+ /** Animation data to evaluate. */
13
13
  animation: GLTFAnimation;
14
+ /** Mapping from glTF node ids to scenegraph nodes. */
15
+ gltfNodeIdToNodeMap: Map<string, GroupNode>;
16
+ /** Start time in seconds. */
14
17
  startTime?: number;
18
+ /** Whether playback is active. */
15
19
  playing?: boolean;
20
+ /** Playback speed multiplier. */
16
21
  speed?: number;
17
22
  };
18
23
 
24
+ /** Evaluates one glTF animation against the generated scenegraph. */
19
25
  class GLTFSingleAnimator {
26
+ /** Animation definition being played. */
20
27
  animation: GLTFAnimation;
28
+ /** Target scenegraph lookup table. */
29
+ gltfNodeIdToNodeMap: Map<string, GroupNode>;
30
+ /** Playback start time in seconds. */
21
31
  startTime: number = 0;
32
+ /** Whether playback is currently enabled. */
22
33
  playing: boolean = true;
34
+ /** Playback speed multiplier. */
23
35
  speed: number = 1;
24
36
 
37
+ /** Creates a single-animation controller. */
25
38
  constructor(props: GLTFSingleAnimatorProps) {
26
39
  this.animation = props.animation;
40
+ this.gltfNodeIdToNodeMap = props.gltfNodeIdToNodeMap;
27
41
  this.animation.name ||= 'unnamed';
28
42
  Object.assign(this, props);
29
43
  }
30
44
 
45
+ /** Advances the animation to the supplied wall-clock time in milliseconds. */
31
46
  setTime(timeMs: number) {
32
47
  if (!this.playing) {
33
48
  return;
@@ -36,24 +51,36 @@ class GLTFSingleAnimator {
36
51
  const absTime = timeMs / 1000;
37
52
  const time = (absTime - this.startTime) * this.speed;
38
53
 
39
- this.animation.channels.forEach(({sampler, target, path}) => {
40
- interpolate(time, sampler, target, path);
41
- applyTranslationRotationScale(target, (target as any)._node as GroupNode);
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}`);
58
+ }
59
+
60
+ interpolate(time, sampler, targetNode, path);
42
61
  });
43
62
  }
44
63
  }
45
64
 
65
+ /** Construction props for {@link GLTFAnimator}. */
46
66
  export type GLTFAnimatorProps = {
67
+ /** Parsed animations from the source glTF. */
47
68
  animations: GLTFAnimation[];
69
+ /** Mapping from glTF node ids to scenegraph nodes. */
70
+ gltfNodeIdToNodeMap: Map<string, GroupNode>;
48
71
  };
49
72
 
73
+ /** Coordinates playback of every animation found in a glTF scene. */
50
74
  export class GLTFAnimator {
75
+ /** Individual animation controllers. */
51
76
  animations: GLTFSingleAnimator[];
52
77
 
78
+ /** Creates an animator for the supplied glTF scenegraph. */
53
79
  constructor(props: GLTFAnimatorProps) {
54
80
  this.animations = props.animations.map((animation, index) => {
55
81
  const name = animation.name || `Animation-${index}`;
56
82
  return new GLTFSingleAnimator({
83
+ gltfNodeIdToNodeMap: props.gltfNodeIdToNodeMap,
57
84
  animation: {name, channels: animation.channels}
58
85
  });
59
86
  });
@@ -65,31 +92,13 @@ export class GLTFAnimator {
65
92
  this.setTime(time);
66
93
  }
67
94
 
95
+ /** Advances every animation to the supplied wall-clock time in milliseconds. */
68
96
  setTime(time: number): void {
69
97
  this.animations.forEach(animation => animation.setTime(time));
70
98
  }
71
99
 
100
+ /** Returns the per-animation controllers managed by this animator. */
72
101
  getAnimations() {
73
102
  return this.animations;
74
103
  }
75
104
  }
76
-
77
- // TODO: share with GLTFInstantiator
78
- const scratchMatrix = new Matrix4();
79
-
80
- function applyTranslationRotationScale(gltfNode: GLTFNodePostprocessed, node: GroupNode) {
81
- node.matrix.identity();
82
-
83
- if (gltfNode.translation) {
84
- node.matrix.translate(gltfNode.translation);
85
- }
86
-
87
- if (gltfNode.rotation) {
88
- const rotationMatrix = scratchMatrix.fromQuaternion(gltfNode.rotation);
89
- node.matrix.multiplyRight(rotationMatrix);
90
- }
91
-
92
- if (gltfNode.scale) {
93
- node.matrix.scale(gltfNode.scale);
94
- }
95
- }
@@ -0,0 +1,214 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {GLTFPostprocessed} from '@loaders.gl/gltf';
6
+
7
+ export type GLTFExtensionSupportLevel = 'built-in' | 'parsed-and-wired' | 'loader-only' | 'none';
8
+
9
+ export type GLTFExtensionSupport = {
10
+ extensionName: string;
11
+ supported: boolean;
12
+ supportLevel: GLTFExtensionSupportLevel;
13
+ comment: string;
14
+ };
15
+
16
+ type GLTFExtensionSupportDefinition = Omit<GLTFExtensionSupport, 'extensionName' | 'supported'>;
17
+
18
+ type GLTFPostprocessedWithRemovedExtensions = GLTFPostprocessed & {
19
+ extensionsRemoved?: string[];
20
+ lights?: unknown[];
21
+ };
22
+
23
+ const UNKNOWN_EXTENSION_SUPPORT: GLTFExtensionSupportDefinition = {
24
+ supportLevel: 'none',
25
+ comment: 'Not currently listed in the luma.gl glTF extension support registry.'
26
+ };
27
+
28
+ const GLTF_EXTENSION_SUPPORT_REGISTRY: Record<string, GLTFExtensionSupportDefinition> = {
29
+ KHR_draco_mesh_compression: {
30
+ supportLevel: 'built-in',
31
+ comment: 'Decoded by loaders.gl before luma.gl builds the scenegraph.'
32
+ },
33
+ EXT_meshopt_compression: {
34
+ supportLevel: 'built-in',
35
+ comment: 'Meshopt-compressed primitives are decoded during load.'
36
+ },
37
+ KHR_mesh_quantization: {
38
+ supportLevel: 'built-in',
39
+ comment: 'Quantized accessors are unpacked before geometry creation.'
40
+ },
41
+ KHR_lights_punctual: {
42
+ supportLevel: 'built-in',
43
+ comment: 'Parsed into luma.gl Light objects.'
44
+ },
45
+ KHR_materials_unlit: {
46
+ supportLevel: 'built-in',
47
+ comment: 'Unlit materials bypass the default lighting path.'
48
+ },
49
+ KHR_materials_emissive_strength: {
50
+ supportLevel: 'built-in',
51
+ comment: 'Applied by the stock PBR shader.'
52
+ },
53
+ KHR_texture_basisu: {
54
+ supportLevel: 'built-in',
55
+ comment: 'BasisU / KTX2 textures pass through when the device supports them.'
56
+ },
57
+ KHR_texture_transform: {
58
+ supportLevel: 'built-in',
59
+ comment: 'UV transforms are applied during load.'
60
+ },
61
+ EXT_texture_webp: {
62
+ supportLevel: 'loader-only',
63
+ comment:
64
+ 'Texture source is resolved during load; final support depends on browser and device decode support.'
65
+ },
66
+ EXT_texture_avif: {
67
+ supportLevel: 'loader-only',
68
+ comment:
69
+ 'Texture source is resolved during load; final support depends on browser and device decode support.'
70
+ },
71
+ KHR_materials_specular: {
72
+ supportLevel: 'built-in',
73
+ comment: 'The stock shader now applies specular factors and textures to the dielectric F0 term.'
74
+ },
75
+ KHR_materials_ior: {
76
+ supportLevel: 'built-in',
77
+ comment: 'The stock shader now drives dielectric reflectance from the glTF IOR value.'
78
+ },
79
+ KHR_materials_transmission: {
80
+ supportLevel: 'built-in',
81
+ comment:
82
+ 'The stock shader now applies transmission to the base layer and exposes transparency through alpha, without a scene-color refraction buffer.'
83
+ },
84
+ KHR_materials_volume: {
85
+ supportLevel: 'built-in',
86
+ comment: 'Thickness and attenuation now tint transmitted light in the stock shader.'
87
+ },
88
+ KHR_materials_clearcoat: {
89
+ supportLevel: 'built-in',
90
+ comment: 'The stock shader now adds a secondary clearcoat specular lobe.'
91
+ },
92
+ KHR_materials_sheen: {
93
+ supportLevel: 'built-in',
94
+ comment: 'The stock shader now adds a sheen lobe for cloth-like materials.'
95
+ },
96
+ KHR_materials_iridescence: {
97
+ supportLevel: 'built-in',
98
+ comment:
99
+ 'The stock shader now tints specular response with a view-dependent thin-film iridescence approximation.'
100
+ },
101
+ KHR_materials_anisotropy: {
102
+ supportLevel: 'built-in',
103
+ comment:
104
+ 'The stock shader now shapes highlights and IBL response with an anisotropy-direction approximation.'
105
+ },
106
+ KHR_materials_pbrSpecularGlossiness: {
107
+ supportLevel: 'loader-only',
108
+ comment:
109
+ 'Extension data can be loaded, but it is not translated into the default metallic-roughness material path.'
110
+ },
111
+ KHR_materials_variants: {
112
+ supportLevel: 'loader-only',
113
+ comment: 'Variant metadata can be loaded, but applications must choose and apply variants.'
114
+ },
115
+ EXT_mesh_gpu_instancing: {
116
+ supportLevel: 'none',
117
+ comment: 'GPU instancing data is not yet converted into luma.gl instanced draw setup.'
118
+ },
119
+ KHR_node_visibility: {
120
+ supportLevel: 'none',
121
+ comment: 'Node-visibility animations and toggles are not mapped onto runtime scenegraph state.'
122
+ },
123
+ KHR_animation_pointer: {
124
+ supportLevel: 'none',
125
+ comment: 'Animation pointers are not mapped onto runtime scenegraph updates.'
126
+ },
127
+ KHR_materials_diffuse_transmission: {
128
+ supportLevel: 'none',
129
+ comment: 'Diffuse-transmission shading is not implemented in the stock PBR shader.'
130
+ },
131
+ KHR_materials_dispersion: {
132
+ supportLevel: 'none',
133
+ comment: 'Chromatic dispersion is not implemented in the stock PBR shader.'
134
+ },
135
+ KHR_materials_volume_scatter: {
136
+ supportLevel: 'none',
137
+ comment: 'Volume scattering is not implemented in the stock PBR shader.'
138
+ },
139
+ KHR_xmp: {
140
+ supportLevel: 'none',
141
+ comment: 'Metadata payloads remain in the loaded glTF, but luma.gl does not interpret them.'
142
+ },
143
+ KHR_xmp_json_ld: {
144
+ supportLevel: 'none',
145
+ comment: 'Metadata is preserved in the glTF, but luma.gl does not interpret it.'
146
+ },
147
+ EXT_lights_image_based: {
148
+ supportLevel: 'none',
149
+ comment: 'Use loadPBREnvironment() or custom environment setup instead.'
150
+ },
151
+ EXT_texture_video: {
152
+ supportLevel: 'none',
153
+ comment: 'Video textures are not created automatically by the stock pipeline.'
154
+ },
155
+ MSFT_lod: {
156
+ supportLevel: 'none',
157
+ comment: 'Level-of-detail switching is not implemented in the stock scenegraph loader.'
158
+ }
159
+ };
160
+
161
+ export function getGLTFExtensionSupport(
162
+ gltf: GLTFPostprocessed
163
+ ): Map<string, GLTFExtensionSupport> {
164
+ const extensionNames = Array.from(collectGLTFExtensionNames(gltf)).sort();
165
+ const extensionSupportEntries: [string, GLTFExtensionSupport][] = extensionNames.map(
166
+ extensionName => {
167
+ const extensionSupportDefinition =
168
+ GLTF_EXTENSION_SUPPORT_REGISTRY[extensionName] || UNKNOWN_EXTENSION_SUPPORT;
169
+
170
+ return [
171
+ extensionName,
172
+ {
173
+ extensionName,
174
+ supported: extensionSupportDefinition.supportLevel === 'built-in',
175
+ supportLevel: extensionSupportDefinition.supportLevel,
176
+ comment: extensionSupportDefinition.comment
177
+ }
178
+ ];
179
+ }
180
+ );
181
+
182
+ return new Map(extensionSupportEntries);
183
+ }
184
+
185
+ function collectGLTFExtensionNames(gltf: GLTFPostprocessed): Set<string> {
186
+ const gltfWithRemovedExtensions = gltf as GLTFPostprocessedWithRemovedExtensions;
187
+ const extensionNames = new Set<string>();
188
+
189
+ addExtensionNames(extensionNames, gltf.extensionsUsed);
190
+ addExtensionNames(extensionNames, gltf.extensionsRequired);
191
+ addExtensionNames(extensionNames, gltfWithRemovedExtensions.extensionsRemoved);
192
+ addExtensionNames(extensionNames, Object.keys(gltf.extensions || {}));
193
+
194
+ if (gltfWithRemovedExtensions.lights?.length || gltf.nodes.some(node => 'light' in node)) {
195
+ extensionNames.add('KHR_lights_punctual');
196
+ }
197
+
198
+ if (
199
+ gltf.materials.some(material => {
200
+ const gltfMaterial = material as typeof material & {unlit?: boolean};
201
+ return gltfMaterial.unlit || gltfMaterial.extensions?.KHR_materials_unlit;
202
+ })
203
+ ) {
204
+ extensionNames.add('KHR_materials_unlit');
205
+ }
206
+
207
+ return extensionNames;
208
+ }
209
+
210
+ function addExtensionNames(extensionNames: Set<string>, newExtensionNames: string[] = []): void {
211
+ for (const extensionName of newExtensionNames) {
212
+ extensionNames.add(extensionName);
213
+ }
214
+ }
package/src/index.ts CHANGED
@@ -3,9 +3,17 @@
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 {} from './pbr/pbr-environment';
7
6
  export {parseGLTFLights} from './parsers/parse-gltf-lights';
8
7
 
9
8
  // glTF Scenegraph Instantiator
10
- export {createScenegraphsFromGLTF} from './gltf/create-scenegraph-from-gltf';
9
+ export {
10
+ createScenegraphsFromGLTF,
11
+ type GLTFScenegraphBounds,
12
+ type GLTFScenegraphs
13
+ } from './gltf/create-scenegraph-from-gltf';
14
+ export {
15
+ getGLTFExtensionSupport,
16
+ type GLTFExtensionSupport,
17
+ type GLTFExtensionSupportLevel
18
+ } from './gltf/gltf-extension-support';
11
19
  export {GLTFAnimator} from './gltf/gltf-animator';
@@ -2,8 +2,10 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
+ import {log} from '@luma.gl/core';
5
6
  import {type GLTFAccessorPostprocessed, type GLTFPostprocessed} from '@loaders.gl/gltf';
6
7
  import {
8
+ GLTFAnimationPath,
7
9
  type GLTFAnimation,
8
10
  type GLTFAnimationChannel,
9
11
  type GLTFAnimationSampler
@@ -11,45 +13,104 @@ import {
11
13
 
12
14
  import {accessorToTypedArray} from '..//webgl-to-webgpu/convert-webgl-attribute';
13
15
 
16
+ /** Parses glTF animation records into the runtime animation model used by `GLTFAnimator`. */
14
17
  export function parseGLTFAnimations(gltf: GLTFPostprocessed): GLTFAnimation[] {
15
18
  const gltfAnimations = gltf.animations || [];
16
- return gltfAnimations.map((animation, index) => {
19
+ const accessorCache1D = new Map<GLTFAccessorPostprocessed, number[]>();
20
+ const accessorCache2D = new Map<GLTFAccessorPostprocessed, number[][]>();
21
+
22
+ return gltfAnimations.flatMap((animation, index) => {
17
23
  const name = animation.name || `Animation-${index}`;
18
- const samplers: GLTFAnimationSampler[] = animation.samplers.map(
19
- ({input, interpolation = 'LINEAR', output}) => ({
20
- input: accessorToJsArray(gltf.accessors[input]) as number[],
21
- interpolation,
22
- output: accessorToJsArray(gltf.accessors[output])
23
- })
24
- );
25
- const channels: GLTFAnimationChannel[] = animation.channels.map(({sampler, target}) => ({
26
- sampler: samplers[sampler],
27
- target: gltf.nodes[target.node ?? 0],
28
- path: target.path as GLTFAnimationChannel['path']
29
- }));
30
- return {name, channels};
24
+ const samplerCache = new Map<number, GLTFAnimationSampler>();
25
+ const channels: GLTFAnimationChannel[] = animation.channels.flatMap(({sampler, target}) => {
26
+ const path = getSupportedAnimationPath(target.path);
27
+ if (!path) {
28
+ return [];
29
+ }
30
+
31
+ const targetNode = gltf.nodes[target.node ?? 0];
32
+ if (!targetNode) {
33
+ throw new Error(`Cannot find animation target ${target.node}`);
34
+ }
35
+
36
+ let parsedSampler = samplerCache.get(sampler);
37
+ if (!parsedSampler) {
38
+ const gltfSampler = animation.samplers[sampler];
39
+ if (!gltfSampler) {
40
+ throw new Error(`Cannot find animation sampler ${sampler}`);
41
+ }
42
+ const {input, interpolation = 'LINEAR', output} = gltfSampler;
43
+ parsedSampler = {
44
+ input: accessorToJsArray1D(gltf.accessors[input], accessorCache1D),
45
+ interpolation,
46
+ output: accessorToJsArray2D(gltf.accessors[output], accessorCache2D)
47
+ };
48
+ samplerCache.set(sampler, parsedSampler);
49
+ }
50
+
51
+ return {
52
+ sampler: parsedSampler,
53
+ targetNodeId: targetNode.id,
54
+ path
55
+ };
56
+ });
57
+
58
+ return channels.length ? [{name, channels}] : [];
31
59
  });
32
60
  }
33
61
 
34
- //
35
-
36
- function accessorToJsArray(
37
- accessor: GLTFAccessorPostprocessed & {_animation?: number[] | number[][]}
38
- ): number[] | number[][] {
39
- if (!accessor._animation) {
40
- const {typedArray: array, components} = accessorToTypedArray(accessor);
41
-
42
- if (components === 1) {
43
- accessor._animation = Array.from(array);
44
- } else {
45
- // Slice array
46
- const slicedArray: number[][] = [];
47
- for (let i = 0; i < array.length; i += components) {
48
- slicedArray.push(Array.from(array.slice(i, i + components)));
49
- }
50
- accessor._animation = slicedArray;
51
- }
62
+ function getSupportedAnimationPath(path: string): GLTFAnimationPath | null {
63
+ if (path === 'pointer') {
64
+ log.warn('KHR_animation_pointer channels are not supported and will be skipped')();
65
+ return null;
66
+ }
67
+
68
+ return path as GLTFAnimationPath;
69
+ }
70
+
71
+ /** Converts a scalar accessor into a cached JavaScript number array. */
72
+ function accessorToJsArray1D(
73
+ accessor: GLTFAccessorPostprocessed,
74
+ accessorCache: Map<GLTFAccessorPostprocessed, number[]>
75
+ ): number[] {
76
+ if (accessorCache.has(accessor)) {
77
+ return accessorCache.get(accessor)!;
52
78
  }
53
79
 
54
- return accessor._animation;
80
+ const {typedArray: array, components} = accessorToTypedArray(accessor);
81
+ assert(components === 1, 'accessorToJsArray1D must have exactly 1 component');
82
+ const result = Array.from(array);
83
+
84
+ accessorCache.set(accessor, result);
85
+ return result;
86
+ }
87
+
88
+ /** Converts a scalar, vector, or matrix accessor into a cached JavaScript array-of-arrays. */
89
+ function accessorToJsArray2D(
90
+ accessor: GLTFAccessorPostprocessed,
91
+ accessorCache: Map<GLTFAccessorPostprocessed, number[][]>
92
+ ): number[][] {
93
+ if (accessorCache.has(accessor)) {
94
+ return accessorCache.get(accessor)!;
95
+ }
96
+
97
+ const {typedArray: array, components} = accessorToTypedArray(accessor);
98
+ assert(components >= 1, 'accessorToJsArray2D must have at least 1 component');
99
+
100
+ const result = [];
101
+
102
+ // Slice array
103
+ for (let i = 0; i < array.length; i += components) {
104
+ result.push(Array.from(array.slice(i, i + components)));
105
+ }
106
+
107
+ accessorCache.set(accessor, result);
108
+ return result;
109
+ }
110
+
111
+ /** Throws when the supplied condition is false. */
112
+ function assert(condition: boolean, message?: string): asserts condition {
113
+ if (!condition) {
114
+ throw new Error(message);
115
+ }
55
116
  }