spine-rigc 2.57.0 → 2.58.1
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 +1 -1
- package/docs/AUTHORING.md +621 -270
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +8 -3
- package/skills/rigc-face/SKILL.md +8 -3
- package/skills/rigc-ingest/SKILL.md +8 -3
- package/skills/rigc-motion/SKILL.md +8 -3
- package/skills/rigc-rigging/SKILL.md +8 -3
- package/src/atlas.ts +10 -10
- package/src/cli/core_commands.ts +6 -5
- package/src/cli/shared.ts +11 -6
- package/src/compile.ts +333 -333
- package/src/deformgen.ts +28 -28
- package/src/emit_spine.ts +5 -5
- package/src/errors.ts +8 -0
- package/src/guide.ts +10 -12
- package/src/keys.ts +4 -4
- package/src/model.ts +10 -10
- package/src/motion.ts +6 -6
- package/src/rig.ts +76 -76
- package/src/stop.ts +30 -30
- package/src/trackgen.ts +11 -11
package/src/compile.ts
CHANGED
|
@@ -889,7 +889,7 @@ function refuseNamesTheEditorCouldKeyDifferently(
|
|
|
889
889
|
throw new CompileError(
|
|
890
890
|
`${found.length} pair(s) of ${what.noun} have no one order: ${what.why}` +
|
|
891
891
|
`${spelled.join('. ')}` +
|
|
892
|
-
(found.length > spelled.length ? `. …and ${found.length - spelled.length} more pair(s)` : ''),
|
|
892
|
+
(found.length > spelled.length ? `. …and ${found.length - spelled.length} more pair(s)` : ''), 'CE_COMPILE_EDITOR_NAME_ORDER_AMBIGUOUS',
|
|
893
893
|
);
|
|
894
894
|
}
|
|
895
895
|
|
|
@@ -969,7 +969,7 @@ export function f32(n: number): number {
|
|
|
969
969
|
}
|
|
970
970
|
// Unreachable — nine significant digits name every float32. Stated rather than
|
|
971
971
|
// assumed, because a fallback here would be a number nobody chose.
|
|
972
|
-
throw new CompileError(`no decimal of at most 9 significant digits names the float32 of ${n}
|
|
972
|
+
throw new CompileError(`no decimal of at most 9 significant digits names the float32 of ${n}`, 'CE_COMPILE_FLOAT32_DECIMAL_UNREPRESENTABLE');
|
|
973
973
|
}
|
|
974
974
|
|
|
975
975
|
/**
|
|
@@ -1144,7 +1144,7 @@ function checkKeyTime(where: string, time: number, authored: number, duration: n
|
|
|
1144
1144
|
throw new CompileError(
|
|
1145
1145
|
`${where}: key at ${at} is ${f32(past)}s past the declared duration ${duration}s — nothing that plays this ` +
|
|
1146
1146
|
`animation for the duration it declares ever reaches it. Move the key to ${duration}, or declare the ` +
|
|
1147
|
-
`duration you meant.`,
|
|
1147
|
+
`duration you meant.`, 'CE_COMPILE_KEY_AFTER_DURATION',
|
|
1148
1148
|
);
|
|
1149
1149
|
}
|
|
1150
1150
|
|
|
@@ -1156,7 +1156,7 @@ function channelHex(v: number): string {
|
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
1158
1158
|
function rgbaHex(v: number[]): string {
|
|
1159
|
-
if (v.length !== 4) throw new CompileError(`rgba value needs 4 channels, got ${v.length}
|
|
1159
|
+
if (v.length !== 4) throw new CompileError(`rgba value needs 4 channels, got ${v.length}`, 'CE_COMPILE_RGBA_CHANNEL_COUNT');
|
|
1160
1160
|
return v.map(channelHex).join('');
|
|
1161
1161
|
}
|
|
1162
1162
|
|
|
@@ -1176,7 +1176,7 @@ function rgbaHex(v: number[]): string {
|
|
|
1176
1176
|
* by: `readCurve(…, 0..3, light r/g/b/a)` then `readCurve(…, 4..6, dark r/g/b)`.
|
|
1177
1177
|
*/
|
|
1178
1178
|
function rgba2Hex(v: number[]): { light: string; dark: string } {
|
|
1179
|
-
if (v.length !== 7) throw new CompileError(`rgba2 value needs 7 channels, got ${v.length}
|
|
1179
|
+
if (v.length !== 7) throw new CompileError(`rgba2 value needs 7 channels, got ${v.length}`, 'CE_COMPILE_RGBA2_CHANNEL_COUNT');
|
|
1180
1180
|
return { light: v.slice(0, 4).map(channelHex).join(''), dark: v.slice(4, 7).map(channelHex).join('') };
|
|
1181
1181
|
}
|
|
1182
1182
|
|
|
@@ -1226,7 +1226,7 @@ const COLOUR_KEYS: Record<SlotColourShape, { channels: number; spelling: string;
|
|
|
1226
1226
|
function colourKey(shape: SlotColourShape, v: number[]): Record<string, string | number> {
|
|
1227
1227
|
const { channels, write } = COLOUR_KEYS[shape];
|
|
1228
1228
|
if (v.length !== channels) {
|
|
1229
|
-
throw new CompileError(`${shape} value needs ${channels} channel${channels === 1 ? '' : 's'}, got ${v.length}
|
|
1229
|
+
throw new CompileError(`${shape} value needs ${channels} channel${channels === 1 ? '' : 's'}, got ${v.length}`, 'CE_COMPILE_COLOUR_CHANNEL_COUNT');
|
|
1230
1230
|
}
|
|
1231
1231
|
return write(v);
|
|
1232
1232
|
}
|
|
@@ -1654,7 +1654,7 @@ function readJson<T>(path: string): T {
|
|
|
1654
1654
|
try {
|
|
1655
1655
|
text = readFileSync(path, 'utf8');
|
|
1656
1656
|
} catch (err) {
|
|
1657
|
-
throw new CompileError(`cannot read ${path}: ${(err as Error).message}
|
|
1657
|
+
throw new CompileError(`cannot read ${path}: ${(err as Error).message}`, 'CE_COMPILE_JSON_READ_FAILED');
|
|
1658
1658
|
}
|
|
1659
1659
|
try {
|
|
1660
1660
|
// A parse failure gets a line/column appended to the runtime's own
|
|
@@ -1662,7 +1662,7 @@ function readJson<T>(path: string): T {
|
|
|
1662
1662
|
// cannot say where.
|
|
1663
1663
|
return parseJsonWithPosition(text) as T;
|
|
1664
1664
|
} catch (err) {
|
|
1665
|
-
throw new CompileError(`cannot read ${path}: ${(err as Error).message}
|
|
1665
|
+
throw new CompileError(`cannot read ${path}: ${(err as Error).message}`, 'CE_COMPILE_JSON_READ_FAILED');
|
|
1666
1666
|
}
|
|
1667
1667
|
}
|
|
1668
1668
|
|
|
@@ -1882,13 +1882,13 @@ function fromLoosePng(
|
|
|
1882
1882
|
// Left to `readFileSync` this arrives as a raw ENOENT with a stack, which
|
|
1883
1883
|
// is the tool telling an agent about its own internals instead of about
|
|
1884
1884
|
// the rig. The validator's messages are the UI, and so are these.
|
|
1885
|
-
throw new CompileError(`image "${relPath}" is not on disk at ${absPath}
|
|
1885
|
+
throw new CompileError(`image "${relPath}" is not on disk at ${absPath}`, 'CE_COMPILE_IMAGE_MISSING');
|
|
1886
1886
|
}
|
|
1887
1887
|
// A loose part IS a page of the atlas this route emits, so it is refused the
|
|
1888
1888
|
// way a pack's page is (issue #732): by the one reader's sentence, as a
|
|
1889
1889
|
// CompileError, rather than as that reader's throw with a stack under it.
|
|
1890
1890
|
const header = readPngHeader(absPath);
|
|
1891
|
-
if (header.problem !== null) throw new CompileError(`image "${relPath}": ${header.problem}
|
|
1891
|
+
if (header.problem !== null) throw new CompileError(`image "${relPath}": ${header.problem}`, 'CE_COMPILE_IMAGE_PNG_INVALID');
|
|
1892
1892
|
const info = header.info;
|
|
1893
1893
|
// Page name is the PNG path *relative to the atlas file*, so the viewer
|
|
1894
1894
|
// resolves it the way every Spine consumer does: against the atlas URL.
|
|
@@ -1913,7 +1913,7 @@ interface AtlasSource {
|
|
|
1913
1913
|
}
|
|
1914
1914
|
|
|
1915
1915
|
function readAtlasIn(path: string): AtlasSource {
|
|
1916
|
-
if (!existsSync(path)) throw new CompileError(`--atlas-in names ${path}, which is not on disk
|
|
1916
|
+
if (!existsSync(path)) throw new CompileError(`--atlas-in names ${path}, which is not on disk`, 'CE_COMPILE_ATLAS_INPUT_MISSING');
|
|
1917
1917
|
const parsed = parseAtlasText(readFileSync(path, 'utf8'));
|
|
1918
1918
|
const byName = new Map<string, { region: AtlasRegion; page: ParsedAtlas['pages'][number] }>();
|
|
1919
1919
|
for (const page of parsed.pages) {
|
|
@@ -1925,7 +1925,7 @@ function readAtlasIn(path: string): AtlasSource {
|
|
|
1925
1925
|
if (!byName.has(key)) byName.set(key, { region, page });
|
|
1926
1926
|
}
|
|
1927
1927
|
}
|
|
1928
|
-
if (byName.size === 0) throw new CompileError(`--atlas-in ${path} declares no regions
|
|
1928
|
+
if (byName.size === 0) throw new CompileError(`--atlas-in ${path} declares no regions`, 'CE_COMPILE_ATLAS_INPUT_EMPTY');
|
|
1929
1929
|
// 🔒 **What each page IS, where the pack is opened** (issue #732). Every page
|
|
1930
1930
|
// the pack names reaches the gate — the atlas this build emits is the pack's
|
|
1931
1931
|
// own text — so a page no reader can open is refused here, all of them in
|
|
@@ -1958,7 +1958,7 @@ function readAtlasIn(path: string): AtlasSource {
|
|
|
1958
1958
|
if (unread.length > 0) {
|
|
1959
1959
|
throw new CompileError(
|
|
1960
1960
|
`--atlas-in ${path}: ${unread.length} of its ${parsed.pages.length} page(s) cannot be read as PNG, and ` +
|
|
1961
|
-
`nothing was compiled against the pack — ${unread.join('; ')}`,
|
|
1961
|
+
`nothing was compiled against the pack — ${unread.join('; ')}`, 'CE_COMPILE_ATLAS_PAGES_UNREADABLE',
|
|
1962
1962
|
);
|
|
1963
1963
|
}
|
|
1964
1964
|
return { path, dir: dirname(path), parsed, byName, grids };
|
|
@@ -2028,13 +2028,13 @@ function resolveFromAtlas(
|
|
|
2028
2028
|
throw new CompileError(
|
|
2029
2029
|
`image "${relPath}" resolves to region "${region}", which the atlas at ${atlas.path} does not have. ` +
|
|
2030
2030
|
(near.length ? `Did you mean ${near.map((n) => JSON.stringify(n)).join(', ')}? ` : '') +
|
|
2031
|
-
`The atlas declares ${known.length} region(s): ${known.join(', ')}`,
|
|
2031
|
+
`The atlas declares ${known.length} region(s): ${known.join(', ')}`, 'CE_COMPILE_ATLAS_REGION_UNKNOWN',
|
|
2032
2032
|
);
|
|
2033
2033
|
}
|
|
2034
2034
|
const absPath = resolve(atlas.dir, found.page.name);
|
|
2035
2035
|
if (!existsSync(absPath)) {
|
|
2036
2036
|
throw new CompileError(
|
|
2037
|
-
`region "${region}" sits on page "${found.page.name}" of ${atlas.path}, which is not on disk at ${absPath}`,
|
|
2037
|
+
`region "${region}" sits on page "${found.page.name}" of ${atlas.path}, which is not on disk at ${absPath}`, 'CE_COMPILE_ATLAS_REGION_PAGE_MISSING',
|
|
2038
2038
|
);
|
|
2039
2039
|
}
|
|
2040
2040
|
// The rectangle ON THE PAGE, which at a quarter turn is not the one `bounds:`
|
|
@@ -2049,7 +2049,7 @@ function resolveFromAtlas(
|
|
|
2049
2049
|
throw new CompileError(
|
|
2050
2050
|
`region "${region}" is at ${found.region.x},${found.region.y} sized ${rectW}x${rectH} on a ` +
|
|
2051
2051
|
`${found.page.width}x${found.page.height} page in ${atlas.path}; the rectangle runs off the page, so its ` +
|
|
2052
|
-
'UVs would leave the texture',
|
|
2052
|
+
'UVs would leave the texture', 'CE_COMPILE_ATLAS_REGION_OUTSIDE_PAGE',
|
|
2053
2053
|
);
|
|
2054
2054
|
}
|
|
2055
2055
|
const info = readPngInfo(absPath);
|
|
@@ -2342,7 +2342,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2342
2342
|
return fn();
|
|
2343
2343
|
} catch (err) {
|
|
2344
2344
|
if (err instanceof CompileError && !err.message.includes(motionPath)) {
|
|
2345
|
-
throw new CompileError(`${motionPath}: ${err.message}
|
|
2345
|
+
throw new CompileError(`${motionPath}: ${err.message}`, err.code);
|
|
2346
2346
|
}
|
|
2347
2347
|
throw err;
|
|
2348
2348
|
}
|
|
@@ -2357,7 +2357,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2357
2357
|
// does not — the class of wrongness that loads, plays and lies.
|
|
2358
2358
|
if (motion.archetype !== rig.name) {
|
|
2359
2359
|
throw new CompileError(
|
|
2360
|
-
`motion spec names archetype "${motion.archetype}" but the rig spec at ${rigPath} is called "${rig.name}"`,
|
|
2360
|
+
`motion spec names archetype "${motion.archetype}" but the rig spec at ${rigPath} is called "${rig.name}"`, 'CE_COMPILE_MOTION_ARCHETYPE_MISMATCH',
|
|
2361
2361
|
);
|
|
2362
2362
|
}
|
|
2363
2363
|
});
|
|
@@ -2378,7 +2378,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2378
2378
|
if (!noStage && (stageWidth === undefined || stageHeight === undefined)) {
|
|
2379
2379
|
throw new CompileError(
|
|
2380
2380
|
'no stage size: give the rig spec a `skeleton.width`/`skeleton.height`, or compile against a cut manifest whose `crop` states them, ' +
|
|
2381
|
-
'or state `"width": null, "height": null` for a skeleton that declares no stage',
|
|
2381
|
+
'or state `"width": null, "height": null` for a skeleton that declares no stage', 'CE_COMPILE_STAGE_SIZE_MISSING',
|
|
2382
2382
|
);
|
|
2383
2383
|
}
|
|
2384
2384
|
/**
|
|
@@ -2434,7 +2434,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2434
2434
|
partDir: string | null = dirname(resolve(baseDir, relPath)),
|
|
2435
2435
|
): CompiledImage => {
|
|
2436
2436
|
if (seenRegions.has(region)) {
|
|
2437
|
-
throw new CompileError(`duplicate region name "${region}" (${relPath})
|
|
2437
|
+
throw new CompileError(`duplicate region name "${region}" (${relPath})`, 'CE_COMPILE_REGION_NAME_DUPLICATE');
|
|
2438
2438
|
}
|
|
2439
2439
|
const loosePath = resolve(baseDir, relPath);
|
|
2440
2440
|
const img =
|
|
@@ -2485,7 +2485,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2485
2485
|
throw new CompileError(
|
|
2486
2486
|
`${where}: "${relPath}" and the art already atlased as region "${region}" are two different files — ` +
|
|
2487
2487
|
`${wanted} and ${already}. An atlas region is named by its PNG's basename, so only one of the two can ` +
|
|
2488
|
-
'hold that name and this attachment would draw the other file\'s pixels. Rename one of the PNGs.',
|
|
2488
|
+
'hold that name and this attachment would draw the other file\'s pixels. Rename one of the PNGs.', 'CE_COMPILE_REGION_FILE_CONFLICT',
|
|
2489
2489
|
);
|
|
2490
2490
|
};
|
|
2491
2491
|
|
|
@@ -2516,7 +2516,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2516
2516
|
`${where}: sequence ${frame} is the region "${region}", and there is no PNG for it at ${wanted}. A ` +
|
|
2517
2517
|
`sequence's frames are the regions "<stem><start + i>", zero-padded to "digits" — here stem ` +
|
|
2518
2518
|
`${JSON.stringify(stem)}, start ${seq.start ?? 1}, digits ${seq.digits ?? 0} — and the compiler draws ` +
|
|
2519
|
-
'no frame in place of another. Add the file, or state the "count" the series really has.',
|
|
2519
|
+
'no frame in place of another. Add the file, or state the "count" the series really has.', 'CE_COMPILE_SEQUENCE_FRAME_PNG_MISSING',
|
|
2520
2520
|
);
|
|
2521
2521
|
}
|
|
2522
2522
|
if (atlasIn !== null && !atlasIn.byName.has(region)) {
|
|
@@ -2526,7 +2526,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2526
2526
|
(near.length ? `Did you mean ${near.map((n) => JSON.stringify(n)).join(', ')}? ` : '') +
|
|
2527
2527
|
`A sequence's frames are the regions "<stem><start + i>", zero-padded to "digits" — here stem ` +
|
|
2528
2528
|
`${JSON.stringify(stem)}, start ${seq.start ?? 1}, digits ${seq.digits ?? 0} — and the compiler draws ` +
|
|
2529
|
-
'no frame in place of another.',
|
|
2529
|
+
'no frame in place of another.', 'CE_COMPILE_SEQUENCE_FRAME_REGION_MISSING',
|
|
2530
2530
|
);
|
|
2531
2531
|
}
|
|
2532
2532
|
const already = regionSource.get(region);
|
|
@@ -2535,7 +2535,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2535
2535
|
throw new CompileError(
|
|
2536
2536
|
`${where}: sequence ${frame} is the region "${region}", and the art already atlased under that name is ` +
|
|
2537
2537
|
`${already}, not ${wanted}. One region name is one set of pixels, so this frame would draw the other ` +
|
|
2538
|
-
"file's. Rename one of them.",
|
|
2538
|
+
"file's. Rename one of them.", 'CE_COMPILE_SEQUENCE_FRAME_FILE_CONFLICT',
|
|
2539
2539
|
);
|
|
2540
2540
|
}
|
|
2541
2541
|
// The directory the frame NAMES are relative to — the images root, not
|
|
@@ -2575,7 +2575,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2575
2575
|
for (const part of parts) {
|
|
2576
2576
|
if (!rigSlotIndex.has(part.slot)) {
|
|
2577
2577
|
throw new CompileError(
|
|
2578
|
-
`the manifest binds a part to slot "${part.slot}" (via rig_slot), which the rig "${rig.name}" does not declare — add the slot to the rig rather than inventing one here`,
|
|
2578
|
+
`the manifest binds a part to slot "${part.slot}" (via rig_slot), which the rig "${rig.name}" does not declare — add the slot to the rig rather than inventing one here`, 'CE_COMPILE_MANIFEST_SLOT_UNKNOWN',
|
|
2579
2579
|
);
|
|
2580
2580
|
}
|
|
2581
2581
|
}
|
|
@@ -2589,7 +2589,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2589
2589
|
const at = rigSlotIndex.get(part.slot)!;
|
|
2590
2590
|
if (at < orderCursor) {
|
|
2591
2591
|
throw new CompileError(
|
|
2592
|
-
`the manifest draws "${part.slot}" (draw_order ${part.draw_order}) out of the rig's slot order; the rig's slots array IS the draw order`,
|
|
2592
|
+
`the manifest draws "${part.slot}" (draw_order ${part.draw_order}) out of the rig's slot order; the rig's slots array IS the draw order`, 'CE_COMPILE_MANIFEST_DRAW_ORDER_MISMATCH',
|
|
2593
2593
|
);
|
|
2594
2594
|
}
|
|
2595
2595
|
orderCursor = at;
|
|
@@ -2604,33 +2604,33 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2604
2604
|
if (meshParts.length > meshBudget) {
|
|
2605
2605
|
throw new CompileError(
|
|
2606
2606
|
`${meshParts.length} mesh slot(s) declared but the rig "${rig.name}" allows ${meshBudget}` +
|
|
2607
|
-
' — raise `invariants.meshSlots` in the rig spec if that budget is the thing being changed',
|
|
2607
|
+
' — raise `invariants.meshSlots` in the rig spec if that budget is the thing being changed', 'CE_COMPILE_MANIFEST_MESH_SLOT_BUDGET',
|
|
2608
2608
|
);
|
|
2609
2609
|
}
|
|
2610
2610
|
for (const part of meshParts) {
|
|
2611
2611
|
if (isBasePlate(part, manifest!)) {
|
|
2612
2612
|
// A base plate mesh is a full-frame canvas every frame.
|
|
2613
|
-
throw new CompileError(`slot "${part.slot}" is the base plate; it must never be a mesh
|
|
2613
|
+
throw new CompileError(`slot "${part.slot}" is the base plate; it must never be a mesh`, 'CE_COMPILE_BASE_PLATE_MESH');
|
|
2614
2614
|
}
|
|
2615
2615
|
const spec = part.mesh!;
|
|
2616
2616
|
const kind = spec.kind ?? 'ring';
|
|
2617
2617
|
if (kind === 'ring') {
|
|
2618
2618
|
if (!part.polygon?.length) {
|
|
2619
|
-
throw new CompileError(`slot "${part.slot}" declares a ring mesh but has no polygon to use as its rim
|
|
2619
|
+
throw new CompileError(`slot "${part.slot}" declares a ring mesh but has no polygon to use as its rim`, 'CE_COMPILE_RING_POLYGON_MISSING');
|
|
2620
2620
|
}
|
|
2621
2621
|
if (spec.hull !== 'polygon') {
|
|
2622
|
-
throw new CompileError(`slot "${part.slot}": mesh.hull must be "polygon", got ${JSON.stringify(spec.hull)}
|
|
2622
|
+
throw new CompileError(`slot "${part.slot}": mesh.hull must be "polygon", got ${JSON.stringify(spec.hull)}`, 'CE_COMPILE_RING_HULL_INVALID');
|
|
2623
2623
|
}
|
|
2624
2624
|
if (!spec.center || spec.inner === undefined) {
|
|
2625
|
-
throw new CompileError(`slot "${part.slot}": a ring mesh needs mesh.center and mesh.inner
|
|
2625
|
+
throw new CompileError(`slot "${part.slot}": a ring mesh needs mesh.center and mesh.inner`, 'CE_COMPILE_RING_PARAMETERS_MISSING');
|
|
2626
2626
|
}
|
|
2627
2627
|
} else {
|
|
2628
2628
|
if (!spec.rows || !spec.chain?.length) {
|
|
2629
|
-
throw new CompileError(`slot "${part.slot}": a ribbon mesh needs mesh.rows and a mesh.chain
|
|
2629
|
+
throw new CompileError(`slot "${part.slot}": a ribbon mesh needs mesh.rows and a mesh.chain`, 'CE_COMPILE_RIBBON_PARAMETERS_MISSING');
|
|
2630
2630
|
}
|
|
2631
2631
|
}
|
|
2632
2632
|
if (!meshControlBones(part).length) {
|
|
2633
|
-
throw new CompileError(`slot "${part.slot}": a mesh with no control bone deforms nothing
|
|
2633
|
+
throw new CompileError(`slot "${part.slot}": a mesh with no control bone deforms nothing`, 'CE_COMPILE_MESH_CONTROL_BONE_MISSING');
|
|
2634
2634
|
}
|
|
2635
2635
|
}
|
|
2636
2636
|
|
|
@@ -2656,7 +2656,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2656
2656
|
const img = addImage(part.image, manifestDir!, isBasePlate(part, manifest!));
|
|
2657
2657
|
if (img.width !== win.w || img.height !== win.h) {
|
|
2658
2658
|
throw new CompileError(
|
|
2659
|
-
`${measuredFrom(img, part.image)} is ${img.width}x${img.height} but the manifest window for "${part.slot}" is ${win.w}x${win.h}`,
|
|
2659
|
+
`${measuredFrom(img, part.image)} is ${img.width}x${img.height} but the manifest window for "${part.slot}" is ${win.w}x${win.h}`, 'CE_COMPILE_MANIFEST_WINDOW_SIZE_MISMATCH',
|
|
2660
2660
|
);
|
|
2661
2661
|
}
|
|
2662
2662
|
slotAttachments.set(part.slot, [img.region]);
|
|
@@ -2691,7 +2691,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2691
2691
|
const img = addImage(relPath, manifestDir!, false);
|
|
2692
2692
|
if (img.width !== win.w || img.height !== win.h) {
|
|
2693
2693
|
throw new CompileError(
|
|
2694
|
-
`${measuredFrom(img, relPath)} is ${img.width}x${img.height} but slot "${part.slot}" declares ${win.w}x${win.h}`,
|
|
2694
|
+
`${measuredFrom(img, relPath)} is ${img.width}x${img.height} but slot "${part.slot}" declares ${win.w}x${win.h}`, 'CE_COMPILE_SLOT_IMAGE_SIZE_MISMATCH',
|
|
2695
2695
|
);
|
|
2696
2696
|
}
|
|
2697
2697
|
names.push(img.region);
|
|
@@ -2714,11 +2714,11 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2714
2714
|
for (const skinName of skinNames) {
|
|
2715
2715
|
for (const [slotName, placeholders] of Object.entries(skinParts.get(skinName)!.attachments)) {
|
|
2716
2716
|
if (!rigSlotIndex.has(slotName)) {
|
|
2717
|
-
throw new CompileError(`rig skin "${skinName}" gives attachments to slot "${slotName}", which the rig does not declare
|
|
2717
|
+
throw new CompileError(`rig skin "${skinName}" gives attachments to slot "${slotName}", which the rig does not declare`, 'CE_COMPILE_SKIN_SLOT_UNKNOWN');
|
|
2718
2718
|
}
|
|
2719
2719
|
if (slotAttachments.has(slotName)) {
|
|
2720
2720
|
throw new CompileError(
|
|
2721
|
-
`slot "${slotName}" is filled by a manifest part AND by rig skin "${skinName}"; one slot, one source of attachments`,
|
|
2721
|
+
`slot "${slotName}" is filled by a manifest part AND by rig skin "${skinName}"; one slot, one source of attachments`, 'CE_COMPILE_SLOT_ATTACHMENT_SOURCES_CONFLICT',
|
|
2722
2722
|
);
|
|
2723
2723
|
}
|
|
2724
2724
|
const names = rigAttachmentNames.get(slotName) ?? [];
|
|
@@ -2809,7 +2809,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2809
2809
|
transforms = computeWorldTransforms(bones);
|
|
2810
2810
|
exactFrames = computeExactFrameTransforms(bones);
|
|
2811
2811
|
} catch (err) {
|
|
2812
|
-
if (err instanceof TransformError) throw new CompileError(err.message);
|
|
2812
|
+
if (err instanceof TransformError) throw new CompileError(err.message, 'CE_COMPILE_SETUP_TRANSFORM_INVALID');
|
|
2813
2813
|
throw err;
|
|
2814
2814
|
}
|
|
2815
2815
|
|
|
@@ -2817,7 +2817,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2817
2817
|
for (const name of meshControlBones(part)) {
|
|
2818
2818
|
if (!boneNames.has(name)) {
|
|
2819
2819
|
throw new CompileError(
|
|
2820
|
-
`slot "${part.slot}" drives control bone "${name}", which the rig "${rig.name}" does not declare`,
|
|
2820
|
+
`slot "${part.slot}" drives control bone "${name}", which the rig "${rig.name}" does not declare`, 'CE_COMPILE_CONTROL_BONE_UNKNOWN',
|
|
2821
2821
|
);
|
|
2822
2822
|
}
|
|
2823
2823
|
}
|
|
@@ -2909,7 +2909,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2909
2909
|
// here down is the half that needs the rig in front of it.
|
|
2910
2910
|
if (setup !== undefined && rigSlot.attachment !== undefined) {
|
|
2911
2911
|
throw new CompileError(
|
|
2912
|
-
`slot "${rigSlot.name}" has a setup attachment in the rig spec AND in the motion spec; the setup pose has one author`,
|
|
2912
|
+
`slot "${rigSlot.name}" has a setup attachment in the rig spec AND in the motion spec; the setup pose has one author`, 'CE_COMPILE_SETUP_ATTACHMENT_AUTHORS_CONFLICT',
|
|
2913
2913
|
);
|
|
2914
2914
|
}
|
|
2915
2915
|
let setupAttachment: string | null;
|
|
@@ -2922,7 +2922,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2922
2922
|
else if (empty) setupAttachment = null;
|
|
2923
2923
|
else {
|
|
2924
2924
|
throw new CompileError(
|
|
2925
|
-
`no setup pose for slot "${rigSlot.name}": give the motion spec a \`setup\` entry or the rig slot an \`attachment\` — the compiler will not guess one`,
|
|
2925
|
+
`no setup pose for slot "${rigSlot.name}": give the motion spec a \`setup\` entry or the rig slot an \`attachment\` — the compiler will not guess one`, 'CE_COMPILE_SETUP_ATTACHMENT_MISSING',
|
|
2926
2926
|
);
|
|
2927
2927
|
}
|
|
2928
2928
|
if (setupAttachment !== null && !names.includes(setupAttachment)) {
|
|
@@ -2971,10 +2971,10 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2971
2971
|
`no art: ${found} — ${supply}, ${many ? 'fix the paths' : 'fix the path'} in the manifest, or state the ` +
|
|
2972
2972
|
'setup pose as null';
|
|
2973
2973
|
}
|
|
2974
|
-
throw new CompileError(message);
|
|
2974
|
+
throw new CompileError(message, 'CE_COMPILE_SETUP_ATTACHMENT_UNAVAILABLE');
|
|
2975
2975
|
}
|
|
2976
2976
|
if (setup?.color && rigSlot.color !== undefined) {
|
|
2977
|
-
throw new CompileError(`slot "${rigSlot.name}" has a setup colour in the rig spec AND in the motion spec
|
|
2977
|
+
throw new CompileError(`slot "${rigSlot.name}" has a setup colour in the rig spec AND in the motion spec`, 'CE_COMPILE_SETUP_COLOUR_AUTHORS_CONFLICT');
|
|
2978
2978
|
}
|
|
2979
2979
|
// The setup attachment is held as the value it is, `null` included; leaving
|
|
2980
2980
|
// the key out for `null` is the emitter's (`emitSlots`).
|
|
@@ -3001,7 +3001,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3001
3001
|
const mesh = part.mesh ? buildMesh(part, manifest!, bones, transforms, exactFrames, rigSlot.bone) : null;
|
|
3002
3002
|
for (const name of names) {
|
|
3003
3003
|
const img = images.find((im) => im.region === name);
|
|
3004
|
-
if (!img) throw new CompileError(`internal: no image for attachment ${name}
|
|
3004
|
+
if (!img) throw new CompileError(`internal: no image for attachment ${name}`, 'CE_COMPILE_INTERNAL_ATTACHMENT_IMAGE_MISSING');
|
|
3005
3005
|
if (mesh) {
|
|
3006
3006
|
// Every state of a mesh slot gets the SAME geometry. That is what makes
|
|
3007
3007
|
// an attachment swap mid-deform safe: the control bone's pose means the
|
|
@@ -3089,7 +3089,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3089
3089
|
`${budgeted.length}, "meshTriangles": <triangles one mesh may carry> }\` to the rig spec: geometry rigc ` +
|
|
3090
3090
|
'built is geometry it will not ship unmeasured, and `A13_MESH_BUDGET` has nothing to measure against ' +
|
|
3091
3091
|
'until that budget is stated. (Authored geometry is exempt — rigc did not draw it.)'
|
|
3092
|
-
: ' — raise `invariants.meshSlots` in the rig spec if that budget is the thing being changed'),
|
|
3092
|
+
: ' — raise `invariants.meshSlots` in the rig spec if that budget is the thing being changed'), 'CE_COMPILE_GENERATED_MESH_SLOT_BUDGET',
|
|
3093
3093
|
);
|
|
3094
3094
|
}
|
|
3095
3095
|
|
|
@@ -3126,7 +3126,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3126
3126
|
throw new CompileError(
|
|
3127
3127
|
`physics constraint "${EVERY_GLOBAL_PHYSICS}": the name is reserved — a motion spec's \`"physics": ` +
|
|
3128
3128
|
`"${EVERY_GLOBAL_PHYSICS}"\` is the timeline that names no constraint and drives every physics constraint ` +
|
|
3129
|
-
'declaring the keyed property global, so a constraint called that could not be keyed by name. Rename it',
|
|
3129
|
+
'declaring the keyed property global, so a constraint called that could not be keyed by name. Rename it', 'CE_COMPILE_PHYSICS_NAME_RESERVED',
|
|
3130
3130
|
);
|
|
3131
3131
|
}
|
|
3132
3132
|
constraintDeclared.add(constraintAt(type, name));
|
|
@@ -3199,12 +3199,12 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3199
3199
|
// apart.
|
|
3200
3200
|
if (constraintDeclared.has(constraintAt('physics', name))) {
|
|
3201
3201
|
throw new CompileError(
|
|
3202
|
-
`physics constraint "${name}" is declared in both the rig spec and the motion spec's physics table`,
|
|
3202
|
+
`physics constraint "${name}" is declared in both the rig spec and the motion spec's physics table`, 'CE_COMPILE_PHYSICS_DECLARATION_DUPLICATE',
|
|
3203
3203
|
);
|
|
3204
3204
|
}
|
|
3205
3205
|
declareConstraint(name, 'physics');
|
|
3206
3206
|
if (!boneNames.has(spec.bone)) {
|
|
3207
|
-
throw new CompileError(`physics constraint "${name}" targets unknown bone "${spec.bone}"
|
|
3207
|
+
throw new CompileError(`physics constraint "${name}" targets unknown bone "${spec.bone}"`, 'CE_COMPILE_PHYSICS_BONE_UNKNOWN');
|
|
3208
3208
|
}
|
|
3209
3209
|
// A stop's copy and clamps sit right after the rig constraint that swings
|
|
3210
3210
|
// each link (`src/stop.ts`); this table is emitted after every rig
|
|
@@ -3214,7 +3214,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3214
3214
|
throw new CompileError(
|
|
3215
3215
|
`physics constraint "${name}" drives "${spec.bone}", a link stop "${stop.chain}" holds, from the physics table — ` +
|
|
3216
3216
|
"the table is emitted after every rig constraint, past the stop's copy and clamp, which would read the link " +
|
|
3217
|
-
'before this swings it; declare the constraint in the rig spec\'s "constraints"',
|
|
3217
|
+
'before this swings it; declare the constraint in the rig spec\'s "constraints"', 'CE_COMPILE_PHYSICS_STOP_ORDER_INVALID',
|
|
3218
3218
|
);
|
|
3219
3219
|
}
|
|
3220
3220
|
// Every component and parameter the table states is held, 0 and the
|
|
@@ -3233,7 +3233,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3233
3233
|
// The parser is happy with this and the constraint does nothing at all.
|
|
3234
3234
|
// A23 catches it too; refusing here means it never reaches the gate.
|
|
3235
3235
|
throw new CompileError(
|
|
3236
|
-
`physics constraint "${name}" drives no component — set at least one of ${PHYSICS_COMPONENTS.join('/')}`,
|
|
3236
|
+
`physics constraint "${name}" drives no component — set at least one of ${PHYSICS_COMPONENTS.join('/')}`, 'CE_COMPILE_PHYSICS_COMPONENTS_EMPTY',
|
|
3237
3237
|
);
|
|
3238
3238
|
}
|
|
3239
3239
|
for (const [param] of PHYSICS_PARAMS) {
|
|
@@ -3342,7 +3342,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3342
3342
|
if (everyGlobal && track.physics === undefined) {
|
|
3343
3343
|
throw new CompileError(
|
|
3344
3344
|
`animation "${animName}": group "${String(track.group)}" lists "${EVERY_GLOBAL_PHYSICS}", which is not a ` +
|
|
3345
|
-
`constraint but the target that names none — write it as the track's \`"physics": "${EVERY_GLOBAL_PHYSICS}"\``,
|
|
3345
|
+
`constraint but the target that names none — write it as the track's \`"physics": "${EVERY_GLOBAL_PHYSICS}"\``, 'CE_COMPILE_PHYSICS_GLOBAL_GROUP_MEMBER',
|
|
3346
3346
|
);
|
|
3347
3347
|
}
|
|
3348
3348
|
if (everyGlobal) {
|
|
@@ -3364,7 +3364,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3364
3364
|
' — it would parse and move nothing. ' +
|
|
3365
3365
|
(track.property === 'reset'
|
|
3366
3366
|
? 'Declare a physics constraint, or remove the track'
|
|
3367
|
-
: `Set "${flag}": true on the constraints it is for, or key one by name`),
|
|
3367
|
+
: `Set "${flag}": true on the constraints it is for, or key one by name`), 'CE_COMPILE_PHYSICS_GLOBAL_TRACK_INERT',
|
|
3368
3368
|
);
|
|
3369
3369
|
}
|
|
3370
3370
|
} else if (family !== null) {
|
|
@@ -3380,21 +3380,21 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3380
3380
|
if (kinds.length > 0) {
|
|
3381
3381
|
throw new CompileError(
|
|
3382
3382
|
`animation "${animName}" keys "${target}" as a ${label}, but the rig declares it as a "${kinds.join('"/"')}" ` +
|
|
3383
|
-
'constraint — a timeline group resolves its target by name AND type, misses, and the loader throws',
|
|
3383
|
+
'constraint — a timeline group resolves its target by name AND type, misses, and the loader throws', 'CE_COMPILE_CONSTRAINT_TRACK_TYPE_MISMATCH',
|
|
3384
3384
|
);
|
|
3385
3385
|
}
|
|
3386
3386
|
const known = constraintNamesOfKind.get(family) ?? [];
|
|
3387
3387
|
throw new CompileError(
|
|
3388
3388
|
`animation "${animName}" keys unknown ${label} "${target}"` +
|
|
3389
|
-
(known.length ? ` (the rig declares: ${known.join(', ')})` : `, and the rig declares no ${family} constraint at all`),
|
|
3389
|
+
(known.length ? ` (the rig declares: ${known.join(', ')})` : `, and the rig declares no ${family} constraint at all`), 'CE_COMPILE_CONSTRAINT_TRACK_TARGET_UNKNOWN',
|
|
3390
3390
|
);
|
|
3391
3391
|
}
|
|
3392
3392
|
} else if (isBoneTrack) {
|
|
3393
3393
|
if (!boneNames.has(target)) {
|
|
3394
|
-
throw new CompileError(`animation "${animName}" keys unknown bone "${target}"
|
|
3394
|
+
throw new CompileError(`animation "${animName}" keys unknown bone "${target}"`, 'CE_COMPILE_ANIMATION_BONE_UNKNOWN');
|
|
3395
3395
|
}
|
|
3396
3396
|
} else if (!slotNames.has(target)) {
|
|
3397
|
-
throw new CompileError(`animation "${animName}" targets unknown slot "${target}"
|
|
3397
|
+
throw new CompileError(`animation "${animName}" targets unknown slot "${target}"`, 'CE_COMPILE_ANIMATION_SLOT_UNKNOWN');
|
|
3398
3398
|
}
|
|
3399
3399
|
// A timeline is a kind, a name and a property — `physics.leg.mix` and
|
|
3400
3400
|
// `path.leg.mix` are two timelines on two constraints, so the claim
|
|
@@ -3403,7 +3403,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3403
3403
|
const claim = `${family ?? (isBoneTrack ? 'bone' : 'slot')} ${target}.${track.property}`;
|
|
3404
3404
|
if (claimed.has(claim)) {
|
|
3405
3405
|
throw new CompileError(
|
|
3406
|
-
`animation "${animName}" has two tracks on ${target}.${track.property}; merge them into one track`,
|
|
3406
|
+
`animation "${animName}" has two tracks on ${target}.${track.property}; merge them into one track`, 'CE_COMPILE_ANIMATION_TRACK_DUPLICATE',
|
|
3407
3407
|
);
|
|
3408
3408
|
}
|
|
3409
3409
|
claimed.add(claim);
|
|
@@ -3427,7 +3427,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3427
3427
|
'channels at every time, its setup value before its first key included, so the one the file states ' +
|
|
3428
3428
|
'later overwrites the other everywhere and the first one\'s keys there are read by nothing. Key each ' +
|
|
3429
3429
|
'channel once: "rgb" and "alpha" move the light colour\'s two halves on their own key times, "rgba" ' +
|
|
3430
|
-
'moves them together, and "rgb2" / "rgba2" add the dark colour',
|
|
3430
|
+
'moves them together, and "rgb2" / "rgba2" add the dark colour', 'CE_COMPILE_COLOUR_TRACK_CHANNEL_OVERLAP',
|
|
3431
3431
|
);
|
|
3432
3432
|
}
|
|
3433
3433
|
}
|
|
@@ -3480,7 +3480,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3480
3480
|
if (kinds.length > 0) {
|
|
3481
3481
|
throw new CompileError(
|
|
3482
3482
|
`animation "${animName}" keys "${name}" as ${group === 'ik' ? 'an' : 'a'} ${group} constraint, but the rig declares it as a ` +
|
|
3483
|
-
`"${kinds.join('"/"')}" constraint — the parser looks a timeline's target up by name AND type, misses, and throws`,
|
|
3483
|
+
`"${kinds.join('"/"')}" constraint — the parser looks a timeline's target up by name AND type, misses, and throws`, 'CE_COMPILE_CONSTRAINT_TIMELINE_TYPE_MISMATCH',
|
|
3484
3484
|
);
|
|
3485
3485
|
}
|
|
3486
3486
|
const known = constraintNamesOfKind.get(group) ?? [];
|
|
@@ -3488,13 +3488,13 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3488
3488
|
`animation "${animName}" keys unknown ${group} constraint "${name}"; ` +
|
|
3489
3489
|
(known.length
|
|
3490
3490
|
? `the rig declares ${group} constraint(s): ${known.join(', ')}`
|
|
3491
|
-
: `the rig declares no ${group} constraint at all`),
|
|
3491
|
+
: `the rig declares no ${group} constraint at all`), 'CE_COMPILE_CONSTRAINT_TIMELINE_TARGET_UNKNOWN',
|
|
3492
3492
|
);
|
|
3493
3493
|
}
|
|
3494
3494
|
if (constraintTimelines[group].has(name)) {
|
|
3495
3495
|
throw new CompileError(
|
|
3496
3496
|
`animation "${animName}" has two ${group} timelines on constraint "${name}"; ` +
|
|
3497
|
-
'the group holds one timeline per constraint, so merge them into one',
|
|
3497
|
+
'the group holds one timeline per constraint, so merge them into one', 'CE_COMPILE_CONSTRAINT_TIMELINE_DUPLICATE',
|
|
3498
3498
|
);
|
|
3499
3499
|
}
|
|
3500
3500
|
const keys = compileConstraintTrack(
|
|
@@ -3532,25 +3532,25 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3532
3532
|
const table = skinTables.get(skinName);
|
|
3533
3533
|
if (!table) {
|
|
3534
3534
|
throw new CompileError(
|
|
3535
|
-
`${at}: this rig emits no skin called "${skinName}" (it emits: ${[...skinTables.keys()].join(', ')})`,
|
|
3535
|
+
`${at}: this rig emits no skin called "${skinName}" (it emits: ${[...skinTables.keys()].join(', ')})`, 'CE_COMPILE_ATTACHMENT_TIMELINE_SKIN_UNKNOWN',
|
|
3536
3536
|
);
|
|
3537
3537
|
}
|
|
3538
3538
|
const perSlot = table[track.slot];
|
|
3539
3539
|
if (!perSlot) {
|
|
3540
3540
|
throw new CompileError(
|
|
3541
3541
|
`${at}: skin "${skinName}" gives slot "${String(track.slot)}" no attachments` +
|
|
3542
|
-
(slotNames.has(track.slot) ? '' : ', and this rig does not declare that slot at all'),
|
|
3542
|
+
(slotNames.has(track.slot) ? '' : ', and this rig does not declare that slot at all'), 'CE_COMPILE_ATTACHMENT_TIMELINE_SLOT_EMPTY',
|
|
3543
3543
|
);
|
|
3544
3544
|
}
|
|
3545
3545
|
const attachment = perSlot[track.attachment];
|
|
3546
3546
|
if (!attachment) {
|
|
3547
3547
|
throw new CompileError(
|
|
3548
3548
|
`${at}: slot "${track.slot}" in skin "${skinName}" has no attachment "${String(track.attachment)}" ` +
|
|
3549
|
-
`(it has: ${Object.keys(perSlot).join(', ')})`,
|
|
3549
|
+
`(it has: ${Object.keys(perSlot).join(', ')})`, 'CE_COMPILE_ATTACHMENT_TIMELINE_ATTACHMENT_UNKNOWN',
|
|
3550
3550
|
);
|
|
3551
3551
|
}
|
|
3552
3552
|
if (deformTimelines.get(skinName)?.get(track.slot)?.has(track.attachment)) {
|
|
3553
|
-
throw new CompileError(`${at}: two deform timelines on one attachment; merge them into one
|
|
3553
|
+
throw new CompileError(`${at}: two deform timelines on one attachment; merge them into one`, 'CE_COMPILE_DEFORM_TIMELINE_DUPLICATE');
|
|
3554
3554
|
}
|
|
3555
3555
|
const keys = compileDeformTrack(
|
|
3556
3556
|
track,
|
|
@@ -3579,28 +3579,28 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3579
3579
|
const table = skinTables.get(skinName);
|
|
3580
3580
|
if (!table) {
|
|
3581
3581
|
throw new CompileError(
|
|
3582
|
-
`${at}: this rig emits no skin called "${skinName}" (it emits: ${[...skinTables.keys()].join(', ')})`,
|
|
3582
|
+
`${at}: this rig emits no skin called "${skinName}" (it emits: ${[...skinTables.keys()].join(', ')})`, 'CE_COMPILE_ATTACHMENT_TIMELINE_SKIN_UNKNOWN',
|
|
3583
3583
|
);
|
|
3584
3584
|
}
|
|
3585
3585
|
const perSlot = table[track.slot];
|
|
3586
3586
|
if (!perSlot) {
|
|
3587
3587
|
throw new CompileError(
|
|
3588
3588
|
`${at}: skin "${skinName}" gives slot "${String(track.slot)}" no attachments` +
|
|
3589
|
-
(slotNames.has(track.slot) ? '' : ', and this rig does not declare that slot at all'),
|
|
3589
|
+
(slotNames.has(track.slot) ? '' : ', and this rig does not declare that slot at all'), 'CE_COMPILE_ATTACHMENT_TIMELINE_SLOT_EMPTY',
|
|
3590
3590
|
);
|
|
3591
3591
|
}
|
|
3592
3592
|
const attachment = perSlot[track.attachment];
|
|
3593
3593
|
if (!attachment) {
|
|
3594
3594
|
throw new CompileError(
|
|
3595
3595
|
`${at}: slot "${track.slot}" in skin "${skinName}" has no attachment "${String(track.attachment)}" ` +
|
|
3596
|
-
`(it has: ${Object.keys(perSlot).join(', ')})`,
|
|
3596
|
+
`(it has: ${Object.keys(perSlot).join(', ')})`, 'CE_COMPILE_ATTACHMENT_TIMELINE_ATTACHMENT_UNKNOWN',
|
|
3597
3597
|
);
|
|
3598
3598
|
}
|
|
3599
3599
|
const keys = compileSequenceTrack(track, anim.duration, attachment, at);
|
|
3600
3600
|
for (const key of keys) compiledDuration = Math.max(compiledDuration, key.time);
|
|
3601
3601
|
const slot = attachmentTimelinesOf(skinName, track.slot);
|
|
3602
3602
|
if (slot.get(track.attachment)?.sequence) {
|
|
3603
|
-
throw new CompileError(`${at}: two sequence timelines on one attachment; merge them into one
|
|
3603
|
+
throw new CompileError(`${at}: two sequence timelines on one attachment; merge them into one`, 'CE_COMPILE_SEQUENCE_TIMELINE_DUPLICATE');
|
|
3604
3604
|
}
|
|
3605
3605
|
slot.set(track.attachment, { ...(slot.get(track.attachment) ?? {}), sequence: keys });
|
|
3606
3606
|
}
|
|
@@ -3625,7 +3625,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3625
3625
|
// a key on a track whose neighbour already sits on the declared duration.
|
|
3626
3626
|
if (Math.abs(compiledDuration - anim.duration) > FRAME) {
|
|
3627
3627
|
throw new CompileError(
|
|
3628
|
-
`animation "${animName}" declares duration ${anim.duration}s but its last key is at ${compiledDuration}s`,
|
|
3628
|
+
`animation "${animName}" declares duration ${anim.duration}s but its last key is at ${compiledDuration}s`, 'CE_COMPILE_ANIMATION_DURATION_MISMATCH',
|
|
3629
3629
|
);
|
|
3630
3630
|
}
|
|
3631
3631
|
// The model's record of the animation. Which groups the file carries, in
|
|
@@ -3715,7 +3715,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3715
3715
|
const editorOrder: ModelEditorOrder = editorOrderOver(skins, [...animations.keys()]);
|
|
3716
3716
|
|
|
3717
3717
|
for (const slot of slots) {
|
|
3718
|
-
if (!boneNames.has(slot.bone)) throw new CompileError(`slot "${slot.name}" has no bone
|
|
3718
|
+
if (!boneNames.has(slot.bone)) throw new CompileError(`slot "${slot.name}" has no bone`, 'CE_COMPILE_SLOT_BONE_MISSING');
|
|
3719
3719
|
}
|
|
3720
3720
|
|
|
3721
3721
|
// One bone order per weighted attachment (issue #1407), on the finished
|
|
@@ -3904,7 +3904,7 @@ function buildBone(spec: RigBone, soFar: ModelBone[], ctx: BoneContext): ModelBo
|
|
|
3904
3904
|
bone.y = f32(world[1]);
|
|
3905
3905
|
} else {
|
|
3906
3906
|
const parent = computeWorldTransforms(soFar).get(spec.parent);
|
|
3907
|
-
if (!parent) throw new CompileError(`bone "${spec.name}" names parent "${spec.parent}", which is declared after it
|
|
3907
|
+
if (!parent) throw new CompileError(`bone "${spec.name}" names parent "${spec.parent}", which is declared after it`, 'CE_COMPILE_BONE_PARENT_ORDER');
|
|
3908
3908
|
const [lx, ly] = toBoneLocal(parent, world[0], world[1]);
|
|
3909
3909
|
bone.x = f32(lx);
|
|
3910
3910
|
bone.y = f32(ly);
|
|
@@ -3936,7 +3936,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3936
3936
|
if (!from) return null;
|
|
3937
3937
|
const needManifest = (what: string): FaceManifest => {
|
|
3938
3938
|
if (!ctx.manifest) {
|
|
3939
|
-
throw new CompileError(`bone "${spec.name}" takes its position from ${what}, which needs a cut manifest
|
|
3939
|
+
throw new CompileError(`bone "${spec.name}" takes its position from ${what}, which needs a cut manifest`, 'CE_COMPILE_BONE_POSITION_MANIFEST_REQUIRED');
|
|
3940
3940
|
}
|
|
3941
3941
|
return ctx.manifest;
|
|
3942
3942
|
};
|
|
@@ -3945,7 +3945,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3945
3945
|
const anchor = manifest.anchors?.[from.anchor];
|
|
3946
3946
|
if (!anchor || anchor.length < 2) {
|
|
3947
3947
|
throw new CompileError(
|
|
3948
|
-
`manifest anchors has no [x, y] for "${from.anchor}" (bone "${spec.name}" of rig "${ctx.rig.name}")`,
|
|
3948
|
+
`manifest anchors has no [x, y] for "${from.anchor}" (bone "${spec.name}" of rig "${ctx.rig.name}")`, 'CE_COMPILE_BONE_ANCHOR_MISSING',
|
|
3949
3949
|
);
|
|
3950
3950
|
}
|
|
3951
3951
|
return [anchor[0], anchor[1]];
|
|
@@ -3955,7 +3955,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3955
3955
|
const part = ctx.partBySlot.get(from.slotWindow);
|
|
3956
3956
|
if (!part) {
|
|
3957
3957
|
throw new CompileError(
|
|
3958
|
-
`bone "${spec.name}" sits at the centre of slot "${from.slotWindow}", which this cut's manifest carries no part for`,
|
|
3958
|
+
`bone "${spec.name}" sits at the centre of slot "${from.slotWindow}", which this cut's manifest carries no part for`, 'CE_COMPILE_BONE_SLOT_WINDOW_MISSING',
|
|
3959
3959
|
);
|
|
3960
3960
|
}
|
|
3961
3961
|
const win = partWindow(part, manifest);
|
|
@@ -3966,7 +3966,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3966
3966
|
const centre = ctx.partBySlot.get(from.meshCenter)?.mesh?.center;
|
|
3967
3967
|
if (!centre) {
|
|
3968
3968
|
throw new CompileError(
|
|
3969
|
-
`bone "${spec.name}" sits on the mesh centre of slot "${from.meshCenter}", which declares no mesh.center`,
|
|
3969
|
+
`bone "${spec.name}" sits on the mesh centre of slot "${from.meshCenter}", which declares no mesh.center`, 'CE_COMPILE_BONE_MESH_CENTER_MISSING',
|
|
3970
3970
|
);
|
|
3971
3971
|
}
|
|
3972
3972
|
return [centre[0], centre[1]];
|
|
@@ -3979,7 +3979,7 @@ function rotationOf(spec: RigBone, ctx: BoneContext): number | null {
|
|
|
3979
3979
|
const source = spec.from?.rotation;
|
|
3980
3980
|
if (source === 'axis') {
|
|
3981
3981
|
if (ctx.axisSpineDeg === null) {
|
|
3982
|
-
throw new CompileError(`bone "${spec.name}" takes its rotation from the cut axis, which the manifest does not declare
|
|
3982
|
+
throw new CompileError(`bone "${spec.name}" takes its rotation from the cut axis, which the manifest does not declare`, 'CE_COMPILE_BONE_ROTATION_AXIS_MISSING');
|
|
3983
3983
|
}
|
|
3984
3984
|
return ctx.axisSpineDeg;
|
|
3985
3985
|
}
|
|
@@ -3988,7 +3988,7 @@ function rotationOf(spec: RigBone, ctx: BoneContext): number | null {
|
|
|
3988
3988
|
const anchor = ctx.manifest?.anchors?.[key];
|
|
3989
3989
|
if (!anchor || anchor.length < 3) {
|
|
3990
3990
|
throw new CompileError(
|
|
3991
|
-
`bone "${spec.name}" takes its rotation from anchor "${key}", which has no third element (a screen-space facing angle)`,
|
|
3991
|
+
`bone "${spec.name}" takes its rotation from anchor "${key}", which has no third element (a screen-space facing angle)`, 'CE_COMPILE_BONE_ANCHOR_ROTATION_MISSING',
|
|
3992
3992
|
);
|
|
3993
3993
|
}
|
|
3994
3994
|
return screenToSpineDegrees(anchor[2]);
|
|
@@ -4124,7 +4124,7 @@ function refuseDefaultSkinContest(slotName: string, placeholder: string, skins:
|
|
|
4124
4124
|
`"Multiple attachments have the same name: ${placeholder} ${placeholder}", because a default-skin attachment ` +
|
|
4125
4125
|
"hangs on the slot beside the named skins' placeholder of that name. Move the default skin's entry for this " +
|
|
4126
4126
|
`slot into a named skin — call it "base" — so every skin filling "${placeholder}" is a named one. Two or more ` +
|
|
4127
|
-
'named skins sharing a placeholder is the shape the editor exports itself, and it needs no `name` (#796).',
|
|
4127
|
+
'named skins sharing a placeholder is the shape the editor exports itself, and it needs no `name` (#796).', 'CE_COMPILE_DEFAULT_SKIN_PLACEHOLDER_CONFLICT',
|
|
4128
4128
|
);
|
|
4129
4129
|
}
|
|
4130
4130
|
|
|
@@ -4304,7 +4304,7 @@ function buildRigAttachment(
|
|
|
4304
4304
|
`is one of ${SPINE_ATTACHMENT_TYPES.join(', ')}, or the key is absent and reads as "region". ` +
|
|
4305
4305
|
'PRESENT-and-null is not absent: `getValue(map, "type", "region")` takes the default only when the key is ' +
|
|
4306
4306
|
'missing (`SkeletonJson.ts:527`), so this map matches no case, `readAttachment` returns null (`:653`), and ' +
|
|
4307
|
-
'the attachment is dropped from the skeleton without a word. Remove the key, or name a type.',
|
|
4307
|
+
'the attachment is dropped from the skeleton without a word. Remove the key, or name a type.', 'CE_COMPILE_ATTACHMENT_TYPE_NOT_NAME',
|
|
4308
4308
|
);
|
|
4309
4309
|
}
|
|
4310
4310
|
const type = stated ?? 'region';
|
|
@@ -4319,7 +4319,7 @@ function buildRigAttachment(
|
|
|
4319
4319
|
`${where}: "name" is ${JSON.stringify(statedName) ?? String(statedName)}, which is not a string. An ` +
|
|
4320
4320
|
"attachment's name is the runtime's `Attachment.name` — `getValue(map, \"name\", placeholder)`, " +
|
|
4321
4321
|
'`SkeletonJson.js:526` — and on a region, mesh or linked mesh it is also what `path` defaults to. Write it ' +
|
|
4322
|
-
'as a string, or leave the key out and the attachment is named by its placeholder.',
|
|
4322
|
+
'as a string, or leave the key out and the attachment is named by its placeholder.', 'CE_COMPILE_ATTACHMENT_NAME_NOT_STRING',
|
|
4323
4323
|
);
|
|
4324
4324
|
}
|
|
4325
4325
|
// A linked mesh in the format's OTHER spelling. `type: "mesh"` and `type:
|
|
@@ -4337,7 +4337,7 @@ function buildRigAttachment(
|
|
|
4337
4337
|
if (type === 'boundingbox') return buildRigBoundingBox(att as RigBoundingBoxAttachment, where, ctx);
|
|
4338
4338
|
if (type === 'clipping') return buildRigClipping(att as RigClippingAttachment, where, ctx);
|
|
4339
4339
|
if (type === 'path') return buildRigPath(att as RigPathAttachment, where, ctx);
|
|
4340
|
-
if (DEFERRED_ATTACHMENTS[type] !== undefined) throw new NotImplementedError(deferredAttachmentRefusal(type, where, ''));
|
|
4340
|
+
if (DEFERRED_ATTACHMENTS[type] !== undefined) throw new NotImplementedError(deferredAttachmentRefusal(type, where, ''), 'CE_COMPILE_ATTACHMENT_TYPE_DEFERRED');
|
|
4341
4341
|
const near = nearMisses(type, SPINE_ATTACHMENT_TYPES);
|
|
4342
4342
|
throw new CompileError(
|
|
4343
4343
|
`${where}: attachment type "${type}" is not one of the ${SPINE_ATTACHMENT_TYPES.length} the Spine 4.3 format ` +
|
|
@@ -4349,7 +4349,7 @@ function buildRigAttachment(
|
|
|
4349
4349
|
// this format standing one level up from where it was written.
|
|
4350
4350
|
(type === 'sequence'
|
|
4351
4351
|
? ' "sequence" is a KEY on a region or a mesh rather than a type of its own (docs/SPEC_COVERAGE.md part 1-6).'
|
|
4352
|
-
: ''),
|
|
4352
|
+
: ''), 'CE_COMPILE_ATTACHMENT_TYPE_UNKNOWN',
|
|
4353
4353
|
);
|
|
4354
4354
|
}
|
|
4355
4355
|
|
|
@@ -4386,26 +4386,26 @@ function buildVertexGeometry(att: RigVertexGeometry, where: string, ctx: Attachm
|
|
|
4386
4386
|
if (typeof count !== 'number' || !Number.isInteger(count) || count < 3) {
|
|
4387
4387
|
throw new CompileError(
|
|
4388
4388
|
`${where}: vertexCount is ${JSON.stringify(count)}; a polygon needs at least 3 vertices, stated outright — ` +
|
|
4389
|
-
'the field has no parser default, and an absent one reads as 0 and takes the polygon with it',
|
|
4389
|
+
'the field has no parser default, and an absent one reads as 0 and takes the polygon with it', 'CE_COMPILE_POLYGON_VERTEX_COUNT_INVALID',
|
|
4390
4390
|
);
|
|
4391
4391
|
}
|
|
4392
4392
|
if (att.vertices && att.weights) {
|
|
4393
4393
|
throw new CompileError(
|
|
4394
|
-
`${where}: geometry comes as "vertices" or as "weights", never both — "weights" is the by-name form of the same data`,
|
|
4394
|
+
`${where}: geometry comes as "vertices" or as "weights", never both — "weights" is the by-name form of the same data`, 'CE_COMPILE_VERTEX_GEOMETRY_SOURCES_CONFLICT',
|
|
4395
4395
|
);
|
|
4396
4396
|
}
|
|
4397
4397
|
if (att.weights) {
|
|
4398
4398
|
if (att.boneIndexing === 'raw') {
|
|
4399
|
-
throw new CompileError(`${where}: "boneIndexing": "raw" describes a "vertices" run; "weights" always binds by name
|
|
4399
|
+
throw new CompileError(`${where}: "boneIndexing": "raw" describes a "vertices" run; "weights" always binds by name`, 'CE_COMPILE_NAMED_WEIGHTS_RAW_INDEXING');
|
|
4400
4400
|
}
|
|
4401
4401
|
if (att.weights.length !== count) {
|
|
4402
|
-
throw new CompileError(`${where}: weights cover ${att.weights.length} vertices but vertexCount is ${count}
|
|
4402
|
+
throw new CompileError(`${where}: weights cover ${att.weights.length} vertices but vertexCount is ${count}`, 'CE_COMPILE_POLYGON_WEIGHT_VERTEX_COUNT');
|
|
4403
4403
|
}
|
|
4404
4404
|
return { weighted: true, bindings: bindNamedWeights(att.weights, where, ctx) };
|
|
4405
4405
|
}
|
|
4406
4406
|
const raw = att.vertices;
|
|
4407
4407
|
if (!raw || raw.length === 0) {
|
|
4408
|
-
throw new CompileError(`${where}: needs geometry — "vertices" (x, y per vertex) or "weights" (bound by name)
|
|
4408
|
+
throw new CompileError(`${where}: needs geometry — "vertices" (x, y per vertex) or "weights" (bound by name)`, 'CE_COMPILE_POLYGON_GEOMETRY_MISSING');
|
|
4409
4409
|
}
|
|
4410
4410
|
const unweighted = raw.length === count * 2;
|
|
4411
4411
|
if (!unweighted) {
|
|
@@ -4415,7 +4415,7 @@ function buildVertexGeometry(att: RigVertexGeometry, where: string, ctx: Attachm
|
|
|
4415
4415
|
'The parser reads that as a WEIGHTED run, whose bone INDEXES point into the emitted bone array — a list ' +
|
|
4416
4416
|
'the spec never writes, so inserting a bone rebinds every vertex in silence. ' +
|
|
4417
4417
|
'Give the bindings by name as "weights": [[{ "bone": …, "x": …, "y": …, "weight": … }, …], …], ' +
|
|
4418
|
-
'fix vertexCount, or say "boneIndexing": "raw" on this attachment to keep the index form deliberately.',
|
|
4418
|
+
'fix vertexCount, or say "boneIndexing": "raw" on this attachment to keep the index form deliberately.', 'CE_COMPILE_POLYGON_WEIGHTED_RUN_IMPLICIT',
|
|
4419
4419
|
);
|
|
4420
4420
|
}
|
|
4421
4421
|
// A raw run still has to decode to exactly `vertexCount` vertices, or the
|
|
@@ -4423,7 +4423,7 @@ function buildVertexGeometry(att: RigVertexGeometry, where: string, ctx: Attachm
|
|
|
4423
4423
|
checkRawRunShape(raw, count, `vertexCount is ${count}`, where);
|
|
4424
4424
|
}
|
|
4425
4425
|
for (const n of raw) {
|
|
4426
|
-
if (!Number.isFinite(n)) throw new CompileError(`${where}: the vertex array holds a non-finite value ${String(n)}
|
|
4426
|
+
if (!Number.isFinite(n)) throw new CompileError(`${where}: the vertex array holds a non-finite value ${String(n)}`, 'CE_COMPILE_VERTEX_VALUE_NONFINITE');
|
|
4427
4427
|
}
|
|
4428
4428
|
if (unweighted) return { weighted: false, xy: raw.map(f32) };
|
|
4429
4429
|
const bindings = bindRawRun(raw.map(f32), ctx.bones, where);
|
|
@@ -4442,17 +4442,17 @@ function checkRawRunShape(raw: readonly number[], vertices: number, countSays: s
|
|
|
4442
4442
|
for (let i = 0; i < raw.length; decoded++) {
|
|
4443
4443
|
const bones = raw[i++];
|
|
4444
4444
|
if (!Number.isInteger(bones) || bones < 1) {
|
|
4445
|
-
throw new CompileError(`${where}: the raw weighted run has a bone count of ${String(bones)} at index ${i - 1}
|
|
4445
|
+
throw new CompileError(`${where}: the raw weighted run has a bone count of ${String(bones)} at index ${i - 1}`, 'CE_COMPILE_RAW_WEIGHT_BONE_COUNT_INVALID');
|
|
4446
4446
|
}
|
|
4447
4447
|
i += bones * 4;
|
|
4448
4448
|
if (i > raw.length) {
|
|
4449
4449
|
throw new CompileError(
|
|
4450
|
-
`${where}: the raw weighted run is truncated — vertex ${decoded} claims ${bones} bone(s) and the array ends first`,
|
|
4450
|
+
`${where}: the raw weighted run is truncated — vertex ${decoded} claims ${bones} bone(s) and the array ends first`, 'CE_COMPILE_RAW_WEIGHT_RUN_TRUNCATED',
|
|
4451
4451
|
);
|
|
4452
4452
|
}
|
|
4453
4453
|
}
|
|
4454
4454
|
if (decoded !== vertices) {
|
|
4455
|
-
throw new CompileError(`${where}: the raw weighted run decodes to ${decoded} vertices but ${countSays}
|
|
4455
|
+
throw new CompileError(`${where}: the raw weighted run decodes to ${decoded} vertices but ${countSays}`, 'CE_COMPILE_RAW_WEIGHT_VERTEX_COUNT_MISMATCH');
|
|
4456
4456
|
}
|
|
4457
4457
|
}
|
|
4458
4458
|
|
|
@@ -4475,7 +4475,7 @@ function bindRawRun(run: readonly number[], bones: readonly ModelBone[], where:
|
|
|
4475
4475
|
for (let k = 0; k < count; k++, i += 4) {
|
|
4476
4476
|
const bone = bones[run[i]];
|
|
4477
4477
|
if (bone === undefined) {
|
|
4478
|
-
throw new CompileError(`${where}: vertex ${out.length} binds bone index ${run[i]}, which is not in the bone list
|
|
4478
|
+
throw new CompileError(`${where}: vertex ${out.length} binds bone index ${run[i]}, which is not in the bone list`, 'CE_COMPILE_RAW_WEIGHT_BONE_INDEX_UNKNOWN');
|
|
4479
4479
|
}
|
|
4480
4480
|
vertex.push({ bone: bone.name, x: run[i + 1], y: run[i + 2], weight: run[i + 3] });
|
|
4481
4481
|
}
|
|
@@ -4676,13 +4676,13 @@ function resolveStageBox(
|
|
|
4676
4676
|
if (asked === undefined) return null;
|
|
4677
4677
|
const field = `skeleton.stageBox (slot "${asked.slot}", attachment "${asked.attachment}")`;
|
|
4678
4678
|
if (ctx.stageWidth === undefined || ctx.stageHeight === undefined) {
|
|
4679
|
-
throw new CompileError(`${field}: the rig declares no stage, so there is no box to write — state skeleton.width and skeleton.height
|
|
4679
|
+
throw new CompileError(`${field}: the rig declares no stage, so there is no box to write — state skeleton.width and skeleton.height`, 'CE_COMPILE_STAGE_BOX_STAGE_MISSING');
|
|
4680
4680
|
}
|
|
4681
4681
|
const rigSlot = rig.slots.find((s) => s.name === asked.slot);
|
|
4682
4682
|
if (rigSlot === undefined) {
|
|
4683
4683
|
throw new CompileError(
|
|
4684
4684
|
`${field}: slot "${asked.slot}" is not one the rig declares. Declare it in "slots" — where it stands in the ` +
|
|
4685
|
-
`array is its place in the draw order, and its "bone" is the box's bone — e.g. { "name": "${asked.slot}", "bone": "root", "attachment": "${asked.attachment}" }`,
|
|
4685
|
+
`array is its place in the draw order, and its "bone" is the box's bone — e.g. { "name": "${asked.slot}", "bone": "root", "attachment": "${asked.attachment}" }`, 'CE_COMPILE_STAGE_BOX_SLOT_UNKNOWN',
|
|
4686
4686
|
);
|
|
4687
4687
|
}
|
|
4688
4688
|
const filledBy = [
|
|
@@ -4694,13 +4694,13 @@ function resolveStageBox(
|
|
|
4694
4694
|
if (filledBy.length > 0) {
|
|
4695
4695
|
throw new CompileError(
|
|
4696
4696
|
`${field}: slot "${asked.slot}" is also filled by ${filledBy.join(' and ')}. The stage box is its slot's one ` +
|
|
4697
|
-
'attachment, so a reader of that slot finds the stage and nothing else: give the box a slot of its own',
|
|
4697
|
+
'attachment, so a reader of that slot finds the stage and nothing else: give the box a slot of its own', 'CE_COMPILE_STAGE_BOX_SLOT_CONFLICT',
|
|
4698
4698
|
);
|
|
4699
4699
|
}
|
|
4700
4700
|
// The box's bone: the root, stating no setup transform — as built, so a root placed `from` the manifest is read as placed.
|
|
4701
4701
|
const bone = ctx.bones.find((b) => b.name === rigSlot.bone);
|
|
4702
4702
|
if (bone === undefined) {
|
|
4703
|
-
throw new CompileError(`${field}: slot "${asked.slot}" names bone "${rigSlot.bone}", which the rig does not declare
|
|
4703
|
+
throw new CompileError(`${field}: slot "${asked.slot}" names bone "${rigSlot.bone}", which the rig does not declare`, 'CE_COMPILE_STAGE_BOX_BONE_UNKNOWN');
|
|
4704
4704
|
}
|
|
4705
4705
|
if (bone.parent !== undefined) {
|
|
4706
4706
|
throw new CompileError(
|
|
@@ -4708,7 +4708,7 @@ function resolveStageBox(
|
|
|
4708
4708
|
"the stage's corners in world space, written as they are, and the gate holds them on the header's grid " +
|
|
4709
4709
|
"through the runtime's own frame of an unrotated root; every bone below it adds that frame's residue once " +
|
|
4710
4710
|
"more (the runtime spells an unrotated b as cos 90° at its pi, -2.3e-8 per level). Put the slot on the root " +
|
|
4711
|
-
`("${ctx.bones[0]?.name ?? 'root'}")`,
|
|
4711
|
+
`("${ctx.bones[0]?.name ?? 'root'}")`, 'CE_COMPILE_STAGE_BOX_BONE_NOT_ROOT',
|
|
4712
4712
|
);
|
|
4713
4713
|
}
|
|
4714
4714
|
const stated = [
|
|
@@ -4719,7 +4719,7 @@ function resolveStageBox(
|
|
|
4719
4719
|
throw new CompileError(
|
|
4720
4720
|
`${field}: slot "${asked.slot}" hangs on the root "${bone.name}", which states a setup transform (${stated.join(', ')}). ` +
|
|
4721
4721
|
"The box's vertices are the stage's corners in world space, written as they are, so the root must not move, " +
|
|
4722
|
-
'turn, scale or shear at setup — move what the root carries onto a bone below it, or drop stageBox',
|
|
4722
|
+
'turn, scale or shear at setup — move what the root carries onto a bone below it, or drop stageBox', 'CE_COMPILE_STAGE_BOX_ROOT_TRANSFORMED',
|
|
4723
4723
|
);
|
|
4724
4724
|
}
|
|
4725
4725
|
const x = rig.skeleton?.x ?? 0;
|
|
@@ -4760,7 +4760,7 @@ function buildRigClipping(
|
|
|
4760
4760
|
if (typeof att.end !== 'string' || !ctx.slotNames.has(att.end)) {
|
|
4761
4761
|
throw new CompileError(
|
|
4762
4762
|
`${where}: end names slot ${JSON.stringify(att.end)}, which this rig does not declare; ` +
|
|
4763
|
-
'a miss loads as null and the clip then runs to the bottom of the draw order',
|
|
4763
|
+
'a miss loads as null and the clip then runs to the bottom of the draw order', 'CE_COMPILE_CLIPPING_END_SLOT_UNKNOWN',
|
|
4764
4764
|
);
|
|
4765
4765
|
}
|
|
4766
4766
|
}
|
|
@@ -4804,7 +4804,7 @@ function setupWorldVertices(
|
|
|
4804
4804
|
let y = 0;
|
|
4805
4805
|
for (const binding of vertex) {
|
|
4806
4806
|
const m = transforms.get(binding.bone);
|
|
4807
|
-
if (!m) throw new CompileError(`${where}: vertex ${out.length} binds bone "${binding.bone}", which has no setup transform
|
|
4807
|
+
if (!m) throw new CompileError(`${where}: vertex ${out.length} binds bone "${binding.bone}", which has no setup transform`, 'CE_COMPILE_VERTEX_BIND_TRANSFORM_MISSING');
|
|
4808
4808
|
const [wx, wy] = toWorld(m, binding.x, binding.y);
|
|
4809
4809
|
x += wx * binding.weight;
|
|
4810
4810
|
y += wy * binding.weight;
|
|
@@ -4947,14 +4947,14 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
4947
4947
|
throw new CompileError(
|
|
4948
4948
|
`${where}: vertexCount is ${count}, which is not a multiple of 3. A path's vertices are knots AND their ` +
|
|
4949
4949
|
'Bezier handles, read in groups of three; the parser sizes its lengths array with `vertexCount / 3` and ' +
|
|
4950
|
-
'accepts a fractional size without a word, so the curves would straddle the knots.',
|
|
4950
|
+
'accepts a fractional size without a word, so the curves would straddle the knots.', 'CE_COMPILE_PATH_VERTEX_COUNT_MULTIPLE',
|
|
4951
4951
|
);
|
|
4952
4952
|
}
|
|
4953
4953
|
const minimum = closed ? 3 : 6;
|
|
4954
4954
|
if (count < minimum) {
|
|
4955
4955
|
throw new CompileError(
|
|
4956
4956
|
`${where}: vertexCount is ${count} and ${closed ? 'a closed' : 'an open'} path needs at least ${minimum} — ` +
|
|
4957
|
-
`${closed ? 'a closed path of K curves carries 3K points' : 'an open one carries 3(K + 1), the first and last being the end knots\' outer handles'}`,
|
|
4957
|
+
`${closed ? 'a closed path of K curves carries 3K points' : 'an open one carries 3(K + 1), the first and last being the end knots\' outer handles'}`, 'CE_COMPILE_PATH_VERTEX_COUNT_MINIMUM',
|
|
4958
4958
|
);
|
|
4959
4959
|
}
|
|
4960
4960
|
const entries = count / 3;
|
|
@@ -4963,7 +4963,7 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
4963
4963
|
lengths = statedPathLengths(att.lengths, entries, closed, where);
|
|
4964
4964
|
} else {
|
|
4965
4965
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
4966
|
-
if (!anchor) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform
|
|
4966
|
+
if (!anchor) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform`, 'CE_COMPILE_SLOT_SETUP_TRANSFORM_MISSING');
|
|
4967
4967
|
const points = setupWorldVertices(vertices, anchor, ctx.transforms, where);
|
|
4968
4968
|
lengths = pathCurveLengths(pathChain(points, true));
|
|
4969
4969
|
// The total the runtime reads is the last CURVE's entry, `lengths[vertexCount
|
|
@@ -4973,7 +4973,7 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
4973
4973
|
if (lengths.length !== entries || !lengths.every((n) => Number.isFinite(n)) || !(total > 0)) {
|
|
4974
4974
|
throw new CompileError(
|
|
4975
4975
|
`${where}: the geometry measures ${lengths.length} entry(ies) where the parser sizes ${entries}, of total ` +
|
|
4976
|
-
`length ${String(total)}; a path of zero length divides by zero the first time a bone is placed on it`,
|
|
4976
|
+
`length ${String(total)}; a path of zero length divides by zero the first time a bone is placed on it`, 'CE_COMPILE_PATH_GEOMETRY_LENGTH_INVALID',
|
|
4977
4977
|
);
|
|
4978
4978
|
}
|
|
4979
4979
|
}
|
|
@@ -5009,25 +5009,25 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
5009
5009
|
*/
|
|
5010
5010
|
function statedPathLengths(stated: unknown, entries: number, closed: boolean, where: string): number[] {
|
|
5011
5011
|
if (!Array.isArray(stated)) {
|
|
5012
|
-
throw new CompileError(`${where}: "lengths" is ${JSON.stringify(stated)}; it is an array of ${entries} number(s)
|
|
5012
|
+
throw new CompileError(`${where}: "lengths" is ${JSON.stringify(stated)}; it is an array of ${entries} number(s)`, 'CE_COMPILE_PATH_LENGTHS_NOT_ARRAY');
|
|
5013
5013
|
}
|
|
5014
5014
|
if (stated.length !== entries) {
|
|
5015
5015
|
throw new CompileError(
|
|
5016
5016
|
`${where}: "lengths" has ${stated.length} entry(ies) where the parser sizes ${entries} — vertexCount / 3 on ` +
|
|
5017
5017
|
`${closed ? 'a closed' : 'an open'} path${closed ? '' : ', one more than its curves: the editor writes the wrap-around curve\'s cumulative last and nothing reads it'}. ` +
|
|
5018
|
-
'Give the array the source states, or leave the field out and rigc measures it',
|
|
5018
|
+
'Give the array the source states, or leave the field out and rigc measures it', 'CE_COMPILE_PATH_LENGTHS_COUNT',
|
|
5019
5019
|
);
|
|
5020
5020
|
}
|
|
5021
5021
|
let previous = 0;
|
|
5022
5022
|
for (let i = 0; i < stated.length; i++) {
|
|
5023
5023
|
const value: unknown = stated[i];
|
|
5024
5024
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
5025
|
-
throw new CompileError(`${where}: "lengths"[${i}] is ${JSON.stringify(value)}; every entry is a finite cumulative length
|
|
5025
|
+
throw new CompileError(`${where}: "lengths"[${i}] is ${JSON.stringify(value)}; every entry is a finite cumulative length`, 'CE_COMPILE_PATH_LENGTH_NONFINITE');
|
|
5026
5026
|
}
|
|
5027
5027
|
if (value < previous) {
|
|
5028
5028
|
throw new CompileError(
|
|
5029
5029
|
`${where}: "lengths"[${i}] is ${value}, below ${i === 0 ? '0' : `the ${previous} before it`}. The array is CUMULATIVE — the length at ` +
|
|
5030
|
-
'the end of each curve — so no entry is below its predecessor',
|
|
5030
|
+
'the end of each curve — so no entry is below its predecessor', 'CE_COMPILE_PATH_LENGTHS_DESCENDING',
|
|
5031
5031
|
);
|
|
5032
5032
|
}
|
|
5033
5033
|
previous = value;
|
|
@@ -5068,7 +5068,7 @@ function atlasedImage(image: string, where: string, ctx: AttachmentContext): Com
|
|
|
5068
5068
|
'measured and atlased — one per file, whichever skin names it (issue #555) — so reaching this means rigc ' +
|
|
5069
5069
|
'skipped one, not that the spec left anything out. ' +
|
|
5070
5070
|
(near.length ? `The nearest region(s) built are ${near.map((n) => JSON.stringify(n)).join(', ')}. ` : '') +
|
|
5071
|
-
`The atlas holds ${built.length} region(s): ${built.join(', ')}`,
|
|
5071
|
+
`The atlas holds ${built.length} region(s): ${built.join(', ')}`, 'CE_COMPILE_INTERNAL_IMAGE_NOT_ATLASED',
|
|
5072
5072
|
);
|
|
5073
5073
|
}
|
|
5074
5074
|
|
|
@@ -5215,7 +5215,7 @@ function sequenceFrameSize(
|
|
|
5215
5215
|
throw new CompileError(
|
|
5216
5216
|
`${where}: the ${frames.length} frames of this sequence measure ${sizes.join(', ')} in ${field} ` +
|
|
5217
5217
|
`(${frames.map((img) => `"${img.region}" ${img[field]}`).join(', ')}), and an attachment has one ` +
|
|
5218
|
-
`${field} every frame is drawn into. State "${field}" — the frames do not agree on one`,
|
|
5218
|
+
`${field} every frame is drawn into. State "${field}" — the frames do not agree on one`, 'CE_COMPILE_SEQUENCE_FRAME_SIZE_AMBIGUOUS',
|
|
5219
5219
|
);
|
|
5220
5220
|
}
|
|
5221
5221
|
}
|
|
@@ -5240,7 +5240,7 @@ function sequenceFrames(att: { path?: string; sequence?: RigSequence }, placehol
|
|
|
5240
5240
|
throw new CompileError(
|
|
5241
5241
|
`${where}: sequence frame ${i} is the region "${region}", which was never added to the atlas — every frame ` +
|
|
5242
5242
|
'of a sequence is atlased or refused by name before any attachment is built, so reaching this means rigc ' +
|
|
5243
|
-
'skipped one',
|
|
5243
|
+
'skipped one', 'CE_COMPILE_INTERNAL_SEQUENCE_FRAME_NOT_ATLASED',
|
|
5244
5244
|
);
|
|
5245
5245
|
}
|
|
5246
5246
|
frames.push(img);
|
|
@@ -5295,7 +5295,7 @@ function buildRigRegion(
|
|
|
5295
5295
|
? ''
|
|
5296
5296
|
: ` in the page's texels, which its scale: ${img.atlasScale} makes a ` +
|
|
5297
5297
|
`${img.width}x${img.height} drawing (+/- ${f32(0.5 / img.atlasScale)}px, the pack's rounding)`) +
|
|
5298
|
-
')',
|
|
5298
|
+
')', 'CE_COMPILE_REGION_ATLAS_SIZE_MISMATCH',
|
|
5299
5299
|
);
|
|
5300
5300
|
}
|
|
5301
5301
|
}
|
|
@@ -5306,7 +5306,7 @@ function buildRigRegion(
|
|
|
5306
5306
|
// No parser default: an omission loads as NaN and every UV collapses, with
|
|
5307
5307
|
// no error at all. So it is this or nothing.
|
|
5308
5308
|
throw new CompileError(
|
|
5309
|
-
`${where}: a region needs width and height — give them, or give an "image" and rigc will measure the PNG`,
|
|
5309
|
+
`${where}: a region needs width and height — give them, or give an "image" and rigc will measure the PNG`, 'CE_COMPILE_REGION_SIZE_MISSING',
|
|
5310
5310
|
);
|
|
5311
5311
|
}
|
|
5312
5312
|
const out: ModelRegionAttachment = { kind: 'region', width: f32(width), height: f32(height) };
|
|
@@ -5390,12 +5390,12 @@ function regionAtlasRect(
|
|
|
5390
5390
|
function bindNamedWeights(weights: RigMeshBinding[][], where: string, ctx: AttachmentContext): ModelBinding[][] {
|
|
5391
5391
|
return weights.map((vertex, i) => {
|
|
5392
5392
|
if (!Array.isArray(vertex) || vertex.length === 0) {
|
|
5393
|
-
throw new CompileError(`${where}: vertex ${i} has no bone bindings; a weighted vertex names at least one bone
|
|
5393
|
+
throw new CompileError(`${where}: vertex ${i} has no bone bindings; a weighted vertex names at least one bone`, 'CE_COMPILE_VERTEX_BINDINGS_EMPTY');
|
|
5394
5394
|
}
|
|
5395
5395
|
return vertex.map((binding) => {
|
|
5396
5396
|
if (!ctx.bones.some((b) => b.name === binding.bone)) {
|
|
5397
5397
|
throw new CompileError(
|
|
5398
|
-
`${where}: vertex ${i} binds bone ${JSON.stringify(binding.bone)}, which the rig does not declare as a bone`,
|
|
5398
|
+
`${where}: vertex ${i} binds bone ${JSON.stringify(binding.bone)}, which the rig does not declare as a bone`, 'CE_COMPILE_VERTEX_BIND_BONE_UNKNOWN',
|
|
5399
5399
|
);
|
|
5400
5400
|
}
|
|
5401
5401
|
return { bone: binding.bone, x: f32(binding.x), y: f32(binding.y), weight: f32(binding.weight) };
|
|
@@ -5513,7 +5513,7 @@ function authoredHullAndEdges(
|
|
|
5513
5513
|
checkHullOrder(outline, vertexCount);
|
|
5514
5514
|
return { hull: outline.hull, edges: att.edges ?? meshEdges(vertexCount, triangles, outline.hull) };
|
|
5515
5515
|
} catch (err) {
|
|
5516
|
-
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}${splitPointer(vertexCount, triangles, named)}
|
|
5516
|
+
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}${splitPointer(vertexCount, triangles, named)}`, 'CE_COMPILE_AUTHORED_MESH_OUTLINE_INVALID');
|
|
5517
5517
|
throw err;
|
|
5518
5518
|
}
|
|
5519
5519
|
}
|
|
@@ -5554,7 +5554,7 @@ function generatedHullAndEdges(geometry: MeshGeometry, where: string): { hull: n
|
|
|
5554
5554
|
checkHullOrder(outline, vertexCount);
|
|
5555
5555
|
} catch (err) {
|
|
5556
5556
|
if (err instanceof MeshError) {
|
|
5557
|
-
throw new CompileError(`${where}: internal: the ${geometry.kind} generator built an inconsistent mesh — ${err.message}
|
|
5557
|
+
throw new CompileError(`${where}: internal: the ${geometry.kind} generator built an inconsistent mesh — ${err.message}`, 'CE_COMPILE_INTERNAL_GENERATED_MESH_OUTLINE_INVALID');
|
|
5558
5558
|
}
|
|
5559
5559
|
throw err;
|
|
5560
5560
|
}
|
|
@@ -5570,27 +5570,27 @@ function buildRigMesh(
|
|
|
5570
5570
|
const authored =
|
|
5571
5571
|
att.uvs !== undefined || att.triangles !== undefined || att.vertices !== undefined || att.weights !== undefined;
|
|
5572
5572
|
if (authored && att.generator) {
|
|
5573
|
-
throw new CompileError(`${where}: a mesh is either authored geometry or a generator, never both
|
|
5573
|
+
throw new CompileError(`${where}: a mesh is either authored geometry or a generator, never both`, 'CE_COMPILE_MESH_AUTHORED_AND_GENERATED');
|
|
5574
5574
|
}
|
|
5575
5575
|
if (att.generator) return buildGeneratedMesh(att, att.generator, placeholder, where, ctx);
|
|
5576
5576
|
if (att.vertices && att.weights) {
|
|
5577
5577
|
throw new CompileError(
|
|
5578
|
-
`${where}: a mesh gives geometry as "vertices" or as "weights", never both — "weights" is the by-name form of the same data`,
|
|
5578
|
+
`${where}: a mesh gives geometry as "vertices" or as "weights", never both — "weights" is the by-name form of the same data`, 'CE_COMPILE_MESH_GEOMETRY_SOURCES_CONFLICT',
|
|
5579
5579
|
);
|
|
5580
5580
|
}
|
|
5581
5581
|
if (!att.uvs || !att.triangles || !(att.vertices || att.weights)) {
|
|
5582
|
-
throw new CompileError(`${where}: an authored mesh needs uvs, triangles and vertices or weights (or a "generator")
|
|
5582
|
+
throw new CompileError(`${where}: an authored mesh needs uvs, triangles and vertices or weights (or a "generator")`, 'CE_COMPILE_MESH_GEOMETRY_MISSING');
|
|
5583
5583
|
}
|
|
5584
5584
|
const uvCount = att.uvs.length;
|
|
5585
5585
|
let vertices: ModelVertices;
|
|
5586
5586
|
let boundBones: string[] = [];
|
|
5587
5587
|
if (att.weights) {
|
|
5588
5588
|
if (att.boneIndexing === 'raw') {
|
|
5589
|
-
throw new CompileError(`${where}: "boneIndexing": "raw" describes a "vertices" run; "weights" always binds by name
|
|
5589
|
+
throw new CompileError(`${where}: "boneIndexing": "raw" describes a "vertices" run; "weights" always binds by name`, 'CE_COMPILE_NAMED_WEIGHTS_RAW_INDEXING');
|
|
5590
5590
|
}
|
|
5591
5591
|
if (att.weights.length !== uvCount / 2) {
|
|
5592
5592
|
throw new CompileError(
|
|
5593
|
-
`${where}: weights cover ${att.weights.length} vertices but there are ${uvCount / 2} uv pairs`,
|
|
5593
|
+
`${where}: weights cover ${att.weights.length} vertices but there are ${uvCount / 2} uv pairs`, 'CE_COMPILE_MESH_WEIGHT_UV_COUNT',
|
|
5594
5594
|
);
|
|
5595
5595
|
}
|
|
5596
5596
|
vertices = { weighted: true, bindings: bindNamedWeights(att.weights, where, ctx) };
|
|
@@ -5605,7 +5605,7 @@ function buildRigMesh(
|
|
|
5605
5605
|
`${where}: this mesh's "vertices" is a weighted run, whose bone INDEXES point into the emitted bone array — ` +
|
|
5606
5606
|
'a list the spec never writes, so inserting a bone rebinds every vertex in silence. ' +
|
|
5607
5607
|
'Give the bindings by name as "weights": [[{ "bone": …, "x": …, "y": …, "weight": … }, …], …], ' +
|
|
5608
|
-
'or say "boneIndexing": "raw" on this attachment to keep the index form deliberately.',
|
|
5608
|
+
'or say "boneIndexing": "raw" on this attachment to keep the index form deliberately.', 'CE_COMPILE_MESH_WEIGHTED_RUN_IMPLICIT',
|
|
5609
5609
|
);
|
|
5610
5610
|
}
|
|
5611
5611
|
if (weighted) {
|
|
@@ -5635,7 +5635,7 @@ function buildRigMesh(
|
|
|
5635
5635
|
const height = seqSize !== null ? seqSize.height : (att.height ?? img?.height);
|
|
5636
5636
|
if (width === undefined || height === undefined) {
|
|
5637
5637
|
throw new CompileError(
|
|
5638
|
-
`${where}: a mesh needs width and height — give them, or give an "image" and rigc will measure the PNG`,
|
|
5638
|
+
`${where}: a mesh needs width and height — give them, or give an "image" and rigc will measure the PNG`, 'CE_COMPILE_MESH_SIZE_MISSING',
|
|
5639
5639
|
);
|
|
5640
5640
|
}
|
|
5641
5641
|
const out: ModelMeshAttachment = {
|
|
@@ -5714,7 +5714,7 @@ function buildRigLinkedMesh(
|
|
|
5714
5714
|
'no geometry of its own — it draws the geometry of the attachment "source" names. The parser returns from ' +
|
|
5715
5715
|
'the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so these keys are read by nothing ' +
|
|
5716
5716
|
"at all: a link declaring 5 uvs beside a 4-vertex source loads the SOURCE's 4 vertices and says nothing. " +
|
|
5717
|
-
`Remove ${geometry.length === 1 ? 'it' : 'them'}, or remove "source" and author this as a mesh of its own.`,
|
|
5717
|
+
`Remove ${geometry.length === 1 ? 'it' : 'them'}, or remove "source" and author this as a mesh of its own.`, 'CE_COMPILE_LINKED_MESH_GEOMETRY_IGNORED',
|
|
5718
5718
|
);
|
|
5719
5719
|
}
|
|
5720
5720
|
const source = att.source;
|
|
@@ -5724,7 +5724,7 @@ function buildRigLinkedMesh(
|
|
|
5724
5724
|
`attachment is filed under in its skin, not its "name"), and this one states ${JSON.stringify(source) ?? String(source)}. ` +
|
|
5725
5725
|
'"source" is what MAKES a mesh linked: `getValue(map, "source", null)` is falsy-tested ' +
|
|
5726
5726
|
'(`SkeletonJson.ts:582`), so an absent or empty one is read as an ordinary mesh, whose `uvs` this ' +
|
|
5727
|
-
'attachment does not have — the parser dereferences `map.uvs.length` and throws.',
|
|
5727
|
+
'attachment does not have — the parser dereferences `map.uvs.length` and throws.', 'CE_COMPILE_LINKED_MESH_SOURCE_MISSING',
|
|
5728
5728
|
);
|
|
5729
5729
|
}
|
|
5730
5730
|
if (att.slot !== undefined && !ctx.slotNames.has(att.slot)) {
|
|
@@ -5732,7 +5732,7 @@ function buildRigLinkedMesh(
|
|
|
5732
5732
|
`${where}: "slot" is ${JSON.stringify(att.slot)}, which the rig does not declare as a slot. It names where ` +
|
|
5733
5733
|
'the source lives and is resolved through `skeletonData.findSlot` (`SkeletonJson.ts:575`), which throws ' +
|
|
5734
5734
|
`\`Source mesh slot not found\` on a miss. The rig's slots are ${[...ctx.slotNames].sort().map((s) => JSON.stringify(s)).join(', ')}. ` +
|
|
5735
|
-
"Leave it out and the source is looked for in this attachment's own slot, which is the parser's default.",
|
|
5735
|
+
"Leave it out and the source is looked for in this attachment's own slot, which is the parser's default.", 'CE_COMPILE_LINKED_MESH_SLOT_UNKNOWN',
|
|
5736
5736
|
);
|
|
5737
5737
|
}
|
|
5738
5738
|
ctx.links.push({
|
|
@@ -5751,7 +5751,7 @@ function buildRigLinkedMesh(
|
|
|
5751
5751
|
const height = seqSize !== null ? seqSize.height : (att.height ?? img?.height);
|
|
5752
5752
|
if (width === undefined || height === undefined) {
|
|
5753
5753
|
throw new CompileError(
|
|
5754
|
-
`${where}: a linked mesh needs width and height — give them, or give an "image" and rigc will measure the PNG`,
|
|
5754
|
+
`${where}: a linked mesh needs width and height — give them, or give an "image" and rigc will measure the PNG`, 'CE_COMPILE_LINKED_MESH_SIZE_MISSING',
|
|
5755
5755
|
);
|
|
5756
5756
|
}
|
|
5757
5757
|
// The link in full — skin, slot and `timelines` as the parser resolves them.
|
|
@@ -5813,7 +5813,7 @@ function resolveLinkedMeshes(
|
|
|
5813
5813
|
`${link.where}: no "skin" is stated, so the parser looks for the source "${link.source}" in the default ` +
|
|
5814
5814
|
`skin — and this rig declares no default skin. The rig's skins are ${skinNames.map((s) => JSON.stringify(s)).join(', ')}. ` +
|
|
5815
5815
|
'State the skin the source is filed under. Left to the round trip this is a `TypeError` off the null ' +
|
|
5816
|
-
'`SkeletonData.defaultSkin`, which names neither this attachment nor where it was looking.',
|
|
5816
|
+
'`SkeletonData.defaultSkin`, which names neither this attachment nor where it was looking.', 'CE_COMPILE_LINKED_MESH_DEFAULT_SKIN_MISSING',
|
|
5817
5817
|
);
|
|
5818
5818
|
}
|
|
5819
5819
|
if (table === undefined) {
|
|
@@ -5821,7 +5821,7 @@ function resolveLinkedMeshes(
|
|
|
5821
5821
|
`${link.where}: "skin" is ${JSON.stringify(link.skin)}, and the rig declares no such skin. The rig's skins ` +
|
|
5822
5822
|
`are ${skinNames.map((s) => JSON.stringify(s)).join(', ')}. ` +
|
|
5823
5823
|
"Left to the round trip this is the runtime's `Skin not found`, which names neither this attachment nor " +
|
|
5824
|
-
'where it was looking.',
|
|
5824
|
+
'where it was looking.', 'CE_COMPILE_LINKED_MESH_SKIN_UNKNOWN',
|
|
5825
5825
|
);
|
|
5826
5826
|
}
|
|
5827
5827
|
const slot = table[link.slot];
|
|
@@ -5835,7 +5835,7 @@ function resolveLinkedMeshes(
|
|
|
5835
5835
|
`holds ${held.length === 0 ? 'no attachment at all' : `${held.length}: ${held.map((k) => JSON.stringify(k)).join(', ')}`}. ` +
|
|
5836
5836
|
'"source" is the PLACEHOLDER the source is filed under — `skin.getAttachment(slotIndex, source)` ' +
|
|
5837
5837
|
"(`SkeletonJson.ts:433`), whose table is keyed by the JSON key, not by the attachment's `name`. " +
|
|
5838
|
-
"Left to the round trip this is the runtime's `Source mesh not found`.",
|
|
5838
|
+
"Left to the round trip this is the runtime's `Source mesh not found`.", 'CE_COMPILE_LINKED_MESH_SOURCE_UNKNOWN',
|
|
5839
5839
|
);
|
|
5840
5840
|
}
|
|
5841
5841
|
const type = found.kind;
|
|
@@ -5845,7 +5845,7 @@ function resolveLinkedMeshes(
|
|
|
5845
5845
|
'them is refused. Measured through spine-core: the runtime resolves links in the order they were read, ' +
|
|
5846
5846
|
"so a link whose source is a link loads the source's geometry when the source comes first in the file " +
|
|
5847
5847
|
'and loads NOTHING — `worldVerticesLength` 0, 0 triangles, a 0x0 size — when the two are swapped, in ' +
|
|
5848
|
-
'silence either way. Point "source" at the mesh itself.',
|
|
5848
|
+
'silence either way. Point "source" at the mesh itself.', 'CE_COMPILE_LINKED_MESH_SOURCE_IS_LINK',
|
|
5849
5849
|
);
|
|
5850
5850
|
}
|
|
5851
5851
|
if (type !== 'mesh') {
|
|
@@ -5853,7 +5853,7 @@ function resolveLinkedMeshes(
|
|
|
5853
5853
|
`${link.where}: "source" is ${JSON.stringify(link.source)}, which is a ${JSON.stringify(type)} attachment ` +
|
|
5854
5854
|
"and not a mesh. A linked mesh takes another MESH's `uvs`, `triangles`, `vertices`, `hull` and `edges` " +
|
|
5855
5855
|
'(`MeshAttachment.setSourceMesh`); the runtime casts whatever it finds and reads those fields off it, ' +
|
|
5856
|
-
'which on any other type is `undefined` and no error.',
|
|
5856
|
+
'which on any other type is `undefined` and no error.', 'CE_COMPILE_LINKED_MESH_SOURCE_NOT_MESH',
|
|
5857
5857
|
);
|
|
5858
5858
|
}
|
|
5859
5859
|
}
|
|
@@ -5885,9 +5885,9 @@ function buildGeneratedMesh(
|
|
|
5885
5885
|
// Two readings of one bone (issue #1021): the runtime's matrices to bind
|
|
5886
5886
|
// through, and the exact frame to measure the control angles in.
|
|
5887
5887
|
const resolve = (name: string, frames: Map<string, BoneTransform> = ctx.transforms): BoneTransform => {
|
|
5888
|
-
if (!ctx.bones.some((b) => b.name === name)) throw new CompileError(`${where}: mesh bone "${name}" is not in the rig's bone list
|
|
5888
|
+
if (!ctx.bones.some((b) => b.name === name)) throw new CompileError(`${where}: mesh bone "${name}" is not in the rig's bone list`, 'CE_COMPILE_MESH_BONE_UNKNOWN');
|
|
5889
5889
|
const transform = frames.get(name);
|
|
5890
|
-
if (!transform) throw new CompileError(`${where}: no setup transform for mesh bone "${name}"
|
|
5890
|
+
if (!transform) throw new CompileError(`${where}: no setup transform for mesh bone "${name}"`, 'CE_COMPILE_MESH_BONE_TRANSFORM_MISSING');
|
|
5891
5891
|
return transform;
|
|
5892
5892
|
};
|
|
5893
5893
|
const refFor = (name: string): MeshBoneRef => {
|
|
@@ -5901,7 +5901,7 @@ function buildGeneratedMesh(
|
|
|
5901
5901
|
// angle about the aperture means anything (issue #684).
|
|
5902
5902
|
const [w, h] = generator.size;
|
|
5903
5903
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
5904
|
-
if (!anchor) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform
|
|
5904
|
+
if (!anchor) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform`, 'CE_COMPILE_SLOT_SETUP_TRANSFORM_MISSING');
|
|
5905
5905
|
const place = (px: number, py: number): [number, number] => [anchor.worldX + px - w / 2, anchor.worldY + h / 2 - py];
|
|
5906
5906
|
// The inverse a control angle is measured through, in the exact frame: the
|
|
5907
5907
|
// anchor's and the control's positions as the spec put them, not as the
|
|
@@ -5925,7 +5925,7 @@ function buildGeneratedMesh(
|
|
|
5925
5925
|
}),
|
|
5926
5926
|
});
|
|
5927
5927
|
} catch (err) {
|
|
5928
|
-
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}
|
|
5928
|
+
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}`, 'CE_COMPILE_RING_RIBBON_GENERATOR_INVALID');
|
|
5929
5929
|
throw err;
|
|
5930
5930
|
}
|
|
5931
5931
|
const vertices = bindWeightedVertices(
|
|
@@ -6068,12 +6068,12 @@ function sampleMeshDepth(
|
|
|
6068
6068
|
summary: NonNullable<CompileResult['meshes'][number]['depth']>;
|
|
6069
6069
|
} {
|
|
6070
6070
|
if (typeof spec.image !== 'string' || spec.image.length === 0) {
|
|
6071
|
-
throw new CompileError(`${where}: the depth block has no "image"; it is the sheet to sample, relative to the rig's images directory
|
|
6071
|
+
throw new CompileError(`${where}: the depth block has no "image"; it is the sheet to sample, relative to the rig's images directory`, 'CE_COMPILE_DEPTH_IMAGE_MISSING');
|
|
6072
6072
|
}
|
|
6073
6073
|
if (spec.near !== 'white' && spec.near !== 'black') {
|
|
6074
6074
|
throw new CompileError(
|
|
6075
6075
|
`${where}: the depth block says "near": ${JSON.stringify(spec.near)}; it is "white" or "black". Both ` +
|
|
6076
|
-
'conventions are in use, and a sheet read with the wrong one turns the part inside out with every gate green.',
|
|
6076
|
+
'conventions are in use, and a sheet read with the wrong one turns the part inside out with every gate green.', 'CE_COMPILE_DEPTH_NEAR_INVALID',
|
|
6077
6077
|
);
|
|
6078
6078
|
}
|
|
6079
6079
|
const tone: DepthTone = {
|
|
@@ -6085,7 +6085,7 @@ function sampleMeshDepth(
|
|
|
6085
6085
|
checkTone(tone, where);
|
|
6086
6086
|
checkZScale(spec.zScale, where);
|
|
6087
6087
|
} catch (err) {
|
|
6088
|
-
if (err instanceof DepthError) throw new CompileError(err.message);
|
|
6088
|
+
if (err instanceof DepthError) throw new CompileError(err.message, 'CE_COMPILE_DEPTH_TONE_SCALE_INVALID');
|
|
6089
6089
|
throw err;
|
|
6090
6090
|
}
|
|
6091
6091
|
|
|
@@ -6157,7 +6157,7 @@ function sampleMeshDepth(
|
|
|
6157
6157
|
: ` in the page's texels, pixel (${f32(points[first][0] * toSheetX)}, ${f32(points[first][1] * toSheetY)}) of ` +
|
|
6158
6158
|
'the drawing-sized sheet') +
|
|
6159
6159
|
`. ${why} ` +
|
|
6160
|
-
'Sampling anyway would give those vertices the sheet\'s background depth and fold them away from the turn.',
|
|
6160
|
+
'Sampling anyway would give those vertices the sheet\'s background depth and fold them away from the turn.', 'CE_COMPILE_DEPTH_COVERAGE_INCOMPLETE',
|
|
6161
6161
|
);
|
|
6162
6162
|
}
|
|
6163
6163
|
|
|
@@ -6196,8 +6196,8 @@ function sampleMeshDepth(
|
|
|
6196
6196
|
/** One bone of the rig as `bindWeightedVertices` wants it: by name, with its setup inverse. */
|
|
6197
6197
|
function meshBoneRef(name: string, where: string, ctx: AttachmentContext): MeshBoneRef {
|
|
6198
6198
|
const transform = ctx.transforms.get(name);
|
|
6199
|
-
if (!transform) throw new CompileError(`${where}: bone "${name}" has no setup transform
|
|
6200
|
-
if (!ctx.bones.some((b) => b.name === name)) throw new CompileError(`${where}: bone "${name}" is not in the rig's bone list
|
|
6199
|
+
if (!transform) throw new CompileError(`${where}: bone "${name}" has no setup transform`, 'CE_COMPILE_MESH_BONE_SETUP_TRANSFORM_MISSING');
|
|
6200
|
+
if (!ctx.bones.some((b) => b.name === name)) throw new CompileError(`${where}: bone "${name}" is not in the rig's bone list`, 'CE_COMPILE_MESH_BONE_NOT_DECLARED');
|
|
6201
6201
|
return { name, toBind: (wx, wy) => toBoneLocal(transform, wx, wy) };
|
|
6202
6202
|
}
|
|
6203
6203
|
|
|
@@ -6272,11 +6272,11 @@ function readGreySheet(
|
|
|
6272
6272
|
const sheetHeight = scaled === undefined ? grid.partHeight : scaled.height;
|
|
6273
6273
|
const noun = purpose === 'depth' ? 'depth map' : 'soft mask';
|
|
6274
6274
|
if (typeof image !== 'string' || image.length === 0) {
|
|
6275
|
-
throw new CompileError(`${where}: the "${purpose}" block has no image to read
|
|
6275
|
+
throw new CompileError(`${where}: the "${purpose}" block has no image to read`, 'CE_COMPILE_GREY_SHEET_IMAGE_MISSING');
|
|
6276
6276
|
}
|
|
6277
6277
|
const path = join(ctx.imagesDir, image);
|
|
6278
6278
|
if (!existsSync(path)) {
|
|
6279
|
-
throw new CompileError(`${where}: the ${noun} "${image}" is not at ${relative(process.cwd(), path)}
|
|
6279
|
+
throw new CompileError(`${where}: the ${noun} "${image}" is not at ${relative(process.cwd(), path)}`, 'CE_COMPILE_GREY_SHEET_IMAGE_NOT_FOUND');
|
|
6280
6280
|
}
|
|
6281
6281
|
const sheet = readPlate(path);
|
|
6282
6282
|
if (sheet.width !== sheetWidth || sheet.height !== sheetHeight) {
|
|
@@ -6290,7 +6290,7 @@ function readGreySheet(
|
|
|
6290
6290
|
`declares scale: ${scaled.scale}, which makes a texel ${f32(1 / scaled.scale)}px of the drawing. It is ` +
|
|
6291
6291
|
"sampled in the part's own pixel grid, which is the drawing's, at each vertex's texel position over " +
|
|
6292
6292
|
`that scale — so the sheet is ${scaled.width}x${scaled.height}, the size of the loose art, whatever the ` +
|
|
6293
|
-
'page holds. Resample the sheet, or point at the one that was made for this part.',
|
|
6293
|
+
'page holds. Resample the sheet, or point at the one that was made for this part.', 'CE_COMPILE_GREY_SHEET_SIZE_MISMATCH',
|
|
6294
6294
|
);
|
|
6295
6295
|
}
|
|
6296
6296
|
// One channel, proved rather than assumed.
|
|
@@ -6305,7 +6305,7 @@ function readGreySheet(
|
|
|
6305
6305
|
throw new CompileError(
|
|
6306
6306
|
`${where}: the ${noun} "${image}" is not greyscale — pixel (${i % sheet.width}, ${Math.floor(i / sheet.width)}) ` +
|
|
6307
6307
|
`is rgb(${r}, ${g}, ${b}). It is one channel; reading red out of a colour file would use whatever that ` +
|
|
6308
|
-
'channel happened to hold.',
|
|
6308
|
+
'channel happened to hold.', 'CE_COMPILE_GREY_SHEET_NOT_GREYSCALE',
|
|
6309
6309
|
);
|
|
6310
6310
|
}
|
|
6311
6311
|
level[i] = r;
|
|
@@ -6339,20 +6339,20 @@ function softRegionWeights(
|
|
|
6339
6339
|
): { weights: MeshVertexWeight[][]; levels: number[]; bone: string; mask: string; digest: string; carried: number; ramped: number } {
|
|
6340
6340
|
const { bone } = spec;
|
|
6341
6341
|
if (typeof bone !== 'string' || bone.length === 0) {
|
|
6342
|
-
throw new CompileError(`${where}: the "soft" block has no "bone"; it is the bone the soft region is carried by
|
|
6342
|
+
throw new CompileError(`${where}: the "soft" block has no "bone"; it is the bone the soft region is carried by`, 'CE_COMPILE_SOFT_BONE_MISSING');
|
|
6343
6343
|
}
|
|
6344
6344
|
if (!ctx.bones.some((b) => b.name === bone)) {
|
|
6345
6345
|
throw new CompileError(
|
|
6346
6346
|
`${where}: "soft" names bone "${bone}", which this rig does not declare. rigc binds a region to a bone that ` +
|
|
6347
6347
|
'already exists rather than creating one — a bone a physics constraint has to target is part of the ' +
|
|
6348
|
-
'skeleton, not a side effect of a mesh.',
|
|
6348
|
+
'skeleton, not a side effect of a mesh.', 'CE_COMPILE_SOFT_BONE_UNKNOWN',
|
|
6349
6349
|
);
|
|
6350
6350
|
}
|
|
6351
6351
|
if (bone === ctx.anchorBone) {
|
|
6352
6352
|
throw new CompileError(
|
|
6353
6353
|
`${where}: "soft" names "${bone}", which is this slot's own bone. Carrying the region with the bone the rest ` +
|
|
6354
6354
|
'of the mesh is already pinned to moves nothing — a soft region needs a bone that can move independently, ' +
|
|
6355
|
-
'which is what a physics constraint is put on.',
|
|
6355
|
+
'which is what a physics constraint is put on.', 'CE_COMPILE_SOFT_BONE_IS_ANCHOR',
|
|
6356
6356
|
);
|
|
6357
6357
|
}
|
|
6358
6358
|
const { map: sheet } = readGreySheet(spec.mask, 'soft', grid, where, ctx);
|
|
@@ -6377,7 +6377,7 @@ function softRegionWeights(
|
|
|
6377
6377
|
throw new CompileError(
|
|
6378
6378
|
`${where}: the soft mask "${spec.mask}" carries no vertex of this mesh — every one of its ${points.length} ` +
|
|
6379
6379
|
'vertices samples black. A region that moves nothing is a physics constraint with nothing on the end of it; ' +
|
|
6380
|
-
'check that the mask is painted where the mesh actually is, and that it is white where the art is soft.',
|
|
6380
|
+
'check that the mask is painted where the mesh actually is, and that it is white where the art is soft.', 'CE_COMPILE_SOFT_MASK_INERT',
|
|
6381
6381
|
);
|
|
6382
6382
|
}
|
|
6383
6383
|
return { weights, levels, bone, mask: spec.mask, digest: depthDigest(sheet), carried, ramped };
|
|
@@ -6409,7 +6409,7 @@ function buildGridAttachment(
|
|
|
6409
6409
|
if (att.image === undefined) {
|
|
6410
6410
|
throw new CompileError(
|
|
6411
6411
|
`${where}: a "grid" generator lays its lattice over the part's own window, so the attachment needs an ` +
|
|
6412
|
-
'"image" — there is nothing else here that says how big that window is',
|
|
6412
|
+
'"image" — there is nothing else here that says how big that window is', 'CE_COMPILE_GRID_IMAGE_MISSING',
|
|
6413
6413
|
);
|
|
6414
6414
|
}
|
|
6415
6415
|
const stated = generator.us !== undefined || generator.vs !== undefined;
|
|
@@ -6418,18 +6418,18 @@ function buildGridAttachment(
|
|
|
6418
6418
|
throw new CompileError(
|
|
6419
6419
|
`${where}: the "grid" generator states both positions ("us"/"vs") and a count ("cols"/"rows"). They are two ` +
|
|
6420
6420
|
'answers to where the columns go, and a spec carrying both leaves a reader unable to say which one built ' +
|
|
6421
|
-
'the mesh. Drop one — positions are the more precise of the two.',
|
|
6421
|
+
'the mesh. Drop one — positions are the more precise of the two.', 'CE_COMPILE_GRID_POSITIONS_AND_COUNT',
|
|
6422
6422
|
);
|
|
6423
6423
|
}
|
|
6424
6424
|
if (!stated && !even) {
|
|
6425
6425
|
throw new CompileError(
|
|
6426
6426
|
`${where}: the "grid" generator says neither where its columns go ("us"/"vs", fractions of the window) nor ` +
|
|
6427
|
-
'how many there are ("cols"/"rows"). A lattice is not a default.',
|
|
6427
|
+
'how many there are ("cols"/"rows"). A lattice is not a default.', 'CE_COMPILE_GRID_POSITION_AND_COUNT_MISSING',
|
|
6428
6428
|
);
|
|
6429
6429
|
}
|
|
6430
6430
|
const spread = (n: unknown, name: string): number[] => {
|
|
6431
6431
|
if (typeof n !== 'number' || !Number.isInteger(n) || n < 2) {
|
|
6432
|
-
throw new CompileError(`${where}: "${name}" is ${JSON.stringify(n)}; it is a whole number of at least 2
|
|
6432
|
+
throw new CompileError(`${where}: "${name}" is ${JSON.stringify(n)}; it is a whole number of at least 2`, 'CE_COMPILE_GRID_COUNT_INVALID');
|
|
6433
6433
|
}
|
|
6434
6434
|
// An even division spans the whole window. A lattice that should sit inside
|
|
6435
6435
|
// it states its own positions — which is what `us`/`vs` are for.
|
|
@@ -6440,7 +6440,7 @@ function buildGridAttachment(
|
|
|
6440
6440
|
if (us === undefined || vs === undefined) {
|
|
6441
6441
|
throw new CompileError(
|
|
6442
6442
|
`${where}: the "grid" generator states ${us === undefined ? '"vs"' : '"us"'} and not ` +
|
|
6443
|
-
`${us === undefined ? '"us"' : '"vs"'}; a lattice needs both axes`,
|
|
6443
|
+
`${us === undefined ? '"us"' : '"vs"'}; a lattice needs both axes`, 'CE_COMPILE_GRID_AXIS_MISSING',
|
|
6444
6444
|
);
|
|
6445
6445
|
}
|
|
6446
6446
|
|
|
@@ -6451,7 +6451,7 @@ function buildGridAttachment(
|
|
|
6451
6451
|
try {
|
|
6452
6452
|
geometry = buildGridMesh({ size: [plate.width, plate.height], us, vs });
|
|
6453
6453
|
} catch (err) {
|
|
6454
|
-
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}
|
|
6454
|
+
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}`, 'CE_COMPILE_GRID_GENERATOR_INVALID');
|
|
6455
6455
|
throw err;
|
|
6456
6456
|
}
|
|
6457
6457
|
|
|
@@ -6459,16 +6459,16 @@ function buildGridAttachment(
|
|
|
6459
6459
|
const h = img.height;
|
|
6460
6460
|
const toArt = w / plate.width;
|
|
6461
6461
|
if (att.width !== undefined && att.width !== w) {
|
|
6462
|
-
throw new CompileError(`${where}: the spec says width ${att.width} and "${att.image}" measures ${w}
|
|
6462
|
+
throw new CompileError(`${where}: the spec says width ${att.width} and "${att.image}" measures ${w}`, 'CE_COMPILE_GENERATED_IMAGE_WIDTH_MISMATCH');
|
|
6463
6463
|
}
|
|
6464
6464
|
if (att.height !== undefined && att.height !== h) {
|
|
6465
|
-
throw new CompileError(`${where}: the spec says height ${att.height} and "${att.image}" measures ${h}
|
|
6465
|
+
throw new CompileError(`${where}: the spec says height ${att.height} and "${att.image}" measures ${h}`, 'CE_COMPILE_GENERATED_IMAGE_HEIGHT_MISMATCH');
|
|
6466
6466
|
}
|
|
6467
6467
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
6468
6468
|
const frame = ctx.exactFrames.get(ctx.anchorBone);
|
|
6469
|
-
if (!anchor || !frame) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform
|
|
6469
|
+
if (!anchor || !frame) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform`, 'CE_COMPILE_SLOT_SETUP_TRANSFORM_MISSING');
|
|
6470
6470
|
if (!ctx.bones.some((b) => b.name === ctx.anchorBone)) {
|
|
6471
|
-
throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" is not in the rig's bone list
|
|
6471
|
+
throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" is not in the rig's bone list`, 'CE_COMPILE_SLOT_BONE_NOT_DECLARED');
|
|
6472
6472
|
}
|
|
6473
6473
|
// Sampled BEFORE the encode, because `bind` rewrites the weights the encode
|
|
6474
6474
|
// then writes out.
|
|
@@ -6559,21 +6559,21 @@ function segmentsFalloffOf(f: RigSegmentsFalloff, where: string): SegmentsFallof
|
|
|
6559
6559
|
if (typeof f.radius !== 'number' || !Number.isFinite(f.radius) || !(f.radius > 0)) {
|
|
6560
6560
|
throw new CompileError(
|
|
6561
6561
|
`${where}: "falloff.radius" is ${JSON.stringify(f.radius)}; it is added to every distance before the power is ` +
|
|
6562
|
-
"taken, a positive number of the part's pixels — at 0 a vertex on a segment would carry an infinite pull",
|
|
6562
|
+
"taken, a positive number of the part's pixels — at 0 a vertex on a segment would carry an infinite pull", 'CE_COMPILE_SEGMENTS_FALLOFF_RADIUS_INVALID',
|
|
6563
6563
|
);
|
|
6564
6564
|
}
|
|
6565
6565
|
const power = f.power ?? SEGMENTS_DEFAULTS.power;
|
|
6566
6566
|
if (typeof power !== 'number' || !Number.isFinite(power) || !(power > 0)) {
|
|
6567
6567
|
throw new CompileError(
|
|
6568
6568
|
`${where}: "falloff.power" is ${JSON.stringify(power)}; it is the exponent a pull falls off with distance by, ` +
|
|
6569
|
-
'a positive number — at 0 or below the far segment pulls as hard as the near one, or harder',
|
|
6569
|
+
'a positive number — at 0 or below the far segment pulls as hard as the near one, or harder', 'CE_COMPILE_SEGMENTS_FALLOFF_POWER_INVALID',
|
|
6570
6570
|
);
|
|
6571
6571
|
}
|
|
6572
6572
|
const maxBones = f.maxBones ?? SEGMENTS_DEFAULTS.maxBones;
|
|
6573
6573
|
if (typeof maxBones !== 'number' || !Number.isInteger(maxBones) || maxBones < 1) {
|
|
6574
6574
|
throw new CompileError(
|
|
6575
6575
|
`${where}: "falloff.maxBones" is ${JSON.stringify(maxBones)}; it is how many bones may pull one vertex, a ` +
|
|
6576
|
-
'whole number of at least 1',
|
|
6576
|
+
'whole number of at least 1', 'CE_COMPILE_SEGMENTS_FALLOFF_MAX_BONES_INVALID',
|
|
6577
6577
|
);
|
|
6578
6578
|
}
|
|
6579
6579
|
const minWeight = f.minWeight ?? SEGMENTS_DEFAULTS.minWeight;
|
|
@@ -6582,7 +6582,7 @@ function segmentsFalloffOf(f: RigSegmentsFalloff, where: string): SegmentsFallof
|
|
|
6582
6582
|
`${where}: "falloff.minWeight" is ${JSON.stringify(minWeight)}; it is the share under which a bone's pull ` +
|
|
6583
6583
|
`is dropped, so it lies in ${SEGMENTS_WEIGHT_STEP}..1 with 1 excluded — at 1 or above every share is dropped, ` +
|
|
6584
6584
|
`and under ${SEGMENTS_WEIGHT_STEP}, one step of the 6-decimal grid weights are written on, a kept binding ` +
|
|
6585
|
-
'could be written as 0',
|
|
6585
|
+
'could be written as 0', 'CE_COMPILE_SEGMENTS_FALLOFF_MIN_WEIGHT_INVALID',
|
|
6586
6586
|
);
|
|
6587
6587
|
}
|
|
6588
6588
|
return { power, radius: f.radius, maxBones, minWeight };
|
|
@@ -6603,12 +6603,12 @@ function segmentsInfluenceOf(
|
|
|
6603
6603
|
throw new CompileError(
|
|
6604
6604
|
`${where}: the "segments" generator states both "falloff" and "influence". Both turn a distance into a ` +
|
|
6605
6605
|
'share — "falloff" as 1 / (d + radius)^power over every segment, "influence" as a pull that reaches zero at ' +
|
|
6606
|
-
'its "radius" — and a mesh is weighted by one of them: remove the other',
|
|
6606
|
+
'its "radius" — and a mesh is weighted by one of them: remove the other', 'CE_COMPILE_SEGMENTS_WEIGHT_MODE_CONFLICT',
|
|
6607
6607
|
);
|
|
6608
6608
|
}
|
|
6609
6609
|
if (typeof spec !== 'object' || spec === null || Array.isArray(spec)) {
|
|
6610
6610
|
throw new CompileError(
|
|
6611
|
-
`${where}: "influence" is ${JSON.stringify(spec)}; it is { "radius", "across", "max", "shape" }, every one required`,
|
|
6611
|
+
`${where}: "influence" is ${JSON.stringify(spec)}; it is { "radius", "across", "max", "shape" }, every one required`, 'CE_COMPILE_SEGMENTS_INFLUENCE_INVALID',
|
|
6612
6612
|
);
|
|
6613
6613
|
}
|
|
6614
6614
|
const i = spec as Partial<Record<keyof SegmentsInfluence, unknown>>;
|
|
@@ -6616,20 +6616,20 @@ function segmentsInfluenceOf(
|
|
|
6616
6616
|
throw new CompileError(
|
|
6617
6617
|
`${where}: "influence.radius" is ${JSON.stringify(i.radius) ?? 'absent'}; it is how far a link reaches, in ` +
|
|
6618
6618
|
"the part's pixels, a positive number with no default — a length on this art: a link whose segment is that " +
|
|
6619
|
-
'far from a vertex or farther never pulls it',
|
|
6619
|
+
'far from a vertex or farther never pulls it', 'CE_COMPILE_SEGMENTS_INFLUENCE_RADIUS_INVALID',
|
|
6620
6620
|
);
|
|
6621
6621
|
}
|
|
6622
6622
|
if (typeof i.across !== 'boolean') {
|
|
6623
6623
|
throw new CompileError(
|
|
6624
6624
|
`${where}: "influence.across" is ${JSON.stringify(i.across) ?? 'absent'}; it is true (every link within ` +
|
|
6625
6625
|
'"radius" pulls, across the entries of "bones") or false (only the links of the entry nearest the vertex), ' +
|
|
6626
|
-
"with no default — whether a chain may pull its neighbour's cloth is the decision this field is",
|
|
6626
|
+
"with no default — whether a chain may pull its neighbour's cloth is the decision this field is", 'CE_COMPILE_SEGMENTS_INFLUENCE_ACROSS_INVALID',
|
|
6627
6627
|
);
|
|
6628
6628
|
}
|
|
6629
6629
|
if (typeof i.max !== 'number' || !Number.isInteger(i.max) || i.max < 1) {
|
|
6630
6630
|
throw new CompileError(
|
|
6631
6631
|
`${where}: "influence.max" is ${JSON.stringify(i.max) ?? 'absent'}; it is how many links may pull one vertex, ` +
|
|
6632
|
-
'the strongest kept, a whole number of at least 1 with no default',
|
|
6632
|
+
'the strongest kept, a whole number of at least 1 with no default', 'CE_COMPILE_SEGMENTS_INFLUENCE_MAX_INVALID',
|
|
6633
6633
|
);
|
|
6634
6634
|
}
|
|
6635
6635
|
const shape = RIG_INFLUENCE_SHAPES.find((name) => name === i.shape);
|
|
@@ -6637,7 +6637,7 @@ function segmentsInfluenceOf(
|
|
|
6637
6637
|
throw new CompileError(
|
|
6638
6638
|
`${where}: "influence.shape" is ${JSON.stringify(i.shape) ?? 'absent'}; one of ` +
|
|
6639
6639
|
`${RIG_INFLUENCE_SHAPES.map((name) => JSON.stringify(name)).join(', ')} — how a pull falls from 1 on a link ` +
|
|
6640
|
-
'to 0 at "radius", with no default: "linear" is 1 - d/radius, "smooth" arrives at both ends with zero slope',
|
|
6640
|
+
'to 0 at "radius", with no default: "linear" is 1 - d/radius, "smooth" arrives at both ends with zero slope', 'CE_COMPILE_SEGMENTS_INFLUENCE_SHAPE_INVALID',
|
|
6641
6641
|
);
|
|
6642
6642
|
}
|
|
6643
6643
|
return { radius: i.radius, across: i.across, max: i.max, shape };
|
|
@@ -6671,7 +6671,7 @@ function buildSegmentsAttachment(
|
|
|
6671
6671
|
if (att.image === undefined) {
|
|
6672
6672
|
throw new CompileError(
|
|
6673
6673
|
`${where}: a "segments" generator lays its lattice over the part's own alpha, so the attachment needs an ` +
|
|
6674
|
-
'"image" — there is nothing else here that says which pixels are art',
|
|
6674
|
+
'"image" — there is nothing else here that says which pixels are art', 'CE_COMPILE_SEGMENTS_IMAGE_MISSING',
|
|
6675
6675
|
);
|
|
6676
6676
|
}
|
|
6677
6677
|
const img = atlasedImage(att.image, where, ctx);
|
|
@@ -6679,7 +6679,7 @@ function buildSegmentsAttachment(
|
|
|
6679
6679
|
throw new CompileError(
|
|
6680
6680
|
`${where}: a "segments" generator lays its lattice over the part's own alpha, and "${att.image}" is lifted ` +
|
|
6681
6681
|
'off a packed page at the coordinates the atlas states, which on this file are not where its texels are — ' +
|
|
6682
|
-
`so there is no art here to cover, only another part of the page. ${img.pageGrid.sentence}`,
|
|
6682
|
+
`so there is no art here to cover, only another part of the page. ${img.pageGrid.sentence}`, 'CE_COMPILE_SEGMENTS_ATLAS_GRID_UNSUPPORTED',
|
|
6683
6683
|
);
|
|
6684
6684
|
}
|
|
6685
6685
|
if (img.atlasScale !== undefined) {
|
|
@@ -6687,7 +6687,7 @@ function buildSegmentsAttachment(
|
|
|
6687
6687
|
`${where}: a "segments" generator measures its "cell" and its falloff "radius" in the part's pixels, and ` +
|
|
6688
6688
|
`"${att.image}" sits on a page that declares scale: ${img.atlasScale}, where a pixel of the drawing is not a ` +
|
|
6689
6689
|
'texel of the page. This generator does not convert between the two; build it from the loose part, or ' +
|
|
6690
|
-
'from a pack at scale 1',
|
|
6690
|
+
'from a pack at scale 1', 'CE_COMPILE_SEGMENTS_ATLAS_SCALE_UNSUPPORTED',
|
|
6691
6691
|
);
|
|
6692
6692
|
}
|
|
6693
6693
|
|
|
@@ -6699,7 +6699,7 @@ function buildSegmentsAttachment(
|
|
|
6699
6699
|
`${where}: the "segments" generator states no "falloff" block. Its "radius" — added to every distance, in ` +
|
|
6700
6700
|
"the part's pixels — has no default, because it is a length on this art: " +
|
|
6701
6701
|
'"falloff": { "radius": <pixels> } is the least it takes (or an "influence" block, whose pull reaches zero ' +
|
|
6702
|
-
'at its own "radius")',
|
|
6702
|
+
'at its own "radius")', 'CE_COMPILE_SEGMENTS_WEIGHT_MODE_MISSING',
|
|
6703
6703
|
);
|
|
6704
6704
|
}
|
|
6705
6705
|
const falloff = influence === null ? segmentsFalloffOf(generator.falloff!, where) : null;
|
|
@@ -6715,16 +6715,16 @@ function buildSegmentsAttachment(
|
|
|
6715
6715
|
cell: generator.cell,
|
|
6716
6716
|
});
|
|
6717
6717
|
} catch (err) {
|
|
6718
|
-
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}
|
|
6718
|
+
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}`, 'CE_COMPILE_SEGMENTS_LATTICE_INVALID');
|
|
6719
6719
|
throw err;
|
|
6720
6720
|
}
|
|
6721
6721
|
const w = img.width;
|
|
6722
6722
|
const h = img.height;
|
|
6723
6723
|
if (att.width !== undefined && att.width !== w) {
|
|
6724
|
-
throw new CompileError(`${where}: the spec says width ${att.width} and "${att.image}" measures ${w}
|
|
6724
|
+
throw new CompileError(`${where}: the spec says width ${att.width} and "${att.image}" measures ${w}`, 'CE_COMPILE_GENERATED_IMAGE_WIDTH_MISMATCH');
|
|
6725
6725
|
}
|
|
6726
6726
|
if (att.height !== undefined && att.height !== h) {
|
|
6727
|
-
throw new CompileError(`${where}: the spec says height ${att.height} and "${att.image}" measures ${h}
|
|
6727
|
+
throw new CompileError(`${where}: the spec says height ${att.height} and "${att.image}" measures ${h}`, 'CE_COMPILE_GENERATED_IMAGE_HEIGHT_MISMATCH');
|
|
6728
6728
|
}
|
|
6729
6729
|
|
|
6730
6730
|
// -- the placement ---------------------------------------------------------
|
|
@@ -6733,14 +6733,14 @@ function buildSegmentsAttachment(
|
|
|
6733
6733
|
// MEASURED in — distances, falloff, `minWeight`, the shared-origin refusal —
|
|
6734
6734
|
// and the runtime's is what the vertices are BOUND through (`place`).
|
|
6735
6735
|
const exactSlotBone = ctx.exactFrames.get(ctx.anchorBone);
|
|
6736
|
-
if (!slotBone || !exactSlotBone) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform
|
|
6736
|
+
if (!slotBone || !exactSlotBone) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform`, 'CE_COMPILE_SLOT_SETUP_TRANSFORM_MISSING');
|
|
6737
6737
|
let anchorAt: [number, number] = [w / 2, h / 2];
|
|
6738
6738
|
if (generator.anchor !== undefined) {
|
|
6739
6739
|
const stated = finitePair(generator.anchor);
|
|
6740
6740
|
if (stated === null) {
|
|
6741
6741
|
throw new CompileError(
|
|
6742
6742
|
`${where}: "anchor" is ${JSON.stringify(generator.anchor)}; it is where the slot bone sits in the part's ` +
|
|
6743
|
-
'pixels, y down, as [x, y] — two finite numbers',
|
|
6743
|
+
'pixels, y down, as [x, y] — two finite numbers', 'CE_COMPILE_SEGMENTS_ANCHOR_INVALID',
|
|
6744
6744
|
);
|
|
6745
6745
|
}
|
|
6746
6746
|
anchorAt = stated;
|
|
@@ -6758,7 +6758,7 @@ function buildSegmentsAttachment(
|
|
|
6758
6758
|
throw new CompileError(
|
|
6759
6759
|
`${where}: the "segments" generator names no bones. "bones" is the one decision this generator takes — which ` +
|
|
6760
6760
|
'segments may pull the part — so it lists at least one: a bone name, a chain as a list of bone names, or ' +
|
|
6761
|
-
'{ "bone", "from", "to" }',
|
|
6761
|
+
'{ "bone", "from", "to" }', 'CE_COMPILE_SEGMENTS_BONES_EMPTY',
|
|
6762
6762
|
);
|
|
6763
6763
|
}
|
|
6764
6764
|
const parentOf = new Map(ctx.bones.map((b) => [b.name, b.parent ?? null]));
|
|
@@ -6767,7 +6767,7 @@ function buildSegmentsAttachment(
|
|
|
6767
6767
|
const segments: EntrySegment[] = [];
|
|
6768
6768
|
const boneOf = (name: unknown, at: string): { index: number; transform: BoneTransform } => {
|
|
6769
6769
|
if (typeof name !== 'string' || !parentOf.has(name)) {
|
|
6770
|
-
throw new CompileError(`${where}: ${at} names mesh bone ${JSON.stringify(name)}, which is not in the rig's bone list
|
|
6770
|
+
throw new CompileError(`${where}: ${at} names mesh bone ${JSON.stringify(name)}, which is not in the rig's bone list`, 'CE_COMPILE_SEGMENTS_BONE_UNKNOWN');
|
|
6771
6771
|
}
|
|
6772
6772
|
let index = named.indexOf(name);
|
|
6773
6773
|
if (index < 0) {
|
|
@@ -6785,7 +6785,7 @@ function buildSegmentsAttachment(
|
|
|
6785
6785
|
'so it has an origin and no segment. Give the bone a "length" (its segment runs from its origin to that ' +
|
|
6786
6786
|
'tip, along its setup rotation), ' +
|
|
6787
6787
|
(chained ? 'end the chain on a bone that has one, ' : 'name it inside a chain whose next link says where it ends, ') +
|
|
6788
|
-
'or state the segment outright as { "bone", "from", "to" }',
|
|
6788
|
+
'or state the segment outright as { "bone", "from", "to" }', 'CE_COMPILE_SEGMENTS_BONE_LENGTH_MISSING',
|
|
6789
6789
|
);
|
|
6790
6790
|
}
|
|
6791
6791
|
return toWorld(transform, length, 0);
|
|
@@ -6799,7 +6799,7 @@ function buildSegmentsAttachment(
|
|
|
6799
6799
|
}
|
|
6800
6800
|
if (Array.isArray(entry)) {
|
|
6801
6801
|
if (entry.length === 0) {
|
|
6802
|
-
throw new CompileError(`${where}: ${at} is an empty chain; a chain lists its bones root first, at least one
|
|
6802
|
+
throw new CompileError(`${where}: ${at} is an empty chain; a chain lists its bones root first, at least one`, 'CE_COMPILE_SEGMENTS_CHAIN_EMPTY');
|
|
6803
6803
|
}
|
|
6804
6804
|
const links = entry.map((name: unknown, k: number) => ({ name, ...boneOf(name, `${at}[${k}]`) }));
|
|
6805
6805
|
links.forEach((link, k) => {
|
|
@@ -6817,7 +6817,7 @@ function buildSegmentsAttachment(
|
|
|
6817
6817
|
throw new CompileError(
|
|
6818
6818
|
`${where}: ${at} chains "${name}" to "${nextName}", and "${nextName}" is not under "${name}" in the bone ` +
|
|
6819
6819
|
`tree — a chain runs down the skeleton, each link a descendant of the one before it. List the chain ` +
|
|
6820
|
-
'root first, or name the two bones as separate entries',
|
|
6820
|
+
'root first, or name the two bones as separate entries', 'CE_COMPILE_SEGMENTS_CHAIN_ORDER_INVALID',
|
|
6821
6821
|
);
|
|
6822
6822
|
}
|
|
6823
6823
|
const b: [number, number] = [next.transform.worldX, next.transform.worldY];
|
|
@@ -6825,7 +6825,7 @@ function buildSegmentsAttachment(
|
|
|
6825
6825
|
throw new CompileError(
|
|
6826
6826
|
`${where}: ${at} chains "${name}" to "${nextName}", and the two bones share their setup origin ` +
|
|
6827
6827
|
`(${origin[0]}, ${origin[1]}), so the link has no length and no direction to weight along. Give ` +
|
|
6828
|
-
`"${nextName}" its own position, or name "${name}" alone with a "length"`,
|
|
6828
|
+
`"${nextName}" its own position, or name "${name}" alone with a "length"`, 'CE_COMPILE_SEGMENTS_CHAIN_ZERO_LENGTH',
|
|
6829
6829
|
);
|
|
6830
6830
|
}
|
|
6831
6831
|
segments.push({ bone: link.index, entry: i, a: origin, b });
|
|
@@ -6840,7 +6840,7 @@ function buildSegmentsAttachment(
|
|
|
6840
6840
|
if (from === null || to === null) {
|
|
6841
6841
|
throw new CompileError(
|
|
6842
6842
|
`${where}: ${at} states a segment whose ${from === null ? '"from"' : '"to"'} is ` +
|
|
6843
|
-
`${JSON.stringify(from === null ? span.from : span.to)}; both ends are [x, y] in the part's pixels, y down`,
|
|
6843
|
+
`${JSON.stringify(from === null ? span.from : span.to)}; both ends are [x, y] in the part's pixels, y down`, 'CE_COMPILE_SEGMENTS_ENDPOINT_INVALID',
|
|
6844
6844
|
);
|
|
6845
6845
|
}
|
|
6846
6846
|
segments.push({ bone: index, entry: i, a: measuredAt(from[0], from[1]), b: measuredAt(to[0], to[1]) });
|
|
@@ -6848,7 +6848,7 @@ function buildSegmentsAttachment(
|
|
|
6848
6848
|
}
|
|
6849
6849
|
throw new CompileError(
|
|
6850
6850
|
`${where}: ${at} is ${JSON.stringify(entry)}; an entry is a bone name, a chain as a list of bone names, or ` +
|
|
6851
|
-
'{ "bone", "from", "to" }',
|
|
6851
|
+
'{ "bone", "from", "to" }', 'CE_COMPILE_SEGMENTS_ENTRY_INVALID',
|
|
6852
6852
|
);
|
|
6853
6853
|
});
|
|
6854
6854
|
|
|
@@ -6868,7 +6868,7 @@ function buildSegmentsAttachment(
|
|
|
6868
6868
|
`"${named[reached.nearest.bone]}" at ${reached.nearest.distance.toFixed(4)}, and "influence.radius" is ` +
|
|
6869
6869
|
`${influence.radius}${influence.across ? '' : ' (with "across": false, the nearest entry\'s links only)'}. ` +
|
|
6870
6870
|
'A vertex needs a link, and rigc will not hand it one the reach does not: raise "radius" over that distance, ' +
|
|
6871
|
-
'or name a segment nearer this part of the art',
|
|
6871
|
+
'or name a segment nearer this part of the art', 'CE_COMPILE_SEGMENTS_VERTEX_UNREACHED',
|
|
6872
6872
|
);
|
|
6873
6873
|
}
|
|
6874
6874
|
const got = segmentShares(measuredAt(px, py), segments, falloff!);
|
|
@@ -6879,7 +6879,7 @@ function buildSegmentsAttachment(
|
|
|
6879
6879
|
`strongest pull(s) share it as ${got.dropped.map((d) => `"${named[d.bone]}" ${d.share.toFixed(4)}`).join(', ')}, ` +
|
|
6880
6880
|
`and every one is under "minWeight" ${minWeight}. A vertex needs a bone, and rigc will not pick one the ` +
|
|
6881
6881
|
`falloff dropped: lower "minWeight" to ${(1 / maxBones).toFixed(4)} or under (1 / "maxBones", which the ` +
|
|
6882
|
-
'strongest share can never fall below), or name a segment nearer this part of the art',
|
|
6882
|
+
'strongest share can never fall below), or name a segment nearer this part of the art', 'CE_COMPILE_SEGMENTS_VERTEX_WEIGHTS_DROPPED',
|
|
6883
6883
|
);
|
|
6884
6884
|
});
|
|
6885
6885
|
const geometry: MeshGeometry = {
|
|
@@ -6984,7 +6984,7 @@ function buildContourAttachment(
|
|
|
6984
6984
|
if (att.image === undefined) {
|
|
6985
6985
|
throw new CompileError(
|
|
6986
6986
|
`${where}: a "contour" generator traces the part's own alpha, so the attachment needs an "image" — ` +
|
|
6987
|
-
'there is nothing else here that says which pixels to measure',
|
|
6987
|
+
'there is nothing else here that says which pixels to measure', 'CE_COMPILE_CONTOUR_IMAGE_MISSING',
|
|
6988
6988
|
);
|
|
6989
6989
|
}
|
|
6990
6990
|
const img = atlasedImage(att.image, where, ctx);
|
|
@@ -7003,7 +7003,7 @@ function buildContourAttachment(
|
|
|
7003
7003
|
throw new CompileError(
|
|
7004
7004
|
`${where}: a "contour" generator traces the part's own alpha, and "${att.image}" is lifted off a packed ` +
|
|
7005
7005
|
'page at the coordinates the atlas states, which on this file are not where its texels are — so there is ' +
|
|
7006
|
-
`no silhouette here to trace, only another part of the page. ${img.pageGrid.sentence}`,
|
|
7006
|
+
`no silhouette here to trace, only another part of the page. ${img.pageGrid.sentence}`, 'CE_COMPILE_CONTOUR_ATLAS_GRID_UNSUPPORTED',
|
|
7007
7007
|
);
|
|
7008
7008
|
}
|
|
7009
7009
|
// ⚠️ Nothing here reads the PNG's colour type. `hasAlpha` answers "where does
|
|
@@ -7031,7 +7031,7 @@ function buildContourAttachment(
|
|
|
7031
7031
|
...(img.atlasScale === undefined ? {} : { pageScale: img.atlasScale }),
|
|
7032
7032
|
});
|
|
7033
7033
|
} catch (err) {
|
|
7034
|
-
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}
|
|
7034
|
+
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}`, 'CE_COMPILE_CONTOUR_GENERATOR_INVALID');
|
|
7035
7035
|
throw err;
|
|
7036
7036
|
}
|
|
7037
7037
|
|
|
@@ -7046,16 +7046,16 @@ function buildContourAttachment(
|
|
|
7046
7046
|
const h = img.height;
|
|
7047
7047
|
const toArt = w / plate.width;
|
|
7048
7048
|
if (att.width !== undefined && att.width !== w) {
|
|
7049
|
-
throw new CompileError(`${where}: the spec says width ${att.width} and "${att.image}" measures ${w}
|
|
7049
|
+
throw new CompileError(`${where}: the spec says width ${att.width} and "${att.image}" measures ${w}`, 'CE_COMPILE_GENERATED_IMAGE_WIDTH_MISMATCH');
|
|
7050
7050
|
}
|
|
7051
7051
|
if (att.height !== undefined && att.height !== h) {
|
|
7052
|
-
throw new CompileError(`${where}: the spec says height ${att.height} and "${att.image}" measures ${h}
|
|
7052
|
+
throw new CompileError(`${where}: the spec says height ${att.height} and "${att.image}" measures ${h}`, 'CE_COMPILE_GENERATED_IMAGE_HEIGHT_MISMATCH');
|
|
7053
7053
|
}
|
|
7054
7054
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
7055
7055
|
const frame = ctx.exactFrames.get(ctx.anchorBone);
|
|
7056
|
-
if (!anchor || !frame) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform
|
|
7056
|
+
if (!anchor || !frame) throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" has no setup transform`, 'CE_COMPILE_SLOT_SETUP_TRANSFORM_MISSING');
|
|
7057
7057
|
if (!ctx.bones.some((b) => b.name === ctx.anchorBone)) {
|
|
7058
|
-
throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" is not in the rig's bone list
|
|
7058
|
+
throw new CompileError(`${where}: slot bone "${ctx.anchorBone}" is not in the rig's bone list`, 'CE_COMPILE_SLOT_BONE_NOT_DECLARED');
|
|
7059
7059
|
}
|
|
7060
7060
|
const vertices = bindWeightedVertices(
|
|
7061
7061
|
geometry,
|
|
@@ -7169,7 +7169,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7169
7169
|
const boneNames = ctx.boneNames;
|
|
7170
7170
|
const needBone = (name: unknown, field: string): string => {
|
|
7171
7171
|
if (typeof name !== 'string' || !boneNames.has(name)) {
|
|
7172
|
-
throw new CompileError(`${where}: ${field} names ${JSON.stringify(name)}, which the rig does not declare as a bone
|
|
7172
|
+
throw new CompileError(`${where}: ${field} names ${JSON.stringify(name)}, which the rig does not declare as a bone`, 'CE_COMPILE_CONSTRAINT_BONE_UNKNOWN');
|
|
7173
7173
|
}
|
|
7174
7174
|
return name;
|
|
7175
7175
|
};
|
|
@@ -7186,14 +7186,14 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7186
7186
|
throw new CompileError(
|
|
7187
7187
|
`${where}: ${field} is ${JSON.stringify(value)}; known: ${allowed.join(', ')} (only the first letter's case is ` +
|
|
7188
7188
|
"free — the parser's enumValue uppercases that one character and nothing else, and an unresolved name " +
|
|
7189
|
-
'becomes undefined without an error)',
|
|
7189
|
+
'becomes undefined without an error)', 'CE_COMPILE_CONSTRAINT_ENUM_UNKNOWN',
|
|
7190
7190
|
);
|
|
7191
7191
|
}
|
|
7192
7192
|
return value;
|
|
7193
7193
|
};
|
|
7194
7194
|
const needNumber = (value: unknown, field: string): number => {
|
|
7195
7195
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
7196
|
-
throw new CompileError(`${where}: ${field} is ${JSON.stringify(value)}, which is not a finite number
|
|
7196
|
+
throw new CompileError(`${where}: ${field} is ${JSON.stringify(value)}, which is not a finite number`, 'CE_COMPILE_CONSTRAINT_NUMBER_NONFINITE');
|
|
7197
7197
|
}
|
|
7198
7198
|
return value;
|
|
7199
7199
|
};
|
|
@@ -7211,7 +7211,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7211
7211
|
const boneList = (): string[] => {
|
|
7212
7212
|
const list = spec.bones;
|
|
7213
7213
|
if (!Array.isArray(list) || list.length === 0) {
|
|
7214
|
-
throw new CompileError(`${where}: a ${spec.type} constraint needs a non-empty "bones" array
|
|
7214
|
+
throw new CompileError(`${where}: a ${spec.type} constraint needs a non-empty "bones" array`, 'CE_COMPILE_CONSTRAINT_BONES_EMPTY');
|
|
7215
7215
|
}
|
|
7216
7216
|
return list.map((name, i) => needBone(name, `bones[${i}]`));
|
|
7217
7217
|
};
|
|
@@ -7236,11 +7236,11 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7236
7236
|
// places in this format that does not fail silently — but it throws at load
|
|
7237
7237
|
// time, in the consumer's process, and that is late.
|
|
7238
7238
|
if (!TRANSFORM_PROPERTIES.includes(from)) {
|
|
7239
|
-
throw new CompileError(`${where}: properties has "${from}"; known: ${TRANSFORM_PROPERTIES.join(', ')}
|
|
7239
|
+
throw new CompileError(`${where}: properties has "${from}"; known: ${TRANSFORM_PROPERTIES.join(', ')}`, 'CE_COMPILE_TRANSFORM_PROPERTY_UNKNOWN');
|
|
7240
7240
|
}
|
|
7241
7241
|
for (const to of Object.keys(entry?.to ?? {})) {
|
|
7242
7242
|
if (!TRANSFORM_PROPERTIES.includes(to)) {
|
|
7243
|
-
throw new CompileError(`${where}: properties.${from}.to has "${to}"; known: ${TRANSFORM_PROPERTIES.join(', ')}
|
|
7243
|
+
throw new CompileError(`${where}: properties.${from}.to has "${to}"; known: ${TRANSFORM_PROPERTIES.join(', ')}`, 'CE_COMPILE_TRANSFORM_TO_PROPERTY_UNKNOWN');
|
|
7244
7244
|
}
|
|
7245
7245
|
}
|
|
7246
7246
|
}
|
|
@@ -7272,7 +7272,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7272
7272
|
// loud, but in the consumer's process. The silent half is the one below it.
|
|
7273
7273
|
const slot = spec.slot;
|
|
7274
7274
|
if (typeof slot !== 'string' || !ctx.slotNames.has(slot)) {
|
|
7275
|
-
throw new CompileError(`${where}: slot names ${JSON.stringify(slot)}, which the rig does not declare as a slot
|
|
7275
|
+
throw new CompileError(`${where}: slot names ${JSON.stringify(slot)}, which the rig does not declare as a slot`, 'CE_COMPILE_CONSTRAINT_SLOT_UNKNOWN');
|
|
7276
7276
|
}
|
|
7277
7277
|
if (!ctx.pathSlots.has(slot)) {
|
|
7278
7278
|
// `PathConstraint.update` opens with
|
|
@@ -7283,7 +7283,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7283
7283
|
`${where}: slot "${slot}" has no path attachment in any skin, so the constraint has no curve to follow — ` +
|
|
7284
7284
|
'PathConstraint.update returns immediately unless the slot\'s attachment is a path, which means this ' +
|
|
7285
7285
|
'constraint would load, report every mix it was given, and move nothing. ' +
|
|
7286
|
-
'Give that slot an attachment with "type": "path".',
|
|
7286
|
+
'Give that slot an attachment with "type": "path".', 'CE_COMPILE_PATH_CONSTRAINT_SLOT_NO_PATH',
|
|
7287
7287
|
);
|
|
7288
7288
|
}
|
|
7289
7289
|
out.slot = slot;
|
|
@@ -7309,14 +7309,14 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7309
7309
|
if (typeof animation !== 'string' || animation.length === 0) {
|
|
7310
7310
|
throw new CompileError(
|
|
7311
7311
|
`${where}: a slider needs an "animation" — the animation it applies. Without one the parser's second pass ` +
|
|
7312
|
-
'over the constraints array throws `Slider animation not found`.',
|
|
7312
|
+
'over the constraints array throws `Slider animation not found`.', 'CE_COMPILE_SLIDER_ANIMATION_MISSING',
|
|
7313
7313
|
);
|
|
7314
7314
|
}
|
|
7315
7315
|
if (!ctx.animationNames.has(animation)) {
|
|
7316
7316
|
const known = [...ctx.animationNames];
|
|
7317
7317
|
throw new CompileError(
|
|
7318
7318
|
`${where}: applies animation "${animation}", which the motion spec does not declare` +
|
|
7319
|
-
(known.length ? ` (it declares: ${known.join(', ')})` : ' (it declares none at all)'),
|
|
7319
|
+
(known.length ? ` (it declares: ${known.join(', ')})` : ' (it declares none at all)'), 'CE_COMPILE_SLIDER_ANIMATION_UNKNOWN',
|
|
7320
7320
|
);
|
|
7321
7321
|
}
|
|
7322
7322
|
// 🔁 The editor keeps a slider's animation by index and re-sorts the object
|
|
@@ -7331,7 +7331,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7331
7331
|
`lists every array-index key (${spelled} here) before the rest, the editor re-sorts the animations into its ` +
|
|
7332
7332
|
'own order on import, and a slider holds its animation by index (measured on Spine 4.3.26; AUTHORING R10). ' +
|
|
7333
7333
|
`Rename ${spelled} so no animation name is a plain non-negative integer — a prefix or a leading zero ` +
|
|
7334
|
-
`("a${repointed.indexNames[0]}", "0${repointed.indexNames[0]}") puts the file's order and the editor's in step.`,
|
|
7334
|
+
`("a${repointed.indexNames[0]}", "0${repointed.indexNames[0]}") puts the file's order and the editor's in step.`, 'CE_COMPILE_SLIDER_ANIMATION_EDITOR_ORDER',
|
|
7335
7335
|
);
|
|
7336
7336
|
}
|
|
7337
7337
|
out.animation = animation;
|
|
@@ -7351,7 +7351,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7351
7351
|
if (typeof property !== 'string' || !RIG_FROM_PROPERTIES.includes(property as (typeof RIG_FROM_PROPERTIES)[number])) {
|
|
7352
7352
|
throw new CompileError(
|
|
7353
7353
|
`${where}: drives off bone "${String(spec.bone)}" but its property is ${JSON.stringify(property)}; ` +
|
|
7354
|
-
`known: ${RIG_FROM_PROPERTIES.join(', ')} (the parser throws on anything else)`,
|
|
7354
|
+
`known: ${RIG_FROM_PROPERTIES.join(', ')} (the parser throws on anything else)`, 'CE_COMPILE_SLIDER_BONE_PROPERTY_UNKNOWN',
|
|
7355
7355
|
);
|
|
7356
7356
|
}
|
|
7357
7357
|
out.property = property;
|
|
@@ -7361,7 +7361,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7361
7361
|
if (spec.local !== undefined) out.local = spec.local;
|
|
7362
7362
|
if (spec.scale !== undefined && spec.scale === 0) {
|
|
7363
7363
|
// time = to + (value - from) * 0, so the slider holds one frame forever.
|
|
7364
|
-
throw new CompileError(`${where}: scale is 0, so the bone's property cannot move the slider's time at all
|
|
7364
|
+
throw new CompileError(`${where}: scale is 0, so the bone's property cannot move the slider's time at all`, 'CE_COMPILE_SLIDER_SCALE_ZERO');
|
|
7365
7365
|
}
|
|
7366
7366
|
// The window of driving values that can reach a frame at all, and the two
|
|
7367
7367
|
// functions every figure in the two reader clauses below comes off.
|
|
@@ -7608,7 +7608,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7608
7608
|
`at ${dead.end.toFixed(3)}° is read as ${readAs.toFixed(3)}° and maps to time ${lands.toFixed(3)}s. ` +
|
|
7609
7609
|
`${consequence} The whole part of the range ${dead.side} is dead and ` +
|
|
7610
7610
|
'nothing at runtime reports it. Add `"local": true` to read the bone\'s own rotation signed and unwrapped — ' +
|
|
7611
|
-
`that is the form a face axis wants — or ${dead.repair}.`,
|
|
7611
|
+
`that is the form a face axis wants — or ${dead.repair}.`, 'CE_COMPILE_SLIDER_WORLD_ROTATION_DEAD_RANGE',
|
|
7612
7612
|
);
|
|
7613
7613
|
}
|
|
7614
7614
|
}
|
|
@@ -7681,14 +7681,14 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7681
7681
|
`${lowest.toFixed(3)} is read as ${readAs.toFixed(3)} and maps to time ${lands.toFixed(3)}s — the time the ` +
|
|
7682
7682
|
`bone at ${readAs.toFixed(3)} maps to. Positions below 0 read as the mirror of positions above it: ` +
|
|
7683
7683
|
`${consequence} Nothing at runtime reports it. Add \`"local": true\` to read the bone's own scale signed and ` +
|
|
7684
|
-
'unfolded — that is the form a squash axis wants — or move the range so it does not dip below 0.',
|
|
7684
|
+
'unfolded — that is the form a squash axis wants — or move the range so it does not dip below 0.', 'CE_COMPILE_SLIDER_WORLD_SCALE_FOLDED',
|
|
7685
7685
|
);
|
|
7686
7686
|
}
|
|
7687
7687
|
for (const field of timeSide) {
|
|
7688
7688
|
if (spec[field] !== undefined) {
|
|
7689
7689
|
throw new CompileError(
|
|
7690
7690
|
`${where}: declares both a "bone" and "${field}". A slider with a bone takes its time from that bone's ` +
|
|
7691
|
-
`property; "${field}" is read only by the bone-less form (\`:361\`), so it would be dropped in silence.`,
|
|
7691
|
+
`property; "${field}" is read only by the bone-less form (\`:361\`), so it would be dropped in silence.`, 'CE_COMPILE_SLIDER_BONELESS_FIELD_WITH_BONE',
|
|
7692
7692
|
);
|
|
7693
7693
|
}
|
|
7694
7694
|
}
|
|
@@ -7699,7 +7699,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7699
7699
|
throw new CompileError(
|
|
7700
7700
|
`${where}: declares "${field}" but no "bone". Every one of ${boneSide.join('/')} is read only inside the ` +
|
|
7701
7701
|
"parser's `if (boneName)` branch (`:350-360`), so it would be dropped in silence. Name the driving bone, " +
|
|
7702
|
-
'or key `slider.<name>.time` in the motion spec instead.',
|
|
7702
|
+
'or key `slider.<name>.time` in the motion spec instead.', 'CE_COMPILE_SLIDER_BONE_FIELD_WITHOUT_BONE',
|
|
7703
7703
|
);
|
|
7704
7704
|
}
|
|
7705
7705
|
}
|
|
@@ -7744,7 +7744,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7744
7744
|
throw new NotImplementedError(
|
|
7745
7745
|
`${where}: constraint type ${JSON.stringify(spec.type)} is not one Spine 4.3 knows. ` +
|
|
7746
7746
|
'The five are: ik, transform, path, physics, slider. An unrecognised type matches no case in the parser and ' +
|
|
7747
|
-
'the constraint is dropped without a word.',
|
|
7747
|
+
'the constraint is dropped without a word.', 'CE_COMPILE_CONSTRAINT_TYPE_UNKNOWN',
|
|
7748
7748
|
);
|
|
7749
7749
|
}
|
|
7750
7750
|
|
|
@@ -7766,7 +7766,7 @@ function buildRigInfo(
|
|
|
7766
7766
|
): RigInfo {
|
|
7767
7767
|
const axisBone = rig.invariants?.axisBone ?? null;
|
|
7768
7768
|
if (axisBone !== null && !bones.some((b) => b.name === axisBone)) {
|
|
7769
|
-
throw new CompileError(`rig "${rig.name}" names "${axisBone}" as its axis bone, which it does not declare
|
|
7769
|
+
throw new CompileError(`rig "${rig.name}" names "${axisBone}" as its axis bone, which it does not declare`, 'CE_COMPILE_AXIS_BONE_UNKNOWN');
|
|
7770
7770
|
}
|
|
7771
7771
|
const axisSubtree: string[] = [];
|
|
7772
7772
|
if (axisBone) {
|
|
@@ -7794,7 +7794,7 @@ function buildRigInfo(
|
|
|
7794
7794
|
if (meshKinds[slot] === undefined) {
|
|
7795
7795
|
throw new CompileError(
|
|
7796
7796
|
`rig "${rig.name}" exempts slot "${slot}" from A39_DEFORM_KEEPS_TRIANGLE_WINDING, but that slot carries no ` +
|
|
7797
|
-
'mesh — winding is a property of triangles, so there is nothing there to exempt',
|
|
7797
|
+
'mesh — winding is a property of triangles, so there is nothing there to exempt', 'CE_COMPILE_WINDING_EXEMPTION_NOT_MESH',
|
|
7798
7798
|
);
|
|
7799
7799
|
}
|
|
7800
7800
|
}
|
|
@@ -7807,12 +7807,12 @@ function buildRigInfo(
|
|
|
7807
7807
|
spineDeg === null ? null : [Math.cos((spineDeg * Math.PI) / 180), Math.sin((spineDeg * Math.PI) / 180)];
|
|
7808
7808
|
const contactDepth = manifest?.stroke?.contact_depth ?? null;
|
|
7809
7809
|
if (contactDepth !== null && !(contactDepth > 0)) {
|
|
7810
|
-
throw new CompileError(`manifest stroke.contact_depth is ${contactDepth}; it must be a positive number of axis pixels
|
|
7810
|
+
throw new CompileError(`manifest stroke.contact_depth is ${contactDepth}; it must be a positive number of axis pixels`, 'CE_COMPILE_CONTACT_DEPTH_INVALID');
|
|
7811
7811
|
}
|
|
7812
7812
|
const capCeiling = manifest?.stroke?.cap_containment_ceiling ?? null;
|
|
7813
7813
|
if (capCeiling !== null && !(capCeiling > 0)) {
|
|
7814
7814
|
throw new CompileError(
|
|
7815
|
-
`manifest stroke.cap_containment_ceiling is ${capCeiling}; it must be a positive number of axis pixels (use null for "not measurable on this cut")`,
|
|
7815
|
+
`manifest stroke.cap_containment_ceiling is ${capCeiling}; it must be a positive number of axis pixels (use null for "not measurable on this cut")`, 'CE_COMPILE_CONTAINMENT_CEILING_INVALID',
|
|
7816
7816
|
);
|
|
7817
7817
|
}
|
|
7818
7818
|
return {
|
|
@@ -7851,13 +7851,13 @@ function checkAxisSelfConsistency(manifest: FaceManifest): void {
|
|
|
7851
7851
|
if (!manifest.axis) return;
|
|
7852
7852
|
const { deg, unit } = manifest.axis;
|
|
7853
7853
|
if (!Array.isArray(unit) || unit.length !== 2) {
|
|
7854
|
-
throw new CompileError(`manifest axis.unit must be [x, y], got ${JSON.stringify(unit)}
|
|
7854
|
+
throw new CompileError(`manifest axis.unit must be [x, y], got ${JSON.stringify(unit)}`, 'CE_COMPILE_AXIS_UNIT_INVALID');
|
|
7855
7855
|
}
|
|
7856
7856
|
const ex = Math.cos((deg * Math.PI) / 180);
|
|
7857
7857
|
const ey = Math.sin((deg * Math.PI) / 180);
|
|
7858
7858
|
if (hypot2(unit[0] - ex, unit[1] - ey) > 1e-3) {
|
|
7859
7859
|
throw new CompileError(
|
|
7860
|
-
`manifest axis.unit [${unit[0]}, ${unit[1]}] does not match axis.deg ${deg} (expected [${f32(ex)}, ${f32(ey)}])`,
|
|
7860
|
+
`manifest axis.unit [${unit[0]}, ${unit[1]}] does not match axis.deg ${deg} (expected [${f32(ex)}, ${f32(ey)}])`, 'CE_COMPILE_AXIS_UNIT_DEG_MISMATCH',
|
|
7861
7861
|
);
|
|
7862
7862
|
}
|
|
7863
7863
|
}
|
|
@@ -7916,9 +7916,9 @@ function buildMesh(
|
|
|
7916
7916
|
const controls = meshControlBones(part);
|
|
7917
7917
|
|
|
7918
7918
|
const refFor = (name: string): MeshBoneRef => {
|
|
7919
|
-
if (!bones.some((b) => b.name === name)) throw new CompileError(`internal: mesh bone "${name}" is not in the bone list
|
|
7919
|
+
if (!bones.some((b) => b.name === name)) throw new CompileError(`internal: mesh bone "${name}" is not in the bone list`, 'CE_COMPILE_INTERNAL_MESH_BONE_UNKNOWN');
|
|
7920
7920
|
const m = transforms.get(name);
|
|
7921
|
-
if (!m) throw new CompileError(`internal: no setup transform for mesh bone "${name}"
|
|
7921
|
+
if (!m) throw new CompileError(`internal: no setup transform for mesh bone "${name}"`, 'CE_COMPILE_INTERNAL_MESH_BONE_TRANSFORM_MISSING');
|
|
7922
7922
|
return { name, toBind: (wx, wy) => toBoneLocal(m, wx, wy) };
|
|
7923
7923
|
};
|
|
7924
7924
|
|
|
@@ -7945,7 +7945,7 @@ function buildMesh(
|
|
|
7945
7945
|
// bits turned an authored 90 into 89.99999734089101 in that refusal.
|
|
7946
7946
|
const controlAngles = ringControlAngles(controls, spec.center!, (name) => {
|
|
7947
7947
|
const m = exactFrames.get(name);
|
|
7948
|
-
if (!m) throw new CompileError(`internal: no setup transform for control bone "${name}"
|
|
7948
|
+
if (!m) throw new CompileError(`internal: no setup transform for control bone "${name}"`, 'CE_COMPILE_INTERNAL_CONTROL_BONE_TRANSFORM_MISSING');
|
|
7949
7949
|
return [m.worldX, cropH - m.worldY];
|
|
7950
7950
|
});
|
|
7951
7951
|
geometry = buildRingMesh({
|
|
@@ -7958,7 +7958,7 @@ function buildMesh(
|
|
|
7958
7958
|
});
|
|
7959
7959
|
}
|
|
7960
7960
|
} catch (err) {
|
|
7961
|
-
if (err instanceof MeshError) throw new CompileError(`slot "${part.slot}" mesh: ${err.message}
|
|
7961
|
+
if (err instanceof MeshError) throw new CompileError(`slot "${part.slot}" mesh: ${err.message}`, 'CE_COMPILE_MANIFEST_RING_RIBBON_INVALID');
|
|
7962
7962
|
throw err;
|
|
7963
7963
|
}
|
|
7964
7964
|
|
|
@@ -8009,17 +8009,17 @@ function buildMesh(
|
|
|
8009
8009
|
*/
|
|
8010
8010
|
function rawCurve(curve: number[] | 'stepped', channels: number, where: string, at: string): number[] | 'stepped' {
|
|
8011
8011
|
if (curve === 'stepped') return 'stepped';
|
|
8012
|
-
if (!Array.isArray(curve)) throw new CompileError(`${where} (t=${at}): curve must be an array or "stepped"
|
|
8012
|
+
if (!Array.isArray(curve)) throw new CompileError(`${where} (t=${at}): curve must be an array or "stepped"`, 'CE_COMPILE_CURVE_NOT_ARRAY');
|
|
8013
8013
|
if (curve.length !== channels * 4) {
|
|
8014
8014
|
// A short array multiplies `undefined` into the cubic and yields NaN with no
|
|
8015
8015
|
// error at all — case 6g, and the reason A05 exists.
|
|
8016
8016
|
throw new CompileError(
|
|
8017
|
-
`${where} (t=${at}): raw curve has ${curve.length} numbers, this timeline needs ${channels} channel(s) x 4 = ${channels * 4}`,
|
|
8017
|
+
`${where} (t=${at}): raw curve has ${curve.length} numbers, this timeline needs ${channels} channel(s) x 4 = ${channels * 4}`, 'CE_COMPILE_CURVE_CHANNEL_COUNT',
|
|
8018
8018
|
);
|
|
8019
8019
|
}
|
|
8020
8020
|
for (const n of curve) {
|
|
8021
8021
|
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
8022
|
-
throw new CompileError(`${where} (t=${at}): raw curve holds a non-finite value ${JSON.stringify(n)}
|
|
8022
|
+
throw new CompileError(`${where} (t=${at}): raw curve holds a non-finite value ${JSON.stringify(n)}`, 'CE_COMPILE_CURVE_VALUE_NONFINITE');
|
|
8023
8023
|
}
|
|
8024
8024
|
}
|
|
8025
8025
|
return curve.map(f32);
|
|
@@ -8043,9 +8043,9 @@ function compileValueTrack(
|
|
|
8043
8043
|
kind: string,
|
|
8044
8044
|
): ModelKey[] {
|
|
8045
8045
|
const shape = shapes[track.property];
|
|
8046
|
-
if (!shape) throw new CompileError(`animation "${animName}": ${kind} "${target}" has no property "${track.property}"
|
|
8046
|
+
if (!shape) throw new CompileError(`animation "${animName}": ${kind} "${target}" has no property "${track.property}"`, 'CE_COMPILE_VALUE_TIMELINE_PROPERTY_UNKNOWN');
|
|
8047
8047
|
const where = `animation "${animName}" ${kind} "${target}" ${track.property}`;
|
|
8048
|
-
if (!track.keys.length) throw new CompileError(`${where}: no keys
|
|
8048
|
+
if (!track.keys.length) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8049
8049
|
|
|
8050
8050
|
const out: ModelKey[] = [];
|
|
8051
8051
|
for (let i = 0; i < track.keys.length; i++) {
|
|
@@ -8053,7 +8053,7 @@ function compileValueTrack(
|
|
|
8053
8053
|
const next = track.keys[i + 1];
|
|
8054
8054
|
const time = keyTime(key.t + shift);
|
|
8055
8055
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8056
|
-
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})
|
|
8056
|
+
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
|
|
8057
8057
|
}
|
|
8058
8058
|
checkKeyTime(where, time, key.t, duration);
|
|
8059
8059
|
// A named-value timeline (`inherit`): the key's `v` is one name, stepped by
|
|
@@ -8066,12 +8066,12 @@ function compileValueTrack(
|
|
|
8066
8066
|
throw new CompileError(
|
|
8067
8067
|
`${where} key at t=${key.t} carries ${key.ease !== undefined ? `an easing ("${key.ease}")` : 'a curve'}, and ` +
|
|
8068
8068
|
`this timeline is stepped by the format — its reader builds no curve, so the mode changes AT the key and ` +
|
|
8069
|
-
'holds until the next one. Remove it',
|
|
8069
|
+
'holds until the next one. Remove it', 'CE_COMPILE_STEPPED_TIMELINE_EASING',
|
|
8070
8070
|
);
|
|
8071
8071
|
}
|
|
8072
8072
|
const mode = shape.named.resolve(key.v);
|
|
8073
8073
|
if (mode === undefined) {
|
|
8074
|
-
throw new CompileError(`${where} key at t=${key.t} names mode ${JSON.stringify(key.v)}; ${shape.named.known}
|
|
8074
|
+
throw new CompileError(`${where} key at t=${key.t} names mode ${JSON.stringify(key.v)}; ${shape.named.known}`, 'CE_COMPILE_TIMELINE_MODE_UNKNOWN');
|
|
8075
8075
|
}
|
|
8076
8076
|
out.push({ time, [shape.named.field]: mode });
|
|
8077
8077
|
continue;
|
|
@@ -8079,18 +8079,18 @@ function compileValueTrack(
|
|
|
8079
8079
|
// A no-field timeline (`reset`) is an event: the key IS the value, so it
|
|
8080
8080
|
// carries none. Anything else must match the field count exactly.
|
|
8081
8081
|
if (shape.fields.length === 0) {
|
|
8082
|
-
if (key.v !== null) throw new CompileError(`${where}: this timeline takes no value; use null
|
|
8083
|
-
if (key.ease) throw new CompileError(`${where}: an event timeline cannot carry an easing
|
|
8082
|
+
if (key.v !== null) throw new CompileError(`${where}: this timeline takes no value; use null`, 'CE_COMPILE_VALUELESS_TIMELINE_VALUE');
|
|
8083
|
+
if (key.ease) throw new CompileError(`${where}: an event timeline cannot carry an easing`, 'CE_COMPILE_EVENT_TIMELINE_EASING');
|
|
8084
8084
|
out.push({ time });
|
|
8085
8085
|
continue;
|
|
8086
8086
|
}
|
|
8087
8087
|
if (!Array.isArray(key.v) || key.v.length !== shape.fields.length) {
|
|
8088
|
-
throw new CompileError(`${where}: key value must be an array of ${shape.fields.length} number(s)
|
|
8088
|
+
throw new CompileError(`${where}: key value must be an array of ${shape.fields.length} number(s)`, 'CE_COMPILE_TIMELINE_VALUE_CHANNEL_COUNT');
|
|
8089
8089
|
}
|
|
8090
8090
|
const entry: ModelKey = { time };
|
|
8091
8091
|
shape.fields.forEach((field, c) => {
|
|
8092
8092
|
const v = key.v as number[];
|
|
8093
|
-
if (!Number.isFinite(v[c])) throw new CompileError(`${where}: non-finite value ${String(v[c])}
|
|
8093
|
+
if (!Number.isFinite(v[c])) throw new CompileError(`${where}: non-finite value ${String(v[c])}`, 'CE_COMPILE_TIMELINE_VALUE_NONFINITE');
|
|
8094
8094
|
// The bound, where the runtime has one. Refused here rather than left to
|
|
8095
8095
|
// `A23` because this is a spec somebody wrote and the key is the thing to
|
|
8096
8096
|
// change — the same division `compileConstraintTrack`'s `range` makes for
|
|
@@ -8106,28 +8106,28 @@ function compileValueTrack(
|
|
|
8106
8106
|
// reads "… mass: value is 0", which says the property twice and the
|
|
8107
8107
|
// second time under the wrong name.
|
|
8108
8108
|
const at = shape.fields.length > 1 ? `${field} at t=${key.t}` : `key at t=${key.t}`;
|
|
8109
|
-
if (refusal !== null) throw new CompileError(`${where} ${at} is ${refusal}
|
|
8109
|
+
if (refusal !== null) throw new CompileError(`${where} ${at} is ${refusal}`, 'CE_COMPILE_PHYSICS_KEY_RANGE_INVALID');
|
|
8110
8110
|
}
|
|
8111
8111
|
entry[field] = f32(v[c]);
|
|
8112
8112
|
});
|
|
8113
8113
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
8114
|
-
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one
|
|
8114
|
+
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one`, 'CE_COMPILE_KEY_EASING_AND_CURVE');
|
|
8115
8115
|
}
|
|
8116
8116
|
if (key.curve !== undefined) {
|
|
8117
|
-
if (!next) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to
|
|
8117
|
+
if (!next) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_CURVE');
|
|
8118
8118
|
entry.curve = rawCurve(key.curve, shape.fields.length, where, String(key.t));
|
|
8119
8119
|
} else if (key.ease && next) {
|
|
8120
8120
|
if (key.ease === 'stepped') {
|
|
8121
8121
|
entry.curve = 'stepped';
|
|
8122
8122
|
} else {
|
|
8123
8123
|
const handles = motion.easings?.[key.ease];
|
|
8124
|
-
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"
|
|
8125
|
-
if (!Array.isArray(next.v)) throw new CompileError(`${where}: next key value must be an array
|
|
8124
|
+
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"`, 'CE_COMPILE_KEY_EASING_UNKNOWN');
|
|
8125
|
+
if (!Array.isArray(next.v)) throw new CompileError(`${where}: next key value must be an array`, 'CE_COMPILE_NEXT_KEY_VALUE_NOT_ARRAY');
|
|
8126
8126
|
const t2 = keyTime(next.t + shift);
|
|
8127
8127
|
entry.curve = easingCurve(handles, time, t2, key.v as number[], next.v as number[]);
|
|
8128
8128
|
}
|
|
8129
8129
|
} else if (key.ease && !next) {
|
|
8130
|
-
throw new CompileError(`${where}: last key carries an easing but has nothing to ease to
|
|
8130
|
+
throw new CompileError(`${where}: last key carries an easing but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_EASING');
|
|
8131
8131
|
}
|
|
8132
8132
|
out.push(entry);
|
|
8133
8133
|
}
|
|
@@ -8176,7 +8176,7 @@ function compileDrawOrder(
|
|
|
8176
8176
|
slots: readonly ModelSlot[],
|
|
8177
8177
|
): ModelKey[] {
|
|
8178
8178
|
const where = `animation "${animName}" drawOrder`;
|
|
8179
|
-
if (!keys.length) throw new CompileError(`${where}: no keys
|
|
8179
|
+
if (!keys.length) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8180
8180
|
const indexOf = new Map(slots.map((s, i) => [s.name, i]));
|
|
8181
8181
|
|
|
8182
8182
|
const out: ModelKey[] = [];
|
|
@@ -8184,7 +8184,7 @@ function compileDrawOrder(
|
|
|
8184
8184
|
const key = keys[i];
|
|
8185
8185
|
const time = keyTime(key.t);
|
|
8186
8186
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8187
|
-
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})
|
|
8187
|
+
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
|
|
8188
8188
|
}
|
|
8189
8189
|
checkKeyTime(where, time, key.t, duration);
|
|
8190
8190
|
// No offsets = "back to the setup draw order", which is the parser's own
|
|
@@ -8199,19 +8199,19 @@ function compileDrawOrder(
|
|
|
8199
8199
|
for (const off of key.offsets) {
|
|
8200
8200
|
const index = indexOf.get(off.slot);
|
|
8201
8201
|
if (index === undefined) {
|
|
8202
|
-
throw new CompileError(`${where} at t=${key.t}: slot "${off.slot}" is not one this rig emits
|
|
8202
|
+
throw new CompileError(`${where} at t=${key.t}: slot "${off.slot}" is not one this rig emits`, 'CE_COMPILE_DRAW_ORDER_SLOT_UNKNOWN');
|
|
8203
8203
|
}
|
|
8204
8204
|
if (seen.has(off.slot)) {
|
|
8205
|
-
throw new CompileError(`${where} at t=${key.t}: slot "${off.slot}" is offset twice in one key
|
|
8205
|
+
throw new CompileError(`${where} at t=${key.t}: slot "${off.slot}" is offset twice in one key`, 'CE_COMPILE_DRAW_ORDER_SLOT_DUPLICATE');
|
|
8206
8206
|
}
|
|
8207
8207
|
if (!Number.isInteger(off.offset)) {
|
|
8208
|
-
throw new CompileError(`${where} at t=${key.t}: slot "${off.slot}" offset ${off.offset} is not a whole number
|
|
8208
|
+
throw new CompileError(`${where} at t=${key.t}: slot "${off.slot}" offset ${off.offset} is not a whole number`, 'CE_COMPILE_DRAW_ORDER_OFFSET_NONINTEGER');
|
|
8209
8209
|
}
|
|
8210
8210
|
const landing = index + off.offset;
|
|
8211
8211
|
if (landing < 0 || landing >= slots.length) {
|
|
8212
8212
|
throw new CompileError(
|
|
8213
8213
|
`${where} at t=${key.t}: slot "${off.slot}" is at index ${index} and offset ${off.offset} puts it at ` +
|
|
8214
|
-
`${landing}, outside the ${slots.length} emitted slots`,
|
|
8214
|
+
`${landing}, outside the ${slots.length} emitted slots`, 'CE_COMPILE_DRAW_ORDER_OFFSET_OUTSIDE_SLOTS',
|
|
8215
8215
|
);
|
|
8216
8216
|
}
|
|
8217
8217
|
seen.add(off.slot);
|
|
@@ -8256,27 +8256,27 @@ function compileEvents(
|
|
|
8256
8256
|
events: Record<string, RigEvent>,
|
|
8257
8257
|
): ModelKey[] {
|
|
8258
8258
|
const where = `animation "${animName}" events`;
|
|
8259
|
-
if (!keys.length) throw new CompileError(`${where}: no keys
|
|
8259
|
+
if (!keys.length) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8260
8260
|
|
|
8261
8261
|
const out: ModelKey[] = [];
|
|
8262
8262
|
for (let i = 0; i < keys.length; i++) {
|
|
8263
8263
|
const key = keys[i];
|
|
8264
8264
|
if (typeof key.name !== 'string' || key.name.length === 0) {
|
|
8265
|
-
throw new CompileError(`${where}: key ${i} has no "name"; an event key fires an event by name
|
|
8265
|
+
throw new CompileError(`${where}: key ${i} has no "name"; an event key fires an event by name`, 'CE_COMPILE_EVENT_KEY_NAME_MISSING');
|
|
8266
8266
|
}
|
|
8267
8267
|
const declared = events[key.name];
|
|
8268
8268
|
if (declared === undefined) {
|
|
8269
8269
|
const known = Object.keys(events);
|
|
8270
8270
|
throw new CompileError(
|
|
8271
8271
|
`${where} at t=${key.t}: event "${key.name}" is not declared in the rig spec's "events" block; ` +
|
|
8272
|
-
(known.length ? `declared: ${known.join(', ')}` : 'that block is empty or absent'),
|
|
8272
|
+
(known.length ? `declared: ${known.join(', ')}` : 'that block is empty or absent'), 'CE_COMPILE_EVENT_KEY_EVENT_UNKNOWN',
|
|
8273
8273
|
);
|
|
8274
8274
|
}
|
|
8275
8275
|
const time = keyTime(key.t);
|
|
8276
8276
|
if (i > 0 && time < (out[i - 1].time as number)) {
|
|
8277
8277
|
throw new CompileError(
|
|
8278
8278
|
`${where}: key times must not go backwards (at t=${key.t}, after t=${String(out[i - 1].time)}) — ` +
|
|
8279
|
-
'the parser writes frames in array order and never sorts them',
|
|
8279
|
+
'the parser writes frames in array order and never sorts them', 'CE_COMPILE_EVENT_KEY_TIMES_BACKWARDS',
|
|
8280
8280
|
);
|
|
8281
8281
|
}
|
|
8282
8282
|
checkKeyTime(where, time, key.t, duration);
|
|
@@ -8284,19 +8284,19 @@ function compileEvents(
|
|
|
8284
8284
|
const entry: ModelKey = { time, name: key.name };
|
|
8285
8285
|
if (key.int !== undefined) {
|
|
8286
8286
|
if (!Number.isInteger(key.int)) {
|
|
8287
|
-
throw new CompileError(`${where} at t=${key.t}: int ${String(key.int)} is not an integer
|
|
8287
|
+
throw new CompileError(`${where} at t=${key.t}: int ${String(key.int)} is not an integer`, 'CE_COMPILE_EVENT_KEY_INT_INVALID');
|
|
8288
8288
|
}
|
|
8289
8289
|
entry.int = key.int;
|
|
8290
8290
|
}
|
|
8291
8291
|
if (key.float !== undefined) {
|
|
8292
8292
|
if (!Number.isFinite(key.float)) {
|
|
8293
|
-
throw new CompileError(`${where} at t=${key.t}: float ${String(key.float)} is not finite
|
|
8293
|
+
throw new CompileError(`${where} at t=${key.t}: float ${String(key.float)} is not finite`, 'CE_COMPILE_EVENT_KEY_FLOAT_NONFINITE');
|
|
8294
8294
|
}
|
|
8295
8295
|
entry.float = f32(key.float);
|
|
8296
8296
|
}
|
|
8297
8297
|
if (key.string !== undefined) {
|
|
8298
8298
|
if (typeof key.string !== 'string') {
|
|
8299
|
-
throw new CompileError(`${where} at t=${key.t}: string ${JSON.stringify(key.string)} is not a string
|
|
8299
|
+
throw new CompileError(`${where} at t=${key.t}: string ${JSON.stringify(key.string)} is not a string`, 'CE_COMPILE_EVENT_KEY_STRING_INVALID');
|
|
8300
8300
|
}
|
|
8301
8301
|
entry.string = key.string;
|
|
8302
8302
|
}
|
|
@@ -8306,10 +8306,10 @@ function compileEvents(
|
|
|
8306
8306
|
if (declared.audio === undefined) {
|
|
8307
8307
|
throw new CompileError(
|
|
8308
8308
|
`${where} at t=${key.t}: ${field} is set but event "${key.name}" declares no "audio"; ` +
|
|
8309
|
-
`the parser reads ${field} only for an event with an audio path, so it would be dropped in silence`,
|
|
8309
|
+
`the parser reads ${field} only for an event with an audio path, so it would be dropped in silence`, 'CE_COMPILE_EVENT_AUDIO_OVERRIDE_NO_AUDIO',
|
|
8310
8310
|
);
|
|
8311
8311
|
}
|
|
8312
|
-
if (!Number.isFinite(v)) throw new CompileError(`${where} at t=${key.t}: ${field} ${String(v)} is not finite
|
|
8312
|
+
if (!Number.isFinite(v)) throw new CompileError(`${where} at t=${key.t}: ${field} ${String(v)} is not finite`, 'CE_COMPILE_EVENT_AUDIO_NUMBER_NONFINITE');
|
|
8313
8313
|
entry[field] = f32(v);
|
|
8314
8314
|
}
|
|
8315
8315
|
out.push(entry);
|
|
@@ -8378,7 +8378,7 @@ function compileConstraintTrack(
|
|
|
8378
8378
|
// An array by the time this runs (`parseMotionSpec`); an EMPTY one is a
|
|
8379
8379
|
// format rule rather than a shape — the parser reads key 0, finds nothing and
|
|
8380
8380
|
// skips the timeline, which is assertion A34's silent case.
|
|
8381
|
-
if (keys.length === 0) throw new CompileError(`${where}: no keys
|
|
8381
|
+
if (keys.length === 0) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8382
8382
|
|
|
8383
8383
|
const read = (key: MotionIkTrack['keys'][number] | MotionTransformTrack['keys'][number], field: string): unknown =>
|
|
8384
8384
|
(key as unknown as Record<string, unknown>)[field];
|
|
@@ -8397,7 +8397,7 @@ function compileConstraintTrack(
|
|
|
8397
8397
|
throw new CompileError(
|
|
8398
8398
|
`${where}: key ${i} (t=${key.t}) names "${field}" and key 0 does not. Every key of ${article} ${group} timeline ` +
|
|
8399
8399
|
'is read with its own default, so a field stated on some keys and not others snaps to the default on ' +
|
|
8400
|
-
'the rest — state it on every key or on none.',
|
|
8400
|
+
'the rest — state it on every key or on none.', 'CE_COMPILE_CONSTRAINT_KEY_FIELD_ADDED',
|
|
8401
8401
|
);
|
|
8402
8402
|
}
|
|
8403
8403
|
for (const field of first) {
|
|
@@ -8405,7 +8405,7 @@ function compileConstraintTrack(
|
|
|
8405
8405
|
throw new CompileError(
|
|
8406
8406
|
`${where}: key 0 names "${field}" and key ${i} (t=${key.t}) does not. Every key of ${article} ${group} timeline ` +
|
|
8407
8407
|
`is read with its own default, so "${field}" would snap to ` +
|
|
8408
|
-
`${JSON.stringify(defaultOf(shape, field))} at t=${key.t} — state it on every key or on none.`,
|
|
8408
|
+
`${JSON.stringify(defaultOf(shape, field))} at t=${key.t} — state it on every key or on none.`, 'CE_COMPILE_CONSTRAINT_KEY_FIELD_OMITTED',
|
|
8409
8409
|
);
|
|
8410
8410
|
}
|
|
8411
8411
|
});
|
|
@@ -8425,7 +8425,7 @@ function compileConstraintTrack(
|
|
|
8425
8425
|
const next = keys[i + 1];
|
|
8426
8426
|
const time = keyTime(key.t);
|
|
8427
8427
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8428
|
-
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})
|
|
8428
|
+
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
|
|
8429
8429
|
}
|
|
8430
8430
|
checkKeyTime(where, time, key.t, duration);
|
|
8431
8431
|
|
|
@@ -8434,14 +8434,14 @@ function compileConstraintTrack(
|
|
|
8434
8434
|
const v = read(key, channel.field);
|
|
8435
8435
|
if (v === undefined) continue;
|
|
8436
8436
|
if (typeof v !== 'number' || !Number.isFinite(v)) {
|
|
8437
|
-
throw new CompileError(`${where} (t=${key.t}): ${channel.field} is ${JSON.stringify(v)}, not a finite number
|
|
8437
|
+
throw new CompileError(`${where} (t=${key.t}): ${channel.field} is ${JSON.stringify(v)}, not a finite number`, 'CE_COMPILE_CONSTRAINT_KEY_NUMBER_NONFINITE');
|
|
8438
8438
|
}
|
|
8439
8439
|
const bounds = shape.range[channel.field];
|
|
8440
8440
|
if (bounds && (v < bounds[0] || v > bounds[1])) {
|
|
8441
8441
|
throw new CompileError(
|
|
8442
8442
|
`${where} (t=${key.t}): ${channel.field} is ${v}, outside ${bounds[0]}..${
|
|
8443
8443
|
bounds[1] === Infinity ? '∞' : bounds[1]
|
|
8444
|
-
} — the runtime documents it as ${channel.field === 'mix' ? 'a percentage 0-1' : 'a distance'}`,
|
|
8444
|
+
} — the runtime documents it as ${channel.field === 'mix' ? 'a percentage 0-1' : 'a distance'}`, 'CE_COMPILE_CONSTRAINT_KEY_RANGE_INVALID',
|
|
8445
8445
|
);
|
|
8446
8446
|
}
|
|
8447
8447
|
entry[channel.field] = f32(v);
|
|
@@ -8458,23 +8458,23 @@ function compileConstraintTrack(
|
|
|
8458
8458
|
continue;
|
|
8459
8459
|
}
|
|
8460
8460
|
if (typeof v !== 'boolean') {
|
|
8461
|
-
throw new CompileError(`${where} (t=${key.t}): ${flag.field} is ${JSON.stringify(v)}, not true or false
|
|
8461
|
+
throw new CompileError(`${where} (t=${key.t}): ${flag.field} is ${JSON.stringify(v)}, not true or false`, 'CE_COMPILE_CONSTRAINT_KEY_FLAG_NOT_BOOLEAN');
|
|
8462
8462
|
}
|
|
8463
8463
|
entry[flag.field] = v;
|
|
8464
8464
|
}
|
|
8465
8465
|
|
|
8466
8466
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
8467
|
-
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one
|
|
8467
|
+
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one`, 'CE_COMPILE_KEY_EASING_AND_CURVE');
|
|
8468
8468
|
}
|
|
8469
8469
|
if (key.curve !== undefined) {
|
|
8470
|
-
if (!next) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to
|
|
8470
|
+
if (!next) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_CURVE');
|
|
8471
8471
|
entry.curve = rawCurve(key.curve, shape.channels.length, where, String(key.t));
|
|
8472
8472
|
} else if (key.ease !== undefined && next) {
|
|
8473
8473
|
if (key.ease === 'stepped') {
|
|
8474
8474
|
entry.curve = 'stepped';
|
|
8475
8475
|
} else {
|
|
8476
8476
|
const handles = motion.easings?.[key.ease];
|
|
8477
|
-
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"
|
|
8477
|
+
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"`, 'CE_COMPILE_KEY_EASING_UNKNOWN');
|
|
8478
8478
|
const t2 = keyTime(next.t);
|
|
8479
8479
|
entry.curve = easingCurve(
|
|
8480
8480
|
handles,
|
|
@@ -8485,7 +8485,7 @@ function compileConstraintTrack(
|
|
|
8485
8485
|
);
|
|
8486
8486
|
}
|
|
8487
8487
|
} else if (key.ease !== undefined && !next) {
|
|
8488
|
-
throw new CompileError(`${where}: last key carries an easing but has nothing to ease to
|
|
8488
|
+
throw new CompileError(`${where}: last key carries an easing but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_EASING');
|
|
8489
8489
|
}
|
|
8490
8490
|
out.push(entry);
|
|
8491
8491
|
}
|
|
@@ -8599,7 +8599,7 @@ export function deformGeometryOf(
|
|
|
8599
8599
|
if (!isModelVertexAttachment(att)) {
|
|
8600
8600
|
throw new CompileError(
|
|
8601
8601
|
`${where}: a deform timeline keys the vertices of an attachment, and this one is a "${type}" — ` +
|
|
8602
|
-
'it has no vertex array to deform. Deformable types: mesh, boundingbox, clipping, path.',
|
|
8602
|
+
'it has no vertex array to deform. Deformable types: mesh, boundingbox, clipping, path.', 'CE_COMPILE_DEFORM_ATTACHMENT_TYPE_INVALID',
|
|
8603
8603
|
);
|
|
8604
8604
|
}
|
|
8605
8605
|
if (att.kind === 'mesh') {
|
|
@@ -8666,7 +8666,7 @@ export function deformGeometryOf(
|
|
|
8666
8666
|
const vertex = vertices.bindings[v];
|
|
8667
8667
|
const n = vertex.length;
|
|
8668
8668
|
if (n < 1) {
|
|
8669
|
-
throw new CompileError(`${where}: the attachment's weighted vertex run has a bone count of ${String(n)} at index ${at}
|
|
8669
|
+
throw new CompileError(`${where}: the attachment's weighted vertex run has a bone count of ${String(n)} at index ${at}`, 'CE_COMPILE_DEFORM_WEIGHT_BONE_COUNT_INVALID');
|
|
8670
8670
|
}
|
|
8671
8671
|
if (n === 1) {
|
|
8672
8672
|
bones.add(vertex[0].bone);
|
|
@@ -8727,7 +8727,7 @@ export function deformGeometryOf(
|
|
|
8727
8727
|
let sum = 0;
|
|
8728
8728
|
for (const binding of vertices.bindings[v]) {
|
|
8729
8729
|
const m = transforms.get(binding.bone);
|
|
8730
|
-
if (!m) throw new CompileError(`${where}: vertex ${v} binds bone "${binding.bone}", which has no setup transform
|
|
8730
|
+
if (!m) throw new CompileError(`${where}: vertex ${v} binds bone "${binding.bone}", which has no setup transform`, 'CE_COMPILE_VERTEX_BIND_TRANSFORM_MISSING');
|
|
8731
8731
|
const weight = binding.weight;
|
|
8732
8732
|
const [x, y] = toWorld(m, binding.x, binding.y);
|
|
8733
8733
|
wx += x * weight;
|
|
@@ -8834,7 +8834,7 @@ function compileSequenceTrack(
|
|
|
8834
8834
|
`${where}: attachment "${track.attachment}" carries no "sequence" block, so there is no series to step. The ` +
|
|
8835
8835
|
'parser gives every region and mesh a series of ONE region (`readSequence(null)` is `new Sequence(1, ' +
|
|
8836
8836
|
'false)`), so this timeline would load and show that region under every mode at every time. Give the ' +
|
|
8837
|
-
'attachment a "sequence" in the rig spec, or remove the track.',
|
|
8837
|
+
'attachment a "sequence" in the rig spec, or remove the track.', 'CE_COMPILE_SEQUENCE_ATTACHMENT_NO_SERIES',
|
|
8838
8838
|
);
|
|
8839
8839
|
}
|
|
8840
8840
|
if (attachment.kind === 'linkedmesh' && attachment.timelines) {
|
|
@@ -8844,23 +8844,23 @@ function compileSequenceTrack(
|
|
|
8844
8844
|
`is not false), so its \`timelineAttachment\` is "${source}" (\`SkeletonJson.js:437-448\`) and ` +
|
|
8845
8845
|
'`SequenceTimeline` applies only where the slot\'s attachment\'s `timelineAttachment` is the one it was ' +
|
|
8846
8846
|
`built for — a key aimed at the link is applied to nothing. Key "${source}" instead (the link steps its own ` +
|
|
8847
|
-
'series by it), or set "timelines": false on the link.',
|
|
8847
|
+
'series by it), or set "timelines": false on the link.', 'CE_COMPILE_SEQUENCE_LINK_TIMELINE_INERT',
|
|
8848
8848
|
);
|
|
8849
8849
|
}
|
|
8850
|
-
if (track.keys.length === 0) throw new CompileError(`${where}: no keys
|
|
8850
|
+
if (track.keys.length === 0) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8851
8851
|
const out: ModelKey[] = [];
|
|
8852
8852
|
for (let i = 0; i < track.keys.length; i++) {
|
|
8853
8853
|
const key = track.keys[i];
|
|
8854
8854
|
const time = keyTime(key.t);
|
|
8855
8855
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8856
|
-
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})
|
|
8856
|
+
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
|
|
8857
8857
|
}
|
|
8858
8858
|
checkKeyTime(where, time, key.t, duration);
|
|
8859
8859
|
if (key.index !== undefined && key.index >= series.count) {
|
|
8860
8860
|
throw new CompileError(
|
|
8861
8861
|
`${where} (t=${key.t}): index ${key.index} is past the end of a ${series.count}-frame series (frames 0 to ` +
|
|
8862
8862
|
`${series.count - 1}). \`Sequence.resolveIndex\` clamps it to the last frame, so the key would start on a ` +
|
|
8863
|
-
'frame it does not name. `index` is 0-based.',
|
|
8863
|
+
'frame it does not name. `index` is 0-based.', 'CE_COMPILE_SEQUENCE_KEY_INDEX_OUTSIDE_SERIES',
|
|
8864
8864
|
);
|
|
8865
8865
|
}
|
|
8866
8866
|
const entry: ModelKey = { time };
|
|
@@ -8886,14 +8886,14 @@ function compileDeformTrack(
|
|
|
8886
8886
|
// An array by the time this runs (`parseMotionSpec`); an EMPTY one is a
|
|
8887
8887
|
// format rule rather than a shape — the parser reads key 0, finds nothing and
|
|
8888
8888
|
// skips the timeline, which is assertion A34's silent case.
|
|
8889
|
-
if (keys.length === 0) throw new CompileError(`${where}: no keys
|
|
8889
|
+
if (keys.length === 0) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8890
8890
|
|
|
8891
8891
|
const out: ModelKey[] = [];
|
|
8892
8892
|
for (let i = 0; i < keys.length; i++) {
|
|
8893
8893
|
const key = keys[i];
|
|
8894
8894
|
const time = keyTime(key.t);
|
|
8895
8895
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8896
|
-
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})
|
|
8896
|
+
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
|
|
8897
8897
|
}
|
|
8898
8898
|
checkKeyTime(where, time, key.t, duration);
|
|
8899
8899
|
// -- a key that states a MODEL rather than a run (issue #294) ------------
|
|
@@ -8910,7 +8910,7 @@ function compileDeformTrack(
|
|
|
8910
8910
|
throw new CompileError(
|
|
8911
8911
|
`${where} (t=${key.t}): the key carries both a "transform" and a "vertices" run, and they are two answers to ` +
|
|
8912
8912
|
'one question. Authored offsets and a stated model do not combine, for the same reason a mesh cannot carry ' +
|
|
8913
|
-
'both a "generator" and authored geometry — drop one.',
|
|
8913
|
+
'both a "generator" and authored geometry — drop one.', 'CE_COMPILE_DEFORM_TRANSFORM_AND_VERTICES',
|
|
8914
8914
|
);
|
|
8915
8915
|
}
|
|
8916
8916
|
if (key.offset !== undefined || key.fromVertex !== undefined) {
|
|
@@ -8918,14 +8918,14 @@ function compileDeformTrack(
|
|
|
8918
8918
|
`${where} (t=${key.t}): the key states a "transform" and a start index. A transform is a model of the whole ` +
|
|
8919
8919
|
`attachment and is evaluated over all ${geometry.vertexCount} of its vertices, so it always starts at deform ` +
|
|
8920
8920
|
'index 0. A model applied to part of a run leaves a step at the run\'s edge — write the partial run by hand ' +
|
|
8921
|
-
'if that is what you mean.',
|
|
8921
|
+
'if that is what you mean.', 'CE_COMPILE_DEFORM_TRANSFORM_START_INDEX',
|
|
8922
8922
|
);
|
|
8923
8923
|
}
|
|
8924
8924
|
if (geometry.setup === null) {
|
|
8925
8925
|
throw new CompileError(
|
|
8926
8926
|
`${where} (t=${key.t}): a "transform" states where every vertex of this attachment goes, and this one has a ` +
|
|
8927
8927
|
`vertex the arithmetic cannot place — ${geometry.setupWhy}. Fix the weights, or write the bind-space pairs ` +
|
|
8928
|
-
'yourself and start the run with "offset".',
|
|
8928
|
+
'yourself and start the run with "offset".', 'CE_COMPILE_DEFORM_TRANSFORM_SETUP_UNRESOLVED',
|
|
8929
8929
|
);
|
|
8930
8930
|
}
|
|
8931
8931
|
const report = evaluateDeformTransform(
|
|
@@ -8942,7 +8942,7 @@ function compileDeformTrack(
|
|
|
8942
8942
|
// assumed: a silent mismatch here is the overrun A35 exists for.
|
|
8943
8943
|
throw new CompileError(
|
|
8944
8944
|
`${where} (t=${key.t}): the transform produced ${report.offsets.length} numbers for ` +
|
|
8945
|
-
`${geometry.vertexCount} vertices`,
|
|
8945
|
+
`${geometry.vertexCount} vertices`, 'CE_COMPILE_DEFORM_TRANSFORM_OFFSET_COUNT',
|
|
8946
8946
|
);
|
|
8947
8947
|
}
|
|
8948
8948
|
// -- one displacement per vertex -> one pair per influence (issue #389) -
|
|
@@ -8958,7 +8958,7 @@ function compileDeformTrack(
|
|
|
8958
8958
|
if (run.length !== geometry.deformLength) {
|
|
8959
8959
|
throw new CompileError(
|
|
8960
8960
|
`${where} (t=${key.t}): the transform expanded to ${run.length} numbers and this attachment's deform ` +
|
|
8961
|
-
`array is ${geometry.deformLength} long`,
|
|
8961
|
+
`array is ${geometry.deformLength} long`, 'CE_COMPILE_DEFORM_TRANSFORM_RUN_LENGTH',
|
|
8962
8962
|
);
|
|
8963
8963
|
}
|
|
8964
8964
|
generated.push({
|
|
@@ -8992,7 +8992,7 @@ function compileDeformTrack(
|
|
|
8992
8992
|
if (key.offset !== undefined && key.fromVertex !== undefined) {
|
|
8993
8993
|
throw new CompileError(
|
|
8994
8994
|
`${where} (t=${key.t}): a key gives its start as "offset" (an index into the deform array) or as ` +
|
|
8995
|
-
'"fromVertex" (a vertex index rigc translates), never both',
|
|
8995
|
+
'"fromVertex" (a vertex index rigc translates), never both', 'CE_COMPILE_DEFORM_START_SOURCES_CONFLICT',
|
|
8996
8996
|
);
|
|
8997
8997
|
}
|
|
8998
8998
|
const run = key.vertices ?? null;
|
|
@@ -9003,7 +9003,7 @@ function compileDeformTrack(
|
|
|
9003
9003
|
if (key.offset !== undefined || key.fromVertex !== undefined) {
|
|
9004
9004
|
throw new CompileError(
|
|
9005
9005
|
`${where} (t=${key.t}): the key has no "vertices", which is the format's way of saying "back to the ` +
|
|
9006
|
-
'setup pose" — so there is nothing for a start index to point at. Drop the offset, or give it a run.',
|
|
9006
|
+
'setup pose" — so there is nothing for a start index to point at. Drop the offset, or give it a run.', 'CE_COMPILE_DEFORM_SETUP_KEY_START_INDEX',
|
|
9007
9007
|
);
|
|
9008
9008
|
}
|
|
9009
9009
|
out.push({ time });
|
|
@@ -9012,7 +9012,7 @@ function compileDeformTrack(
|
|
|
9012
9012
|
if (!Array.isArray(run) || run.length === 0) {
|
|
9013
9013
|
throw new CompileError(
|
|
9014
9014
|
`${where} (t=${key.t}): "vertices" is ${JSON.stringify(key.vertices)}; give an array of x, y offsets, ` +
|
|
9015
|
-
'or omit it entirely for "back to the setup pose"',
|
|
9015
|
+
'or omit it entirely for "back to the setup pose"', 'CE_COMPILE_DEFORM_VERTICES_NOT_ARRAY',
|
|
9016
9016
|
);
|
|
9017
9017
|
}
|
|
9018
9018
|
// ⛔ No parity clause here, and its absence is the rule (issue #576).
|
|
@@ -9042,7 +9042,7 @@ function compileDeformTrack(
|
|
|
9042
9042
|
// format holds, and the half that refused was the authoring half.
|
|
9043
9043
|
for (const n of run) {
|
|
9044
9044
|
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
9045
|
-
throw new CompileError(`${where} (t=${key.t}): "vertices" holds a non-finite value ${JSON.stringify(n)}
|
|
9045
|
+
throw new CompileError(`${where} (t=${key.t}): "vertices" holds a non-finite value ${JSON.stringify(n)}`, 'CE_COMPILE_DEFORM_VERTEX_VALUE_NONFINITE');
|
|
9046
9046
|
}
|
|
9047
9047
|
}
|
|
9048
9048
|
const start = deformStart(key, run.length, geometry, where);
|
|
@@ -9051,7 +9051,7 @@ function compileDeformTrack(
|
|
|
9051
9051
|
`${where} (t=${key.t}): the run starts at deform index ${start} and is ${run.length} long, which ends at ` +
|
|
9052
9052
|
`${start + run.length}; this attachment's deform array is ${geometry.deformLength} long ` +
|
|
9053
9053
|
`(${geometry.weighted ? `${geometry.deformLength / 2} bone influences` : `${geometry.vertexCount} vertices`}). ` +
|
|
9054
|
-
'The parser copies into a Float32Array, so everything past the end is dropped without a word.',
|
|
9054
|
+
'The parser copies into a Float32Array, so everything past the end is dropped without a word.', 'CE_COMPILE_DEFORM_RUN_OUTSIDE_ATTACHMENT',
|
|
9055
9055
|
);
|
|
9056
9056
|
}
|
|
9057
9057
|
const entry: ModelKey = { time };
|
|
@@ -9122,7 +9122,7 @@ function deformStart(
|
|
|
9122
9122
|
if (key.offset !== undefined) {
|
|
9123
9123
|
if (!Number.isInteger(key.offset) || key.offset < 0) {
|
|
9124
9124
|
throw new CompileError(
|
|
9125
|
-
`${where} (t=${key.t}): offset is ${JSON.stringify(key.offset)}; it is an index into the deform array, so a whole number ≥ 0`,
|
|
9125
|
+
`${where} (t=${key.t}): offset is ${JSON.stringify(key.offset)}; it is an index into the deform array, so a whole number ≥ 0`, 'CE_COMPILE_DEFORM_OFFSET_INVALID',
|
|
9126
9126
|
);
|
|
9127
9127
|
}
|
|
9128
9128
|
// ⛔ An odd `offset` is not refused either, and it went the same way as the
|
|
@@ -9140,7 +9140,7 @@ function deformStart(
|
|
|
9140
9140
|
if (key.fromVertex === undefined) return 0;
|
|
9141
9141
|
const from = key.fromVertex;
|
|
9142
9142
|
if (!Number.isInteger(from) || from < 0) {
|
|
9143
|
-
throw new CompileError(`${where} (t=${key.t}): fromVertex is ${JSON.stringify(from)}; it is a vertex index, so a whole number ≥ 0
|
|
9143
|
+
throw new CompileError(`${where} (t=${key.t}): fromVertex is ${JSON.stringify(from)}; it is a vertex index, so a whole number ≥ 0`, 'CE_COMPILE_DEFORM_FROM_VERTEX_INVALID');
|
|
9144
9144
|
}
|
|
9145
9145
|
// ⌈⌉ rather than ÷, because an odd run REACHES a last vertex without covering
|
|
9146
9146
|
// it: its final number is that vertex's x and the y beside it stays at setup
|
|
@@ -9151,7 +9151,7 @@ function deformStart(
|
|
|
9151
9151
|
if (from + covered > geometry.vertexCount) {
|
|
9152
9152
|
throw new CompileError(
|
|
9153
9153
|
`${where} (t=${key.t}): fromVertex ${from} plus ${covered} vertex offset(s) runs to vertex ${from + covered}, ` +
|
|
9154
|
-
`and the attachment has ${geometry.vertexCount}`,
|
|
9154
|
+
`and the attachment has ${geometry.vertexCount}`, 'CE_COMPILE_DEFORM_FROM_VERTEX_OUTSIDE_ATTACHMENT',
|
|
9155
9155
|
);
|
|
9156
9156
|
}
|
|
9157
9157
|
if (!geometry.weighted) return from * 2;
|
|
@@ -9164,7 +9164,7 @@ function deformStart(
|
|
|
9164
9164
|
'is not a thing that array can hold: the world offset of a multi-bone vertex is the weighted sum of a ' +
|
|
9165
9165
|
'per-bone offset in each bone\'s own bind space, so rigc will not guess one for you. Either key the ' +
|
|
9166
9166
|
'control bone instead, or write the bind-space pairs yourself and start the run with "offset" ' +
|
|
9167
|
-
`(vertex ${from} starts at deform index ${2 * counts.slice(0, from).reduce((a, b) => a + b, 0)}).`,
|
|
9167
|
+
`(vertex ${from} starts at deform index ${2 * counts.slice(0, from).reduce((a, b) => a + b, 0)}).`, 'CE_COMPILE_DEFORM_WEIGHTED_FROM_VERTEX_AMBIGUOUS',
|
|
9168
9168
|
);
|
|
9169
9169
|
}
|
|
9170
9170
|
let start = 0;
|
|
@@ -9192,21 +9192,21 @@ function deformKeyCurve(
|
|
|
9192
9192
|
next: ModelKey | undefined,
|
|
9193
9193
|
): ModelKey {
|
|
9194
9194
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
9195
|
-
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one
|
|
9195
|
+
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one`, 'CE_COMPILE_KEY_EASING_AND_CURVE');
|
|
9196
9196
|
}
|
|
9197
9197
|
if (key.curve !== undefined) {
|
|
9198
|
-
if (next === undefined) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to
|
|
9198
|
+
if (next === undefined) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_CURVE');
|
|
9199
9199
|
entry.curve = rawCurve(key.curve, 1, where, String(key.t));
|
|
9200
9200
|
return entry;
|
|
9201
9201
|
}
|
|
9202
9202
|
if (key.ease === undefined) return entry;
|
|
9203
|
-
if (next === undefined) throw new CompileError(`${where}: last key carries an easing but has nothing to ease to
|
|
9203
|
+
if (next === undefined) throw new CompileError(`${where}: last key carries an easing but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_EASING');
|
|
9204
9204
|
if (key.ease === 'stepped') {
|
|
9205
9205
|
entry.curve = 'stepped';
|
|
9206
9206
|
return entry;
|
|
9207
9207
|
}
|
|
9208
9208
|
const handles = motion.easings?.[key.ease];
|
|
9209
|
-
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"
|
|
9209
|
+
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"`, 'CE_COMPILE_KEY_EASING_UNKNOWN');
|
|
9210
9210
|
// The one channel runs 0 -> 1; the VALUE being held is the geometry, so the
|
|
9211
9211
|
// hold test compares the two keys' runs rather than those two constants.
|
|
9212
9212
|
entry.curve = easingCurve(handles, entry.time as number, keyTime(keys[index + 1].t), [0], [1], sameDeform(entry, next));
|
|
@@ -9242,7 +9242,7 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9242
9242
|
if (named.length > 1) {
|
|
9243
9243
|
throw new CompileError(
|
|
9244
9244
|
`animation "${animName}": a track names more than one target (${named.join(', ')}); the list is ` +
|
|
9245
|
-
`${targetFields.join('/')} and a track names exactly one`,
|
|
9245
|
+
`${targetFields.join('/')} and a track names exactly one`, 'CE_COMPILE_TRACK_TARGETS_MULTIPLE',
|
|
9246
9246
|
);
|
|
9247
9247
|
}
|
|
9248
9248
|
const family = constraintFamilyOf(track);
|
|
@@ -9252,12 +9252,12 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9252
9252
|
const who = direct === undefined ? `group "${String(track.group)}"` : `${label} "${direct}"`;
|
|
9253
9253
|
if (!(track.property in tracks)) {
|
|
9254
9254
|
throw new CompileError(
|
|
9255
|
-
`animation "${animName}": ${who} has no timeline "${track.property}" (it has: ${Object.keys(tracks).join(', ')})`,
|
|
9255
|
+
`animation "${animName}": ${who} has no timeline "${track.property}" (it has: ${Object.keys(tracks).join(', ')})`, 'CE_COMPILE_CONSTRAINT_TRACK_PROPERTY_UNKNOWN',
|
|
9256
9256
|
);
|
|
9257
9257
|
}
|
|
9258
9258
|
if (direct !== undefined) return [direct];
|
|
9259
9259
|
const members = motion.groups?.[String(track.group)];
|
|
9260
|
-
if (!members) throw new CompileError(`animation "${animName}": unknown group "${String(track.group)}"
|
|
9260
|
+
if (!members) throw new CompileError(`animation "${animName}": unknown group "${String(track.group)}"`, 'CE_COMPILE_ANIMATION_GROUP_UNKNOWN');
|
|
9261
9261
|
return members;
|
|
9262
9262
|
}
|
|
9263
9263
|
// A constraint timeline with no constraint named. Worth its own refusal: the
|
|
@@ -9268,12 +9268,12 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9268
9268
|
if (owning.length) {
|
|
9269
9269
|
throw new CompileError(
|
|
9270
9270
|
`animation "${animName}": "${track.property}" is a ${owning.map((f) => CONSTRAINT_TRACK_FAMILIES[f].label).join(' / ')} ` +
|
|
9271
|
-
`timeline, and this track names no constraint — put the name in "${owning.join('" or "')}"`,
|
|
9271
|
+
`timeline, and this track names no constraint — put the name in "${owning.join('" or "')}"`, 'CE_COMPILE_CONSTRAINT_TRACK_TARGET_MISSING',
|
|
9272
9272
|
);
|
|
9273
9273
|
}
|
|
9274
9274
|
const isBoneTrack = track.property in BONE_TRACKS;
|
|
9275
9275
|
if (isBoneTrack && !track.bone && !track.group) {
|
|
9276
|
-
throw new CompileError(`animation "${animName}": "${track.property}" is a bone track but no bone is named
|
|
9276
|
+
throw new CompileError(`animation "${animName}": "${track.property}" is a bone track but no bone is named`, 'CE_COMPILE_BONE_TRACK_TARGET_MISSING');
|
|
9277
9277
|
}
|
|
9278
9278
|
if (!isBoneTrack && track.bone) {
|
|
9279
9279
|
// The bone family's sibling of `compileTrack`'s slot refusal, raised here for
|
|
@@ -9288,7 +9288,7 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9288
9288
|
throw new CompileError(
|
|
9289
9289
|
`animation "${animName}" bone "${track.bone}" has no timeline "${track.property}" ` +
|
|
9290
9290
|
`(it has: ${Object.keys(BONE_TRACKS).join(', ')})` +
|
|
9291
|
-
(track.property in SLOT_TRACKS ? `. "${track.property}" is a slot timeline — put the name in "slot"` : ''),
|
|
9291
|
+
(track.property in SLOT_TRACKS ? `. "${track.property}" is a slot timeline — put the name in "slot"` : ''), 'CE_COMPILE_BONE_TRACK_PROPERTY_UNKNOWN',
|
|
9292
9292
|
);
|
|
9293
9293
|
}
|
|
9294
9294
|
if (track.bone) return [track.bone];
|
|
@@ -9300,7 +9300,7 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9300
9300
|
// following the part and two objects moving together, which reads as a
|
|
9301
9301
|
// composite.
|
|
9302
9302
|
const members = motion.groups?.[track.group];
|
|
9303
|
-
if (!members) throw new CompileError(`animation "${animName}": unknown group "${track.group}"
|
|
9303
|
+
if (!members) throw new CompileError(`animation "${animName}": unknown group "${track.group}"`, 'CE_COMPILE_ANIMATION_GROUP_UNKNOWN');
|
|
9304
9304
|
// 🚨 The group is the one target shape whose FAMILY is decided by the
|
|
9305
9305
|
// property, and three tables decide it: `constraintFamilyOf` asks
|
|
9306
9306
|
// `property in PHYSICS_TRACKS`, this function asks `property in BONE_TRACKS`,
|
|
@@ -9326,12 +9326,12 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9326
9326
|
`animation "${animName}" group "${track.group}" has no timeline "${track.property}" ` +
|
|
9327
9327
|
`(a bone group has: ${Object.keys(BONE_TRACKS).join(', ')}; ` +
|
|
9328
9328
|
`a slot group has: ${Object.keys(SLOT_TRACKS).join(', ')}; ` +
|
|
9329
|
-
`a ${CONSTRAINT_TRACK_FAMILIES.physics.label} group has: ${Object.keys(PHYSICS_TRACKS).join(', ')})`,
|
|
9329
|
+
`a ${CONSTRAINT_TRACK_FAMILIES.physics.label} group has: ${Object.keys(PHYSICS_TRACKS).join(', ')})`, 'CE_COMPILE_GROUP_TRACK_PROPERTY_UNKNOWN',
|
|
9330
9330
|
);
|
|
9331
9331
|
}
|
|
9332
9332
|
return members;
|
|
9333
9333
|
}
|
|
9334
|
-
throw new CompileError(`animation "${animName}": a track targets neither slot nor group
|
|
9334
|
+
throw new CompileError(`animation "${animName}": a track targets neither slot nor group`, 'CE_COMPILE_TRACK_TARGET_MISSING');
|
|
9335
9335
|
}
|
|
9336
9336
|
|
|
9337
9337
|
/**
|
|
@@ -9352,23 +9352,23 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9352
9352
|
function checkMotionGroups(motion: MotionSpec): void {
|
|
9353
9353
|
for (const [name, members] of Object.entries(motion.groups ?? {})) {
|
|
9354
9354
|
if (!Array.isArray(members)) {
|
|
9355
|
-
throw new CompileError(`group "${name}" is ${JSON.stringify(members)}; it is an array of member names
|
|
9355
|
+
throw new CompileError(`group "${name}" is ${JSON.stringify(members)}; it is an array of member names`, 'CE_COMPILE_GROUP_MEMBERS_NOT_ARRAY');
|
|
9356
9356
|
}
|
|
9357
9357
|
if (members.length === 0) {
|
|
9358
9358
|
throw new CompileError(
|
|
9359
|
-
`group "${name}" declares no members, so every track naming it would compile no timeline and gate green`,
|
|
9359
|
+
`group "${name}" declares no members, so every track naming it would compile no timeline and gate green`, 'CE_COMPILE_GROUP_MEMBERS_EMPTY',
|
|
9360
9360
|
);
|
|
9361
9361
|
}
|
|
9362
9362
|
const seen = new Set<string>();
|
|
9363
9363
|
members.forEach((member, i) => {
|
|
9364
9364
|
if (typeof member !== 'string' || member.length === 0) {
|
|
9365
|
-
throw new CompileError(`group "${name}" member ${i} is ${JSON.stringify(member)}; it is a bone or slot name
|
|
9365
|
+
throw new CompileError(`group "${name}" member ${i} is ${JSON.stringify(member)}; it is a bone or slot name`, 'CE_COMPILE_GROUP_MEMBER_NOT_NAME');
|
|
9366
9366
|
}
|
|
9367
9367
|
if (seen.has(member)) {
|
|
9368
9368
|
throw new CompileError(
|
|
9369
9369
|
`group "${name}" names member "${member}" twice (at index ${members.indexOf(member)} and ${i}). ` +
|
|
9370
9370
|
'Member order is what `stagger` counts and what a per-member value map is read against, so a repeat is ' +
|
|
9371
|
-
'two different delays and two different values for one bone.',
|
|
9371
|
+
'two different delays and two different values for one bone.', 'CE_COMPILE_GROUP_MEMBER_DUPLICATE',
|
|
9372
9372
|
);
|
|
9373
9373
|
}
|
|
9374
9374
|
seen.add(member);
|
|
@@ -9414,13 +9414,13 @@ function resolveMemberTrack(
|
|
|
9414
9414
|
if ((carriesMap || carriesModel) && track.group === undefined && track.bone === undefined) {
|
|
9415
9415
|
throw new CompileError(
|
|
9416
9416
|
`${at}: per-member values need a target whose members are named — put them on a track that names a "group", or ` +
|
|
9417
|
-
'a "bone" for the one-member case',
|
|
9417
|
+
'a "bone" for the one-member case', 'CE_COMPILE_MEMBER_VALUES_TARGET_UNNAMED',
|
|
9418
9418
|
);
|
|
9419
9419
|
}
|
|
9420
9420
|
if (carriesMap && targetKind === 'bone') {
|
|
9421
9421
|
throw new CompileError(
|
|
9422
9422
|
`${at}: a per-member "v" map names members, and a bone track has one target rather than members. Write the ` +
|
|
9423
|
-
'value directly, or move the track onto a group.',
|
|
9423
|
+
'value directly, or move the track onto a group.', 'CE_COMPILE_BONE_MEMBER_VALUE_MAP',
|
|
9424
9424
|
);
|
|
9425
9425
|
}
|
|
9426
9426
|
|
|
@@ -9437,7 +9437,7 @@ function resolveMemberTrack(
|
|
|
9437
9437
|
throw new CompileError(
|
|
9438
9438
|
`${where}: this key carries both a "derive" model and a per-member "v" map — two answers to one question. ` +
|
|
9439
9439
|
'The model states the arithmetic and the depths that produced the numbers; the map states the numbers. ' +
|
|
9440
|
-
'Pick one.',
|
|
9440
|
+
'Pick one.', 'CE_COMPILE_MEMBER_DERIVE_AND_VALUE_MAP',
|
|
9441
9441
|
);
|
|
9442
9442
|
}
|
|
9443
9443
|
if (key.derive === undefined) {
|
|
@@ -9452,7 +9452,7 @@ function resolveMemberTrack(
|
|
|
9452
9452
|
if (!known.has(named)) {
|
|
9453
9453
|
throw new CompileError(
|
|
9454
9454
|
`${where}: the value map names "${named}", which group "${target}" does not declare ` +
|
|
9455
|
-
`(its members are: ${targets.join(', ')})`,
|
|
9455
|
+
`(its members are: ${targets.join(', ')})`, 'CE_COMPILE_MEMBER_VALUE_NAME_UNKNOWN',
|
|
9456
9456
|
);
|
|
9457
9457
|
}
|
|
9458
9458
|
}
|
|
@@ -9464,7 +9464,7 @@ function resolveMemberTrack(
|
|
|
9464
9464
|
`(it states: ${Object.keys(table).join(', ') || 'nothing'}). ` +
|
|
9465
9465
|
'A member is refused rather than defaulted: an absent value is exactly the thing a map of six is ' +
|
|
9466
9466
|
'written to make visible, and defaulting it to the identity would key that one bone with a different ' +
|
|
9467
|
-
'motion in silence.',
|
|
9467
|
+
'motion in silence.', 'CE_COMPILE_MEMBER_VALUE_MISSING',
|
|
9468
9468
|
);
|
|
9469
9469
|
}
|
|
9470
9470
|
const value = table[name];
|
|
@@ -9500,7 +9500,7 @@ function resolveMemberTrack(
|
|
|
9500
9500
|
throw new CompileError(
|
|
9501
9501
|
`${where}: derive reads member "${name}"'s setup position, and this rig declares no such bone. ` +
|
|
9502
9502
|
'A model over a group of slots or constraints has no coordinate to be evaluated at — state the values ' +
|
|
9503
|
-
'as a "v" map.',
|
|
9503
|
+
'as a "v" map.', 'CE_COMPILE_MEMBER_DERIVE_TARGET_NOT_BONE',
|
|
9504
9504
|
);
|
|
9505
9505
|
}
|
|
9506
9506
|
parents.add(bone.parent ?? '(root)');
|
|
@@ -9511,7 +9511,7 @@ function resolveMemberTrack(
|
|
|
9511
9511
|
`${where}: derive measures every member's "${coordinate}" from its parent's origin and "about" says where the ` +
|
|
9512
9512
|
`axis crosses it, so one space is what makes the members comparable. These members sit under ` +
|
|
9513
9513
|
`${parents.size} different parents (${[...parents].join(', ')}), and their coordinates are therefore ` +
|
|
9514
|
-
'measured from different origins. Split the track by parent, or state the values as a "v" map.',
|
|
9514
|
+
'measured from different origins. Split the track by parent, or state the values as a "v" map.', 'CE_COMPILE_MEMBER_DERIVE_PARENTS_DIFFER',
|
|
9515
9515
|
);
|
|
9516
9516
|
}
|
|
9517
9517
|
const report = evaluateTrackDerive(key.derive, track.property, members, onModelGrid, where);
|
|
@@ -9571,7 +9571,7 @@ function compileTrack(
|
|
|
9571
9571
|
if (shape === undefined) {
|
|
9572
9572
|
throw new CompileError(
|
|
9573
9573
|
`animation "${animName}" slot "${target}" has no timeline "${track.property}" ` +
|
|
9574
|
-
`(it has: ${Object.keys(SLOT_TRACKS).join(', ')})`,
|
|
9574
|
+
`(it has: ${Object.keys(SLOT_TRACKS).join(', ')})`, 'CE_COMPILE_SLOT_TRACK_PROPERTY_UNKNOWN',
|
|
9575
9575
|
);
|
|
9576
9576
|
}
|
|
9577
9577
|
// 🚨 Raised here, with the target and before the keys, for the same reason as
|
|
@@ -9596,10 +9596,10 @@ function compileTrack(
|
|
|
9596
9596
|
`${where}: slot "${target}" declares no setup "dark", and an "${shape}" timeline poses a slot's dark colour — ` +
|
|
9597
9597
|
'the runtime allocates one only for a slot whose setup pose has it, so applying this animation throws ' +
|
|
9598
9598
|
`instead of tinting. Give slot "${target}" a \`dark\` in the rig spec (the colour it holds at rest), or ` +
|
|
9599
|
-
`key "${shape === 'rgba2' ? 'rgba' : 'rgb'}" if only the light colour moves`,
|
|
9599
|
+
`key "${shape === 'rgba2' ? 'rgba' : 'rgb'}" if only the light colour moves`, 'CE_COMPILE_DARK_TIMELINE_SETUP_DARK_MISSING',
|
|
9600
9600
|
);
|
|
9601
9601
|
}
|
|
9602
|
-
if (!track.keys.length) throw new CompileError(`${where}: no keys
|
|
9602
|
+
if (!track.keys.length) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
9603
9603
|
|
|
9604
9604
|
const out: ModelKey[] = [];
|
|
9605
9605
|
for (let i = 0; i < track.keys.length; i++) {
|
|
@@ -9607,13 +9607,13 @@ function compileTrack(
|
|
|
9607
9607
|
const next = track.keys[i + 1];
|
|
9608
9608
|
const time = keyTime(key.t + shift);
|
|
9609
9609
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
9610
|
-
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})
|
|
9610
|
+
throw new CompileError(`${where}: key times must strictly increase (at t=${key.t})`, 'CE_COMPILE_KEY_TIMES_NOT_INCREASING');
|
|
9611
9611
|
}
|
|
9612
9612
|
checkKeyTime(where, time, key.t, duration);
|
|
9613
9613
|
|
|
9614
9614
|
if (shape === 'attachment') {
|
|
9615
9615
|
if (key.v !== null && typeof key.v !== 'string') {
|
|
9616
|
-
throw new CompileError(`${where}: attachment key value must be a string or null
|
|
9616
|
+
throw new CompileError(`${where}: attachment key value must be a string or null`, 'CE_COMPILE_ATTACHMENT_KEY_VALUE_INVALID');
|
|
9617
9617
|
}
|
|
9618
9618
|
// Resolved against every skin, not just `default` — see `SlotAttachmentIndex`.
|
|
9619
9619
|
const known = attachments.bySlot.get(target);
|
|
@@ -9628,10 +9628,10 @@ function compileTrack(
|
|
|
9628
9628
|
`(searched: ${attachments.searched.join(', ')}) — ` +
|
|
9629
9629
|
(known === undefined || known.size === 0
|
|
9630
9630
|
? 'the slot has no attachments at all'
|
|
9631
|
-
: `the slot has: ${[...known].join(', ')}`),
|
|
9631
|
+
: `the slot has: ${[...known].join(', ')}`), 'CE_COMPILE_ATTACHMENT_KEY_UNKNOWN',
|
|
9632
9632
|
);
|
|
9633
9633
|
}
|
|
9634
|
-
if (key.ease) throw new CompileError(`${where}: attachment keys cannot carry an easing
|
|
9634
|
+
if (key.ease) throw new CompileError(`${where}: attachment keys cannot carry an easing`, 'CE_COMPILE_ATTACHMENT_KEY_EASING');
|
|
9635
9635
|
// Attachment timelines are inherently stepped — exactly what lip-sync wants.
|
|
9636
9636
|
out.push({ time, name: key.v });
|
|
9637
9637
|
continue;
|
|
@@ -9650,7 +9650,7 @@ function compileTrack(
|
|
|
9650
9650
|
// a second thing to keep in step.
|
|
9651
9651
|
const { spelling, channels } = COLOUR_KEYS[shape];
|
|
9652
9652
|
const colourOf = (v: number[]): Record<string, string | number> => colourKey(shape, v);
|
|
9653
|
-
if (!Array.isArray(key.v)) throw new CompileError(`${where}: ${shape} key value must be ${spelling}
|
|
9653
|
+
if (!Array.isArray(key.v)) throw new CompileError(`${where}: ${shape} key value must be ${spelling}`, 'CE_COMPILE_SLOT_KEY_VALUE_SHAPE');
|
|
9654
9654
|
if (shape === 'alpha' && key.v.length === 1 && !(Number.isFinite(key.v[0]) && key.v[0] >= 0 && key.v[0] <= 1)) {
|
|
9655
9655
|
// The one colour shape written as a number rather than a byte, so the one
|
|
9656
9656
|
// that is not clamped onto 0..1 by `channelHex` on the way out. The
|
|
@@ -9661,15 +9661,15 @@ function compileTrack(
|
|
|
9661
9661
|
// clamping is choosing the value.
|
|
9662
9662
|
throw new CompileError(
|
|
9663
9663
|
`${where}: key at t=${key.t} is ${String(key.v[0])}; an alpha is a number from 0 to 1 — the runtime clamps ` +
|
|
9664
|
-
'the posed alpha to that range after interpolating, so a value outside it is one no pose ever holds',
|
|
9664
|
+
'the posed alpha to that range after interpolating, so a value outside it is one no pose ever holds', 'CE_COMPILE_ALPHA_KEY_RANGE_INVALID',
|
|
9665
9665
|
);
|
|
9666
9666
|
}
|
|
9667
9667
|
const entry: ModelKey = { time, ...colourOf(key.v) };
|
|
9668
9668
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
9669
|
-
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one
|
|
9669
|
+
throw new CompileError(`${where}: a key carries both a named easing and a raw curve; pick one`, 'CE_COMPILE_KEY_EASING_AND_CURVE');
|
|
9670
9670
|
}
|
|
9671
9671
|
if (key.curve !== undefined) {
|
|
9672
|
-
if (!next) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to
|
|
9672
|
+
if (!next) throw new CompileError(`${where}: last key carries a curve but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_CURVE');
|
|
9673
9673
|
entry.curve = rawCurve(key.curve, channels, where, String(key.t));
|
|
9674
9674
|
out.push(entry);
|
|
9675
9675
|
continue;
|
|
@@ -9679,9 +9679,9 @@ function compileTrack(
|
|
|
9679
9679
|
entry.curve = 'stepped';
|
|
9680
9680
|
} else {
|
|
9681
9681
|
const handles = motion.easings?.[key.ease];
|
|
9682
|
-
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"
|
|
9682
|
+
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"`, 'CE_COMPILE_KEY_EASING_UNKNOWN');
|
|
9683
9683
|
if (!Array.isArray(next.v)) {
|
|
9684
|
-
throw new CompileError(`${where}: ${shape} key value must be ${spelling}
|
|
9684
|
+
throw new CompileError(`${where}: ${shape} key value must be ${spelling}`, 'CE_COMPILE_SLOT_KEY_VALUE_SHAPE');
|
|
9685
9685
|
}
|
|
9686
9686
|
const t2 = keyTime(next.t + shift);
|
|
9687
9687
|
// 4 numbers per channel, in the format's own channel order — 16 for
|
|
@@ -9696,7 +9696,7 @@ function compileTrack(
|
|
|
9696
9696
|
entry.curve = easingCurve(handles, time, t2, key.v, next.v, held);
|
|
9697
9697
|
}
|
|
9698
9698
|
} else if (key.ease && !next) {
|
|
9699
|
-
throw new CompileError(`${where}: last key carries an easing but has nothing to ease to
|
|
9699
|
+
throw new CompileError(`${where}: last key carries an easing but has nothing to ease to`, 'CE_COMPILE_LAST_KEY_EASING');
|
|
9700
9700
|
}
|
|
9701
9701
|
out.push(entry);
|
|
9702
9702
|
}
|