@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 +3 -0
- package/dist/three-animation-pointer.js +97 -88
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +30 -0
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
|
|
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",
|
|
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
|
|
14
|
+
class G {
|
|
15
15
|
/** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
|
|
16
|
-
constructor(
|
|
17
|
-
this.name = R, this.parser =
|
|
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
|
|
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(
|
|
30
|
+
loadAnimationTargetFromChannelWithAnimationPointer(r) {
|
|
31
31
|
var o;
|
|
32
32
|
L();
|
|
33
|
-
const
|
|
34
|
-
if (!
|
|
35
|
-
let e,
|
|
36
|
-
if (
|
|
37
|
-
const m =
|
|
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,
|
|
40
|
+
console.warn("Invalid path", m, t);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
if (i.startsWith("/materials/") ?
|
|
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 (
|
|
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
|
|
137
|
-
if (e = i.substring(
|
|
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
|
|
208
|
-
this.pointerPathMap.set(
|
|
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,
|
|
211
|
+
console.warn("Failed resolving animation node id: " + n, t);
|
|
212
212
|
return;
|
|
213
213
|
}
|
|
214
214
|
let u;
|
|
215
|
-
return
|
|
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(
|
|
217
|
+
createAnimationTracksWithAnimationPointer(r, t, a, e, s) {
|
|
218
218
|
var d;
|
|
219
|
-
if (!(
|
|
220
|
-
const u =
|
|
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 =
|
|
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 (
|
|
230
|
-
for (const c of
|
|
231
|
-
c.morphTargetInfluences && (i[3] = c.name, i[4] = p,
|
|
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
|
-
|
|
235
|
+
g(this.parser);
|
|
236
236
|
function y(p, c) {
|
|
237
237
|
try {
|
|
238
|
-
const b = c.split(".").filter(Boolean),
|
|
239
|
-
return
|
|
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
|
|
244
|
+
function g(p) {
|
|
245
245
|
o = i.join(".");
|
|
246
246
|
let c, b = !1;
|
|
247
|
-
switch (
|
|
247
|
+
switch (a.itemSize) {
|
|
248
248
|
case 1:
|
|
249
|
-
Object.prototype.toString.call(
|
|
250
|
-
y(
|
|
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",
|
|
261
|
+
console.warn("Unsupported output accessor format", a);
|
|
262
262
|
return;
|
|
263
263
|
}
|
|
264
|
-
const l = e.interpolation !== void 0 ?
|
|
265
|
-
let
|
|
266
|
-
o.endsWith(".fov") && (
|
|
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
|
-
|
|
270
|
-
|
|
269
|
+
t.array,
|
|
270
|
+
f,
|
|
271
271
|
l
|
|
272
272
|
);
|
|
273
|
-
if (l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(_), o &&
|
|
274
|
-
const x = new Float32Array(
|
|
275
|
-
for (let T = 0,
|
|
276
|
-
x[T] =
|
|
277
|
-
const
|
|
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
|
-
|
|
279
|
+
t.array,
|
|
280
280
|
x,
|
|
281
281
|
l
|
|
282
282
|
);
|
|
283
|
-
l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(
|
|
283
|
+
l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(P);
|
|
284
284
|
}
|
|
285
285
|
}
|
|
286
286
|
return m;
|
|
287
287
|
}
|
|
288
|
-
_tryResolveTargetId(
|
|
289
|
-
let
|
|
290
|
-
return
|
|
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(
|
|
299
|
-
const
|
|
300
|
-
for (let h = 0, y =
|
|
301
|
-
const
|
|
302
|
-
let l =
|
|
303
|
-
l || (l =
|
|
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(
|
|
310
|
+
Promise.all(I)
|
|
311
311
|
]).then(function(h) {
|
|
312
|
-
const y = h[0],
|
|
313
|
-
for (let l = 0,
|
|
314
|
-
const _ = y[l], x =
|
|
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
|
|
318
|
-
if (
|
|
319
|
-
for (let
|
|
320
|
-
b.push(
|
|
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
|
-
|
|
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
|
|
331
|
-
C.findNode = function(
|
|
332
|
-
if (!
|
|
333
|
-
if (
|
|
334
|
-
const
|
|
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
|
|
337
|
-
n !== null || u.type !== "Mesh" && u.type !== "SkinnedMesh" || u.material && (u.material.uuid ===
|
|
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 (
|
|
340
|
-
const
|
|
345
|
+
} else if (t.startsWith(".nodes.") || t.startsWith(".lights.") || t.startsWith(".cameras.")) {
|
|
346
|
+
const a = t.split(".");
|
|
341
347
|
let e;
|
|
342
|
-
for (let
|
|
343
|
-
const n =
|
|
348
|
+
for (let s = 1; s < a.length; s++) {
|
|
349
|
+
const n = a[s];
|
|
344
350
|
if (n.length == 36)
|
|
345
|
-
e =
|
|
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 =
|
|
360
|
+
const o = r.getObjectByName(n);
|
|
352
361
|
o && (e = o);
|
|
353
362
|
}
|
|
354
363
|
}
|
|
355
364
|
if (!e) {
|
|
356
|
-
const
|
|
357
|
-
return
|
|
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
|
|
370
|
+
return v(r, t);
|
|
362
371
|
};
|
|
363
372
|
}
|
|
364
373
|
export {
|
|
365
|
-
|
|
366
|
-
|
|
374
|
+
G as GLTFAnimationPointerExtension,
|
|
375
|
+
G as default
|
|
367
376
|
};
|
package/package.json
CHANGED
|
@@ -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
|