@needle-tools/three-animation-pointer 1.1.1 → 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 +3 -0
- package/dist/three-animation-pointer.js +123 -120
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +11 -3
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.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
|
+
|
|
7
10
|
## [1.1.1] - 2026-06-12
|
|
8
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.
|
|
9
12
|
|
|
@@ -1,20 +1,20 @@
|
|
|
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 E } 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", 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:
|
|
12
|
+
STEP: M
|
|
13
13
|
};
|
|
14
14
|
class z {
|
|
15
15
|
/** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
|
|
16
16
|
constructor(a) {
|
|
17
|
-
this.name =
|
|
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 z {
|
|
|
24
24
|
}
|
|
25
25
|
/* DUPLICATE of functionality in GLTFLoader */
|
|
26
26
|
loadAnimationTargetFromChannel(a) {
|
|
27
|
-
const
|
|
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
|
|
31
|
+
var o;
|
|
32
32
|
L();
|
|
33
|
-
const
|
|
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,
|
|
35
|
+
let e, t = k.node, n;
|
|
36
36
|
if (r) {
|
|
37
|
-
const
|
|
38
|
-
let
|
|
39
|
-
if (!
|
|
40
|
-
console.warn("Invalid path",
|
|
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 (
|
|
44
|
-
console.warn("Failed resolving animation node id: " + n,
|
|
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 (
|
|
48
|
-
case
|
|
49
|
-
const
|
|
50
|
-
switch (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 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 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 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 v = m.pointer;
|
|
208
|
+
this.pointerPathMap.set(v, i);
|
|
207
209
|
}
|
|
208
210
|
if (n == null || isNaN(n)) {
|
|
209
|
-
console.warn("Failed resolving animation node id: " + n,
|
|
211
|
+
console.warn("Failed resolving animation node id: " + n, s);
|
|
210
212
|
return;
|
|
211
213
|
}
|
|
212
|
-
let
|
|
213
|
-
return
|
|
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,
|
|
216
|
-
var
|
|
217
|
-
if (!(
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
222
|
-
const
|
|
223
|
-
var
|
|
224
|
-
if (
|
|
225
|
-
const
|
|
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
|
|
228
|
-
|
|
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
|
-
|
|
233
|
-
function
|
|
235
|
+
f(this.parser);
|
|
236
|
+
function y(p, c) {
|
|
234
237
|
try {
|
|
235
|
-
const
|
|
236
|
-
return
|
|
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
|
|
242
|
-
|
|
243
|
-
let
|
|
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
|
-
|
|
250
|
+
y(a, o) ? (c = D, b = !0) : c = E;
|
|
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 (!
|
|
260
|
+
if (!c) {
|
|
258
261
|
console.warn("Unsupported output accessor format", r);
|
|
259
262
|
return;
|
|
260
263
|
}
|
|
261
|
-
const
|
|
262
|
-
let
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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 (
|
|
271
|
-
const
|
|
272
|
-
for (let
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
283
|
+
l === "CUBICSPLINE" && p._createCubicSplineTrackInterpolant(_), m.push(I);
|
|
281
284
|
}
|
|
282
285
|
}
|
|
283
286
|
return m;
|
|
284
287
|
}
|
|
285
|
-
_tryResolveTargetId(a,
|
|
288
|
+
_tryResolveTargetId(a, s) {
|
|
286
289
|
let r = "";
|
|
287
|
-
return
|
|
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,30 +296,30 @@ class z {
|
|
|
293
296
|
* @return {Promise<AnimationClip>}
|
|
294
297
|
*/
|
|
295
298
|
loadAnimation(a) {
|
|
296
|
-
const
|
|
297
|
-
for (let
|
|
298
|
-
const
|
|
299
|
-
let
|
|
300
|
-
|
|
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(
|
|
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(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
|
|
322
|
+
return new w(n, void 0, b);
|
|
320
323
|
});
|
|
321
324
|
}
|
|
322
325
|
}
|
|
@@ -324,38 +327,38 @@ let H = !1, U = null;
|
|
|
324
327
|
function L() {
|
|
325
328
|
if (H) return;
|
|
326
329
|
H = !0;
|
|
327
|
-
const
|
|
328
|
-
C.findNode = function(a,
|
|
329
|
-
if (!
|
|
330
|
-
if (
|
|
331
|
-
const r =
|
|
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);
|
|
332
335
|
let n = null;
|
|
333
|
-
return a.traverse((
|
|
334
|
-
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)));
|
|
335
338
|
}), n;
|
|
336
|
-
} else if (
|
|
337
|
-
const r =
|
|
339
|
+
} else if (s.startsWith(".nodes.") || s.startsWith(".lights.") || s.startsWith(".cameras.")) {
|
|
340
|
+
const r = s.split(".");
|
|
338
341
|
let e;
|
|
339
|
-
for (let
|
|
340
|
-
const n = r[
|
|
342
|
+
for (let t = 1; t < r.length; t++) {
|
|
343
|
+
const n = r[t];
|
|
341
344
|
if (n.length == 36)
|
|
342
345
|
e = a.getObjectByProperty("uuid", n);
|
|
343
346
|
else if (e && e[n]) {
|
|
344
|
-
const
|
|
345
|
-
let
|
|
346
|
-
|
|
347
|
+
const o = Number.parseInt(n);
|
|
348
|
+
let m = n;
|
|
349
|
+
o >= 0 && (m = o), e = e[m];
|
|
347
350
|
} else {
|
|
348
|
-
const
|
|
349
|
-
|
|
351
|
+
const o = a.getObjectByName(n);
|
|
352
|
+
o && (e = o);
|
|
350
353
|
}
|
|
351
354
|
}
|
|
352
355
|
if (!e) {
|
|
353
|
-
const
|
|
354
|
-
return
|
|
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
|
|
361
|
+
return A(a, s);
|
|
359
362
|
};
|
|
360
363
|
}
|
|
361
364
|
export {
|
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 = [];
|
|
@@ -810,4 +818,4 @@ function _ensurePropertyBindingPatch() {
|
|
|
810
818
|
|
|
811
819
|
};
|
|
812
820
|
|
|
813
|
-
}
|
|
821
|
+
}
|