@rig-c/core 2.69.0 → 2.71.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 +29 -9
- package/bin/rigc.cjs +2 -2
- package/docs/AUTHORING.md +279 -9
- package/package.json +5 -2
- package/skills/rigc/SKILL.md +1 -1
- package/skills/rigc-face/SKILL.md +1 -1
- package/skills/rigc-ingest/SKILL.md +1 -1
- package/skills/rigc-motion/SKILL.md +1 -1
- package/skills/rigc-rigging/SKILL.md +1 -1
- package/src/assertions/report.ts +1 -1
- package/src/atlas.ts +6 -1
- package/src/cli/core_commands.ts +68 -6
- package/src/cli/shared.ts +30 -2
- package/src/cli/write_whole.ts +1 -1
- package/src/compile.ts +48 -0
- package/src/core/animation.ts +56 -24
- package/src/core/index.ts +62 -1
- package/src/core/raw.ts +38 -12
- package/src/core/tracks.ts +23 -8
- package/src/core/vertices.ts +182 -44
- package/src/deformgen.ts +23 -7
- package/src/depth.ts +17 -4
- package/src/motion.ts +4 -4
- package/src/render_shared.ts +19 -0
- package/src/runtime/curves.ts +106 -0
- package/src/runtime/index.ts +80 -0
- package/src/runtime/model.ts +719 -0
- package/src/runtime/pose.ts +1014 -0
- package/src/seam.ts +6 -2
- package/src/sheet.ts +920 -0
- package/src/trackgen.ts +27 -4
|
@@ -0,0 +1,1014 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The runtime poser's per-instance half (issue #1591, stage 0): one
|
|
3
|
+
* `RuntimePose` per skeleton on screen, every buffer allocated when it is
|
|
4
|
+
* made and overwritten in place on every pose, so that posing a frame
|
|
5
|
+
* allocates nothing.
|
|
6
|
+
*
|
|
7
|
+
* ## What a pose is
|
|
8
|
+
*
|
|
9
|
+
* The oracle's sample, as `src/core/animation.ts` poses it (`poseAnimations`):
|
|
10
|
+
* every pose is posed from the setup pose — the bones' setup local values,
|
|
11
|
+
* then the animation's bone timelines at `t` at full weight, then the world
|
|
12
|
+
* transforms parents first, then the ik and transform constraints in the
|
|
13
|
+
* document's order with their timelines' values at `t`, each followed by the
|
|
14
|
+
* bones below what it moved posed again; then each slot's colour and what it
|
|
15
|
+
* shows; then, when asked, every shown attachment's world vertices. Nothing
|
|
16
|
+
* of one pose survives into the next but the key cursors, which only make
|
|
17
|
+
* the key search start nearer.
|
|
18
|
+
*
|
|
19
|
+
* ## The arithmetic is the oracle's
|
|
20
|
+
*
|
|
21
|
+
* Every formula below is the one the named function of `src/core/` computes,
|
|
22
|
+
* in its order of operations, on the same doubles: a bone's frame and its
|
|
23
|
+
* five inherit modes (`world.ts`), the timelines (`animation.ts`), the one-
|
|
24
|
+
* and two-bone ik, the world-space transform, the read-back of a moved bone's
|
|
25
|
+
* local values and the update order (`constraints.ts`), a region's corners
|
|
26
|
+
* and a mesh's vertices (`vertices.ts`). Each of those orders was fixed
|
|
27
|
+
* there by measurement against spine-core 4.3.13 to the last bit; the runtime
|
|
28
|
+
* does not re-measure them, it is held to the oracle at tolerance 0
|
|
29
|
+
* (`tools/core_gate.ts`, *The runtime poser*), so an order changed here is a
|
|
30
|
+
* difference there.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ The exactness hazards, each kept as the oracle has it: the setup value
|
|
33
|
+
* plus 0 before a timeline's first key (which turns a `-0` setup into `0`);
|
|
34
|
+
* a keyed ik or transform value reaching the pose through the setup blend
|
|
35
|
+
* `setup + (value − setup)·1`; `Math.PI` for a folded uniform two-bone ik and
|
|
36
|
+
* the runtime's 3.1415927 everywhere else; the `(−180, 180]` wrap; a mix of
|
|
37
|
+
* exactly 0 touching nothing; an inactive bone posed as zeros by the
|
|
38
|
+
* hierarchy and never posed again by a constraint, nor the bones below it.
|
|
39
|
+
*/
|
|
40
|
+
import { COLLAPSED_X_AXIS_SQ, RUNTIME_DEG, RUNTIME_PI } from '../core/world.ts';
|
|
41
|
+
import { channelValue, keyAt } from './curves.ts';
|
|
42
|
+
import {
|
|
43
|
+
DRAW_MESH,
|
|
44
|
+
DRAW_NONE,
|
|
45
|
+
DRAW_REGION,
|
|
46
|
+
IK_BEND_POSITIVE,
|
|
47
|
+
IK_COMPRESS,
|
|
48
|
+
IK_STRETCH,
|
|
49
|
+
L_ROTATION,
|
|
50
|
+
L_SCALE_X,
|
|
51
|
+
L_SCALE_Y,
|
|
52
|
+
L_SHEAR_X,
|
|
53
|
+
L_SHEAR_Y,
|
|
54
|
+
L_X,
|
|
55
|
+
L_Y,
|
|
56
|
+
LOCALS,
|
|
57
|
+
MODE_NO_ROTATION_OR_REFLECTION,
|
|
58
|
+
MODE_NO_SCALE,
|
|
59
|
+
MODE_NO_SCALE_OR_REFLECTION,
|
|
60
|
+
MODE_NORMAL,
|
|
61
|
+
MODE_ONLY_TRANSLATION,
|
|
62
|
+
RAD,
|
|
63
|
+
RuntimeInputError,
|
|
64
|
+
SCALE_Y_UNIFORM,
|
|
65
|
+
SCALE_Y_VOLUME,
|
|
66
|
+
W_A,
|
|
67
|
+
W_B,
|
|
68
|
+
W_C,
|
|
69
|
+
W_D,
|
|
70
|
+
W_X,
|
|
71
|
+
W_Y,
|
|
72
|
+
WORLDS,
|
|
73
|
+
type RuntimeAnimation,
|
|
74
|
+
type RuntimeIk,
|
|
75
|
+
type RuntimeModel,
|
|
76
|
+
type RuntimeTransform,
|
|
77
|
+
} from './model.ts';
|
|
78
|
+
|
|
79
|
+
const DEG = RUNTIME_DEG;
|
|
80
|
+
const TWO_PI = 2 * RUNTIME_PI;
|
|
81
|
+
// The transform properties, by `TRANSFORM_PROPERTIES` index (src/core/constraints.ts).
|
|
82
|
+
const P_ROTATE = 0;
|
|
83
|
+
const P_X = 1;
|
|
84
|
+
const P_Y = 2;
|
|
85
|
+
const P_SCALE_X = 3;
|
|
86
|
+
const P_SCALE_Y = 4;
|
|
87
|
+
|
|
88
|
+
/** A change brought once into (−180, 180] — −180 goes to 180 (src/core/constraints.ts). */
|
|
89
|
+
function wrap180(r: number): number {
|
|
90
|
+
return r > 180 ? r - 360 : r <= -180 ? r + 360 : r;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** The runtime's `scaleY` for a bone an ik scaled by `s` along its length (src/core/constraints.ts, the `volume` measurement). */
|
|
94
|
+
function volumeScaleY(scaleY: number, s: number): number {
|
|
95
|
+
return scaleY / (s < 0.7 ? 0.25 + s * 0.642857 : s);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
99
|
+
|
|
100
|
+
/** One skeleton's pose, every value in place. Read `world`, `light`, `shown` and `vertices` after a pose. */
|
|
101
|
+
export class RuntimePose {
|
|
102
|
+
readonly model: RuntimeModel;
|
|
103
|
+
/** Every bone's local values, `LOCALS` per bone (`L_*`). */
|
|
104
|
+
readonly locals: Float64Array;
|
|
105
|
+
/** Every bone's world transform, `WORLDS` per bone (`W_*`). */
|
|
106
|
+
readonly world: Float64Array;
|
|
107
|
+
/** Every slot's light colour, four per slot. */
|
|
108
|
+
readonly light: Float64Array;
|
|
109
|
+
/** What every slot shows: an index into `model.attachments`, or −1 for nothing. */
|
|
110
|
+
readonly shown: Int32Array;
|
|
111
|
+
/** Every attachment's world vertices at its `offset`, written by `computeVertices` for the attachments shown. */
|
|
112
|
+
readonly vertices: Float64Array;
|
|
113
|
+
/** The running sum of `step`'s steps, and the animation time the last pose was posed at. */
|
|
114
|
+
trackTime = 0;
|
|
115
|
+
time = 0;
|
|
116
|
+
|
|
117
|
+
private current: RuntimeAnimation | null = null;
|
|
118
|
+
private readonly cursors: Int32Array;
|
|
119
|
+
// The constraints' values as posed: an ik's mix, softness and flags, a transform's six mixes.
|
|
120
|
+
private readonly ikMix: Float64Array;
|
|
121
|
+
private readonly ikSoftness: Float64Array;
|
|
122
|
+
private readonly ikFlags: Uint8Array;
|
|
123
|
+
private readonly mixes: Float64Array;
|
|
124
|
+
// 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.
|
|
125
|
+
private readonly changed: Int32Array;
|
|
126
|
+
private readonly inWorld: Int32Array;
|
|
127
|
+
private readonly changedMark: Uint8Array;
|
|
128
|
+
private readonly inWorldMark: Uint8Array;
|
|
129
|
+
private readonly below: Uint8Array;
|
|
130
|
+
private readonly visit: Uint8Array;
|
|
131
|
+
private readonly w = new Float64Array(WORLDS);
|
|
132
|
+
|
|
133
|
+
constructor(model: RuntimeModel) {
|
|
134
|
+
this.model = model;
|
|
135
|
+
const nb = model.boneNames.length;
|
|
136
|
+
const ns = model.slots.length;
|
|
137
|
+
const nc = model.constraints.length;
|
|
138
|
+
this.locals = new Float64Array(nb * LOCALS);
|
|
139
|
+
this.world = new Float64Array(nb * WORLDS);
|
|
140
|
+
this.light = new Float64Array(ns * 4);
|
|
141
|
+
this.shown = new Int32Array(ns);
|
|
142
|
+
this.vertices = new Float64Array(model.vertexCount);
|
|
143
|
+
this.cursors = new Int32Array(Math.max(1, model.cursors)).fill(-1);
|
|
144
|
+
this.ikMix = new Float64Array(nc);
|
|
145
|
+
this.ikSoftness = new Float64Array(nc);
|
|
146
|
+
this.ikFlags = new Uint8Array(nc);
|
|
147
|
+
this.mixes = new Float64Array(nc * 6);
|
|
148
|
+
// Sized by the longest target list (`RuntimeModel.scratch`): a transform may name one bone several times, and every entry is applied and reposed.
|
|
149
|
+
this.changed = new Int32Array(model.scratch);
|
|
150
|
+
this.inWorld = new Int32Array(model.scratch);
|
|
151
|
+
this.changedMark = new Uint8Array(nb);
|
|
152
|
+
this.inWorldMark = new Uint8Array(nb);
|
|
153
|
+
this.below = new Uint8Array(nb);
|
|
154
|
+
this.visit = new Uint8Array(nb);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The animation this pose plays (a covered one: `model.animation` throws a `RuntimeRefusal` for a refused one), from track time 0. */
|
|
158
|
+
setAnimation(name: string): void {
|
|
159
|
+
this.current = this.model.animation(name);
|
|
160
|
+
this.cursors.fill(-1);
|
|
161
|
+
this.trackTime = 0;
|
|
162
|
+
this.time = 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The setup pose: every constraint applied with its own values, every slot as it is set up. */
|
|
166
|
+
setupPose(): void {
|
|
167
|
+
this.current = null;
|
|
168
|
+
this.time = 0;
|
|
169
|
+
this.locals.set(this.model.setup);
|
|
170
|
+
this.hierarchy();
|
|
171
|
+
this.constraintValues(null, 0);
|
|
172
|
+
this.solveConstraints();
|
|
173
|
+
this.slotValues(null, 0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** The animation posed at its time `t`, from the setup pose (the header). */
|
|
177
|
+
apply(t: number): void {
|
|
178
|
+
const anim = this.current;
|
|
179
|
+
if (anim === null) throw new RuntimeInputError('apply: no animation is set (setAnimation first)');
|
|
180
|
+
if (!Number.isFinite(t)) throw new RuntimeInputError(`apply: the time ${t} is not finite`);
|
|
181
|
+
this.time = t;
|
|
182
|
+
this.boneTimelines(anim, t);
|
|
183
|
+
this.hierarchy();
|
|
184
|
+
this.constraintValues(anim, t);
|
|
185
|
+
this.solveConstraints();
|
|
186
|
+
this.slotValues(anim, t);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** 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). */
|
|
190
|
+
step(dt: number): void {
|
|
191
|
+
const anim = this.current;
|
|
192
|
+
if (anim === null) throw new RuntimeInputError('step: no animation is set (setAnimation first)');
|
|
193
|
+
this.trackTime += dt;
|
|
194
|
+
this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// the bones
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
/** The setup locals with the bone timelines' values at `t` (`posedBones`): added, or multiplied for a scale; a channel before its timeline's first key at setup plus 0, or times 1. */
|
|
202
|
+
private boneTimelines(anim: RuntimeAnimation, t: number): void {
|
|
203
|
+
const L = this.locals;
|
|
204
|
+
const S = this.model.setup;
|
|
205
|
+
L.set(S);
|
|
206
|
+
const timelines = anim.bones;
|
|
207
|
+
for (let k = 0; k < timelines.length; k++) {
|
|
208
|
+
const tl = timelines[k];
|
|
209
|
+
const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
|
|
210
|
+
const o = tl.bone * LOCALS;
|
|
211
|
+
const fields = tl.fields;
|
|
212
|
+
for (let c = 0; c < fields.length; c++) {
|
|
213
|
+
const at = o + fields[c];
|
|
214
|
+
if (tl.multiply[c] === 1) L[at] = S[at] * (i < 0 ? 1 : channelValue(tl.curve, i, c, t));
|
|
215
|
+
else L[at] = S[at] + (i < 0 ? 0 : channelValue(tl.curve, i, c, t));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Every bone's world transform from its locals, parents first (`worldTransforms`): an inactive bone as zeros. */
|
|
221
|
+
private hierarchy(): void {
|
|
222
|
+
const nb = this.model.boneNames.length;
|
|
223
|
+
for (let i = 0; i < nb; i++) this.poseBone(i, true);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* One bone's world transform from its locals under its parent's current
|
|
228
|
+
* world (`worldTransforms` and the update order's `poseBone`). An inactive
|
|
229
|
+
* bone is zeroed by the hierarchy (`fresh`) and left as it is by a repose.
|
|
230
|
+
*/
|
|
231
|
+
private poseBone(i: number, fresh: boolean): void {
|
|
232
|
+
const m = this.model;
|
|
233
|
+
const W = this.world;
|
|
234
|
+
const o = i * WORLDS;
|
|
235
|
+
if (m.active[i] === 0) {
|
|
236
|
+
if (fresh) {
|
|
237
|
+
W[o + W_A] = 0;
|
|
238
|
+
W[o + W_B] = 0;
|
|
239
|
+
W[o + W_C] = 0;
|
|
240
|
+
W[o + W_D] = 0;
|
|
241
|
+
W[o + W_X] = 0;
|
|
242
|
+
W[o + W_Y] = 0;
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const L = this.locals;
|
|
247
|
+
const l = i * LOCALS;
|
|
248
|
+
const x = L[l + L_X];
|
|
249
|
+
const y = L[l + L_Y];
|
|
250
|
+
const rotation = L[l + L_ROTATION];
|
|
251
|
+
const scaleX = L[l + L_SCALE_X];
|
|
252
|
+
const scaleY = L[l + L_SCALE_Y];
|
|
253
|
+
const shearX = L[l + L_SHEAR_X];
|
|
254
|
+
const shearY = L[l + L_SHEAR_Y];
|
|
255
|
+
const parent = m.parents[i];
|
|
256
|
+
if (parent < 0) {
|
|
257
|
+
// A root is its own frame at its own x, y.
|
|
258
|
+
const xAngle = (rotation + shearX) * RAD;
|
|
259
|
+
const yAngle = (rotation + 90 + shearY) * RAD;
|
|
260
|
+
W[o + W_A] = Math.cos(xAngle) * scaleX;
|
|
261
|
+
W[o + W_B] = Math.cos(yAngle) * scaleY;
|
|
262
|
+
W[o + W_C] = Math.sin(xAngle) * scaleX;
|
|
263
|
+
W[o + W_D] = Math.sin(yAngle) * scaleY;
|
|
264
|
+
W[o + W_X] = x;
|
|
265
|
+
W[o + W_Y] = y;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const q = parent * WORLDS;
|
|
269
|
+
const pa = W[q + W_A];
|
|
270
|
+
const pb = W[q + W_B];
|
|
271
|
+
const pc = W[q + W_C];
|
|
272
|
+
const pd = W[q + W_D];
|
|
273
|
+
const px = W[q + W_X];
|
|
274
|
+
const py = W[q + W_Y];
|
|
275
|
+
switch (m.modes[i]) {
|
|
276
|
+
case MODE_NORMAL: {
|
|
277
|
+
const xAngle = (rotation + shearX) * RAD;
|
|
278
|
+
const yAngle = (rotation + 90 + shearY) * RAD;
|
|
279
|
+
const la = Math.cos(xAngle) * scaleX;
|
|
280
|
+
const lb = Math.cos(yAngle) * scaleY;
|
|
281
|
+
const lc = Math.sin(xAngle) * scaleX;
|
|
282
|
+
const ld = Math.sin(yAngle) * scaleY;
|
|
283
|
+
W[o + W_A] = pa * la + pb * lc;
|
|
284
|
+
W[o + W_B] = pa * lb + pb * ld;
|
|
285
|
+
W[o + W_C] = pc * la + pd * lc;
|
|
286
|
+
W[o + W_D] = pc * lb + pd * ld;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
case MODE_ONLY_TRANSLATION: {
|
|
290
|
+
const xAngle = (rotation + shearX) * RAD;
|
|
291
|
+
const yAngle = (rotation + 90 + shearY) * RAD;
|
|
292
|
+
W[o + W_A] = Math.cos(xAngle) * scaleX;
|
|
293
|
+
W[o + W_B] = Math.cos(yAngle) * scaleY;
|
|
294
|
+
W[o + W_C] = Math.sin(xAngle) * scaleX;
|
|
295
|
+
W[o + W_D] = Math.sin(yAngle) * scaleY;
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case MODE_NO_ROTATION_OR_REFLECTION: {
|
|
299
|
+
const xLengthSq = pa * pa + pc * pc;
|
|
300
|
+
let c0: number;
|
|
301
|
+
let c1: number;
|
|
302
|
+
let c2: number;
|
|
303
|
+
let c3: number;
|
|
304
|
+
let r: number;
|
|
305
|
+
if (xLengthSq > COLLAPSED_X_AXIS_SQ) {
|
|
306
|
+
const k = Math.abs(pa * pd - pb * pc) / xLengthSq;
|
|
307
|
+
c0 = pa;
|
|
308
|
+
c1 = -pc * k;
|
|
309
|
+
c2 = pc;
|
|
310
|
+
c3 = pa * k;
|
|
311
|
+
r = rotation - Math.atan2(pc, pa) * DEG;
|
|
312
|
+
} else {
|
|
313
|
+
c0 = 0;
|
|
314
|
+
c1 = -pb;
|
|
315
|
+
c2 = 0;
|
|
316
|
+
c3 = pd;
|
|
317
|
+
r = rotation - 90 + Math.atan2(pd, pb) * DEG;
|
|
318
|
+
}
|
|
319
|
+
const xAngle = (r + shearX) * RAD;
|
|
320
|
+
const yAngle = (r + shearY + 90) * RAD;
|
|
321
|
+
const la = Math.cos(xAngle) * scaleX;
|
|
322
|
+
const lb = Math.cos(yAngle) * scaleY;
|
|
323
|
+
const lc = Math.sin(xAngle) * scaleX;
|
|
324
|
+
const ld = Math.sin(yAngle) * scaleY;
|
|
325
|
+
W[o + W_A] = c0 * la + c1 * lc;
|
|
326
|
+
W[o + W_B] = c0 * lb + c1 * ld;
|
|
327
|
+
W[o + W_C] = c2 * la + c3 * lc;
|
|
328
|
+
W[o + W_D] = c2 * lb + c3 * ld;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
default: {
|
|
332
|
+
// noScale and noScaleOrReflection (`noScaleDirection`, then the frame at rotation 0).
|
|
333
|
+
const rr = rotation * RAD;
|
|
334
|
+
const cos = Math.cos(rr);
|
|
335
|
+
const sin = Math.sin(rr);
|
|
336
|
+
const ux0 = pa * cos + pb * sin;
|
|
337
|
+
const uy0 = pc * cos + pd * sin;
|
|
338
|
+
const inverse = 1 / Math.sqrt(ux0 * ux0 + uy0 * uy0);
|
|
339
|
+
const ux = ux0 * inverse;
|
|
340
|
+
const uy = uy0 * inverse;
|
|
341
|
+
const flip = m.modes[i] === MODE_NO_SCALE && pa * pd - pb * pc < 0 ? -1 : 1;
|
|
342
|
+
const t0 = ux;
|
|
343
|
+
const t1 = -uy * flip;
|
|
344
|
+
const t2 = uy;
|
|
345
|
+
const t3 = ux * flip;
|
|
346
|
+
const xAngle = (0 + shearX) * RAD;
|
|
347
|
+
const yAngle = (0 + 90 + shearY) * RAD;
|
|
348
|
+
const la = Math.cos(xAngle) * scaleX;
|
|
349
|
+
const lb = Math.cos(yAngle) * scaleY;
|
|
350
|
+
const lc = Math.sin(xAngle) * scaleX;
|
|
351
|
+
const ld = Math.sin(yAngle) * scaleY;
|
|
352
|
+
W[o + W_A] = t0 * la + t1 * lc;
|
|
353
|
+
W[o + W_B] = t0 * lb + t1 * ld;
|
|
354
|
+
W[o + W_C] = t2 * la + t3 * lc;
|
|
355
|
+
W[o + W_D] = t2 * lb + t3 * ld;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
W[o + W_X] = pa * x + pb * y + px;
|
|
359
|
+
W[o + W_Y] = pc * x + pd * y + py;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ---------------------------------------------------------------------------
|
|
363
|
+
// the constraints
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
|
|
366
|
+
/** Each constraint's values at `t` (`posedRecords`): its own before its timeline's first key, else the keyed values through the setup blend and the key's flags. */
|
|
367
|
+
private constraintValues(anim: RuntimeAnimation | null, t: number): void {
|
|
368
|
+
const cs = this.model.constraints;
|
|
369
|
+
for (let k = 0; k < cs.length; k++) {
|
|
370
|
+
const c = cs[k];
|
|
371
|
+
const tl = anim === null ? null : anim.constraints[k];
|
|
372
|
+
const i = tl === null ? -1 : keyAt(tl.curve, this.cursors, tl.cursor, t);
|
|
373
|
+
if (c.kind === 'ik') {
|
|
374
|
+
if (tl === null || i < 0) {
|
|
375
|
+
this.ikMix[k] = c.mix;
|
|
376
|
+
this.ikSoftness[k] = c.softness;
|
|
377
|
+
this.ikFlags[k] = c.flags;
|
|
378
|
+
} else {
|
|
379
|
+
this.ikMix[k] = c.mix + (channelValue(tl.curve, i, 0, t) - c.mix) * 1;
|
|
380
|
+
this.ikSoftness[k] = c.softness + (channelValue(tl.curve, i, 1, t) - c.softness) * 1;
|
|
381
|
+
this.ikFlags[k] = (tl.flags as Uint8Array)[i];
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
const o = k * 6;
|
|
385
|
+
for (let p = 0; p < 6; p++) this.mixes[o + p] = tl === null || i < 0 ? c.mixes[p] : c.mixes[p] + (channelValue(tl.curve, i, p, t) - c.mixes[p]) * 1;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Every applied constraint in the document's order, each followed by the update order's repose (`applyConstraints`). */
|
|
391
|
+
private solveConstraints(): void {
|
|
392
|
+
const cs = this.model.constraints;
|
|
393
|
+
for (let k = 0; k < cs.length; k++) {
|
|
394
|
+
const c = cs[k];
|
|
395
|
+
if (!c.applied) continue;
|
|
396
|
+
if (c.kind === 'ik') this.solveIk(c, k);
|
|
397
|
+
else this.solveTransform(c, k);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private solveIk(c: RuntimeIk, k: number): void {
|
|
402
|
+
const mix = this.ikMix[k];
|
|
403
|
+
if (mix === 0) return;
|
|
404
|
+
const flags = this.ikFlags[k];
|
|
405
|
+
if (c.bones.length === 1) {
|
|
406
|
+
this.solveOne(c.bones[0], c.target, mix, (flags & IK_COMPRESS) !== 0, (flags & IK_STRETCH) !== 0, c.scaleY);
|
|
407
|
+
this.changed[0] = c.bones[0];
|
|
408
|
+
this.repose(1, 0);
|
|
409
|
+
} else if (this.solveTwo(c, mix, this.ikSoftness[k], flags)) {
|
|
410
|
+
this.changed[0] = c.bones[0];
|
|
411
|
+
this.repose(1, 0);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** A one-bone ik (`solveOne`). */
|
|
416
|
+
private solveOne(bi: number, ti: number, mix: number, compress: boolean, stretch: boolean, scaleYMode: number): void {
|
|
417
|
+
const m = this.model;
|
|
418
|
+
const L = this.locals;
|
|
419
|
+
const W = this.world;
|
|
420
|
+
const parent = m.parents[bi];
|
|
421
|
+
const q = parent * WORLDS;
|
|
422
|
+
let pa = 1;
|
|
423
|
+
let pb = 0;
|
|
424
|
+
let pc = 0;
|
|
425
|
+
let pd = 1;
|
|
426
|
+
let pwx = 0;
|
|
427
|
+
let pwy = 0;
|
|
428
|
+
if (parent >= 0) {
|
|
429
|
+
pa = W[q + W_A];
|
|
430
|
+
pb = W[q + W_B];
|
|
431
|
+
pc = W[q + W_C];
|
|
432
|
+
pd = W[q + W_D];
|
|
433
|
+
pwx = W[q + W_X];
|
|
434
|
+
pwy = W[q + W_Y];
|
|
435
|
+
}
|
|
436
|
+
const bw = bi * WORLDS;
|
|
437
|
+
const tw = ti * WORLDS;
|
|
438
|
+
const l = bi * LOCALS;
|
|
439
|
+
const mode = m.modes[bi];
|
|
440
|
+
let turn = 0;
|
|
441
|
+
let lx: number;
|
|
442
|
+
let ly: number;
|
|
443
|
+
if (mode === MODE_NO_ROTATION_OR_REFLECTION) {
|
|
444
|
+
const k = Math.abs(pa * pd - pb * pc) / Math.max(0.00001, pa * pa + pc * pc);
|
|
445
|
+
pb = -pc * k;
|
|
446
|
+
pd = pa * k;
|
|
447
|
+
turn = Math.atan2(pc, pa) * DEG;
|
|
448
|
+
const det = pa * pd - pb * pc;
|
|
449
|
+
const x = W[tw + W_X] - pwx;
|
|
450
|
+
const y = W[tw + W_Y] - pwy;
|
|
451
|
+
if (Math.abs(det) <= 0.00001) {
|
|
452
|
+
lx = 0;
|
|
453
|
+
ly = 0;
|
|
454
|
+
} else {
|
|
455
|
+
lx = (x * pd - y * pb) / det - L[l + L_X];
|
|
456
|
+
ly = (y * pa - x * pc) / det - L[l + L_Y];
|
|
457
|
+
}
|
|
458
|
+
} else if (mode === MODE_ONLY_TRANSLATION) {
|
|
459
|
+
lx = W[tw + W_X] - W[bw + W_X];
|
|
460
|
+
ly = W[tw + W_Y] - W[bw + W_Y];
|
|
461
|
+
} else {
|
|
462
|
+
const det = pa * pd - pb * pc;
|
|
463
|
+
const x = W[tw + W_X] - pwx;
|
|
464
|
+
const y = W[tw + W_Y] - pwy;
|
|
465
|
+
if (Math.abs(det) <= 0.00001) {
|
|
466
|
+
lx = 0;
|
|
467
|
+
ly = 0;
|
|
468
|
+
} else {
|
|
469
|
+
lx = (x * pd - y * pb) / det - L[l + L_X];
|
|
470
|
+
ly = (y * pa - x * pc) / det - L[l + L_Y];
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const rotation = L[l + L_ROTATION];
|
|
474
|
+
const scaleX = L[l + L_SCALE_X];
|
|
475
|
+
let r = -L[l + L_SHEAR_X] - rotation + turn + Math.atan2(ly, lx) * DEG;
|
|
476
|
+
if (scaleX < 0) r += 180;
|
|
477
|
+
L[l + L_ROTATION] = rotation + wrap180(r) * mix;
|
|
478
|
+
const len = m.lengths[bi] * scaleX;
|
|
479
|
+
if ((compress || stretch) && len > 0.00001) {
|
|
480
|
+
const wx = W[tw + W_X] - W[bw + W_X];
|
|
481
|
+
const wy = W[tw + W_Y] - W[bw + W_Y];
|
|
482
|
+
const d = mode === MODE_NO_SCALE || mode === MODE_NO_SCALE_OR_REFLECTION ? Math.sqrt(wx * wx + wy * wy) : Math.sqrt(lx * lx + ly * ly);
|
|
483
|
+
if ((compress && d < len) || (stretch && d > len)) {
|
|
484
|
+
const s = (d / len - 1) * mix + 1;
|
|
485
|
+
L[l + L_SCALE_X] = scaleX * s;
|
|
486
|
+
if (scaleYMode === SCALE_Y_UNIFORM) L[l + L_SCALE_Y] = L[l + L_SCALE_Y] * s;
|
|
487
|
+
else if (scaleYMode === SCALE_Y_VOLUME) L[l + L_SCALE_Y] = volumeScaleY(L[l + L_SCALE_Y], s);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** A two-bone ik (`solveTwo`): whether it changed the parent. */
|
|
493
|
+
private solveTwo(c: RuntimeIk, mix: number, softness: number, flags: number): boolean {
|
|
494
|
+
const m = this.model;
|
|
495
|
+
const L = this.locals;
|
|
496
|
+
const W = this.world;
|
|
497
|
+
const pi = c.bones[0];
|
|
498
|
+
const ci = c.bones[1];
|
|
499
|
+
if (m.modes[pi] !== MODE_NORMAL || m.modes[ci] !== MODE_NORMAL) return false;
|
|
500
|
+
const stretch = (flags & IK_STRETCH) !== 0;
|
|
501
|
+
const g = m.parents[pi] * WORLDS;
|
|
502
|
+
const pw = pi * WORLDS;
|
|
503
|
+
const tw = c.target * WORLDS;
|
|
504
|
+
const pl = pi * LOCALS;
|
|
505
|
+
const cl = ci * LOCALS;
|
|
506
|
+
const ga = W[g + W_A];
|
|
507
|
+
const gb = W[g + W_B];
|
|
508
|
+
const gc = W[g + W_C];
|
|
509
|
+
const gd = W[g + W_D];
|
|
510
|
+
const gx = W[g + W_X];
|
|
511
|
+
const gy = W[g + W_Y];
|
|
512
|
+
const gdet = ga * gd - gb * gc;
|
|
513
|
+
const gid = Math.abs(gdet) <= 0.00001 ? 0 : 1 / gdet;
|
|
514
|
+
const px = L[pl + L_X];
|
|
515
|
+
const py = L[pl + L_Y];
|
|
516
|
+
const psx = L[pl + L_SCALE_X];
|
|
517
|
+
const psy = L[pl + L_SCALE_Y];
|
|
518
|
+
const csx = L[cl + L_SCALE_X];
|
|
519
|
+
let sx = psx;
|
|
520
|
+
let sy = psy;
|
|
521
|
+
const cx = L[cl + L_X];
|
|
522
|
+
let cy = L[cl + L_Y];
|
|
523
|
+
const uniform = Math.abs(Math.abs(psx) - Math.abs(psy)) <= 0.00001;
|
|
524
|
+
if (!uniform || stretch) cy = 0;
|
|
525
|
+
let x = W[pw + W_A] * cx + W[pw + W_B] * cy + W[pw + W_X] - gx;
|
|
526
|
+
let y = W[pw + W_C] * cx + W[pw + W_D] * cy + W[pw + W_Y] - gy;
|
|
527
|
+
const ox = (x * gd - y * gb) * gid;
|
|
528
|
+
const oy = (y * ga - x * gc) * gid;
|
|
529
|
+
const dx = ox - px;
|
|
530
|
+
const dy = oy - py;
|
|
531
|
+
const l1 = Math.sqrt(dx * dx + dy * dy);
|
|
532
|
+
let l2 = m.lengths[ci] * Math.abs(csx);
|
|
533
|
+
if (l1 < 0.00001) {
|
|
534
|
+
this.solveOne(pi, c.target, mix, false, stretch, 0);
|
|
535
|
+
L[cl + L_ROTATION] = 0;
|
|
536
|
+
L[cl + L_Y] = cy;
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
x = W[tw + W_X] - gx;
|
|
540
|
+
y = W[tw + W_Y] - gy;
|
|
541
|
+
let tx = (x * gd - y * gb) * gid;
|
|
542
|
+
let ty = (y * ga - x * gc) * gid;
|
|
543
|
+
tx -= px;
|
|
544
|
+
ty -= py;
|
|
545
|
+
let d2 = tx * tx + ty * ty;
|
|
546
|
+
const bend = (flags & IK_BEND_POSITIVE) !== 0 ? 1 : -1;
|
|
547
|
+
if (softness !== 0) {
|
|
548
|
+
const soft = softness * (Math.abs(psx) * (Math.abs(csx) + 1) * 0.5);
|
|
549
|
+
const d = Math.sqrt(d2);
|
|
550
|
+
const sd = d - l1 - l2 * Math.abs(psx) + soft;
|
|
551
|
+
if (sd > 0) {
|
|
552
|
+
const q = Math.min(1, sd / (soft * 2)) - 1;
|
|
553
|
+
const pull = (sd - soft * (1 - q * q)) / d;
|
|
554
|
+
tx -= pull * tx;
|
|
555
|
+
ty -= pull * ty;
|
|
556
|
+
d2 = tx * tx + ty * ty;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
let a1: number;
|
|
560
|
+
let a2: number;
|
|
561
|
+
if (uniform) {
|
|
562
|
+
l2 *= Math.abs(psx);
|
|
563
|
+
let cos = (d2 - l1 * l1 - l2 * l2) / (2 * l1 * l2);
|
|
564
|
+
if (cos < -1) {
|
|
565
|
+
cos = -1;
|
|
566
|
+
a2 = Math.PI * bend;
|
|
567
|
+
} else if (cos > 1) {
|
|
568
|
+
cos = 1;
|
|
569
|
+
a2 = 0;
|
|
570
|
+
if (stretch) {
|
|
571
|
+
const f = (Math.sqrt(d2) / (l1 + l2) - 1) * mix + 1;
|
|
572
|
+
sx *= f;
|
|
573
|
+
if (c.scaleY === SCALE_Y_UNIFORM) sy *= f;
|
|
574
|
+
else if (c.scaleY === SCALE_Y_VOLUME) sy = volumeScaleY(sy, f);
|
|
575
|
+
}
|
|
576
|
+
} else a2 = Math.acos(cos) * bend;
|
|
577
|
+
const along = l1 + l2 * cos;
|
|
578
|
+
const across = l2 * Math.sin(a2);
|
|
579
|
+
a1 = Math.atan2(ty * along - tx * across, tx * along + ty * across);
|
|
580
|
+
} else {
|
|
581
|
+
const ea = Math.abs(psx) * l2;
|
|
582
|
+
const eb = Math.abs(psy) * l2;
|
|
583
|
+
const aa = ea * ea;
|
|
584
|
+
const bb = eb * eb;
|
|
585
|
+
const qa = bb - aa;
|
|
586
|
+
const qb = -2 * bb * l1;
|
|
587
|
+
const qc = bb * l1 * l1 + aa * d2 - aa * bb;
|
|
588
|
+
const disc = qb * qb - 4 * qa * qc;
|
|
589
|
+
let solved = false;
|
|
590
|
+
a1 = 0;
|
|
591
|
+
a2 = 0;
|
|
592
|
+
if (disc >= 0) {
|
|
593
|
+
let q = Math.sqrt(disc);
|
|
594
|
+
if (qb < 0) q = -q;
|
|
595
|
+
q = -(qb + q) * 0.5;
|
|
596
|
+
const r0 = q / qa;
|
|
597
|
+
const r1 = qc / q;
|
|
598
|
+
const X = Math.abs(r0) < Math.abs(r1) ? r0 : r1;
|
|
599
|
+
const y2 = d2 - X * X;
|
|
600
|
+
if (y2 >= 0) {
|
|
601
|
+
const Y = Math.sqrt(y2) * bend;
|
|
602
|
+
a1 = Math.atan2(ty, tx) - Math.atan2(Y, X);
|
|
603
|
+
a2 = Math.atan2(Y / Math.abs(psy), (X - l1) / Math.abs(psx));
|
|
604
|
+
solved = true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (!solved) {
|
|
608
|
+
let nearAngle = RUNTIME_PI;
|
|
609
|
+
let nearX = l1 - ea;
|
|
610
|
+
let nearY = 0;
|
|
611
|
+
let nearDist = (l1 - ea) * (l1 - ea);
|
|
612
|
+
let farAngle = 0;
|
|
613
|
+
let farX = l1 + ea;
|
|
614
|
+
let farY = 0;
|
|
615
|
+
let farDist = (l1 + ea) * (l1 + ea);
|
|
616
|
+
const vertex = (-ea * l1) / (aa - bb);
|
|
617
|
+
if (vertex >= -1 && vertex <= 1) {
|
|
618
|
+
const angle = Math.acos(vertex);
|
|
619
|
+
const vx = ea * Math.cos(angle) + l1;
|
|
620
|
+
const vy = eb * Math.sin(angle);
|
|
621
|
+
const dist = vx * vx + vy * vy;
|
|
622
|
+
if (dist < nearDist) {
|
|
623
|
+
nearAngle = angle;
|
|
624
|
+
nearX = vx;
|
|
625
|
+
nearY = vy;
|
|
626
|
+
nearDist = dist;
|
|
627
|
+
}
|
|
628
|
+
if (dist > farDist) {
|
|
629
|
+
farAngle = angle;
|
|
630
|
+
farX = vx;
|
|
631
|
+
farY = vy;
|
|
632
|
+
farDist = dist;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (d2 <= (nearDist + farDist) * 0.5) {
|
|
636
|
+
a1 = Math.atan2(ty, tx) - Math.atan2(nearY * bend, nearX);
|
|
637
|
+
a2 = nearAngle * bend;
|
|
638
|
+
} else {
|
|
639
|
+
a1 = Math.atan2(ty, tx) - Math.atan2(farY * bend, farX);
|
|
640
|
+
a2 = farAngle * bend;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
const signs = (psx < 0 ? -1 : 1) * (psy < 0 ? -1 : 1);
|
|
645
|
+
const offset = Math.atan2(cy, cx);
|
|
646
|
+
const parentRotation = L[pl + L_ROTATION];
|
|
647
|
+
const parentChange = (a1 - signs * offset) * DEG + (psx < 0 ? 180 : 0) - parentRotation;
|
|
648
|
+
L[pl + L_ROTATION] = parentRotation + wrap180(parentChange) * mix;
|
|
649
|
+
L[pl + L_SCALE_X] = sx;
|
|
650
|
+
L[pl + L_SCALE_Y] = sy;
|
|
651
|
+
const childTarget = ((a2 + signs * offset) * DEG - L[cl + L_SHEAR_X]) * signs + (csx < 0 ? 180 : 0);
|
|
652
|
+
const childRotation = L[cl + L_ROTATION];
|
|
653
|
+
L[cl + L_Y] = cy;
|
|
654
|
+
L[cl + L_ROTATION] = childRotation + wrap180(childTarget - childRotation) * mix;
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** A world-space, absolute transform (`solveTransform`): each bone's world edited in turn and written back, then the repose. */
|
|
659
|
+
private solveTransform(c: RuntimeTransform, k: number): void {
|
|
660
|
+
const W = this.world;
|
|
661
|
+
const w = this.w;
|
|
662
|
+
const mixes = this.mixes;
|
|
663
|
+
const o6 = k * 6;
|
|
664
|
+
let n = 0;
|
|
665
|
+
for (let bk = 0; bk < c.bones.length; bk++) {
|
|
666
|
+
const bi = c.bones[bk];
|
|
667
|
+
const o = bi * WORLDS;
|
|
668
|
+
w[W_A] = W[o + W_A];
|
|
669
|
+
w[W_B] = W[o + W_B];
|
|
670
|
+
w[W_C] = W[o + W_C];
|
|
671
|
+
w[W_D] = W[o + W_D];
|
|
672
|
+
w[W_X] = W[o + W_X];
|
|
673
|
+
w[W_Y] = W[o + W_Y];
|
|
674
|
+
let applied = false;
|
|
675
|
+
for (let f = 0; f < c.fromProperty.length; f++) {
|
|
676
|
+
const value = this.sourceValue(c, c.fromProperty[f]) - c.fromOffset[f];
|
|
677
|
+
for (let q = c.toStart[f]; q < c.toEnd[f]; q++) {
|
|
678
|
+
const property = c.toProperty[q];
|
|
679
|
+
const mix = mixes[o6 + property];
|
|
680
|
+
if (mix === 0) continue;
|
|
681
|
+
let v = value * c.toScale[q] + c.toOffset[q];
|
|
682
|
+
if (c.clamp) {
|
|
683
|
+
const lo = Math.min(c.toOffset[q], c.toMax[q]);
|
|
684
|
+
const hi = Math.max(c.toOffset[q], c.toMax[q]);
|
|
685
|
+
v = v < lo ? lo : v > hi ? hi : v;
|
|
686
|
+
}
|
|
687
|
+
applied = true;
|
|
688
|
+
applyWorld(w, property, v, mix);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
if (!applied) continue;
|
|
692
|
+
W[o + W_A] = w[W_A];
|
|
693
|
+
W[o + W_B] = w[W_B];
|
|
694
|
+
W[o + W_C] = w[W_C];
|
|
695
|
+
W[o + W_D] = w[W_D];
|
|
696
|
+
W[o + W_X] = w[W_X];
|
|
697
|
+
W[o + W_Y] = w[W_Y];
|
|
698
|
+
this.changed[n] = bi;
|
|
699
|
+
this.inWorld[n] = bi;
|
|
700
|
+
n++;
|
|
701
|
+
}
|
|
702
|
+
if (n > 0) this.repose(n, n);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** The source's world value of a property (`sourceValue`, world source). */
|
|
706
|
+
private sourceValue(c: RuntimeTransform, property: number): number {
|
|
707
|
+
const W = this.world;
|
|
708
|
+
const s = c.source * WORLDS;
|
|
709
|
+
const o = c.offsets;
|
|
710
|
+
const a = W[s + W_A];
|
|
711
|
+
const b = W[s + W_B];
|
|
712
|
+
const cc = W[s + W_C];
|
|
713
|
+
const d = W[s + W_D];
|
|
714
|
+
switch (property) {
|
|
715
|
+
case P_ROTATE: {
|
|
716
|
+
const det = a * d - b * cc;
|
|
717
|
+
let r = Math.atan2(cc, a) * DEG + (det > 0 ? o[P_ROTATE] : -o[P_ROTATE]);
|
|
718
|
+
if (r < 0) r += 360;
|
|
719
|
+
return r;
|
|
720
|
+
}
|
|
721
|
+
case P_X:
|
|
722
|
+
return a * o[P_X] + b * o[P_Y] + W[s + W_X];
|
|
723
|
+
case P_Y:
|
|
724
|
+
return cc * o[P_X] + d * o[P_Y] + W[s + W_Y];
|
|
725
|
+
case P_SCALE_X:
|
|
726
|
+
return Math.sqrt(a * a + cc * cc) + o[P_SCALE_X];
|
|
727
|
+
case P_SCALE_Y:
|
|
728
|
+
return Math.sqrt(b * b + d * d) + o[P_SCALE_Y];
|
|
729
|
+
default:
|
|
730
|
+
return (Math.atan2(d, b) - Math.atan2(cc, a)) * DEG - 90 + o[5];
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* After a constraint (`repose`): the `nInWorld` bones it moved in world space
|
|
736
|
+
* (the first of `inWorld`) read back into local values, then every bone below
|
|
737
|
+
* one of the `nChanged` it changed (`changed`) posed again, in bone order —
|
|
738
|
+
* but not through an inactive bone it did not move itself, and not a bone it
|
|
739
|
+
* moved in world space.
|
|
740
|
+
*/
|
|
741
|
+
private repose(nChanged: number, nInWorld: number): void {
|
|
742
|
+
const m = this.model;
|
|
743
|
+
const changed = this.changed;
|
|
744
|
+
const changedMark = this.changedMark;
|
|
745
|
+
const inWorldMark = this.inWorldMark;
|
|
746
|
+
const below = this.below;
|
|
747
|
+
for (let k = 0; k < nInWorld; k++) {
|
|
748
|
+
this.localFromWorld(this.inWorld[k]);
|
|
749
|
+
inWorldMark[this.inWorld[k]] = 1;
|
|
750
|
+
}
|
|
751
|
+
for (let k = 0; k < nChanged; k++) {
|
|
752
|
+
changedMark[changed[k]] = 1;
|
|
753
|
+
below[changed[k]] = 1;
|
|
754
|
+
}
|
|
755
|
+
const starts = m.subtreeStart;
|
|
756
|
+
const list = m.subtrees;
|
|
757
|
+
const parents = m.parents;
|
|
758
|
+
const active = m.active;
|
|
759
|
+
if (nChanged === 1) {
|
|
760
|
+
const e = starts[changed[0] + 1];
|
|
761
|
+
for (let q = starts[changed[0]]; q < e; q++) this.reposeOne(list[q], parents, active);
|
|
762
|
+
for (let q = starts[changed[0]]; q < e; q++) below[list[q]] = 0;
|
|
763
|
+
} else {
|
|
764
|
+
// The union of the changed bones' subtrees, in bone order.
|
|
765
|
+
const visit = this.visit;
|
|
766
|
+
let first = parents.length;
|
|
767
|
+
for (let k = 0; k < nChanged; k++) {
|
|
768
|
+
const b = changed[k];
|
|
769
|
+
if (b < first) first = b;
|
|
770
|
+
const e = starts[b + 1];
|
|
771
|
+
for (let q = starts[b]; q < e; q++) visit[list[q]] = 1;
|
|
772
|
+
}
|
|
773
|
+
for (let b = first; b < parents.length; b++) if (visit[b] === 1) this.reposeOne(b, parents, active);
|
|
774
|
+
for (let b = first; b < parents.length; b++) {
|
|
775
|
+
visit[b] = 0;
|
|
776
|
+
below[b] = 0;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
for (let k = 0; k < nChanged; k++) changedMark[changed[k]] = 0;
|
|
780
|
+
for (let k = 0; k < nInWorld; k++) inWorldMark[this.inWorld[k]] = 0;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
private reposeOne(b: number, parents: Int32Array, active: Uint8Array): void {
|
|
784
|
+
const below = this.below;
|
|
785
|
+
const p = parents[b];
|
|
786
|
+
if (p >= 0 && below[p] === 1 && (active[p] === 1 || this.changedMark[p] === 1)) below[b] = 1;
|
|
787
|
+
if (below[b] === 1 && this.inWorldMark[b] === 0) this.poseBone(b, false);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/** A bone's local values read back from its world transform (`localFromWorld`) — `normal`, `onlyTranslation` and `noRotationOrReflection`; a world target in a `noScale` mode is refused at load. */
|
|
791
|
+
private localFromWorld(bi: number): void {
|
|
792
|
+
const m = this.model;
|
|
793
|
+
const W = this.world;
|
|
794
|
+
const L = this.locals;
|
|
795
|
+
const w = bi * WORLDS;
|
|
796
|
+
const l = bi * LOCALS;
|
|
797
|
+
const parent = m.parents[bi];
|
|
798
|
+
const mode = parent < 0 ? MODE_NORMAL : m.modes[bi];
|
|
799
|
+
let pa = 1;
|
|
800
|
+
let pb = 0;
|
|
801
|
+
let pc = 0;
|
|
802
|
+
let pd = 1;
|
|
803
|
+
let px = 0;
|
|
804
|
+
let py = 0;
|
|
805
|
+
if (parent >= 0) {
|
|
806
|
+
const q = parent * WORLDS;
|
|
807
|
+
pa = W[q + W_A];
|
|
808
|
+
pb = W[q + W_B];
|
|
809
|
+
pc = W[q + W_C];
|
|
810
|
+
pd = W[q + W_D];
|
|
811
|
+
px = W[q + W_X];
|
|
812
|
+
py = W[q + W_Y];
|
|
813
|
+
}
|
|
814
|
+
const pid = 1 / (pa * pd - pb * pc);
|
|
815
|
+
const dx = W[w + W_X] - px;
|
|
816
|
+
const dy = W[w + W_Y] - py;
|
|
817
|
+
L[l + L_X] = dx * (pd * pid) - dy * (pb * pid);
|
|
818
|
+
L[l + L_Y] = dy * (pa * pid) - dx * (pc * pid);
|
|
819
|
+
L[l + L_SHEAR_X] = 0;
|
|
820
|
+
const wa = W[w + W_A];
|
|
821
|
+
const wb = W[w + W_B];
|
|
822
|
+
const wc = W[w + W_C];
|
|
823
|
+
const wd = W[w + W_D];
|
|
824
|
+
if (mode === MODE_ONLY_TRANSLATION) {
|
|
825
|
+
this.decompose(l, wa, wc, wb, wd);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (mode === MODE_NO_ROTATION_OR_REFLECTION) {
|
|
829
|
+
const xLengthInverse = 1 / (pa * pa + pc * pc);
|
|
830
|
+
const detInverse = 1 / Math.abs(pa * pd - pb * pc);
|
|
831
|
+
this.decompose(l, (pa * wa + pc * wc) * xLengthInverse, (pa * wc - pc * wa) * detInverse, (pa * wb + pc * wd) * xLengthInverse, (pa * wd - pc * wb) * detInverse);
|
|
832
|
+
L[l + L_ROTATION] = L[l + L_ROTATION] + Math.atan2(pc, pa) * DEG;
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const ia = pd * pid;
|
|
836
|
+
const ib = pb * pid;
|
|
837
|
+
const ic = pc * pid;
|
|
838
|
+
const id = pa * pid;
|
|
839
|
+
this.decompose(l, ia * wa - ib * wc, id * wc - ic * wa, ia * wb - ib * wd, id * wd - ic * wb);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
/** A local matrix with columns `(la, lc)` and `(lb, ld)` as rotation, scales and shear (`decompose`). */
|
|
843
|
+
private decompose(l: number, la: number, lc: number, lb: number, ld: number): void {
|
|
844
|
+
const L = this.locals;
|
|
845
|
+
const sx = Math.sqrt(la * la + lc * lc);
|
|
846
|
+
const yLength = Math.sqrt(lb * lb + ld * ld);
|
|
847
|
+
if (!(sx > 0.0001)) {
|
|
848
|
+
L[l + L_SCALE_X] = 0;
|
|
849
|
+
L[l + L_SCALE_Y] = yLength;
|
|
850
|
+
L[l + L_SHEAR_Y] = 0;
|
|
851
|
+
L[l + L_ROTATION] = yLength > 0.00001 ? Math.atan2(ld, lb) * DEG - 90 : 0;
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const xAngle = Math.atan2(lc, la) * DEG;
|
|
855
|
+
const det = la * ld - lb * lc;
|
|
856
|
+
const yAngle = Math.atan2(ld, lb) * DEG;
|
|
857
|
+
L[l + L_ROTATION] = xAngle;
|
|
858
|
+
L[l + L_SCALE_X] = sx;
|
|
859
|
+
L[l + L_SCALE_Y] = det < 0 ? -yLength : yLength;
|
|
860
|
+
const right = det < 0 ? -90 : 90;
|
|
861
|
+
let shear = yAngle - (xAngle + right);
|
|
862
|
+
shear = shear > 180 ? shear - 360 : shear < -180 ? shear + 360 : shear;
|
|
863
|
+
L[l + L_SHEAR_Y] = yLength > 0.00001 ? shear : 0;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// ---------------------------------------------------------------------------
|
|
867
|
+
// the slots and the vertices
|
|
868
|
+
// ---------------------------------------------------------------------------
|
|
869
|
+
|
|
870
|
+
/** Every slot's colour and what it shows (`posedSlots`): the setup's, then each of its timelines at `t` in order. */
|
|
871
|
+
private slotValues(anim: RuntimeAnimation | null, t: number): void {
|
|
872
|
+
const slots = this.model.slots;
|
|
873
|
+
const light = this.light;
|
|
874
|
+
for (let s = 0; s < slots.length; s++) {
|
|
875
|
+
const slot = slots[s];
|
|
876
|
+
this.shown[s] = slot.setupAttachment;
|
|
877
|
+
const o = s * 4;
|
|
878
|
+
light[o] = slot.light[0];
|
|
879
|
+
light[o + 1] = slot.light[1];
|
|
880
|
+
light[o + 2] = slot.light[2];
|
|
881
|
+
light[o + 3] = slot.light[3];
|
|
882
|
+
}
|
|
883
|
+
if (anim === null) return;
|
|
884
|
+
const timelines = anim.slots;
|
|
885
|
+
for (let k = 0; k < timelines.length; k++) {
|
|
886
|
+
const tl = timelines[k];
|
|
887
|
+
const i = keyAt(tl.curve, this.cursors, tl.cursor, t);
|
|
888
|
+
const attachments = tl.attachments;
|
|
889
|
+
if (attachments !== null) {
|
|
890
|
+
this.shown[tl.slot] = i < 0 ? slots[tl.slot].setupAttachment : attachments[i];
|
|
891
|
+
continue;
|
|
892
|
+
}
|
|
893
|
+
const channels = tl.channels as Int32Array;
|
|
894
|
+
const setup = slots[tl.slot].light;
|
|
895
|
+
const o = tl.slot * 4;
|
|
896
|
+
for (let c = 0; c < channels.length; c++) {
|
|
897
|
+
const at = channels[c];
|
|
898
|
+
light[o + at] = i < 0 ? setup[at] : clamp01(channelValue(tl.curve, i, c, t));
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/** Every shown attachment's world vertices into `vertices` at its offset (`regionCorners`, `worldVertices`). */
|
|
904
|
+
computeVertices(): void {
|
|
905
|
+
const m = this.model;
|
|
906
|
+
const W = this.world;
|
|
907
|
+
const out = this.vertices;
|
|
908
|
+
const shown = this.shown;
|
|
909
|
+
const attachments = m.attachments;
|
|
910
|
+
for (let s = 0; s < shown.length; s++) {
|
|
911
|
+
const index = shown[s];
|
|
912
|
+
if (index < 0) continue;
|
|
913
|
+
const at = attachments[index];
|
|
914
|
+
if (at.draw === DRAW_NONE) continue;
|
|
915
|
+
let o = at.offset;
|
|
916
|
+
if (at.draw === DRAW_REGION || at.draw === DRAW_MESH) {
|
|
917
|
+
const b = at.bone * WORLDS;
|
|
918
|
+
const a = W[b + W_A];
|
|
919
|
+
const bb = W[b + W_B];
|
|
920
|
+
const c = W[b + W_C];
|
|
921
|
+
const d = W[b + W_D];
|
|
922
|
+
const wx = W[b + W_X];
|
|
923
|
+
const wy = W[b + W_Y];
|
|
924
|
+
const local = at.local;
|
|
925
|
+
if (at.draw === DRAW_REGION) {
|
|
926
|
+
for (let q = 0; q < 8; q += 2) {
|
|
927
|
+
const lx = local[q];
|
|
928
|
+
const ly = local[q + 1];
|
|
929
|
+
out[o++] = a * lx + bb * ly + wx;
|
|
930
|
+
out[o++] = c * lx + d * ly + wy;
|
|
931
|
+
}
|
|
932
|
+
} else {
|
|
933
|
+
for (let q = 0; q < at.count; q += 2) {
|
|
934
|
+
const x = local[q];
|
|
935
|
+
const y = local[q + 1];
|
|
936
|
+
out[o++] = x * a + y * bb + wx;
|
|
937
|
+
out[o++] = x * c + y * d + wy;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
// Weighted: per vertex, the sum over its bindings in order.
|
|
943
|
+
const counts = at.bindingCounts;
|
|
944
|
+
const bones = at.bindingBones;
|
|
945
|
+
const xs = at.bindingX;
|
|
946
|
+
const ys = at.bindingY;
|
|
947
|
+
const weights = at.bindingWeights;
|
|
948
|
+
let n = 0;
|
|
949
|
+
for (let v = 0; v < counts.length; v++) {
|
|
950
|
+
let wx = 0;
|
|
951
|
+
let wy = 0;
|
|
952
|
+
const end = n + counts[v];
|
|
953
|
+
for (; n < end; n++) {
|
|
954
|
+
const b = bones[n] * WORLDS;
|
|
955
|
+
const x = xs[n];
|
|
956
|
+
const y = ys[n];
|
|
957
|
+
const weight = weights[n];
|
|
958
|
+
wx += (x * W[b + W_A] + y * W[b + W_B] + W[b + W_X]) * weight;
|
|
959
|
+
wy += (x * W[b + W_C] + y * W[b + W_D] + W[b + W_Y]) * weight;
|
|
960
|
+
}
|
|
961
|
+
out[o++] = wx;
|
|
962
|
+
out[o++] = wy;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/** A world-space, absolute transform of one property on a world transform (`applyWorld`). */
|
|
969
|
+
function applyWorld(w: Float64Array, property: number, v: number, mix: number): void {
|
|
970
|
+
switch (property) {
|
|
971
|
+
case P_ROTATE: {
|
|
972
|
+
let r = v * RAD - Math.atan2(w[W_C], w[W_A]);
|
|
973
|
+
if (r > RUNTIME_PI) r -= TWO_PI;
|
|
974
|
+
else if (r < -RUNTIME_PI) r += TWO_PI;
|
|
975
|
+
r *= mix;
|
|
976
|
+
const cos = Math.cos(r);
|
|
977
|
+
const sin = Math.sin(r);
|
|
978
|
+
const a = w[W_A];
|
|
979
|
+
const b = w[W_B];
|
|
980
|
+
w[W_A] = cos * a - sin * w[W_C];
|
|
981
|
+
w[W_B] = cos * b - sin * w[W_D];
|
|
982
|
+
w[W_C] = sin * a + cos * w[W_C];
|
|
983
|
+
w[W_D] = sin * b + cos * w[W_D];
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
case P_X:
|
|
987
|
+
w[W_X] = w[W_X] + (v - w[W_X]) * mix;
|
|
988
|
+
return;
|
|
989
|
+
case P_Y:
|
|
990
|
+
w[W_Y] = w[W_Y] + (v - w[W_Y]) * mix;
|
|
991
|
+
return;
|
|
992
|
+
case P_SCALE_X:
|
|
993
|
+
case P_SCALE_Y: {
|
|
994
|
+
const p = property === P_SCALE_X ? W_A : W_B;
|
|
995
|
+
const q = property === P_SCALE_X ? W_C : W_D;
|
|
996
|
+
const s = Math.sqrt(w[p] * w[p] + w[q] * w[q]);
|
|
997
|
+
if (s === 0) return;
|
|
998
|
+
const k = 1 + ((v - s) * mix) / s;
|
|
999
|
+
w[p] *= k;
|
|
1000
|
+
w[q] *= k;
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
default: {
|
|
1004
|
+
const yAngle = Math.atan2(w[W_D], w[W_B]);
|
|
1005
|
+
const xAngle = Math.atan2(w[W_C], w[W_A]);
|
|
1006
|
+
let r = (v + 90) * RAD - (yAngle - xAngle);
|
|
1007
|
+
r = r > RUNTIME_PI ? r - TWO_PI : r < -RUNTIME_PI ? r + TWO_PI : r;
|
|
1008
|
+
const angle = yAngle + r * mix;
|
|
1009
|
+
const s = Math.sqrt(w[W_B] * w[W_B] + w[W_D] * w[W_D]);
|
|
1010
|
+
w[W_B] = Math.cos(angle) * s;
|
|
1011
|
+
w[W_D] = Math.sin(angle) * s;
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|