@needle-tools/three-animation-pointer 1.0.3 → 1.0.5
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 +97 -84
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +55 -5
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.0.5] - 2025-10-15
|
|
8
|
+
- Add: support for boolean animation tracks (when using ubyte type)
|
|
9
|
+
|
|
10
|
+
## [1.0.4] - 2025-10-08
|
|
11
|
+
- Add: KHR_node_visibility extension
|
|
12
|
+
|
|
7
13
|
## [1.0.3] - 2025-09-11
|
|
8
14
|
- Fix: Handle undefined path if AnimationClip tracks have invalid/unexpected names. Fallback to default three implementation
|
|
9
15
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { SkinnedMesh as S, InterpolateDiscrete as M, InterpolateLinear as F, AnimationClip as W, PropertyBinding as C, QuaternionKeyframeTrack as w, ColorKeyframeTrack as D, VectorKeyframeTrack as E, NumberKeyframeTrack as O } from "three";
|
|
2
|
-
const
|
|
1
|
+
import { SkinnedMesh as S, InterpolateDiscrete as M, InterpolateLinear as F, AnimationClip as W, PropertyBinding as C, QuaternionKeyframeTrack as w, ColorKeyframeTrack as D, VectorKeyframeTrack as B, BooleanKeyframeTrack as E, NumberKeyframeTrack as O } from "three";
|
|
2
|
+
const d = {
|
|
3
3
|
node: "node",
|
|
4
4
|
material: "material",
|
|
5
5
|
camera: "camera",
|
|
@@ -11,7 +11,7 @@ const f = {
|
|
|
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
16
|
constructor(a) {
|
|
17
17
|
this.name = R, this.parser = a, this.animationPointerResolver = null;
|
|
@@ -28,26 +28,26 @@ class j {
|
|
|
28
28
|
return this.parser.getDependency("node", r);
|
|
29
29
|
}
|
|
30
30
|
loadAnimationTargetFromChannelWithAnimationPointer(a) {
|
|
31
|
-
var
|
|
32
|
-
|
|
31
|
+
var u;
|
|
32
|
+
L();
|
|
33
33
|
const t = a.target, r = t.extensions && t.extensions[R] && t.path && t.path === "pointer";
|
|
34
34
|
if (!r) return null;
|
|
35
|
-
let e, n =
|
|
35
|
+
let e, n = d.node, s;
|
|
36
36
|
if (r) {
|
|
37
|
-
const
|
|
38
|
-
let o =
|
|
37
|
+
const h = t.extensions[R];
|
|
38
|
+
let o = h.pointer;
|
|
39
39
|
if (!o) {
|
|
40
|
-
console.warn("Invalid path",
|
|
40
|
+
console.warn("Invalid path", h, t);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
-
if (o.startsWith("/materials/") ? n =
|
|
43
|
+
if (o.startsWith("/materials/") ? n = d.material : o.startsWith("/extensions/KHR_lights_punctual/lights/") ? n = d.light : o.startsWith("/cameras/") && (n = d.camera), s = this._tryResolveTargetId(o, n), s === null || isNaN(s)) {
|
|
44
44
|
console.warn("Failed resolving animation node id: " + s, o);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
switch (n) {
|
|
48
|
-
case
|
|
49
|
-
const
|
|
50
|
-
switch (e = o.substring(
|
|
48
|
+
case d.material:
|
|
49
|
+
const T = ("/materials/" + s.toString() + "/").length, g = o.substring(0, T);
|
|
50
|
+
switch (e = o.substring(T), e) {
|
|
51
51
|
case "pbrMetallicRoughness/baseColorFactor":
|
|
52
52
|
e = "color";
|
|
53
53
|
break;
|
|
@@ -130,11 +130,11 @@ class j {
|
|
|
130
130
|
e = "specularColor";
|
|
131
131
|
break;
|
|
132
132
|
}
|
|
133
|
-
o =
|
|
133
|
+
o = g + e;
|
|
134
134
|
break;
|
|
135
|
-
case
|
|
136
|
-
const
|
|
137
|
-
switch (e = o.substring(
|
|
135
|
+
case d.node:
|
|
136
|
+
const k = ("/nodes/" + s.toString() + "/").length, l = o.substring(0, k);
|
|
137
|
+
switch (e = o.substring(k), e) {
|
|
138
138
|
case "translation":
|
|
139
139
|
e = "position";
|
|
140
140
|
break;
|
|
@@ -147,12 +147,15 @@ class j {
|
|
|
147
147
|
case "weights":
|
|
148
148
|
e = "morphTargetInfluences";
|
|
149
149
|
break;
|
|
150
|
+
case "KHR_node_visibility/visible":
|
|
151
|
+
e = "visible";
|
|
152
|
+
break;
|
|
150
153
|
}
|
|
151
154
|
o = l + e;
|
|
152
155
|
break;
|
|
153
|
-
case
|
|
154
|
-
const
|
|
155
|
-
switch (e = o.substring(
|
|
156
|
+
case d.light:
|
|
157
|
+
const c = ("/extensions/KHR_lights_punctual/lights/" + s.toString() + "/").length;
|
|
158
|
+
switch (e = o.substring(c), e) {
|
|
156
159
|
case "color":
|
|
157
160
|
break;
|
|
158
161
|
case "intensity":
|
|
@@ -169,9 +172,9 @@ class j {
|
|
|
169
172
|
}
|
|
170
173
|
o = "/lights/" + s.toString() + "/" + e;
|
|
171
174
|
break;
|
|
172
|
-
case
|
|
173
|
-
const
|
|
174
|
-
switch (e = o.substring(
|
|
175
|
+
case d.camera:
|
|
176
|
+
const f = ("/cameras/" + s.toString() + "/").length, b = o.substring(0, f);
|
|
177
|
+
switch (e = o.substring(f), e) {
|
|
175
178
|
case "perspective/yfov":
|
|
176
179
|
e = "fov";
|
|
177
180
|
break;
|
|
@@ -196,71 +199,81 @@ class j {
|
|
|
196
199
|
o = b + e;
|
|
197
200
|
break;
|
|
198
201
|
}
|
|
199
|
-
(
|
|
202
|
+
(u = this.animationPointerResolver) != null && u.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[R].pointer = o;
|
|
200
203
|
}
|
|
201
204
|
if (s == null || isNaN(s)) {
|
|
202
205
|
console.warn("Failed resolving animation node id: " + s, t);
|
|
203
206
|
return;
|
|
204
207
|
}
|
|
205
208
|
let i;
|
|
206
|
-
return n ===
|
|
209
|
+
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;
|
|
207
210
|
}
|
|
208
211
|
createAnimationTracksWithAnimationPointer(a, t, r, e, n) {
|
|
209
212
|
if (!(n.extensions && n.extensions[R] && n.path && n.path === "pointer")) return null;
|
|
210
213
|
let i = n.extensions[R].pointer;
|
|
211
214
|
if (!i) return null;
|
|
212
|
-
const
|
|
215
|
+
const u = [];
|
|
213
216
|
i = i.replaceAll("/", ".");
|
|
214
|
-
const
|
|
215
|
-
var
|
|
216
|
-
if (
|
|
217
|
-
for (const
|
|
218
|
-
|
|
219
|
-
return
|
|
217
|
+
const h = i.split(".");
|
|
218
|
+
var T = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
|
|
219
|
+
if (h[2] = T, h[3] === "morphTargetInfluences" && a.type === "Group") {
|
|
220
|
+
for (const l of a.children)
|
|
221
|
+
l instanceof S && l.morphTargetInfluences && (h[3] = l.name, h[4] = "morphTargetInfluences", k(this.parser));
|
|
222
|
+
return u;
|
|
223
|
+
}
|
|
224
|
+
k(this.parser);
|
|
225
|
+
function g(l, c) {
|
|
226
|
+
try {
|
|
227
|
+
const f = c.split(".").filter(Boolean), m = f[f.length - 1].replace(/\[.*\]$/, "");
|
|
228
|
+
return m in l ? typeof l[m] == "boolean" : !1;
|
|
229
|
+
} catch {
|
|
230
|
+
return !1;
|
|
231
|
+
}
|
|
220
232
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
let l;
|
|
233
|
+
function k(l) {
|
|
234
|
+
i = h.join(".");
|
|
235
|
+
let c, f = !1;
|
|
225
236
|
switch (r.itemSize) {
|
|
226
237
|
case 1:
|
|
227
|
-
|
|
238
|
+
const _ = Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
|
|
239
|
+
g(a, i);
|
|
240
|
+
_ ? console.log("Assuming boolean animation for", i, "based on property type and accessor type") : console.log("Not a boolean target", i), _ ? (c = E, f = !0) : c = O;
|
|
228
241
|
break;
|
|
229
242
|
case 2:
|
|
230
243
|
case 3:
|
|
231
|
-
|
|
244
|
+
c = B;
|
|
232
245
|
break;
|
|
233
246
|
case 4:
|
|
234
|
-
i.endsWith(".quaternion") ?
|
|
247
|
+
i.endsWith(".quaternion") ? c = w : c = D;
|
|
235
248
|
break;
|
|
236
249
|
}
|
|
237
|
-
if (!
|
|
250
|
+
if (!c) {
|
|
238
251
|
console.warn("Unsupported output accessor format", r);
|
|
239
252
|
return;
|
|
240
253
|
}
|
|
241
|
-
const
|
|
242
|
-
let
|
|
243
|
-
i.endsWith(".fov") && (
|
|
244
|
-
const
|
|
254
|
+
const b = e.interpolation !== void 0 ? U[e.interpolation] : F;
|
|
255
|
+
let m = l._getArrayFromAccessor(r);
|
|
256
|
+
i.endsWith(".fov") && (m = m.map((x) => x / Math.PI * 180)), f && (m = m.map((x) => x > 0));
|
|
257
|
+
const p = new c(
|
|
245
258
|
i,
|
|
246
259
|
t.array,
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
m,
|
|
261
|
+
b
|
|
249
262
|
);
|
|
250
|
-
if (
|
|
251
|
-
const
|
|
252
|
-
for (let
|
|
253
|
-
|
|
254
|
-
const
|
|
263
|
+
if (b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(p), i && r.itemSize === 4 && i.startsWith(".materials.") && i.endsWith(".color")) {
|
|
264
|
+
const x = new Float32Array(m.length / 4);
|
|
265
|
+
for (let _ = 0, P = m.length / 4; _ < P; _ += 1)
|
|
266
|
+
x[_] = m[_ * 4 + 3];
|
|
267
|
+
const y = new c(
|
|
255
268
|
i.replace(".color", ".opacity"),
|
|
256
269
|
t.array,
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
x,
|
|
271
|
+
b
|
|
259
272
|
);
|
|
260
|
-
|
|
273
|
+
b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(y);
|
|
261
274
|
}
|
|
262
275
|
}
|
|
263
|
-
return
|
|
276
|
+
return u;
|
|
264
277
|
}
|
|
265
278
|
_tryResolveTargetId(a, t) {
|
|
266
279
|
let r = "";
|
|
@@ -273,38 +286,38 @@ class j {
|
|
|
273
286
|
* @return {Promise<AnimationClip>}
|
|
274
287
|
*/
|
|
275
288
|
loadAnimation(a) {
|
|
276
|
-
const t = this, r = this.parser.json, e = this.parser, n = r.animations[a], s = n.name ? n.name : "animation_" + a, i = [],
|
|
277
|
-
for (let
|
|
278
|
-
const l = n.channels[
|
|
279
|
-
let
|
|
280
|
-
|
|
289
|
+
const t = this, r = this.parser.json, e = this.parser, n = r.animations[a], s = n.name ? n.name : "animation_" + a, i = [], u = [], h = [], o = [], T = [];
|
|
290
|
+
for (let g = 0, k = n.channels.length; g < k; g++) {
|
|
291
|
+
const l = n.channels[g], c = n.samplers[l.sampler], f = l.target, b = n.parameters !== void 0 ? n.parameters[c.input] : c.input, m = n.parameters !== void 0 ? n.parameters[c.output] : c.output;
|
|
292
|
+
let p = t.loadAnimationTargetFromChannelWithAnimationPointer(l);
|
|
293
|
+
p || (p = t.loadAnimationTargetFromChannel(l)), i.push(p), u.push(e.getDependency("accessor", b)), h.push(e.getDependency("accessor", m)), o.push(c), T.push(f);
|
|
281
294
|
}
|
|
282
295
|
return Promise.all([
|
|
283
296
|
Promise.all(i),
|
|
284
|
-
Promise.all(
|
|
285
|
-
Promise.all(
|
|
297
|
+
Promise.all(u),
|
|
298
|
+
Promise.all(h),
|
|
286
299
|
Promise.all(o),
|
|
287
|
-
Promise.all(
|
|
288
|
-
]).then(function(
|
|
289
|
-
const
|
|
290
|
-
for (let
|
|
291
|
-
const
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
let
|
|
295
|
-
if (
|
|
296
|
-
for (let
|
|
297
|
-
|
|
300
|
+
Promise.all(T)
|
|
301
|
+
]).then(function(g) {
|
|
302
|
+
const k = g[0], l = g[1], c = g[2], f = g[3], b = g[4], m = [];
|
|
303
|
+
for (let p = 0, x = k.length; p < x; p++) {
|
|
304
|
+
const y = k[p], _ = l[p], P = c[p], N = f[p], K = b[p];
|
|
305
|
+
if (y === void 0) continue;
|
|
306
|
+
y.updateMatrix && (y.updateMatrix(), y.matrixAutoUpdate = !0);
|
|
307
|
+
let v = t.createAnimationTracksWithAnimationPointer(y, _, P, N, K);
|
|
308
|
+
if (v || (v = e._createAnimationTracks(y, _, P, N, K)), v)
|
|
309
|
+
for (let A = 0; A < v.length; A++)
|
|
310
|
+
m.push(v[A]);
|
|
298
311
|
}
|
|
299
|
-
return new W(s, void 0,
|
|
312
|
+
return new W(s, void 0, m);
|
|
300
313
|
});
|
|
301
314
|
}
|
|
302
315
|
}
|
|
303
|
-
let H = !1,
|
|
304
|
-
function
|
|
316
|
+
let H = !1, j = null;
|
|
317
|
+
function L() {
|
|
305
318
|
if (H) return;
|
|
306
319
|
H = !0;
|
|
307
|
-
const I =
|
|
320
|
+
const I = j || (j = C.findNode);
|
|
308
321
|
C.findNode = function(a, t) {
|
|
309
322
|
if (!t) return I(a, t);
|
|
310
323
|
if (t.startsWith(".materials.")) {
|
|
@@ -321,12 +334,12 @@ function B() {
|
|
|
321
334
|
if (s.length == 36)
|
|
322
335
|
e = a.getObjectByProperty("uuid", s);
|
|
323
336
|
else if (e && e[s]) {
|
|
324
|
-
const
|
|
325
|
-
let
|
|
326
|
-
|
|
337
|
+
const u = Number.parseInt(s);
|
|
338
|
+
let h = s;
|
|
339
|
+
u >= 0 && (h = u), e = e[h];
|
|
327
340
|
} else {
|
|
328
|
-
const
|
|
329
|
-
|
|
341
|
+
const u = a.getObjectByName(s);
|
|
342
|
+
u && (e = u);
|
|
330
343
|
}
|
|
331
344
|
}
|
|
332
345
|
if (!e) {
|
|
@@ -339,6 +352,6 @@ function B() {
|
|
|
339
352
|
};
|
|
340
353
|
}
|
|
341
354
|
export {
|
|
342
|
-
|
|
343
|
-
|
|
355
|
+
G as GLTFAnimationPointerExtension,
|
|
356
|
+
G as default
|
|
344
357
|
};
|
package/package.json
CHANGED
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
PropertyBinding,
|
|
8
8
|
QuaternionKeyframeTrack,
|
|
9
9
|
VectorKeyframeTrack,
|
|
10
|
-
SkinnedMesh
|
|
10
|
+
SkinnedMesh,
|
|
11
|
+
BooleanKeyframeTrack
|
|
11
12
|
} from 'three';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -260,7 +261,9 @@ export class GLTFAnimationPointerExtension {
|
|
|
260
261
|
case 'weights':
|
|
261
262
|
targetProperty = 'morphTargetInfluences';
|
|
262
263
|
break;
|
|
263
|
-
|
|
264
|
+
case "KHR_node_visibility/visible":
|
|
265
|
+
targetProperty = 'visible';
|
|
266
|
+
break;
|
|
264
267
|
}
|
|
265
268
|
|
|
266
269
|
path = pathStartNode + targetProperty;
|
|
@@ -412,6 +415,29 @@ export class GLTFAnimationPointerExtension {
|
|
|
412
415
|
// default
|
|
413
416
|
__createTrack( this.parser );
|
|
414
417
|
|
|
418
|
+
|
|
419
|
+
function isBooleanTarget(node, trackPath) {
|
|
420
|
+
// trackPath like ".nodes.MyNode.visible" or ".materials.<uuid>.wireframe"
|
|
421
|
+
try {
|
|
422
|
+
|
|
423
|
+
// get last token after '.'
|
|
424
|
+
const sections = trackPath.split('.').filter(Boolean);
|
|
425
|
+
let last = sections[sections.length - 1]; // e.g., 'visible' or 'iridescenceThicknessRange[0]'
|
|
426
|
+
|
|
427
|
+
// strip indexers like [0]
|
|
428
|
+
const propName = last.replace(/\[.*\]$/, '');
|
|
429
|
+
|
|
430
|
+
// bail if not a direct property
|
|
431
|
+
if (!(propName in node)) return false;
|
|
432
|
+
|
|
433
|
+
const val = node[propName];
|
|
434
|
+
return typeof val === 'boolean';
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
415
441
|
/** Create a new track using the current parts array */
|
|
416
442
|
function __createTrack( parser ) {
|
|
417
443
|
|
|
@@ -421,11 +447,31 @@ export class GLTFAnimationPointerExtension {
|
|
|
421
447
|
console.log( node, inputAccessor, outputAccessor, target, animationPointerPropertyPath );
|
|
422
448
|
|
|
423
449
|
let TypedKeyframeTrack;
|
|
424
|
-
|
|
450
|
+
let convertToBoolean = false;
|
|
425
451
|
switch ( outputAccessor.itemSize ) {
|
|
426
452
|
|
|
427
|
-
case 1:
|
|
428
|
-
|
|
453
|
+
case 1:
|
|
454
|
+
// Get type of outputAccessor.array elements
|
|
455
|
+
const arrayType = Object.prototype.toString.call(outputAccessor.array);
|
|
456
|
+
const isUInt8 = (arrayType === '[object Uint8Array]');
|
|
457
|
+
|
|
458
|
+
const looksLikeBool = isUInt8 && // UNSIGNED_BYTE as a strong hint
|
|
459
|
+
isBooleanTarget(node, animationPointerPropertyPath);
|
|
460
|
+
|
|
461
|
+
// Log once per target property if it looks like a boolean
|
|
462
|
+
if (looksLikeBool) {
|
|
463
|
+
console.log('Assuming boolean animation for', animationPointerPropertyPath, 'based on property type and accessor type');
|
|
464
|
+
}
|
|
465
|
+
else
|
|
466
|
+
{
|
|
467
|
+
console.log('Not a boolean target', animationPointerPropertyPath);
|
|
468
|
+
}
|
|
469
|
+
if (looksLikeBool) {
|
|
470
|
+
TypedKeyframeTrack = BooleanKeyframeTrack;
|
|
471
|
+
convertToBoolean = true; // map 0 => false, >0 => true
|
|
472
|
+
} else {
|
|
473
|
+
TypedKeyframeTrack = NumberKeyframeTrack;
|
|
474
|
+
}
|
|
429
475
|
break;
|
|
430
476
|
case 2:
|
|
431
477
|
case 3:
|
|
@@ -455,7 +501,11 @@ export class GLTFAnimationPointerExtension {
|
|
|
455
501
|
if ( animationPointerPropertyPath.endsWith( '.fov' ) ) {
|
|
456
502
|
|
|
457
503
|
outputArray = outputArray.map( value => value / Math.PI * 180 );
|
|
504
|
+
}
|
|
458
505
|
|
|
506
|
+
if (convertToBoolean) {
|
|
507
|
+
// boolean values are stored as ubyte in glTF, 0 = false, anything else = true
|
|
508
|
+
outputArray = outputArray.map( v => v > 0 );
|
|
459
509
|
}
|
|
460
510
|
|
|
461
511
|
const track = new TypedKeyframeTrack(
|