@needle-tools/three-animation-pointer 1.1.2 → 1.1.3

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.3]
8
+ - Fix: morph target (blend shape) weight animation bound to the wrong object when a multi-material mesh is used by multiple nodes. three.js clones a shared mesh per node, so every node's `Group` has identically named submeshes; the submesh segment of `.nodes.<node>.<submesh>.morphTargetInfluences` was looked up from the mixer root instead of inside `<node>`, so all tracks bound to the first node's submeshes. Child names are now resolved within the already resolved node.
9
+
7
10
  ## [1.1.2] - 2026-06-17
8
11
  - Fix: don't mutate the `KHR_animation_pointer.pointer` value in `parser.json`. The resolved three.js property path is now kept in plugin-local state, so other plugins / app code can still read the original spec-defined pointer after load — by @0b5vr in [#2](https://github.com/needle-tools/three-animation-pointer/pull/2)
9
12
 
@@ -1,50 +1,50 @@
1
- import { InterpolateDiscrete as M, InterpolateLinear as F, AnimationClip as w, PropertyBinding as C, QuaternionKeyframeTrack as S, ColorKeyframeTrack as W, VectorKeyframeTrack as B, BooleanKeyframeTrack as D, NumberKeyframeTrack as E } from "three";
1
+ import { InterpolateDiscrete as M, InterpolateLinear as F, AnimationClip as w, PropertyBinding as C, QuaternionKeyframeTrack as S, ColorKeyframeTrack as W, VectorKeyframeTrack as B, BooleanKeyframeTrack as D, NumberKeyframeTrack as O } from "three";
2
2
  const k = {
3
3
  node: "node",
4
4
  material: "material",
5
5
  camera: "camera",
6
6
  light: "light"
7
- }, R = "KHR_animation_pointer", O = {
7
+ }, R = "KHR_animation_pointer", E = {
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: M
13
13
  };
14
- class z {
14
+ class G {
15
15
  /** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
16
- constructor(a) {
17
- this.name = R, this.parser = a, this.animationPointerResolver = null, this.pointerPathMap = /* @__PURE__ */ new Map();
16
+ constructor(r) {
17
+ this.name = R, this.parser = r, this.animationPointerResolver = null, this.pointerPathMap = /* @__PURE__ */ new Map();
18
18
  }
19
19
  /**
20
20
  * @param {import("..").AnimationPointerResolver | null} animationPointerResolver
21
21
  */
22
- setAnimationPointerResolver(a) {
23
- return this.animationPointerResolver = a, this;
22
+ setAnimationPointerResolver(r) {
23
+ return this.animationPointerResolver = r, this;
24
24
  }
25
25
  /* DUPLICATE of functionality in GLTFLoader */
26
- loadAnimationTargetFromChannel(a) {
27
- const s = a.target, r = s.node !== void 0 ? s.node : s.id;
28
- return this.parser.getDependency("node", r);
26
+ loadAnimationTargetFromChannel(r) {
27
+ const t = r.target, a = t.node !== void 0 ? t.node : t.id;
28
+ return this.parser.getDependency("node", a);
29
29
  }
30
- loadAnimationTargetFromChannelWithAnimationPointer(a) {
30
+ loadAnimationTargetFromChannelWithAnimationPointer(r) {
31
31
  var o;
32
32
  L();
33
- const s = a.target, r = s.extensions && s.extensions[R] && s.path && s.path === "pointer";
34
- if (!r) return null;
35
- let e, t = k.node, n;
36
- if (r) {
37
- const m = s.extensions[R];
33
+ const t = r.target, a = t.extensions && t.extensions[R] && t.path && t.path === "pointer";
34
+ if (!a) return null;
35
+ let e, s = k.node, n;
36
+ if (a) {
37
+ const m = t.extensions[R];
38
38
  let i = m.pointer;
39
39
  if (!i) {
40
- console.warn("Invalid path", m, s);
40
+ console.warn("Invalid path", m, t);
41
41
  return;
42
42
  }
43
- if (i.startsWith("/materials/") ? t = k.material : i.startsWith("/extensions/KHR_lights_punctual/lights/") ? t = k.light : i.startsWith("/cameras/") && (t = k.camera), n = this._tryResolveTargetId(i, t), n === null || isNaN(n)) {
43
+ if (i.startsWith("/materials/") ? s = k.material : i.startsWith("/extensions/KHR_lights_punctual/lights/") ? s = k.light : i.startsWith("/cameras/") && (s = k.camera), n = this._tryResolveTargetId(i, s), n === null || isNaN(n)) {
44
44
  console.warn("Failed resolving animation node id: " + n, i);
45
45
  return;
46
46
  }
47
- switch (t) {
47
+ switch (s) {
48
48
  case k.material:
49
49
  const h = ("/materials/" + n.toString() + "/").length, y = i.substring(0, h);
50
50
  switch (e = i.substring(h), e) {
@@ -133,8 +133,8 @@ class z {
133
133
  i = y + e;
134
134
  break;
135
135
  case k.node:
136
- const f = ("/nodes/" + n.toString() + "/").length, d = i.substring(0, f);
137
- if (e = i.substring(f), e.startsWith("weights/")) {
136
+ const g = ("/nodes/" + n.toString() + "/").length, d = i.substring(0, g);
137
+ if (e = i.substring(g), e.startsWith("weights/")) {
138
138
  const l = e.substring(8);
139
139
  /^\d+$/.test(l) ? e = "morphTargetInfluences[" + l + "]" : console.warn(R + ": invalid weight index", e, i);
140
140
  } else
@@ -204,50 +204,50 @@ class z {
204
204
  break;
205
205
  }
206
206
  (o = this.animationPointerResolver) != null && o.resolvePath && (i = this.animationPointerResolver.resolvePath(i));
207
- const v = m.pointer;
208
- this.pointerPathMap.set(v, i);
207
+ const I = m.pointer;
208
+ this.pointerPathMap.set(I, i);
209
209
  }
210
210
  if (n == null || isNaN(n)) {
211
- console.warn("Failed resolving animation node id: " + n, s);
211
+ console.warn("Failed resolving animation node id: " + n, t);
212
212
  return;
213
213
  }
214
214
  let u;
215
- return t === k.node ? u = this.parser.getDependency("node", n) : t === k.material ? u = this.parser.getDependency("material", n) : t === k.light ? u = this.parser.getDependency("light", n) : t === k.camera ? u = this.parser.getDependency("camera", n) : console.error("Unhandled type", t), u;
215
+ return s === k.node ? u = this.parser.getDependency("node", n) : s === k.material ? u = this.parser.getDependency("material", n) : s === k.light ? u = this.parser.getDependency("light", n) : s === k.camera ? u = this.parser.getDependency("camera", n) : console.error("Unhandled type", s), u;
216
216
  }
217
- createAnimationTracksWithAnimationPointer(a, s, r, e, t) {
217
+ createAnimationTracksWithAnimationPointer(r, t, a, e, s) {
218
218
  var d;
219
- if (!(t.extensions && t.extensions[R] && t.path && t.path === "pointer")) return null;
220
- const u = t.extensions[R].pointer;
219
+ if (!(s.extensions && s.extensions[R] && s.path && s.path === "pointer")) return null;
220
+ const u = s.extensions[R].pointer;
221
221
  let o = this.pointerPathMap.get(u);
222
222
  if (!o) return null;
223
223
  const m = [];
224
224
  o = o.replaceAll("/", ".");
225
225
  const i = o.split(".");
226
- var h = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
226
+ var h = r.name !== void 0 && r.name !== null ? r.name : r.uuid;
227
227
  if (i[2] = h, (d = i[3]) != null && d.startsWith("morphTargetInfluences")) {
228
228
  const p = i[3];
229
- if (a.type === "Group") {
230
- for (const c of a.children)
231
- c.morphTargetInfluences && (i[3] = c.name, i[4] = p, f(this.parser));
229
+ if (r.type === "Group") {
230
+ for (const c of r.children)
231
+ c.morphTargetInfluences && (i[3] = c.name, i[4] = p, g(this.parser));
232
232
  return m;
233
233
  }
234
234
  }
235
- f(this.parser);
235
+ g(this.parser);
236
236
  function y(p, c) {
237
237
  try {
238
- const b = c.split(".").filter(Boolean), g = b[b.length - 1].replace(/\[.*\]$/, "");
239
- return g in p ? typeof p[g] == "boolean" : !1;
238
+ const b = c.split(".").filter(Boolean), f = b[b.length - 1].replace(/\[.*\]$/, "");
239
+ return f in p ? typeof p[f] == "boolean" : !1;
240
240
  } catch {
241
241
  return !1;
242
242
  }
243
243
  }
244
- function f(p) {
244
+ function g(p) {
245
245
  o = i.join(".");
246
246
  let c, b = !1;
247
- switch (r.itemSize) {
247
+ switch (a.itemSize) {
248
248
  case 1:
249
- Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
250
- y(a, o) ? (c = D, b = !0) : c = E;
249
+ Object.prototype.toString.call(a.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
250
+ y(r, o) ? (c = D, b = !0) : c = O;
251
251
  break;
252
252
  case 2:
253
253
  case 3:
@@ -258,36 +258,36 @@ class z {
258
258
  break;
259
259
  }
260
260
  if (!c) {
261
- console.warn("Unsupported output accessor format", r);
261
+ console.warn("Unsupported output accessor format", a);
262
262
  return;
263
263
  }
264
- const l = e.interpolation !== void 0 ? O[e.interpolation] : F;
265
- let g = p._getArrayFromAccessor(r);
266
- o.endsWith(".fov") && (g = g.map((x) => x / Math.PI * 180)), b && (g = g.map((x) => x > 0));
264
+ const l = e.interpolation !== void 0 ? E[e.interpolation] : F;
265
+ let f = p._getArrayFromAccessor(a);
266
+ o.endsWith(".fov") && (f = f.map((x) => x / Math.PI * 180)), b && (f = f.map((x) => x > 0));
267
267
  const _ = new c(
268
268
  o,
269
- s.array,
270
- g,
269
+ t.array,
270
+ f,
271
271
  l
272
272
  );
273
- if (l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(_), o && r.itemSize === 4 && o.startsWith(".materials.") && o.endsWith(".color")) {
274
- const x = new Float32Array(g.length / 4);
275
- for (let T = 0, K = g.length / 4; T < K; T += 1)
276
- x[T] = g[T * 4 + 3];
277
- const I = new c(
273
+ if (l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(_), o && a.itemSize === 4 && o.startsWith(".materials.") && o.endsWith(".color")) {
274
+ const x = new Float32Array(f.length / 4);
275
+ for (let T = 0, A = f.length / 4; T < A; T += 1)
276
+ x[T] = f[T * 4 + 3];
277
+ const P = new c(
278
278
  o.replace(".color", ".opacity"),
279
- s.array,
279
+ t.array,
280
280
  x,
281
281
  l
282
282
  );
283
- l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(I);
283
+ l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(P);
284
284
  }
285
285
  }
286
286
  return m;
287
287
  }
288
- _tryResolveTargetId(a, s) {
289
- let r = "";
290
- return s === "node" ? r = a.substring(7) : s === "material" ? r = a.substring(11) : s === "light" ? r = a.substring(39) : s === "camera" && (r = a.substring(9)), r = r.substring(0, r.indexOf("/")), Number.parseInt(r);
288
+ _tryResolveTargetId(r, t) {
289
+ let a = "";
290
+ return t === "node" ? a = r.substring(7) : t === "material" ? a = r.substring(11) : t === "light" ? a = r.substring(39) : t === "camera" && (a = r.substring(9)), a = a.substring(0, a.indexOf("/")), Number.parseInt(a);
291
291
  }
292
292
  /* MOSTLY DUPLICATE of GLTFLoader.loadAnimation, but also tries to resolve KHR_animation_pointer. */
293
293
  /**
@@ -295,73 +295,82 @@ class z {
295
295
  * @param {number} animationIndex
296
296
  * @return {Promise<AnimationClip>}
297
297
  */
298
- loadAnimation(a) {
299
- const s = this, r = this.parser.json, e = this.parser, t = r.animations[a], n = t.name ? t.name : "animation_" + a, u = [], o = [], m = [], i = [], v = [];
300
- for (let h = 0, y = t.channels.length; h < y; h++) {
301
- const f = t.channels[h], d = t.samplers[f.sampler], p = f.target, c = t.parameters !== void 0 ? t.parameters[d.input] : d.input, b = t.parameters !== void 0 ? t.parameters[d.output] : d.output;
302
- let l = s.loadAnimationTargetFromChannelWithAnimationPointer(f);
303
- l || (l = s.loadAnimationTargetFromChannel(f)), u.push(l), o.push(e.getDependency("accessor", c)), m.push(e.getDependency("accessor", b)), i.push(d), v.push(p);
298
+ loadAnimation(r) {
299
+ const t = this, a = this.parser.json, e = this.parser, s = a.animations[r], n = s.name ? s.name : "animation_" + r, u = [], o = [], m = [], i = [], I = [];
300
+ for (let h = 0, y = s.channels.length; h < y; h++) {
301
+ const g = s.channels[h], d = s.samplers[g.sampler], p = g.target, c = s.parameters !== void 0 ? s.parameters[d.input] : d.input, b = s.parameters !== void 0 ? s.parameters[d.output] : d.output;
302
+ let l = t.loadAnimationTargetFromChannelWithAnimationPointer(g);
303
+ l || (l = t.loadAnimationTargetFromChannel(g)), u.push(l), o.push(e.getDependency("accessor", c)), m.push(e.getDependency("accessor", b)), i.push(d), I.push(p);
304
304
  }
305
305
  return Promise.all([
306
306
  Promise.all(u),
307
307
  Promise.all(o),
308
308
  Promise.all(m),
309
309
  Promise.all(i),
310
- Promise.all(v)
310
+ Promise.all(I)
311
311
  ]).then(function(h) {
312
- const y = h[0], f = h[1], d = h[2], p = h[3], c = h[4], b = [];
313
- for (let l = 0, g = y.length; l < g; l++) {
314
- const _ = y[l], x = f[l], I = d[l], T = p[l], K = c[l];
312
+ const y = h[0], g = h[1], d = h[2], p = h[3], c = h[4], b = [];
313
+ for (let l = 0, f = y.length; l < f; l++) {
314
+ const _ = y[l], x = g[l], P = d[l], T = p[l], A = c[l];
315
315
  if (_ === void 0) continue;
316
316
  _.updateMatrix && (_.updateMatrix(), _.matrixAutoUpdate = !0);
317
- let P = s.createAnimationTracksWithAnimationPointer(_, x, I, T, K);
318
- if (P || (P = e._createAnimationTracks(_, x, I, T, K)), P)
319
- for (let N = 0; N < P.length; N++)
320
- b.push(P[N]);
317
+ let N = t.createAnimationTracksWithAnimationPointer(_, x, P, T, A);
318
+ if (N || (N = e._createAnimationTracks(_, x, P, T, A)), N)
319
+ for (let K = 0; K < N.length; K++)
320
+ b.push(N[K]);
321
321
  }
322
322
  return new w(n, void 0, b);
323
323
  });
324
324
  }
325
325
  }
326
- let H = !1, U = null;
326
+ function U(v, r) {
327
+ for (const t of v.children) {
328
+ const a = t.getObjectByName(r);
329
+ if (a) return a;
330
+ }
331
+ }
332
+ let H = !1, j = null;
327
333
  function L() {
328
334
  if (H) return;
329
335
  H = !0;
330
- const A = U || (U = C.findNode);
331
- C.findNode = function(a, s) {
332
- if (!s) return A(a, s);
333
- if (s.startsWith(".materials.")) {
334
- const r = s.substring(11).substring(s.indexOf(".")), e = r.indexOf("."), t = e < 0 ? r : r.substring(0, e);
336
+ const v = j || (j = C.findNode);
337
+ C.findNode = function(r, t) {
338
+ if (!t) return v(r, t);
339
+ if (t.startsWith(".materials.")) {
340
+ const a = t.substring(11).substring(t.indexOf(".")), e = a.indexOf("."), s = e < 0 ? a : a.substring(0, e);
335
341
  let n = null;
336
- return a.traverse((u) => {
337
- n !== null || u.type !== "Mesh" && u.type !== "SkinnedMesh" || u.material && (u.material.uuid === t || u.material.name === t) && (n = u.material, n !== null && (r.endsWith(".map") ? n = n.map : r.endsWith(".emissiveMap") && (n = n.emissiveMap)));
342
+ return r.traverse((u) => {
343
+ n !== null || u.type !== "Mesh" && u.type !== "SkinnedMesh" || u.material && (u.material.uuid === s || u.material.name === s) && (n = u.material, n !== null && (a.endsWith(".map") ? n = n.map : a.endsWith(".emissiveMap") && (n = n.emissiveMap)));
338
344
  }), n;
339
- } else if (s.startsWith(".nodes.") || s.startsWith(".lights.") || s.startsWith(".cameras.")) {
340
- const r = s.split(".");
345
+ } else if (t.startsWith(".nodes.") || t.startsWith(".lights.") || t.startsWith(".cameras.")) {
346
+ const a = t.split(".");
341
347
  let e;
342
- for (let t = 1; t < r.length; t++) {
343
- const n = r[t];
348
+ for (let s = 1; s < a.length; s++) {
349
+ const n = a[s];
344
350
  if (n.length == 36)
345
- e = a.getObjectByProperty("uuid", n);
351
+ e = r.getObjectByProperty("uuid", n);
346
352
  else if (e && e[n]) {
347
353
  const o = Number.parseInt(n);
348
354
  let m = n;
349
355
  o >= 0 && (m = o), e = e[m];
356
+ } else if (e && e.isObject3D) {
357
+ const o = U(e, n);
358
+ o && (e = o);
350
359
  } else {
351
- const o = a.getObjectByName(n);
360
+ const o = r.getObjectByName(n);
352
361
  o && (e = o);
353
362
  }
354
363
  }
355
364
  if (!e) {
356
- const t = A(a, r[2]);
357
- return t || console.warn(R + ": Property binding not found", s, a, a.name, r), t;
365
+ const s = v(r, a[2]);
366
+ return s || console.warn(R + ": Property binding not found", t, r, r.name, a), s;
358
367
  }
359
368
  return e;
360
369
  }
361
- return A(a, s);
370
+ return v(r, t);
362
371
  };
363
372
  }
364
373
  export {
365
- z as GLTFAnimationPointerExtension,
366
- z as default
374
+ G as GLTFAnimationPointerExtension,
375
+ G as default
367
376
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/three-animation-pointer",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "KHR_animation_pointer support for three.js",
5
5
  "type": "module",
6
6
  "main": "dist/three-animation-pointer.js",
@@ -706,6 +706,25 @@ export class GLTFAnimationPointerExtension {
706
706
 
707
707
 
708
708
 
709
+ /**
710
+ * Like Object3D.getObjectByName, but does not match the object itself.
711
+ * @param {import("three").Object3D} parent
712
+ * @param {string} name
713
+ * @returns {import("three").Object3D | undefined}
714
+ */
715
+ function _findDescendantByName( parent, name ) {
716
+
717
+ for ( const child of parent.children ) {
718
+
719
+ const found = child.getObjectByName( name );
720
+ if ( found ) return found;
721
+
722
+ }
723
+
724
+ return undefined;
725
+
726
+ }
727
+
709
728
  let _havePatchedPropertyBindings = false;
710
729
 
711
730
  // HACK monkey patching findNode to ensure we can map to other types required by KHR_animation_pointer.
@@ -784,6 +803,17 @@ function _ensurePropertyBindingPatch() {
784
803
  if (_animationPointerDebug)
785
804
  console.log(currentTarget);
786
805
 
806
+ } else if (currentTarget && currentTarget.isObject3D) {
807
+
808
+ // access by child node name, e.g. the submeshes of a multi-material mesh:
809
+ // `.nodes.<node>.<submesh>.morphTargetInfluences`.
810
+ // Must resolve inside the already resolved node: a glTF mesh used by multiple nodes
811
+ // is cloned per node, so every node has identically named submeshes.
812
+ const foundNode = _findDescendantByName(currentTarget, val);
813
+
814
+ if (foundNode)
815
+ currentTarget = foundNode;
816
+
787
817
  } else {
788
818
 
789
819
  // access by node name