@rig-c/core 2.76.0 → 2.78.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/docs/AUTHORING.md +19 -16
- package/package.json +1 -1
- package/src/runtime/curves.ts +16 -7
- package/src/runtime/index.ts +6 -5
- package/src/runtime/model.ts +204 -29
- package/src/runtime/pose.ts +613 -25
package/src/runtime/pose.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The runtime poser's per-instance half (issue #1591, stage
|
|
2
|
+
* The runtime poser's per-instance half (issue #1591, stage 5): one
|
|
3
3
|
* `RuntimePose` per skeleton on screen, every buffer allocated when it is
|
|
4
4
|
* made and overwritten in place on every pose, so that posing a frame
|
|
5
5
|
* allocates nothing.
|
|
@@ -9,22 +9,24 @@
|
|
|
9
9
|
* The oracle's sample, as `src/core/animation.ts` poses it (`poseAnimations`):
|
|
10
10
|
* every pose is posed from the setup pose — the bones' setup local values,
|
|
11
11
|
* then the animation's bone timelines at `t` at full weight, then the world
|
|
12
|
-
* transforms parents first, then the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* transforms parents first, then the draw order and each slot's light and dark
|
|
13
|
+
* colour, attachment and own deform timelines, then the ik, transform, path,
|
|
14
|
+
* physics and slider constraints in the document's order with their timelines'
|
|
15
|
+
* values at `t`, each followed by the bones below what it moved posed again,
|
|
16
|
+
* then the sliders' draw-order, slot and deform timelines; then, when asked,
|
|
17
|
+
* every shown attachment's world vertices. Nothing of one unstepped pose
|
|
18
|
+
* survives but key cursors. A stepped pose also keeps its physics clock,
|
|
19
|
+
* offsets, velocities and lags; sliders select values within this pose rather
|
|
20
|
+
* than advancing an independent track.
|
|
19
21
|
*
|
|
20
22
|
* ## The arithmetic is the oracle's
|
|
21
23
|
*
|
|
22
24
|
* Every formula below is the one the named function of `src/core/` computes,
|
|
23
25
|
* in its order of operations, on the same doubles: a bone's frame and its
|
|
24
26
|
* five inherit modes (`world.ts`), the timelines (`animation.ts`), the one-
|
|
25
|
-
* and two-bone ik, the world-space transform, the
|
|
26
|
-
* local values and the update order (`constraints.ts`),
|
|
27
|
-
* and a mesh's vertices (`vertices.ts`). Each of those orders was fixed
|
|
27
|
+
* and two-bone ik, the world-space transform, the path constraint (`constraints_path.ts`),
|
|
28
|
+
* the read-back of a moved bone's local values and the update order (`constraints.ts`),
|
|
29
|
+
* a region's corners and a mesh's vertices (`vertices.ts`). Each of those orders was fixed
|
|
28
30
|
* there by measurement against spine-core 4.3.13 to the last bit; the runtime
|
|
29
31
|
* does not re-measure them, it is held to the oracle at tolerance 0
|
|
30
32
|
* (`tools/core_gate.ts`, *The runtime poser*), so an order changed here is a
|
|
@@ -32,7 +34,7 @@
|
|
|
32
34
|
*
|
|
33
35
|
* ⚠️ The exactness hazards, each kept as the oracle has it: the setup value
|
|
34
36
|
* plus 0 before a timeline's first key (which turns a `-0` setup into `0`);
|
|
35
|
-
* a keyed ik or
|
|
37
|
+
* a keyed ik, transform or path value reaching the pose through the setup blend
|
|
36
38
|
* `setup + (value − setup)·1`; `Math.PI` for a folded uniform two-bone ik and
|
|
37
39
|
* the runtime's 3.1415927 everywhere else; the `(−180, 180]` wrap; a mix of
|
|
38
40
|
* exactly 0 touching nothing; an inactive bone posed as zeros by the
|
|
@@ -62,6 +64,13 @@ import {
|
|
|
62
64
|
MODE_NO_SCALE_OR_REFLECTION,
|
|
63
65
|
MODE_NORMAL,
|
|
64
66
|
MODE_ONLY_TRANSLATION,
|
|
67
|
+
PATH_POSITION_PERCENT,
|
|
68
|
+
PATH_ROTATE_CHAIN,
|
|
69
|
+
PATH_ROTATE_CHAIN_SCALE,
|
|
70
|
+
PATH_ROTATE_TANGENT,
|
|
71
|
+
PATH_SPACING_LENGTH,
|
|
72
|
+
PATH_SPACING_PERCENT,
|
|
73
|
+
PATH_SPACING_PROPORTIONAL,
|
|
65
74
|
RAD,
|
|
66
75
|
RuntimeInputError,
|
|
67
76
|
SCALE_Y_UNIFORM,
|
|
@@ -74,9 +83,11 @@ import {
|
|
|
74
83
|
W_Y,
|
|
75
84
|
WORLDS,
|
|
76
85
|
type RuntimeAnimation,
|
|
86
|
+
type RuntimeConstraint,
|
|
77
87
|
type RuntimeEvent,
|
|
78
88
|
type RuntimeIk,
|
|
79
89
|
type RuntimeModel,
|
|
90
|
+
type RuntimePath,
|
|
80
91
|
type RuntimeTransform,
|
|
81
92
|
type RuntimeSlider,
|
|
82
93
|
type RuntimePhysicsTimeline,
|
|
@@ -84,6 +95,7 @@ import {
|
|
|
84
95
|
|
|
85
96
|
const DEG = RUNTIME_DEG;
|
|
86
97
|
const TWO_PI = 2 * RUNTIME_PI;
|
|
98
|
+
const PATH_EPSILON = 0.00001;
|
|
87
99
|
// The transform properties, by `TRANSFORM_PROPERTIES` index (src/core/constraints.ts).
|
|
88
100
|
const P_ROTATE = 0;
|
|
89
101
|
const P_X = 1;
|
|
@@ -107,7 +119,7 @@ const clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
|
107
119
|
export interface RuntimePoseOptions { readonly clock?: (dt: number) => number }
|
|
108
120
|
export type RuntimePhysicsPhase = 'none' | 'reset' | 'update';
|
|
109
121
|
|
|
110
|
-
/** One skeleton's pose, every value in place. Read `world`, `light`, `shown` and `vertices` after a pose. */
|
|
122
|
+
/** One skeleton's pose, every value in place. Read `world`, `light`, `dark`, `shown`, `drawOrder` and `vertices` after a pose. */
|
|
111
123
|
export class RuntimePose {
|
|
112
124
|
readonly model: RuntimeModel;
|
|
113
125
|
/** Every bone's local values, `LOCALS` per bone (`L_*`). */
|
|
@@ -116,8 +128,12 @@ export class RuntimePose {
|
|
|
116
128
|
readonly world: Float64Array;
|
|
117
129
|
/** Every slot's light colour, four per slot. */
|
|
118
130
|
readonly light: Float64Array;
|
|
131
|
+
/** Every slot's dark colour, three per slot (`r2`, `g2`, `b2`), or zeros when `model.slots[s].dark === null`. */
|
|
132
|
+
readonly dark: Float64Array;
|
|
119
133
|
/** What every slot shows: an index into `model.attachments`, or −1 for nothing. */
|
|
120
134
|
readonly shown: Int32Array;
|
|
135
|
+
/** The slot indices in the live frame's draw order (`0 .. model.slots.length - 1`), updated in place. */
|
|
136
|
+
readonly drawOrder: Int32Array;
|
|
121
137
|
/** Every attachment's world vertices at its `offset`, written by `computeVertices` for the attachments shown. */
|
|
122
138
|
readonly vertices: Float64Array;
|
|
123
139
|
/** Held/interpolated deform values, one pair per binding when weighted. */
|
|
@@ -134,11 +150,20 @@ export class RuntimePose {
|
|
|
134
150
|
|
|
135
151
|
private current: RuntimeAnimation | null = null;
|
|
136
152
|
private readonly cursors: Int32Array;
|
|
137
|
-
// The constraints' values as posed: an ik's mix, softness and flags, a transform's six mixes.
|
|
153
|
+
// The constraints' values as posed: an ik's mix, softness and flags, a transform's six mixes, a path's position, spacing and three mixes.
|
|
138
154
|
private readonly ikMix: Float64Array;
|
|
139
155
|
private readonly ikSoftness: Float64Array;
|
|
140
156
|
private readonly ikFlags: Uint8Array;
|
|
141
157
|
private readonly mixes: Float64Array;
|
|
158
|
+
private readonly pathValues: Float64Array;
|
|
159
|
+
private readonly pathSlotSign: Int8Array;
|
|
160
|
+
private readonly pathSpaces: Float64Array;
|
|
161
|
+
private readonly pathLengths: Float64Array;
|
|
162
|
+
private readonly pathPositions: Float64Array;
|
|
163
|
+
private readonly pathWorldPts: Float64Array;
|
|
164
|
+
private readonly pathChain: Float64Array;
|
|
165
|
+
private readonly pathCurveEnds: Float64Array;
|
|
166
|
+
private readonly pathSegmentEnds = new Float64Array(10);
|
|
142
167
|
// The update order's scratch: the bones a constraint changed and moved in world space, in order (repeats kept, as the oracle keeps them), and per-bone marks.
|
|
143
168
|
private readonly changed: Int32Array;
|
|
144
169
|
private readonly inWorld: Int32Array;
|
|
@@ -156,6 +181,7 @@ export class RuntimePose {
|
|
|
156
181
|
private readonly hasPhysics: boolean;
|
|
157
182
|
private readonly physicsStates: PhysicsState[];
|
|
158
183
|
private readonly physicsRecords: Array<CorePhysicsRecord | null>;
|
|
184
|
+
private readonly physicsScope: Uint8Array;
|
|
159
185
|
private readonly environment = new Float64Array([1, 0, 0, 1]);
|
|
160
186
|
private readonly sliderTime: Float64Array;
|
|
161
187
|
private readonly sliderMix: Float64Array;
|
|
@@ -174,10 +200,14 @@ export class RuntimePose {
|
|
|
174
200
|
const nb = model.boneNames.length;
|
|
175
201
|
const ns = model.slots.length;
|
|
176
202
|
const nc = model.constraints.length;
|
|
203
|
+
this.physicsScope = new Uint8Array(nc);
|
|
177
204
|
this.locals = new Float64Array(nb * LOCALS);
|
|
178
205
|
this.world = new Float64Array(nb * WORLDS);
|
|
179
206
|
this.light = new Float64Array(ns * 4);
|
|
207
|
+
this.dark = new Float64Array(ns * 3);
|
|
180
208
|
this.shown = new Int32Array(ns);
|
|
209
|
+
this.drawOrder = new Int32Array(ns);
|
|
210
|
+
for (let s = 0; s < ns; s++) this.drawOrder[s] = s;
|
|
181
211
|
this.vertices = new Float64Array(model.vertexCount);
|
|
182
212
|
this.deform = new Float64Array(model.deformCount);
|
|
183
213
|
this.deformed = new Uint8Array(model.attachments.length);
|
|
@@ -186,6 +216,16 @@ export class RuntimePose {
|
|
|
186
216
|
this.ikSoftness = new Float64Array(nc);
|
|
187
217
|
this.ikFlags = new Uint8Array(nc);
|
|
188
218
|
this.mixes = new Float64Array(nc * 6);
|
|
219
|
+
this.pathValues = new Float64Array(nc * 5);
|
|
220
|
+
this.pathSlotSign = new Int8Array(nc);
|
|
221
|
+
const ms = model.maxPathSpaces;
|
|
222
|
+
const mv = model.maxPathVertices;
|
|
223
|
+
this.pathSpaces = new Float64Array(ms);
|
|
224
|
+
this.pathLengths = new Float64Array(ms);
|
|
225
|
+
this.pathPositions = new Float64Array(ms > 0 ? ms * 3 + 2 : 0);
|
|
226
|
+
this.pathWorldPts = new Float64Array(mv);
|
|
227
|
+
this.pathChain = new Float64Array(mv > 0 ? mv + 4 : 0);
|
|
228
|
+
this.pathCurveEnds = new Float64Array(mv > 0 ? Math.ceil(mv / 6) + 1 : 0);
|
|
189
229
|
// Sized by the longest target list (`RuntimeModel.scratch`): a transform may name one bone several times, and every entry is applied and reposed.
|
|
190
230
|
this.changed = new Int32Array(model.scratch);
|
|
191
231
|
this.inWorld = new Int32Array(model.scratch);
|
|
@@ -233,9 +273,11 @@ export class RuntimePose {
|
|
|
233
273
|
this.physicsValues(null, 0, false);
|
|
234
274
|
this.locals.set(this.model.setup);
|
|
235
275
|
this.hierarchy();
|
|
276
|
+
this.slotValues(null, 0);
|
|
277
|
+
this.deformValues(null, 0);
|
|
236
278
|
this.constraintValues(null, 0);
|
|
237
279
|
this.solveConstraints();
|
|
238
|
-
this.
|
|
280
|
+
this.sliderAttachments();
|
|
239
281
|
}
|
|
240
282
|
|
|
241
283
|
/** The animation posed at its time `t`, from the setup pose (the header). */
|
|
@@ -255,9 +297,11 @@ export class RuntimePose {
|
|
|
255
297
|
this.time = t;
|
|
256
298
|
this.boneTimelines(anim, t);
|
|
257
299
|
this.hierarchy();
|
|
300
|
+
this.slotValues(anim, t);
|
|
301
|
+
this.deformValues(anim, t);
|
|
258
302
|
this.constraintValues(anim, t);
|
|
259
303
|
this.solveConstraints();
|
|
260
|
-
this.
|
|
304
|
+
this.sliderAttachments();
|
|
261
305
|
}
|
|
262
306
|
|
|
263
307
|
/** One step of a looping track: the track time moved by `dt`, the animation posed at it wrapped by the duration (`loopedTime` in src/core/raw.ts). */
|
|
@@ -496,6 +540,37 @@ export class RuntimePose {
|
|
|
496
540
|
}
|
|
497
541
|
this.sliderTime[k] = time;
|
|
498
542
|
this.sliderMix[k] = mix;
|
|
543
|
+
} else if (c.kind === 'path') {
|
|
544
|
+
const keyed = anim === null ? null : anim.paths[k];
|
|
545
|
+
let position = c.position;
|
|
546
|
+
let spacing = c.spacing;
|
|
547
|
+
let mixRotate = c.mixRotate;
|
|
548
|
+
let mixX = c.mixX;
|
|
549
|
+
let mixY = c.mixY;
|
|
550
|
+
if (keyed !== null) {
|
|
551
|
+
if (keyed.position !== null) {
|
|
552
|
+
const q = keyAt(keyed.position, this.cursors, keyed.positionCursor, t);
|
|
553
|
+
if (q >= 0) position = c.position + (channelValue(keyed.position, q, 0, t) - c.position) * 1;
|
|
554
|
+
}
|
|
555
|
+
if (keyed.spacing !== null) {
|
|
556
|
+
const q = keyAt(keyed.spacing, this.cursors, keyed.spacingCursor, t);
|
|
557
|
+
if (q >= 0) spacing = c.spacing + (channelValue(keyed.spacing, q, 0, t) - c.spacing) * 1;
|
|
558
|
+
}
|
|
559
|
+
if (keyed.mix !== null) {
|
|
560
|
+
const q = keyAt(keyed.mix, this.cursors, keyed.mixCursor, t);
|
|
561
|
+
if (q >= 0) {
|
|
562
|
+
mixRotate = c.mixRotate + (channelValue(keyed.mix, q, 0, t) - c.mixRotate) * 1;
|
|
563
|
+
mixX = c.mixX + (channelValue(keyed.mix, q, 1, t) - c.mixX) * 1;
|
|
564
|
+
mixY = c.mixY + (channelValue(keyed.mix, q, 2, t) - c.mixY) * 1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const o = k * 5;
|
|
569
|
+
this.pathValues[o] = position;
|
|
570
|
+
this.pathValues[o + 1] = spacing;
|
|
571
|
+
this.pathValues[o + 2] = mixRotate;
|
|
572
|
+
this.pathValues[o + 3] = mixX;
|
|
573
|
+
this.pathValues[o + 4] = mixY;
|
|
499
574
|
}
|
|
500
575
|
}
|
|
501
576
|
}
|
|
@@ -503,12 +578,15 @@ export class RuntimePose {
|
|
|
503
578
|
/** Every applied constraint in the document's order, each followed by the update order's repose (`applyConstraints`). */
|
|
504
579
|
private solveConstraints(): void {
|
|
505
580
|
const cs = this.model.constraints;
|
|
581
|
+
const hasPath = this.model.hasPath;
|
|
506
582
|
for (let k = 0; k < cs.length; k++) {
|
|
583
|
+
if (hasPath) this.snapSlotBones(k, false);
|
|
507
584
|
const c = cs[k];
|
|
508
585
|
if (!c.applied) continue;
|
|
509
586
|
if (c.kind === 'ik') this.solveIk(c, k);
|
|
510
587
|
else if (c.kind === 'transform') this.solveTransform(c, k);
|
|
511
588
|
else if (c.kind === 'slider') this.solveSlider(c, k);
|
|
589
|
+
else if (c.kind === 'path') this.solvePath(c, k);
|
|
512
590
|
else if (this.physicsPhase !== 'none' && !this.model.omitPhysics) {
|
|
513
591
|
const b = c.bones[0];
|
|
514
592
|
if (stepRuntimePhysics(this.physicsRecords[k]!, this.physicsStates[k], this.world, b * WORLDS, this.model.lengths[b], this.physicsTime, this.physicsPhase === 'reset', this.environment)) {
|
|
@@ -516,6 +594,18 @@ export class RuntimePose {
|
|
|
516
594
|
this.repose(1, 1);
|
|
517
595
|
}
|
|
518
596
|
}
|
|
597
|
+
if (hasPath) this.snapSlotBones(k, true);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private snapSlotBones(at: number, after: boolean): void {
|
|
602
|
+
const cs = this.model.constraints;
|
|
603
|
+
const W = this.world;
|
|
604
|
+
for (let k = 0; k < cs.length; k++) {
|
|
605
|
+
const c = cs[k];
|
|
606
|
+
if (c.kind !== 'path' || c.slotEventAt !== at || c.slotEventAfter !== after || (k === at && !after)) continue;
|
|
607
|
+
const sb = c.slotBone * WORLDS;
|
|
608
|
+
this.pathSlotSign[k] = W[sb + W_A] * W[sb + W_D] - W[sb + W_B] * W[sb + W_C] > 0 ? 1 : -1;
|
|
519
609
|
}
|
|
520
610
|
}
|
|
521
611
|
|
|
@@ -533,19 +623,54 @@ export class RuntimePose {
|
|
|
533
623
|
this.physicsReady = true;
|
|
534
624
|
}
|
|
535
625
|
|
|
626
|
+
private checkPhysicsScope(scope: readonly string[] | undefined): Uint8Array | null {
|
|
627
|
+
if (scope === undefined) return null;
|
|
628
|
+
if (!Array.isArray(scope)) throw new RuntimeInputError(`physics movement scope is ${String(scope)}, not an array of physics constraint names`);
|
|
629
|
+
const selected = this.physicsScope;
|
|
630
|
+
selected.fill(0);
|
|
631
|
+
const cs = this.model.constraints;
|
|
632
|
+
for (let i = 0; i < scope.length; i++) {
|
|
633
|
+
const name = scope[i];
|
|
634
|
+
if (typeof name !== 'string') throw new RuntimeInputError(`physics movement scope name ${String(name)} is not a string`);
|
|
635
|
+
let found: RuntimeConstraint | undefined;
|
|
636
|
+
for (let k = 0; k < cs.length; k++) {
|
|
637
|
+
if (cs[k].kind === 'physics' && cs[k].name === name) {
|
|
638
|
+
found = cs[k];
|
|
639
|
+
break;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (found === undefined) {
|
|
643
|
+
for (let k = 0; k < cs.length; k++) {
|
|
644
|
+
if (cs[k].name === name) {
|
|
645
|
+
found = cs[k];
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (found === undefined) throw new RuntimeInputError(`physics movement scope: no constraint "${name}"; this document declares [${cs.map((c) => c.name).join(', ')}]`);
|
|
651
|
+
if (found.kind !== 'physics') throw new RuntimeInputError(`physics movement scope: constraint "${name}" is ${found.kind}, not physics`);
|
|
652
|
+
selected[found.index] = 1;
|
|
653
|
+
}
|
|
654
|
+
return this.model.ignorePhysicsScope ? null : selected;
|
|
655
|
+
}
|
|
656
|
+
|
|
536
657
|
/** External motion changes the previous origin, never the authored locals. */
|
|
537
|
-
physicsTranslate(x: number, y: number): void {
|
|
658
|
+
physicsTranslate(x: number, y: number, scope?: readonly string[]): void {
|
|
659
|
+
const selected = this.checkPhysicsScope(scope);
|
|
538
660
|
if (!Number.isFinite(x) || !Number.isFinite(y)) throw new RuntimeInputError('physicsTranslate: x and y must be finite');
|
|
539
661
|
for (let k = 0; k < this.physicsStates.length; k++) {
|
|
662
|
+
if (selected !== null && selected[k] === 0) continue;
|
|
540
663
|
const s = this.physicsStates[k];
|
|
541
664
|
s.ux -= x; s.uy -= y; s.cx -= x; s.cy -= y;
|
|
542
665
|
}
|
|
543
666
|
}
|
|
544
667
|
|
|
545
|
-
physicsRotate(x: number, y: number, degrees: number): void {
|
|
668
|
+
physicsRotate(x: number, y: number, degrees: number, scope?: readonly string[]): void {
|
|
669
|
+
const selected = this.checkPhysicsScope(scope);
|
|
546
670
|
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(degrees)) throw new RuntimeInputError('physicsRotate: x, y and degrees must be finite');
|
|
547
671
|
const r = degrees * RAD, cos = Math.cos(r), sin = Math.sin(r);
|
|
548
672
|
for (let k = 0; k < this.physicsStates.length; k++) {
|
|
673
|
+
if (selected !== null && selected[k] === 0) continue;
|
|
549
674
|
const s = this.physicsStates[k];
|
|
550
675
|
const dx = s.cx - x, dy = s.cy - y;
|
|
551
676
|
const tx = dx * cos - dy * sin - dx, ty = dx * sin + dy * cos - dy;
|
|
@@ -662,9 +787,394 @@ export class RuntimePose {
|
|
|
662
787
|
if (c.bones.length > 0) this.repose(c.bones.length, 0);
|
|
663
788
|
}
|
|
664
789
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
this.
|
|
790
|
+
/** A path constraint (`solvePath` in `src/core/constraints_path.ts`), every buffer overwritten in place. */
|
|
791
|
+
private solvePath(c: RuntimePath, k: number): void {
|
|
792
|
+
if (c.attachment < 0 || this.model.omitPath) return;
|
|
793
|
+
const o5 = k * 5;
|
|
794
|
+
const position = this.pathValues[o5];
|
|
795
|
+
const spacing = this.pathValues[o5 + 1];
|
|
796
|
+
const mixRotate = this.pathValues[o5 + 2];
|
|
797
|
+
const mixX = this.pathValues[o5 + 3];
|
|
798
|
+
const mixY = this.pathValues[o5 + 4];
|
|
799
|
+
if (mixRotate === 0 && mixX === 0 && mixY === 0) return;
|
|
800
|
+
const wantsTangent = c.rotateMode === PATH_ROTATE_TANGENT;
|
|
801
|
+
const scale = c.rotateMode === PATH_ROTATE_CHAIN_SCALE;
|
|
802
|
+
const bones = c.bones;
|
|
803
|
+
const boneCount = bones.length;
|
|
804
|
+
const spaceCount = wantsTangent ? boneCount : boneCount + 1;
|
|
805
|
+
const spaces = this.pathSpaces;
|
|
806
|
+
const lengths = this.pathLengths;
|
|
807
|
+
const W = this.world;
|
|
808
|
+
const modelLengths = this.model.lengths;
|
|
809
|
+
spaces[0] = 0;
|
|
810
|
+
if (c.spacingMode === PATH_SPACING_PERCENT) {
|
|
811
|
+
if (scale) {
|
|
812
|
+
for (let i = 0; i < spaceCount - 1; i++) {
|
|
813
|
+
const bi = bones[i];
|
|
814
|
+
const boneLength = modelLengths[bi];
|
|
815
|
+
const w = bi * WORLDS;
|
|
816
|
+
const x = boneLength * W[w + W_A];
|
|
817
|
+
const y = boneLength * W[w + W_C];
|
|
818
|
+
lengths[i] = Math.sqrt(x * x + y * y);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
for (let i = 1; i < spaceCount; i++) spaces[i] = spacing;
|
|
822
|
+
} else if (c.spacingMode === PATH_SPACING_PROPORTIONAL) {
|
|
823
|
+
let sum = 0;
|
|
824
|
+
for (let i = 0, n = spaceCount - 1; i < n; ) {
|
|
825
|
+
const bi = bones[i];
|
|
826
|
+
const boneLength = modelLengths[bi];
|
|
827
|
+
if (boneLength < PATH_EPSILON) {
|
|
828
|
+
if (scale) lengths[i] = 0;
|
|
829
|
+
spaces[++i] = spacing;
|
|
830
|
+
} else {
|
|
831
|
+
const w = bi * WORLDS;
|
|
832
|
+
const x = boneLength * W[w + W_A];
|
|
833
|
+
const y = boneLength * W[w + W_C];
|
|
834
|
+
const length = Math.sqrt(x * x + y * y);
|
|
835
|
+
if (scale) lengths[i] = length;
|
|
836
|
+
spaces[++i] = length;
|
|
837
|
+
sum += length;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (sum > 0) {
|
|
841
|
+
sum = (spaceCount / sum) * spacing;
|
|
842
|
+
for (let i = 1; i < spaceCount; i++) spaces[i] *= sum;
|
|
843
|
+
}
|
|
844
|
+
} else {
|
|
845
|
+
const addsLength = c.spacingMode === PATH_SPACING_LENGTH;
|
|
846
|
+
for (let i = 0, n = spaceCount - 1; i < n; ) {
|
|
847
|
+
const bi = bones[i];
|
|
848
|
+
const boneLength = modelLengths[bi];
|
|
849
|
+
if (boneLength < PATH_EPSILON) {
|
|
850
|
+
if (scale) lengths[i] = 0;
|
|
851
|
+
spaces[++i] = spacing;
|
|
852
|
+
} else {
|
|
853
|
+
const w = bi * WORLDS;
|
|
854
|
+
const x = boneLength * W[w + W_A];
|
|
855
|
+
const y = boneLength * W[w + W_C];
|
|
856
|
+
const length = Math.sqrt(x * x + y * y);
|
|
857
|
+
if (scale) lengths[i] = length;
|
|
858
|
+
spaces[++i] = ((addsLength ? Math.max(0, boneLength + spacing) : spacing) * length) / boneLength;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
this.positionsOfPath(c, position, spaceCount, wantsTangent);
|
|
863
|
+
const positions = this.pathPositions;
|
|
864
|
+
let atX = positions[0];
|
|
865
|
+
let atY = positions[1];
|
|
866
|
+
let offsetRotation = c.offsetRotation;
|
|
867
|
+
let fromTip: boolean;
|
|
868
|
+
if (offsetRotation === 0) fromTip = c.rotateMode === PATH_ROTATE_CHAIN;
|
|
869
|
+
else {
|
|
870
|
+
fromTip = false;
|
|
871
|
+
let positive: boolean;
|
|
872
|
+
if (c.slotEventAt === k && !c.slotEventAfter) {
|
|
873
|
+
const sb = c.slotBone * WORLDS;
|
|
874
|
+
positive = W[sb + W_A] * W[sb + W_D] - W[sb + W_B] * W[sb + W_C] > 0;
|
|
875
|
+
} else {
|
|
876
|
+
positive = c.slotEventAt >= 0 && this.pathSlotSign[k] > 0;
|
|
877
|
+
}
|
|
878
|
+
offsetRotation *= positive ? RAD : -RAD;
|
|
879
|
+
}
|
|
880
|
+
for (let i = 0, p = 3; i < boneCount; i++, p += 3) {
|
|
881
|
+
const bi = bones[i];
|
|
882
|
+
const o = bi * WORLDS;
|
|
883
|
+
let wa = W[o + W_A];
|
|
884
|
+
let wb = W[o + W_B];
|
|
885
|
+
let wc = W[o + W_C];
|
|
886
|
+
let wd = W[o + W_D];
|
|
887
|
+
const wx = W[o + W_X] + (atX - W[o + W_X]) * mixX;
|
|
888
|
+
const wy = W[o + W_Y] + (atY - W[o + W_Y]) * mixY;
|
|
889
|
+
const x = positions[p];
|
|
890
|
+
const y = positions[p + 1];
|
|
891
|
+
const dx = x - atX;
|
|
892
|
+
const dy = y - atY;
|
|
893
|
+
if (scale) {
|
|
894
|
+
const length = lengths[i];
|
|
895
|
+
if (length >= PATH_EPSILON) {
|
|
896
|
+
const s = (Math.sqrt(dx * dx + dy * dy) / length - 1) * mixRotate + 1;
|
|
897
|
+
wa *= s;
|
|
898
|
+
wc *= s;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
atX = x;
|
|
902
|
+
atY = y;
|
|
903
|
+
if (mixRotate > 0) {
|
|
904
|
+
const a = wa;
|
|
905
|
+
const bb = wb;
|
|
906
|
+
const cc = wc;
|
|
907
|
+
const d = wd;
|
|
908
|
+
let r: number;
|
|
909
|
+
if (wantsTangent) r = positions[p - 1];
|
|
910
|
+
else if (spaces[i + 1] === 0) r = positions[p + 2];
|
|
911
|
+
else r = Math.atan2(dy, dx);
|
|
912
|
+
r -= Math.atan2(cc, a);
|
|
913
|
+
if (fromTip) {
|
|
914
|
+
const cos = Math.cos(r);
|
|
915
|
+
const sin = Math.sin(r);
|
|
916
|
+
const length = modelLengths[bi];
|
|
917
|
+
atX += (length * (cos * a - sin * cc) - dx) * mixRotate;
|
|
918
|
+
atY += (length * (sin * a + cos * cc) - dy) * mixRotate;
|
|
919
|
+
} else r += offsetRotation;
|
|
920
|
+
if (r > RUNTIME_PI) r -= TWO_PI;
|
|
921
|
+
else if (r < -RUNTIME_PI) r += TWO_PI;
|
|
922
|
+
r *= mixRotate;
|
|
923
|
+
const cos = Math.cos(r);
|
|
924
|
+
const sin = Math.sin(r);
|
|
925
|
+
wa = cos * a - sin * cc;
|
|
926
|
+
wb = cos * bb - sin * d;
|
|
927
|
+
wc = sin * a + cos * cc;
|
|
928
|
+
wd = sin * bb + cos * d;
|
|
929
|
+
}
|
|
930
|
+
W[o + W_A] = wa;
|
|
931
|
+
W[o + W_B] = wb;
|
|
932
|
+
W[o + W_C] = wc;
|
|
933
|
+
W[o + W_D] = wd;
|
|
934
|
+
W[o + W_X] = wx;
|
|
935
|
+
W[o + W_Y] = wy;
|
|
936
|
+
this.changed[i] = bi;
|
|
937
|
+
this.inWorld[i] = bi;
|
|
938
|
+
}
|
|
939
|
+
if (boneCount > 0) this.repose(boneCount, boneCount);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
private computePathWorld(c: RuntimePath): void {
|
|
943
|
+
const at = this.model.attachments[c.attachment];
|
|
944
|
+
const W = this.world;
|
|
945
|
+
const pts = this.pathWorldPts;
|
|
946
|
+
const deformed = this.deformed[c.attachment] !== 0;
|
|
947
|
+
if (at.bindingCounts.length === 0) {
|
|
948
|
+
const b = c.slotBone * WORLDS;
|
|
949
|
+
const a = W[b + W_A];
|
|
950
|
+
const bb = W[b + W_B];
|
|
951
|
+
const cc = W[b + W_C];
|
|
952
|
+
const d = W[b + W_D];
|
|
953
|
+
const wx = W[b + W_X];
|
|
954
|
+
const wy = W[b + W_Y];
|
|
955
|
+
const local = deformed ? this.deform : at.local;
|
|
956
|
+
const lo = deformed ? at.deformOffset : 0;
|
|
957
|
+
for (let q = 0; q < c.pathVertices; q += 2) {
|
|
958
|
+
const x = local[lo + q];
|
|
959
|
+
const y = local[lo + q + 1];
|
|
960
|
+
pts[q] = x * a + y * bb + wx;
|
|
961
|
+
pts[q + 1] = x * cc + y * d + wy;
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const counts = at.bindingCounts;
|
|
966
|
+
const bones = at.bindingBones;
|
|
967
|
+
const xs = at.bindingX;
|
|
968
|
+
const ys = at.bindingY;
|
|
969
|
+
const weights = at.bindingWeights;
|
|
970
|
+
let n = 0;
|
|
971
|
+
let o = 0;
|
|
972
|
+
for (let v = 0; v < counts.length; v++) {
|
|
973
|
+
let wx = 0;
|
|
974
|
+
let wy = 0;
|
|
975
|
+
const end = n + counts[v];
|
|
976
|
+
for (; n < end; n++) {
|
|
977
|
+
const b = bones[n] * WORLDS;
|
|
978
|
+
const weight = weights[n];
|
|
979
|
+
const x = deformed ? xs[n] + this.deform[at.deformOffset + n * 2] : xs[n];
|
|
980
|
+
const y = deformed ? ys[n] + this.deform[at.deformOffset + n * 2 + 1] : ys[n];
|
|
981
|
+
wx += (x * W[b + W_A] + y * W[b + W_B] + W[b + W_X]) * weight;
|
|
982
|
+
wy += (x * W[b + W_C] + y * W[b + W_D] + W[b + W_Y]) * weight;
|
|
983
|
+
}
|
|
984
|
+
pts[o++] = wx;
|
|
985
|
+
pts[o++] = wy;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
private positionsOfPath(c: RuntimePath, position: number, spaceCount: number, wantsTangent: boolean): void {
|
|
990
|
+
this.computePathWorld(c);
|
|
991
|
+
const pts = this.pathWorldPts;
|
|
992
|
+
const vc = c.pathVertices;
|
|
993
|
+
const n = vc / 2;
|
|
994
|
+
const spaces = this.pathSpaces;
|
|
995
|
+
const out = this.pathPositions;
|
|
996
|
+
const outLen = spaceCount * 3 + 2;
|
|
997
|
+
for (let i = 0; i < outLen; i++) out[i] = 0;
|
|
998
|
+
const closed = c.closed;
|
|
999
|
+
if (!c.constantSpeed) {
|
|
1000
|
+
const lengths = c.lengths;
|
|
1001
|
+
const curveCount = n / 3 - (closed ? 1 : 2);
|
|
1002
|
+
const total = lengths[curveCount];
|
|
1003
|
+
if (c.positionMode === PATH_POSITION_PERCENT) position *= total;
|
|
1004
|
+
const spaceScale = c.spacingMode === PATH_SPACING_PERCENT ? total : c.spacingMode === PATH_SPACING_PROPORTIONAL ? total / spaceCount : 1;
|
|
1005
|
+
let curve = 0;
|
|
1006
|
+
for (let i = 0, o = 0; i < spaceCount; i++, o += 3) {
|
|
1007
|
+
const space = spaces[i] * spaceScale;
|
|
1008
|
+
position += space;
|
|
1009
|
+
let p = position;
|
|
1010
|
+
if (closed) {
|
|
1011
|
+
p %= total;
|
|
1012
|
+
if (p < 0) p += total;
|
|
1013
|
+
curve = 0;
|
|
1014
|
+
} else if (p < 0) {
|
|
1015
|
+
onLineBefore(p, pts[2], pts[3], pts[4], pts[5], out, o);
|
|
1016
|
+
continue;
|
|
1017
|
+
} else if (p > total) {
|
|
1018
|
+
const m0 = 2 * (n - 3);
|
|
1019
|
+
const m1 = 2 * (n - 2);
|
|
1020
|
+
onLinePast(p - total, pts[m0], pts[m0 + 1], pts[m1], pts[m1 + 1], out, o);
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
for (;; curve++) {
|
|
1024
|
+
const length = lengths[curve];
|
|
1025
|
+
if (p > length) continue;
|
|
1026
|
+
if (curve === 0) p /= length;
|
|
1027
|
+
else {
|
|
1028
|
+
const prev = lengths[curve - 1];
|
|
1029
|
+
p = (p - prev) / (length - prev);
|
|
1030
|
+
}
|
|
1031
|
+
break;
|
|
1032
|
+
}
|
|
1033
|
+
const kk = 3 * curve + 1;
|
|
1034
|
+
const i0 = (kk % n) * 2;
|
|
1035
|
+
const i1 = ((kk + 1) % n) * 2;
|
|
1036
|
+
const i2 = ((kk + 2) % n) * 2;
|
|
1037
|
+
const i3 = ((kk + 3) % n) * 2;
|
|
1038
|
+
onCurve(p, pts[i0], pts[i0 + 1], pts[i1], pts[i1 + 1], pts[i2], pts[i2 + 1], pts[i3], pts[i3 + 1], out, o, wantsTangent || (i > 0 && space === 0));
|
|
1039
|
+
}
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
const chain = this.pathChain;
|
|
1043
|
+
let chainLen = 0;
|
|
1044
|
+
if (closed) {
|
|
1045
|
+
for (let q = 2; q < vc; q++) chain[chainLen++] = pts[q];
|
|
1046
|
+
chain[chainLen++] = pts[0];
|
|
1047
|
+
chain[chainLen++] = pts[1];
|
|
1048
|
+
chain[chainLen++] = pts[2];
|
|
1049
|
+
chain[chainLen++] = pts[3];
|
|
1050
|
+
} else {
|
|
1051
|
+
for (let q = 2; q < vc - 2; q++) chain[chainLen++] = pts[q];
|
|
1052
|
+
}
|
|
1053
|
+
const curveCount = closed ? n / 3 : n / 3 - 1;
|
|
1054
|
+
const curveEnds = this.pathCurveEnds;
|
|
1055
|
+
let total = 0;
|
|
1056
|
+
let cx0 = chain[0];
|
|
1057
|
+
let cy0 = chain[1];
|
|
1058
|
+
for (let i = 0, w = 2; i < curveCount; i++, w += 6) {
|
|
1059
|
+
const cx1 = chain[w];
|
|
1060
|
+
const cy1 = chain[w + 1];
|
|
1061
|
+
const cx2 = chain[w + 2];
|
|
1062
|
+
const cy2 = chain[w + 3];
|
|
1063
|
+
const x2 = chain[w + 4];
|
|
1064
|
+
const y2 = chain[w + 5];
|
|
1065
|
+
const h2x = (cx0 - cx1 * 2 + cx2) * 0.1875;
|
|
1066
|
+
const h2y = (cy0 - cy1 * 2 + cy2) * 0.1875;
|
|
1067
|
+
const d3x = ((cx1 - cx2) * 3 - cx0 + x2) * 0.09375;
|
|
1068
|
+
const d3y = ((cy1 - cy2) * 3 - cy0 + y2) * 0.09375;
|
|
1069
|
+
let d2x = h2x * 2 + d3x;
|
|
1070
|
+
let d2y = h2y * 2 + d3y;
|
|
1071
|
+
let d1x = (cx1 - cx0) * 0.75 + h2x + d3x * 0.16666667;
|
|
1072
|
+
let d1y = (cy1 - cy0) * 0.75 + h2y + d3y * 0.16666667;
|
|
1073
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1074
|
+
d1x += d2x;
|
|
1075
|
+
d1y += d2y;
|
|
1076
|
+
d2x += d3x;
|
|
1077
|
+
d2y += d3y;
|
|
1078
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1079
|
+
d1x += d2x;
|
|
1080
|
+
d1y += d2y;
|
|
1081
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1082
|
+
d1x += d2x + d3x;
|
|
1083
|
+
d1y += d2y + d3y;
|
|
1084
|
+
total += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1085
|
+
curveEnds[i] = total;
|
|
1086
|
+
cx0 = x2;
|
|
1087
|
+
cy0 = y2;
|
|
1088
|
+
}
|
|
1089
|
+
const pathTotal = curveCount > 0 ? curveEnds[curveCount - 1] : 0;
|
|
1090
|
+
if (c.positionMode === PATH_POSITION_PERCENT) position *= pathTotal;
|
|
1091
|
+
const spaceScale = c.spacingMode === PATH_SPACING_PERCENT ? pathTotal : c.spacingMode === PATH_SPACING_PROPORTIONAL ? pathTotal / spaceCount : 1;
|
|
1092
|
+
const segmentEnds = this.pathSegmentEnds;
|
|
1093
|
+
for (let s = 0; s < 10; s++) segmentEnds[s] = 0;
|
|
1094
|
+
let curveTotal = 0;
|
|
1095
|
+
let lastCurve = -1;
|
|
1096
|
+
let x1 = 0, y1 = 0, cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0, x2 = 0, y2 = 0;
|
|
1097
|
+
for (let i = 0, o = 0, curve = 0, segment = 0; i < spaceCount; i++, o += 3) {
|
|
1098
|
+
const space = spaces[i] * spaceScale;
|
|
1099
|
+
position += space;
|
|
1100
|
+
let p = position;
|
|
1101
|
+
if (closed) {
|
|
1102
|
+
p %= pathTotal;
|
|
1103
|
+
if (p < 0) p += pathTotal;
|
|
1104
|
+
curve = 0;
|
|
1105
|
+
segment = 0;
|
|
1106
|
+
} else if (p < 0) {
|
|
1107
|
+
onLineBefore(p, chain[0], chain[1], chain[2], chain[3], out, o);
|
|
1108
|
+
continue;
|
|
1109
|
+
} else if (p > pathTotal) {
|
|
1110
|
+
onLinePast(p - pathTotal, chain[chainLen - 4], chain[chainLen - 3], chain[chainLen - 2], chain[chainLen - 1], out, o);
|
|
1111
|
+
continue;
|
|
1112
|
+
}
|
|
1113
|
+
for (;; curve++) {
|
|
1114
|
+
const length = curveEnds[curve];
|
|
1115
|
+
if (p > length) continue;
|
|
1116
|
+
if (curve === 0) p /= length;
|
|
1117
|
+
else {
|
|
1118
|
+
const prev = curveEnds[curve - 1];
|
|
1119
|
+
p = (p - prev) / (length - prev);
|
|
1120
|
+
}
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
if (curve !== lastCurve) {
|
|
1124
|
+
lastCurve = curve;
|
|
1125
|
+
const ii = curve * 6;
|
|
1126
|
+
x1 = chain[ii];
|
|
1127
|
+
y1 = chain[ii + 1];
|
|
1128
|
+
cx1 = chain[ii + 2];
|
|
1129
|
+
cy1 = chain[ii + 3];
|
|
1130
|
+
cx2 = chain[ii + 4];
|
|
1131
|
+
cy2 = chain[ii + 5];
|
|
1132
|
+
x2 = chain[ii + 6];
|
|
1133
|
+
y2 = chain[ii + 7];
|
|
1134
|
+
const h2x = (x1 - cx1 * 2 + cx2) * 0.03;
|
|
1135
|
+
const h2y = (y1 - cy1 * 2 + cy2) * 0.03;
|
|
1136
|
+
const d3x = ((cx1 - cx2) * 3 - x1 + x2) * 0.006;
|
|
1137
|
+
const d3y = ((cy1 - cy2) * 3 - y1 + y2) * 0.006;
|
|
1138
|
+
let d2x = h2x * 2 + d3x;
|
|
1139
|
+
let d2y = h2y * 2 + d3y;
|
|
1140
|
+
let d1x = (cx1 - x1) * 0.3 + h2x + d3x * 0.16666667;
|
|
1141
|
+
let d1y = (cy1 - y1) * 0.3 + h2y + d3y * 0.16666667;
|
|
1142
|
+
curveTotal = Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1143
|
+
segmentEnds[0] = curveTotal;
|
|
1144
|
+
for (let s = 1; s < 8; s++) {
|
|
1145
|
+
d1x += d2x;
|
|
1146
|
+
d1y += d2y;
|
|
1147
|
+
d2x += d3x;
|
|
1148
|
+
d2y += d3y;
|
|
1149
|
+
curveTotal += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1150
|
+
segmentEnds[s] = curveTotal;
|
|
1151
|
+
}
|
|
1152
|
+
d1x += d2x;
|
|
1153
|
+
d1y += d2y;
|
|
1154
|
+
curveTotal += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1155
|
+
segmentEnds[8] = curveTotal;
|
|
1156
|
+
d1x += d2x + d3x;
|
|
1157
|
+
d1y += d2y + d3y;
|
|
1158
|
+
curveTotal += Math.sqrt(d1x * d1x + d1y * d1y);
|
|
1159
|
+
segmentEnds[9] = curveTotal;
|
|
1160
|
+
segment = 0;
|
|
1161
|
+
}
|
|
1162
|
+
p *= curveTotal;
|
|
1163
|
+
for (;; segment++) {
|
|
1164
|
+
const length = segmentEnds[segment];
|
|
1165
|
+
if (p > length) continue;
|
|
1166
|
+
if (segment === 0) p /= length;
|
|
1167
|
+
else {
|
|
1168
|
+
const prev = segmentEnds[segment - 1];
|
|
1169
|
+
p = segment + (p - prev) / (length - prev);
|
|
1170
|
+
}
|
|
1171
|
+
break;
|
|
1172
|
+
}
|
|
1173
|
+
onCurve(p * 0.1, x1, y1, cx1, cy1, cx2, cy2, x2, y2, out, o, wantsTangent || (i > 0 && space === 0));
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
private sliderAttachments(): void {
|
|
668
1178
|
for (let k = 0; k < this.model.constraints.length; k++) {
|
|
669
1179
|
this.sliderSlots(k);
|
|
670
1180
|
this.sliderDeforms(k);
|
|
@@ -677,6 +1187,13 @@ export class RuntimePose {
|
|
|
677
1187
|
const k = index, c = cs[k];
|
|
678
1188
|
if (c.kind !== 'slider' || !c.applied || this.sliderMix[k] === 0) return;
|
|
679
1189
|
const a = c.animation, t = this.sliderAt[k], alpha = this.sliderMix[k];
|
|
1190
|
+
if (!this.model.omitDrawOrder && a.drawOrder !== null) {
|
|
1191
|
+
const di = keyAt(a.drawOrder.curve, this.cursors, a.drawOrder.cursor, t);
|
|
1192
|
+
if (di >= 0) {
|
|
1193
|
+
const ns = this.drawOrder.length, orders = a.drawOrder.orders, base = di * ns;
|
|
1194
|
+
for (let s = 0; s < ns; s++) this.drawOrder[s] = orders[base + s];
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
680
1197
|
for (let q = 0; q < a.slots.length; q++) {
|
|
681
1198
|
const tl = a.slots[q], i = keyAt(tl.curve, this.cursors, tl.cursor, t);
|
|
682
1199
|
if (i < 0) continue;
|
|
@@ -687,9 +1204,17 @@ export class RuntimePose {
|
|
|
687
1204
|
}
|
|
688
1205
|
else {
|
|
689
1206
|
const channels = tl.channels!;
|
|
1207
|
+
const slot = this.model.slots[tl.slot];
|
|
1208
|
+
const o = tl.slot * 4, d = tl.slot * 3;
|
|
690
1209
|
for (let f = 0; f < channels.length; f++) {
|
|
691
|
-
const
|
|
692
|
-
|
|
1210
|
+
const at = channels[f], v = channelValue(tl.curve, i, f, t);
|
|
1211
|
+
if (at < 4) {
|
|
1212
|
+
const n = o + at;
|
|
1213
|
+
this.light[n] = clamp01(alpha === 1 ? v : this.light[n] + (v - this.light[n]) * alpha);
|
|
1214
|
+
} else if (slot.dark !== null && !this.model.omitDarkColour) {
|
|
1215
|
+
const n = d + (at - 4);
|
|
1216
|
+
this.dark[n] = clamp01(alpha === 1 ? v : this.dark[n] + (v - this.dark[n]) * alpha);
|
|
1217
|
+
}
|
|
693
1218
|
}
|
|
694
1219
|
}
|
|
695
1220
|
}
|
|
@@ -1213,16 +1738,32 @@ export class RuntimePose {
|
|
|
1213
1738
|
private slotValues(anim: RuntimeAnimation | null, t: number): void {
|
|
1214
1739
|
const slots = this.model.slots;
|
|
1215
1740
|
const light = this.light;
|
|
1741
|
+
const dark = this.dark;
|
|
1216
1742
|
for (let s = 0; s < slots.length; s++) {
|
|
1217
1743
|
const slot = slots[s];
|
|
1218
1744
|
this.shown[s] = slot.setupAttachment;
|
|
1745
|
+
this.drawOrder[s] = s;
|
|
1219
1746
|
const o = s * 4;
|
|
1220
1747
|
light[o] = slot.light[0];
|
|
1221
1748
|
light[o + 1] = slot.light[1];
|
|
1222
1749
|
light[o + 2] = slot.light[2];
|
|
1223
1750
|
light[o + 3] = slot.light[3];
|
|
1751
|
+
const setupDark = slot.dark;
|
|
1752
|
+
if (setupDark !== null) {
|
|
1753
|
+
const d = s * 3;
|
|
1754
|
+
dark[d] = setupDark[0];
|
|
1755
|
+
dark[d + 1] = setupDark[1];
|
|
1756
|
+
dark[d + 2] = setupDark[2];
|
|
1757
|
+
}
|
|
1224
1758
|
}
|
|
1225
1759
|
if (anim === null) return;
|
|
1760
|
+
if (!this.model.omitDrawOrder && anim.drawOrder !== null) {
|
|
1761
|
+
const di = keyAt(anim.drawOrder.curve, this.cursors, anim.drawOrder.cursor, t);
|
|
1762
|
+
if (di >= 0) {
|
|
1763
|
+
const ns = slots.length, orders = anim.drawOrder.orders, base = di * ns;
|
|
1764
|
+
for (let s = 0; s < ns; s++) this.drawOrder[s] = orders[base + s];
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1226
1767
|
const timelines = anim.slots;
|
|
1227
1768
|
for (let k = 0; k < timelines.length; k++) {
|
|
1228
1769
|
const tl = timelines[k];
|
|
@@ -1233,11 +1774,18 @@ export class RuntimePose {
|
|
|
1233
1774
|
continue;
|
|
1234
1775
|
}
|
|
1235
1776
|
const channels = tl.channels as Int32Array;
|
|
1236
|
-
const
|
|
1777
|
+
const slot = slots[tl.slot];
|
|
1778
|
+
const setup = slot.light;
|
|
1779
|
+
const setupDark = slot.dark;
|
|
1237
1780
|
const o = tl.slot * 4;
|
|
1781
|
+
const d = tl.slot * 3;
|
|
1238
1782
|
for (let c = 0; c < channels.length; c++) {
|
|
1239
1783
|
const at = channels[c];
|
|
1240
|
-
|
|
1784
|
+
if (at < 4) {
|
|
1785
|
+
light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
|
|
1786
|
+
} else if (setupDark !== null && !this.model.omitDarkColour) {
|
|
1787
|
+
dark[d + (at - 4)] = i < 0 ? setupDark[at - 4] : clamp01(channelValue(tl.curve, i, c, t));
|
|
1788
|
+
}
|
|
1241
1789
|
}
|
|
1242
1790
|
}
|
|
1243
1791
|
}
|
|
@@ -1388,3 +1936,43 @@ function applyWorld(w: Float64Array, property: number, v: number, mix: number):
|
|
|
1388
1936
|
}
|
|
1389
1937
|
}
|
|
1390
1938
|
}
|
|
1939
|
+
|
|
1940
|
+
function onLineBefore(p: number, x1: number, y1: number, x2: number, y2: number, out: Float64Array, o: number): void {
|
|
1941
|
+
const r = Math.atan2(y2 - y1, x2 - x1);
|
|
1942
|
+
out[o] = x1 + p * Math.cos(r);
|
|
1943
|
+
out[o + 1] = y1 + p * Math.sin(r);
|
|
1944
|
+
out[o + 2] = r;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
function onLinePast(p: number, x0: number, y0: number, x1: number, y1: number, out: Float64Array, o: number): void {
|
|
1948
|
+
const r = Math.atan2(y1 - y0, x1 - x0);
|
|
1949
|
+
out[o] = x1 + p * Math.cos(r);
|
|
1950
|
+
out[o + 1] = y1 + p * Math.sin(r);
|
|
1951
|
+
out[o + 2] = r;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
function onCurve(p: number, x1: number, y1: number, cx1: number, cy1: number, cx2: number, cy2: number, x2: number, y2: number, out: Float64Array, o: number, wantsTangent: boolean): void {
|
|
1955
|
+
if (Number.isNaN(p)) {
|
|
1956
|
+
out[o] = x1;
|
|
1957
|
+
out[o + 1] = y1;
|
|
1958
|
+
out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
const t2 = p * p;
|
|
1962
|
+
const t3 = t2 * p;
|
|
1963
|
+
const u = 1 - p;
|
|
1964
|
+
const u2 = u * u;
|
|
1965
|
+
const u3 = u2 * u;
|
|
1966
|
+
const ut = u * p;
|
|
1967
|
+
const ut3x = ut * 3;
|
|
1968
|
+
const w1 = u * ut3x;
|
|
1969
|
+
const w2 = ut3x * p;
|
|
1970
|
+
const x = x1 * u3 + cx1 * w1 + cx2 * w2 + x2 * t3;
|
|
1971
|
+
const y = y1 * u3 + cy1 * w1 + cy2 * w2 + y2 * t3;
|
|
1972
|
+
out[o] = x;
|
|
1973
|
+
out[o + 1] = y;
|
|
1974
|
+
if (wantsTangent) {
|
|
1975
|
+
if (p < 0.001) out[o + 2] = Math.atan2(cy1 - y1, cx1 - x1);
|
|
1976
|
+
else out[o + 2] = Math.atan2(y - (y1 * u2 + cy1 * ut * 2 + cy2 * t2), x - (x1 * u2 + cx1 * ut * 2 + cx2 * t2));
|
|
1977
|
+
}
|
|
1978
|
+
}
|