@needle-tools/three-animation-pointer 1.1.1 → 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 +6 -0
- package/dist/three-animation-pointer.js +145 -133
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +41 -3
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.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
|
+
|
|
10
|
+
## [1.1.2] - 2026-06-17
|
|
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)
|
|
12
|
+
|
|
7
13
|
## [1.1.1] - 2026-06-12
|
|
8
14
|
- 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
15
|
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import { InterpolateDiscrete as
|
|
2
|
-
const
|
|
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
|
+
const k = {
|
|
3
3
|
node: "node",
|
|
4
4
|
material: "material",
|
|
5
5
|
camera: "camera",
|
|
6
6
|
light: "light"
|
|
7
|
-
},
|
|
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
|
-
STEP:
|
|
12
|
+
STEP: M
|
|
13
13
|
};
|
|
14
|
-
class
|
|
14
|
+
class G {
|
|
15
15
|
/** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
|
|
16
|
-
constructor(
|
|
17
|
-
this.name =
|
|
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(
|
|
23
|
-
return this.animationPointerResolver =
|
|
22
|
+
setAnimationPointerResolver(r) {
|
|
23
|
+
return this.animationPointerResolver = r, this;
|
|
24
24
|
}
|
|
25
25
|
/* DUPLICATE of functionality in GLTFLoader */
|
|
26
|
-
loadAnimationTargetFromChannel(
|
|
27
|
-
const t =
|
|
28
|
-
return this.parser.getDependency("node",
|
|
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(
|
|
31
|
-
var
|
|
30
|
+
loadAnimationTargetFromChannelWithAnimationPointer(r) {
|
|
31
|
+
var o;
|
|
32
32
|
L();
|
|
33
|
-
const t =
|
|
34
|
-
if (!
|
|
35
|
-
let e, s =
|
|
36
|
-
if (
|
|
37
|
-
const
|
|
38
|
-
let
|
|
39
|
-
if (!
|
|
40
|
-
console.warn("Invalid path",
|
|
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
|
+
let i = m.pointer;
|
|
39
|
+
if (!i) {
|
|
40
|
+
console.warn("Invalid path", m, t);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
if (
|
|
44
|
-
console.warn("Failed resolving animation node id: " + 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
|
+
console.warn("Failed resolving animation node id: " + n, i);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
switch (s) {
|
|
48
|
-
case
|
|
49
|
-
const
|
|
50
|
-
switch (e =
|
|
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 z {
|
|
|
130
130
|
e = "specularColor";
|
|
131
131
|
break;
|
|
132
132
|
}
|
|
133
|
-
|
|
133
|
+
i = y + e;
|
|
134
134
|
break;
|
|
135
|
-
case
|
|
136
|
-
const
|
|
137
|
-
if (e =
|
|
138
|
-
const
|
|
139
|
-
/^\d+$/.test(
|
|
135
|
+
case k.node:
|
|
136
|
+
const g = ("/nodes/" + n.toString() + "/").length, d = i.substring(0, g);
|
|
137
|
+
if (e = i.substring(g), 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 z {
|
|
|
155
155
|
e = "visible";
|
|
156
156
|
break;
|
|
157
157
|
}
|
|
158
|
-
|
|
158
|
+
i = d + e;
|
|
159
159
|
break;
|
|
160
|
-
case
|
|
161
|
-
const
|
|
162
|
-
switch (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 z {
|
|
|
174
174
|
e = "distance";
|
|
175
175
|
break;
|
|
176
176
|
}
|
|
177
|
-
|
|
177
|
+
i = "/lights/" + n.toString() + "/" + e;
|
|
178
178
|
break;
|
|
179
|
-
case
|
|
180
|
-
const
|
|
181
|
-
switch (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 z {
|
|
|
200
200
|
e = "zoom";
|
|
201
201
|
break;
|
|
202
202
|
}
|
|
203
|
-
|
|
203
|
+
i = b + e;
|
|
204
204
|
break;
|
|
205
205
|
}
|
|
206
|
-
(
|
|
206
|
+
(o = this.animationPointerResolver) != null && o.resolvePath && (i = this.animationPointerResolver.resolvePath(i));
|
|
207
|
+
const I = m.pointer;
|
|
208
|
+
this.pointerPathMap.set(I, i);
|
|
207
209
|
}
|
|
208
210
|
if (n == null || isNaN(n)) {
|
|
209
211
|
console.warn("Failed resolving animation node id: " + n, t);
|
|
210
212
|
return;
|
|
211
213
|
}
|
|
212
|
-
let
|
|
213
|
-
return s ===
|
|
214
|
+
let 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;
|
|
214
216
|
}
|
|
215
|
-
createAnimationTracksWithAnimationPointer(
|
|
216
|
-
var
|
|
217
|
-
if (!(s.extensions && s.extensions[
|
|
218
|
-
|
|
219
|
-
|
|
217
|
+
createAnimationTracksWithAnimationPointer(r, t, a, e, s) {
|
|
218
|
+
var d;
|
|
219
|
+
if (!(s.extensions && s.extensions[R] && s.path && s.path === "pointer")) return null;
|
|
220
|
+
const u = s.extensions[R].pointer;
|
|
221
|
+
let o = this.pointerPathMap.get(u);
|
|
222
|
+
if (!o) return null;
|
|
220
223
|
const m = [];
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
var
|
|
224
|
-
if (
|
|
225
|
-
const
|
|
226
|
-
if (
|
|
227
|
-
for (const
|
|
228
|
-
|
|
224
|
+
o = o.replaceAll("/", ".");
|
|
225
|
+
const i = o.split(".");
|
|
226
|
+
var h = r.name !== void 0 && r.name !== null ? r.name : r.uuid;
|
|
227
|
+
if (i[2] = h, (d = i[3]) != null && d.startsWith("morphTargetInfluences")) {
|
|
228
|
+
const p = i[3];
|
|
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));
|
|
229
232
|
return m;
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
|
-
|
|
233
|
-
function
|
|
235
|
+
g(this.parser);
|
|
236
|
+
function y(p, c) {
|
|
234
237
|
try {
|
|
235
|
-
const
|
|
236
|
-
return
|
|
238
|
+
const b = c.split(".").filter(Boolean), f = b[b.length - 1].replace(/\[.*\]$/, "");
|
|
239
|
+
return f in p ? typeof p[f] == "boolean" : !1;
|
|
237
240
|
} catch {
|
|
238
241
|
return !1;
|
|
239
242
|
}
|
|
240
243
|
}
|
|
241
|
-
function
|
|
242
|
-
|
|
243
|
-
let
|
|
244
|
-
switch (
|
|
244
|
+
function g(p) {
|
|
245
|
+
o = i.join(".");
|
|
246
|
+
let c, b = !1;
|
|
247
|
+
switch (a.itemSize) {
|
|
245
248
|
case 1:
|
|
246
|
-
Object.prototype.toString.call(
|
|
247
|
-
|
|
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;
|
|
248
251
|
break;
|
|
249
252
|
case 2:
|
|
250
253
|
case 3:
|
|
251
|
-
|
|
254
|
+
c = B;
|
|
252
255
|
break;
|
|
253
256
|
case 4:
|
|
254
|
-
|
|
257
|
+
o.endsWith(".quaternion") ? c = S : c = W;
|
|
255
258
|
break;
|
|
256
259
|
}
|
|
257
|
-
if (!
|
|
258
|
-
console.warn("Unsupported output accessor format",
|
|
260
|
+
if (!c) {
|
|
261
|
+
console.warn("Unsupported output accessor format", a);
|
|
259
262
|
return;
|
|
260
263
|
}
|
|
261
|
-
const
|
|
262
|
-
let
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
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
|
+
const _ = new c(
|
|
268
|
+
o,
|
|
266
269
|
t.array,
|
|
267
|
-
|
|
268
|
-
|
|
270
|
+
f,
|
|
271
|
+
l
|
|
269
272
|
);
|
|
270
|
-
if (
|
|
271
|
-
const
|
|
272
|
-
for (let
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
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
|
+
o.replace(".color", ".opacity"),
|
|
276
279
|
t.array,
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
x,
|
|
281
|
+
l
|
|
279
282
|
);
|
|
280
|
-
|
|
283
|
+
l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(P);
|
|
281
284
|
}
|
|
282
285
|
}
|
|
283
286
|
return m;
|
|
284
287
|
}
|
|
285
|
-
_tryResolveTargetId(
|
|
286
|
-
let
|
|
287
|
-
return t === "node" ?
|
|
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);
|
|
288
291
|
}
|
|
289
292
|
/* MOSTLY DUPLICATE of GLTFLoader.loadAnimation, but also tries to resolve KHR_animation_pointer. */
|
|
290
293
|
/**
|
|
@@ -292,73 +295,82 @@ class z {
|
|
|
292
295
|
* @param {number} animationIndex
|
|
293
296
|
* @return {Promise<AnimationClip>}
|
|
294
297
|
*/
|
|
295
|
-
loadAnimation(
|
|
296
|
-
const t = this,
|
|
297
|
-
for (let
|
|
298
|
-
const
|
|
299
|
-
let
|
|
300
|
-
|
|
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);
|
|
301
304
|
}
|
|
302
305
|
return Promise.all([
|
|
303
|
-
Promise.all(
|
|
304
|
-
Promise.all(m),
|
|
305
|
-
Promise.all(h),
|
|
306
|
+
Promise.all(u),
|
|
306
307
|
Promise.all(o),
|
|
307
|
-
Promise.all(
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
308
|
+
Promise.all(m),
|
|
309
|
+
Promise.all(i),
|
|
310
|
+
Promise.all(I)
|
|
311
|
+
]).then(function(h) {
|
|
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
|
+
if (_ === void 0) continue;
|
|
316
|
+
_.updateMatrix && (_.updateMatrix(), _.matrixAutoUpdate = !0);
|
|
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]);
|
|
318
321
|
}
|
|
319
|
-
return new
|
|
322
|
+
return new w(n, void 0, b);
|
|
320
323
|
});
|
|
321
324
|
}
|
|
322
325
|
}
|
|
323
|
-
|
|
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;
|
|
324
333
|
function L() {
|
|
325
334
|
if (H) return;
|
|
326
335
|
H = !0;
|
|
327
|
-
const
|
|
328
|
-
C.findNode = function(
|
|
329
|
-
if (!t) return
|
|
336
|
+
const v = j || (j = C.findNode);
|
|
337
|
+
C.findNode = function(r, t) {
|
|
338
|
+
if (!t) return v(r, t);
|
|
330
339
|
if (t.startsWith(".materials.")) {
|
|
331
|
-
const
|
|
340
|
+
const a = t.substring(11).substring(t.indexOf(".")), e = a.indexOf("."), s = e < 0 ? a : a.substring(0, e);
|
|
332
341
|
let n = null;
|
|
333
|
-
return
|
|
334
|
-
n !== null ||
|
|
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)));
|
|
335
344
|
}), n;
|
|
336
345
|
} else if (t.startsWith(".nodes.") || t.startsWith(".lights.") || t.startsWith(".cameras.")) {
|
|
337
|
-
const
|
|
346
|
+
const a = t.split(".");
|
|
338
347
|
let e;
|
|
339
|
-
for (let s = 1; s <
|
|
340
|
-
const n =
|
|
348
|
+
for (let s = 1; s < a.length; s++) {
|
|
349
|
+
const n = a[s];
|
|
341
350
|
if (n.length == 36)
|
|
342
|
-
e =
|
|
351
|
+
e = r.getObjectByProperty("uuid", n);
|
|
343
352
|
else if (e && e[n]) {
|
|
344
|
-
const
|
|
345
|
-
let
|
|
346
|
-
|
|
353
|
+
const o = Number.parseInt(n);
|
|
354
|
+
let m = n;
|
|
355
|
+
o >= 0 && (m = o), e = e[m];
|
|
356
|
+
} else if (e && e.isObject3D) {
|
|
357
|
+
const o = U(e, n);
|
|
358
|
+
o && (e = o);
|
|
347
359
|
} else {
|
|
348
|
-
const
|
|
349
|
-
|
|
360
|
+
const o = r.getObjectByName(n);
|
|
361
|
+
o && (e = o);
|
|
350
362
|
}
|
|
351
363
|
}
|
|
352
364
|
if (!e) {
|
|
353
|
-
const s =
|
|
354
|
-
return s || console.warn(
|
|
365
|
+
const s = v(r, a[2]);
|
|
366
|
+
return s || console.warn(R + ": Property binding not found", t, r, r.name, a), s;
|
|
355
367
|
}
|
|
356
368
|
return e;
|
|
357
369
|
}
|
|
358
|
-
return
|
|
370
|
+
return v(r, t);
|
|
359
371
|
};
|
|
360
372
|
}
|
|
361
373
|
export {
|
|
362
|
-
|
|
363
|
-
|
|
374
|
+
G as GLTFAnimationPointerExtension,
|
|
375
|
+
G as default
|
|
364
376
|
};
|
package/package.json
CHANGED
|
@@ -54,6 +54,12 @@ export class GLTFAnimationPointerExtension {
|
|
|
54
54
|
/** @type {import("..").AnimationPointerResolver | null} */
|
|
55
55
|
this.animationPointerResolver = null;
|
|
56
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
|
+
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
/**
|
|
@@ -351,7 +357,8 @@ export class GLTFAnimationPointerExtension {
|
|
|
351
357
|
|
|
352
358
|
}
|
|
353
359
|
|
|
354
|
-
|
|
360
|
+
const pointer = ext.pointer;
|
|
361
|
+
this.pointerPathMap.set( pointer, path );
|
|
355
362
|
|
|
356
363
|
}
|
|
357
364
|
|
|
@@ -384,7 +391,8 @@ export class GLTFAnimationPointerExtension {
|
|
|
384
391
|
const useExtension = target.extensions && target.extensions[ KHR_ANIMATION_POINTER ] && target.path && target.path === 'pointer';
|
|
385
392
|
if ( ! useExtension ) return null;
|
|
386
393
|
|
|
387
|
-
|
|
394
|
+
const pointer = target.extensions[ KHR_ANIMATION_POINTER ].pointer;
|
|
395
|
+
let animationPointerPropertyPath = this.pointerPathMap.get( pointer );
|
|
388
396
|
if ( ! animationPointerPropertyPath ) return null;
|
|
389
397
|
|
|
390
398
|
const tracks = [];
|
|
@@ -698,6 +706,25 @@ export class GLTFAnimationPointerExtension {
|
|
|
698
706
|
|
|
699
707
|
|
|
700
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
|
+
|
|
701
728
|
let _havePatchedPropertyBindings = false;
|
|
702
729
|
|
|
703
730
|
// HACK monkey patching findNode to ensure we can map to other types required by KHR_animation_pointer.
|
|
@@ -776,6 +803,17 @@ function _ensurePropertyBindingPatch() {
|
|
|
776
803
|
if (_animationPointerDebug)
|
|
777
804
|
console.log(currentTarget);
|
|
778
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
|
+
|
|
779
817
|
} else {
|
|
780
818
|
|
|
781
819
|
// access by node name
|
|
@@ -810,4 +848,4 @@ function _ensurePropertyBindingPatch() {
|
|
|
810
848
|
|
|
811
849
|
};
|
|
812
850
|
|
|
813
|
-
}
|
|
851
|
+
}
|