@needle-tools/three-animation-pointer 1.1.0 → 1.1.1
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/README.md +1 -0
- package/dist/three-animation-pointer.js +47 -47
- package/package.json +2 -2
- package/src/GLTFLoaderAnimationPointer.js +4 -4
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.1] - 2026-06-12
|
|
8
|
+
- 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
|
+
|
|
7
10
|
## [1.1.0] - 2026-06-01
|
|
8
11
|
- 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
12
|
|
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,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InterpolateDiscrete as w, InterpolateLinear as F, AnimationClip as S, PropertyBinding as C, QuaternionKeyframeTrack as M, ColorKeyframeTrack as W, VectorKeyframeTrack as B, BooleanKeyframeTrack as D, NumberKeyframeTrack as E } from "three";
|
|
2
2
|
const d = {
|
|
3
3
|
node: "node",
|
|
4
4
|
material: "material",
|
|
5
5
|
camera: "camera",
|
|
6
6
|
light: "light"
|
|
7
|
-
}, y = "KHR_animation_pointer",
|
|
7
|
+
}, y = "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
12
|
STEP: w
|
|
13
13
|
};
|
|
14
|
-
class
|
|
14
|
+
class z {
|
|
15
15
|
/** @type {import("three/examples/jsm/loaders/GLTFLoader").GLTFParser} */
|
|
16
16
|
constructor(a) {
|
|
17
17
|
this.name = y, this.parser = a, this.animationPointerResolver = null;
|
|
@@ -29,10 +29,10 @@ class G {
|
|
|
29
29
|
}
|
|
30
30
|
loadAnimationTargetFromChannelWithAnimationPointer(a) {
|
|
31
31
|
var m;
|
|
32
|
-
|
|
32
|
+
L();
|
|
33
33
|
const t = a.target, r = t.extensions && t.extensions[y] && t.path && t.path === "pointer";
|
|
34
34
|
if (!r) return null;
|
|
35
|
-
let e,
|
|
35
|
+
let e, s = d.node, n;
|
|
36
36
|
if (r) {
|
|
37
37
|
const h = t.extensions[y];
|
|
38
38
|
let o = h.pointer;
|
|
@@ -40,13 +40,13 @@ class G {
|
|
|
40
40
|
console.warn("Invalid path", h, t);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
if (o.startsWith("/materials/") ?
|
|
44
|
-
console.warn("Failed resolving animation node id: " +
|
|
43
|
+
if (o.startsWith("/materials/") ? s = d.material : o.startsWith("/extensions/KHR_lights_punctual/lights/") ? s = d.light : o.startsWith("/cameras/") && (s = d.camera), n = this._tryResolveTargetId(o, s), n === null || isNaN(n)) {
|
|
44
|
+
console.warn("Failed resolving animation node id: " + n, o);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
switch (
|
|
47
|
+
switch (s) {
|
|
48
48
|
case d.material:
|
|
49
|
-
const T = ("/materials/" +
|
|
49
|
+
const T = ("/materials/" + n.toString() + "/").length, g = o.substring(0, T);
|
|
50
50
|
switch (e = o.substring(T), e) {
|
|
51
51
|
case "pbrMetallicRoughness/baseColorFactor":
|
|
52
52
|
e = "color";
|
|
@@ -133,7 +133,7 @@ class G {
|
|
|
133
133
|
o = g + e;
|
|
134
134
|
break;
|
|
135
135
|
case d.node:
|
|
136
|
-
const _ = ("/nodes/" +
|
|
136
|
+
const _ = ("/nodes/" + n.toString() + "/").length, b = o.substring(0, _);
|
|
137
137
|
if (e = o.substring(_), e.startsWith("weights/")) {
|
|
138
138
|
const f = e.substring(8);
|
|
139
139
|
/^\d+$/.test(f) ? e = "morphTargetInfluences[" + f + "]" : console.warn(y + ": invalid weight index", e, o);
|
|
@@ -158,7 +158,7 @@ class G {
|
|
|
158
158
|
o = b + e;
|
|
159
159
|
break;
|
|
160
160
|
case d.light:
|
|
161
|
-
const u = ("/extensions/KHR_lights_punctual/lights/" +
|
|
161
|
+
const u = ("/extensions/KHR_lights_punctual/lights/" + n.toString() + "/").length;
|
|
162
162
|
switch (e = o.substring(u), e) {
|
|
163
163
|
case "color":
|
|
164
164
|
break;
|
|
@@ -174,10 +174,10 @@ class G {
|
|
|
174
174
|
e = "distance";
|
|
175
175
|
break;
|
|
176
176
|
}
|
|
177
|
-
o = "/lights/" +
|
|
177
|
+
o = "/lights/" + n.toString() + "/" + e;
|
|
178
178
|
break;
|
|
179
179
|
case d.camera:
|
|
180
|
-
const l = ("/cameras/" +
|
|
180
|
+
const l = ("/cameras/" + n.toString() + "/").length, k = o.substring(0, l);
|
|
181
181
|
switch (e = o.substring(l), e) {
|
|
182
182
|
case "perspective/yfov":
|
|
183
183
|
e = "fov";
|
|
@@ -205,17 +205,17 @@ class G {
|
|
|
205
205
|
}
|
|
206
206
|
(m = this.animationPointerResolver) != null && m.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[y].pointer = o;
|
|
207
207
|
}
|
|
208
|
-
if (
|
|
209
|
-
console.warn("Failed resolving animation node id: " +
|
|
208
|
+
if (n == null || isNaN(n)) {
|
|
209
|
+
console.warn("Failed resolving animation node id: " + n, t);
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
let i;
|
|
213
|
-
return
|
|
213
|
+
return s === d.node ? i = this.parser.getDependency("node", n) : s === d.material ? i = this.parser.getDependency("material", n) : s === d.light ? i = this.parser.getDependency("light", n) : s === d.camera ? i = this.parser.getDependency("camera", n) : console.error("Unhandled type", s), i;
|
|
214
214
|
}
|
|
215
|
-
createAnimationTracksWithAnimationPointer(a, t, r, e,
|
|
215
|
+
createAnimationTracksWithAnimationPointer(a, t, r, e, s) {
|
|
216
216
|
var b;
|
|
217
|
-
if (!(
|
|
218
|
-
let i =
|
|
217
|
+
if (!(s.extensions && s.extensions[y] && s.path && s.path === "pointer")) return null;
|
|
218
|
+
let i = s.extensions[y].pointer;
|
|
219
219
|
if (!i) return null;
|
|
220
220
|
const m = [];
|
|
221
221
|
i = i.replaceAll("/", ".");
|
|
@@ -225,7 +225,7 @@ class G {
|
|
|
225
225
|
const u = h[3];
|
|
226
226
|
if (a.type === "Group") {
|
|
227
227
|
for (const l of a.children)
|
|
228
|
-
l
|
|
228
|
+
l.morphTargetInfluences && (h[3] = l.name, h[4] = u, _(this.parser));
|
|
229
229
|
return m;
|
|
230
230
|
}
|
|
231
231
|
}
|
|
@@ -244,21 +244,21 @@ class G {
|
|
|
244
244
|
switch (r.itemSize) {
|
|
245
245
|
case 1:
|
|
246
246
|
Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
|
|
247
|
-
g(a, i) ? (l =
|
|
247
|
+
g(a, i) ? (l = D, k = !0) : l = E;
|
|
248
248
|
break;
|
|
249
249
|
case 2:
|
|
250
250
|
case 3:
|
|
251
|
-
l =
|
|
251
|
+
l = B;
|
|
252
252
|
break;
|
|
253
253
|
case 4:
|
|
254
|
-
i.endsWith(".quaternion") ? l =
|
|
254
|
+
i.endsWith(".quaternion") ? l = M : l = W;
|
|
255
255
|
break;
|
|
256
256
|
}
|
|
257
257
|
if (!l) {
|
|
258
258
|
console.warn("Unsupported output accessor format", r);
|
|
259
259
|
return;
|
|
260
260
|
}
|
|
261
|
-
const f = e.interpolation !== void 0 ?
|
|
261
|
+
const f = e.interpolation !== void 0 ? O[e.interpolation] : F;
|
|
262
262
|
let c = u._getArrayFromAccessor(r);
|
|
263
263
|
i.endsWith(".fov") && (c = c.map((p) => p / Math.PI * 180)), k && (c = c.map((p) => p > 0));
|
|
264
264
|
const R = new l(
|
|
@@ -293,9 +293,9 @@ class G {
|
|
|
293
293
|
* @return {Promise<AnimationClip>}
|
|
294
294
|
*/
|
|
295
295
|
loadAnimation(a) {
|
|
296
|
-
const t = this, r = this.parser.json, e = this.parser,
|
|
297
|
-
for (let g = 0, _ =
|
|
298
|
-
const b =
|
|
296
|
+
const t = this, r = this.parser.json, e = this.parser, s = r.animations[a], n = s.name ? s.name : "animation_" + a, i = [], m = [], h = [], o = [], T = [];
|
|
297
|
+
for (let g = 0, _ = s.channels.length; g < _; g++) {
|
|
298
|
+
const b = s.channels[g], u = s.samplers[b.sampler], l = b.target, k = s.parameters !== void 0 ? s.parameters[u.input] : u.input, f = s.parameters !== void 0 ? s.parameters[u.output] : u.output;
|
|
299
299
|
let c = t.loadAnimationTargetFromChannelWithAnimationPointer(b);
|
|
300
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);
|
|
301
301
|
}
|
|
@@ -316,42 +316,42 @@ class G {
|
|
|
316
316
|
for (let K = 0; K < I.length; K++)
|
|
317
317
|
f.push(I[K]);
|
|
318
318
|
}
|
|
319
|
-
return new
|
|
319
|
+
return new S(n, void 0, f);
|
|
320
320
|
});
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
-
let H = !1,
|
|
324
|
-
function
|
|
323
|
+
let H = !1, U = null;
|
|
324
|
+
function L() {
|
|
325
325
|
if (H) return;
|
|
326
326
|
H = !0;
|
|
327
|
-
const P =
|
|
327
|
+
const P = U || (U = C.findNode);
|
|
328
328
|
C.findNode = function(a, t) {
|
|
329
329
|
if (!t) return P(a, t);
|
|
330
330
|
if (t.startsWith(".materials.")) {
|
|
331
|
-
const r = t.substring(11).substring(t.indexOf(".")), e = r.indexOf("."),
|
|
332
|
-
let
|
|
331
|
+
const r = t.substring(11).substring(t.indexOf(".")), e = r.indexOf("."), s = e < 0 ? r : r.substring(0, e);
|
|
332
|
+
let n = null;
|
|
333
333
|
return a.traverse((i) => {
|
|
334
|
-
|
|
335
|
-
}),
|
|
334
|
+
n !== null || i.type !== "Mesh" && i.type !== "SkinnedMesh" || i.material && (i.material.uuid === s || i.material.name === s) && (n = i.material, n !== null && (r.endsWith(".map") ? n = n.map : r.endsWith(".emissiveMap") && (n = n.emissiveMap)));
|
|
335
|
+
}), n;
|
|
336
336
|
} else if (t.startsWith(".nodes.") || t.startsWith(".lights.") || t.startsWith(".cameras.")) {
|
|
337
337
|
const r = t.split(".");
|
|
338
338
|
let e;
|
|
339
|
-
for (let
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
342
|
-
e = a.getObjectByProperty("uuid",
|
|
343
|
-
else if (e && e[
|
|
344
|
-
const m = Number.parseInt(
|
|
345
|
-
let h =
|
|
339
|
+
for (let s = 1; s < r.length; s++) {
|
|
340
|
+
const n = r[s];
|
|
341
|
+
if (n.length == 36)
|
|
342
|
+
e = a.getObjectByProperty("uuid", n);
|
|
343
|
+
else if (e && e[n]) {
|
|
344
|
+
const m = Number.parseInt(n);
|
|
345
|
+
let h = n;
|
|
346
346
|
m >= 0 && (h = m), e = e[h];
|
|
347
347
|
} else {
|
|
348
|
-
const m = a.getObjectByName(
|
|
348
|
+
const m = a.getObjectByName(n);
|
|
349
349
|
m && (e = m);
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
352
|
if (!e) {
|
|
353
|
-
const
|
|
354
|
-
return
|
|
353
|
+
const s = P(a, r[2]);
|
|
354
|
+
return s || console.warn(y + ": Property binding not found", t, a, a.name, r), s;
|
|
355
355
|
}
|
|
356
356
|
return e;
|
|
357
357
|
}
|
|
@@ -359,6 +359,6 @@ function j() {
|
|
|
359
359
|
};
|
|
360
360
|
}
|
|
361
361
|
export {
|
|
362
|
-
|
|
363
|
-
|
|
362
|
+
z as GLTFAnimationPointerExtension,
|
|
363
|
+
z as default
|
|
364
364
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needle-tools/three-animation-pointer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
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
|
|
|
@@ -408,12 +407,13 @@ export class GLTFAnimationPointerExtension {
|
|
|
408
407
|
if ( node.type === 'Group' ) {
|
|
409
408
|
|
|
410
409
|
if ( _animationPointerDebug )
|
|
411
|
-
console.log( 'Detected multi-material
|
|
410
|
+
console.log( 'Detected multi-material mesh export', animationPointerPropertyPath, node );
|
|
412
411
|
|
|
413
|
-
//
|
|
412
|
+
// Bind any child that has morph influences. The children may be plain
|
|
413
|
+
// Mesh (no skin/bones) or SkinnedMesh, so we must not require SkinnedMesh.
|
|
414
414
|
for ( const ch of node.children ) {
|
|
415
415
|
|
|
416
|
-
if ( ch
|
|
416
|
+
if ( ch.morphTargetInfluences ) {
|
|
417
417
|
|
|
418
418
|
parts[ 3 ] = ch.name;
|
|
419
419
|
parts[ 4 ] = property;
|