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

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,12 @@ 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.2] - 2026-06-17
8
+ - 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
+
10
+ ## [1.1.1] - 2026-06-12
11
+ - 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.
12
+
7
13
  ## [1.1.0] - 2026-06-01
8
14
  - 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
15
 
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,20 +1,20 @@
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";
2
- const d = {
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";
2
+ const k = {
3
3
  node: "node",
4
4
  material: "material",
5
5
  camera: "camera",
6
6
  light: "light"
7
- }, y = "KHR_animation_pointer", U = {
7
+ }, R = "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
- STEP: w
12
+ STEP: M
13
13
  };
14
- class G {
14
+ class z {
15
15
  /** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
16
16
  constructor(a) {
17
- this.name = y, this.parser = a, this.animationPointerResolver = null;
17
+ this.name = R, this.parser = a, this.animationPointerResolver = null, this.pointerPathMap = /* @__PURE__ */ new Map();
18
18
  }
19
19
  /**
20
20
  * @param {import("..").AnimationPointerResolver | null} animationPointerResolver
@@ -24,30 +24,30 @@ class G {
24
24
  }
25
25
  /* DUPLICATE of functionality in GLTFLoader */
26
26
  loadAnimationTargetFromChannel(a) {
27
- const t = a.target, r = t.node !== void 0 ? t.node : t.id;
27
+ const s = a.target, r = s.node !== void 0 ? s.node : s.id;
28
28
  return this.parser.getDependency("node", r);
29
29
  }
30
30
  loadAnimationTargetFromChannelWithAnimationPointer(a) {
31
- var m;
32
- j();
33
- const t = a.target, r = t.extensions && t.extensions[y] && t.path && t.path === "pointer";
31
+ var o;
32
+ L();
33
+ const s = a.target, r = s.extensions && s.extensions[R] && s.path && s.path === "pointer";
34
34
  if (!r) return null;
35
- let e, n = d.node, s;
35
+ let e, t = k.node, n;
36
36
  if (r) {
37
- const h = t.extensions[y];
38
- let o = h.pointer;
39
- if (!o) {
40
- console.warn("Invalid path", h, t);
37
+ const m = s.extensions[R];
38
+ let i = m.pointer;
39
+ if (!i) {
40
+ console.warn("Invalid path", m, s);
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 (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)) {
44
+ console.warn("Failed resolving animation node id: " + n, i);
45
45
  return;
46
46
  }
47
- switch (n) {
48
- case d.material:
49
- const T = ("/materials/" + s.toString() + "/").length, g = o.substring(0, T);
50
- switch (e = o.substring(T), e) {
47
+ switch (t) {
48
+ case k.material:
49
+ const h = ("/materials/" + n.toString() + "/").length, y = i.substring(0, h);
50
+ switch (e = i.substring(h), e) {
51
51
  case "pbrMetallicRoughness/baseColorFactor":
52
52
  e = "color";
53
53
  break;
@@ -130,13 +130,13 @@ class G {
130
130
  e = "specularColor";
131
131
  break;
132
132
  }
133
- o = g + e;
133
+ i = y + e;
134
134
  break;
135
- case d.node:
136
- const _ = ("/nodes/" + s.toString() + "/").length, b = o.substring(0, _);
137
- if (e = o.substring(_), e.startsWith("weights/")) {
138
- const f = e.substring(8);
139
- /^\d+$/.test(f) ? e = "morphTargetInfluences[" + f + "]" : console.warn(y + ": invalid weight index", e, o);
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/")) {
138
+ const l = e.substring(8);
139
+ /^\d+$/.test(l) ? e = "morphTargetInfluences[" + l + "]" : console.warn(R + ": invalid weight index", e, i);
140
140
  } else
141
141
  switch (e) {
142
142
  case "translation":
@@ -155,11 +155,11 @@ class G {
155
155
  e = "visible";
156
156
  break;
157
157
  }
158
- o = b + e;
158
+ i = d + e;
159
159
  break;
160
- case d.light:
161
- const u = ("/extensions/KHR_lights_punctual/lights/" + s.toString() + "/").length;
162
- switch (e = o.substring(u), e) {
160
+ case k.light:
161
+ const p = ("/extensions/KHR_lights_punctual/lights/" + n.toString() + "/").length;
162
+ switch (e = i.substring(p), e) {
163
163
  case "color":
164
164
  break;
165
165
  case "intensity":
@@ -174,11 +174,11 @@ class G {
174
174
  e = "distance";
175
175
  break;
176
176
  }
177
- o = "/lights/" + s.toString() + "/" + e;
177
+ i = "/lights/" + n.toString() + "/" + e;
178
178
  break;
179
- case d.camera:
180
- const l = ("/cameras/" + s.toString() + "/").length, k = o.substring(0, l);
181
- switch (e = o.substring(l), e) {
179
+ case k.camera:
180
+ const c = ("/cameras/" + n.toString() + "/").length, b = i.substring(0, c);
181
+ switch (e = i.substring(c), e) {
182
182
  case "perspective/yfov":
183
183
  e = "fov";
184
184
  break;
@@ -200,91 +200,94 @@ class G {
200
200
  e = "zoom";
201
201
  break;
202
202
  }
203
- o = k + e;
203
+ i = b + e;
204
204
  break;
205
205
  }
206
- (m = this.animationPointerResolver) != null && m.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[y].pointer = o;
206
+ (o = this.animationPointerResolver) != null && o.resolvePath && (i = this.animationPointerResolver.resolvePath(i));
207
+ const v = m.pointer;
208
+ this.pointerPathMap.set(v, i);
207
209
  }
208
- if (s == null || isNaN(s)) {
209
- console.warn("Failed resolving animation node id: " + s, t);
210
+ if (n == null || isNaN(n)) {
211
+ console.warn("Failed resolving animation node id: " + n, s);
210
212
  return;
211
213
  }
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;
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;
214
216
  }
215
- createAnimationTracksWithAnimationPointer(a, t, r, e, n) {
216
- var b;
217
- if (!(n.extensions && n.extensions[y] && n.path && n.path === "pointer")) return null;
218
- let i = n.extensions[y].pointer;
219
- if (!i) return null;
217
+ createAnimationTracksWithAnimationPointer(a, s, r, e, t) {
218
+ var d;
219
+ if (!(t.extensions && t.extensions[R] && t.path && t.path === "pointer")) return null;
220
+ const u = t.extensions[R].pointer;
221
+ let o = this.pointerPathMap.get(u);
222
+ if (!o) return null;
220
223
  const m = [];
221
- i = i.replaceAll("/", ".");
222
- const h = i.split(".");
223
- var T = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
224
- if (h[2] = T, (b = h[3]) != null && b.startsWith("morphTargetInfluences")) {
225
- const u = h[3];
224
+ o = o.replaceAll("/", ".");
225
+ const i = o.split(".");
226
+ var h = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
227
+ if (i[2] = h, (d = i[3]) != null && d.startsWith("morphTargetInfluences")) {
228
+ const p = i[3];
226
229
  if (a.type === "Group") {
227
- for (const l of a.children)
228
- l instanceof S && l.morphTargetInfluences && (h[3] = l.name, h[4] = u, _(this.parser));
230
+ for (const c of a.children)
231
+ c.morphTargetInfluences && (i[3] = c.name, i[4] = p, f(this.parser));
229
232
  return m;
230
233
  }
231
234
  }
232
- _(this.parser);
233
- function g(u, l) {
235
+ f(this.parser);
236
+ function y(p, c) {
234
237
  try {
235
- const k = l.split(".").filter(Boolean), c = k[k.length - 1].replace(/\[.*\]$/, "");
236
- return c in u ? typeof u[c] == "boolean" : !1;
238
+ const b = c.split(".").filter(Boolean), g = b[b.length - 1].replace(/\[.*\]$/, "");
239
+ return g in p ? typeof p[g] == "boolean" : !1;
237
240
  } catch {
238
241
  return !1;
239
242
  }
240
243
  }
241
- function _(u) {
242
- i = h.join(".");
243
- let l, k = !1;
244
+ function f(p) {
245
+ o = i.join(".");
246
+ let c, b = !1;
244
247
  switch (r.itemSize) {
245
248
  case 1:
246
249
  Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
247
- g(a, i) ? (l = E, k = !0) : l = O;
250
+ y(a, o) ? (c = D, b = !0) : c = E;
248
251
  break;
249
252
  case 2:
250
253
  case 3:
251
- l = D;
254
+ c = B;
252
255
  break;
253
256
  case 4:
254
- i.endsWith(".quaternion") ? l = W : l = B;
257
+ o.endsWith(".quaternion") ? c = S : c = W;
255
258
  break;
256
259
  }
257
- if (!l) {
260
+ if (!c) {
258
261
  console.warn("Unsupported output accessor format", r);
259
262
  return;
260
263
  }
261
- const f = e.interpolation !== void 0 ? U[e.interpolation] : F;
262
- let c = u._getArrayFromAccessor(r);
263
- i.endsWith(".fov") && (c = c.map((p) => p / Math.PI * 180)), k && (c = c.map((p) => p > 0));
264
- const R = new l(
265
- i,
266
- t.array,
267
- c,
268
- f
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));
267
+ const _ = new c(
268
+ o,
269
+ s.array,
270
+ g,
271
+ l
269
272
  );
270
- if (f === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(R), m.push(R), i && r.itemSize === 4 && i.startsWith(".materials.") && i.endsWith(".color")) {
271
- const p = new Float32Array(c.length / 4);
272
- for (let x = 0, A = c.length / 4; x < A; x += 1)
273
- p[x] = c[x * 4 + 3];
274
- const v = new l(
275
- i.replace(".color", ".opacity"),
276
- t.array,
277
- p,
278
- f
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(
278
+ o.replace(".color", ".opacity"),
279
+ s.array,
280
+ x,
281
+ l
279
282
  );
280
- f === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(R), m.push(v);
283
+ l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(I);
281
284
  }
282
285
  }
283
286
  return m;
284
287
  }
285
- _tryResolveTargetId(a, t) {
288
+ _tryResolveTargetId(a, s) {
286
289
  let r = "";
287
- return t === "node" ? r = a.substring(7) : t === "material" ? r = a.substring(11) : t === "light" ? r = a.substring(39) : t === "camera" && (r = a.substring(9)), r = r.substring(0, r.indexOf("/")), Number.parseInt(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
291
  }
289
292
  /* MOSTLY DUPLICATE of GLTFLoader.loadAnimation, but also tries to resolve KHR_animation_pointer. */
290
293
  /**
@@ -293,72 +296,72 @@ class G {
293
296
  * @return {Promise<AnimationClip>}
294
297
  */
295
298
  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;
299
- let c = t.loadAnimationTargetFromChannelWithAnimationPointer(b);
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);
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);
301
304
  }
302
305
  return Promise.all([
303
- Promise.all(i),
304
- Promise.all(m),
305
- Promise.all(h),
306
+ Promise.all(u),
306
307
  Promise.all(o),
307
- Promise.all(T)
308
- ]).then(function(g) {
309
- const _ = g[0], b = g[1], u = g[2], l = g[3], k = g[4], f = [];
310
- for (let c = 0, R = _.length; c < R; c++) {
311
- const p = _[c], v = b[c], x = u[c], A = l[c], N = k[c];
312
- if (p === void 0) continue;
313
- p.updateMatrix && (p.updateMatrix(), p.matrixAutoUpdate = !0);
314
- let I = t.createAnimationTracksWithAnimationPointer(p, v, x, A, N);
315
- if (I || (I = e._createAnimationTracks(p, v, x, A, N)), I)
316
- for (let K = 0; K < I.length; K++)
317
- f.push(I[K]);
308
+ Promise.all(m),
309
+ Promise.all(i),
310
+ Promise.all(v)
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];
315
+ if (_ === void 0) continue;
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]);
318
321
  }
319
- return new M(s, void 0, f);
322
+ return new w(n, void 0, b);
320
323
  });
321
324
  }
322
325
  }
323
- let H = !1, L = null;
324
- function j() {
326
+ let H = !1, U = null;
327
+ function L() {
325
328
  if (H) return;
326
329
  H = !0;
327
- const P = L || (L = C.findNode);
328
- C.findNode = function(a, t) {
329
- if (!t) return P(a, t);
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;
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;
336
- } else if (t.startsWith(".nodes.") || t.startsWith(".lights.") || t.startsWith(".cameras.")) {
337
- const r = t.split(".");
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);
335
+ 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)));
338
+ }), n;
339
+ } else if (s.startsWith(".nodes.") || s.startsWith(".lights.") || s.startsWith(".cameras.")) {
340
+ const r = s.split(".");
338
341
  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;
346
- m >= 0 && (h = m), e = e[h];
342
+ for (let t = 1; t < r.length; t++) {
343
+ const n = r[t];
344
+ if (n.length == 36)
345
+ e = a.getObjectByProperty("uuid", n);
346
+ else if (e && e[n]) {
347
+ const o = Number.parseInt(n);
348
+ let m = n;
349
+ o >= 0 && (m = o), e = e[m];
347
350
  } else {
348
- const m = a.getObjectByName(s);
349
- m && (e = m);
351
+ const o = a.getObjectByName(n);
352
+ o && (e = o);
350
353
  }
351
354
  }
352
355
  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;
356
+ const t = A(a, r[2]);
357
+ return t || console.warn(R + ": Property binding not found", s, a, a.name, r), t;
355
358
  }
356
359
  return e;
357
360
  }
358
- return P(a, t);
361
+ return A(a, s);
359
362
  };
360
363
  }
361
364
  export {
362
- G as GLTFAnimationPointerExtension,
363
- G as default
365
+ z as GLTFAnimationPointerExtension,
366
+ z as default
364
367
  };
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.2",
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
 
@@ -55,6 +54,12 @@ export class GLTFAnimationPointerExtension {
55
54
  /** @type {import("..").AnimationPointerResolver | null} */
56
55
  this.animationPointerResolver = null;
57
56
 
57
+ /**
58
+ * @type {Map<string, string>}
59
+ * Maps from original pointer paths to resolved three.js property paths.
60
+ */
61
+ this.pointerPathMap = new Map();
62
+
58
63
  }
59
64
 
60
65
  /**
@@ -352,7 +357,8 @@ export class GLTFAnimationPointerExtension {
352
357
 
353
358
  }
354
359
 
355
- target.extensions[ KHR_ANIMATION_POINTER ].pointer = path;
360
+ const pointer = ext.pointer;
361
+ this.pointerPathMap.set( pointer, path );
356
362
 
357
363
  }
358
364
 
@@ -385,7 +391,8 @@ export class GLTFAnimationPointerExtension {
385
391
  const useExtension = target.extensions && target.extensions[ KHR_ANIMATION_POINTER ] && target.path && target.path === 'pointer';
386
392
  if ( ! useExtension ) return null;
387
393
 
388
- let animationPointerPropertyPath = target.extensions[ KHR_ANIMATION_POINTER ].pointer;
394
+ const pointer = target.extensions[ KHR_ANIMATION_POINTER ].pointer;
395
+ let animationPointerPropertyPath = this.pointerPathMap.get( pointer );
389
396
  if ( ! animationPointerPropertyPath ) return null;
390
397
 
391
398
  const tracks = [];
@@ -408,12 +415,13 @@ export class GLTFAnimationPointerExtension {
408
415
  if ( node.type === 'Group' ) {
409
416
 
410
417
  if ( _animationPointerDebug )
411
- console.log( 'Detected multi-material skinnedMesh export', animationPointerPropertyPath, node );
418
+ console.log( 'Detected multi-material mesh export', animationPointerPropertyPath, node );
412
419
 
413
- // We assume the children are skinned meshes
420
+ // Bind any child that has morph influences. The children may be plain
421
+ // Mesh (no skin/bones) or SkinnedMesh, so we must not require SkinnedMesh.
414
422
  for ( const ch of node.children ) {
415
423
 
416
- if ( ch instanceof SkinnedMesh && ch.morphTargetInfluences ) {
424
+ if ( ch.morphTargetInfluences ) {
417
425
 
418
426
  parts[ 3 ] = ch.name;
419
427
  parts[ 4 ] = property;
@@ -810,4 +818,4 @@ function _ensurePropertyBindingPatch() {
810
818
 
811
819
  };
812
820
 
813
- }
821
+ }