@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.71.0",
|
|
4
4
|
"description": "AI-authored 2D rigging and animation, verified before it is written. rigc compiles a rig spec into skeleton data in the Spine 4.3 format, gates it with named assertions held to a spine-core round trip's verdicts, and emits nothing that fails. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"./errors": "./src/errors.ts",
|
|
20
20
|
"./meshcompare": "./src/meshcompare.ts",
|
|
21
21
|
"./core": "./src/core/entry.ts",
|
|
22
|
+
"./runtime": "./src/runtime/index.ts",
|
|
22
23
|
"./cli": "./cli.ts",
|
|
23
24
|
"./package.json": "./package.json",
|
|
24
25
|
"./*.ts": "./*.ts",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"./*": "./*.ts"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
|
-
"bun": ">=1.
|
|
36
|
+
"bun": ">=1.4.0"
|
|
36
37
|
},
|
|
37
38
|
"license": "MIT",
|
|
38
39
|
"author": "firejune",
|
|
@@ -106,6 +107,8 @@
|
|
|
106
107
|
"@esotericsoftware/spine-core": "4.3.13",
|
|
107
108
|
"@types/bun": "^1.4.0",
|
|
108
109
|
"eslint": "^10.9.0",
|
|
110
|
+
"phaser": "3.90.0",
|
|
111
|
+
"pixi.js": "8.22.0",
|
|
109
112
|
"spine-html": "^0.4.1",
|
|
110
113
|
"typescript": "^5.9.3",
|
|
111
114
|
"typescript-eslint": "^8.67.0",
|
package/skills/rigc/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: rigc
|
|
3
3
|
description: Author, build and validate Spine 4.3 skeleton data (skeleton.json plus its .atlas) from loose part PNGs with rigc, the rig compiler that verifies its own output with named assertions before writing it. Use for any request to make a Spine rig or Spine animation from PNG parts, or where the source is a Live2D, Unity or video model whose pictures you can render, to run or read rigc build, validate, render, preview, check or vote, or to write or fix a *.rig.json or *.motion.json spec; it says which shipped guide to open for the need at hand. Not for Live2D conversion, cutting an illustration into parts, or real-time face tracking.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Requires Bun 1.
|
|
5
|
+
compatibility: Requires Bun 1.4 or later. The tool is the npm package rig-c (bunx rig-c, or bun add -d rig-c); the command it installs is rigc.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# rigc — a rig compiler for agents
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: rigc-face
|
|
3
3
|
description: Author a face on plain Spine data with rigc — a blink, a gaze shift, a breathing portrait and a head turn a few degrees off axis, built from deform timelines and per-part parallax. Use when the request is a talking or living portrait, a standing character, an expression or a head turn, such as "rig this face", "make the portrait blink and look around" or "turn the head". Not for Live2D file conversion, cutting a face illustration into parts, or VTuber-style real-time face tracking.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Requires Bun 1.
|
|
5
|
+
compatibility: Requires Bun 1.4 or later and the npm package rig-c.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Face — a turn, a gaze and a blink
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: rigc-ingest
|
|
3
3
|
description: Work with a Spine skeleton.json somebody else authored — exported from the Spine editor or another tool — using rigc. Read and validate it, understand a complaint rigc raised about it, decompile it into rigc specs with `rigc ingest`, normalise, re-pivot or rename it, and extend it with an animation it does not have. Use when the input is an existing skeleton.json with its .atlas and page images rather than loose part PNGs. Not for Live2D file conversion or runtime tracking.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Requires Bun 1.
|
|
5
|
+
compatibility: Requires Bun 1.4 or later and the npm package rig-c.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Ingest — a skeleton you did not author
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: rigc-motion
|
|
3
3
|
description: Author a Spine animation with rigc from key poses — an idle, a loop, a move from one picture to another — with timing and spacing, ease in and out, anticipation, arcs, overlap, follow-through, squash and stretch, and candidate variants a person can choose between. Use when the request is a movement on an existing or planned rig, in the animator's words too: "animate this rig", "make it breathe", "go from pose A to pose B", "make it feel heavier", "the cape should follow through". Not for Live2D, separating an image into parts, or real-time tracking.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Requires Bun 1.
|
|
5
|
+
compatibility: Requires Bun 1.4 or later and the npm package rig-c.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Motion — what goes between two poses
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: rigc-rigging
|
|
3
3
|
description: Decide a Spine rig's hierarchy with rigc — how many bones, where each pivot sits, what hangs off what, offsets, chains and what a chain can reach, siblings versus chains, constraints as structure — and which of those decisions the reference frames can check. Use when the request is a skeleton from loose part PNGs, such as "rig these parts", "make a Spine skeleton" or "where do the joints go", before any motion is authored. Not for Live2D, cutting an illustration into parts, or VTuber-style tracking.
|
|
4
4
|
license: MIT
|
|
5
|
-
compatibility: Requires Bun 1.
|
|
5
|
+
compatibility: Requires Bun 1.4 or later and the npm package rig-c.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Rigging — the hierarchy itself
|
package/src/assertions/report.ts
CHANGED
|
@@ -20,7 +20,7 @@ export const VALIDATE_PROFILES: readonly AssertionProfile[] = ['spine', 'rig-pla
|
|
|
20
20
|
* the target and never the alias.
|
|
21
21
|
*/
|
|
22
22
|
export function profileAliasLine(alias: string, target: AssertionProfile): string {
|
|
23
|
-
return `rigc: --profile ${alias} is deprecated and reads as --profile ${target}, the same rules; it is accepted until the next
|
|
23
|
+
return `rigc: --profile ${alias} is deprecated and reads as --profile ${target}, the same rules; it is accepted until the next major release — pass --profile ${target}`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
package/src/atlas.ts
CHANGED
|
@@ -775,6 +775,11 @@ export interface PackInput {
|
|
|
775
775
|
absPath: string;
|
|
776
776
|
width: number;
|
|
777
777
|
height: number;
|
|
778
|
+
/**
|
|
779
|
+
* Already-drawn pixels (`render --sheet`), used in place of `readPlate(absPath)`
|
|
780
|
+
* when given so an in-memory crop is not written out only to be read back in.
|
|
781
|
+
*/
|
|
782
|
+
plate?: Plate;
|
|
778
783
|
/**
|
|
779
784
|
* What the region's attachments draw (`packFootprints`), read only under
|
|
780
785
|
* `shape: 'polygon'`. Absent means the rectangle.
|
|
@@ -2535,7 +2540,7 @@ export function packAtlas(inputs: PackInput[], opts: PackOptions = {}): PackResu
|
|
|
2535
2540
|
const owners: Array<{ source: Plate; place: Placement; shape: CellShape }> = [];
|
|
2536
2541
|
for (const place of onPage) {
|
|
2537
2542
|
const input = byRegion.get(place.region)!;
|
|
2538
|
-
const source = readPlate(input.absPath);
|
|
2543
|
+
const source = input.plate ?? readPlate(input.absPath);
|
|
2539
2544
|
if (source.width !== input.width || source.height !== input.height) {
|
|
2540
2545
|
// The size in the atlas came from the PNG's IHDR (`readPngInfo`); this is
|
|
2541
2546
|
// the decoded image. They disagreeing means the file changed between the
|
package/src/cli/core_commands.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { outsideLine, outsideViewport, pinnedRecord, readViewportBox, viewportOf
|
|
|
40
40
|
import { PASS_THROUGH_FILE, passFrameLine, type PassPair, passSummaryLine, passThroughFileOf, passThroughOf, passThroughText, PassThroughError, readPassPairs } from '../passthrough.ts';
|
|
41
41
|
import type { Plate } from '../../tools/plate.ts';
|
|
42
42
|
import { OVERLAPS_FILE, OverlapError, overlapLine, overlapPlate, overlapsFileOf, overlapsOf, overlapsPlateFile, overlapsText, plateParticipants, staticLine } from '../overlaps.ts';
|
|
43
|
+
import { buildSheetSet, SHEET_MANIFEST_FILE, type SheetSetPack, SheetUsageError, sheetRigNameOf, sheetSidecarRecord, sheetSourceOf } from '../sheet.ts';
|
|
43
44
|
import { readSeamPairs, SEAM_FILE, seamFileOf, seamLine, SeamError, type SeamPair, seamsOf, seamText } from '../seam.ts';
|
|
44
45
|
import { environmentCollisions, environmentFrame, EnvironmentError, environmentResponse, readEnvironmentSchedule, type EnvironmentStep } from '../environment.ts';
|
|
45
46
|
import { estimatePose, poseLines, type PoseOptions } from '../pose.ts';
|
|
@@ -49,7 +50,7 @@ import { attachmentRegionJoins } from '../region_joins.ts';
|
|
|
49
50
|
import { cmdRepack } from './repack.ts';
|
|
50
51
|
import { writeWhole } from './write_whole.ts';
|
|
51
52
|
import { GUIDE_FILE, GUIDE_RULE_TABLES_ELSEWHERE, guideAnswer, refusalLine } from '../guide.ts';
|
|
52
|
-
import { ATLAS_ABSENT, COMMANDS, type CommandRun, DelegateError, modelTextBeside, resolveBuild, type BuildGate, runBuild, PACKAGE_ROOT, DEFAULT_CHAINFIT_OUT, DEFAULT_POSE_OUT, DEFAULT_SKILLS_DIR, ExplainError, GuideError, meshBudget, meshDepthNote, meshFit, meshInfluenceNote, PAGE_GRID_UNLOCATED, readAnimationFlag, readJsonFile, readPoserFlag, readSkeletonText, readVersion, resolveCut, resolveDrawable, runCheck, SkillsInstallError, STAGELESS_FRAMING, UsageError, writeJson, documentStageBeside } from './shared.ts';
|
|
53
|
+
import { ATLAS_ABSENT, COMMANDS, type CommandRun, DelegateError, modelTextBeside, resolveBuild, type BuildGate, runBuild, PACKAGE_ROOT, DEFAULT_CHAINFIT_OUT, DEFAULT_POSE_OUT, DEFAULT_SKILLS_DIR, ExplainError, GuideError, meshBudget, meshDepthNote, meshFit, meshInfluenceNote, PAGE_GRID_UNLOCATED, readAnimationFlag, readJsonFile, readPageEdges, readPoserFlag, readSheetFormat, readSkeletonText, readVersion, resolveCut, resolveDrawable, runCheck, SkillsInstallError, STAGELESS_FRAMING, UsageError, writeJson, documentStageBeside } from './shared.ts';
|
|
53
54
|
import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
54
55
|
import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
55
56
|
import { spawnSync } from 'node:child_process';
|
|
@@ -656,6 +657,18 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
656
657
|
const maxSide = readPositiveNumber(flags, 'max', 256, 16);
|
|
657
658
|
// A box given rather than fitted (issue #1414), read before a line is printed, so a malformed one is refused first.
|
|
658
659
|
const given = readGivenViewport(flags);
|
|
660
|
+
const wantsSheet = flags.sheet !== undefined;
|
|
661
|
+
if (flags['sheet-format'] !== undefined && !wantsSheet) {
|
|
662
|
+
throw new UsageError('--sheet-format takes --sheet: pass --sheet alongside --sheet-format <format>, or drop --sheet-format');
|
|
663
|
+
}
|
|
664
|
+
if (flags['page-edges'] !== undefined && !wantsSheet) {
|
|
665
|
+
throw new UsageError(
|
|
666
|
+
'--page-edges on render takes --sheet: without --sheet, render writes loose f####.png frames and contact.png, ' +
|
|
667
|
+
'which have no packed atlas pages. Pass --sheet or drop --page-edges',
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
const sheetFormat = wantsSheet ? readSheetFormat(flags) : undefined;
|
|
671
|
+
const sheetPageEdges = wantsSheet ? readPageEdges(flags) : undefined;
|
|
659
672
|
const outRoot = resolve(flags.out ?? 'render');
|
|
660
673
|
const geometry = flags.geometry !== undefined;
|
|
661
674
|
// The fold reading (issue #1383) is its own flag rather than a part of --geometry, so a caller that asked for
|
|
@@ -953,9 +966,45 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
953
966
|
}
|
|
954
967
|
}
|
|
955
968
|
}
|
|
956
|
-
|
|
969
|
+
const sheetPacks = new Map<string, SheetSetPack>();
|
|
970
|
+
if (wantsSheet) {
|
|
971
|
+
const modelDocText = modelTextBeside(skeletonPath, skeletonText) ?? null;
|
|
972
|
+
const rigName = sheetRigNameOf(skeletonPath, modelDocText, skeletonText);
|
|
973
|
+
const source = sheetSourceOf(skeletonPath, skeletonText, modelDocText);
|
|
974
|
+
const pinned = given === undefined ? undefined : pinnedRecord(given.by, outsideViewport(sampled, viewport));
|
|
975
|
+
for (const [name, frames] of sampled) {
|
|
976
|
+
const animation = name === SETUP_POSE_DIR && facts.animations.length === 0 ? null : name;
|
|
977
|
+
const setName = frameSetDir(name, fps);
|
|
978
|
+
try {
|
|
979
|
+
sheetPacks.set(
|
|
980
|
+
name,
|
|
981
|
+
buildSheetSet({
|
|
982
|
+
rigName,
|
|
983
|
+
setName,
|
|
984
|
+
animation,
|
|
985
|
+
fps,
|
|
986
|
+
maxSide,
|
|
987
|
+
viewport,
|
|
988
|
+
frames,
|
|
989
|
+
atlasPages: pages,
|
|
990
|
+
source,
|
|
991
|
+
format: sheetFormat,
|
|
992
|
+
pageEdges: sheetPageEdges,
|
|
993
|
+
skin,
|
|
994
|
+
subset,
|
|
995
|
+
environment: environment?.steps,
|
|
996
|
+
pinned,
|
|
997
|
+
}),
|
|
998
|
+
);
|
|
999
|
+
} catch (err) {
|
|
1000
|
+
if (err instanceof SheetUsageError) throw new UsageError(err.message);
|
|
1001
|
+
throw err;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return { viewport, fitted, sampled, geometryTexts, foldReadings, passReadings, seamReadings, angleReadings, overlapReadings, sheetPacks };
|
|
957
1006
|
});
|
|
958
|
-
const { viewport, fitted, sampled, geometryTexts, foldReadings, passReadings, seamReadings, angleReadings, overlapReadings } = posed.value;
|
|
1007
|
+
const { viewport, fitted, sampled, geometryTexts, foldReadings, passReadings, seamReadings, angleReadings, overlapReadings, sheetPacks } = posed.value;
|
|
959
1008
|
console.log(` .. poser ${posed.note}`);
|
|
960
1009
|
if (environment !== undefined) {
|
|
961
1010
|
const response = environmentResponse(skeletonText);
|
|
@@ -1014,6 +1063,14 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
1014
1063
|
writeFileSync(join(dir, OVERLAPS_FILE), overlapsText(overlapReading.file));
|
|
1015
1064
|
for (const [index, plate] of overlapReading.plates) plate.writePng(join(dir, overlapsPlateFile(index)));
|
|
1016
1065
|
}
|
|
1066
|
+
const sheetPack = sheetPacks.get(name);
|
|
1067
|
+
if (sheetPack !== undefined) {
|
|
1068
|
+
for (const page of sheetPack.pages) {
|
|
1069
|
+
page.plate.writePng(join(dir, page.image));
|
|
1070
|
+
writeFileSync(join(dir, page.table), page.jsonText);
|
|
1071
|
+
}
|
|
1072
|
+
writeFileSync(join(dir, SHEET_MANIFEST_FILE), sheetPack.manifestText);
|
|
1073
|
+
}
|
|
1017
1074
|
const duration = frames[frames.length - 1].time;
|
|
1018
1075
|
const physics = physicsDeclared.length === 0 ? undefined : physicsSteppedOf(frames, fps);
|
|
1019
1076
|
sets.push({
|
|
@@ -1026,6 +1083,7 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
1026
1083
|
duration,
|
|
1027
1084
|
...(physics === undefined ? {} : { physics: { stepped: physics.stepped.length, fps } }),
|
|
1028
1085
|
...(overlapReading === undefined ? {} : { overlaps: overlapsRecord(overlapReading.file, overlapReading.plates.size) }),
|
|
1086
|
+
...(sheetPack === undefined ? {} : { sheet: sheetSidecarRecord(sheetPack) }),
|
|
1029
1087
|
});
|
|
1030
1088
|
const how = frames.length === 1 ? 'a single pose' : `${duration.toFixed(3)}s`;
|
|
1031
1089
|
const folding = foldReading?.meshes.filter((m) => m.worst !== null).length ?? 0;
|
|
@@ -1038,7 +1096,11 @@ export function cmdRender(flags: Record<string, string>): void {
|
|
|
1038
1096
|
overlapReading === undefined
|
|
1039
1097
|
? ''
|
|
1040
1098
|
: ` + ${OVERLAPS_FILE} (${overlapReading.file.pairs.filter((p) => p.varies).length} moving pair(s), ${overlapReading.plates.size} outline plate(s))`;
|
|
1041
|
-
const
|
|
1099
|
+
const sheetNote =
|
|
1100
|
+
sheetPack === undefined
|
|
1101
|
+
? ''
|
|
1102
|
+
: ` + ${sheetPack.pages.map((p) => `${p.image} + ${p.table}`).join(' + ')} + ${SHEET_MANIFEST_FILE} (${sheetPack.pages.length} page(s), ${sheetPack.pageEdges})`;
|
|
1103
|
+
const extras = `${sheet ? ` + ${SHEET_FILE}` : ''}${sheetNote}${geometryOut === undefined ? '' : ` + ${GEOMETRY_FILE}`}${foldNote}${passNote}${seamNote}${overlapNote}`;
|
|
1042
1104
|
console.log(` .. ${name.padEnd(16)} ${frames.length} frame(s), ${how}${extras} -> ${dir}`);
|
|
1043
1105
|
if (physics !== undefined) console.log(` .. ${physicsLine(physics.stepped, physicsDeclared, fps, frames.length)}`);
|
|
1044
1106
|
for (const mesh of foldReading?.meshes ?? []) {
|
|
@@ -2313,7 +2375,7 @@ function resolveDelegate(name: string): string | null {
|
|
|
2313
2375
|
|
|
2314
2376
|
/**
|
|
2315
2377
|
* Whether the Bun running this hands an empty argument to a process it starts
|
|
2316
|
-
* (issue #1465). Bun 1.2.0
|
|
2378
|
+
* (issue #1465). Bun 1.2.0 (then the declared minimum) does not: `["a", "", "b"]`
|
|
2317
2379
|
* reaches the child as `["a", "b"]`, through `spawnSync` and `Bun.spawnSync`
|
|
2318
2380
|
* alike and whatever the child is; 1.4.2 hands it over. That is measured here
|
|
2319
2381
|
* rather than read off a version number, by starting this Bun twice with
|
|
@@ -2466,7 +2528,7 @@ function cmdDelegate(command: string, raw: readonly string[]): void {
|
|
|
2466
2528
|
// `rigc review -- <dir>` reached the bin as `<dir>` (measured, issue #1486). One given here is the one dropped, and
|
|
2467
2529
|
// the arguments after it arrive as they were.
|
|
2468
2530
|
const ran = spawnSync(process.execPath, [bin, '--', ...argv], { stdio: 'inherit' });
|
|
2469
|
-
// ⚠️ The exit and the signal are read before `error`: Bun 1.2.0
|
|
2531
|
+
// ⚠️ The exit and the signal are read before `error`: Bun 1.2.0 (then the declared minimum) sets `error` on every
|
|
2470
2532
|
// child that exits non-zero, so reading it first refused review's own exit 7 as "could not run". `error` is a
|
|
2471
2533
|
// launch that failed only where the child left neither a status nor a signal.
|
|
2472
2534
|
if (typeof ran.status === 'number') process.exit(ran.status);
|
package/src/cli/shared.ts
CHANGED
|
@@ -41,6 +41,7 @@ import { FOLDS_FILE } from '../folds.ts';
|
|
|
41
41
|
import { DRAWN_ALPHA, PASS_THROUGH_FILE, PASS_THROUGH_RUN } from '../passthrough.ts';
|
|
42
42
|
import { SEAM_FILE } from '../seam.ts';
|
|
43
43
|
import { OVERLAPS_FILE } from '../overlaps.ts';
|
|
44
|
+
import { DEFAULT_SHEET_FORMAT, SHEET_FORMATS, SHEET_MANIFEST_FILE, SHEET_MANIFEST_SPEC, SHEET_PADDING, SHEET_PAGE_SIZE, type SheetFormat } from '../sheet.ts';
|
|
44
45
|
import { readViewportBox, ViewportError } from '../viewportpin.ts';
|
|
45
46
|
import { parseJsonWithPosition } from '../json-position.ts';
|
|
46
47
|
import { RUNG_IDS } from '../ladder.ts';
|
|
@@ -151,7 +152,7 @@ export { PACKAGE_ROOT, readPackageMeta, readVersion };
|
|
|
151
152
|
* needs a value` (issue #328). `CLI10`/`CLI11` in `selftest.ts` now hold the two
|
|
152
153
|
* halves together by reading `--help` rather than by naming a flag.
|
|
153
154
|
*/
|
|
154
|
-
const BOOLEAN_FLAGS = new Set(['all', 'all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack', 'copy', 'geometry', 'folds', 'overlaps', 'accept-skeleton-differences', 'keep-pages', 'dim']);
|
|
155
|
+
const BOOLEAN_FLAGS = new Set(['all', 'all-frames', 'all-bones', 'help', 'copy-images', 'again', 'pack', 'copy', 'geometry', 'folds', 'overlaps', 'sheet', 'accept-skeleton-differences', 'keep-pages', 'dim']);
|
|
155
156
|
|
|
156
157
|
/**
|
|
157
158
|
* The flags a command is allowed to spell more than once.
|
|
@@ -1078,6 +1079,15 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
1078
1079
|
'pair per set: the lowest and the highest angle in degrees, the span, and the frame each was first reached on. Read on the frames\' own ' +
|
|
1079
1080
|
'grid, so a peak between two frames is not read. A reading, not a gate: nothing is written. An unknown bone, a bone against itself, ' +
|
|
1080
1081
|
'a malformed list and a pair named twice are refused by name, and so is a bone with no direction on a frame (a world matrix of zero)',
|
|
1082
|
+
sheet:
|
|
1083
|
+
`also pack each rendered set's frames over a transparent [0, 0, 0, 0] canvas into deterministic RGBA8 sprite sheet page(s) ` +
|
|
1084
|
+
`(\`sheet.png\`, \`sheet2.png\`, …; max edge ${SHEET_PAGE_SIZE}, padding ${SHEET_PADDING} with clamped edge extrusion, trimmed to alpha > 0 ` +
|
|
1085
|
+
'with a 1x1 transparent sentinel for empty frames), a shared TexturePacker-style JSON-Hash table per page (`sheet.json`, `sheet2.json`, …; ' +
|
|
1086
|
+
`\`anchor\` at world (0, 0)), and \`${SHEET_MANIFEST_FILE}\` (\`${SHEET_MANIFEST_SPEC}\`) ` +
|
|
1087
|
+
'in each set directory; diagnostic `f####.png` and `contact.png` stay over opaque `BACKGROUND` unchanged',
|
|
1088
|
+
'sheet-format':
|
|
1089
|
+
`the table format \`render --sheet\` writes beside each sheet page (default \`${DEFAULT_SHEET_FORMAT}\`, verified against pixi.js@8.22.0 and phaser@3.90.0 by tools/sheet_readers.ts; known formats: ${SHEET_FORMATS.join(', ')}). ` +
|
|
1090
|
+
'Takes --sheet',
|
|
1081
1091
|
'viewport-from':
|
|
1082
1092
|
"draw on an earlier render's grid exactly — the box, the scale and the pixel size its frames.json records (the file, or the " +
|
|
1083
1093
|
'directory `render --out` wrote it into) — so a world point lands on the same pixel in both renders. A slot that reaches past ' +
|
|
@@ -1285,6 +1295,7 @@ const FLAG_VALUES: Record<string, string> = {
|
|
|
1285
1295
|
'pass-through': '<cloth>:<leg>[,<cloth>:<leg>…]',
|
|
1286
1296
|
seam: '<slot>:<slot>[,<slot>:<slot>…]',
|
|
1287
1297
|
'angle-range': '<bone>:<relative-to>[,…]',
|
|
1298
|
+
'sheet-format': 'json-hash',
|
|
1288
1299
|
'viewport-from': '<frames.json|dir>',
|
|
1289
1300
|
reference: '<dir|skeleton.json>',
|
|
1290
1301
|
'reference-atlas': '<path>',
|
|
@@ -1753,9 +1764,15 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1753
1764
|
'rigc render … --overlaps (which slot each frame drew over which, by name, with an outline plate)',
|
|
1754
1765
|
'rigc render … --angle-range <bone>:<relative-to>[,…] (how far a bone turns against another, per animation)',
|
|
1755
1766
|
'rigc render … --viewport <x,y,w,h> | --viewport-from <frames.json> (a box given, not fitted: two renders on one pixel grid)',
|
|
1767
|
+
'rigc render … --sheet [--sheet-format json-hash] [--page-edges pot|free] (deterministic sprite sheets + JSON hash table + sheet-manifest.json per set)',
|
|
1756
1768
|
],
|
|
1757
|
-
flags: ['candidate', 'atlas', 'animation', 'skin', 'slot', 'hide', 'fps', 'max', 'viewport', 'viewport-from', 'geometry', 'folds', 'pass-through', 'seam', 'overlaps', 'angle-range', 'environment', 'poser', 'out'],
|
|
1769
|
+
flags: ['candidate', 'atlas', 'animation', 'skin', 'slot', 'hide', 'fps', 'max', 'viewport', 'viewport-from', 'geometry', 'folds', 'pass-through', 'seam', 'overlaps', 'angle-range', 'sheet', 'sheet-format', 'page-edges', 'environment', 'poser', 'out'],
|
|
1758
1770
|
overrides: {
|
|
1771
|
+
'page-edges': {
|
|
1772
|
+
meaning:
|
|
1773
|
+
`what each sprite sheet page's edges may be, --sheet only (default ${DEFAULT_PAGE_EDGES}, max edge ${SHEET_PAGE_SIZE}, padding ${SHEET_PADDING}): ` +
|
|
1774
|
+
'pot is a power of two on both; free chooses the smallest-area rectangle up to the max edge that holds the trimmed frames',
|
|
1775
|
+
},
|
|
1759
1776
|
out: {
|
|
1760
1777
|
value: '<dir>',
|
|
1761
1778
|
meaning:
|
|
@@ -2252,6 +2269,17 @@ export function readPageEdges(flags: Record<string, string>): PageEdges {
|
|
|
2252
2269
|
return found;
|
|
2253
2270
|
}
|
|
2254
2271
|
|
|
2272
|
+
/**
|
|
2273
|
+
* Read `--sheet-format`, or its default — `json-hash` (issue #1560).
|
|
2274
|
+
*/
|
|
2275
|
+
export function readSheetFormat(flags: Record<string, string>): SheetFormat {
|
|
2276
|
+
const raw = flags['sheet-format'];
|
|
2277
|
+
if (raw === undefined) return DEFAULT_SHEET_FORMAT;
|
|
2278
|
+
const found = SHEET_FORMATS.find((f) => f === raw);
|
|
2279
|
+
if (!found) throw new UsageError(`--sheet-format ${JSON.stringify(raw)}; known formats: ${SHEET_FORMATS.join(', ')}`);
|
|
2280
|
+
return found;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2255
2283
|
/**
|
|
2256
2284
|
* Read `--pack-shape`, or its default — `rect` (issue #1099).
|
|
2257
2285
|
*
|
package/src/cli/write_whole.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* refusal — the writer a command uses for a document too large to trust to
|
|
4
4
|
* `console.log` (issue #1487).
|
|
5
5
|
*
|
|
6
|
-
* ⚖️ Why not `console.log`: on Bun 1.2.0
|
|
6
|
+
* ⚖️ Why not `console.log`: on Bun 1.2.0 (then the declared minimum), one ~322 KB
|
|
7
7
|
* line with non-ASCII in it came out 330,634 bytes for 330,632, a stray
|
|
8
8
|
* `\xe2\x00` at a 64 KiB chunk boundary — to a file, a pipe and a slow pipe
|
|
9
9
|
* alike. And on a stdout whose pipe is non-blocking (a parent set `O_NONBLOCK`
|
package/src/compile.ts
CHANGED
|
@@ -3015,6 +3015,46 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3015
3015
|
// The same for a `soft` mask's per-vertex level, which a deform envelope's
|
|
3016
3016
|
// `mask` reads (issue #1413). Never emitted either.
|
|
3017
3017
|
const attachmentSoftMasks = new Map<string, DeformSoftMask>();
|
|
3018
|
+
// `skin/slot/attachment` -> per-axis narrowing mode when a deform turn key on
|
|
3019
|
+
// that attachment declares `narrowing: false` (issue #1584), so `sampleMeshDepth`
|
|
3020
|
+
// evaluates `turnCeiling` with the `asin(|A₀ / A_axis|)` closed form that
|
|
3021
|
+
// matches `A39` on the keyed turn.
|
|
3022
|
+
const attachmentTurnNarrowing = new Map<string, { yaw?: boolean; pitch?: boolean }>();
|
|
3023
|
+
const attachmentTurnNarrowingBy = new Map<string, { yaw?: { mode: boolean; anim: string }; pitch?: { mode: boolean; anim: string } }>();
|
|
3024
|
+
withMotionSource(() => {
|
|
3025
|
+
for (const [turnAnimName, turnAnim] of Object.entries(motion.animations ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
|
|
3026
|
+
for (const turnTrack of turnAnim.deform ?? []) {
|
|
3027
|
+
const turnDeformSkin = turnTrack.skin ?? DEFAULT_SKIN;
|
|
3028
|
+
const turnTriple = `${turnDeformSkin}/${turnTrack.slot}/${turnTrack.attachment}`;
|
|
3029
|
+
for (const turnKey of turnTrack.keys ?? []) {
|
|
3030
|
+
const turnSpec = turnKey.transform as { kind?: unknown; narrowing?: unknown } | undefined;
|
|
3031
|
+
if (turnSpec && (turnSpec.kind === 'yaw' || turnSpec.kind === 'pitch')) {
|
|
3032
|
+
const turnAxis = turnSpec.kind;
|
|
3033
|
+
const turnNarrowMode = turnSpec.narrowing !== false;
|
|
3034
|
+
const turnPrev = attachmentTurnNarrowingBy.get(turnTriple)?.[turnAxis];
|
|
3035
|
+
if (turnPrev !== undefined && turnPrev.mode !== turnNarrowMode) {
|
|
3036
|
+
const turnFalseAnim = turnNarrowMode ? turnPrev.anim : turnAnimName;
|
|
3037
|
+
const turnTrueAnim = turnNarrowMode ? turnAnimName : turnPrev.anim;
|
|
3038
|
+
throw new CompileError(
|
|
3039
|
+
`deform ${turnTriple} keys "${turnAxis}" with narrowing=false in animation "${turnFalseAnim}" and with narrowing=true in animation "${turnTrueAnim}". ` +
|
|
3040
|
+
'An attachment\'s turn ceiling on one axis is either asin(|A₀ / A_axis|) (without narrowing) or atan(|A₀ / A_axis|) (with narrowing) — ' +
|
|
3041
|
+
`mixing both on one attachment and axis would report one ceiling where two apply. Use one "narrowing" setting across every "${turnAxis}" key on ${turnTriple}.`,
|
|
3042
|
+
'CE_COMPILE_DEFORM_TURN_NARROWING_MIXED',
|
|
3043
|
+
);
|
|
3044
|
+
}
|
|
3045
|
+
const curBy = attachmentTurnNarrowingBy.get(turnTriple) ?? {};
|
|
3046
|
+
if (curBy[turnAxis] === undefined) curBy[turnAxis] = { mode: turnNarrowMode, anim: turnAnimName };
|
|
3047
|
+
attachmentTurnNarrowingBy.set(turnTriple, curBy);
|
|
3048
|
+
if (!turnNarrowMode) {
|
|
3049
|
+
const cur = attachmentTurnNarrowing.get(turnTriple) ?? {};
|
|
3050
|
+
cur[turnAxis] = false;
|
|
3051
|
+
attachmentTurnNarrowing.set(turnTriple, cur);
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
});
|
|
3018
3058
|
|
|
3019
3059
|
for (const rigSlot of rig.slots) {
|
|
3020
3060
|
const part = partBySlot.get(rigSlot.name);
|
|
@@ -3183,6 +3223,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3183
3223
|
imagesDir,
|
|
3184
3224
|
depths: attachmentDepths,
|
|
3185
3225
|
softMasks: attachmentSoftMasks,
|
|
3226
|
+
turnNarrowing: attachmentTurnNarrowing.get(`${skinName}/${rigSlot.name}/${placeholder}`),
|
|
3186
3227
|
slotNames: new Set(rig.slots.map((s) => s.name)),
|
|
3187
3228
|
atlasRegions: atlasIn === null ? null : atlasIn.byName,
|
|
3188
3229
|
links: pendingLinks,
|
|
@@ -4333,6 +4374,12 @@ interface AttachmentContext {
|
|
|
4333
4374
|
* emitted, for `depths`' reason.
|
|
4334
4375
|
*/
|
|
4335
4376
|
softMasks: Map<string, DeformSoftMask>;
|
|
4377
|
+
/**
|
|
4378
|
+
* Per-axis `narrowing` mode when a deform turn key on this attachment states
|
|
4379
|
+
* `"narrowing": false` (issue #1584), so `sampleMeshDepth` computes the turn
|
|
4380
|
+
* ceiling with `asin(|A₀ / A_axis|)` on that axis.
|
|
4381
|
+
*/
|
|
4382
|
+
turnNarrowing?: { yaw?: boolean; pitch?: boolean };
|
|
4336
4383
|
/** Every slot the rig declares — a clipping attachment's `end` resolves here. */
|
|
4337
4384
|
slotNames: Set<string>;
|
|
4338
4385
|
/**
|
|
@@ -6324,6 +6371,7 @@ function sampleMeshDepth(
|
|
|
6324
6371
|
points.map(([px, py]) => toBind(px, py)),
|
|
6325
6372
|
z,
|
|
6326
6373
|
triangles,
|
|
6374
|
+
ctx.turnNarrowing === undefined ? undefined : { narrowing: ctx.turnNarrowing },
|
|
6327
6375
|
),
|
|
6328
6376
|
},
|
|
6329
6377
|
};
|
package/src/core/animation.ts
CHANGED
|
@@ -189,11 +189,12 @@ import {
|
|
|
189
189
|
foldInheritMode,
|
|
190
190
|
gridRound,
|
|
191
191
|
readBlend,
|
|
192
|
-
readColour,
|
|
193
192
|
shownAttachment,
|
|
194
193
|
shownRow,
|
|
195
194
|
slidersKeyingWhy,
|
|
196
195
|
sourceOfDoc,
|
|
196
|
+
viewColours,
|
|
197
|
+
WHITE,
|
|
197
198
|
type CompiledDocument,
|
|
198
199
|
type CoreAnimation,
|
|
199
200
|
type CoreBoneRow,
|
|
@@ -834,12 +835,15 @@ export function posedSlots(
|
|
|
834
835
|
/** Filled with each slot's placeholder once the sample's own timelines have moved it, before the sliders — what the attachments are posed from (`attachmentStates`). */
|
|
835
836
|
placeholders?: Map<string, string | null>,
|
|
836
837
|
): { rows: CoreSlotRow[]; conflicts: string[] } {
|
|
837
|
-
|
|
838
|
+
// The view's stated colours, read once (`viewColours`); a planted reader of the light colour is called as it always was.
|
|
839
|
+
const stated = viewColours(doc);
|
|
840
|
+
const colour = plant.colour ?? stated;
|
|
838
841
|
const byName = new Map(timelines.slots.map((s) => [s.name, s]));
|
|
839
842
|
return slotRowsFrom(doc, plant, sliders, placeholders, (slot, live) => {
|
|
840
843
|
let placeholder = slot.setup;
|
|
841
|
-
const
|
|
842
|
-
const
|
|
844
|
+
const setupLight = slot.color === undefined ? WHITE : colour(slot.color);
|
|
845
|
+
const light = [setupLight[0], setupLight[1], setupLight[2], setupLight[3]];
|
|
846
|
+
const setupDark = slot.dark === undefined ? null : stated(slot.dark);
|
|
843
847
|
const dark = setupDark === null ? null : [setupDark[0], setupDark[1], setupDark[2]];
|
|
844
848
|
for (const tl of live ? (byName.get(slot.name)?.timelines ?? []) : []) {
|
|
845
849
|
if (tl.kind === 'attachment') {
|
|
@@ -848,7 +852,6 @@ export function posedSlots(
|
|
|
848
852
|
continue;
|
|
849
853
|
}
|
|
850
854
|
const v = valuesAt(tl.keys, t, plant);
|
|
851
|
-
const setupLight = slot.color === undefined ? [1, 1, 1, 1] : colour(slot.color);
|
|
852
855
|
const channels = SLOT_COLOUR_TIMELINES[tl.kind].channels;
|
|
853
856
|
channels.forEach((name, i) => {
|
|
854
857
|
const at = { r: 0, g: 1, b: 2, a: 3, r2: 4, g2: 5, b2: 6 }[name];
|
|
@@ -955,15 +958,22 @@ export function posedBoneRows(doc: CompiledDocument, timelines: CoreAnimationTim
|
|
|
955
958
|
/** `posedBoneRows`, with the world transforms the rows were read off — what the sample's attachments are posed through. */
|
|
956
959
|
export function posedBoneWorld(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant = {}, constraints?: CoreConstraintTimelines, sliders?: SliderApplication[], step?: PoseStep): { rows: CoreBoneRow[]; world: Map<string, CoreWorld> } {
|
|
957
960
|
const posed = posedBoneStep(doc, timelines, t, plant, constraints, sliders, step);
|
|
958
|
-
|
|
961
|
+
const stage = plant.stage;
|
|
962
|
+
if (stage !== undefined) stage.begin('readout');
|
|
963
|
+
const rows = stepRows(posed);
|
|
964
|
+
if (stage !== undefined) stage.end('readout');
|
|
965
|
+
return { rows, world: posed.world };
|
|
959
966
|
}
|
|
960
967
|
|
|
961
968
|
/**
|
|
962
969
|
* `posedBoneWorld`'s world transforms without its rows (issue #1179, the
|
|
963
970
|
* second part): the same pose by the same operations in the same order, since
|
|
964
971
|
* `stepRows` only reads the world once it is posed. A walk reads `world` and
|
|
965
|
-
* no row, so the scan's
|
|
966
|
-
*
|
|
972
|
+
* no row, so every walk asks for this — the scan's (`scanWalkIn` in
|
|
973
|
+
* `./raw.ts`, A10's), the raw entry's batch walks and the live track's step.
|
|
974
|
+
* The rows were the only thing the other variant adds, and their rounding
|
|
975
|
+
* (`rawNumber`, `keptNumber`) refuses nothing, so leaving them out moves no
|
|
976
|
+
* number and no refusal of a walk.
|
|
967
977
|
*/
|
|
968
978
|
export function posedBoneWorldAlone(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: PoseStep | undefined): Map<string, CoreWorld> {
|
|
969
979
|
return posedBoneStep(doc, timelines, t, plant, constraints, sliders, step).world;
|
|
@@ -997,20 +1007,34 @@ function stepRows(posed: PosedStep): CoreBoneRow[] {
|
|
|
997
1007
|
|
|
998
1008
|
/** `posedBoneWorld` up to its rows, refusing a bone the evaluator gave no transform (`PosedStep`). */
|
|
999
1009
|
function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines, t: number, plant: TimelinePlant, constraints: CoreConstraintTimelines | undefined, sliders: SliderApplication[] | undefined, step: PoseStep | undefined): PosedStep {
|
|
1010
|
+
const stage = plant.stage;
|
|
1011
|
+
if (stage !== undefined) stage.begin('timeline');
|
|
1000
1012
|
const bones = posedBones(doc, timelines, t, plant);
|
|
1013
|
+
if (stage !== undefined) stage.end('timeline');
|
|
1001
1014
|
// One step of the stepped phase (issue #956, `./constraints_physics.ts`): the physics records posed by their timelines and stepped under the walk's context; the deformed curve a path walks as above.
|
|
1002
|
-
if (constraints !== undefined && step !== undefined)
|
|
1015
|
+
if (constraints !== undefined && step !== undefined) {
|
|
1016
|
+
return steppedLocals(doc, bones, plant, () => pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, sliders, step);
|
|
1017
|
+
}
|
|
1003
1018
|
const active = activeBones(doc);
|
|
1004
1019
|
// The caller's adjustment, after the timelines and before anything reads the locals (`LocalAdjust`).
|
|
1005
|
-
if (step?.adjustment !== undefined)
|
|
1006
|
-
|
|
1020
|
+
if (step?.adjustment !== undefined) {
|
|
1021
|
+
if (stage !== undefined) stage.begin('timeline');
|
|
1022
|
+
adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
|
|
1023
|
+
if (stage !== undefined) stage.end('timeline');
|
|
1024
|
+
}
|
|
1025
|
+
if (stage !== undefined) stage.begin('world');
|
|
1026
|
+
const initialWorld = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
1027
|
+
if (stage !== undefined) stage.end('world');
|
|
1028
|
+
let world = initialWorld;
|
|
1007
1029
|
if (constraints !== undefined) {
|
|
1030
|
+
if (stage !== undefined) stage.begin('constraints');
|
|
1008
1031
|
const setupRecords = constraintRecords(doc);
|
|
1009
1032
|
// A path walks the curve the sample's deform timelines left on its slot (`./deform.ts`); a slider's deform of a walked path leaves the bones out (`pathAnimationsWhy`).
|
|
1010
1033
|
const records = pathDeformed(doc, posedRecords(setupRecords, constraints, t), timelines, t, plant);
|
|
1011
1034
|
// A path constraint may read a slot bone the runtime has not yet brought up to date in this pass, as the previous pass left it (`./constraints_path.ts`, *Which slot bone*); `poseAnimations` holds that pass to the setup pose's reading.
|
|
1012
1035
|
const previous = setupRecords.some((r) => r.kind === 'path') ? applyConstraints(doc.bones, (plant.evaluate ?? worldTransforms)(doc.bones, active), active, plant.constraints ? plant.constraints(setupRecords) : setupRecords, null, undefined, undefined, undefined, solverRules(plant.solver)) : null;
|
|
1013
|
-
world = applyConstraints(bones,
|
|
1036
|
+
world = applyConstraints(bones, initialWorld, active, plant.constraints ? plant.constraints(records) : records, previous, sliders, undefined, undefined, solverRules(plant.solver));
|
|
1037
|
+
if (stage !== undefined) stage.end('constraints');
|
|
1014
1038
|
}
|
|
1015
1039
|
for (const b of bones) if (world.get(b.name) === undefined) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
|
|
1016
1040
|
return { bones, world, active, round: plant.round ?? gridRound };
|
|
@@ -1027,27 +1051,35 @@ function posedBoneStep(doc: CompiledDocument, timelines: CoreAnimationTimelines,
|
|
|
1027
1051
|
* past the timelines for both.
|
|
1028
1052
|
*/
|
|
1029
1053
|
function steppedLocals(doc: CompiledDocument, bones: ModelBone[], plant: TimelinePlant, records: () => CoreConstraintRecord[], physicsKeyed: NonNullable<CoreConstraintTimelines['physicsKeyed']>, t: number, sliders: SliderApplication[] | undefined, step: PoseStep): PosedStep {
|
|
1054
|
+
const stage = plant.stage;
|
|
1030
1055
|
const active = activeBones(doc);
|
|
1031
1056
|
// The caller's adjustment, after the timelines and before anything reads the locals (`LocalAdjust`).
|
|
1032
|
-
if (step.adjustment !== undefined)
|
|
1033
|
-
|
|
1057
|
+
if (step.adjustment !== undefined) {
|
|
1058
|
+
if (stage !== undefined) stage.begin('timeline');
|
|
1059
|
+
adjustLocals(doc, bones, step.adjustment.callback, step.adjustment.context);
|
|
1060
|
+
if (stage !== undefined) stage.end('timeline');
|
|
1061
|
+
}
|
|
1062
|
+
if (stage !== undefined) stage.begin('world');
|
|
1063
|
+
const initialWorld = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
1064
|
+
if (stage !== undefined) {
|
|
1065
|
+
stage.end('world');
|
|
1066
|
+
stage.begin('constraints');
|
|
1067
|
+
}
|
|
1034
1068
|
const stepped = stepPhysicsRecords(records(), physicsKeyed, t, active, step.ctx, step.before);
|
|
1035
|
-
world = applyConstraints(bones,
|
|
1069
|
+
const world = applyConstraints(bones, initialWorld, active, plant.constraints ? plant.constraints(stepped) : stepped, null, sliders, step.ctx, undefined, solverRules(plant.solver));
|
|
1070
|
+
if (stage !== undefined) stage.end('constraints');
|
|
1036
1071
|
for (const b of bones) if (world.get(b.name) === undefined) throw new CoreInputError(`the evaluator returned no transform for bone "${b.name}"`);
|
|
1037
1072
|
return { bones, world, active, round: plant.round ?? gridRound };
|
|
1038
1073
|
}
|
|
1039
1074
|
|
|
1040
1075
|
/**
|
|
1041
|
-
* `steppedLocals`' world transforms, the oracle's rows
|
|
1042
|
-
* `
|
|
1043
|
-
*
|
|
1044
|
-
*
|
|
1045
|
-
* constraint timeline — and no physics timeline keyed.
|
|
1076
|
+
* `steppedLocals`' world transforms, without the oracle's rows (as
|
|
1077
|
+
* `posedBoneWorldAlone` poses them): the live tracks' entry to the stepped
|
|
1078
|
+
* phase (`./tracks.ts`). The document's constraint records as they stand —
|
|
1079
|
+
* the live tracks mix no constraint timeline — and no physics timeline keyed.
|
|
1046
1080
|
*/
|
|
1047
|
-
export function steppedLocalsWorld(doc: CompiledDocument, bones: ModelBone[], plant: TimelinePlant, sliders: SliderApplication[], step: PoseStep
|
|
1048
|
-
|
|
1049
|
-
if (rows) stepRows(posed);
|
|
1050
|
-
return posed.world;
|
|
1081
|
+
export function steppedLocalsWorld(doc: CompiledDocument, bones: ModelBone[], plant: TimelinePlant, sliders: SliderApplication[], step: PoseStep): Map<string, CoreWorld> {
|
|
1082
|
+
return steppedLocals(doc, bones, plant, () => constraintRecords(doc), [], 0, sliders, step).world;
|
|
1051
1083
|
}
|
|
1052
1084
|
|
|
1053
1085
|
/** Each path record with the curve its slot shows deformed by the sample's own deform timelines at `t` (`./deform.ts`), or as it was. */
|