@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 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 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;
@@ -153,9 +153,9 @@ class j {
153
153
  }
154
154
  o = l + e;
155
155
  break;
156
- case f.light:
157
- const p = ("/extensions/KHR_lights_punctual/lights/" + s.toString() + "/").length;
158
- 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) {
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 f.camera:
176
- const g = ("/cameras/" + s.toString() + "/").length, b = o.substring(0, g);
177
- 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) {
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
- (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;
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 === 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;
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 c = [];
215
+ const u = [];
216
216
  i = i.replaceAll("/", ".");
217
- const m = i.split(".");
218
- var x = a.name !== void 0 && a.name !== null ? a.name : a.uuid;
219
- if (m[2] = x, m[3] === "morphTargetInfluences" && a.type === "Group") {
220
- for (const u of a.children)
221
- u instanceof S && u.morphTargetInfluences && (m[3] = u.name, m[4] = "morphTargetInfluences", h(this.parser));
222
- 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
223
  }
224
- h(this.parser);
225
- function h(u) {
226
- i = m.join(".");
227
- let l;
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
- 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;
231
241
  break;
232
242
  case 2:
233
243
  case 3:
234
- l = E;
244
+ c = B;
235
245
  break;
236
246
  case 4:
237
- i.endsWith(".quaternion") ? l = w : l = D;
247
+ i.endsWith(".quaternion") ? c = w : c = D;
238
248
  break;
239
249
  }
240
- if (!l) {
250
+ if (!c) {
241
251
  console.warn("Unsupported output accessor format", r);
242
252
  return;
243
253
  }
244
- const p = e.interpolation !== void 0 ? U[e.interpolation] : F;
245
- let g = u._getArrayFromAccessor(r);
246
- i.endsWith(".fov") && (g = g.map((k) => k / Math.PI * 180));
247
- 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(
248
258
  i,
249
259
  t.array,
250
- g,
251
- p
260
+ m,
261
+ b
252
262
  );
253
- if (p === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(b), c.push(b), i && r.itemSize === 4 && i.startsWith(".materials.") && i.endsWith(".color")) {
254
- const k = new Float32Array(g.length / 4);
255
- for (let T = 0, _ = g.length / 4; T < _; T += 1)
256
- k[T] = g[T * 4 + 3];
257
- 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(
258
268
  i.replace(".color", ".opacity"),
259
269
  t.array,
260
- k,
261
- p
270
+ x,
271
+ b
262
272
  );
263
- p === "CUBICSPLINE" && u._createCubicSplineTrackInterpolant(b), c.push(d);
273
+ b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(y);
264
274
  }
265
275
  }
266
- return c;
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 = [], c = [], m = [], o = [], x = [];
280
- for (let h = 0, u = n.channels.length; h < u; h++) {
281
- 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;
282
- let d = t.loadAnimationTargetFromChannelWithAnimationPointer(l);
283
- 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);
284
294
  }
285
295
  return Promise.all([
286
296
  Promise.all(i),
287
- Promise.all(c),
288
- Promise.all(m),
297
+ Promise.all(u),
298
+ Promise.all(h),
289
299
  Promise.all(o),
290
- Promise.all(x)
291
- ]).then(function(h) {
292
- const u = h[0], l = h[1], p = h[2], g = h[3], b = h[4], k = [];
293
- for (let d = 0, T = u.length; d < T; d++) {
294
- const _ = u[d], P = l[d], A = p[d], K = g[d], N = b[d];
295
- if (_ === void 0) continue;
296
- _.updateMatrix && (_.updateMatrix(), _.matrixAutoUpdate = !0);
297
- let y = t.createAnimationTracksWithAnimationPointer(_, P, A, K, N);
298
- if (y || (y = e._createAnimationTracks(_, P, A, K, N)), y)
299
- for (let v = 0; v < y.length; v++)
300
- k.push(y[v]);
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, k);
312
+ return new W(s, void 0, m);
303
313
  });
304
314
  }
305
315
  }
306
- let H = !1, z = null;
307
- function B() {
316
+ let H = !1, j = null;
317
+ function L() {
308
318
  if (H) return;
309
319
  H = !0;
310
- const I = z || (z = C.findNode);
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 c = Number.parseInt(s);
328
- let m = s;
329
- 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];
330
340
  } else {
331
- const c = a.getObjectByName(s);
332
- c && (e = c);
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
- j as GLTFAnimationPointerExtension,
346
- j as default
355
+ G as GLTFAnimationPointerExtension,
356
+ G as default
347
357
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/three-animation-pointer",
3
- "version": "1.0.4",
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
  /**
@@ -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
- 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
+ }
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(