@needle-tools/three-animation-pointer 1.0.4 → 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 +3 -0
- package/dist/three-animation-pointer.js +94 -84
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +52 -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.0.5] - 2025-10-15
|
|
8
|
+
- Add: support for boolean animation tracks (when using ubyte type)
|
|
9
|
+
|
|
7
10
|
## [1.0.4] - 2025-10-08
|
|
8
11
|
- Add: KHR_node_visibility extension
|
|
9
12
|
|
|
@@ -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;
|
|
@@ -153,9 +153,9 @@ class j {
|
|
|
153
153
|
}
|
|
154
154
|
o = l + e;
|
|
155
155
|
break;
|
|
156
|
-
case
|
|
157
|
-
const
|
|
158
|
-
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) {
|
|
159
159
|
case "color":
|
|
160
160
|
break;
|
|
161
161
|
case "intensity":
|
|
@@ -172,9 +172,9 @@ class j {
|
|
|
172
172
|
}
|
|
173
173
|
o = "/lights/" + s.toString() + "/" + e;
|
|
174
174
|
break;
|
|
175
|
-
case
|
|
176
|
-
const
|
|
177
|
-
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) {
|
|
178
178
|
case "perspective/yfov":
|
|
179
179
|
e = "fov";
|
|
180
180
|
break;
|
|
@@ -199,71 +199,81 @@ class j {
|
|
|
199
199
|
o = b + e;
|
|
200
200
|
break;
|
|
201
201
|
}
|
|
202
|
-
(
|
|
202
|
+
(u = this.animationPointerResolver) != null && u.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[R].pointer = o;
|
|
203
203
|
}
|
|
204
204
|
if (s == null || isNaN(s)) {
|
|
205
205
|
console.warn("Failed resolving animation node id: " + s, t);
|
|
206
206
|
return;
|
|
207
207
|
}
|
|
208
208
|
let i;
|
|
209
|
-
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;
|
|
210
210
|
}
|
|
211
211
|
createAnimationTracksWithAnimationPointer(a, t, r, e, n) {
|
|
212
212
|
if (!(n.extensions && n.extensions[R] && n.path && n.path === "pointer")) return null;
|
|
213
213
|
let i = n.extensions[R].pointer;
|
|
214
214
|
if (!i) return null;
|
|
215
|
-
const
|
|
215
|
+
const u = [];
|
|
216
216
|
i = i.replaceAll("/", ".");
|
|
217
|
-
const
|
|
218
|
-
var
|
|
219
|
-
if (
|
|
220
|
-
for (const
|
|
221
|
-
|
|
222
|
-
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
223
|
}
|
|
224
|
-
|
|
225
|
-
function
|
|
226
|
-
|
|
227
|
-
|
|
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
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function k(l) {
|
|
234
|
+
i = h.join(".");
|
|
235
|
+
let c, f = !1;
|
|
228
236
|
switch (r.itemSize) {
|
|
229
237
|
case 1:
|
|
230
|
-
|
|
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;
|
|
231
241
|
break;
|
|
232
242
|
case 2:
|
|
233
243
|
case 3:
|
|
234
|
-
|
|
244
|
+
c = B;
|
|
235
245
|
break;
|
|
236
246
|
case 4:
|
|
237
|
-
i.endsWith(".quaternion") ?
|
|
247
|
+
i.endsWith(".quaternion") ? c = w : c = D;
|
|
238
248
|
break;
|
|
239
249
|
}
|
|
240
|
-
if (!
|
|
250
|
+
if (!c) {
|
|
241
251
|
console.warn("Unsupported output accessor format", r);
|
|
242
252
|
return;
|
|
243
253
|
}
|
|
244
|
-
const
|
|
245
|
-
let
|
|
246
|
-
i.endsWith(".fov") && (
|
|
247
|
-
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(
|
|
248
258
|
i,
|
|
249
259
|
t.array,
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
m,
|
|
261
|
+
b
|
|
252
262
|
);
|
|
253
|
-
if (
|
|
254
|
-
const
|
|
255
|
-
for (let
|
|
256
|
-
|
|
257
|
-
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(
|
|
258
268
|
i.replace(".color", ".opacity"),
|
|
259
269
|
t.array,
|
|
260
|
-
|
|
261
|
-
|
|
270
|
+
x,
|
|
271
|
+
b
|
|
262
272
|
);
|
|
263
|
-
|
|
273
|
+
b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(y);
|
|
264
274
|
}
|
|
265
275
|
}
|
|
266
|
-
return
|
|
276
|
+
return u;
|
|
267
277
|
}
|
|
268
278
|
_tryResolveTargetId(a, t) {
|
|
269
279
|
let r = "";
|
|
@@ -276,38 +286,38 @@ class j {
|
|
|
276
286
|
* @return {Promise<AnimationClip>}
|
|
277
287
|
*/
|
|
278
288
|
loadAnimation(a) {
|
|
279
|
-
const t = this, r = this.parser.json, e = this.parser, n = r.animations[a], s = n.name ? n.name : "animation_" + a, i = [],
|
|
280
|
-
for (let
|
|
281
|
-
const l = n.channels[
|
|
282
|
-
let
|
|
283
|
-
|
|
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);
|
|
284
294
|
}
|
|
285
295
|
return Promise.all([
|
|
286
296
|
Promise.all(i),
|
|
287
|
-
Promise.all(
|
|
288
|
-
Promise.all(
|
|
297
|
+
Promise.all(u),
|
|
298
|
+
Promise.all(h),
|
|
289
299
|
Promise.all(o),
|
|
290
|
-
Promise.all(
|
|
291
|
-
]).then(function(
|
|
292
|
-
const
|
|
293
|
-
for (let
|
|
294
|
-
const
|
|
295
|
-
if (
|
|
296
|
-
|
|
297
|
-
let
|
|
298
|
-
if (
|
|
299
|
-
for (let
|
|
300
|
-
|
|
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]);
|
|
301
311
|
}
|
|
302
|
-
return new W(s, void 0,
|
|
312
|
+
return new W(s, void 0, m);
|
|
303
313
|
});
|
|
304
314
|
}
|
|
305
315
|
}
|
|
306
|
-
let H = !1,
|
|
307
|
-
function
|
|
316
|
+
let H = !1, j = null;
|
|
317
|
+
function L() {
|
|
308
318
|
if (H) return;
|
|
309
319
|
H = !0;
|
|
310
|
-
const I =
|
|
320
|
+
const I = j || (j = C.findNode);
|
|
311
321
|
C.findNode = function(a, t) {
|
|
312
322
|
if (!t) return I(a, t);
|
|
313
323
|
if (t.startsWith(".materials.")) {
|
|
@@ -324,12 +334,12 @@ function B() {
|
|
|
324
334
|
if (s.length == 36)
|
|
325
335
|
e = a.getObjectByProperty("uuid", s);
|
|
326
336
|
else if (e && e[s]) {
|
|
327
|
-
const
|
|
328
|
-
let
|
|
329
|
-
|
|
337
|
+
const u = Number.parseInt(s);
|
|
338
|
+
let h = s;
|
|
339
|
+
u >= 0 && (h = u), e = e[h];
|
|
330
340
|
} else {
|
|
331
|
-
const
|
|
332
|
-
|
|
341
|
+
const u = a.getObjectByName(s);
|
|
342
|
+
u && (e = u);
|
|
333
343
|
}
|
|
334
344
|
}
|
|
335
345
|
if (!e) {
|
|
@@ -342,6 +352,6 @@ function B() {
|
|
|
342
352
|
};
|
|
343
353
|
}
|
|
344
354
|
export {
|
|
345
|
-
|
|
346
|
-
|
|
355
|
+
G as GLTFAnimationPointerExtension,
|
|
356
|
+
G as default
|
|
347
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
|
/**
|
|
@@ -414,6 +415,29 @@ export class GLTFAnimationPointerExtension {
|
|
|
414
415
|
// default
|
|
415
416
|
__createTrack( this.parser );
|
|
416
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
|
+
|
|
417
441
|
/** Create a new track using the current parts array */
|
|
418
442
|
function __createTrack( parser ) {
|
|
419
443
|
|
|
@@ -423,11 +447,31 @@ export class GLTFAnimationPointerExtension {
|
|
|
423
447
|
console.log( node, inputAccessor, outputAccessor, target, animationPointerPropertyPath );
|
|
424
448
|
|
|
425
449
|
let TypedKeyframeTrack;
|
|
426
|
-
|
|
450
|
+
let convertToBoolean = false;
|
|
427
451
|
switch ( outputAccessor.itemSize ) {
|
|
428
452
|
|
|
429
|
-
case 1:
|
|
430
|
-
|
|
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
|
+
}
|
|
431
475
|
break;
|
|
432
476
|
case 2:
|
|
433
477
|
case 3:
|
|
@@ -457,7 +501,11 @@ export class GLTFAnimationPointerExtension {
|
|
|
457
501
|
if ( animationPointerPropertyPath.endsWith( '.fov' ) ) {
|
|
458
502
|
|
|
459
503
|
outputArray = outputArray.map( value => value / Math.PI * 180 );
|
|
504
|
+
}
|
|
460
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 );
|
|
461
509
|
}
|
|
462
510
|
|
|
463
511
|
const track = new TypedKeyframeTrack(
|