@needle-tools/three-animation-pointer 1.0.7 → 1.1.0
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 +100 -92
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +35 -18
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.0] - 2026-06-01
|
|
8
|
+
- 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
|
+
|
|
7
10
|
## [1.0.7] - 2025-10-20
|
|
8
11
|
- Fix: missing "extensions" in KHR_node_visibility
|
|
9
12
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { SkinnedMesh as S, InterpolateDiscrete as
|
|
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
2
|
const d = {
|
|
3
3
|
node: "node",
|
|
4
4
|
material: "material",
|
|
5
5
|
camera: "camera",
|
|
6
6
|
light: "light"
|
|
7
|
-
},
|
|
7
|
+
}, y = "KHR_animation_pointer", U = {
|
|
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: w
|
|
13
13
|
};
|
|
14
14
|
class G {
|
|
15
15
|
/** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
|
|
16
16
|
constructor(a) {
|
|
17
|
-
this.name =
|
|
17
|
+
this.name = y, this.parser = a, this.animationPointerResolver = null;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
20
|
* @param {import("..").AnimationPointerResolver | null} animationPointerResolver
|
|
@@ -28,13 +28,13 @@ class G {
|
|
|
28
28
|
return this.parser.getDependency("node", r);
|
|
29
29
|
}
|
|
30
30
|
loadAnimationTargetFromChannelWithAnimationPointer(a) {
|
|
31
|
-
var
|
|
31
|
+
var m;
|
|
32
32
|
j();
|
|
33
|
-
const t = a.target, r = t.extensions && t.extensions[
|
|
33
|
+
const t = a.target, r = t.extensions && t.extensions[y] && t.path && t.path === "pointer";
|
|
34
34
|
if (!r) return null;
|
|
35
35
|
let e, n = d.node, s;
|
|
36
36
|
if (r) {
|
|
37
|
-
const h = t.extensions[
|
|
37
|
+
const h = t.extensions[y];
|
|
38
38
|
let o = h.pointer;
|
|
39
39
|
if (!o) {
|
|
40
40
|
console.warn("Invalid path", h, t);
|
|
@@ -46,7 +46,7 @@ class G {
|
|
|
46
46
|
}
|
|
47
47
|
switch (n) {
|
|
48
48
|
case d.material:
|
|
49
|
-
const T = ("/materials/" + s.toString() + "/").length,
|
|
49
|
+
const T = ("/materials/" + s.toString() + "/").length, g = o.substring(0, T);
|
|
50
50
|
switch (e = o.substring(T), e) {
|
|
51
51
|
case "pbrMetallicRoughness/baseColorFactor":
|
|
52
52
|
e = "color";
|
|
@@ -130,32 +130,36 @@ class G {
|
|
|
130
130
|
e = "specularColor";
|
|
131
131
|
break;
|
|
132
132
|
}
|
|
133
|
-
o =
|
|
133
|
+
o = g + e;
|
|
134
134
|
break;
|
|
135
135
|
case d.node:
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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);
|
|
140
|
+
} else
|
|
141
|
+
switch (e) {
|
|
142
|
+
case "translation":
|
|
143
|
+
e = "position";
|
|
144
|
+
break;
|
|
145
|
+
case "rotation":
|
|
146
|
+
e = "quaternion";
|
|
147
|
+
break;
|
|
148
|
+
case "scale":
|
|
149
|
+
e = "scale";
|
|
150
|
+
break;
|
|
151
|
+
case "weights":
|
|
152
|
+
e = "morphTargetInfluences";
|
|
153
|
+
break;
|
|
154
|
+
case "extensions/KHR_node_visibility/visible":
|
|
155
|
+
e = "visible";
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
o = b + e;
|
|
155
159
|
break;
|
|
156
160
|
case d.light:
|
|
157
|
-
const
|
|
158
|
-
switch (e = o.substring(
|
|
161
|
+
const u = ("/extensions/KHR_lights_punctual/lights/" + s.toString() + "/").length;
|
|
162
|
+
switch (e = o.substring(u), e) {
|
|
159
163
|
case "color":
|
|
160
164
|
break;
|
|
161
165
|
case "intensity":
|
|
@@ -173,8 +177,8 @@ class G {
|
|
|
173
177
|
o = "/lights/" + s.toString() + "/" + e;
|
|
174
178
|
break;
|
|
175
179
|
case d.camera:
|
|
176
|
-
const
|
|
177
|
-
switch (e = o.substring(
|
|
180
|
+
const l = ("/cameras/" + s.toString() + "/").length, k = o.substring(0, l);
|
|
181
|
+
switch (e = o.substring(l), e) {
|
|
178
182
|
case "perspective/yfov":
|
|
179
183
|
e = "fov";
|
|
180
184
|
break;
|
|
@@ -196,10 +200,10 @@ class G {
|
|
|
196
200
|
e = "zoom";
|
|
197
201
|
break;
|
|
198
202
|
}
|
|
199
|
-
o =
|
|
203
|
+
o = k + e;
|
|
200
204
|
break;
|
|
201
205
|
}
|
|
202
|
-
(
|
|
206
|
+
(m = this.animationPointerResolver) != null && m.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[y].pointer = o;
|
|
203
207
|
}
|
|
204
208
|
if (s == null || isNaN(s)) {
|
|
205
209
|
console.warn("Failed resolving animation node id: " + s, t);
|
|
@@ -209,70 +213,74 @@ class G {
|
|
|
209
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;
|
|
210
214
|
}
|
|
211
215
|
createAnimationTracksWithAnimationPointer(a, t, r, e, n) {
|
|
212
|
-
|
|
213
|
-
|
|
216
|
+
var b;
|
|
217
|
+
if (!(n.extensions && n.extensions[y] && n.path && n.path === "pointer")) return null;
|
|
218
|
+
let i = n.extensions[y].pointer;
|
|
214
219
|
if (!i) return null;
|
|
215
|
-
const
|
|
220
|
+
const m = [];
|
|
216
221
|
i = i.replaceAll("/", ".");
|
|
217
222
|
const h = i.split(".");
|
|
218
223
|
var T = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
|
|
219
|
-
if (h[2] = T, h[3]
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
224
|
+
if (h[2] = T, (b = h[3]) != null && b.startsWith("morphTargetInfluences")) {
|
|
225
|
+
const u = h[3];
|
|
226
|
+
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));
|
|
229
|
+
return m;
|
|
230
|
+
}
|
|
223
231
|
}
|
|
224
|
-
|
|
225
|
-
function
|
|
232
|
+
_(this.parser);
|
|
233
|
+
function g(u, l) {
|
|
226
234
|
try {
|
|
227
|
-
const
|
|
228
|
-
return
|
|
235
|
+
const k = l.split(".").filter(Boolean), c = k[k.length - 1].replace(/\[.*\]$/, "");
|
|
236
|
+
return c in u ? typeof u[c] == "boolean" : !1;
|
|
229
237
|
} catch {
|
|
230
238
|
return !1;
|
|
231
239
|
}
|
|
232
240
|
}
|
|
233
|
-
function
|
|
241
|
+
function _(u) {
|
|
234
242
|
i = h.join(".");
|
|
235
|
-
let
|
|
243
|
+
let l, k = !1;
|
|
236
244
|
switch (r.itemSize) {
|
|
237
245
|
case 1:
|
|
238
246
|
Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
|
|
239
|
-
|
|
247
|
+
g(a, i) ? (l = E, k = !0) : l = O;
|
|
240
248
|
break;
|
|
241
249
|
case 2:
|
|
242
250
|
case 3:
|
|
243
|
-
|
|
251
|
+
l = D;
|
|
244
252
|
break;
|
|
245
253
|
case 4:
|
|
246
|
-
i.endsWith(".quaternion") ?
|
|
254
|
+
i.endsWith(".quaternion") ? l = W : l = B;
|
|
247
255
|
break;
|
|
248
256
|
}
|
|
249
|
-
if (!
|
|
257
|
+
if (!l) {
|
|
250
258
|
console.warn("Unsupported output accessor format", r);
|
|
251
259
|
return;
|
|
252
260
|
}
|
|
253
|
-
const
|
|
254
|
-
let
|
|
255
|
-
i.endsWith(".fov") && (
|
|
256
|
-
const
|
|
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(
|
|
257
265
|
i,
|
|
258
266
|
t.array,
|
|
259
|
-
|
|
260
|
-
|
|
267
|
+
c,
|
|
268
|
+
f
|
|
261
269
|
);
|
|
262
|
-
if (
|
|
263
|
-
const
|
|
264
|
-
for (let
|
|
265
|
-
|
|
266
|
-
const
|
|
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(
|
|
267
275
|
i.replace(".color", ".opacity"),
|
|
268
276
|
t.array,
|
|
269
|
-
|
|
270
|
-
|
|
277
|
+
p,
|
|
278
|
+
f
|
|
271
279
|
);
|
|
272
|
-
|
|
280
|
+
f === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(R), m.push(v);
|
|
273
281
|
}
|
|
274
282
|
}
|
|
275
|
-
return
|
|
283
|
+
return m;
|
|
276
284
|
}
|
|
277
285
|
_tryResolveTargetId(a, t) {
|
|
278
286
|
let r = "";
|
|
@@ -285,30 +293,30 @@ class G {
|
|
|
285
293
|
* @return {Promise<AnimationClip>}
|
|
286
294
|
*/
|
|
287
295
|
loadAnimation(a) {
|
|
288
|
-
const t = this, r = this.parser.json, e = this.parser, n = r.animations[a], s = n.name ? n.name : "animation_" + a, i = [],
|
|
289
|
-
for (let
|
|
290
|
-
const
|
|
291
|
-
let
|
|
292
|
-
|
|
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);
|
|
293
301
|
}
|
|
294
302
|
return Promise.all([
|
|
295
303
|
Promise.all(i),
|
|
296
|
-
Promise.all(
|
|
304
|
+
Promise.all(m),
|
|
297
305
|
Promise.all(h),
|
|
298
306
|
Promise.all(o),
|
|
299
307
|
Promise.all(T)
|
|
300
|
-
]).then(function(
|
|
301
|
-
const
|
|
302
|
-
for (let
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
let
|
|
307
|
-
if (
|
|
308
|
-
for (let
|
|
309
|
-
|
|
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]);
|
|
310
318
|
}
|
|
311
|
-
return new
|
|
319
|
+
return new M(s, void 0, f);
|
|
312
320
|
});
|
|
313
321
|
}
|
|
314
322
|
}
|
|
@@ -316,9 +324,9 @@ let H = !1, L = null;
|
|
|
316
324
|
function j() {
|
|
317
325
|
if (H) return;
|
|
318
326
|
H = !0;
|
|
319
|
-
const
|
|
327
|
+
const P = L || (L = C.findNode);
|
|
320
328
|
C.findNode = function(a, t) {
|
|
321
|
-
if (!t) return
|
|
329
|
+
if (!t) return P(a, t);
|
|
322
330
|
if (t.startsWith(".materials.")) {
|
|
323
331
|
const r = t.substring(11).substring(t.indexOf(".")), e = r.indexOf("."), n = e < 0 ? r : r.substring(0, e);
|
|
324
332
|
let s = null;
|
|
@@ -333,21 +341,21 @@ function j() {
|
|
|
333
341
|
if (s.length == 36)
|
|
334
342
|
e = a.getObjectByProperty("uuid", s);
|
|
335
343
|
else if (e && e[s]) {
|
|
336
|
-
const
|
|
344
|
+
const m = Number.parseInt(s);
|
|
337
345
|
let h = s;
|
|
338
|
-
|
|
346
|
+
m >= 0 && (h = m), e = e[h];
|
|
339
347
|
} else {
|
|
340
|
-
const
|
|
341
|
-
|
|
348
|
+
const m = a.getObjectByName(s);
|
|
349
|
+
m && (e = m);
|
|
342
350
|
}
|
|
343
351
|
}
|
|
344
352
|
if (!e) {
|
|
345
|
-
const n =
|
|
346
|
-
return n || console.warn(
|
|
353
|
+
const n = P(a, r[2]);
|
|
354
|
+
return n || console.warn(y + ": Property binding not found", t, a, a.name, r), n;
|
|
347
355
|
}
|
|
348
356
|
return e;
|
|
349
357
|
}
|
|
350
|
-
return
|
|
358
|
+
return P(a, t);
|
|
351
359
|
};
|
|
352
360
|
}
|
|
353
361
|
export {
|
package/package.json
CHANGED
|
@@ -247,23 +247,37 @@ export class GLTFAnimationPointerExtension {
|
|
|
247
247
|
const pathStartNode = path.substring( 0, pathIndexNode );
|
|
248
248
|
targetProperty = path.substring( pathIndexNode );
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
if ( targetProperty.startsWith( 'weights/' ) ) {
|
|
251
|
+
// `/nodes/{}/weights/{}` - control individual morph weights
|
|
252
|
+
const weightIndex = targetProperty.substring( 'weights/'.length );
|
|
253
|
+
if ( /^\d+$/.test( weightIndex ) ) {
|
|
251
254
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
255
|
+
targetProperty = 'morphTargetInfluences[' + weightIndex + ']';
|
|
256
|
+
|
|
257
|
+
} else {
|
|
258
|
+
|
|
259
|
+
console.warn( KHR_ANIMATION_POINTER + ': invalid weight index', targetProperty, path );
|
|
260
|
+
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
switch ( targetProperty ) {
|
|
264
|
+
|
|
265
|
+
case 'translation':
|
|
266
|
+
targetProperty = 'position';
|
|
267
|
+
break;
|
|
268
|
+
case 'rotation':
|
|
269
|
+
targetProperty = 'quaternion';
|
|
270
|
+
break;
|
|
271
|
+
case 'scale':
|
|
272
|
+
targetProperty = 'scale';
|
|
273
|
+
break;
|
|
274
|
+
case 'weights':
|
|
275
|
+
targetProperty = 'morphTargetInfluences';
|
|
276
|
+
break;
|
|
277
|
+
case "extensions/KHR_node_visibility/visible":
|
|
278
|
+
targetProperty = 'visible';
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
267
281
|
}
|
|
268
282
|
|
|
269
283
|
path = pathStartNode + targetProperty;
|
|
@@ -385,8 +399,11 @@ export class GLTFAnimationPointerExtension {
|
|
|
385
399
|
|
|
386
400
|
// specially handle the morphTargetInfluences property for multi-material meshes
|
|
387
401
|
// in which case the target object is a Group and the children are the actual targets
|
|
402
|
+
// Note that there is also morphTargetInfluences[i] for individual morph targets
|
|
388
403
|
// see NE-3311
|
|
389
|
-
if ( parts[ 3 ]
|
|
404
|
+
if ( parts[ 3 ]?.startsWith( 'morphTargetInfluences' ) ) {
|
|
405
|
+
|
|
406
|
+
const property = parts[ 3 ];
|
|
390
407
|
|
|
391
408
|
if ( node.type === 'Group' ) {
|
|
392
409
|
|
|
@@ -399,7 +416,7 @@ export class GLTFAnimationPointerExtension {
|
|
|
399
416
|
if ( ch instanceof SkinnedMesh && ch.morphTargetInfluences ) {
|
|
400
417
|
|
|
401
418
|
parts[ 3 ] = ch.name;
|
|
402
|
-
parts[ 4 ] =
|
|
419
|
+
parts[ 4 ] = property;
|
|
403
420
|
__createTrack( this.parser );
|
|
404
421
|
|
|
405
422
|
}
|