@needle-tools/three-animation-pointer 1.1.0 → 1.1.1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,9 @@ All notable changes to this package will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.1.1] - 2026-06-12
8
+ - Fix: morph target (blend shape) weight animation was silently dropped for multi-material meshes without skin/bones. The multi-material morph branch only bound children that were `SkinnedMesh`; such meshes load as a `Group` of plain `Mesh` children, so no track was created (and the empty-but-truthy result skipped the fallback). Now any child with `morphTargetInfluences` is bound.
9
+
7
10
  ## [1.1.0] - 2026-06-01
8
11
  - Add: support for `/nodes/{}/weights/{}` to animate individual morph target weights (works with multi-material meshes / primitives) — by @0b5vr in [#1](https://github.com/needle-tools/three-animation-pointer/pull/1)
9
12
 
package/README.md CHANGED
@@ -6,6 +6,7 @@ This package adds a [three.js](https://github.com/mrdoob/three.js) GLTFLoader pl
6
6
  - all material properties 💎 (including texture transforms)
7
7
  - cameras 🎥
8
8
  - lights 💡
9
+ - morph target weights 😀 (all weights or individual ones via `/nodes/{}/weights/{}`)
9
10
  - custom code/scripts 📄
10
11
 
11
12
 
@@ -1,17 +1,17 @@
1
- import { SkinnedMesh as S, InterpolateDiscrete as w, InterpolateLinear as F, AnimationClip as M, PropertyBinding as C, QuaternionKeyframeTrack as W, ColorKeyframeTrack as B, VectorKeyframeTrack as D, BooleanKeyframeTrack as E, NumberKeyframeTrack as O } from "three";
1
+ import { InterpolateDiscrete as w, InterpolateLinear as F, AnimationClip as S, PropertyBinding as C, QuaternionKeyframeTrack as M, ColorKeyframeTrack as W, VectorKeyframeTrack as B, BooleanKeyframeTrack as D, NumberKeyframeTrack as E } from "three";
2
2
  const d = {
3
3
  node: "node",
4
4
  material: "material",
5
5
  camera: "camera",
6
6
  light: "light"
7
- }, y = "KHR_animation_pointer", U = {
7
+ }, y = "KHR_animation_pointer", O = {
8
8
  // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each
9
9
  // keyframe track will be initialized with a default interpolation type, then modified.
10
10
  CUBICSPLINE: void 0,
11
11
  LINEAR: F,
12
12
  STEP: w
13
13
  };
14
- class G {
14
+ class z {
15
15
  /** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
16
16
  constructor(a) {
17
17
  this.name = y, this.parser = a, this.animationPointerResolver = null;
@@ -29,10 +29,10 @@ class G {
29
29
  }
30
30
  loadAnimationTargetFromChannelWithAnimationPointer(a) {
31
31
  var m;
32
- j();
32
+ L();
33
33
  const t = a.target, r = t.extensions && t.extensions[y] && t.path && t.path === "pointer";
34
34
  if (!r) return null;
35
- let e, n = d.node, s;
35
+ let e, s = d.node, n;
36
36
  if (r) {
37
37
  const h = t.extensions[y];
38
38
  let o = h.pointer;
@@ -40,13 +40,13 @@ class G {
40
40
  console.warn("Invalid path", h, t);
41
41
  return;
42
42
  }
43
- if (o.startsWith("/materials/") ? n = d.material : o.startsWith("/extensions/KHR_lights_punctual/lights/") ? n = d.light : o.startsWith("/cameras/") && (n = d.camera), s = this._tryResolveTargetId(o, n), s === null || isNaN(s)) {
44
- console.warn("Failed resolving animation node id: " + s, o);
43
+ if (o.startsWith("/materials/") ? s = d.material : o.startsWith("/extensions/KHR_lights_punctual/lights/") ? s = d.light : o.startsWith("/cameras/") && (s = d.camera), n = this._tryResolveTargetId(o, s), n === null || isNaN(n)) {
44
+ console.warn("Failed resolving animation node id: " + n, o);
45
45
  return;
46
46
  }
47
- switch (n) {
47
+ switch (s) {
48
48
  case d.material:
49
- const T = ("/materials/" + s.toString() + "/").length, g = o.substring(0, T);
49
+ const T = ("/materials/" + n.toString() + "/").length, g = o.substring(0, T);
50
50
  switch (e = o.substring(T), e) {
51
51
  case "pbrMetallicRoughness/baseColorFactor":
52
52
  e = "color";
@@ -133,7 +133,7 @@ class G {
133
133
  o = g + e;
134
134
  break;
135
135
  case d.node:
136
- const _ = ("/nodes/" + s.toString() + "/").length, b = o.substring(0, _);
136
+ const _ = ("/nodes/" + n.toString() + "/").length, b = o.substring(0, _);
137
137
  if (e = o.substring(_), e.startsWith("weights/")) {
138
138
  const f = e.substring(8);
139
139
  /^\d+$/.test(f) ? e = "morphTargetInfluences[" + f + "]" : console.warn(y + ": invalid weight index", e, o);
@@ -158,7 +158,7 @@ class G {
158
158
  o = b + e;
159
159
  break;
160
160
  case d.light:
161
- const u = ("/extensions/KHR_lights_punctual/lights/" + s.toString() + "/").length;
161
+ const u = ("/extensions/KHR_lights_punctual/lights/" + n.toString() + "/").length;
162
162
  switch (e = o.substring(u), e) {
163
163
  case "color":
164
164
  break;
@@ -174,10 +174,10 @@ class G {
174
174
  e = "distance";
175
175
  break;
176
176
  }
177
- o = "/lights/" + s.toString() + "/" + e;
177
+ o = "/lights/" + n.toString() + "/" + e;
178
178
  break;
179
179
  case d.camera:
180
- const l = ("/cameras/" + s.toString() + "/").length, k = o.substring(0, l);
180
+ const l = ("/cameras/" + n.toString() + "/").length, k = o.substring(0, l);
181
181
  switch (e = o.substring(l), e) {
182
182
  case "perspective/yfov":
183
183
  e = "fov";
@@ -205,17 +205,17 @@ class G {
205
205
  }
206
206
  (m = this.animationPointerResolver) != null && m.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[y].pointer = o;
207
207
  }
208
- if (s == null || isNaN(s)) {
209
- console.warn("Failed resolving animation node id: " + s, t);
208
+ if (n == null || isNaN(n)) {
209
+ console.warn("Failed resolving animation node id: " + n, t);
210
210
  return;
211
211
  }
212
212
  let i;
213
- return n === d.node ? i = this.parser.getDependency("node", s) : n === d.material ? i = this.parser.getDependency("material", s) : n === d.light ? i = this.parser.getDependency("light", s) : n === d.camera ? i = this.parser.getDependency("camera", s) : console.error("Unhandled type", n), i;
213
+ return s === d.node ? i = this.parser.getDependency("node", n) : s === d.material ? i = this.parser.getDependency("material", n) : s === d.light ? i = this.parser.getDependency("light", n) : s === d.camera ? i = this.parser.getDependency("camera", n) : console.error("Unhandled type", s), i;
214
214
  }
215
- createAnimationTracksWithAnimationPointer(a, t, r, e, n) {
215
+ createAnimationTracksWithAnimationPointer(a, t, r, e, s) {
216
216
  var b;
217
- if (!(n.extensions && n.extensions[y] && n.path && n.path === "pointer")) return null;
218
- let i = n.extensions[y].pointer;
217
+ if (!(s.extensions && s.extensions[y] && s.path && s.path === "pointer")) return null;
218
+ let i = s.extensions[y].pointer;
219
219
  if (!i) return null;
220
220
  const m = [];
221
221
  i = i.replaceAll("/", ".");
@@ -225,7 +225,7 @@ class G {
225
225
  const u = h[3];
226
226
  if (a.type === "Group") {
227
227
  for (const l of a.children)
228
- l instanceof S && l.morphTargetInfluences && (h[3] = l.name, h[4] = u, _(this.parser));
228
+ l.morphTargetInfluences && (h[3] = l.name, h[4] = u, _(this.parser));
229
229
  return m;
230
230
  }
231
231
  }
@@ -244,21 +244,21 @@ class G {
244
244
  switch (r.itemSize) {
245
245
  case 1:
246
246
  Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
247
- g(a, i) ? (l = E, k = !0) : l = O;
247
+ g(a, i) ? (l = D, k = !0) : l = E;
248
248
  break;
249
249
  case 2:
250
250
  case 3:
251
- l = D;
251
+ l = B;
252
252
  break;
253
253
  case 4:
254
- i.endsWith(".quaternion") ? l = W : l = B;
254
+ i.endsWith(".quaternion") ? l = M : l = W;
255
255
  break;
256
256
  }
257
257
  if (!l) {
258
258
  console.warn("Unsupported output accessor format", r);
259
259
  return;
260
260
  }
261
- const f = e.interpolation !== void 0 ? U[e.interpolation] : F;
261
+ const f = e.interpolation !== void 0 ? O[e.interpolation] : F;
262
262
  let c = u._getArrayFromAccessor(r);
263
263
  i.endsWith(".fov") && (c = c.map((p) => p / Math.PI * 180)), k && (c = c.map((p) => p > 0));
264
264
  const R = new l(
@@ -293,9 +293,9 @@ class G {
293
293
  * @return {Promise<AnimationClip>}
294
294
  */
295
295
  loadAnimation(a) {
296
- const t = this, r = this.parser.json, e = this.parser, n = r.animations[a], s = n.name ? n.name : "animation_" + a, i = [], m = [], h = [], o = [], T = [];
297
- for (let g = 0, _ = n.channels.length; g < _; g++) {
298
- const b = n.channels[g], u = n.samplers[b.sampler], l = b.target, k = n.parameters !== void 0 ? n.parameters[u.input] : u.input, f = n.parameters !== void 0 ? n.parameters[u.output] : u.output;
296
+ const t = this, r = this.parser.json, e = this.parser, s = r.animations[a], n = s.name ? s.name : "animation_" + a, i = [], m = [], h = [], o = [], T = [];
297
+ for (let g = 0, _ = s.channels.length; g < _; g++) {
298
+ const b = s.channels[g], u = s.samplers[b.sampler], l = b.target, k = s.parameters !== void 0 ? s.parameters[u.input] : u.input, f = s.parameters !== void 0 ? s.parameters[u.output] : u.output;
299
299
  let c = t.loadAnimationTargetFromChannelWithAnimationPointer(b);
300
300
  c || (c = t.loadAnimationTargetFromChannel(b)), i.push(c), m.push(e.getDependency("accessor", k)), h.push(e.getDependency("accessor", f)), o.push(u), T.push(l);
301
301
  }
@@ -316,42 +316,42 @@ class G {
316
316
  for (let K = 0; K < I.length; K++)
317
317
  f.push(I[K]);
318
318
  }
319
- return new M(s, void 0, f);
319
+ return new S(n, void 0, f);
320
320
  });
321
321
  }
322
322
  }
323
- let H = !1, L = null;
324
- function j() {
323
+ let H = !1, U = null;
324
+ function L() {
325
325
  if (H) return;
326
326
  H = !0;
327
- const P = L || (L = C.findNode);
327
+ const P = U || (U = C.findNode);
328
328
  C.findNode = function(a, t) {
329
329
  if (!t) return P(a, t);
330
330
  if (t.startsWith(".materials.")) {
331
- const r = t.substring(11).substring(t.indexOf(".")), e = r.indexOf("."), n = e < 0 ? r : r.substring(0, e);
332
- let s = null;
331
+ const r = t.substring(11).substring(t.indexOf(".")), e = r.indexOf("."), s = e < 0 ? r : r.substring(0, e);
332
+ let n = null;
333
333
  return a.traverse((i) => {
334
- s !== null || i.type !== "Mesh" && i.type !== "SkinnedMesh" || i.material && (i.material.uuid === n || i.material.name === n) && (s = i.material, s !== null && (r.endsWith(".map") ? s = s.map : r.endsWith(".emissiveMap") && (s = s.emissiveMap)));
335
- }), s;
334
+ n !== null || i.type !== "Mesh" && i.type !== "SkinnedMesh" || i.material && (i.material.uuid === s || i.material.name === s) && (n = i.material, n !== null && (r.endsWith(".map") ? n = n.map : r.endsWith(".emissiveMap") && (n = n.emissiveMap)));
335
+ }), n;
336
336
  } else if (t.startsWith(".nodes.") || t.startsWith(".lights.") || t.startsWith(".cameras.")) {
337
337
  const r = t.split(".");
338
338
  let e;
339
- for (let n = 1; n < r.length; n++) {
340
- const s = r[n];
341
- if (s.length == 36)
342
- e = a.getObjectByProperty("uuid", s);
343
- else if (e && e[s]) {
344
- const m = Number.parseInt(s);
345
- let h = s;
339
+ for (let s = 1; s < r.length; s++) {
340
+ const n = r[s];
341
+ if (n.length == 36)
342
+ e = a.getObjectByProperty("uuid", n);
343
+ else if (e && e[n]) {
344
+ const m = Number.parseInt(n);
345
+ let h = n;
346
346
  m >= 0 && (h = m), e = e[h];
347
347
  } else {
348
- const m = a.getObjectByName(s);
348
+ const m = a.getObjectByName(n);
349
349
  m && (e = m);
350
350
  }
351
351
  }
352
352
  if (!e) {
353
- const n = P(a, r[2]);
354
- return n || console.warn(y + ": Property binding not found", t, a, a.name, r), n;
353
+ const s = P(a, r[2]);
354
+ return s || console.warn(y + ": Property binding not found", t, a, a.name, r), s;
355
355
  }
356
356
  return e;
357
357
  }
@@ -359,6 +359,6 @@ function j() {
359
359
  };
360
360
  }
361
361
  export {
362
- G as GLTFAnimationPointerExtension,
363
- G as default
362
+ z as GLTFAnimationPointerExtension,
363
+ z as default
364
364
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/three-animation-pointer",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "KHR_animation_pointer support for three.js",
5
5
  "type": "module",
6
6
  "main": "dist/three-animation-pointer.js",
@@ -46,4 +46,4 @@
46
46
  "registry": "https://registry.npmjs.org/",
47
47
  "access": "public"
48
48
  }
49
- }
49
+ }
@@ -7,7 +7,6 @@ import {
7
7
  PropertyBinding,
8
8
  QuaternionKeyframeTrack,
9
9
  VectorKeyframeTrack,
10
- SkinnedMesh,
11
10
  BooleanKeyframeTrack
12
11
  } from 'three';
13
12
 
@@ -408,12 +407,13 @@ export class GLTFAnimationPointerExtension {
408
407
  if ( node.type === 'Group' ) {
409
408
 
410
409
  if ( _animationPointerDebug )
411
- console.log( 'Detected multi-material skinnedMesh export', animationPointerPropertyPath, node );
410
+ console.log( 'Detected multi-material mesh export', animationPointerPropertyPath, node );
412
411
 
413
- // We assume the children are skinned meshes
412
+ // Bind any child that has morph influences. The children may be plain
413
+ // Mesh (no skin/bones) or SkinnedMesh, so we must not require SkinnedMesh.
414
414
  for ( const ch of node.children ) {
415
415
 
416
- if ( ch instanceof SkinnedMesh && ch.morphTargetInfluences ) {
416
+ if ( ch.morphTargetInfluences ) {
417
417
 
418
418
  parts[ 3 ] = ch.name;
419
419
  parts[ 4 ] = property;