@rig-c/core 2.71.0 → 2.73.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 +10 -0
- package/docs/AUTHORING.md +172 -11
- package/docs/FACE.md +19 -0
- package/package.json +1 -1
- package/src/alpha_clip.ts +255 -0
- package/src/cli/core_commands.ts +2 -1
- package/src/cli/shared.ts +14 -1
- package/src/compile.ts +364 -27
- package/src/core/clipping.ts +5 -1
- package/src/core/events.ts +17 -5
- package/src/deformgen.ts +2 -2
- package/src/mesh.ts +3 -1
- package/src/meshcompare.ts +146 -22
- package/src/rig.ts +32 -7
- package/src/runtime/index.ts +8 -4
- package/src/runtime/model.ts +37 -20
- package/src/runtime/pose.ts +41 -1
- package/src/types.ts +26 -0
package/src/runtime/model.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The runtime poser's load-time half (issue #1591, stage
|
|
2
|
+
* The runtime poser's load-time half (issue #1591, stage 1): a skin view of
|
|
3
3
|
* the compiled model (`readModel`, then `underSkin` / `underNoSkin` — the
|
|
4
4
|
* oracle's reader and its views, read and never written) compiled once into
|
|
5
5
|
* index-resolved bones and slots, a constraint plan, compiled timelines and
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
* (`RuntimeMissing`), and:
|
|
13
13
|
*
|
|
14
14
|
* - **refuses the model** (`RuntimeRefusal`, every missing feature listed)
|
|
15
|
-
* when one is a property of the whole model — a
|
|
16
|
-
* path, physics or slider constraint, a transform constraint reading or
|
|
15
|
+
* when one is a property of the whole model — a path, physics or slider constraint, a transform constraint reading or
|
|
17
16
|
* writing local values or adding (`localSource`, `localTarget`,
|
|
18
17
|
* `additive`), an ik or transform constraint over a bone the view leaves
|
|
19
18
|
* inactive, a world-space transform onto a `noScale` or
|
|
@@ -23,16 +22,15 @@
|
|
|
23
22
|
* - **refuses an animation** (`RuntimeModel.refused`; `animation(name)`
|
|
24
23
|
* throws the same `RuntimeRefusal`) when the construct is the animation's
|
|
25
24
|
* — an `inherit` timeline, a deform or sequence timeline, a draw-order
|
|
26
|
-
* timeline,
|
|
25
|
+
* timeline, a dark-colour (`rgba2`, `rgb2`) timeline, or an
|
|
27
26
|
* attachment key showing a clipping attachment or a region with no atlas
|
|
28
27
|
* rectangle. Every other animation of the model is posed.
|
|
29
28
|
*
|
|
30
|
-
* `RUNTIME_FEATURES` is the closed list of what can be named.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* a player that misses its cues and reports nothing.
|
|
29
|
+
* `RUNTIME_FEATURES` is the closed list of what can be named. Stage 1 reads
|
|
30
|
+
* named/no-skin views and delivers event keys, with immutable payloads compiled
|
|
31
|
+
* at load. `ignore: ['events']` remains an explicit diagnostic opt-out; it is
|
|
32
|
+
* never the default. A merged view with conflicting attachments still refuses
|
|
33
|
+
* by name, as the oracle itself cannot resolve that view without file order.
|
|
36
34
|
*
|
|
37
35
|
* ## What the oracle decides, the runtime reads
|
|
38
36
|
*
|
|
@@ -48,18 +46,16 @@
|
|
|
48
46
|
* no randomness, no I/O.
|
|
49
47
|
*/
|
|
50
48
|
import type { ModelVertices } from '../model.ts';
|
|
51
|
-
import { activeBones, constraintRecords, CORE_REGION_KINDS, foldInheritMode, readBlend, readColour, shownAttachment, shownRow, sourceOfDoc, type CompiledDocument, type CoreAnimation, type CoreBlendMode } from '../core/index.ts';
|
|
49
|
+
import { activeBones, constraintRecords, CORE_REGION_KINDS, foldInheritMode, readBlend, readColour, shownAttachment, shownRow, sourceOfDoc, underSkin, type CompiledDocument, type CoreAnimation, type CoreBlendMode } from '../core/index.ts';
|
|
52
50
|
import { constraintInactiveWhy, constraintsAbsentWhy, TRANSFORM_PROPERTIES, type CoreIkRecord, type CoreTransformRecord } from '../core/constraints.ts';
|
|
53
|
-
import { CORE_DEFAULT_SKIN } from '../core/skins.ts';
|
|
54
51
|
import { RUNTIME_PI } from '../core/world.ts';
|
|
55
52
|
import { compileCurve, type RuntimeCurve } from './curves.ts';
|
|
56
53
|
|
|
57
54
|
/** The stage of issue #1591 this poser implements. */
|
|
58
|
-
export const RUNTIME_STAGE =
|
|
55
|
+
export const RUNTIME_STAGE = 1;
|
|
59
56
|
|
|
60
57
|
/** Every construct the runtime poser can refuse, by the name its refusals carry. */
|
|
61
58
|
export const RUNTIME_FEATURES = [
|
|
62
|
-
'skin',
|
|
63
59
|
'path-constraint',
|
|
64
60
|
'physics-constraint',
|
|
65
61
|
'slider-constraint',
|
|
@@ -75,7 +71,6 @@ export const RUNTIME_FEATURES = [
|
|
|
75
71
|
'deform-timeline',
|
|
76
72
|
'sequence-timeline',
|
|
77
73
|
'draw-order-timeline',
|
|
78
|
-
'events',
|
|
79
74
|
'dark-colour-timeline',
|
|
80
75
|
] as const;
|
|
81
76
|
export type RuntimeFeature = (typeof RUNTIME_FEATURES)[number];
|
|
@@ -124,6 +119,12 @@ export interface RuntimePlant {
|
|
|
124
119
|
readonly admit?: readonly RuntimeFeature[];
|
|
125
120
|
/** The update order's scratch sized by the bone count instead of the longest target list: the short-buffer plant. */
|
|
126
121
|
readonly scratchByBones?: boolean;
|
|
122
|
+
/** Wrong-skin source selected by a control, never by a consumer. */
|
|
123
|
+
readonly attachmentSkin?: string;
|
|
124
|
+
/** Drops a key at zero on a wrap, the event boundary plant. */
|
|
125
|
+
readonly eventWrapOffByOne?: boolean;
|
|
126
|
+
/** The old restore behavior, which repeats keys up to the restored time. */
|
|
127
|
+
readonly restoreEventsFromStart?: boolean;
|
|
127
128
|
}
|
|
128
129
|
|
|
129
130
|
// A bone's local values, seven per bone.
|
|
@@ -277,6 +278,17 @@ export interface RuntimeConstraintTimeline {
|
|
|
277
278
|
readonly flags: Uint8Array | null;
|
|
278
279
|
}
|
|
279
280
|
|
|
281
|
+
/** One immutable event delivery, reused by every pose of this model. */
|
|
282
|
+
export interface RuntimeEvent {
|
|
283
|
+
readonly name: string;
|
|
284
|
+
readonly time: number;
|
|
285
|
+
readonly int: number;
|
|
286
|
+
readonly float: number;
|
|
287
|
+
readonly string: string;
|
|
288
|
+
readonly volume: number;
|
|
289
|
+
readonly balance: number;
|
|
290
|
+
}
|
|
291
|
+
|
|
280
292
|
/** One covered animation, compiled. */
|
|
281
293
|
export interface RuntimeAnimation {
|
|
282
294
|
readonly name: string;
|
|
@@ -287,12 +299,16 @@ export interface RuntimeAnimation {
|
|
|
287
299
|
readonly constraints: ReadonlyArray<RuntimeConstraintTimeline | null>;
|
|
288
300
|
/** How many key cursors a pose keeps for it. */
|
|
289
301
|
readonly cursors: number;
|
|
290
|
-
/**
|
|
302
|
+
/** Number of authored event keys (including an explicitly muted diagnostic). */
|
|
291
303
|
readonly events: number;
|
|
304
|
+
readonly eventKeys: readonly RuntimeEvent[];
|
|
292
305
|
}
|
|
293
306
|
|
|
294
307
|
/** The compiled model a pose is made of (`compileRuntime`). Read-only once built. */
|
|
295
308
|
export interface RuntimeModel {
|
|
309
|
+
/** Control-only boundary defect; fixed at load. */
|
|
310
|
+
readonly eventWrapOffByOne: boolean;
|
|
311
|
+
readonly restoreEventsFromStart: boolean;
|
|
296
312
|
readonly doc: CompiledDocument;
|
|
297
313
|
readonly boneNames: readonly string[];
|
|
298
314
|
readonly parents: Int32Array;
|
|
@@ -365,7 +381,6 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
365
381
|
const refusals = new Refusals(new Set(plant.admit ?? []));
|
|
366
382
|
const ignoreEvents = (options.ignore ?? []).includes('events');
|
|
367
383
|
|
|
368
|
-
if (doc.skins.some((k) => k.name !== CORE_DEFAULT_SKIN)) refusals.note('skin', `the document declares skins [${doc.skins.map((k) => k.name).join(', ')}]; stage ${RUNTIME_STAGE} poses the default skin alone`);
|
|
369
384
|
const absentWhy = constraintsAbsentWhy(doc);
|
|
370
385
|
if (absentWhy !== null) refusals.note('oracle-absent', `the oracle leaves the bones out: ${absentWhy}`);
|
|
371
386
|
|
|
@@ -440,6 +455,7 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
440
455
|
const attachments: RuntimeAttachment[] = [];
|
|
441
456
|
let vertexCount = 0;
|
|
442
457
|
const sourceOf = sourceOfDoc(doc);
|
|
458
|
+
const attachmentView = plant.attachmentSkin === undefined ? doc : underSkin(doc, plant.attachmentSkin);
|
|
443
459
|
/** Per slot, each placeholder's attachment index (−1 shows nothing) and what it misses, resolved once and its refusal noted for every animation that shows it. */
|
|
444
460
|
const resolvedBySlot = doc.slots.map(() => new Map<string | null, { index: number; missing: readonly [RuntimeFeature, string] | null }>());
|
|
445
461
|
const resolve = (si: number, placeholder: string | null, animation: string | null): number => {
|
|
@@ -454,7 +470,7 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
454
470
|
};
|
|
455
471
|
const resolveRecord = (si: number, placeholder: string | null): { index: number; missing: readonly [RuntimeFeature, string] | null } => {
|
|
456
472
|
const slot = doc.slots[si];
|
|
457
|
-
const shown = placeholder === null ? null : shownAttachment(
|
|
473
|
+
const shown = placeholder === null ? null : shownAttachment(attachmentView, slot, placeholder);
|
|
458
474
|
if (shown === null) return { index: -1, missing: null };
|
|
459
475
|
if ('conflict' in shown) return { index: -1, missing: ['skin-conflict', `slot "${slot.name}" placeholder "${placeholder}" is filled by skins that disagree`] };
|
|
460
476
|
const row = shownRow(shown);
|
|
@@ -538,6 +554,8 @@ export function compileRuntime(doc: CompiledDocument, options: RuntimeOptions =
|
|
|
538
554
|
if (refusals.model.length > 0) throw new RuntimeRefusal(refusals.model);
|
|
539
555
|
const names = doc.animations.map((a) => a.name);
|
|
540
556
|
return {
|
|
557
|
+
eventWrapOffByOne: plant.eventWrapOffByOne === true,
|
|
558
|
+
restoreEventsFromStart: plant.restoreEventsFromStart === true,
|
|
541
559
|
doc,
|
|
542
560
|
boneNames: doc.bones.map((b) => b.name),
|
|
543
561
|
parents,
|
|
@@ -667,7 +685,6 @@ function compileAnimation(
|
|
|
667
685
|
if (deform.length > 0) refusals.note('deform-timeline', `${deform.length} deform timeline(s): ${deform.slice(0, 3).map((a) => `${a.skin}/${a.slot}/${a.attachment}`).join(', ')}${deform.length > 3 ? ', …' : ''}`, name);
|
|
668
686
|
if (sequence.length > 0) refusals.note('sequence-timeline', `${sequence.length} sequence timeline(s): ${sequence.slice(0, 3).map((a) => `${a.skin}/${a.slot}/${a.attachment}`).join(', ')}${sequence.length > 3 ? ', …' : ''}`, name);
|
|
669
687
|
if (tl.drawOrder.length > 0) refusals.note('draw-order-timeline', `${tl.drawOrder.length} draw-order key(s)`, name);
|
|
670
|
-
if (tl.events.length > 0 && !ignoreEvents) refusals.note('events', `${tl.events.length} event key(s); a poser that fires none is handed out only on request (ignore: ['events'])`, name);
|
|
671
688
|
|
|
672
689
|
const lastBy = <T extends { name: string }>(targets: readonly T[]): Map<string, T> => new Map(targets.map((t) => [t.name, t]));
|
|
673
690
|
const bones: RuntimeBoneTimeline[] = [];
|
|
@@ -715,5 +732,5 @@ function compileAnimation(
|
|
|
715
732
|
constraintTimelines[c.index] = { constraint: c.index, curve: compileCurve(keyed.keys, TRANSFORM_PROPERTIES.length), cursor: cursors++, flags: null };
|
|
716
733
|
}
|
|
717
734
|
}
|
|
718
|
-
return { name, duration: tl.duration, bones, slots: slotTimelines, constraints: constraintTimelines, cursors, events: tl.events.length };
|
|
735
|
+
return { name, duration: tl.duration, bones, slots: slotTimelines, constraints: constraintTimelines, cursors, events: tl.events.length, eventKeys: Object.freeze(ignoreEvents ? [] : tl.events.map(key => Object.freeze({ name: key.name, time: key.time, int: key.int, float: key.float, string: key.string, volume: key.volume ?? 0, balance: key.balance ?? 0 }))) };
|
|
719
736
|
}
|
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 1): 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.
|
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
W_Y,
|
|
72
72
|
WORLDS,
|
|
73
73
|
type RuntimeAnimation,
|
|
74
|
+
type RuntimeEvent,
|
|
74
75
|
type RuntimeIk,
|
|
75
76
|
type RuntimeModel,
|
|
76
77
|
type RuntimeTransform,
|
|
@@ -114,6 +115,11 @@ export class RuntimePose {
|
|
|
114
115
|
trackTime = 0;
|
|
115
116
|
time = 0;
|
|
116
117
|
|
|
118
|
+
private readonly fired: RuntimeEvent[] = [];
|
|
119
|
+
/** Borrowed immutable event records, in delivery order, until the next apply/step. */
|
|
120
|
+
get events(): readonly RuntimeEvent[] { return this.fired; }
|
|
121
|
+
private eventTime = -1;
|
|
122
|
+
|
|
117
123
|
private current: RuntimeAnimation | null = null;
|
|
118
124
|
private readonly cursors: Int32Array;
|
|
119
125
|
// The constraints' values as posed: an ik's mix, softness and flags, a transform's six mixes.
|
|
@@ -160,12 +166,30 @@ export class RuntimePose {
|
|
|
160
166
|
this.cursors.fill(-1);
|
|
161
167
|
this.trackTime = 0;
|
|
162
168
|
this.time = 0;
|
|
169
|
+
this.eventTime = -1;
|
|
170
|
+
this.fired.length = 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Restore one animation's two clocks without delivering keys already passed.
|
|
175
|
+
* A skin/view replacement uses the old pose's trackTime and wrapped time.
|
|
176
|
+
* Unlike setAnimation + apply, restoration never replays the initial interval.
|
|
177
|
+
*/
|
|
178
|
+
restore(name: string, trackTime: number, animationTime: number): void {
|
|
179
|
+
if (!Number.isFinite(trackTime)) throw new RuntimeInputError(`restore: track time ${trackTime} is not finite`);
|
|
180
|
+
if (!Number.isFinite(animationTime)) throw new RuntimeInputError(`restore: animation time ${animationTime} is not finite`);
|
|
181
|
+
this.setAnimation(name);
|
|
182
|
+
this.trackTime = trackTime;
|
|
183
|
+
if (!this.model.restoreEventsFromStart) this.eventTime = animationTime;
|
|
184
|
+
this.apply(animationTime);
|
|
163
185
|
}
|
|
164
186
|
|
|
165
187
|
/** The setup pose: every constraint applied with its own values, every slot as it is set up. */
|
|
166
188
|
setupPose(): void {
|
|
167
189
|
this.current = null;
|
|
168
190
|
this.time = 0;
|
|
191
|
+
this.eventTime = -1;
|
|
192
|
+
this.fired.length = 0;
|
|
169
193
|
this.locals.set(this.model.setup);
|
|
170
194
|
this.hierarchy();
|
|
171
195
|
this.constraintValues(null, 0);
|
|
@@ -178,6 +202,7 @@ export class RuntimePose {
|
|
|
178
202
|
const anim = this.current;
|
|
179
203
|
if (anim === null) throw new RuntimeInputError('apply: no animation is set (setAnimation first)');
|
|
180
204
|
if (!Number.isFinite(t)) throw new RuntimeInputError(`apply: the time ${t} is not finite`);
|
|
205
|
+
this.collectEvents(anim, t);
|
|
181
206
|
this.time = t;
|
|
182
207
|
this.boneTimelines(anim, t);
|
|
183
208
|
this.hierarchy();
|
|
@@ -194,6 +219,21 @@ export class RuntimePose {
|
|
|
194
219
|
this.apply(anim.duration === 0 ? 0 : this.trackTime % anim.duration);
|
|
195
220
|
}
|
|
196
221
|
|
|
222
|
+
/** The oracle interval (last,t], tail then head on a wrap; never copies a payload. */
|
|
223
|
+
private collectEvents(anim: RuntimeAnimation, t: number): void {
|
|
224
|
+
const last = this.eventTime;
|
|
225
|
+
const wrapped = t < last;
|
|
226
|
+
const keys = anim.eventKeys;
|
|
227
|
+
let count = 0;
|
|
228
|
+
if (wrapped) for (let i = 0; i < keys.length; i++) if (keys[i].time > last) this.fired[count++] = keys[i];
|
|
229
|
+
for (let i = 0; i < keys.length; i++) {
|
|
230
|
+
const key = keys[i];
|
|
231
|
+
if (key.time <= t && (wrapped ? !this.model.eventWrapOffByOne || key.time > 0 : key.time > last)) this.fired[count++] = key;
|
|
232
|
+
}
|
|
233
|
+
this.fired.length = count;
|
|
234
|
+
this.eventTime = t;
|
|
235
|
+
}
|
|
236
|
+
|
|
197
237
|
// ---------------------------------------------------------------------------
|
|
198
238
|
// the bones
|
|
199
239
|
// ---------------------------------------------------------------------------
|
package/src/types.ts
CHANGED
|
@@ -1562,7 +1562,33 @@ export interface DroppedState {
|
|
|
1562
1562
|
why?: string;
|
|
1563
1563
|
}
|
|
1564
1564
|
|
|
1565
|
+
/** Alpha-derived clipping fit, on the source plate's pixel-centre grid. */
|
|
1566
|
+
export interface AlphaClipReport {
|
|
1567
|
+
target: string;
|
|
1568
|
+
source: string;
|
|
1569
|
+
alpha: number;
|
|
1570
|
+
tolerance: number;
|
|
1571
|
+
maxVertices: number;
|
|
1572
|
+
tracedVertices: number;
|
|
1573
|
+
simplifiedVertices: number;
|
|
1574
|
+
vertices: number;
|
|
1575
|
+
artPixels: number;
|
|
1576
|
+
coveredArt: number;
|
|
1577
|
+
coverage: number;
|
|
1578
|
+
falsePositive: number;
|
|
1579
|
+
falseNegative: number;
|
|
1580
|
+
/** Original trace-corner distance to simplified outline, in drawing pixels, before binding. */
|
|
1581
|
+
maxTraceDeviation: number;
|
|
1582
|
+
/** Maximum setup-world displacement caused by final float32 bind/world rounding, in world units. */
|
|
1583
|
+
maxBindingDeviationWorld: number;
|
|
1584
|
+
overshoot: number;
|
|
1585
|
+
/** source normalized UVs in the emitted clipping vertex order, including carrier crossings. */
|
|
1586
|
+
uvs: Array<[number, number]>;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1565
1589
|
export interface CompileResult {
|
|
1590
|
+
/** Present only for generated clipping; does not alter emitted model/skeleton geometry. */
|
|
1591
|
+
alphaClips?: AlphaClipReport[];
|
|
1566
1592
|
/**
|
|
1567
1593
|
* The compiled model (`src/model.ts`): what the skeleton below was emitted
|
|
1568
1594
|
* from. This cut fills its `bones` and `setupWorld`; every other field is
|