rig-play 1.6.0 → 1.8.0
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/README.md +27 -5
- package/dist/index.js +166 -48
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +53 -6
- package/dist/player.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -300,15 +300,37 @@ player.play('wave', { track: 2, loop: false, mix: 0.1 }); // a one-shot…
|
|
|
300
300
|
player.queueClear(2, { mix: 0.1 }); // …mixed back out over its last 0.1 s
|
|
301
301
|
player.queue('idle_b', { track: 0, mix: 0.3 }); // after the current idle cycle completes
|
|
302
302
|
player.clear(1, { mix: 0.2 }); // the layer gone, the base showing through
|
|
303
|
-
player.entry(2); // { animation, loop, trackTime, animationTime, duration } | null
|
|
303
|
+
player.entry(2); // { animation, loop, trackTime, animationTime, duration, alpha, additive } | null
|
|
304
|
+
player.play('blink', { track: 3, alpha: 0.6 }); // the same clip at a weight…
|
|
305
|
+
player.setAlpha(3, 0.85); // …changed while it plays
|
|
306
|
+
player.play('breath', { track: 4, additive: true }); // adds to what the tracks below posed
|
|
304
307
|
```
|
|
305
308
|
|
|
309
|
+
**Weight and additive layers** (rig-c 2.39.0, the runtime's `TrackEntry.alpha` and
|
|
310
|
+
`additive`): `alpha` (0..1, default 1) multiplies every timeline the entry applies;
|
|
311
|
+
`setAlpha(track, alpha)` changes the current entry's weight from the next pose and
|
|
312
|
+
re-poses the held pose. It does not touch events — an entry fires at any alpha, 0
|
|
313
|
+
included, and one mixing out fires none — or attachments: the current entry keeps
|
|
314
|
+
its attachments at any alpha, one mixing out keeps none. `additive: true`, fixed
|
|
315
|
+
when the entry is set or queued, adds the entry's keyed bone and deform values to
|
|
316
|
+
what the tracks below posed instead of replacing them, so a layer keying the same
|
|
317
|
+
bone channels as an idle does not cover its motion; a rotation is added as it
|
|
318
|
+
stands, with no shortest-angle wrap, and an additive entry mixing out fades with its
|
|
319
|
+
alpha and is never held. Colour, attachment and event timelines apply as on a
|
|
320
|
+
replacing entry. A weight below 1 or an additive entry composes, as a mix does.
|
|
321
|
+
|
|
306
322
|
`play(name)` keeps meaning what it meant: a plain play on track 0 runs on rig-c's
|
|
307
323
|
single live track, which poses everything the core poses. The first call that
|
|
308
324
|
composes — a `mix`, a `track` above 0, a `queue`, a `clear` — moves the player onto
|
|
309
|
-
the tracks, carrying the single track over by replay (its animation set, pose 0
|
|
310
|
-
taken, stepped to its time
|
|
311
|
-
|
|
325
|
+
the tracks, carrying the single track over by **replay** (its animation set, pose 0
|
|
326
|
+
taken, stepped to its time in `seekStep` increments — one pose per step played, so
|
|
327
|
+
on a stage that idled a while that is one long frame; the core offers no hand-over
|
|
328
|
+
of a live track's state). To never pay it, stand the player on the tracks from the
|
|
329
|
+
start: `new RigPlayer(root, assets, { tracks: true })`. Pick the single track for a
|
|
330
|
+
rig whose animations key what the tracks do not mix (below); pick the tracks for
|
|
331
|
+
layering. Either way, after the first `mix`, `track` above 0, `queue` or `clear`,
|
|
332
|
+
`seek` and `skin` are refused by name, since the player cannot replay a composition
|
|
333
|
+
to a time — step it forward instead.
|
|
312
334
|
Every entry mixes from the setup pose each step, so `adjust` writes still last one
|
|
313
335
|
pose. **What the tracks do not mix**, in rig-c's words at `play`/`queue`: an
|
|
314
336
|
animation keying a constraint (ik, transform, path, physics, slider), a sequence,
|
|
@@ -653,7 +675,7 @@ name, and unloading is still elements first, bitmaps second.
|
|
|
653
675
|
| `new Skeleton(assets.data)` and `new AnimationState(new AnimationStateData(assets.data))` | `new RigPlayer(root, assets, options?)` | The player holds the pose and the track. |
|
|
654
676
|
| `state.setAnimation(0, 'walk', true)` | `player.play('walk', { loop: true })` | `loop` defaults to true. `play` opens the animation at its first pose with the physics reset there. `player.current` names what is playing. |
|
|
655
677
|
| `skeleton.findSlot('x').attachment = null` to hide a slot, or a second skeleton with slots emptied | `RigPlayerOptions.slots`, `player.slots`, `player.posedFrame(slots)` | A draw filter since 1.6.0 — the pose is the whole rig's; a clipping slot left out does not clip. See *Drawing some slots only*. |
|
|
656
|
-
| `state.setAnimation(1, 'smile', false)` with a mix, `addAnimation`, `setEmptyAnimation`, `addEmptyAnimation`, `getCurrent(track)` | `player.play(name, { track, loop, mix })`, `player.queue(name, { track, loop, mix, delay })`, `player.clear(track, { mix })`, `player.queueClear(track, { mix, delay })`, `player.entry(track)` | Since 1.5.0, rig-c's live tracks: the runtime's mixing at tolerance 0. Constraint, sequence, draw-order and path-deform timelines are not mixed and are refused on the tracks. See *Several tracks*. |
|
|
678
|
+
| `state.setAnimation(1, 'smile', false)` with a mix, `addAnimation`, `setEmptyAnimation`, `addEmptyAnimation`, `getCurrent(track)`, `entry.alpha`, `entry.additive` | `player.play(name, { track, loop, mix, alpha, additive })`, `player.queue(name, { track, loop, mix, delay, alpha, additive })`, `player.clear(track, { mix })`, `player.queueClear(track, { mix, delay })`, `player.setAlpha(track, alpha)`, `player.entry(track)` | Since 1.5.0, rig-c's live tracks: the runtime's mixing at tolerance 0. Constraint, sequence, draw-order and path-deform timelines are not mixed and are refused on the tracks. See *Several tracks*. |
|
|
657
679
|
| `state.update(delta)`, `state.apply(skeleton)`, `skeleton.update(delta)`, `skeleton.updateWorldTransform(Physics.update)`, `renderer.render(skeleton)` | `player.frame(dt)` | Or `player.advance(dt)` then `player.draw()`, to time the two apart. |
|
|
658
680
|
| time scale (the caller's own, on the spine-core side) | `player.timeScale` | Multiplies every step. 0 holds the pose without re-posing it; a negative value is not stepped. |
|
|
659
681
|
| holding a pose (the caller stopped advancing) | `player.pause()`, `player.resume()` | `frame` keeps drawing the held pose. |
|
package/dist/index.js
CHANGED
|
@@ -379,7 +379,7 @@ function timelineIdentity(slot, shown) {
|
|
|
379
379
|
const nameOf = (a) => `${a.skin}/${a.slot}/${a.attachment}`;
|
|
380
380
|
function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {}, mixed) {
|
|
381
381
|
const evalDeform = plant.deform ?? deformAt;
|
|
382
|
-
const
|
|
382
|
+
const blendOf2 = plant.deformBlend ?? blendDeform;
|
|
383
383
|
const evalFrame = plant.sequence ?? sequenceFrameAt;
|
|
384
384
|
const shown = [];
|
|
385
385
|
const why = [];
|
|
@@ -410,7 +410,7 @@ function attachmentStates(doc, resolve, placeholders, sample, sliders, plant = {
|
|
|
410
410
|
if (!seen.includes(now2.identity)) seen.push(now2.identity);
|
|
411
411
|
if (a.deform !== null && now2.vertices !== null) {
|
|
412
412
|
const target = evalDeform(now2.vertices, a.deform, t);
|
|
413
|
-
if (target !== null) deform = blend === null ? target :
|
|
413
|
+
if (target !== null) deform = blend === null ? target : blendOf2(now2.vertices, deform, target, blend.alpha, blend.additive);
|
|
414
414
|
}
|
|
415
415
|
if (a.sequence !== null && now2.count !== void 0) {
|
|
416
416
|
const f = evalFrame(a.sequence, now2.count, t);
|
|
@@ -5072,6 +5072,9 @@ const FIRST = 2;
|
|
|
5072
5072
|
const MODE = 3;
|
|
5073
5073
|
const HOLD = 4;
|
|
5074
5074
|
const EMPTY = { name: null, duration: 0, timelines: [], ids: /* @__PURE__ */ new Set() };
|
|
5075
|
+
function addable(tl) {
|
|
5076
|
+
return tl.type === "deform" || tl.type === "bone" && tl.kind !== "inherit";
|
|
5077
|
+
}
|
|
5075
5078
|
const BONE_PROPERTIES = {
|
|
5076
5079
|
rotate: ["rotate"],
|
|
5077
5080
|
translate: ["x", "y"],
|
|
@@ -5141,6 +5144,8 @@ function newEntry(anim, loop) {
|
|
|
5141
5144
|
mixTime: 0,
|
|
5142
5145
|
mixDuration: 0,
|
|
5143
5146
|
totalAlpha: 0,
|
|
5147
|
+
alpha: 1,
|
|
5148
|
+
additive: false,
|
|
5144
5149
|
keepHold: false,
|
|
5145
5150
|
timelineMode: [],
|
|
5146
5151
|
timelineHoldMix: [],
|
|
@@ -5317,6 +5322,19 @@ class Mixer {
|
|
|
5317
5322
|
}
|
|
5318
5323
|
return this.plant.mixReversed === true ? 1 - m : m;
|
|
5319
5324
|
}
|
|
5325
|
+
/** `TrackEntry.alpha` — inverted or ignored under the plants. */
|
|
5326
|
+
alphaOf(e) {
|
|
5327
|
+
if (this.plant.alphaIgnored === true) return 1;
|
|
5328
|
+
return this.plant.alphaInverted === true ? 1 - e.alpha : e.alpha;
|
|
5329
|
+
}
|
|
5330
|
+
/** `TrackEntry.additive` — never under the plant that applies an additive entry as a replacing one. */
|
|
5331
|
+
additiveOf(e) {
|
|
5332
|
+
return e.additive && this.plant.additiveAsReplace !== true;
|
|
5333
|
+
}
|
|
5334
|
+
/** Whether `computeHold` reads an entry as additive — never under the plant that holds as if none were. */
|
|
5335
|
+
holdsAdditive(e) {
|
|
5336
|
+
return e !== null && this.additiveOf(e) && this.plant.additiveHeld !== true;
|
|
5337
|
+
}
|
|
5320
5338
|
update(delta) {
|
|
5321
5339
|
const tracks = this.tracks;
|
|
5322
5340
|
for (let i = 0, n = tracks.length; i < n; i++) {
|
|
@@ -5387,7 +5405,7 @@ class Mixer {
|
|
|
5387
5405
|
for (let i = 0, n = tracks.length; i < n; i++) {
|
|
5388
5406
|
const current = tracks[i];
|
|
5389
5407
|
if (current === null || current.delay > 0) continue;
|
|
5390
|
-
let alpha =
|
|
5408
|
+
let alpha = this.alphaOf(current);
|
|
5391
5409
|
if (current.mixingFrom !== null) alpha *= this.applyMixingFrom(current, pose, i);
|
|
5392
5410
|
else if (current.trackTime >= current.trackEnd && current.next === null) alpha = 0;
|
|
5393
5411
|
const animationLast = current.animationLast;
|
|
@@ -5397,18 +5415,22 @@ class Mixer {
|
|
|
5397
5415
|
if (i === 0 && alpha === 1) {
|
|
5398
5416
|
for (const tl of timelines) {
|
|
5399
5417
|
if (tl.type === "attachment") this.applyAttachment(pose, tl, animationTime, SETUP, true);
|
|
5400
|
-
else this.applyTimeline(pose, tl, animationLast, animationTime, true, 1, SETUP, false);
|
|
5418
|
+
else this.applyTimeline(pose, tl, animationLast, animationTime, true, 1, SETUP, false, false);
|
|
5401
5419
|
}
|
|
5402
5420
|
} else {
|
|
5403
5421
|
const modes = current.timelineMode;
|
|
5404
|
-
const retain = alpha >= 0;
|
|
5405
|
-
const
|
|
5422
|
+
const retain = this.plant.attachmentsBelowHalfAlpha === true ? alpha >= 0.5 : alpha >= 0;
|
|
5423
|
+
const add = this.additiveOf(current);
|
|
5424
|
+
const shortest = add && this.plant.additiveRotateRemembered !== true;
|
|
5425
|
+
const firstFrame = !shortest && current.timelinesRotation.length !== count << 1;
|
|
5406
5426
|
if (firstFrame) current.timelinesRotation.length = count << 1;
|
|
5407
5427
|
timelines.forEach((tl, ii) => {
|
|
5408
5428
|
const from = this.fromOf(modes[ii] & MODE, i);
|
|
5409
|
-
if (tl.type === "bone" && tl.kind === "rotate") this.applyRotate(pose, tl, animationTime, alpha, from, current.timelinesRotation, ii << 1, firstFrame);
|
|
5410
|
-
else if (tl.type === "attachment")
|
|
5411
|
-
|
|
5429
|
+
if (!shortest && tl.type === "bone" && tl.kind === "rotate") this.applyRotate(pose, tl, animationTime, alpha, from, current.timelinesRotation, ii << 1, firstFrame);
|
|
5430
|
+
else if (tl.type === "attachment") {
|
|
5431
|
+
if (add && this.plant.additiveAttachmentsDirect === true) this.applyAttachmentDirect(pose, tl, animationTime, from, false);
|
|
5432
|
+
else this.applyAttachment(pose, tl, animationTime, from, retain);
|
|
5433
|
+
} else this.applyTimeline(pose, tl, animationLast, animationTime, true, alpha, from, false, add);
|
|
5412
5434
|
});
|
|
5413
5435
|
}
|
|
5414
5436
|
if (i === 0) base = animationTime;
|
|
@@ -5428,15 +5450,17 @@ class Mixer {
|
|
|
5428
5450
|
const from = to.mixingFrom;
|
|
5429
5451
|
const fromMix = from.mixingFrom !== null ? this.applyMixingFrom(from, pose, track) : 1;
|
|
5430
5452
|
const mix = this.mixOf(to);
|
|
5431
|
-
const a =
|
|
5432
|
-
const keep = 1 - mix * 1;
|
|
5453
|
+
const a = this.alphaOf(from) * fromMix;
|
|
5454
|
+
const keep = 1 - mix * (this.plant.holdIgnoresEntryAlpha === true ? 1 : this.alphaOf(to));
|
|
5433
5455
|
const alphaMix = a * (1 - mix);
|
|
5434
5456
|
const alphaHold = keep > 0 ? alphaMix / keep : a;
|
|
5435
5457
|
const timelines = from.anim.timelines;
|
|
5436
5458
|
const count = timelines.length;
|
|
5437
5459
|
const modes = from.timelineMode;
|
|
5438
5460
|
const holdMix = from.timelineHoldMix;
|
|
5439
|
-
const
|
|
5461
|
+
const add = this.additiveOf(from);
|
|
5462
|
+
const shortest = add && this.plant.additiveRotateRemembered !== true;
|
|
5463
|
+
const firstFrame = !shortest && from.timelinesRotation.length !== count << 1;
|
|
5440
5464
|
if (firstFrame) from.timelinesRotation.length = count << 1;
|
|
5441
5465
|
const animationLast = from.animationLast;
|
|
5442
5466
|
const animationTime = animationTimeOf(from);
|
|
@@ -5451,9 +5475,11 @@ class Mixer {
|
|
|
5451
5475
|
alpha = h === null || h === void 0 ? alphaHold : alphaHold * (1 - this.mixOf(h));
|
|
5452
5476
|
} else alpha = alphaMix;
|
|
5453
5477
|
from.totalAlpha += alpha;
|
|
5454
|
-
if (tl.type === "bone" && tl.kind === "rotate") this.applyRotate(pose, tl, animationTime, alpha, mixFrom, from.timelinesRotation, i << 1, firstFrame);
|
|
5455
|
-
else if (tl.type === "attachment")
|
|
5456
|
-
|
|
5478
|
+
if (!shortest && tl.type === "bone" && tl.kind === "rotate") this.applyRotate(pose, tl, animationTime, alpha, mixFrom, from.timelinesRotation, i << 1, firstFrame);
|
|
5479
|
+
else if (tl.type === "attachment") {
|
|
5480
|
+
if (add && this.plant.additiveAttachmentsDirect === true) this.applyAttachmentDirect(pose, tl, animationTime, mixFrom, true);
|
|
5481
|
+
else this.applyAttachment(pose, tl, animationTime, mixFrom, false);
|
|
5482
|
+
} else this.applyTimeline(pose, tl, animationLast, animationTime, fire, alpha, mixFrom, true, add);
|
|
5457
5483
|
});
|
|
5458
5484
|
from.nextAnimationLast = animationTime;
|
|
5459
5485
|
from.nextTrackLast = from.trackTime;
|
|
@@ -5480,14 +5506,20 @@ class Mixer {
|
|
|
5480
5506
|
const holdMix = entry.timelineHoldMix;
|
|
5481
5507
|
holdMix.length = 0;
|
|
5482
5508
|
const to = entry.mixingTo;
|
|
5509
|
+
const add = this.holdsAdditive(entry);
|
|
5510
|
+
const toAdds = this.holdsAdditive(to);
|
|
5483
5511
|
timelines.forEach((tl, i) => {
|
|
5484
5512
|
const from = this.propertyFrom(track, tl.ids);
|
|
5513
|
+
if (add && addable(tl)) {
|
|
5514
|
+
modes[i] = from;
|
|
5515
|
+
return;
|
|
5516
|
+
}
|
|
5485
5517
|
let mode;
|
|
5486
|
-
if (to === null || tl.instant || !hasAny(to.anim, tl.ids)) mode = from;
|
|
5518
|
+
if (to === null || tl.instant || toAdds && addable(tl) || !hasAny(to.anim, tl.ids)) mode = from;
|
|
5487
5519
|
else {
|
|
5488
5520
|
mode = from | HOLD;
|
|
5489
5521
|
for (let next = to.mixingTo; next !== null; next = next.mixingTo) {
|
|
5490
|
-
if (!hasAny(next.anim, tl.ids)) {
|
|
5522
|
+
if (this.holdsAdditive(next) && addable(tl) || !hasAny(next.anim, tl.ids)) {
|
|
5491
5523
|
if (next.mixDuration > 0) holdMix[i] = next;
|
|
5492
5524
|
break;
|
|
5493
5525
|
}
|
|
@@ -5523,15 +5555,15 @@ class Mixer {
|
|
|
5523
5555
|
const channel2 = this.plant.channel ?? channelAt;
|
|
5524
5556
|
return keys[index].values.map((_v, c) => channel2(keys, index, c, t));
|
|
5525
5557
|
}
|
|
5526
|
-
/** `Timeline.apply` for every timeline but an attachment's and a rotation mixed below full weight. */
|
|
5527
|
-
applyTimeline(pose, tl, lastTime, time, fire, alpha, from, out) {
|
|
5558
|
+
/** `Timeline.apply` for every timeline but an attachment's and a rotation mixed below full weight; `add` is the entry's, read only by the timelines the runtime adds (`addable`). */
|
|
5559
|
+
applyTimeline(pose, tl, lastTime, time, fire, alpha, from, out, add) {
|
|
5528
5560
|
switch (tl.type) {
|
|
5529
5561
|
case "bone":
|
|
5530
|
-
return this.applyBone(pose, tl, time, alpha, from, out);
|
|
5562
|
+
return this.applyBone(pose, tl, time, alpha, from, out, add);
|
|
5531
5563
|
case "colour":
|
|
5532
5564
|
return this.applyColour(pose, tl, time, alpha, from);
|
|
5533
5565
|
case "deform":
|
|
5534
|
-
return this.applyDeform(pose, tl, time, alpha, from);
|
|
5566
|
+
return this.applyDeform(pose, tl, time, alpha, from, add);
|
|
5535
5567
|
case "events":
|
|
5536
5568
|
if (fire) pose.events.push(...eventsFired(tl.keys, lastTime, time, rawNumber, time < lastTime));
|
|
5537
5569
|
return;
|
|
@@ -5539,7 +5571,7 @@ class Mixer {
|
|
|
5539
5571
|
return;
|
|
5540
5572
|
}
|
|
5541
5573
|
}
|
|
5542
|
-
applyBone(pose, tl, time, alpha, from, out) {
|
|
5574
|
+
applyBone(pose, tl, time, alpha, from, out, add) {
|
|
5543
5575
|
const bone2 = pose.bone(tl.bone);
|
|
5544
5576
|
if (bone2 === null) return;
|
|
5545
5577
|
const setup = this.doc.bones[tl.bone];
|
|
@@ -5562,7 +5594,7 @@ class Mixer {
|
|
|
5562
5594
|
const relative = (field, i) => {
|
|
5563
5595
|
const s = setup[field] ?? 0;
|
|
5564
5596
|
const c = bone2[field] ?? 0;
|
|
5565
|
-
bone2[field] = v === null ? beforeFirstKey(from, alpha, c, s) : from === SETUP ? s + v[i] * alpha : c + (v[i] + s - c) * alpha;
|
|
5597
|
+
bone2[field] = v === null ? beforeFirstKey(from, alpha, c, s) : from === SETUP ? s + v[i] * alpha : c + (add ? v[i] : v[i] + s - c) * alpha;
|
|
5566
5598
|
};
|
|
5567
5599
|
const pair = (fx, fy) => {
|
|
5568
5600
|
const sx = setup[fx] ?? 0;
|
|
@@ -5580,6 +5612,9 @@ class Mixer {
|
|
|
5580
5612
|
if (from === SETUP) {
|
|
5581
5613
|
bone2[fx] = sx + v[0] * alpha;
|
|
5582
5614
|
bone2[fy] = sy + v[1] * alpha;
|
|
5615
|
+
} else if (add) {
|
|
5616
|
+
bone2[fx] = (bone2[fx] ?? 0) + v[0] * alpha;
|
|
5617
|
+
bone2[fy] = (bone2[fy] ?? 0) + v[1] * alpha;
|
|
5583
5618
|
} else {
|
|
5584
5619
|
bone2[fx] = (bone2[fx] ?? 0) + (sx + v[0] - (bone2[fx] ?? 0)) * alpha;
|
|
5585
5620
|
bone2[fy] = (bone2[fy] ?? 0) + (sy + v[1] - (bone2[fy] ?? 0)) * alpha;
|
|
@@ -5588,7 +5623,7 @@ class Mixer {
|
|
|
5588
5623
|
const scale = (field, i) => {
|
|
5589
5624
|
const s = setup[field] ?? 1;
|
|
5590
5625
|
const c = bone2[field] ?? 1;
|
|
5591
|
-
bone2[field] = v === null ? beforeFirstKey(from, alpha, c, s) : scaleValue(v[i] * s, alpha, from, out, c, s);
|
|
5626
|
+
bone2[field] = v === null ? beforeFirstKey(from, alpha, c, s) : scaleValue(v[i] * s, alpha, from, out, add, c, s);
|
|
5592
5627
|
};
|
|
5593
5628
|
switch (tl.kind) {
|
|
5594
5629
|
case "rotate":
|
|
@@ -5624,8 +5659,8 @@ class Mixer {
|
|
|
5624
5659
|
bone2.scaleY = (bone2.scaleY ?? 1) + (sy - (bone2.scaleY ?? 1)) * alpha;
|
|
5625
5660
|
}
|
|
5626
5661
|
} else {
|
|
5627
|
-
bone2.scaleX = scaleValue(v[0] * (setup.scaleX ?? 1), alpha, from, out, bone2.scaleX ?? 1, setup.scaleX ?? 1);
|
|
5628
|
-
bone2.scaleY = scaleValue(v[1] * (setup.scaleY ?? 1), alpha, from, out, bone2.scaleY ?? 1, setup.scaleY ?? 1);
|
|
5662
|
+
bone2.scaleX = scaleValue(v[0] * (setup.scaleX ?? 1), alpha, from, out, add, bone2.scaleX ?? 1, setup.scaleX ?? 1);
|
|
5663
|
+
bone2.scaleY = scaleValue(v[1] * (setup.scaleY ?? 1), alpha, from, out, add, bone2.scaleY ?? 1, setup.scaleY ?? 1);
|
|
5629
5664
|
}
|
|
5630
5665
|
break;
|
|
5631
5666
|
case "scalex":
|
|
@@ -5640,7 +5675,7 @@ class Mixer {
|
|
|
5640
5675
|
applyRotate(pose, tl, time, alpha, from, rotations, i, firstFrame) {
|
|
5641
5676
|
if (firstFrame) rotations[i] = 0;
|
|
5642
5677
|
if (alpha === 1) {
|
|
5643
|
-
this.applyBone(pose, tl, time, 1, from, false);
|
|
5678
|
+
this.applyBone(pose, tl, time, 1, from, false, false);
|
|
5644
5679
|
return;
|
|
5645
5680
|
}
|
|
5646
5681
|
const bone2 = pose.bone(tl.bone);
|
|
@@ -5712,6 +5747,14 @@ class Mixer {
|
|
|
5712
5747
|
if (retain) s.attachmentState = ATTACH_RETAIN;
|
|
5713
5748
|
else if (!setup) s.attachmentState = ATTACH_SETUP;
|
|
5714
5749
|
}
|
|
5750
|
+
/** `AttachmentTimeline.apply` — the plain timeline, writing no attachment state: only `additiveAttachmentsDirect` reaches it, the reading `CO52` rejects. */
|
|
5751
|
+
applyAttachmentDirect(pose, tl, time, from, out) {
|
|
5752
|
+
if (!pose.slotLive(tl.slot)) return;
|
|
5753
|
+
const index = (this.plant.search ?? keyIndexAt)(tl.keys, time);
|
|
5754
|
+
if (out || index < 0) {
|
|
5755
|
+
if (from !== CURRENT) pose.setAttachment(tl.slot, this.doc.slots[tl.slot].setup);
|
|
5756
|
+
} else pose.setAttachment(tl.slot, tl.keys[index].name ?? null);
|
|
5757
|
+
}
|
|
5715
5758
|
applyColour(pose, tl, time, alpha, from) {
|
|
5716
5759
|
if (!pose.slotLive(tl.slot)) return;
|
|
5717
5760
|
const slot = this.doc.slots[tl.slot];
|
|
@@ -5807,7 +5850,7 @@ class Mixer {
|
|
|
5807
5850
|
}
|
|
5808
5851
|
}
|
|
5809
5852
|
/** `DeformTimeline.apply`: every live slot showing the record the timeline keys, its deform array mixed. */
|
|
5810
|
-
applyDeform(pose, tl, time, alpha, from) {
|
|
5853
|
+
applyDeform(pose, tl, time, alpha, from, add) {
|
|
5811
5854
|
const keys = tl.timeline.deform;
|
|
5812
5855
|
if (keys === null) return;
|
|
5813
5856
|
pose.slots.forEach((s, k) => {
|
|
@@ -5837,12 +5880,26 @@ class Mixer {
|
|
|
5837
5880
|
}
|
|
5838
5881
|
return;
|
|
5839
5882
|
}
|
|
5840
|
-
|
|
5841
|
-
|
|
5883
|
+
const fromSetup = mixFrom === SETUP;
|
|
5884
|
+
if (alpha === 1) {
|
|
5885
|
+
if (add && !fromSetup) {
|
|
5886
|
+
const current = s.deform;
|
|
5887
|
+
if (!vertices.weighted) {
|
|
5888
|
+
const setup = setupArray(vertices);
|
|
5889
|
+
s.deform = current.map((c, i) => c + (target[i] - setup[i]));
|
|
5890
|
+
} else s.deform = current.map((c, i) => c + target[i]);
|
|
5891
|
+
} else s.deform = [...target];
|
|
5892
|
+
} else if (fromSetup) {
|
|
5842
5893
|
if (!vertices.weighted) {
|
|
5843
5894
|
const setup = setupArray(vertices);
|
|
5844
5895
|
s.deform = target.map((v, i) => setup[i] + (v - setup[i]) * alpha);
|
|
5845
5896
|
} else s.deform = target.map((v) => v * alpha);
|
|
5897
|
+
} else if (add) {
|
|
5898
|
+
const current = s.deform;
|
|
5899
|
+
if (!vertices.weighted) {
|
|
5900
|
+
const setup = setupArray(vertices);
|
|
5901
|
+
s.deform = current.map((c, i) => c + (target[i] - setup[i]) * alpha);
|
|
5902
|
+
} else s.deform = current.map((c, i) => c + target[i] * alpha);
|
|
5846
5903
|
} else {
|
|
5847
5904
|
const current = s.deform;
|
|
5848
5905
|
s.deform = current.map((c, i) => c + (target[i] - c) * alpha);
|
|
@@ -5855,9 +5912,10 @@ function beforeFirstKey(from, alpha, current, setup) {
|
|
|
5855
5912
|
if (from === FIRST) return current + (setup - current) * alpha;
|
|
5856
5913
|
return current;
|
|
5857
5914
|
}
|
|
5858
|
-
function scaleValue(value, alpha, from, out, current, setup) {
|
|
5859
|
-
if (alpha === 1) return value;
|
|
5915
|
+
function scaleValue(value, alpha, from, out, add, current, setup) {
|
|
5916
|
+
if (alpha === 1 && !add) return value;
|
|
5860
5917
|
let base = from === SETUP ? setup : current;
|
|
5918
|
+
if (add) return base + (value - setup) * alpha;
|
|
5861
5919
|
if (out) return base + (Math.abs(value) * Math.sign(base) - base) * alpha;
|
|
5862
5920
|
base = Math.abs(base) * Math.sign(value);
|
|
5863
5921
|
return base + (value - base) * alpha;
|
|
@@ -5878,6 +5936,16 @@ function loopOf(options, call) {
|
|
|
5878
5936
|
if (typeof options !== "object" || options === null || typeof options.loop !== "boolean") throw new CoreInputError(`the live tracks' ${call} options.loop is ${JSON.stringify(options?.loop)}, not true or false`);
|
|
5879
5937
|
return options.loop;
|
|
5880
5938
|
}
|
|
5939
|
+
function alphaValue(v, what, call) {
|
|
5940
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 1) throw new CoreInputError(`the live tracks' ${call} ${what} is ${typeof v === "number" ? String(v) : JSON.stringify(v)}, not a number from 0 to 1`);
|
|
5941
|
+
return v;
|
|
5942
|
+
}
|
|
5943
|
+
function blendOf(options, call) {
|
|
5944
|
+
const alpha = options.alpha === void 0 ? 1 : alphaValue(options.alpha, "options.alpha", call);
|
|
5945
|
+
const additive = options.additive;
|
|
5946
|
+
if (additive !== void 0 && typeof additive !== "boolean") throw new CoreInputError(`the live tracks' ${call} options.additive is ${JSON.stringify(additive)}, not true or false — an entry adds or it does not`);
|
|
5947
|
+
return { alpha, additive: additive === true };
|
|
5948
|
+
}
|
|
5881
5949
|
function delayOf(v, call) {
|
|
5882
5950
|
if (typeof v !== "number" || !Number.isFinite(v)) throw new CoreInputError(`the live tracks' ${call} delay is ${String(v)}, not a finite number`);
|
|
5883
5951
|
return v;
|
|
@@ -5911,8 +5979,11 @@ class Tracks {
|
|
|
5911
5979
|
const i = trackIndex(track, "setAnimation");
|
|
5912
5980
|
const loop = loopOf(options, "setAnimation");
|
|
5913
5981
|
const mix = finiteAtOrAbove0(options.mixDuration, "mixDuration", "setAnimation");
|
|
5982
|
+
const blend = blendOf(options, "setAnimation");
|
|
5914
5983
|
const entry = this.mixer.setAnimation(i, this.mixer.animation(animation), loop);
|
|
5915
5984
|
entry.mixDuration = mix;
|
|
5985
|
+
entry.alpha = blend.alpha;
|
|
5986
|
+
entry.additive = blend.additive;
|
|
5916
5987
|
}
|
|
5917
5988
|
setEmptyAnimation(track, mixDuration) {
|
|
5918
5989
|
this.live("setEmptyAnimation");
|
|
@@ -5928,8 +5999,11 @@ class Tracks {
|
|
|
5928
5999
|
const loop = loopOf(options, "addAnimation");
|
|
5929
6000
|
const mix = finiteAtOrAbove0(options.mixDuration, "mixDuration", "addAnimation");
|
|
5930
6001
|
const delay = delayOf(options.delay, "addAnimation");
|
|
6002
|
+
const blend = blendOf(options, "addAnimation");
|
|
5931
6003
|
const entry = this.mixer.addAnimation(i, this.mixer.animation(animation), loop, delay);
|
|
5932
6004
|
this.mixer.setMixDuration(entry, mix, delay);
|
|
6005
|
+
entry.alpha = blend.alpha;
|
|
6006
|
+
entry.additive = blend.additive;
|
|
5933
6007
|
}
|
|
5934
6008
|
addEmptyAnimation(track, mixDuration, delay) {
|
|
5935
6009
|
this.live("addEmptyAnimation");
|
|
@@ -5941,10 +6015,18 @@ class Tracks {
|
|
|
5941
6015
|
entry.mixDuration = mix;
|
|
5942
6016
|
entry.trackEnd = mix;
|
|
5943
6017
|
}
|
|
6018
|
+
setAlpha(track, alpha) {
|
|
6019
|
+
this.live("setAlpha");
|
|
6020
|
+
const i = trackIndex(track, "setAlpha");
|
|
6021
|
+
const a = alphaValue(alpha, "alpha", "setAlpha");
|
|
6022
|
+
const e = this.mixer.tracks[i] ?? null;
|
|
6023
|
+
if (e === null) throw new CoreInputError(`the live tracks' setAlpha names track ${i}, which plays nothing — there is no entry to weigh`);
|
|
6024
|
+
e.alpha = a;
|
|
6025
|
+
}
|
|
5944
6026
|
current(track) {
|
|
5945
6027
|
const e = this.mixer.tracks[trackIndex(track, "current")] ?? null;
|
|
5946
6028
|
if (e === null) return null;
|
|
5947
|
-
return { animation: e.anim.name, loop: e.loop, trackTime: e.trackTime, animationTime: animationTimeOf(e), duration: e.anim.duration };
|
|
6029
|
+
return { animation: e.anim.name, loop: e.loop, trackTime: e.trackTime, animationTime: animationTimeOf(e), duration: e.anim.duration, alpha: e.alpha, additive: e.additive };
|
|
5948
6030
|
}
|
|
5949
6031
|
start() {
|
|
5950
6032
|
this.live("start");
|
|
@@ -8342,6 +8424,10 @@ class RigPlayer {
|
|
|
8342
8424
|
loop = true;
|
|
8343
8425
|
/** One past the highest track touched — how many `current(track)` reads a step takes. */
|
|
8344
8426
|
trackCount = 0;
|
|
8427
|
+
/** `RigPlayerOptions.tracks`: every play opens the live tracks, never the single track. */
|
|
8428
|
+
trackMode;
|
|
8429
|
+
/** Set once anything beyond a plain play on track 0 happened: a mix, another track, a queue, a clear. `seek` and `skin` then cannot replay and refuse. */
|
|
8430
|
+
beyondBase = false;
|
|
8345
8431
|
paused = false;
|
|
8346
8432
|
pose = null;
|
|
8347
8433
|
setup = null;
|
|
@@ -8364,6 +8450,7 @@ class RigPlayer {
|
|
|
8364
8450
|
this.view = options.skin === void 0 ? underNoSkin(read.doc) : underSkin(read.doc, options.skin);
|
|
8365
8451
|
this.source = frameSource(this.view, read.parsed);
|
|
8366
8452
|
this.seekStep = options.seekStep ?? 1 / 60;
|
|
8453
|
+
this.trackMode = options.tracks === true;
|
|
8367
8454
|
if (!(this.seekStep > 0)) throw new Error(`seekStep must be a positive number of seconds, got ${String(options.seekStep)}`);
|
|
8368
8455
|
this.root = root;
|
|
8369
8456
|
this.assets = assets;
|
|
@@ -8433,8 +8520,12 @@ class RigPlayer {
|
|
|
8433
8520
|
const track = options.track ?? 0;
|
|
8434
8521
|
const loop = options.loop ?? true;
|
|
8435
8522
|
const mix = options.mix ?? 0;
|
|
8523
|
+
const alpha = options.alpha ?? 1;
|
|
8524
|
+
const additive = options.additive ?? false;
|
|
8436
8525
|
this.paused = false;
|
|
8437
|
-
|
|
8526
|
+
const plain = track === 0 && mix === 0 && alpha === 1 && !additive;
|
|
8527
|
+
if (!plain) this.beyondBase = true;
|
|
8528
|
+
if (this.tracks === null && plain && !this.trackMode) {
|
|
8438
8529
|
this.animation = name;
|
|
8439
8530
|
this.loop = loop;
|
|
8440
8531
|
this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook });
|
|
@@ -8442,7 +8533,7 @@ class RigPlayer {
|
|
|
8442
8533
|
return;
|
|
8443
8534
|
}
|
|
8444
8535
|
const tracks = this.compose();
|
|
8445
|
-
tracks.setAnimation(track, name, { loop, mixDuration: mix });
|
|
8536
|
+
tracks.setAnimation(track, name, { loop, mixDuration: mix, alpha, additive });
|
|
8446
8537
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8447
8538
|
if (track === 0) {
|
|
8448
8539
|
this.animation = name;
|
|
@@ -8458,8 +8549,9 @@ class RigPlayer {
|
|
|
8458
8549
|
*/
|
|
8459
8550
|
queue(name, options = {}) {
|
|
8460
8551
|
const track = options.track ?? 0;
|
|
8552
|
+
this.beyondBase = true;
|
|
8461
8553
|
const tracks = this.compose();
|
|
8462
|
-
tracks.addAnimation(track, name, { loop: options.loop ?? true, mixDuration: options.mix ?? 0, delay: options.delay ?? 0 });
|
|
8554
|
+
tracks.addAnimation(track, name, { loop: options.loop ?? true, mixDuration: options.mix ?? 0, delay: options.delay ?? 0, alpha: options.alpha ?? 1, additive: options.additive ?? false });
|
|
8463
8555
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8464
8556
|
this.pose = tracks.step(0);
|
|
8465
8557
|
}
|
|
@@ -8469,6 +8561,7 @@ class RigPlayer {
|
|
|
8469
8561
|
* setup pose. The pose the player holds is re-posed at once.
|
|
8470
8562
|
*/
|
|
8471
8563
|
clear(track = 0, options = {}) {
|
|
8564
|
+
this.beyondBase = true;
|
|
8472
8565
|
const tracks = this.compose();
|
|
8473
8566
|
tracks.setEmptyAnimation(track, options.mix ?? 0);
|
|
8474
8567
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
@@ -8481,31 +8574,48 @@ class RigPlayer {
|
|
|
8481
8574
|
* its last `mix` seconds.
|
|
8482
8575
|
*/
|
|
8483
8576
|
queueClear(track = 0, options = {}) {
|
|
8577
|
+
this.beyondBase = true;
|
|
8484
8578
|
const tracks = this.compose();
|
|
8485
8579
|
tracks.addEmptyAnimation(track, options.mix ?? 0, options.delay ?? 0);
|
|
8486
8580
|
this.trackCount = Math.max(this.trackCount, track + 1);
|
|
8487
8581
|
this.pose = tracks.step(0);
|
|
8488
8582
|
}
|
|
8583
|
+
/**
|
|
8584
|
+
* Sets the weight of `track`'s current entry, from 0 to 1, from the next pose
|
|
8585
|
+
* on — the pose the player holds is re-posed at once, so a held player shows
|
|
8586
|
+
* it on its next `draw`. An entry it is mixing from keeps its own weight. A
|
|
8587
|
+
* track playing nothing, or a weight outside 0..1, is refused in the core's
|
|
8588
|
+
* words. On the single track this composes: the player moves onto the tracks.
|
|
8589
|
+
*/
|
|
8590
|
+
setAlpha(track, alpha) {
|
|
8591
|
+
this.beyondBase = true;
|
|
8592
|
+
const tracks = this.compose();
|
|
8593
|
+
tracks.setAlpha(track, alpha);
|
|
8594
|
+
this.pose = tracks.step(0);
|
|
8595
|
+
}
|
|
8489
8596
|
/** `track`'s current entry, or null where it plays nothing (and before `play`). */
|
|
8490
8597
|
entry(track = 0) {
|
|
8491
8598
|
if (this.tracks !== null) {
|
|
8492
8599
|
const view = this.tracks.current(track);
|
|
8493
|
-
return view === null ? null : { animation: view.animation, loop: view.loop, trackTime: view.trackTime, animationTime: view.animationTime, duration: view.duration };
|
|
8600
|
+
return view === null ? null : { animation: view.animation, loop: view.loop, trackTime: view.trackTime, animationTime: view.animationTime, duration: view.duration, alpha: view.alpha, additive: view.additive };
|
|
8494
8601
|
}
|
|
8495
8602
|
const single = this.track;
|
|
8496
8603
|
if (track !== 0 || single === null) return null;
|
|
8497
|
-
return { animation: this.animation, loop: this.loop, trackTime: single.trackTime, animationTime: single.animationTime, duration: single.duration };
|
|
8604
|
+
return { animation: this.animation, loop: this.loop, trackTime: single.trackTime, animationTime: single.animationTime, duration: single.duration, alpha: 1, additive: false };
|
|
8498
8605
|
}
|
|
8499
8606
|
/** Whether the player is composed: on the live tracks rather than the single track. */
|
|
8500
8607
|
get composed() {
|
|
8501
8608
|
return this.tracks !== null;
|
|
8502
8609
|
}
|
|
8503
8610
|
/**
|
|
8504
|
-
* The live tracks, opened by the first composing call
|
|
8505
|
-
* play
|
|
8506
|
-
*
|
|
8507
|
-
*
|
|
8508
|
-
*
|
|
8611
|
+
* The live tracks, opened by the first composing call (or by the first
|
|
8612
|
+
* `play` under `tracks: true`). A single track in play is carried over by
|
|
8613
|
+
* replay: track 0 set to its animation, pose 0 taken by `start()` (the
|
|
8614
|
+
* player's — nothing steps before it) and the tracks stepped to the single
|
|
8615
|
+
* track's time in `seekStep` increments, so a physics rig's history
|
|
8616
|
+
* restarts there and nothing is fired on the way. The core offers no
|
|
8617
|
+
* hand-over of a live track's state, so that replay costs one pose per
|
|
8618
|
+
* `seekStep` of time played — which `tracks: true` avoids entirely.
|
|
8509
8619
|
*/
|
|
8510
8620
|
compose() {
|
|
8511
8621
|
if (this.tracks !== null) return this.tracks;
|
|
@@ -8542,12 +8652,20 @@ class RigPlayer {
|
|
|
8542
8652
|
seek(time) {
|
|
8543
8653
|
if (this.animation === null) throw new Error("seek before play: no animation is open");
|
|
8544
8654
|
if (!(time >= 0) || !Number.isFinite(time)) throw new Error(`seek time must be a finite time at or above 0, got ${String(time)}`);
|
|
8545
|
-
if (this.
|
|
8655
|
+
if (this.beyondBase) throw new Error("seek is defined for a single animation on track 0; after a mix, another track, a queue or a clear the player cannot replay to a time — step it forward with frame/advance instead");
|
|
8546
8656
|
this.reopen();
|
|
8547
8657
|
this.stepTo(time);
|
|
8548
8658
|
}
|
|
8549
|
-
/**
|
|
8659
|
+
/** Whatever is open, opened again with track 0's animation as `play` last set it, at pose 0: the single track, or the tracks under `tracks: true`. */
|
|
8550
8660
|
reopen() {
|
|
8661
|
+
if (this.tracks !== null) {
|
|
8662
|
+
const tracks = openTracks(this.view, { reset: "animation", adjust: this.adjustHook });
|
|
8663
|
+
tracks.setAnimation(0, this.animation, { loop: this.loop, mixDuration: 0 });
|
|
8664
|
+
this.trackCount = 1;
|
|
8665
|
+
this.tracks = tracks;
|
|
8666
|
+
this.pose = tracks.start();
|
|
8667
|
+
return;
|
|
8668
|
+
}
|
|
8551
8669
|
this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook });
|
|
8552
8670
|
this.pose = this.track.first;
|
|
8553
8671
|
}
|
|
@@ -8616,14 +8734,14 @@ class RigPlayer {
|
|
|
8616
8734
|
}
|
|
8617
8735
|
set skin(name) {
|
|
8618
8736
|
if (name === this.skinName) return;
|
|
8619
|
-
if (this.
|
|
8737
|
+
if (this.beyondBase) throw new Error("skin cannot change after a mix, another track, a queue or a clear: the player cannot replay those to the current time — set it before composing, or play again");
|
|
8620
8738
|
const view = name === void 0 ? underNoSkin(this.read.doc) : underSkin(this.read.doc, name);
|
|
8621
8739
|
this.view = view;
|
|
8622
8740
|
this.source = frameSource(view, this.read.parsed);
|
|
8623
8741
|
this.skinName = name;
|
|
8624
8742
|
this.setup = null;
|
|
8625
|
-
if (this.animation !== null && this.track !== null) {
|
|
8626
|
-
const time = this.
|
|
8743
|
+
if (this.animation !== null && (this.track !== null || this.tracks !== null)) {
|
|
8744
|
+
const time = this.time;
|
|
8627
8745
|
this.reopen();
|
|
8628
8746
|
this.stepTo(time);
|
|
8629
8747
|
}
|