@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 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 f = {
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 j {
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 c;
32
- B();
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 = f.node, s;
35
+ let e, n = d.node, s;
36
36
  if (r) {
37
- const m = t.extensions[R];
38
- let o = m.pointer;
37
+ const h = t.extensions[R];
38
+ let o = h.pointer;
39
39
  if (!o) {
40
- console.warn("Invalid path", m, t);
40
+ console.warn("Invalid path", h, t);
41
41
  return;
42
42
  }
43
- if (o.startsWith("/materials/") ? n = f.material : o.startsWith("/extensions/KHR_lights_punctual/lights/") ? n = f.light : o.startsWith("/cameras/") && (n = f.camera), s = this._tryResolveTargetId(o, n), s === null || isNaN(s)) {
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 f.material:
49
- const x = ("/materials/" + s.toString() + "/").length, h = o.substring(0, x);
50
- switch (e = o.substring(x), e) {
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 = h + e;
133
+ o = g + e;
134
134
  break;
135
- case f.node:
136
- const u = ("/nodes/" + s.toString() + "/").length, l = o.substring(0, u);
137
- switch (e = o.substring(u), e) {
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 f.light:
154
- const p = ("/extensions/KHR_lights_punctual/lights/" + s.toString() + "/").length;
155
- switch (e = o.substring(p), e) {
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 f.camera:
173
- const g = ("/cameras/" + s.toString() + "/").length, b = o.substring(0, g);
174
- switch (e = o.substring(g), e) {
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
- (c = this.animationPointerResolver) != null && c.resolvePath && (o = this.animationPointerResolver.resolvePath(o)), t.extensions[R].pointer = o;
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 === f.node ? i = this.parser.getDependency("node", s) : n === f.material ? i = this.parser.getDependency("material", s) : n === f.light ? i = this.parser.getDependency("light", s) : n === f.camera ? i = this.parser.getDependency("camera", s) : console.error("Unhandled type", n), i;
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 c = [];
215
+ const u = [];
213
216
  i = i.replaceAll("/", ".");
214
- const m = i.split(".");
215
- var x = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
216
- if (m[2] = x, m[3] === "morphTargetInfluences" && a.type === "Group") {
217
- for (const u of a.children)
218
- u instanceof S && u.morphTargetInfluences && (m[3] = u.name, m[4] = "morphTargetInfluences", h(this.parser));
219
- return c;
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
- h(this.parser);
222
- function h(u) {
223
- i = m.join(".");
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
- l = O;
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
- l = E;
244
+ c = B;
232
245
  break;
233
246
  case 4:
234
- i.endsWith(".quaternion") ? l = w : l = D;
247
+ i.endsWith(".quaternion") ? c = w : c = D;
235
248
  break;
236
249
  }
237
- if (!l) {
250
+ if (!c) {
238
251
  console.warn("Unsupported output accessor format", r);
239
252
  return;
240
253
  }
241
- const p = e.interpolation !== void 0 ? U[e.interpolation] : F;
242
- let g = u._getArrayFromAccessor(r);
243
- i.endsWith(".fov") && (g = g.map((k) => k / Math.PI * 180));
244
- const b = new l(
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
- g,
248
- p
260
+ m,
261
+ b
249
262
  );
250
- if (p === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(b), c.push(b), i && r.itemSize === 4 && i.startsWith(".materials.") && i.endsWith(".color")) {
251
- const k = new Float32Array(g.length / 4);
252
- for (let T = 0, _ = g.length / 4; T < _; T += 1)
253
- k[T] = g[T * 4 + 3];
254
- const d = new l(
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
- k,
258
- p
270
+ x,
271
+ b
259
272
  );
260
- p === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(b), c.push(d);
273
+ b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(y);
261
274
  }
262
275
  }
263
- return c;
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 = [], c = [], m = [], o = [], x = [];
277
- for (let h = 0, u = n.channels.length; h < u; h++) {
278
- const l = n.channels[h], p = n.samplers[l.sampler], g = l.target, b = n.parameters !== void 0 ? n.parameters[p.input] : p.input, k = n.parameters !== void 0 ? n.parameters[p.output] : p.output;
279
- let d = t.loadAnimationTargetFromChannelWithAnimationPointer(l);
280
- d || (d = t.loadAnimationTargetFromChannel(l)), i.push(d), c.push(e.getDependency("accessor", b)), m.push(e.getDependency("accessor", k)), o.push(p), x.push(g);
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(c),
285
- Promise.all(m),
297
+ Promise.all(u),
298
+ Promise.all(h),
286
299
  Promise.all(o),
287
- Promise.all(x)
288
- ]).then(function(h) {
289
- const u = h[0], l = h[1], p = h[2], g = h[3], b = h[4], k = [];
290
- for (let d = 0, T = u.length; d < T; d++) {
291
- const _ = u[d], v = l[d], A = p[d], N = g[d], K = b[d];
292
- if (_ === void 0) continue;
293
- _.updateMatrix && (_.updateMatrix(), _.matrixAutoUpdate = !0);
294
- let y = t.createAnimationTracksWithAnimationPointer(_, v, A, N, K);
295
- if (y || (y = e._createAnimationTracks(_, v, A, N, K)), y)
296
- for (let P = 0; P < y.length; P++)
297
- k.push(y[P]);
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, k);
312
+ return new W(s, void 0, m);
300
313
  });
301
314
  }
302
315
  }
303
- let H = !1, z = null;
304
- function B() {
316
+ let H = !1, j = null;
317
+ function L() {
305
318
  if (H) return;
306
319
  H = !0;
307
- const I = z || (z = C.findNode);
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 c = Number.parseInt(s);
325
- let m = s;
326
- c >= 0 && (m = c), e = e[m];
337
+ const u = Number.parseInt(s);
338
+ let h = s;
339
+ u >= 0 && (h = u), e = e[h];
327
340
  } else {
328
- const c = a.getObjectByName(s);
329
- c && (e = c);
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
- j as GLTFAnimationPointerExtension,
343
- j as default
355
+ G as GLTFAnimationPointerExtension,
356
+ G as default
344
357
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/three-animation-pointer",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "KHR_animation_pointer support for three.js",
5
5
  "type": "module",
6
6
  "main": "dist/three-animation-pointer.js",
@@ -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
- TypedKeyframeTrack = NumberKeyframeTrack;
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(