@needle-tools/three-animation-pointer 1.0.4 → 1.0.6
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 +91 -82
- package/package.json +1 -1
- package/src/GLTFLoaderAnimationPointer.js +43 -4
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
|
+
- Fix: Remove leftover log
|
|
9
|
+
|
|
10
|
+
## [1.0.5] - 2025-10-15
|
|
11
|
+
- Add: support for boolean animation tracks (when using ubyte type)
|
|
12
|
+
|
|
7
13
|
## [1.0.4] - 2025-10-08
|
|
8
14
|
- Add: KHR_node_visibility extension
|
|
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
|
|
2
|
-
const
|
|
1
|
+
import { SkinnedMesh as S, InterpolateDiscrete as M, InterpolateLinear as F, AnimationClip as W, PropertyBinding as C, QuaternionKeyframeTrack as w, ColorKeyframeTrack as B, VectorKeyframeTrack as D, 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
|
+
j();
|
|
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, f = 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 = f + 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,7 +172,7 @@ class j {
|
|
|
172
172
|
}
|
|
173
173
|
o = "/lights/" + s.toString() + "/" + e;
|
|
174
174
|
break;
|
|
175
|
-
case
|
|
175
|
+
case d.camera:
|
|
176
176
|
const g = ("/cameras/" + s.toString() + "/").length, b = o.substring(0, g);
|
|
177
177
|
switch (e = o.substring(g), e) {
|
|
178
178
|
case "perspective/yfov":
|
|
@@ -199,71 +199,80 @@ 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 f(l, c) {
|
|
226
|
+
try {
|
|
227
|
+
const g = c.split(".").filter(Boolean), m = g[g.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, g = !1;
|
|
228
236
|
switch (r.itemSize) {
|
|
229
237
|
case 1:
|
|
230
|
-
|
|
238
|
+
Object.prototype.toString.call(r.array) === "[object Uint8Array]" && // UNSIGNED_BYTE as a strong hint
|
|
239
|
+
f(a, i) ? (c = E, g = !0) : c = O;
|
|
231
240
|
break;
|
|
232
241
|
case 2:
|
|
233
242
|
case 3:
|
|
234
|
-
|
|
243
|
+
c = D;
|
|
235
244
|
break;
|
|
236
245
|
case 4:
|
|
237
|
-
i.endsWith(".quaternion") ?
|
|
246
|
+
i.endsWith(".quaternion") ? c = w : c = B;
|
|
238
247
|
break;
|
|
239
248
|
}
|
|
240
|
-
if (!
|
|
249
|
+
if (!c) {
|
|
241
250
|
console.warn("Unsupported output accessor format", r);
|
|
242
251
|
return;
|
|
243
252
|
}
|
|
244
|
-
const
|
|
245
|
-
let
|
|
246
|
-
i.endsWith(".fov") && (
|
|
247
|
-
const
|
|
253
|
+
const b = e.interpolation !== void 0 ? U[e.interpolation] : F;
|
|
254
|
+
let m = l._getArrayFromAccessor(r);
|
|
255
|
+
i.endsWith(".fov") && (m = m.map((_) => _ / Math.PI * 180)), g && (m = m.map((_) => _ > 0));
|
|
256
|
+
const p = new c(
|
|
248
257
|
i,
|
|
249
258
|
t.array,
|
|
250
|
-
|
|
251
|
-
|
|
259
|
+
m,
|
|
260
|
+
b
|
|
252
261
|
);
|
|
253
|
-
if (
|
|
254
|
-
const
|
|
255
|
-
for (let
|
|
256
|
-
|
|
257
|
-
const
|
|
262
|
+
if (b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(p), i && r.itemSize === 4 && i.startsWith(".materials.") && i.endsWith(".color")) {
|
|
263
|
+
const _ = new Float32Array(m.length / 4);
|
|
264
|
+
for (let y = 0, P = m.length / 4; y < P; y += 1)
|
|
265
|
+
_[y] = m[y * 4 + 3];
|
|
266
|
+
const x = new c(
|
|
258
267
|
i.replace(".color", ".opacity"),
|
|
259
268
|
t.array,
|
|
260
|
-
|
|
261
|
-
|
|
269
|
+
_,
|
|
270
|
+
b
|
|
262
271
|
);
|
|
263
|
-
|
|
272
|
+
b === "CUBICSPLINE" && l._createCubicSplineTrackInterpolant(p), u.push(x);
|
|
264
273
|
}
|
|
265
274
|
}
|
|
266
|
-
return
|
|
275
|
+
return u;
|
|
267
276
|
}
|
|
268
277
|
_tryResolveTargetId(a, t) {
|
|
269
278
|
let r = "";
|
|
@@ -276,38 +285,38 @@ class j {
|
|
|
276
285
|
* @return {Promise<AnimationClip>}
|
|
277
286
|
*/
|
|
278
287
|
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
|
-
|
|
288
|
+
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 = [];
|
|
289
|
+
for (let f = 0, k = n.channels.length; f < k; f++) {
|
|
290
|
+
const l = n.channels[f], c = n.samplers[l.sampler], g = l.target, b = n.parameters !== void 0 ? n.parameters[c.input] : c.input, m = n.parameters !== void 0 ? n.parameters[c.output] : c.output;
|
|
291
|
+
let p = t.loadAnimationTargetFromChannelWithAnimationPointer(l);
|
|
292
|
+
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(g);
|
|
284
293
|
}
|
|
285
294
|
return Promise.all([
|
|
286
295
|
Promise.all(i),
|
|
287
|
-
Promise.all(
|
|
288
|
-
Promise.all(
|
|
296
|
+
Promise.all(u),
|
|
297
|
+
Promise.all(h),
|
|
289
298
|
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
|
-
|
|
299
|
+
Promise.all(T)
|
|
300
|
+
]).then(function(f) {
|
|
301
|
+
const k = f[0], l = f[1], c = f[2], g = f[3], b = f[4], m = [];
|
|
302
|
+
for (let p = 0, _ = k.length; p < _; p++) {
|
|
303
|
+
const x = k[p], y = l[p], P = c[p], K = g[p], N = b[p];
|
|
304
|
+
if (x === void 0) continue;
|
|
305
|
+
x.updateMatrix && (x.updateMatrix(), x.matrixAutoUpdate = !0);
|
|
306
|
+
let v = t.createAnimationTracksWithAnimationPointer(x, y, P, K, N);
|
|
307
|
+
if (v || (v = e._createAnimationTracks(x, y, P, K, N)), v)
|
|
308
|
+
for (let A = 0; A < v.length; A++)
|
|
309
|
+
m.push(v[A]);
|
|
301
310
|
}
|
|
302
|
-
return new W(s, void 0,
|
|
311
|
+
return new W(s, void 0, m);
|
|
303
312
|
});
|
|
304
313
|
}
|
|
305
314
|
}
|
|
306
|
-
let H = !1,
|
|
307
|
-
function
|
|
315
|
+
let H = !1, L = null;
|
|
316
|
+
function j() {
|
|
308
317
|
if (H) return;
|
|
309
318
|
H = !0;
|
|
310
|
-
const I =
|
|
319
|
+
const I = L || (L = C.findNode);
|
|
311
320
|
C.findNode = function(a, t) {
|
|
312
321
|
if (!t) return I(a, t);
|
|
313
322
|
if (t.startsWith(".materials.")) {
|
|
@@ -324,12 +333,12 @@ function B() {
|
|
|
324
333
|
if (s.length == 36)
|
|
325
334
|
e = a.getObjectByProperty("uuid", s);
|
|
326
335
|
else if (e && e[s]) {
|
|
327
|
-
const
|
|
328
|
-
let
|
|
329
|
-
|
|
336
|
+
const u = Number.parseInt(s);
|
|
337
|
+
let h = s;
|
|
338
|
+
u >= 0 && (h = u), e = e[h];
|
|
330
339
|
} else {
|
|
331
|
-
const
|
|
332
|
-
|
|
340
|
+
const u = a.getObjectByName(s);
|
|
341
|
+
u && (e = u);
|
|
333
342
|
}
|
|
334
343
|
}
|
|
335
344
|
if (!e) {
|
|
@@ -342,6 +351,6 @@ function B() {
|
|
|
342
351
|
};
|
|
343
352
|
}
|
|
344
353
|
export {
|
|
345
|
-
|
|
346
|
-
|
|
354
|
+
G as GLTFAnimationPointerExtension,
|
|
355
|
+
G as default
|
|
347
356
|
};
|
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,22 @@ 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
|
+
if (looksLikeBool) {
|
|
461
|
+
TypedKeyframeTrack = BooleanKeyframeTrack;
|
|
462
|
+
convertToBoolean = true; // map 0 => false, >0 => true
|
|
463
|
+
} else {
|
|
464
|
+
TypedKeyframeTrack = NumberKeyframeTrack;
|
|
465
|
+
}
|
|
431
466
|
break;
|
|
432
467
|
case 2:
|
|
433
468
|
case 3:
|
|
@@ -457,7 +492,11 @@ export class GLTFAnimationPointerExtension {
|
|
|
457
492
|
if ( animationPointerPropertyPath.endsWith( '.fov' ) ) {
|
|
458
493
|
|
|
459
494
|
outputArray = outputArray.map( value => value / Math.PI * 180 );
|
|
495
|
+
}
|
|
460
496
|
|
|
497
|
+
if (convertToBoolean) {
|
|
498
|
+
// boolean values are stored as ubyte in glTF, 0 = false, anything else = true
|
|
499
|
+
outputArray = outputArray.map( v => v > 0 );
|
|
461
500
|
}
|
|
462
501
|
|
|
463
502
|
const track = new TypedKeyframeTrack(
|