spine-rigc 2.56.0 → 2.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/docs/AUTHORING.md +709 -264
- package/package.json +1 -1
- package/skills/rigc/SKILL.md +7 -2
- package/skills/rigc-face/SKILL.md +3 -1
- package/skills/rigc-ingest/SKILL.md +3 -1
- package/skills/rigc-motion/SKILL.md +3 -1
- package/skills/rigc-rigging/SKILL.md +3 -1
- package/src/atlas.ts +10 -10
- package/src/cli/core_commands.ts +52 -0
- package/src/cli/shared.ts +119 -3
- package/src/compile.ts +356 -334
- package/src/deformgen.ts +28 -28
- package/src/emit_spine.ts +5 -5
- package/src/errors.ts +8 -0
- package/src/guide.ts +4 -9
- package/src/keys.ts +4 -4
- package/src/meshsplit.ts +683 -0
- 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
|
@@ -99,6 +99,7 @@ import {
|
|
|
99
99
|
type MeshGeometry,
|
|
100
100
|
type MeshVertexWeight,
|
|
101
101
|
} from './mesh.ts';
|
|
102
|
+
import { meshPieces, shellWord } from './meshsplit.ts';
|
|
102
103
|
import { Plate, readPlate } from '../tools/plate.ts';
|
|
103
104
|
import {
|
|
104
105
|
extractRegion,
|
|
@@ -888,7 +889,7 @@ function refuseNamesTheEditorCouldKeyDifferently(
|
|
|
888
889
|
throw new CompileError(
|
|
889
890
|
`${found.length} pair(s) of ${what.noun} have no one order: ${what.why}` +
|
|
890
891
|
`${spelled.join('. ')}` +
|
|
891
|
-
(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',
|
|
892
893
|
);
|
|
893
894
|
}
|
|
894
895
|
|
|
@@ -968,7 +969,7 @@ export function f32(n: number): number {
|
|
|
968
969
|
}
|
|
969
970
|
// Unreachable — nine significant digits name every float32. Stated rather than
|
|
970
971
|
// assumed, because a fallback here would be a number nobody chose.
|
|
971
|
-
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');
|
|
972
973
|
}
|
|
973
974
|
|
|
974
975
|
/**
|
|
@@ -1143,7 +1144,7 @@ function checkKeyTime(where: string, time: number, authored: number, duration: n
|
|
|
1143
1144
|
throw new CompileError(
|
|
1144
1145
|
`${where}: key at ${at} is ${f32(past)}s past the declared duration ${duration}s — nothing that plays this ` +
|
|
1145
1146
|
`animation for the duration it declares ever reaches it. Move the key to ${duration}, or declare the ` +
|
|
1146
|
-
`duration you meant.`,
|
|
1147
|
+
`duration you meant.`, 'CE_COMPILE_KEY_AFTER_DURATION',
|
|
1147
1148
|
);
|
|
1148
1149
|
}
|
|
1149
1150
|
|
|
@@ -1155,7 +1156,7 @@ function channelHex(v: number): string {
|
|
|
1155
1156
|
}
|
|
1156
1157
|
|
|
1157
1158
|
function rgbaHex(v: number[]): string {
|
|
1158
|
-
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');
|
|
1159
1160
|
return v.map(channelHex).join('');
|
|
1160
1161
|
}
|
|
1161
1162
|
|
|
@@ -1175,7 +1176,7 @@ function rgbaHex(v: number[]): string {
|
|
|
1175
1176
|
* by: `readCurve(…, 0..3, light r/g/b/a)` then `readCurve(…, 4..6, dark r/g/b)`.
|
|
1176
1177
|
*/
|
|
1177
1178
|
function rgba2Hex(v: number[]): { light: string; dark: string } {
|
|
1178
|
-
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');
|
|
1179
1180
|
return { light: v.slice(0, 4).map(channelHex).join(''), dark: v.slice(4, 7).map(channelHex).join('') };
|
|
1180
1181
|
}
|
|
1181
1182
|
|
|
@@ -1225,7 +1226,7 @@ const COLOUR_KEYS: Record<SlotColourShape, { channels: number; spelling: string;
|
|
|
1225
1226
|
function colourKey(shape: SlotColourShape, v: number[]): Record<string, string | number> {
|
|
1226
1227
|
const { channels, write } = COLOUR_KEYS[shape];
|
|
1227
1228
|
if (v.length !== channels) {
|
|
1228
|
-
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');
|
|
1229
1230
|
}
|
|
1230
1231
|
return write(v);
|
|
1231
1232
|
}
|
|
@@ -1653,7 +1654,7 @@ function readJson<T>(path: string): T {
|
|
|
1653
1654
|
try {
|
|
1654
1655
|
text = readFileSync(path, 'utf8');
|
|
1655
1656
|
} catch (err) {
|
|
1656
|
-
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');
|
|
1657
1658
|
}
|
|
1658
1659
|
try {
|
|
1659
1660
|
// A parse failure gets a line/column appended to the runtime's own
|
|
@@ -1661,7 +1662,7 @@ function readJson<T>(path: string): T {
|
|
|
1661
1662
|
// cannot say where.
|
|
1662
1663
|
return parseJsonWithPosition(text) as T;
|
|
1663
1664
|
} catch (err) {
|
|
1664
|
-
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');
|
|
1665
1666
|
}
|
|
1666
1667
|
}
|
|
1667
1668
|
|
|
@@ -1881,13 +1882,13 @@ function fromLoosePng(
|
|
|
1881
1882
|
// Left to `readFileSync` this arrives as a raw ENOENT with a stack, which
|
|
1882
1883
|
// is the tool telling an agent about its own internals instead of about
|
|
1883
1884
|
// the rig. The validator's messages are the UI, and so are these.
|
|
1884
|
-
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');
|
|
1885
1886
|
}
|
|
1886
1887
|
// A loose part IS a page of the atlas this route emits, so it is refused the
|
|
1887
1888
|
// way a pack's page is (issue #732): by the one reader's sentence, as a
|
|
1888
1889
|
// CompileError, rather than as that reader's throw with a stack under it.
|
|
1889
1890
|
const header = readPngHeader(absPath);
|
|
1890
|
-
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');
|
|
1891
1892
|
const info = header.info;
|
|
1892
1893
|
// Page name is the PNG path *relative to the atlas file*, so the viewer
|
|
1893
1894
|
// resolves it the way every Spine consumer does: against the atlas URL.
|
|
@@ -1912,7 +1913,7 @@ interface AtlasSource {
|
|
|
1912
1913
|
}
|
|
1913
1914
|
|
|
1914
1915
|
function readAtlasIn(path: string): AtlasSource {
|
|
1915
|
-
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');
|
|
1916
1917
|
const parsed = parseAtlasText(readFileSync(path, 'utf8'));
|
|
1917
1918
|
const byName = new Map<string, { region: AtlasRegion; page: ParsedAtlas['pages'][number] }>();
|
|
1918
1919
|
for (const page of parsed.pages) {
|
|
@@ -1924,7 +1925,7 @@ function readAtlasIn(path: string): AtlasSource {
|
|
|
1924
1925
|
if (!byName.has(key)) byName.set(key, { region, page });
|
|
1925
1926
|
}
|
|
1926
1927
|
}
|
|
1927
|
-
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');
|
|
1928
1929
|
// 🔒 **What each page IS, where the pack is opened** (issue #732). Every page
|
|
1929
1930
|
// the pack names reaches the gate — the atlas this build emits is the pack's
|
|
1930
1931
|
// own text — so a page no reader can open is refused here, all of them in
|
|
@@ -1957,7 +1958,7 @@ function readAtlasIn(path: string): AtlasSource {
|
|
|
1957
1958
|
if (unread.length > 0) {
|
|
1958
1959
|
throw new CompileError(
|
|
1959
1960
|
`--atlas-in ${path}: ${unread.length} of its ${parsed.pages.length} page(s) cannot be read as PNG, and ` +
|
|
1960
|
-
`nothing was compiled against the pack — ${unread.join('; ')}`,
|
|
1961
|
+
`nothing was compiled against the pack — ${unread.join('; ')}`, 'CE_COMPILE_ATLAS_PAGES_UNREADABLE',
|
|
1961
1962
|
);
|
|
1962
1963
|
}
|
|
1963
1964
|
return { path, dir: dirname(path), parsed, byName, grids };
|
|
@@ -2027,13 +2028,13 @@ function resolveFromAtlas(
|
|
|
2027
2028
|
throw new CompileError(
|
|
2028
2029
|
`image "${relPath}" resolves to region "${region}", which the atlas at ${atlas.path} does not have. ` +
|
|
2029
2030
|
(near.length ? `Did you mean ${near.map((n) => JSON.stringify(n)).join(', ')}? ` : '') +
|
|
2030
|
-
`The atlas declares ${known.length} region(s): ${known.join(', ')}`,
|
|
2031
|
+
`The atlas declares ${known.length} region(s): ${known.join(', ')}`, 'CE_COMPILE_ATLAS_REGION_UNKNOWN',
|
|
2031
2032
|
);
|
|
2032
2033
|
}
|
|
2033
2034
|
const absPath = resolve(atlas.dir, found.page.name);
|
|
2034
2035
|
if (!existsSync(absPath)) {
|
|
2035
2036
|
throw new CompileError(
|
|
2036
|
-
`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',
|
|
2037
2038
|
);
|
|
2038
2039
|
}
|
|
2039
2040
|
// The rectangle ON THE PAGE, which at a quarter turn is not the one `bounds:`
|
|
@@ -2048,7 +2049,7 @@ function resolveFromAtlas(
|
|
|
2048
2049
|
throw new CompileError(
|
|
2049
2050
|
`region "${region}" is at ${found.region.x},${found.region.y} sized ${rectW}x${rectH} on a ` +
|
|
2050
2051
|
`${found.page.width}x${found.page.height} page in ${atlas.path}; the rectangle runs off the page, so its ` +
|
|
2051
|
-
'UVs would leave the texture',
|
|
2052
|
+
'UVs would leave the texture', 'CE_COMPILE_ATLAS_REGION_OUTSIDE_PAGE',
|
|
2052
2053
|
);
|
|
2053
2054
|
}
|
|
2054
2055
|
const info = readPngInfo(absPath);
|
|
@@ -2341,7 +2342,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2341
2342
|
return fn();
|
|
2342
2343
|
} catch (err) {
|
|
2343
2344
|
if (err instanceof CompileError && !err.message.includes(motionPath)) {
|
|
2344
|
-
throw new CompileError(`${motionPath}: ${err.message}
|
|
2345
|
+
throw new CompileError(`${motionPath}: ${err.message}`, err.code);
|
|
2345
2346
|
}
|
|
2346
2347
|
throw err;
|
|
2347
2348
|
}
|
|
@@ -2356,7 +2357,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2356
2357
|
// does not — the class of wrongness that loads, plays and lies.
|
|
2357
2358
|
if (motion.archetype !== rig.name) {
|
|
2358
2359
|
throw new CompileError(
|
|
2359
|
-
`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',
|
|
2360
2361
|
);
|
|
2361
2362
|
}
|
|
2362
2363
|
});
|
|
@@ -2377,7 +2378,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2377
2378
|
if (!noStage && (stageWidth === undefined || stageHeight === undefined)) {
|
|
2378
2379
|
throw new CompileError(
|
|
2379
2380
|
'no stage size: give the rig spec a `skeleton.width`/`skeleton.height`, or compile against a cut manifest whose `crop` states them, ' +
|
|
2380
|
-
'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',
|
|
2381
2382
|
);
|
|
2382
2383
|
}
|
|
2383
2384
|
/**
|
|
@@ -2433,7 +2434,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2433
2434
|
partDir: string | null = dirname(resolve(baseDir, relPath)),
|
|
2434
2435
|
): CompiledImage => {
|
|
2435
2436
|
if (seenRegions.has(region)) {
|
|
2436
|
-
throw new CompileError(`duplicate region name "${region}" (${relPath})
|
|
2437
|
+
throw new CompileError(`duplicate region name "${region}" (${relPath})`, 'CE_COMPILE_REGION_NAME_DUPLICATE');
|
|
2437
2438
|
}
|
|
2438
2439
|
const loosePath = resolve(baseDir, relPath);
|
|
2439
2440
|
const img =
|
|
@@ -2484,7 +2485,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2484
2485
|
throw new CompileError(
|
|
2485
2486
|
`${where}: "${relPath}" and the art already atlased as region "${region}" are two different files — ` +
|
|
2486
2487
|
`${wanted} and ${already}. An atlas region is named by its PNG's basename, so only one of the two can ` +
|
|
2487
|
-
'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',
|
|
2488
2489
|
);
|
|
2489
2490
|
};
|
|
2490
2491
|
|
|
@@ -2515,7 +2516,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2515
2516
|
`${where}: sequence ${frame} is the region "${region}", and there is no PNG for it at ${wanted}. A ` +
|
|
2516
2517
|
`sequence's frames are the regions "<stem><start + i>", zero-padded to "digits" — here stem ` +
|
|
2517
2518
|
`${JSON.stringify(stem)}, start ${seq.start ?? 1}, digits ${seq.digits ?? 0} — and the compiler draws ` +
|
|
2518
|
-
'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',
|
|
2519
2520
|
);
|
|
2520
2521
|
}
|
|
2521
2522
|
if (atlasIn !== null && !atlasIn.byName.has(region)) {
|
|
@@ -2525,7 +2526,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2525
2526
|
(near.length ? `Did you mean ${near.map((n) => JSON.stringify(n)).join(', ')}? ` : '') +
|
|
2526
2527
|
`A sequence's frames are the regions "<stem><start + i>", zero-padded to "digits" — here stem ` +
|
|
2527
2528
|
`${JSON.stringify(stem)}, start ${seq.start ?? 1}, digits ${seq.digits ?? 0} — and the compiler draws ` +
|
|
2528
|
-
'no frame in place of another.',
|
|
2529
|
+
'no frame in place of another.', 'CE_COMPILE_SEQUENCE_FRAME_REGION_MISSING',
|
|
2529
2530
|
);
|
|
2530
2531
|
}
|
|
2531
2532
|
const already = regionSource.get(region);
|
|
@@ -2534,7 +2535,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2534
2535
|
throw new CompileError(
|
|
2535
2536
|
`${where}: sequence ${frame} is the region "${region}", and the art already atlased under that name is ` +
|
|
2536
2537
|
`${already}, not ${wanted}. One region name is one set of pixels, so this frame would draw the other ` +
|
|
2537
|
-
"file's. Rename one of them.",
|
|
2538
|
+
"file's. Rename one of them.", 'CE_COMPILE_SEQUENCE_FRAME_FILE_CONFLICT',
|
|
2538
2539
|
);
|
|
2539
2540
|
}
|
|
2540
2541
|
// The directory the frame NAMES are relative to — the images root, not
|
|
@@ -2574,7 +2575,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2574
2575
|
for (const part of parts) {
|
|
2575
2576
|
if (!rigSlotIndex.has(part.slot)) {
|
|
2576
2577
|
throw new CompileError(
|
|
2577
|
-
`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',
|
|
2578
2579
|
);
|
|
2579
2580
|
}
|
|
2580
2581
|
}
|
|
@@ -2588,7 +2589,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2588
2589
|
const at = rigSlotIndex.get(part.slot)!;
|
|
2589
2590
|
if (at < orderCursor) {
|
|
2590
2591
|
throw new CompileError(
|
|
2591
|
-
`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',
|
|
2592
2593
|
);
|
|
2593
2594
|
}
|
|
2594
2595
|
orderCursor = at;
|
|
@@ -2603,33 +2604,33 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2603
2604
|
if (meshParts.length > meshBudget) {
|
|
2604
2605
|
throw new CompileError(
|
|
2605
2606
|
`${meshParts.length} mesh slot(s) declared but the rig "${rig.name}" allows ${meshBudget}` +
|
|
2606
|
-
' — 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',
|
|
2607
2608
|
);
|
|
2608
2609
|
}
|
|
2609
2610
|
for (const part of meshParts) {
|
|
2610
2611
|
if (isBasePlate(part, manifest!)) {
|
|
2611
2612
|
// A base plate mesh is a full-frame canvas every frame.
|
|
2612
|
-
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');
|
|
2613
2614
|
}
|
|
2614
2615
|
const spec = part.mesh!;
|
|
2615
2616
|
const kind = spec.kind ?? 'ring';
|
|
2616
2617
|
if (kind === 'ring') {
|
|
2617
2618
|
if (!part.polygon?.length) {
|
|
2618
|
-
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');
|
|
2619
2620
|
}
|
|
2620
2621
|
if (spec.hull !== 'polygon') {
|
|
2621
|
-
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');
|
|
2622
2623
|
}
|
|
2623
2624
|
if (!spec.center || spec.inner === undefined) {
|
|
2624
|
-
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');
|
|
2625
2626
|
}
|
|
2626
2627
|
} else {
|
|
2627
2628
|
if (!spec.rows || !spec.chain?.length) {
|
|
2628
|
-
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');
|
|
2629
2630
|
}
|
|
2630
2631
|
}
|
|
2631
2632
|
if (!meshControlBones(part).length) {
|
|
2632
|
-
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');
|
|
2633
2634
|
}
|
|
2634
2635
|
}
|
|
2635
2636
|
|
|
@@ -2655,7 +2656,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2655
2656
|
const img = addImage(part.image, manifestDir!, isBasePlate(part, manifest!));
|
|
2656
2657
|
if (img.width !== win.w || img.height !== win.h) {
|
|
2657
2658
|
throw new CompileError(
|
|
2658
|
-
`${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',
|
|
2659
2660
|
);
|
|
2660
2661
|
}
|
|
2661
2662
|
slotAttachments.set(part.slot, [img.region]);
|
|
@@ -2690,7 +2691,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2690
2691
|
const img = addImage(relPath, manifestDir!, false);
|
|
2691
2692
|
if (img.width !== win.w || img.height !== win.h) {
|
|
2692
2693
|
throw new CompileError(
|
|
2693
|
-
`${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',
|
|
2694
2695
|
);
|
|
2695
2696
|
}
|
|
2696
2697
|
names.push(img.region);
|
|
@@ -2713,11 +2714,11 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2713
2714
|
for (const skinName of skinNames) {
|
|
2714
2715
|
for (const [slotName, placeholders] of Object.entries(skinParts.get(skinName)!.attachments)) {
|
|
2715
2716
|
if (!rigSlotIndex.has(slotName)) {
|
|
2716
|
-
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');
|
|
2717
2718
|
}
|
|
2718
2719
|
if (slotAttachments.has(slotName)) {
|
|
2719
2720
|
throw new CompileError(
|
|
2720
|
-
`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',
|
|
2721
2722
|
);
|
|
2722
2723
|
}
|
|
2723
2724
|
const names = rigAttachmentNames.get(slotName) ?? [];
|
|
@@ -2808,7 +2809,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2808
2809
|
transforms = computeWorldTransforms(bones);
|
|
2809
2810
|
exactFrames = computeExactFrameTransforms(bones);
|
|
2810
2811
|
} catch (err) {
|
|
2811
|
-
if (err instanceof TransformError) throw new CompileError(err.message);
|
|
2812
|
+
if (err instanceof TransformError) throw new CompileError(err.message, 'CE_COMPILE_SETUP_TRANSFORM_INVALID');
|
|
2812
2813
|
throw err;
|
|
2813
2814
|
}
|
|
2814
2815
|
|
|
@@ -2816,7 +2817,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2816
2817
|
for (const name of meshControlBones(part)) {
|
|
2817
2818
|
if (!boneNames.has(name)) {
|
|
2818
2819
|
throw new CompileError(
|
|
2819
|
-
`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',
|
|
2820
2821
|
);
|
|
2821
2822
|
}
|
|
2822
2823
|
}
|
|
@@ -2908,7 +2909,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2908
2909
|
// here down is the half that needs the rig in front of it.
|
|
2909
2910
|
if (setup !== undefined && rigSlot.attachment !== undefined) {
|
|
2910
2911
|
throw new CompileError(
|
|
2911
|
-
`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',
|
|
2912
2913
|
);
|
|
2913
2914
|
}
|
|
2914
2915
|
let setupAttachment: string | null;
|
|
@@ -2921,7 +2922,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2921
2922
|
else if (empty) setupAttachment = null;
|
|
2922
2923
|
else {
|
|
2923
2924
|
throw new CompileError(
|
|
2924
|
-
`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',
|
|
2925
2926
|
);
|
|
2926
2927
|
}
|
|
2927
2928
|
if (setupAttachment !== null && !names.includes(setupAttachment)) {
|
|
@@ -2970,10 +2971,10 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
2970
2971
|
`no art: ${found} — ${supply}, ${many ? 'fix the paths' : 'fix the path'} in the manifest, or state the ` +
|
|
2971
2972
|
'setup pose as null';
|
|
2972
2973
|
}
|
|
2973
|
-
throw new CompileError(message);
|
|
2974
|
+
throw new CompileError(message, 'CE_COMPILE_SETUP_ATTACHMENT_UNAVAILABLE');
|
|
2974
2975
|
}
|
|
2975
2976
|
if (setup?.color && rigSlot.color !== undefined) {
|
|
2976
|
-
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');
|
|
2977
2978
|
}
|
|
2978
2979
|
// The setup attachment is held as the value it is, `null` included; leaving
|
|
2979
2980
|
// the key out for `null` is the emitter's (`emitSlots`).
|
|
@@ -3000,7 +3001,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3000
3001
|
const mesh = part.mesh ? buildMesh(part, manifest!, bones, transforms, exactFrames, rigSlot.bone) : null;
|
|
3001
3002
|
for (const name of names) {
|
|
3002
3003
|
const img = images.find((im) => im.region === name);
|
|
3003
|
-
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');
|
|
3004
3005
|
if (mesh) {
|
|
3005
3006
|
// Every state of a mesh slot gets the SAME geometry. That is what makes
|
|
3006
3007
|
// an attachment swap mid-deform safe: the control bone's pose means the
|
|
@@ -3088,7 +3089,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3088
3089
|
`${budgeted.length}, "meshTriangles": <triangles one mesh may carry> }\` to the rig spec: geometry rigc ` +
|
|
3089
3090
|
'built is geometry it will not ship unmeasured, and `A13_MESH_BUDGET` has nothing to measure against ' +
|
|
3090
3091
|
'until that budget is stated. (Authored geometry is exempt — rigc did not draw it.)'
|
|
3091
|
-
: ' — 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',
|
|
3092
3093
|
);
|
|
3093
3094
|
}
|
|
3094
3095
|
|
|
@@ -3125,7 +3126,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3125
3126
|
throw new CompileError(
|
|
3126
3127
|
`physics constraint "${EVERY_GLOBAL_PHYSICS}": the name is reserved — a motion spec's \`"physics": ` +
|
|
3127
3128
|
`"${EVERY_GLOBAL_PHYSICS}"\` is the timeline that names no constraint and drives every physics constraint ` +
|
|
3128
|
-
'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',
|
|
3129
3130
|
);
|
|
3130
3131
|
}
|
|
3131
3132
|
constraintDeclared.add(constraintAt(type, name));
|
|
@@ -3198,12 +3199,12 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3198
3199
|
// apart.
|
|
3199
3200
|
if (constraintDeclared.has(constraintAt('physics', name))) {
|
|
3200
3201
|
throw new CompileError(
|
|
3201
|
-
`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',
|
|
3202
3203
|
);
|
|
3203
3204
|
}
|
|
3204
3205
|
declareConstraint(name, 'physics');
|
|
3205
3206
|
if (!boneNames.has(spec.bone)) {
|
|
3206
|
-
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');
|
|
3207
3208
|
}
|
|
3208
3209
|
// A stop's copy and clamps sit right after the rig constraint that swings
|
|
3209
3210
|
// each link (`src/stop.ts`); this table is emitted after every rig
|
|
@@ -3213,7 +3214,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3213
3214
|
throw new CompileError(
|
|
3214
3215
|
`physics constraint "${name}" drives "${spec.bone}", a link stop "${stop.chain}" holds, from the physics table — ` +
|
|
3215
3216
|
"the table is emitted after every rig constraint, past the stop's copy and clamp, which would read the link " +
|
|
3216
|
-
'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',
|
|
3217
3218
|
);
|
|
3218
3219
|
}
|
|
3219
3220
|
// Every component and parameter the table states is held, 0 and the
|
|
@@ -3232,7 +3233,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3232
3233
|
// The parser is happy with this and the constraint does nothing at all.
|
|
3233
3234
|
// A23 catches it too; refusing here means it never reaches the gate.
|
|
3234
3235
|
throw new CompileError(
|
|
3235
|
-
`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',
|
|
3236
3237
|
);
|
|
3237
3238
|
}
|
|
3238
3239
|
for (const [param] of PHYSICS_PARAMS) {
|
|
@@ -3341,7 +3342,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3341
3342
|
if (everyGlobal && track.physics === undefined) {
|
|
3342
3343
|
throw new CompileError(
|
|
3343
3344
|
`animation "${animName}": group "${String(track.group)}" lists "${EVERY_GLOBAL_PHYSICS}", which is not a ` +
|
|
3344
|
-
`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',
|
|
3345
3346
|
);
|
|
3346
3347
|
}
|
|
3347
3348
|
if (everyGlobal) {
|
|
@@ -3363,7 +3364,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3363
3364
|
' — it would parse and move nothing. ' +
|
|
3364
3365
|
(track.property === 'reset'
|
|
3365
3366
|
? 'Declare a physics constraint, or remove the track'
|
|
3366
|
-
: `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',
|
|
3367
3368
|
);
|
|
3368
3369
|
}
|
|
3369
3370
|
} else if (family !== null) {
|
|
@@ -3379,21 +3380,21 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3379
3380
|
if (kinds.length > 0) {
|
|
3380
3381
|
throw new CompileError(
|
|
3381
3382
|
`animation "${animName}" keys "${target}" as a ${label}, but the rig declares it as a "${kinds.join('"/"')}" ` +
|
|
3382
|
-
'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',
|
|
3383
3384
|
);
|
|
3384
3385
|
}
|
|
3385
3386
|
const known = constraintNamesOfKind.get(family) ?? [];
|
|
3386
3387
|
throw new CompileError(
|
|
3387
3388
|
`animation "${animName}" keys unknown ${label} "${target}"` +
|
|
3388
|
-
(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',
|
|
3389
3390
|
);
|
|
3390
3391
|
}
|
|
3391
3392
|
} else if (isBoneTrack) {
|
|
3392
3393
|
if (!boneNames.has(target)) {
|
|
3393
|
-
throw new CompileError(`animation "${animName}" keys unknown bone "${target}"
|
|
3394
|
+
throw new CompileError(`animation "${animName}" keys unknown bone "${target}"`, 'CE_COMPILE_ANIMATION_BONE_UNKNOWN');
|
|
3394
3395
|
}
|
|
3395
3396
|
} else if (!slotNames.has(target)) {
|
|
3396
|
-
throw new CompileError(`animation "${animName}" targets unknown slot "${target}"
|
|
3397
|
+
throw new CompileError(`animation "${animName}" targets unknown slot "${target}"`, 'CE_COMPILE_ANIMATION_SLOT_UNKNOWN');
|
|
3397
3398
|
}
|
|
3398
3399
|
// A timeline is a kind, a name and a property — `physics.leg.mix` and
|
|
3399
3400
|
// `path.leg.mix` are two timelines on two constraints, so the claim
|
|
@@ -3402,7 +3403,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3402
3403
|
const claim = `${family ?? (isBoneTrack ? 'bone' : 'slot')} ${target}.${track.property}`;
|
|
3403
3404
|
if (claimed.has(claim)) {
|
|
3404
3405
|
throw new CompileError(
|
|
3405
|
-
`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',
|
|
3406
3407
|
);
|
|
3407
3408
|
}
|
|
3408
3409
|
claimed.add(claim);
|
|
@@ -3426,7 +3427,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3426
3427
|
'channels at every time, its setup value before its first key included, so the one the file states ' +
|
|
3427
3428
|
'later overwrites the other everywhere and the first one\'s keys there are read by nothing. Key each ' +
|
|
3428
3429
|
'channel once: "rgb" and "alpha" move the light colour\'s two halves on their own key times, "rgba" ' +
|
|
3429
|
-
'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',
|
|
3430
3431
|
);
|
|
3431
3432
|
}
|
|
3432
3433
|
}
|
|
@@ -3479,7 +3480,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3479
3480
|
if (kinds.length > 0) {
|
|
3480
3481
|
throw new CompileError(
|
|
3481
3482
|
`animation "${animName}" keys "${name}" as ${group === 'ik' ? 'an' : 'a'} ${group} constraint, but the rig declares it as a ` +
|
|
3482
|
-
`"${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',
|
|
3483
3484
|
);
|
|
3484
3485
|
}
|
|
3485
3486
|
const known = constraintNamesOfKind.get(group) ?? [];
|
|
@@ -3487,13 +3488,13 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3487
3488
|
`animation "${animName}" keys unknown ${group} constraint "${name}"; ` +
|
|
3488
3489
|
(known.length
|
|
3489
3490
|
? `the rig declares ${group} constraint(s): ${known.join(', ')}`
|
|
3490
|
-
: `the rig declares no ${group} constraint at all`),
|
|
3491
|
+
: `the rig declares no ${group} constraint at all`), 'CE_COMPILE_CONSTRAINT_TIMELINE_TARGET_UNKNOWN',
|
|
3491
3492
|
);
|
|
3492
3493
|
}
|
|
3493
3494
|
if (constraintTimelines[group].has(name)) {
|
|
3494
3495
|
throw new CompileError(
|
|
3495
3496
|
`animation "${animName}" has two ${group} timelines on constraint "${name}"; ` +
|
|
3496
|
-
'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',
|
|
3497
3498
|
);
|
|
3498
3499
|
}
|
|
3499
3500
|
const keys = compileConstraintTrack(
|
|
@@ -3531,25 +3532,25 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3531
3532
|
const table = skinTables.get(skinName);
|
|
3532
3533
|
if (!table) {
|
|
3533
3534
|
throw new CompileError(
|
|
3534
|
-
`${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',
|
|
3535
3536
|
);
|
|
3536
3537
|
}
|
|
3537
3538
|
const perSlot = table[track.slot];
|
|
3538
3539
|
if (!perSlot) {
|
|
3539
3540
|
throw new CompileError(
|
|
3540
3541
|
`${at}: skin "${skinName}" gives slot "${String(track.slot)}" no attachments` +
|
|
3541
|
-
(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',
|
|
3542
3543
|
);
|
|
3543
3544
|
}
|
|
3544
3545
|
const attachment = perSlot[track.attachment];
|
|
3545
3546
|
if (!attachment) {
|
|
3546
3547
|
throw new CompileError(
|
|
3547
3548
|
`${at}: slot "${track.slot}" in skin "${skinName}" has no attachment "${String(track.attachment)}" ` +
|
|
3548
|
-
`(it has: ${Object.keys(perSlot).join(', ')})`,
|
|
3549
|
+
`(it has: ${Object.keys(perSlot).join(', ')})`, 'CE_COMPILE_ATTACHMENT_TIMELINE_ATTACHMENT_UNKNOWN',
|
|
3549
3550
|
);
|
|
3550
3551
|
}
|
|
3551
3552
|
if (deformTimelines.get(skinName)?.get(track.slot)?.has(track.attachment)) {
|
|
3552
|
-
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');
|
|
3553
3554
|
}
|
|
3554
3555
|
const keys = compileDeformTrack(
|
|
3555
3556
|
track,
|
|
@@ -3578,28 +3579,28 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3578
3579
|
const table = skinTables.get(skinName);
|
|
3579
3580
|
if (!table) {
|
|
3580
3581
|
throw new CompileError(
|
|
3581
|
-
`${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',
|
|
3582
3583
|
);
|
|
3583
3584
|
}
|
|
3584
3585
|
const perSlot = table[track.slot];
|
|
3585
3586
|
if (!perSlot) {
|
|
3586
3587
|
throw new CompileError(
|
|
3587
3588
|
`${at}: skin "${skinName}" gives slot "${String(track.slot)}" no attachments` +
|
|
3588
|
-
(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',
|
|
3589
3590
|
);
|
|
3590
3591
|
}
|
|
3591
3592
|
const attachment = perSlot[track.attachment];
|
|
3592
3593
|
if (!attachment) {
|
|
3593
3594
|
throw new CompileError(
|
|
3594
3595
|
`${at}: slot "${track.slot}" in skin "${skinName}" has no attachment "${String(track.attachment)}" ` +
|
|
3595
|
-
`(it has: ${Object.keys(perSlot).join(', ')})`,
|
|
3596
|
+
`(it has: ${Object.keys(perSlot).join(', ')})`, 'CE_COMPILE_ATTACHMENT_TIMELINE_ATTACHMENT_UNKNOWN',
|
|
3596
3597
|
);
|
|
3597
3598
|
}
|
|
3598
3599
|
const keys = compileSequenceTrack(track, anim.duration, attachment, at);
|
|
3599
3600
|
for (const key of keys) compiledDuration = Math.max(compiledDuration, key.time);
|
|
3600
3601
|
const slot = attachmentTimelinesOf(skinName, track.slot);
|
|
3601
3602
|
if (slot.get(track.attachment)?.sequence) {
|
|
3602
|
-
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');
|
|
3603
3604
|
}
|
|
3604
3605
|
slot.set(track.attachment, { ...(slot.get(track.attachment) ?? {}), sequence: keys });
|
|
3605
3606
|
}
|
|
@@ -3624,7 +3625,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3624
3625
|
// a key on a track whose neighbour already sits on the declared duration.
|
|
3625
3626
|
if (Math.abs(compiledDuration - anim.duration) > FRAME) {
|
|
3626
3627
|
throw new CompileError(
|
|
3627
|
-
`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',
|
|
3628
3629
|
);
|
|
3629
3630
|
}
|
|
3630
3631
|
// The model's record of the animation. Which groups the file carries, in
|
|
@@ -3714,7 +3715,7 @@ function compileInto(opts: CompileOptions, droppedStates: DroppedState[]): Compi
|
|
|
3714
3715
|
const editorOrder: ModelEditorOrder = editorOrderOver(skins, [...animations.keys()]);
|
|
3715
3716
|
|
|
3716
3717
|
for (const slot of slots) {
|
|
3717
|
-
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');
|
|
3718
3719
|
}
|
|
3719
3720
|
|
|
3720
3721
|
// One bone order per weighted attachment (issue #1407), on the finished
|
|
@@ -3903,7 +3904,7 @@ function buildBone(spec: RigBone, soFar: ModelBone[], ctx: BoneContext): ModelBo
|
|
|
3903
3904
|
bone.y = f32(world[1]);
|
|
3904
3905
|
} else {
|
|
3905
3906
|
const parent = computeWorldTransforms(soFar).get(spec.parent);
|
|
3906
|
-
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');
|
|
3907
3908
|
const [lx, ly] = toBoneLocal(parent, world[0], world[1]);
|
|
3908
3909
|
bone.x = f32(lx);
|
|
3909
3910
|
bone.y = f32(ly);
|
|
@@ -3935,7 +3936,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3935
3936
|
if (!from) return null;
|
|
3936
3937
|
const needManifest = (what: string): FaceManifest => {
|
|
3937
3938
|
if (!ctx.manifest) {
|
|
3938
|
-
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');
|
|
3939
3940
|
}
|
|
3940
3941
|
return ctx.manifest;
|
|
3941
3942
|
};
|
|
@@ -3944,7 +3945,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3944
3945
|
const anchor = manifest.anchors?.[from.anchor];
|
|
3945
3946
|
if (!anchor || anchor.length < 2) {
|
|
3946
3947
|
throw new CompileError(
|
|
3947
|
-
`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',
|
|
3948
3949
|
);
|
|
3949
3950
|
}
|
|
3950
3951
|
return [anchor[0], anchor[1]];
|
|
@@ -3954,7 +3955,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3954
3955
|
const part = ctx.partBySlot.get(from.slotWindow);
|
|
3955
3956
|
if (!part) {
|
|
3956
3957
|
throw new CompileError(
|
|
3957
|
-
`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',
|
|
3958
3959
|
);
|
|
3959
3960
|
}
|
|
3960
3961
|
const win = partWindow(part, manifest);
|
|
@@ -3965,7 +3966,7 @@ function cropPointOf(spec: RigBone, ctx: BoneContext): [number, number] | null {
|
|
|
3965
3966
|
const centre = ctx.partBySlot.get(from.meshCenter)?.mesh?.center;
|
|
3966
3967
|
if (!centre) {
|
|
3967
3968
|
throw new CompileError(
|
|
3968
|
-
`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',
|
|
3969
3970
|
);
|
|
3970
3971
|
}
|
|
3971
3972
|
return [centre[0], centre[1]];
|
|
@@ -3978,7 +3979,7 @@ function rotationOf(spec: RigBone, ctx: BoneContext): number | null {
|
|
|
3978
3979
|
const source = spec.from?.rotation;
|
|
3979
3980
|
if (source === 'axis') {
|
|
3980
3981
|
if (ctx.axisSpineDeg === null) {
|
|
3981
|
-
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');
|
|
3982
3983
|
}
|
|
3983
3984
|
return ctx.axisSpineDeg;
|
|
3984
3985
|
}
|
|
@@ -3987,7 +3988,7 @@ function rotationOf(spec: RigBone, ctx: BoneContext): number | null {
|
|
|
3987
3988
|
const anchor = ctx.manifest?.anchors?.[key];
|
|
3988
3989
|
if (!anchor || anchor.length < 3) {
|
|
3989
3990
|
throw new CompileError(
|
|
3990
|
-
`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',
|
|
3991
3992
|
);
|
|
3992
3993
|
}
|
|
3993
3994
|
return screenToSpineDegrees(anchor[2]);
|
|
@@ -4123,7 +4124,7 @@ function refuseDefaultSkinContest(slotName: string, placeholder: string, skins:
|
|
|
4123
4124
|
`"Multiple attachments have the same name: ${placeholder} ${placeholder}", because a default-skin attachment ` +
|
|
4124
4125
|
"hangs on the slot beside the named skins' placeholder of that name. Move the default skin's entry for this " +
|
|
4125
4126
|
`slot into a named skin — call it "base" — so every skin filling "${placeholder}" is a named one. Two or more ` +
|
|
4126
|
-
'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',
|
|
4127
4128
|
);
|
|
4128
4129
|
}
|
|
4129
4130
|
|
|
@@ -4303,7 +4304,7 @@ function buildRigAttachment(
|
|
|
4303
4304
|
`is one of ${SPINE_ATTACHMENT_TYPES.join(', ')}, or the key is absent and reads as "region". ` +
|
|
4304
4305
|
'PRESENT-and-null is not absent: `getValue(map, "type", "region")` takes the default only when the key is ' +
|
|
4305
4306
|
'missing (`SkeletonJson.ts:527`), so this map matches no case, `readAttachment` returns null (`:653`), and ' +
|
|
4306
|
-
'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',
|
|
4307
4308
|
);
|
|
4308
4309
|
}
|
|
4309
4310
|
const type = stated ?? 'region';
|
|
@@ -4318,7 +4319,7 @@ function buildRigAttachment(
|
|
|
4318
4319
|
`${where}: "name" is ${JSON.stringify(statedName) ?? String(statedName)}, which is not a string. An ` +
|
|
4319
4320
|
"attachment's name is the runtime's `Attachment.name` — `getValue(map, \"name\", placeholder)`, " +
|
|
4320
4321
|
'`SkeletonJson.js:526` — and on a region, mesh or linked mesh it is also what `path` defaults to. Write it ' +
|
|
4321
|
-
'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',
|
|
4322
4323
|
);
|
|
4323
4324
|
}
|
|
4324
4325
|
// A linked mesh in the format's OTHER spelling. `type: "mesh"` and `type:
|
|
@@ -4336,7 +4337,7 @@ function buildRigAttachment(
|
|
|
4336
4337
|
if (type === 'boundingbox') return buildRigBoundingBox(att as RigBoundingBoxAttachment, where, ctx);
|
|
4337
4338
|
if (type === 'clipping') return buildRigClipping(att as RigClippingAttachment, where, ctx);
|
|
4338
4339
|
if (type === 'path') return buildRigPath(att as RigPathAttachment, where, ctx);
|
|
4339
|
-
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');
|
|
4340
4341
|
const near = nearMisses(type, SPINE_ATTACHMENT_TYPES);
|
|
4341
4342
|
throw new CompileError(
|
|
4342
4343
|
`${where}: attachment type "${type}" is not one of the ${SPINE_ATTACHMENT_TYPES.length} the Spine 4.3 format ` +
|
|
@@ -4348,7 +4349,7 @@ function buildRigAttachment(
|
|
|
4348
4349
|
// this format standing one level up from where it was written.
|
|
4349
4350
|
(type === 'sequence'
|
|
4350
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).'
|
|
4351
|
-
: ''),
|
|
4352
|
+
: ''), 'CE_COMPILE_ATTACHMENT_TYPE_UNKNOWN',
|
|
4352
4353
|
);
|
|
4353
4354
|
}
|
|
4354
4355
|
|
|
@@ -4385,26 +4386,26 @@ function buildVertexGeometry(att: RigVertexGeometry, where: string, ctx: Attachm
|
|
|
4385
4386
|
if (typeof count !== 'number' || !Number.isInteger(count) || count < 3) {
|
|
4386
4387
|
throw new CompileError(
|
|
4387
4388
|
`${where}: vertexCount is ${JSON.stringify(count)}; a polygon needs at least 3 vertices, stated outright — ` +
|
|
4388
|
-
'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',
|
|
4389
4390
|
);
|
|
4390
4391
|
}
|
|
4391
4392
|
if (att.vertices && att.weights) {
|
|
4392
4393
|
throw new CompileError(
|
|
4393
|
-
`${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',
|
|
4394
4395
|
);
|
|
4395
4396
|
}
|
|
4396
4397
|
if (att.weights) {
|
|
4397
4398
|
if (att.boneIndexing === 'raw') {
|
|
4398
|
-
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');
|
|
4399
4400
|
}
|
|
4400
4401
|
if (att.weights.length !== count) {
|
|
4401
|
-
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');
|
|
4402
4403
|
}
|
|
4403
4404
|
return { weighted: true, bindings: bindNamedWeights(att.weights, where, ctx) };
|
|
4404
4405
|
}
|
|
4405
4406
|
const raw = att.vertices;
|
|
4406
4407
|
if (!raw || raw.length === 0) {
|
|
4407
|
-
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');
|
|
4408
4409
|
}
|
|
4409
4410
|
const unweighted = raw.length === count * 2;
|
|
4410
4411
|
if (!unweighted) {
|
|
@@ -4414,7 +4415,7 @@ function buildVertexGeometry(att: RigVertexGeometry, where: string, ctx: Attachm
|
|
|
4414
4415
|
'The parser reads that as a WEIGHTED run, whose bone INDEXES point into the emitted bone array — a list ' +
|
|
4415
4416
|
'the spec never writes, so inserting a bone rebinds every vertex in silence. ' +
|
|
4416
4417
|
'Give the bindings by name as "weights": [[{ "bone": …, "x": …, "y": …, "weight": … }, …], …], ' +
|
|
4417
|
-
'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',
|
|
4418
4419
|
);
|
|
4419
4420
|
}
|
|
4420
4421
|
// A raw run still has to decode to exactly `vertexCount` vertices, or the
|
|
@@ -4422,7 +4423,7 @@ function buildVertexGeometry(att: RigVertexGeometry, where: string, ctx: Attachm
|
|
|
4422
4423
|
checkRawRunShape(raw, count, `vertexCount is ${count}`, where);
|
|
4423
4424
|
}
|
|
4424
4425
|
for (const n of raw) {
|
|
4425
|
-
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');
|
|
4426
4427
|
}
|
|
4427
4428
|
if (unweighted) return { weighted: false, xy: raw.map(f32) };
|
|
4428
4429
|
const bindings = bindRawRun(raw.map(f32), ctx.bones, where);
|
|
@@ -4441,17 +4442,17 @@ function checkRawRunShape(raw: readonly number[], vertices: number, countSays: s
|
|
|
4441
4442
|
for (let i = 0; i < raw.length; decoded++) {
|
|
4442
4443
|
const bones = raw[i++];
|
|
4443
4444
|
if (!Number.isInteger(bones) || bones < 1) {
|
|
4444
|
-
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');
|
|
4445
4446
|
}
|
|
4446
4447
|
i += bones * 4;
|
|
4447
4448
|
if (i > raw.length) {
|
|
4448
4449
|
throw new CompileError(
|
|
4449
|
-
`${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',
|
|
4450
4451
|
);
|
|
4451
4452
|
}
|
|
4452
4453
|
}
|
|
4453
4454
|
if (decoded !== vertices) {
|
|
4454
|
-
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');
|
|
4455
4456
|
}
|
|
4456
4457
|
}
|
|
4457
4458
|
|
|
@@ -4474,7 +4475,7 @@ function bindRawRun(run: readonly number[], bones: readonly ModelBone[], where:
|
|
|
4474
4475
|
for (let k = 0; k < count; k++, i += 4) {
|
|
4475
4476
|
const bone = bones[run[i]];
|
|
4476
4477
|
if (bone === undefined) {
|
|
4477
|
-
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');
|
|
4478
4479
|
}
|
|
4479
4480
|
vertex.push({ bone: bone.name, x: run[i + 1], y: run[i + 2], weight: run[i + 3] });
|
|
4480
4481
|
}
|
|
@@ -4675,13 +4676,13 @@ function resolveStageBox(
|
|
|
4675
4676
|
if (asked === undefined) return null;
|
|
4676
4677
|
const field = `skeleton.stageBox (slot "${asked.slot}", attachment "${asked.attachment}")`;
|
|
4677
4678
|
if (ctx.stageWidth === undefined || ctx.stageHeight === undefined) {
|
|
4678
|
-
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');
|
|
4679
4680
|
}
|
|
4680
4681
|
const rigSlot = rig.slots.find((s) => s.name === asked.slot);
|
|
4681
4682
|
if (rigSlot === undefined) {
|
|
4682
4683
|
throw new CompileError(
|
|
4683
4684
|
`${field}: slot "${asked.slot}" is not one the rig declares. Declare it in "slots" — where it stands in the ` +
|
|
4684
|
-
`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',
|
|
4685
4686
|
);
|
|
4686
4687
|
}
|
|
4687
4688
|
const filledBy = [
|
|
@@ -4693,13 +4694,13 @@ function resolveStageBox(
|
|
|
4693
4694
|
if (filledBy.length > 0) {
|
|
4694
4695
|
throw new CompileError(
|
|
4695
4696
|
`${field}: slot "${asked.slot}" is also filled by ${filledBy.join(' and ')}. The stage box is its slot's one ` +
|
|
4696
|
-
'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',
|
|
4697
4698
|
);
|
|
4698
4699
|
}
|
|
4699
4700
|
// The box's bone: the root, stating no setup transform — as built, so a root placed `from` the manifest is read as placed.
|
|
4700
4701
|
const bone = ctx.bones.find((b) => b.name === rigSlot.bone);
|
|
4701
4702
|
if (bone === undefined) {
|
|
4702
|
-
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');
|
|
4703
4704
|
}
|
|
4704
4705
|
if (bone.parent !== undefined) {
|
|
4705
4706
|
throw new CompileError(
|
|
@@ -4707,7 +4708,7 @@ function resolveStageBox(
|
|
|
4707
4708
|
"the stage's corners in world space, written as they are, and the gate holds them on the header's grid " +
|
|
4708
4709
|
"through the runtime's own frame of an unrotated root; every bone below it adds that frame's residue once " +
|
|
4709
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 " +
|
|
4710
|
-
`("${ctx.bones[0]?.name ?? 'root'}")`,
|
|
4711
|
+
`("${ctx.bones[0]?.name ?? 'root'}")`, 'CE_COMPILE_STAGE_BOX_BONE_NOT_ROOT',
|
|
4711
4712
|
);
|
|
4712
4713
|
}
|
|
4713
4714
|
const stated = [
|
|
@@ -4718,7 +4719,7 @@ function resolveStageBox(
|
|
|
4718
4719
|
throw new CompileError(
|
|
4719
4720
|
`${field}: slot "${asked.slot}" hangs on the root "${bone.name}", which states a setup transform (${stated.join(', ')}). ` +
|
|
4720
4721
|
"The box's vertices are the stage's corners in world space, written as they are, so the root must not move, " +
|
|
4721
|
-
'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',
|
|
4722
4723
|
);
|
|
4723
4724
|
}
|
|
4724
4725
|
const x = rig.skeleton?.x ?? 0;
|
|
@@ -4759,7 +4760,7 @@ function buildRigClipping(
|
|
|
4759
4760
|
if (typeof att.end !== 'string' || !ctx.slotNames.has(att.end)) {
|
|
4760
4761
|
throw new CompileError(
|
|
4761
4762
|
`${where}: end names slot ${JSON.stringify(att.end)}, which this rig does not declare; ` +
|
|
4762
|
-
'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',
|
|
4763
4764
|
);
|
|
4764
4765
|
}
|
|
4765
4766
|
}
|
|
@@ -4803,7 +4804,7 @@ function setupWorldVertices(
|
|
|
4803
4804
|
let y = 0;
|
|
4804
4805
|
for (const binding of vertex) {
|
|
4805
4806
|
const m = transforms.get(binding.bone);
|
|
4806
|
-
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');
|
|
4807
4808
|
const [wx, wy] = toWorld(m, binding.x, binding.y);
|
|
4808
4809
|
x += wx * binding.weight;
|
|
4809
4810
|
y += wy * binding.weight;
|
|
@@ -4946,14 +4947,14 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
4946
4947
|
throw new CompileError(
|
|
4947
4948
|
`${where}: vertexCount is ${count}, which is not a multiple of 3. A path's vertices are knots AND their ` +
|
|
4948
4949
|
'Bezier handles, read in groups of three; the parser sizes its lengths array with `vertexCount / 3` and ' +
|
|
4949
|
-
'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',
|
|
4950
4951
|
);
|
|
4951
4952
|
}
|
|
4952
4953
|
const minimum = closed ? 3 : 6;
|
|
4953
4954
|
if (count < minimum) {
|
|
4954
4955
|
throw new CompileError(
|
|
4955
4956
|
`${where}: vertexCount is ${count} and ${closed ? 'a closed' : 'an open'} path needs at least ${minimum} — ` +
|
|
4956
|
-
`${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',
|
|
4957
4958
|
);
|
|
4958
4959
|
}
|
|
4959
4960
|
const entries = count / 3;
|
|
@@ -4962,7 +4963,7 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
4962
4963
|
lengths = statedPathLengths(att.lengths, entries, closed, where);
|
|
4963
4964
|
} else {
|
|
4964
4965
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
4965
|
-
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');
|
|
4966
4967
|
const points = setupWorldVertices(vertices, anchor, ctx.transforms, where);
|
|
4967
4968
|
lengths = pathCurveLengths(pathChain(points, true));
|
|
4968
4969
|
// The total the runtime reads is the last CURVE's entry, `lengths[vertexCount
|
|
@@ -4972,7 +4973,7 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
4972
4973
|
if (lengths.length !== entries || !lengths.every((n) => Number.isFinite(n)) || !(total > 0)) {
|
|
4973
4974
|
throw new CompileError(
|
|
4974
4975
|
`${where}: the geometry measures ${lengths.length} entry(ies) where the parser sizes ${entries}, of total ` +
|
|
4975
|
-
`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',
|
|
4976
4977
|
);
|
|
4977
4978
|
}
|
|
4978
4979
|
}
|
|
@@ -5008,25 +5009,25 @@ function buildRigPath(att: RigPathAttachment, where: string, ctx: AttachmentCont
|
|
|
5008
5009
|
*/
|
|
5009
5010
|
function statedPathLengths(stated: unknown, entries: number, closed: boolean, where: string): number[] {
|
|
5010
5011
|
if (!Array.isArray(stated)) {
|
|
5011
|
-
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');
|
|
5012
5013
|
}
|
|
5013
5014
|
if (stated.length !== entries) {
|
|
5014
5015
|
throw new CompileError(
|
|
5015
5016
|
`${where}: "lengths" has ${stated.length} entry(ies) where the parser sizes ${entries} — vertexCount / 3 on ` +
|
|
5016
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'}. ` +
|
|
5017
|
-
'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',
|
|
5018
5019
|
);
|
|
5019
5020
|
}
|
|
5020
5021
|
let previous = 0;
|
|
5021
5022
|
for (let i = 0; i < stated.length; i++) {
|
|
5022
5023
|
const value: unknown = stated[i];
|
|
5023
5024
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
5024
|
-
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');
|
|
5025
5026
|
}
|
|
5026
5027
|
if (value < previous) {
|
|
5027
5028
|
throw new CompileError(
|
|
5028
5029
|
`${where}: "lengths"[${i}] is ${value}, below ${i === 0 ? '0' : `the ${previous} before it`}. The array is CUMULATIVE — the length at ` +
|
|
5029
|
-
'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',
|
|
5030
5031
|
);
|
|
5031
5032
|
}
|
|
5032
5033
|
previous = value;
|
|
@@ -5067,7 +5068,7 @@ function atlasedImage(image: string, where: string, ctx: AttachmentContext): Com
|
|
|
5067
5068
|
'measured and atlased — one per file, whichever skin names it (issue #555) — so reaching this means rigc ' +
|
|
5068
5069
|
'skipped one, not that the spec left anything out. ' +
|
|
5069
5070
|
(near.length ? `The nearest region(s) built are ${near.map((n) => JSON.stringify(n)).join(', ')}. ` : '') +
|
|
5070
|
-
`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',
|
|
5071
5072
|
);
|
|
5072
5073
|
}
|
|
5073
5074
|
|
|
@@ -5214,7 +5215,7 @@ function sequenceFrameSize(
|
|
|
5214
5215
|
throw new CompileError(
|
|
5215
5216
|
`${where}: the ${frames.length} frames of this sequence measure ${sizes.join(', ')} in ${field} ` +
|
|
5216
5217
|
`(${frames.map((img) => `"${img.region}" ${img[field]}`).join(', ')}), and an attachment has one ` +
|
|
5217
|
-
`${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',
|
|
5218
5219
|
);
|
|
5219
5220
|
}
|
|
5220
5221
|
}
|
|
@@ -5239,7 +5240,7 @@ function sequenceFrames(att: { path?: string; sequence?: RigSequence }, placehol
|
|
|
5239
5240
|
throw new CompileError(
|
|
5240
5241
|
`${where}: sequence frame ${i} is the region "${region}", which was never added to the atlas — every frame ` +
|
|
5241
5242
|
'of a sequence is atlased or refused by name before any attachment is built, so reaching this means rigc ' +
|
|
5242
|
-
'skipped one',
|
|
5243
|
+
'skipped one', 'CE_COMPILE_INTERNAL_SEQUENCE_FRAME_NOT_ATLASED',
|
|
5243
5244
|
);
|
|
5244
5245
|
}
|
|
5245
5246
|
frames.push(img);
|
|
@@ -5294,7 +5295,7 @@ function buildRigRegion(
|
|
|
5294
5295
|
? ''
|
|
5295
5296
|
: ` in the page's texels, which its scale: ${img.atlasScale} makes a ` +
|
|
5296
5297
|
`${img.width}x${img.height} drawing (+/- ${f32(0.5 / img.atlasScale)}px, the pack's rounding)`) +
|
|
5297
|
-
')',
|
|
5298
|
+
')', 'CE_COMPILE_REGION_ATLAS_SIZE_MISMATCH',
|
|
5298
5299
|
);
|
|
5299
5300
|
}
|
|
5300
5301
|
}
|
|
@@ -5305,7 +5306,7 @@ function buildRigRegion(
|
|
|
5305
5306
|
// No parser default: an omission loads as NaN and every UV collapses, with
|
|
5306
5307
|
// no error at all. So it is this or nothing.
|
|
5307
5308
|
throw new CompileError(
|
|
5308
|
-
`${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',
|
|
5309
5310
|
);
|
|
5310
5311
|
}
|
|
5311
5312
|
const out: ModelRegionAttachment = { kind: 'region', width: f32(width), height: f32(height) };
|
|
@@ -5389,12 +5390,12 @@ function regionAtlasRect(
|
|
|
5389
5390
|
function bindNamedWeights(weights: RigMeshBinding[][], where: string, ctx: AttachmentContext): ModelBinding[][] {
|
|
5390
5391
|
return weights.map((vertex, i) => {
|
|
5391
5392
|
if (!Array.isArray(vertex) || vertex.length === 0) {
|
|
5392
|
-
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');
|
|
5393
5394
|
}
|
|
5394
5395
|
return vertex.map((binding) => {
|
|
5395
5396
|
if (!ctx.bones.some((b) => b.name === binding.bone)) {
|
|
5396
5397
|
throw new CompileError(
|
|
5397
|
-
`${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',
|
|
5398
5399
|
);
|
|
5399
5400
|
}
|
|
5400
5401
|
return { bone: binding.bone, x: f32(binding.x), y: f32(binding.y), weight: f32(binding.weight) };
|
|
@@ -5499,6 +5500,7 @@ function authoredHullAndEdges(
|
|
|
5499
5500
|
vertexCount: number,
|
|
5500
5501
|
triangles: number[],
|
|
5501
5502
|
where: string,
|
|
5503
|
+
named: { skin: string; slot: string; placeholder: string },
|
|
5502
5504
|
): { hull: number; edges: number[] } {
|
|
5503
5505
|
try {
|
|
5504
5506
|
const outline = traceOutline(vertexCount, triangles);
|
|
@@ -5511,11 +5513,31 @@ function authoredHullAndEdges(
|
|
|
5511
5513
|
checkHullOrder(outline, vertexCount);
|
|
5512
5514
|
return { hull: outline.hull, edges: att.edges ?? meshEdges(vertexCount, triangles, outline.hull) };
|
|
5513
5515
|
} catch (err) {
|
|
5514
|
-
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}
|
|
5516
|
+
if (err instanceof MeshError) throw new CompileError(`${where}: ${err.message}${splitPointer(vertexCount, triangles, named)}`, 'CE_COMPILE_AUTHORED_MESH_OUTLINE_INVALID');
|
|
5515
5517
|
throw err;
|
|
5516
5518
|
}
|
|
5517
5519
|
}
|
|
5518
5520
|
|
|
5521
|
+
/**
|
|
5522
|
+
* The command that answers an outline refusal whose cause is pieces that share
|
|
5523
|
+
* no vertex (issue #1414): one mesh is one outline, so the fix is one mesh per
|
|
5524
|
+
* piece, and `rigc split` writes that proposal. Empty for every other cause —
|
|
5525
|
+
* a pinch, a hole, a doubled triangle — whose fix is the triangles, and for a
|
|
5526
|
+
* list the refusal is about because its indices are not vertices at all.
|
|
5527
|
+
*/
|
|
5528
|
+
function splitPointer(vertexCount: number, triangles: readonly number[], named: { skin: string; slot: string; placeholder: string }): string {
|
|
5529
|
+
if (triangles.length % 3 !== 0 || triangles.some((v) => !Number.isInteger(v) || v < 0 || v >= vertexCount)) return '';
|
|
5530
|
+
const count = meshPieces(vertexCount, triangles).pieces.length;
|
|
5531
|
+
if (count < 2) return '';
|
|
5532
|
+
// Each name one shell word (issue #1442): the command is copied into a shell, and a bare `left legs` is two arguments.
|
|
5533
|
+
const skin = named.skin === 'default' ? '' : ` --skin ${shellWord(named.skin)}`;
|
|
5534
|
+
return (
|
|
5535
|
+
` — the triangles are ${count} pieces that share no vertex, and one mesh is one outline: ` +
|
|
5536
|
+
`\`rigc split --rig <rig> --motion <motion> --slot ${shellWord(named.slot)} --attachment ${shellWord(named.placeholder)}${skin} --out <dir>\` ` +
|
|
5537
|
+
'writes one mesh per piece, its deform keys renumbered (AUTHORING §3.4.11)'
|
|
5538
|
+
);
|
|
5539
|
+
}
|
|
5540
|
+
|
|
5519
5541
|
/**
|
|
5520
5542
|
* The same two fields for geometry rigc built. The generators already put the
|
|
5521
5543
|
* outline first and in order — a contour is all outline, a ring's outer ring
|
|
@@ -5532,7 +5554,7 @@ function generatedHullAndEdges(geometry: MeshGeometry, where: string): { hull: n
|
|
|
5532
5554
|
checkHullOrder(outline, vertexCount);
|
|
5533
5555
|
} catch (err) {
|
|
5534
5556
|
if (err instanceof MeshError) {
|
|
5535
|
-
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');
|
|
5536
5558
|
}
|
|
5537
5559
|
throw err;
|
|
5538
5560
|
}
|
|
@@ -5548,27 +5570,27 @@ function buildRigMesh(
|
|
|
5548
5570
|
const authored =
|
|
5549
5571
|
att.uvs !== undefined || att.triangles !== undefined || att.vertices !== undefined || att.weights !== undefined;
|
|
5550
5572
|
if (authored && att.generator) {
|
|
5551
|
-
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');
|
|
5552
5574
|
}
|
|
5553
5575
|
if (att.generator) return buildGeneratedMesh(att, att.generator, placeholder, where, ctx);
|
|
5554
5576
|
if (att.vertices && att.weights) {
|
|
5555
5577
|
throw new CompileError(
|
|
5556
|
-
`${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',
|
|
5557
5579
|
);
|
|
5558
5580
|
}
|
|
5559
5581
|
if (!att.uvs || !att.triangles || !(att.vertices || att.weights)) {
|
|
5560
|
-
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');
|
|
5561
5583
|
}
|
|
5562
5584
|
const uvCount = att.uvs.length;
|
|
5563
5585
|
let vertices: ModelVertices;
|
|
5564
5586
|
let boundBones: string[] = [];
|
|
5565
5587
|
if (att.weights) {
|
|
5566
5588
|
if (att.boneIndexing === 'raw') {
|
|
5567
|
-
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');
|
|
5568
5590
|
}
|
|
5569
5591
|
if (att.weights.length !== uvCount / 2) {
|
|
5570
5592
|
throw new CompileError(
|
|
5571
|
-
`${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',
|
|
5572
5594
|
);
|
|
5573
5595
|
}
|
|
5574
5596
|
vertices = { weighted: true, bindings: bindNamedWeights(att.weights, where, ctx) };
|
|
@@ -5583,7 +5605,7 @@ function buildRigMesh(
|
|
|
5583
5605
|
`${where}: this mesh's "vertices" is a weighted run, whose bone INDEXES point into the emitted bone array — ` +
|
|
5584
5606
|
'a list the spec never writes, so inserting a bone rebinds every vertex in silence. ' +
|
|
5585
5607
|
'Give the bindings by name as "weights": [[{ "bone": …, "x": …, "y": …, "weight": … }, …], …], ' +
|
|
5586
|
-
'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',
|
|
5587
5609
|
);
|
|
5588
5610
|
}
|
|
5589
5611
|
if (weighted) {
|
|
@@ -5599,7 +5621,7 @@ function buildRigMesh(
|
|
|
5599
5621
|
vertices = { weighted: false, xy: raw.map(f32) };
|
|
5600
5622
|
}
|
|
5601
5623
|
}
|
|
5602
|
-
const { hull, edges } = authoredHullAndEdges(att, uvCount / 2, att.triangles, where);
|
|
5624
|
+
const { hull, edges } = authoredHullAndEdges(att, uvCount / 2, att.triangles, where, { skin: ctx.skinName, slot: ctx.slotName, placeholder });
|
|
5603
5625
|
// `width`/`height` are the size of the image the mesh is drawn on. Stated
|
|
5604
5626
|
// wins; otherwise the PNG the attachment names, already measured for the
|
|
5605
5627
|
// atlas — the same `CompiledImage.width` a region reads, so a page with a
|
|
@@ -5613,7 +5635,7 @@ function buildRigMesh(
|
|
|
5613
5635
|
const height = seqSize !== null ? seqSize.height : (att.height ?? img?.height);
|
|
5614
5636
|
if (width === undefined || height === undefined) {
|
|
5615
5637
|
throw new CompileError(
|
|
5616
|
-
`${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',
|
|
5617
5639
|
);
|
|
5618
5640
|
}
|
|
5619
5641
|
const out: ModelMeshAttachment = {
|
|
@@ -5692,7 +5714,7 @@ function buildRigLinkedMesh(
|
|
|
5692
5714
|
'no geometry of its own — it draws the geometry of the attachment "source" names. The parser returns from ' +
|
|
5693
5715
|
'the `source` branch before `readVertices` (`SkeletonJson.ts:582-586`), so these keys are read by nothing ' +
|
|
5694
5716
|
"at all: a link declaring 5 uvs beside a 4-vertex source loads the SOURCE's 4 vertices and says nothing. " +
|
|
5695
|
-
`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',
|
|
5696
5718
|
);
|
|
5697
5719
|
}
|
|
5698
5720
|
const source = att.source;
|
|
@@ -5702,7 +5724,7 @@ function buildRigLinkedMesh(
|
|
|
5702
5724
|
`attachment is filed under in its skin, not its "name"), and this one states ${JSON.stringify(source) ?? String(source)}. ` +
|
|
5703
5725
|
'"source" is what MAKES a mesh linked: `getValue(map, "source", null)` is falsy-tested ' +
|
|
5704
5726
|
'(`SkeletonJson.ts:582`), so an absent or empty one is read as an ordinary mesh, whose `uvs` this ' +
|
|
5705
|
-
'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',
|
|
5706
5728
|
);
|
|
5707
5729
|
}
|
|
5708
5730
|
if (att.slot !== undefined && !ctx.slotNames.has(att.slot)) {
|
|
@@ -5710,7 +5732,7 @@ function buildRigLinkedMesh(
|
|
|
5710
5732
|
`${where}: "slot" is ${JSON.stringify(att.slot)}, which the rig does not declare as a slot. It names where ` +
|
|
5711
5733
|
'the source lives and is resolved through `skeletonData.findSlot` (`SkeletonJson.ts:575`), which throws ' +
|
|
5712
5734
|
`\`Source mesh slot not found\` on a miss. The rig's slots are ${[...ctx.slotNames].sort().map((s) => JSON.stringify(s)).join(', ')}. ` +
|
|
5713
|
-
"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',
|
|
5714
5736
|
);
|
|
5715
5737
|
}
|
|
5716
5738
|
ctx.links.push({
|
|
@@ -5729,7 +5751,7 @@ function buildRigLinkedMesh(
|
|
|
5729
5751
|
const height = seqSize !== null ? seqSize.height : (att.height ?? img?.height);
|
|
5730
5752
|
if (width === undefined || height === undefined) {
|
|
5731
5753
|
throw new CompileError(
|
|
5732
|
-
`${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',
|
|
5733
5755
|
);
|
|
5734
5756
|
}
|
|
5735
5757
|
// The link in full — skin, slot and `timelines` as the parser resolves them.
|
|
@@ -5791,7 +5813,7 @@ function resolveLinkedMeshes(
|
|
|
5791
5813
|
`${link.where}: no "skin" is stated, so the parser looks for the source "${link.source}" in the default ` +
|
|
5792
5814
|
`skin — and this rig declares no default skin. The rig's skins are ${skinNames.map((s) => JSON.stringify(s)).join(', ')}. ` +
|
|
5793
5815
|
'State the skin the source is filed under. Left to the round trip this is a `TypeError` off the null ' +
|
|
5794
|
-
'`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',
|
|
5795
5817
|
);
|
|
5796
5818
|
}
|
|
5797
5819
|
if (table === undefined) {
|
|
@@ -5799,7 +5821,7 @@ function resolveLinkedMeshes(
|
|
|
5799
5821
|
`${link.where}: "skin" is ${JSON.stringify(link.skin)}, and the rig declares no such skin. The rig's skins ` +
|
|
5800
5822
|
`are ${skinNames.map((s) => JSON.stringify(s)).join(', ')}. ` +
|
|
5801
5823
|
"Left to the round trip this is the runtime's `Skin not found`, which names neither this attachment nor " +
|
|
5802
|
-
'where it was looking.',
|
|
5824
|
+
'where it was looking.', 'CE_COMPILE_LINKED_MESH_SKIN_UNKNOWN',
|
|
5803
5825
|
);
|
|
5804
5826
|
}
|
|
5805
5827
|
const slot = table[link.slot];
|
|
@@ -5813,7 +5835,7 @@ function resolveLinkedMeshes(
|
|
|
5813
5835
|
`holds ${held.length === 0 ? 'no attachment at all' : `${held.length}: ${held.map((k) => JSON.stringify(k)).join(', ')}`}. ` +
|
|
5814
5836
|
'"source" is the PLACEHOLDER the source is filed under — `skin.getAttachment(slotIndex, source)` ' +
|
|
5815
5837
|
"(`SkeletonJson.ts:433`), whose table is keyed by the JSON key, not by the attachment's `name`. " +
|
|
5816
|
-
"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',
|
|
5817
5839
|
);
|
|
5818
5840
|
}
|
|
5819
5841
|
const type = found.kind;
|
|
@@ -5823,7 +5845,7 @@ function resolveLinkedMeshes(
|
|
|
5823
5845
|
'them is refused. Measured through spine-core: the runtime resolves links in the order they were read, ' +
|
|
5824
5846
|
"so a link whose source is a link loads the source's geometry when the source comes first in the file " +
|
|
5825
5847
|
'and loads NOTHING — `worldVerticesLength` 0, 0 triangles, a 0x0 size — when the two are swapped, in ' +
|
|
5826
|
-
'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',
|
|
5827
5849
|
);
|
|
5828
5850
|
}
|
|
5829
5851
|
if (type !== 'mesh') {
|
|
@@ -5831,7 +5853,7 @@ function resolveLinkedMeshes(
|
|
|
5831
5853
|
`${link.where}: "source" is ${JSON.stringify(link.source)}, which is a ${JSON.stringify(type)} attachment ` +
|
|
5832
5854
|
"and not a mesh. A linked mesh takes another MESH's `uvs`, `triangles`, `vertices`, `hull` and `edges` " +
|
|
5833
5855
|
'(`MeshAttachment.setSourceMesh`); the runtime casts whatever it finds and reads those fields off it, ' +
|
|
5834
|
-
'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',
|
|
5835
5857
|
);
|
|
5836
5858
|
}
|
|
5837
5859
|
}
|
|
@@ -5863,9 +5885,9 @@ function buildGeneratedMesh(
|
|
|
5863
5885
|
// Two readings of one bone (issue #1021): the runtime's matrices to bind
|
|
5864
5886
|
// through, and the exact frame to measure the control angles in.
|
|
5865
5887
|
const resolve = (name: string, frames: Map<string, BoneTransform> = ctx.transforms): BoneTransform => {
|
|
5866
|
-
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');
|
|
5867
5889
|
const transform = frames.get(name);
|
|
5868
|
-
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');
|
|
5869
5891
|
return transform;
|
|
5870
5892
|
};
|
|
5871
5893
|
const refFor = (name: string): MeshBoneRef => {
|
|
@@ -5879,7 +5901,7 @@ function buildGeneratedMesh(
|
|
|
5879
5901
|
// angle about the aperture means anything (issue #684).
|
|
5880
5902
|
const [w, h] = generator.size;
|
|
5881
5903
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
5882
|
-
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');
|
|
5883
5905
|
const place = (px: number, py: number): [number, number] => [anchor.worldX + px - w / 2, anchor.worldY + h / 2 - py];
|
|
5884
5906
|
// The inverse a control angle is measured through, in the exact frame: the
|
|
5885
5907
|
// anchor's and the control's positions as the spec put them, not as the
|
|
@@ -5903,7 +5925,7 @@ function buildGeneratedMesh(
|
|
|
5903
5925
|
}),
|
|
5904
5926
|
});
|
|
5905
5927
|
} catch (err) {
|
|
5906
|
-
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');
|
|
5907
5929
|
throw err;
|
|
5908
5930
|
}
|
|
5909
5931
|
const vertices = bindWeightedVertices(
|
|
@@ -6046,12 +6068,12 @@ function sampleMeshDepth(
|
|
|
6046
6068
|
summary: NonNullable<CompileResult['meshes'][number]['depth']>;
|
|
6047
6069
|
} {
|
|
6048
6070
|
if (typeof spec.image !== 'string' || spec.image.length === 0) {
|
|
6049
|
-
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');
|
|
6050
6072
|
}
|
|
6051
6073
|
if (spec.near !== 'white' && spec.near !== 'black') {
|
|
6052
6074
|
throw new CompileError(
|
|
6053
6075
|
`${where}: the depth block says "near": ${JSON.stringify(spec.near)}; it is "white" or "black". Both ` +
|
|
6054
|
-
'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',
|
|
6055
6077
|
);
|
|
6056
6078
|
}
|
|
6057
6079
|
const tone: DepthTone = {
|
|
@@ -6063,7 +6085,7 @@ function sampleMeshDepth(
|
|
|
6063
6085
|
checkTone(tone, where);
|
|
6064
6086
|
checkZScale(spec.zScale, where);
|
|
6065
6087
|
} catch (err) {
|
|
6066
|
-
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');
|
|
6067
6089
|
throw err;
|
|
6068
6090
|
}
|
|
6069
6091
|
|
|
@@ -6135,7 +6157,7 @@ function sampleMeshDepth(
|
|
|
6135
6157
|
: ` in the page's texels, pixel (${f32(points[first][0] * toSheetX)}, ${f32(points[first][1] * toSheetY)}) of ` +
|
|
6136
6158
|
'the drawing-sized sheet') +
|
|
6137
6159
|
`. ${why} ` +
|
|
6138
|
-
'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',
|
|
6139
6161
|
);
|
|
6140
6162
|
}
|
|
6141
6163
|
|
|
@@ -6174,8 +6196,8 @@ function sampleMeshDepth(
|
|
|
6174
6196
|
/** One bone of the rig as `bindWeightedVertices` wants it: by name, with its setup inverse. */
|
|
6175
6197
|
function meshBoneRef(name: string, where: string, ctx: AttachmentContext): MeshBoneRef {
|
|
6176
6198
|
const transform = ctx.transforms.get(name);
|
|
6177
|
-
if (!transform) throw new CompileError(`${where}: bone "${name}" has no setup transform
|
|
6178
|
-
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');
|
|
6179
6201
|
return { name, toBind: (wx, wy) => toBoneLocal(transform, wx, wy) };
|
|
6180
6202
|
}
|
|
6181
6203
|
|
|
@@ -6250,11 +6272,11 @@ function readGreySheet(
|
|
|
6250
6272
|
const sheetHeight = scaled === undefined ? grid.partHeight : scaled.height;
|
|
6251
6273
|
const noun = purpose === 'depth' ? 'depth map' : 'soft mask';
|
|
6252
6274
|
if (typeof image !== 'string' || image.length === 0) {
|
|
6253
|
-
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');
|
|
6254
6276
|
}
|
|
6255
6277
|
const path = join(ctx.imagesDir, image);
|
|
6256
6278
|
if (!existsSync(path)) {
|
|
6257
|
-
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');
|
|
6258
6280
|
}
|
|
6259
6281
|
const sheet = readPlate(path);
|
|
6260
6282
|
if (sheet.width !== sheetWidth || sheet.height !== sheetHeight) {
|
|
@@ -6268,7 +6290,7 @@ function readGreySheet(
|
|
|
6268
6290
|
`declares scale: ${scaled.scale}, which makes a texel ${f32(1 / scaled.scale)}px of the drawing. It is ` +
|
|
6269
6291
|
"sampled in the part's own pixel grid, which is the drawing's, at each vertex's texel position over " +
|
|
6270
6292
|
`that scale — so the sheet is ${scaled.width}x${scaled.height}, the size of the loose art, whatever the ` +
|
|
6271
|
-
'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',
|
|
6272
6294
|
);
|
|
6273
6295
|
}
|
|
6274
6296
|
// One channel, proved rather than assumed.
|
|
@@ -6283,7 +6305,7 @@ function readGreySheet(
|
|
|
6283
6305
|
throw new CompileError(
|
|
6284
6306
|
`${where}: the ${noun} "${image}" is not greyscale — pixel (${i % sheet.width}, ${Math.floor(i / sheet.width)}) ` +
|
|
6285
6307
|
`is rgb(${r}, ${g}, ${b}). It is one channel; reading red out of a colour file would use whatever that ` +
|
|
6286
|
-
'channel happened to hold.',
|
|
6308
|
+
'channel happened to hold.', 'CE_COMPILE_GREY_SHEET_NOT_GREYSCALE',
|
|
6287
6309
|
);
|
|
6288
6310
|
}
|
|
6289
6311
|
level[i] = r;
|
|
@@ -6317,20 +6339,20 @@ function softRegionWeights(
|
|
|
6317
6339
|
): { weights: MeshVertexWeight[][]; levels: number[]; bone: string; mask: string; digest: string; carried: number; ramped: number } {
|
|
6318
6340
|
const { bone } = spec;
|
|
6319
6341
|
if (typeof bone !== 'string' || bone.length === 0) {
|
|
6320
|
-
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');
|
|
6321
6343
|
}
|
|
6322
6344
|
if (!ctx.bones.some((b) => b.name === bone)) {
|
|
6323
6345
|
throw new CompileError(
|
|
6324
6346
|
`${where}: "soft" names bone "${bone}", which this rig does not declare. rigc binds a region to a bone that ` +
|
|
6325
6347
|
'already exists rather than creating one — a bone a physics constraint has to target is part of the ' +
|
|
6326
|
-
'skeleton, not a side effect of a mesh.',
|
|
6348
|
+
'skeleton, not a side effect of a mesh.', 'CE_COMPILE_SOFT_BONE_UNKNOWN',
|
|
6327
6349
|
);
|
|
6328
6350
|
}
|
|
6329
6351
|
if (bone === ctx.anchorBone) {
|
|
6330
6352
|
throw new CompileError(
|
|
6331
6353
|
`${where}: "soft" names "${bone}", which is this slot's own bone. Carrying the region with the bone the rest ` +
|
|
6332
6354
|
'of the mesh is already pinned to moves nothing — a soft region needs a bone that can move independently, ' +
|
|
6333
|
-
'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',
|
|
6334
6356
|
);
|
|
6335
6357
|
}
|
|
6336
6358
|
const { map: sheet } = readGreySheet(spec.mask, 'soft', grid, where, ctx);
|
|
@@ -6355,7 +6377,7 @@ function softRegionWeights(
|
|
|
6355
6377
|
throw new CompileError(
|
|
6356
6378
|
`${where}: the soft mask "${spec.mask}" carries no vertex of this mesh — every one of its ${points.length} ` +
|
|
6357
6379
|
'vertices samples black. A region that moves nothing is a physics constraint with nothing on the end of it; ' +
|
|
6358
|
-
'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',
|
|
6359
6381
|
);
|
|
6360
6382
|
}
|
|
6361
6383
|
return { weights, levels, bone, mask: spec.mask, digest: depthDigest(sheet), carried, ramped };
|
|
@@ -6387,7 +6409,7 @@ function buildGridAttachment(
|
|
|
6387
6409
|
if (att.image === undefined) {
|
|
6388
6410
|
throw new CompileError(
|
|
6389
6411
|
`${where}: a "grid" generator lays its lattice over the part's own window, so the attachment needs an ` +
|
|
6390
|
-
'"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',
|
|
6391
6413
|
);
|
|
6392
6414
|
}
|
|
6393
6415
|
const stated = generator.us !== undefined || generator.vs !== undefined;
|
|
@@ -6396,18 +6418,18 @@ function buildGridAttachment(
|
|
|
6396
6418
|
throw new CompileError(
|
|
6397
6419
|
`${where}: the "grid" generator states both positions ("us"/"vs") and a count ("cols"/"rows"). They are two ` +
|
|
6398
6420
|
'answers to where the columns go, and a spec carrying both leaves a reader unable to say which one built ' +
|
|
6399
|
-
'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',
|
|
6400
6422
|
);
|
|
6401
6423
|
}
|
|
6402
6424
|
if (!stated && !even) {
|
|
6403
6425
|
throw new CompileError(
|
|
6404
6426
|
`${where}: the "grid" generator says neither where its columns go ("us"/"vs", fractions of the window) nor ` +
|
|
6405
|
-
'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',
|
|
6406
6428
|
);
|
|
6407
6429
|
}
|
|
6408
6430
|
const spread = (n: unknown, name: string): number[] => {
|
|
6409
6431
|
if (typeof n !== 'number' || !Number.isInteger(n) || n < 2) {
|
|
6410
|
-
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');
|
|
6411
6433
|
}
|
|
6412
6434
|
// An even division spans the whole window. A lattice that should sit inside
|
|
6413
6435
|
// it states its own positions — which is what `us`/`vs` are for.
|
|
@@ -6418,7 +6440,7 @@ function buildGridAttachment(
|
|
|
6418
6440
|
if (us === undefined || vs === undefined) {
|
|
6419
6441
|
throw new CompileError(
|
|
6420
6442
|
`${where}: the "grid" generator states ${us === undefined ? '"vs"' : '"us"'} and not ` +
|
|
6421
|
-
`${us === undefined ? '"us"' : '"vs"'}; a lattice needs both axes`,
|
|
6443
|
+
`${us === undefined ? '"us"' : '"vs"'}; a lattice needs both axes`, 'CE_COMPILE_GRID_AXIS_MISSING',
|
|
6422
6444
|
);
|
|
6423
6445
|
}
|
|
6424
6446
|
|
|
@@ -6429,7 +6451,7 @@ function buildGridAttachment(
|
|
|
6429
6451
|
try {
|
|
6430
6452
|
geometry = buildGridMesh({ size: [plate.width, plate.height], us, vs });
|
|
6431
6453
|
} catch (err) {
|
|
6432
|
-
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');
|
|
6433
6455
|
throw err;
|
|
6434
6456
|
}
|
|
6435
6457
|
|
|
@@ -6437,16 +6459,16 @@ function buildGridAttachment(
|
|
|
6437
6459
|
const h = img.height;
|
|
6438
6460
|
const toArt = w / plate.width;
|
|
6439
6461
|
if (att.width !== undefined && att.width !== w) {
|
|
6440
|
-
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');
|
|
6441
6463
|
}
|
|
6442
6464
|
if (att.height !== undefined && att.height !== h) {
|
|
6443
|
-
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');
|
|
6444
6466
|
}
|
|
6445
6467
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
6446
6468
|
const frame = ctx.exactFrames.get(ctx.anchorBone);
|
|
6447
|
-
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');
|
|
6448
6470
|
if (!ctx.bones.some((b) => b.name === ctx.anchorBone)) {
|
|
6449
|
-
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');
|
|
6450
6472
|
}
|
|
6451
6473
|
// Sampled BEFORE the encode, because `bind` rewrites the weights the encode
|
|
6452
6474
|
// then writes out.
|
|
@@ -6537,21 +6559,21 @@ function segmentsFalloffOf(f: RigSegmentsFalloff, where: string): SegmentsFallof
|
|
|
6537
6559
|
if (typeof f.radius !== 'number' || !Number.isFinite(f.radius) || !(f.radius > 0)) {
|
|
6538
6560
|
throw new CompileError(
|
|
6539
6561
|
`${where}: "falloff.radius" is ${JSON.stringify(f.radius)}; it is added to every distance before the power is ` +
|
|
6540
|
-
"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',
|
|
6541
6563
|
);
|
|
6542
6564
|
}
|
|
6543
6565
|
const power = f.power ?? SEGMENTS_DEFAULTS.power;
|
|
6544
6566
|
if (typeof power !== 'number' || !Number.isFinite(power) || !(power > 0)) {
|
|
6545
6567
|
throw new CompileError(
|
|
6546
6568
|
`${where}: "falloff.power" is ${JSON.stringify(power)}; it is the exponent a pull falls off with distance by, ` +
|
|
6547
|
-
'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',
|
|
6548
6570
|
);
|
|
6549
6571
|
}
|
|
6550
6572
|
const maxBones = f.maxBones ?? SEGMENTS_DEFAULTS.maxBones;
|
|
6551
6573
|
if (typeof maxBones !== 'number' || !Number.isInteger(maxBones) || maxBones < 1) {
|
|
6552
6574
|
throw new CompileError(
|
|
6553
6575
|
`${where}: "falloff.maxBones" is ${JSON.stringify(maxBones)}; it is how many bones may pull one vertex, a ` +
|
|
6554
|
-
'whole number of at least 1',
|
|
6576
|
+
'whole number of at least 1', 'CE_COMPILE_SEGMENTS_FALLOFF_MAX_BONES_INVALID',
|
|
6555
6577
|
);
|
|
6556
6578
|
}
|
|
6557
6579
|
const minWeight = f.minWeight ?? SEGMENTS_DEFAULTS.minWeight;
|
|
@@ -6560,7 +6582,7 @@ function segmentsFalloffOf(f: RigSegmentsFalloff, where: string): SegmentsFallof
|
|
|
6560
6582
|
`${where}: "falloff.minWeight" is ${JSON.stringify(minWeight)}; it is the share under which a bone's pull ` +
|
|
6561
6583
|
`is dropped, so it lies in ${SEGMENTS_WEIGHT_STEP}..1 with 1 excluded — at 1 or above every share is dropped, ` +
|
|
6562
6584
|
`and under ${SEGMENTS_WEIGHT_STEP}, one step of the 6-decimal grid weights are written on, a kept binding ` +
|
|
6563
|
-
'could be written as 0',
|
|
6585
|
+
'could be written as 0', 'CE_COMPILE_SEGMENTS_FALLOFF_MIN_WEIGHT_INVALID',
|
|
6564
6586
|
);
|
|
6565
6587
|
}
|
|
6566
6588
|
return { power, radius: f.radius, maxBones, minWeight };
|
|
@@ -6581,12 +6603,12 @@ function segmentsInfluenceOf(
|
|
|
6581
6603
|
throw new CompileError(
|
|
6582
6604
|
`${where}: the "segments" generator states both "falloff" and "influence". Both turn a distance into a ` +
|
|
6583
6605
|
'share — "falloff" as 1 / (d + radius)^power over every segment, "influence" as a pull that reaches zero at ' +
|
|
6584
|
-
'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',
|
|
6585
6607
|
);
|
|
6586
6608
|
}
|
|
6587
6609
|
if (typeof spec !== 'object' || spec === null || Array.isArray(spec)) {
|
|
6588
6610
|
throw new CompileError(
|
|
6589
|
-
`${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',
|
|
6590
6612
|
);
|
|
6591
6613
|
}
|
|
6592
6614
|
const i = spec as Partial<Record<keyof SegmentsInfluence, unknown>>;
|
|
@@ -6594,20 +6616,20 @@ function segmentsInfluenceOf(
|
|
|
6594
6616
|
throw new CompileError(
|
|
6595
6617
|
`${where}: "influence.radius" is ${JSON.stringify(i.radius) ?? 'absent'}; it is how far a link reaches, in ` +
|
|
6596
6618
|
"the part's pixels, a positive number with no default — a length on this art: a link whose segment is that " +
|
|
6597
|
-
'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',
|
|
6598
6620
|
);
|
|
6599
6621
|
}
|
|
6600
6622
|
if (typeof i.across !== 'boolean') {
|
|
6601
6623
|
throw new CompileError(
|
|
6602
6624
|
`${where}: "influence.across" is ${JSON.stringify(i.across) ?? 'absent'}; it is true (every link within ` +
|
|
6603
6625
|
'"radius" pulls, across the entries of "bones") or false (only the links of the entry nearest the vertex), ' +
|
|
6604
|
-
"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',
|
|
6605
6627
|
);
|
|
6606
6628
|
}
|
|
6607
6629
|
if (typeof i.max !== 'number' || !Number.isInteger(i.max) || i.max < 1) {
|
|
6608
6630
|
throw new CompileError(
|
|
6609
6631
|
`${where}: "influence.max" is ${JSON.stringify(i.max) ?? 'absent'}; it is how many links may pull one vertex, ` +
|
|
6610
|
-
'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',
|
|
6611
6633
|
);
|
|
6612
6634
|
}
|
|
6613
6635
|
const shape = RIG_INFLUENCE_SHAPES.find((name) => name === i.shape);
|
|
@@ -6615,7 +6637,7 @@ function segmentsInfluenceOf(
|
|
|
6615
6637
|
throw new CompileError(
|
|
6616
6638
|
`${where}: "influence.shape" is ${JSON.stringify(i.shape) ?? 'absent'}; one of ` +
|
|
6617
6639
|
`${RIG_INFLUENCE_SHAPES.map((name) => JSON.stringify(name)).join(', ')} — how a pull falls from 1 on a link ` +
|
|
6618
|
-
'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',
|
|
6619
6641
|
);
|
|
6620
6642
|
}
|
|
6621
6643
|
return { radius: i.radius, across: i.across, max: i.max, shape };
|
|
@@ -6649,7 +6671,7 @@ function buildSegmentsAttachment(
|
|
|
6649
6671
|
if (att.image === undefined) {
|
|
6650
6672
|
throw new CompileError(
|
|
6651
6673
|
`${where}: a "segments" generator lays its lattice over the part's own alpha, so the attachment needs an ` +
|
|
6652
|
-
'"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',
|
|
6653
6675
|
);
|
|
6654
6676
|
}
|
|
6655
6677
|
const img = atlasedImage(att.image, where, ctx);
|
|
@@ -6657,7 +6679,7 @@ function buildSegmentsAttachment(
|
|
|
6657
6679
|
throw new CompileError(
|
|
6658
6680
|
`${where}: a "segments" generator lays its lattice over the part's own alpha, and "${att.image}" is lifted ` +
|
|
6659
6681
|
'off a packed page at the coordinates the atlas states, which on this file are not where its texels are — ' +
|
|
6660
|
-
`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',
|
|
6661
6683
|
);
|
|
6662
6684
|
}
|
|
6663
6685
|
if (img.atlasScale !== undefined) {
|
|
@@ -6665,7 +6687,7 @@ function buildSegmentsAttachment(
|
|
|
6665
6687
|
`${where}: a "segments" generator measures its "cell" and its falloff "radius" in the part's pixels, and ` +
|
|
6666
6688
|
`"${att.image}" sits on a page that declares scale: ${img.atlasScale}, where a pixel of the drawing is not a ` +
|
|
6667
6689
|
'texel of the page. This generator does not convert between the two; build it from the loose part, or ' +
|
|
6668
|
-
'from a pack at scale 1',
|
|
6690
|
+
'from a pack at scale 1', 'CE_COMPILE_SEGMENTS_ATLAS_SCALE_UNSUPPORTED',
|
|
6669
6691
|
);
|
|
6670
6692
|
}
|
|
6671
6693
|
|
|
@@ -6677,7 +6699,7 @@ function buildSegmentsAttachment(
|
|
|
6677
6699
|
`${where}: the "segments" generator states no "falloff" block. Its "radius" — added to every distance, in ` +
|
|
6678
6700
|
"the part's pixels — has no default, because it is a length on this art: " +
|
|
6679
6701
|
'"falloff": { "radius": <pixels> } is the least it takes (or an "influence" block, whose pull reaches zero ' +
|
|
6680
|
-
'at its own "radius")',
|
|
6702
|
+
'at its own "radius")', 'CE_COMPILE_SEGMENTS_WEIGHT_MODE_MISSING',
|
|
6681
6703
|
);
|
|
6682
6704
|
}
|
|
6683
6705
|
const falloff = influence === null ? segmentsFalloffOf(generator.falloff!, where) : null;
|
|
@@ -6693,16 +6715,16 @@ function buildSegmentsAttachment(
|
|
|
6693
6715
|
cell: generator.cell,
|
|
6694
6716
|
});
|
|
6695
6717
|
} catch (err) {
|
|
6696
|
-
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');
|
|
6697
6719
|
throw err;
|
|
6698
6720
|
}
|
|
6699
6721
|
const w = img.width;
|
|
6700
6722
|
const h = img.height;
|
|
6701
6723
|
if (att.width !== undefined && att.width !== w) {
|
|
6702
|
-
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');
|
|
6703
6725
|
}
|
|
6704
6726
|
if (att.height !== undefined && att.height !== h) {
|
|
6705
|
-
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');
|
|
6706
6728
|
}
|
|
6707
6729
|
|
|
6708
6730
|
// -- the placement ---------------------------------------------------------
|
|
@@ -6711,14 +6733,14 @@ function buildSegmentsAttachment(
|
|
|
6711
6733
|
// MEASURED in — distances, falloff, `minWeight`, the shared-origin refusal —
|
|
6712
6734
|
// and the runtime's is what the vertices are BOUND through (`place`).
|
|
6713
6735
|
const exactSlotBone = ctx.exactFrames.get(ctx.anchorBone);
|
|
6714
|
-
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');
|
|
6715
6737
|
let anchorAt: [number, number] = [w / 2, h / 2];
|
|
6716
6738
|
if (generator.anchor !== undefined) {
|
|
6717
6739
|
const stated = finitePair(generator.anchor);
|
|
6718
6740
|
if (stated === null) {
|
|
6719
6741
|
throw new CompileError(
|
|
6720
6742
|
`${where}: "anchor" is ${JSON.stringify(generator.anchor)}; it is where the slot bone sits in the part's ` +
|
|
6721
|
-
'pixels, y down, as [x, y] — two finite numbers',
|
|
6743
|
+
'pixels, y down, as [x, y] — two finite numbers', 'CE_COMPILE_SEGMENTS_ANCHOR_INVALID',
|
|
6722
6744
|
);
|
|
6723
6745
|
}
|
|
6724
6746
|
anchorAt = stated;
|
|
@@ -6736,7 +6758,7 @@ function buildSegmentsAttachment(
|
|
|
6736
6758
|
throw new CompileError(
|
|
6737
6759
|
`${where}: the "segments" generator names no bones. "bones" is the one decision this generator takes — which ` +
|
|
6738
6760
|
'segments may pull the part — so it lists at least one: a bone name, a chain as a list of bone names, or ' +
|
|
6739
|
-
'{ "bone", "from", "to" }',
|
|
6761
|
+
'{ "bone", "from", "to" }', 'CE_COMPILE_SEGMENTS_BONES_EMPTY',
|
|
6740
6762
|
);
|
|
6741
6763
|
}
|
|
6742
6764
|
const parentOf = new Map(ctx.bones.map((b) => [b.name, b.parent ?? null]));
|
|
@@ -6745,7 +6767,7 @@ function buildSegmentsAttachment(
|
|
|
6745
6767
|
const segments: EntrySegment[] = [];
|
|
6746
6768
|
const boneOf = (name: unknown, at: string): { index: number; transform: BoneTransform } => {
|
|
6747
6769
|
if (typeof name !== 'string' || !parentOf.has(name)) {
|
|
6748
|
-
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');
|
|
6749
6771
|
}
|
|
6750
6772
|
let index = named.indexOf(name);
|
|
6751
6773
|
if (index < 0) {
|
|
@@ -6763,7 +6785,7 @@ function buildSegmentsAttachment(
|
|
|
6763
6785
|
'so it has an origin and no segment. Give the bone a "length" (its segment runs from its origin to that ' +
|
|
6764
6786
|
'tip, along its setup rotation), ' +
|
|
6765
6787
|
(chained ? 'end the chain on a bone that has one, ' : 'name it inside a chain whose next link says where it ends, ') +
|
|
6766
|
-
'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',
|
|
6767
6789
|
);
|
|
6768
6790
|
}
|
|
6769
6791
|
return toWorld(transform, length, 0);
|
|
@@ -6777,7 +6799,7 @@ function buildSegmentsAttachment(
|
|
|
6777
6799
|
}
|
|
6778
6800
|
if (Array.isArray(entry)) {
|
|
6779
6801
|
if (entry.length === 0) {
|
|
6780
|
-
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');
|
|
6781
6803
|
}
|
|
6782
6804
|
const links = entry.map((name: unknown, k: number) => ({ name, ...boneOf(name, `${at}[${k}]`) }));
|
|
6783
6805
|
links.forEach((link, k) => {
|
|
@@ -6795,7 +6817,7 @@ function buildSegmentsAttachment(
|
|
|
6795
6817
|
throw new CompileError(
|
|
6796
6818
|
`${where}: ${at} chains "${name}" to "${nextName}", and "${nextName}" is not under "${name}" in the bone ` +
|
|
6797
6819
|
`tree — a chain runs down the skeleton, each link a descendant of the one before it. List the chain ` +
|
|
6798
|
-
'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',
|
|
6799
6821
|
);
|
|
6800
6822
|
}
|
|
6801
6823
|
const b: [number, number] = [next.transform.worldX, next.transform.worldY];
|
|
@@ -6803,7 +6825,7 @@ function buildSegmentsAttachment(
|
|
|
6803
6825
|
throw new CompileError(
|
|
6804
6826
|
`${where}: ${at} chains "${name}" to "${nextName}", and the two bones share their setup origin ` +
|
|
6805
6827
|
`(${origin[0]}, ${origin[1]}), so the link has no length and no direction to weight along. Give ` +
|
|
6806
|
-
`"${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',
|
|
6807
6829
|
);
|
|
6808
6830
|
}
|
|
6809
6831
|
segments.push({ bone: link.index, entry: i, a: origin, b });
|
|
@@ -6818,7 +6840,7 @@ function buildSegmentsAttachment(
|
|
|
6818
6840
|
if (from === null || to === null) {
|
|
6819
6841
|
throw new CompileError(
|
|
6820
6842
|
`${where}: ${at} states a segment whose ${from === null ? '"from"' : '"to"'} is ` +
|
|
6821
|
-
`${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',
|
|
6822
6844
|
);
|
|
6823
6845
|
}
|
|
6824
6846
|
segments.push({ bone: index, entry: i, a: measuredAt(from[0], from[1]), b: measuredAt(to[0], to[1]) });
|
|
@@ -6826,7 +6848,7 @@ function buildSegmentsAttachment(
|
|
|
6826
6848
|
}
|
|
6827
6849
|
throw new CompileError(
|
|
6828
6850
|
`${where}: ${at} is ${JSON.stringify(entry)}; an entry is a bone name, a chain as a list of bone names, or ` +
|
|
6829
|
-
'{ "bone", "from", "to" }',
|
|
6851
|
+
'{ "bone", "from", "to" }', 'CE_COMPILE_SEGMENTS_ENTRY_INVALID',
|
|
6830
6852
|
);
|
|
6831
6853
|
});
|
|
6832
6854
|
|
|
@@ -6846,7 +6868,7 @@ function buildSegmentsAttachment(
|
|
|
6846
6868
|
`"${named[reached.nearest.bone]}" at ${reached.nearest.distance.toFixed(4)}, and "influence.radius" is ` +
|
|
6847
6869
|
`${influence.radius}${influence.across ? '' : ' (with "across": false, the nearest entry\'s links only)'}. ` +
|
|
6848
6870
|
'A vertex needs a link, and rigc will not hand it one the reach does not: raise "radius" over that distance, ' +
|
|
6849
|
-
'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',
|
|
6850
6872
|
);
|
|
6851
6873
|
}
|
|
6852
6874
|
const got = segmentShares(measuredAt(px, py), segments, falloff!);
|
|
@@ -6857,7 +6879,7 @@ function buildSegmentsAttachment(
|
|
|
6857
6879
|
`strongest pull(s) share it as ${got.dropped.map((d) => `"${named[d.bone]}" ${d.share.toFixed(4)}`).join(', ')}, ` +
|
|
6858
6880
|
`and every one is under "minWeight" ${minWeight}. A vertex needs a bone, and rigc will not pick one the ` +
|
|
6859
6881
|
`falloff dropped: lower "minWeight" to ${(1 / maxBones).toFixed(4)} or under (1 / "maxBones", which the ` +
|
|
6860
|
-
'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',
|
|
6861
6883
|
);
|
|
6862
6884
|
});
|
|
6863
6885
|
const geometry: MeshGeometry = {
|
|
@@ -6962,7 +6984,7 @@ function buildContourAttachment(
|
|
|
6962
6984
|
if (att.image === undefined) {
|
|
6963
6985
|
throw new CompileError(
|
|
6964
6986
|
`${where}: a "contour" generator traces the part's own alpha, so the attachment needs an "image" — ` +
|
|
6965
|
-
'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',
|
|
6966
6988
|
);
|
|
6967
6989
|
}
|
|
6968
6990
|
const img = atlasedImage(att.image, where, ctx);
|
|
@@ -6981,7 +7003,7 @@ function buildContourAttachment(
|
|
|
6981
7003
|
throw new CompileError(
|
|
6982
7004
|
`${where}: a "contour" generator traces the part's own alpha, and "${att.image}" is lifted off a packed ` +
|
|
6983
7005
|
'page at the coordinates the atlas states, which on this file are not where its texels are — so there is ' +
|
|
6984
|
-
`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',
|
|
6985
7007
|
);
|
|
6986
7008
|
}
|
|
6987
7009
|
// ⚠️ Nothing here reads the PNG's colour type. `hasAlpha` answers "where does
|
|
@@ -7009,7 +7031,7 @@ function buildContourAttachment(
|
|
|
7009
7031
|
...(img.atlasScale === undefined ? {} : { pageScale: img.atlasScale }),
|
|
7010
7032
|
});
|
|
7011
7033
|
} catch (err) {
|
|
7012
|
-
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');
|
|
7013
7035
|
throw err;
|
|
7014
7036
|
}
|
|
7015
7037
|
|
|
@@ -7024,16 +7046,16 @@ function buildContourAttachment(
|
|
|
7024
7046
|
const h = img.height;
|
|
7025
7047
|
const toArt = w / plate.width;
|
|
7026
7048
|
if (att.width !== undefined && att.width !== w) {
|
|
7027
|
-
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');
|
|
7028
7050
|
}
|
|
7029
7051
|
if (att.height !== undefined && att.height !== h) {
|
|
7030
|
-
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');
|
|
7031
7053
|
}
|
|
7032
7054
|
const anchor = ctx.transforms.get(ctx.anchorBone);
|
|
7033
7055
|
const frame = ctx.exactFrames.get(ctx.anchorBone);
|
|
7034
|
-
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');
|
|
7035
7057
|
if (!ctx.bones.some((b) => b.name === ctx.anchorBone)) {
|
|
7036
|
-
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');
|
|
7037
7059
|
}
|
|
7038
7060
|
const vertices = bindWeightedVertices(
|
|
7039
7061
|
geometry,
|
|
@@ -7147,7 +7169,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7147
7169
|
const boneNames = ctx.boneNames;
|
|
7148
7170
|
const needBone = (name: unknown, field: string): string => {
|
|
7149
7171
|
if (typeof name !== 'string' || !boneNames.has(name)) {
|
|
7150
|
-
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');
|
|
7151
7173
|
}
|
|
7152
7174
|
return name;
|
|
7153
7175
|
};
|
|
@@ -7164,14 +7186,14 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7164
7186
|
throw new CompileError(
|
|
7165
7187
|
`${where}: ${field} is ${JSON.stringify(value)}; known: ${allowed.join(', ')} (only the first letter's case is ` +
|
|
7166
7188
|
"free — the parser's enumValue uppercases that one character and nothing else, and an unresolved name " +
|
|
7167
|
-
'becomes undefined without an error)',
|
|
7189
|
+
'becomes undefined without an error)', 'CE_COMPILE_CONSTRAINT_ENUM_UNKNOWN',
|
|
7168
7190
|
);
|
|
7169
7191
|
}
|
|
7170
7192
|
return value;
|
|
7171
7193
|
};
|
|
7172
7194
|
const needNumber = (value: unknown, field: string): number => {
|
|
7173
7195
|
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
7174
|
-
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');
|
|
7175
7197
|
}
|
|
7176
7198
|
return value;
|
|
7177
7199
|
};
|
|
@@ -7189,7 +7211,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7189
7211
|
const boneList = (): string[] => {
|
|
7190
7212
|
const list = spec.bones;
|
|
7191
7213
|
if (!Array.isArray(list) || list.length === 0) {
|
|
7192
|
-
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');
|
|
7193
7215
|
}
|
|
7194
7216
|
return list.map((name, i) => needBone(name, `bones[${i}]`));
|
|
7195
7217
|
};
|
|
@@ -7214,11 +7236,11 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7214
7236
|
// places in this format that does not fail silently — but it throws at load
|
|
7215
7237
|
// time, in the consumer's process, and that is late.
|
|
7216
7238
|
if (!TRANSFORM_PROPERTIES.includes(from)) {
|
|
7217
|
-
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');
|
|
7218
7240
|
}
|
|
7219
7241
|
for (const to of Object.keys(entry?.to ?? {})) {
|
|
7220
7242
|
if (!TRANSFORM_PROPERTIES.includes(to)) {
|
|
7221
|
-
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');
|
|
7222
7244
|
}
|
|
7223
7245
|
}
|
|
7224
7246
|
}
|
|
@@ -7250,7 +7272,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7250
7272
|
// loud, but in the consumer's process. The silent half is the one below it.
|
|
7251
7273
|
const slot = spec.slot;
|
|
7252
7274
|
if (typeof slot !== 'string' || !ctx.slotNames.has(slot)) {
|
|
7253
|
-
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');
|
|
7254
7276
|
}
|
|
7255
7277
|
if (!ctx.pathSlots.has(slot)) {
|
|
7256
7278
|
// `PathConstraint.update` opens with
|
|
@@ -7261,7 +7283,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7261
7283
|
`${where}: slot "${slot}" has no path attachment in any skin, so the constraint has no curve to follow — ` +
|
|
7262
7284
|
'PathConstraint.update returns immediately unless the slot\'s attachment is a path, which means this ' +
|
|
7263
7285
|
'constraint would load, report every mix it was given, and move nothing. ' +
|
|
7264
|
-
'Give that slot an attachment with "type": "path".',
|
|
7286
|
+
'Give that slot an attachment with "type": "path".', 'CE_COMPILE_PATH_CONSTRAINT_SLOT_NO_PATH',
|
|
7265
7287
|
);
|
|
7266
7288
|
}
|
|
7267
7289
|
out.slot = slot;
|
|
@@ -7287,14 +7309,14 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7287
7309
|
if (typeof animation !== 'string' || animation.length === 0) {
|
|
7288
7310
|
throw new CompileError(
|
|
7289
7311
|
`${where}: a slider needs an "animation" — the animation it applies. Without one the parser's second pass ` +
|
|
7290
|
-
'over the constraints array throws `Slider animation not found`.',
|
|
7312
|
+
'over the constraints array throws `Slider animation not found`.', 'CE_COMPILE_SLIDER_ANIMATION_MISSING',
|
|
7291
7313
|
);
|
|
7292
7314
|
}
|
|
7293
7315
|
if (!ctx.animationNames.has(animation)) {
|
|
7294
7316
|
const known = [...ctx.animationNames];
|
|
7295
7317
|
throw new CompileError(
|
|
7296
7318
|
`${where}: applies animation "${animation}", which the motion spec does not declare` +
|
|
7297
|
-
(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',
|
|
7298
7320
|
);
|
|
7299
7321
|
}
|
|
7300
7322
|
// 🔁 The editor keeps a slider's animation by index and re-sorts the object
|
|
@@ -7309,7 +7331,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7309
7331
|
`lists every array-index key (${spelled} here) before the rest, the editor re-sorts the animations into its ` +
|
|
7310
7332
|
'own order on import, and a slider holds its animation by index (measured on Spine 4.3.26; AUTHORING R10). ' +
|
|
7311
7333
|
`Rename ${spelled} so no animation name is a plain non-negative integer — a prefix or a leading zero ` +
|
|
7312
|
-
`("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',
|
|
7313
7335
|
);
|
|
7314
7336
|
}
|
|
7315
7337
|
out.animation = animation;
|
|
@@ -7329,7 +7351,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7329
7351
|
if (typeof property !== 'string' || !RIG_FROM_PROPERTIES.includes(property as (typeof RIG_FROM_PROPERTIES)[number])) {
|
|
7330
7352
|
throw new CompileError(
|
|
7331
7353
|
`${where}: drives off bone "${String(spec.bone)}" but its property is ${JSON.stringify(property)}; ` +
|
|
7332
|
-
`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',
|
|
7333
7355
|
);
|
|
7334
7356
|
}
|
|
7335
7357
|
out.property = property;
|
|
@@ -7339,7 +7361,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7339
7361
|
if (spec.local !== undefined) out.local = spec.local;
|
|
7340
7362
|
if (spec.scale !== undefined && spec.scale === 0) {
|
|
7341
7363
|
// time = to + (value - from) * 0, so the slider holds one frame forever.
|
|
7342
|
-
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');
|
|
7343
7365
|
}
|
|
7344
7366
|
// The window of driving values that can reach a frame at all, and the two
|
|
7345
7367
|
// functions every figure in the two reader clauses below comes off.
|
|
@@ -7586,7 +7608,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7586
7608
|
`at ${dead.end.toFixed(3)}° is read as ${readAs.toFixed(3)}° and maps to time ${lands.toFixed(3)}s. ` +
|
|
7587
7609
|
`${consequence} The whole part of the range ${dead.side} is dead and ` +
|
|
7588
7610
|
'nothing at runtime reports it. Add `"local": true` to read the bone\'s own rotation signed and unwrapped — ' +
|
|
7589
|
-
`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',
|
|
7590
7612
|
);
|
|
7591
7613
|
}
|
|
7592
7614
|
}
|
|
@@ -7659,14 +7681,14 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7659
7681
|
`${lowest.toFixed(3)} is read as ${readAs.toFixed(3)} and maps to time ${lands.toFixed(3)}s — the time the ` +
|
|
7660
7682
|
`bone at ${readAs.toFixed(3)} maps to. Positions below 0 read as the mirror of positions above it: ` +
|
|
7661
7683
|
`${consequence} Nothing at runtime reports it. Add \`"local": true\` to read the bone's own scale signed and ` +
|
|
7662
|
-
'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',
|
|
7663
7685
|
);
|
|
7664
7686
|
}
|
|
7665
7687
|
for (const field of timeSide) {
|
|
7666
7688
|
if (spec[field] !== undefined) {
|
|
7667
7689
|
throw new CompileError(
|
|
7668
7690
|
`${where}: declares both a "bone" and "${field}". A slider with a bone takes its time from that bone's ` +
|
|
7669
|
-
`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',
|
|
7670
7692
|
);
|
|
7671
7693
|
}
|
|
7672
7694
|
}
|
|
@@ -7677,7 +7699,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7677
7699
|
throw new CompileError(
|
|
7678
7700
|
`${where}: declares "${field}" but no "bone". Every one of ${boneSide.join('/')} is read only inside the ` +
|
|
7679
7701
|
"parser's `if (boneName)` branch (`:350-360`), so it would be dropped in silence. Name the driving bone, " +
|
|
7680
|
-
'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',
|
|
7681
7703
|
);
|
|
7682
7704
|
}
|
|
7683
7705
|
}
|
|
@@ -7722,7 +7744,7 @@ function buildRigConstraint(spec: RigConstraintInput, ctx: ConstraintContext): M
|
|
|
7722
7744
|
throw new NotImplementedError(
|
|
7723
7745
|
`${where}: constraint type ${JSON.stringify(spec.type)} is not one Spine 4.3 knows. ` +
|
|
7724
7746
|
'The five are: ik, transform, path, physics, slider. An unrecognised type matches no case in the parser and ' +
|
|
7725
|
-
'the constraint is dropped without a word.',
|
|
7747
|
+
'the constraint is dropped without a word.', 'CE_COMPILE_CONSTRAINT_TYPE_UNKNOWN',
|
|
7726
7748
|
);
|
|
7727
7749
|
}
|
|
7728
7750
|
|
|
@@ -7744,7 +7766,7 @@ function buildRigInfo(
|
|
|
7744
7766
|
): RigInfo {
|
|
7745
7767
|
const axisBone = rig.invariants?.axisBone ?? null;
|
|
7746
7768
|
if (axisBone !== null && !bones.some((b) => b.name === axisBone)) {
|
|
7747
|
-
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');
|
|
7748
7770
|
}
|
|
7749
7771
|
const axisSubtree: string[] = [];
|
|
7750
7772
|
if (axisBone) {
|
|
@@ -7772,7 +7794,7 @@ function buildRigInfo(
|
|
|
7772
7794
|
if (meshKinds[slot] === undefined) {
|
|
7773
7795
|
throw new CompileError(
|
|
7774
7796
|
`rig "${rig.name}" exempts slot "${slot}" from A39_DEFORM_KEEPS_TRIANGLE_WINDING, but that slot carries no ` +
|
|
7775
|
-
'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',
|
|
7776
7798
|
);
|
|
7777
7799
|
}
|
|
7778
7800
|
}
|
|
@@ -7785,12 +7807,12 @@ function buildRigInfo(
|
|
|
7785
7807
|
spineDeg === null ? null : [Math.cos((spineDeg * Math.PI) / 180), Math.sin((spineDeg * Math.PI) / 180)];
|
|
7786
7808
|
const contactDepth = manifest?.stroke?.contact_depth ?? null;
|
|
7787
7809
|
if (contactDepth !== null && !(contactDepth > 0)) {
|
|
7788
|
-
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');
|
|
7789
7811
|
}
|
|
7790
7812
|
const capCeiling = manifest?.stroke?.cap_containment_ceiling ?? null;
|
|
7791
7813
|
if (capCeiling !== null && !(capCeiling > 0)) {
|
|
7792
7814
|
throw new CompileError(
|
|
7793
|
-
`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',
|
|
7794
7816
|
);
|
|
7795
7817
|
}
|
|
7796
7818
|
return {
|
|
@@ -7829,13 +7851,13 @@ function checkAxisSelfConsistency(manifest: FaceManifest): void {
|
|
|
7829
7851
|
if (!manifest.axis) return;
|
|
7830
7852
|
const { deg, unit } = manifest.axis;
|
|
7831
7853
|
if (!Array.isArray(unit) || unit.length !== 2) {
|
|
7832
|
-
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');
|
|
7833
7855
|
}
|
|
7834
7856
|
const ex = Math.cos((deg * Math.PI) / 180);
|
|
7835
7857
|
const ey = Math.sin((deg * Math.PI) / 180);
|
|
7836
7858
|
if (hypot2(unit[0] - ex, unit[1] - ey) > 1e-3) {
|
|
7837
7859
|
throw new CompileError(
|
|
7838
|
-
`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',
|
|
7839
7861
|
);
|
|
7840
7862
|
}
|
|
7841
7863
|
}
|
|
@@ -7894,9 +7916,9 @@ function buildMesh(
|
|
|
7894
7916
|
const controls = meshControlBones(part);
|
|
7895
7917
|
|
|
7896
7918
|
const refFor = (name: string): MeshBoneRef => {
|
|
7897
|
-
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');
|
|
7898
7920
|
const m = transforms.get(name);
|
|
7899
|
-
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');
|
|
7900
7922
|
return { name, toBind: (wx, wy) => toBoneLocal(m, wx, wy) };
|
|
7901
7923
|
};
|
|
7902
7924
|
|
|
@@ -7923,7 +7945,7 @@ function buildMesh(
|
|
|
7923
7945
|
// bits turned an authored 90 into 89.99999734089101 in that refusal.
|
|
7924
7946
|
const controlAngles = ringControlAngles(controls, spec.center!, (name) => {
|
|
7925
7947
|
const m = exactFrames.get(name);
|
|
7926
|
-
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');
|
|
7927
7949
|
return [m.worldX, cropH - m.worldY];
|
|
7928
7950
|
});
|
|
7929
7951
|
geometry = buildRingMesh({
|
|
@@ -7936,7 +7958,7 @@ function buildMesh(
|
|
|
7936
7958
|
});
|
|
7937
7959
|
}
|
|
7938
7960
|
} catch (err) {
|
|
7939
|
-
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');
|
|
7940
7962
|
throw err;
|
|
7941
7963
|
}
|
|
7942
7964
|
|
|
@@ -7987,17 +8009,17 @@ function buildMesh(
|
|
|
7987
8009
|
*/
|
|
7988
8010
|
function rawCurve(curve: number[] | 'stepped', channels: number, where: string, at: string): number[] | 'stepped' {
|
|
7989
8011
|
if (curve === 'stepped') return 'stepped';
|
|
7990
|
-
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');
|
|
7991
8013
|
if (curve.length !== channels * 4) {
|
|
7992
8014
|
// A short array multiplies `undefined` into the cubic and yields NaN with no
|
|
7993
8015
|
// error at all — case 6g, and the reason A05 exists.
|
|
7994
8016
|
throw new CompileError(
|
|
7995
|
-
`${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',
|
|
7996
8018
|
);
|
|
7997
8019
|
}
|
|
7998
8020
|
for (const n of curve) {
|
|
7999
8021
|
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
8000
|
-
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');
|
|
8001
8023
|
}
|
|
8002
8024
|
}
|
|
8003
8025
|
return curve.map(f32);
|
|
@@ -8021,9 +8043,9 @@ function compileValueTrack(
|
|
|
8021
8043
|
kind: string,
|
|
8022
8044
|
): ModelKey[] {
|
|
8023
8045
|
const shape = shapes[track.property];
|
|
8024
|
-
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');
|
|
8025
8047
|
const where = `animation "${animName}" ${kind} "${target}" ${track.property}`;
|
|
8026
|
-
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');
|
|
8027
8049
|
|
|
8028
8050
|
const out: ModelKey[] = [];
|
|
8029
8051
|
for (let i = 0; i < track.keys.length; i++) {
|
|
@@ -8031,7 +8053,7 @@ function compileValueTrack(
|
|
|
8031
8053
|
const next = track.keys[i + 1];
|
|
8032
8054
|
const time = keyTime(key.t + shift);
|
|
8033
8055
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8034
|
-
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');
|
|
8035
8057
|
}
|
|
8036
8058
|
checkKeyTime(where, time, key.t, duration);
|
|
8037
8059
|
// A named-value timeline (`inherit`): the key's `v` is one name, stepped by
|
|
@@ -8044,12 +8066,12 @@ function compileValueTrack(
|
|
|
8044
8066
|
throw new CompileError(
|
|
8045
8067
|
`${where} key at t=${key.t} carries ${key.ease !== undefined ? `an easing ("${key.ease}")` : 'a curve'}, and ` +
|
|
8046
8068
|
`this timeline is stepped by the format — its reader builds no curve, so the mode changes AT the key and ` +
|
|
8047
|
-
'holds until the next one. Remove it',
|
|
8069
|
+
'holds until the next one. Remove it', 'CE_COMPILE_STEPPED_TIMELINE_EASING',
|
|
8048
8070
|
);
|
|
8049
8071
|
}
|
|
8050
8072
|
const mode = shape.named.resolve(key.v);
|
|
8051
8073
|
if (mode === undefined) {
|
|
8052
|
-
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');
|
|
8053
8075
|
}
|
|
8054
8076
|
out.push({ time, [shape.named.field]: mode });
|
|
8055
8077
|
continue;
|
|
@@ -8057,18 +8079,18 @@ function compileValueTrack(
|
|
|
8057
8079
|
// A no-field timeline (`reset`) is an event: the key IS the value, so it
|
|
8058
8080
|
// carries none. Anything else must match the field count exactly.
|
|
8059
8081
|
if (shape.fields.length === 0) {
|
|
8060
|
-
if (key.v !== null) throw new CompileError(`${where}: this timeline takes no value; use null
|
|
8061
|
-
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');
|
|
8062
8084
|
out.push({ time });
|
|
8063
8085
|
continue;
|
|
8064
8086
|
}
|
|
8065
8087
|
if (!Array.isArray(key.v) || key.v.length !== shape.fields.length) {
|
|
8066
|
-
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');
|
|
8067
8089
|
}
|
|
8068
8090
|
const entry: ModelKey = { time };
|
|
8069
8091
|
shape.fields.forEach((field, c) => {
|
|
8070
8092
|
const v = key.v as number[];
|
|
8071
|
-
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');
|
|
8072
8094
|
// The bound, where the runtime has one. Refused here rather than left to
|
|
8073
8095
|
// `A23` because this is a spec somebody wrote and the key is the thing to
|
|
8074
8096
|
// change — the same division `compileConstraintTrack`'s `range` makes for
|
|
@@ -8084,28 +8106,28 @@ function compileValueTrack(
|
|
|
8084
8106
|
// reads "… mass: value is 0", which says the property twice and the
|
|
8085
8107
|
// second time under the wrong name.
|
|
8086
8108
|
const at = shape.fields.length > 1 ? `${field} at t=${key.t}` : `key at t=${key.t}`;
|
|
8087
|
-
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');
|
|
8088
8110
|
}
|
|
8089
8111
|
entry[field] = f32(v[c]);
|
|
8090
8112
|
});
|
|
8091
8113
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
8092
|
-
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');
|
|
8093
8115
|
}
|
|
8094
8116
|
if (key.curve !== undefined) {
|
|
8095
|
-
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');
|
|
8096
8118
|
entry.curve = rawCurve(key.curve, shape.fields.length, where, String(key.t));
|
|
8097
8119
|
} else if (key.ease && next) {
|
|
8098
8120
|
if (key.ease === 'stepped') {
|
|
8099
8121
|
entry.curve = 'stepped';
|
|
8100
8122
|
} else {
|
|
8101
8123
|
const handles = motion.easings?.[key.ease];
|
|
8102
|
-
if (!handles) throw new CompileError(`${where}: unknown easing "${key.ease}"
|
|
8103
|
-
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');
|
|
8104
8126
|
const t2 = keyTime(next.t + shift);
|
|
8105
8127
|
entry.curve = easingCurve(handles, time, t2, key.v as number[], next.v as number[]);
|
|
8106
8128
|
}
|
|
8107
8129
|
} else if (key.ease && !next) {
|
|
8108
|
-
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');
|
|
8109
8131
|
}
|
|
8110
8132
|
out.push(entry);
|
|
8111
8133
|
}
|
|
@@ -8154,7 +8176,7 @@ function compileDrawOrder(
|
|
|
8154
8176
|
slots: readonly ModelSlot[],
|
|
8155
8177
|
): ModelKey[] {
|
|
8156
8178
|
const where = `animation "${animName}" drawOrder`;
|
|
8157
|
-
if (!keys.length) throw new CompileError(`${where}: no keys
|
|
8179
|
+
if (!keys.length) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8158
8180
|
const indexOf = new Map(slots.map((s, i) => [s.name, i]));
|
|
8159
8181
|
|
|
8160
8182
|
const out: ModelKey[] = [];
|
|
@@ -8162,7 +8184,7 @@ function compileDrawOrder(
|
|
|
8162
8184
|
const key = keys[i];
|
|
8163
8185
|
const time = keyTime(key.t);
|
|
8164
8186
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8165
|
-
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');
|
|
8166
8188
|
}
|
|
8167
8189
|
checkKeyTime(where, time, key.t, duration);
|
|
8168
8190
|
// No offsets = "back to the setup draw order", which is the parser's own
|
|
@@ -8177,19 +8199,19 @@ function compileDrawOrder(
|
|
|
8177
8199
|
for (const off of key.offsets) {
|
|
8178
8200
|
const index = indexOf.get(off.slot);
|
|
8179
8201
|
if (index === undefined) {
|
|
8180
|
-
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');
|
|
8181
8203
|
}
|
|
8182
8204
|
if (seen.has(off.slot)) {
|
|
8183
|
-
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');
|
|
8184
8206
|
}
|
|
8185
8207
|
if (!Number.isInteger(off.offset)) {
|
|
8186
|
-
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');
|
|
8187
8209
|
}
|
|
8188
8210
|
const landing = index + off.offset;
|
|
8189
8211
|
if (landing < 0 || landing >= slots.length) {
|
|
8190
8212
|
throw new CompileError(
|
|
8191
8213
|
`${where} at t=${key.t}: slot "${off.slot}" is at index ${index} and offset ${off.offset} puts it at ` +
|
|
8192
|
-
`${landing}, outside the ${slots.length} emitted slots`,
|
|
8214
|
+
`${landing}, outside the ${slots.length} emitted slots`, 'CE_COMPILE_DRAW_ORDER_OFFSET_OUTSIDE_SLOTS',
|
|
8193
8215
|
);
|
|
8194
8216
|
}
|
|
8195
8217
|
seen.add(off.slot);
|
|
@@ -8234,27 +8256,27 @@ function compileEvents(
|
|
|
8234
8256
|
events: Record<string, RigEvent>,
|
|
8235
8257
|
): ModelKey[] {
|
|
8236
8258
|
const where = `animation "${animName}" events`;
|
|
8237
|
-
if (!keys.length) throw new CompileError(`${where}: no keys
|
|
8259
|
+
if (!keys.length) throw new CompileError(`${where}: no keys`, 'CE_COMPILE_TIMELINE_KEYS_EMPTY');
|
|
8238
8260
|
|
|
8239
8261
|
const out: ModelKey[] = [];
|
|
8240
8262
|
for (let i = 0; i < keys.length; i++) {
|
|
8241
8263
|
const key = keys[i];
|
|
8242
8264
|
if (typeof key.name !== 'string' || key.name.length === 0) {
|
|
8243
|
-
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');
|
|
8244
8266
|
}
|
|
8245
8267
|
const declared = events[key.name];
|
|
8246
8268
|
if (declared === undefined) {
|
|
8247
8269
|
const known = Object.keys(events);
|
|
8248
8270
|
throw new CompileError(
|
|
8249
8271
|
`${where} at t=${key.t}: event "${key.name}" is not declared in the rig spec's "events" block; ` +
|
|
8250
|
-
(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',
|
|
8251
8273
|
);
|
|
8252
8274
|
}
|
|
8253
8275
|
const time = keyTime(key.t);
|
|
8254
8276
|
if (i > 0 && time < (out[i - 1].time as number)) {
|
|
8255
8277
|
throw new CompileError(
|
|
8256
8278
|
`${where}: key times must not go backwards (at t=${key.t}, after t=${String(out[i - 1].time)}) — ` +
|
|
8257
|
-
'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',
|
|
8258
8280
|
);
|
|
8259
8281
|
}
|
|
8260
8282
|
checkKeyTime(where, time, key.t, duration);
|
|
@@ -8262,19 +8284,19 @@ function compileEvents(
|
|
|
8262
8284
|
const entry: ModelKey = { time, name: key.name };
|
|
8263
8285
|
if (key.int !== undefined) {
|
|
8264
8286
|
if (!Number.isInteger(key.int)) {
|
|
8265
|
-
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');
|
|
8266
8288
|
}
|
|
8267
8289
|
entry.int = key.int;
|
|
8268
8290
|
}
|
|
8269
8291
|
if (key.float !== undefined) {
|
|
8270
8292
|
if (!Number.isFinite(key.float)) {
|
|
8271
|
-
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');
|
|
8272
8294
|
}
|
|
8273
8295
|
entry.float = f32(key.float);
|
|
8274
8296
|
}
|
|
8275
8297
|
if (key.string !== undefined) {
|
|
8276
8298
|
if (typeof key.string !== 'string') {
|
|
8277
|
-
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');
|
|
8278
8300
|
}
|
|
8279
8301
|
entry.string = key.string;
|
|
8280
8302
|
}
|
|
@@ -8284,10 +8306,10 @@ function compileEvents(
|
|
|
8284
8306
|
if (declared.audio === undefined) {
|
|
8285
8307
|
throw new CompileError(
|
|
8286
8308
|
`${where} at t=${key.t}: ${field} is set but event "${key.name}" declares no "audio"; ` +
|
|
8287
|
-
`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',
|
|
8288
8310
|
);
|
|
8289
8311
|
}
|
|
8290
|
-
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');
|
|
8291
8313
|
entry[field] = f32(v);
|
|
8292
8314
|
}
|
|
8293
8315
|
out.push(entry);
|
|
@@ -8356,7 +8378,7 @@ function compileConstraintTrack(
|
|
|
8356
8378
|
// An array by the time this runs (`parseMotionSpec`); an EMPTY one is a
|
|
8357
8379
|
// format rule rather than a shape — the parser reads key 0, finds nothing and
|
|
8358
8380
|
// skips the timeline, which is assertion A34's silent case.
|
|
8359
|
-
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');
|
|
8360
8382
|
|
|
8361
8383
|
const read = (key: MotionIkTrack['keys'][number] | MotionTransformTrack['keys'][number], field: string): unknown =>
|
|
8362
8384
|
(key as unknown as Record<string, unknown>)[field];
|
|
@@ -8375,7 +8397,7 @@ function compileConstraintTrack(
|
|
|
8375
8397
|
throw new CompileError(
|
|
8376
8398
|
`${where}: key ${i} (t=${key.t}) names "${field}" and key 0 does not. Every key of ${article} ${group} timeline ` +
|
|
8377
8399
|
'is read with its own default, so a field stated on some keys and not others snaps to the default on ' +
|
|
8378
|
-
'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',
|
|
8379
8401
|
);
|
|
8380
8402
|
}
|
|
8381
8403
|
for (const field of first) {
|
|
@@ -8383,7 +8405,7 @@ function compileConstraintTrack(
|
|
|
8383
8405
|
throw new CompileError(
|
|
8384
8406
|
`${where}: key 0 names "${field}" and key ${i} (t=${key.t}) does not. Every key of ${article} ${group} timeline ` +
|
|
8385
8407
|
`is read with its own default, so "${field}" would snap to ` +
|
|
8386
|
-
`${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',
|
|
8387
8409
|
);
|
|
8388
8410
|
}
|
|
8389
8411
|
});
|
|
@@ -8403,7 +8425,7 @@ function compileConstraintTrack(
|
|
|
8403
8425
|
const next = keys[i + 1];
|
|
8404
8426
|
const time = keyTime(key.t);
|
|
8405
8427
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8406
|
-
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');
|
|
8407
8429
|
}
|
|
8408
8430
|
checkKeyTime(where, time, key.t, duration);
|
|
8409
8431
|
|
|
@@ -8412,14 +8434,14 @@ function compileConstraintTrack(
|
|
|
8412
8434
|
const v = read(key, channel.field);
|
|
8413
8435
|
if (v === undefined) continue;
|
|
8414
8436
|
if (typeof v !== 'number' || !Number.isFinite(v)) {
|
|
8415
|
-
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');
|
|
8416
8438
|
}
|
|
8417
8439
|
const bounds = shape.range[channel.field];
|
|
8418
8440
|
if (bounds && (v < bounds[0] || v > bounds[1])) {
|
|
8419
8441
|
throw new CompileError(
|
|
8420
8442
|
`${where} (t=${key.t}): ${channel.field} is ${v}, outside ${bounds[0]}..${
|
|
8421
8443
|
bounds[1] === Infinity ? '∞' : bounds[1]
|
|
8422
|
-
} — 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',
|
|
8423
8445
|
);
|
|
8424
8446
|
}
|
|
8425
8447
|
entry[channel.field] = f32(v);
|
|
@@ -8436,23 +8458,23 @@ function compileConstraintTrack(
|
|
|
8436
8458
|
continue;
|
|
8437
8459
|
}
|
|
8438
8460
|
if (typeof v !== 'boolean') {
|
|
8439
|
-
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');
|
|
8440
8462
|
}
|
|
8441
8463
|
entry[flag.field] = v;
|
|
8442
8464
|
}
|
|
8443
8465
|
|
|
8444
8466
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
8445
|
-
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');
|
|
8446
8468
|
}
|
|
8447
8469
|
if (key.curve !== undefined) {
|
|
8448
|
-
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');
|
|
8449
8471
|
entry.curve = rawCurve(key.curve, shape.channels.length, where, String(key.t));
|
|
8450
8472
|
} else if (key.ease !== undefined && next) {
|
|
8451
8473
|
if (key.ease === 'stepped') {
|
|
8452
8474
|
entry.curve = 'stepped';
|
|
8453
8475
|
} else {
|
|
8454
8476
|
const handles = motion.easings?.[key.ease];
|
|
8455
|
-
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');
|
|
8456
8478
|
const t2 = keyTime(next.t);
|
|
8457
8479
|
entry.curve = easingCurve(
|
|
8458
8480
|
handles,
|
|
@@ -8463,7 +8485,7 @@ function compileConstraintTrack(
|
|
|
8463
8485
|
);
|
|
8464
8486
|
}
|
|
8465
8487
|
} else if (key.ease !== undefined && !next) {
|
|
8466
|
-
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');
|
|
8467
8489
|
}
|
|
8468
8490
|
out.push(entry);
|
|
8469
8491
|
}
|
|
@@ -8577,7 +8599,7 @@ export function deformGeometryOf(
|
|
|
8577
8599
|
if (!isModelVertexAttachment(att)) {
|
|
8578
8600
|
throw new CompileError(
|
|
8579
8601
|
`${where}: a deform timeline keys the vertices of an attachment, and this one is a "${type}" — ` +
|
|
8580
|
-
'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',
|
|
8581
8603
|
);
|
|
8582
8604
|
}
|
|
8583
8605
|
if (att.kind === 'mesh') {
|
|
@@ -8644,7 +8666,7 @@ export function deformGeometryOf(
|
|
|
8644
8666
|
const vertex = vertices.bindings[v];
|
|
8645
8667
|
const n = vertex.length;
|
|
8646
8668
|
if (n < 1) {
|
|
8647
|
-
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');
|
|
8648
8670
|
}
|
|
8649
8671
|
if (n === 1) {
|
|
8650
8672
|
bones.add(vertex[0].bone);
|
|
@@ -8705,7 +8727,7 @@ export function deformGeometryOf(
|
|
|
8705
8727
|
let sum = 0;
|
|
8706
8728
|
for (const binding of vertices.bindings[v]) {
|
|
8707
8729
|
const m = transforms.get(binding.bone);
|
|
8708
|
-
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');
|
|
8709
8731
|
const weight = binding.weight;
|
|
8710
8732
|
const [x, y] = toWorld(m, binding.x, binding.y);
|
|
8711
8733
|
wx += x * weight;
|
|
@@ -8812,7 +8834,7 @@ function compileSequenceTrack(
|
|
|
8812
8834
|
`${where}: attachment "${track.attachment}" carries no "sequence" block, so there is no series to step. The ` +
|
|
8813
8835
|
'parser gives every region and mesh a series of ONE region (`readSequence(null)` is `new Sequence(1, ' +
|
|
8814
8836
|
'false)`), so this timeline would load and show that region under every mode at every time. Give the ' +
|
|
8815
|
-
'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',
|
|
8816
8838
|
);
|
|
8817
8839
|
}
|
|
8818
8840
|
if (attachment.kind === 'linkedmesh' && attachment.timelines) {
|
|
@@ -8822,23 +8844,23 @@ function compileSequenceTrack(
|
|
|
8822
8844
|
`is not false), so its \`timelineAttachment\` is "${source}" (\`SkeletonJson.js:437-448\`) and ` +
|
|
8823
8845
|
'`SequenceTimeline` applies only where the slot\'s attachment\'s `timelineAttachment` is the one it was ' +
|
|
8824
8846
|
`built for — a key aimed at the link is applied to nothing. Key "${source}" instead (the link steps its own ` +
|
|
8825
|
-
'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',
|
|
8826
8848
|
);
|
|
8827
8849
|
}
|
|
8828
|
-
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');
|
|
8829
8851
|
const out: ModelKey[] = [];
|
|
8830
8852
|
for (let i = 0; i < track.keys.length; i++) {
|
|
8831
8853
|
const key = track.keys[i];
|
|
8832
8854
|
const time = keyTime(key.t);
|
|
8833
8855
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8834
|
-
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');
|
|
8835
8857
|
}
|
|
8836
8858
|
checkKeyTime(where, time, key.t, duration);
|
|
8837
8859
|
if (key.index !== undefined && key.index >= series.count) {
|
|
8838
8860
|
throw new CompileError(
|
|
8839
8861
|
`${where} (t=${key.t}): index ${key.index} is past the end of a ${series.count}-frame series (frames 0 to ` +
|
|
8840
8862
|
`${series.count - 1}). \`Sequence.resolveIndex\` clamps it to the last frame, so the key would start on a ` +
|
|
8841
|
-
'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',
|
|
8842
8864
|
);
|
|
8843
8865
|
}
|
|
8844
8866
|
const entry: ModelKey = { time };
|
|
@@ -8864,14 +8886,14 @@ function compileDeformTrack(
|
|
|
8864
8886
|
// An array by the time this runs (`parseMotionSpec`); an EMPTY one is a
|
|
8865
8887
|
// format rule rather than a shape — the parser reads key 0, finds nothing and
|
|
8866
8888
|
// skips the timeline, which is assertion A34's silent case.
|
|
8867
|
-
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');
|
|
8868
8890
|
|
|
8869
8891
|
const out: ModelKey[] = [];
|
|
8870
8892
|
for (let i = 0; i < keys.length; i++) {
|
|
8871
8893
|
const key = keys[i];
|
|
8872
8894
|
const time = keyTime(key.t);
|
|
8873
8895
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
8874
|
-
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');
|
|
8875
8897
|
}
|
|
8876
8898
|
checkKeyTime(where, time, key.t, duration);
|
|
8877
8899
|
// -- a key that states a MODEL rather than a run (issue #294) ------------
|
|
@@ -8888,7 +8910,7 @@ function compileDeformTrack(
|
|
|
8888
8910
|
throw new CompileError(
|
|
8889
8911
|
`${where} (t=${key.t}): the key carries both a "transform" and a "vertices" run, and they are two answers to ` +
|
|
8890
8912
|
'one question. Authored offsets and a stated model do not combine, for the same reason a mesh cannot carry ' +
|
|
8891
|
-
'both a "generator" and authored geometry — drop one.',
|
|
8913
|
+
'both a "generator" and authored geometry — drop one.', 'CE_COMPILE_DEFORM_TRANSFORM_AND_VERTICES',
|
|
8892
8914
|
);
|
|
8893
8915
|
}
|
|
8894
8916
|
if (key.offset !== undefined || key.fromVertex !== undefined) {
|
|
@@ -8896,14 +8918,14 @@ function compileDeformTrack(
|
|
|
8896
8918
|
`${where} (t=${key.t}): the key states a "transform" and a start index. A transform is a model of the whole ` +
|
|
8897
8919
|
`attachment and is evaluated over all ${geometry.vertexCount} of its vertices, so it always starts at deform ` +
|
|
8898
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 ' +
|
|
8899
|
-
'if that is what you mean.',
|
|
8921
|
+
'if that is what you mean.', 'CE_COMPILE_DEFORM_TRANSFORM_START_INDEX',
|
|
8900
8922
|
);
|
|
8901
8923
|
}
|
|
8902
8924
|
if (geometry.setup === null) {
|
|
8903
8925
|
throw new CompileError(
|
|
8904
8926
|
`${where} (t=${key.t}): a "transform" states where every vertex of this attachment goes, and this one has a ` +
|
|
8905
8927
|
`vertex the arithmetic cannot place — ${geometry.setupWhy}. Fix the weights, or write the bind-space pairs ` +
|
|
8906
|
-
'yourself and start the run with "offset".',
|
|
8928
|
+
'yourself and start the run with "offset".', 'CE_COMPILE_DEFORM_TRANSFORM_SETUP_UNRESOLVED',
|
|
8907
8929
|
);
|
|
8908
8930
|
}
|
|
8909
8931
|
const report = evaluateDeformTransform(
|
|
@@ -8920,7 +8942,7 @@ function compileDeformTrack(
|
|
|
8920
8942
|
// assumed: a silent mismatch here is the overrun A35 exists for.
|
|
8921
8943
|
throw new CompileError(
|
|
8922
8944
|
`${where} (t=${key.t}): the transform produced ${report.offsets.length} numbers for ` +
|
|
8923
|
-
`${geometry.vertexCount} vertices`,
|
|
8945
|
+
`${geometry.vertexCount} vertices`, 'CE_COMPILE_DEFORM_TRANSFORM_OFFSET_COUNT',
|
|
8924
8946
|
);
|
|
8925
8947
|
}
|
|
8926
8948
|
// -- one displacement per vertex -> one pair per influence (issue #389) -
|
|
@@ -8936,7 +8958,7 @@ function compileDeformTrack(
|
|
|
8936
8958
|
if (run.length !== geometry.deformLength) {
|
|
8937
8959
|
throw new CompileError(
|
|
8938
8960
|
`${where} (t=${key.t}): the transform expanded to ${run.length} numbers and this attachment's deform ` +
|
|
8939
|
-
`array is ${geometry.deformLength} long`,
|
|
8961
|
+
`array is ${geometry.deformLength} long`, 'CE_COMPILE_DEFORM_TRANSFORM_RUN_LENGTH',
|
|
8940
8962
|
);
|
|
8941
8963
|
}
|
|
8942
8964
|
generated.push({
|
|
@@ -8970,7 +8992,7 @@ function compileDeformTrack(
|
|
|
8970
8992
|
if (key.offset !== undefined && key.fromVertex !== undefined) {
|
|
8971
8993
|
throw new CompileError(
|
|
8972
8994
|
`${where} (t=${key.t}): a key gives its start as "offset" (an index into the deform array) or as ` +
|
|
8973
|
-
'"fromVertex" (a vertex index rigc translates), never both',
|
|
8995
|
+
'"fromVertex" (a vertex index rigc translates), never both', 'CE_COMPILE_DEFORM_START_SOURCES_CONFLICT',
|
|
8974
8996
|
);
|
|
8975
8997
|
}
|
|
8976
8998
|
const run = key.vertices ?? null;
|
|
@@ -8981,7 +9003,7 @@ function compileDeformTrack(
|
|
|
8981
9003
|
if (key.offset !== undefined || key.fromVertex !== undefined) {
|
|
8982
9004
|
throw new CompileError(
|
|
8983
9005
|
`${where} (t=${key.t}): the key has no "vertices", which is the format's way of saying "back to the ` +
|
|
8984
|
-
'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',
|
|
8985
9007
|
);
|
|
8986
9008
|
}
|
|
8987
9009
|
out.push({ time });
|
|
@@ -8990,7 +9012,7 @@ function compileDeformTrack(
|
|
|
8990
9012
|
if (!Array.isArray(run) || run.length === 0) {
|
|
8991
9013
|
throw new CompileError(
|
|
8992
9014
|
`${where} (t=${key.t}): "vertices" is ${JSON.stringify(key.vertices)}; give an array of x, y offsets, ` +
|
|
8993
|
-
'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',
|
|
8994
9016
|
);
|
|
8995
9017
|
}
|
|
8996
9018
|
// ⛔ No parity clause here, and its absence is the rule (issue #576).
|
|
@@ -9020,7 +9042,7 @@ function compileDeformTrack(
|
|
|
9020
9042
|
// format holds, and the half that refused was the authoring half.
|
|
9021
9043
|
for (const n of run) {
|
|
9022
9044
|
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
9023
|
-
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');
|
|
9024
9046
|
}
|
|
9025
9047
|
}
|
|
9026
9048
|
const start = deformStart(key, run.length, geometry, where);
|
|
@@ -9029,7 +9051,7 @@ function compileDeformTrack(
|
|
|
9029
9051
|
`${where} (t=${key.t}): the run starts at deform index ${start} and is ${run.length} long, which ends at ` +
|
|
9030
9052
|
`${start + run.length}; this attachment's deform array is ${geometry.deformLength} long ` +
|
|
9031
9053
|
`(${geometry.weighted ? `${geometry.deformLength / 2} bone influences` : `${geometry.vertexCount} vertices`}). ` +
|
|
9032
|
-
'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',
|
|
9033
9055
|
);
|
|
9034
9056
|
}
|
|
9035
9057
|
const entry: ModelKey = { time };
|
|
@@ -9100,7 +9122,7 @@ function deformStart(
|
|
|
9100
9122
|
if (key.offset !== undefined) {
|
|
9101
9123
|
if (!Number.isInteger(key.offset) || key.offset < 0) {
|
|
9102
9124
|
throw new CompileError(
|
|
9103
|
-
`${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',
|
|
9104
9126
|
);
|
|
9105
9127
|
}
|
|
9106
9128
|
// ⛔ An odd `offset` is not refused either, and it went the same way as the
|
|
@@ -9118,7 +9140,7 @@ function deformStart(
|
|
|
9118
9140
|
if (key.fromVertex === undefined) return 0;
|
|
9119
9141
|
const from = key.fromVertex;
|
|
9120
9142
|
if (!Number.isInteger(from) || from < 0) {
|
|
9121
|
-
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');
|
|
9122
9144
|
}
|
|
9123
9145
|
// ⌈⌉ rather than ÷, because an odd run REACHES a last vertex without covering
|
|
9124
9146
|
// it: its final number is that vertex's x and the y beside it stays at setup
|
|
@@ -9129,7 +9151,7 @@ function deformStart(
|
|
|
9129
9151
|
if (from + covered > geometry.vertexCount) {
|
|
9130
9152
|
throw new CompileError(
|
|
9131
9153
|
`${where} (t=${key.t}): fromVertex ${from} plus ${covered} vertex offset(s) runs to vertex ${from + covered}, ` +
|
|
9132
|
-
`and the attachment has ${geometry.vertexCount}`,
|
|
9154
|
+
`and the attachment has ${geometry.vertexCount}`, 'CE_COMPILE_DEFORM_FROM_VERTEX_OUTSIDE_ATTACHMENT',
|
|
9133
9155
|
);
|
|
9134
9156
|
}
|
|
9135
9157
|
if (!geometry.weighted) return from * 2;
|
|
@@ -9142,7 +9164,7 @@ function deformStart(
|
|
|
9142
9164
|
'is not a thing that array can hold: the world offset of a multi-bone vertex is the weighted sum of a ' +
|
|
9143
9165
|
'per-bone offset in each bone\'s own bind space, so rigc will not guess one for you. Either key the ' +
|
|
9144
9166
|
'control bone instead, or write the bind-space pairs yourself and start the run with "offset" ' +
|
|
9145
|
-
`(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',
|
|
9146
9168
|
);
|
|
9147
9169
|
}
|
|
9148
9170
|
let start = 0;
|
|
@@ -9170,21 +9192,21 @@ function deformKeyCurve(
|
|
|
9170
9192
|
next: ModelKey | undefined,
|
|
9171
9193
|
): ModelKey {
|
|
9172
9194
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
9173
|
-
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');
|
|
9174
9196
|
}
|
|
9175
9197
|
if (key.curve !== undefined) {
|
|
9176
|
-
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');
|
|
9177
9199
|
entry.curve = rawCurve(key.curve, 1, where, String(key.t));
|
|
9178
9200
|
return entry;
|
|
9179
9201
|
}
|
|
9180
9202
|
if (key.ease === undefined) return entry;
|
|
9181
|
-
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');
|
|
9182
9204
|
if (key.ease === 'stepped') {
|
|
9183
9205
|
entry.curve = 'stepped';
|
|
9184
9206
|
return entry;
|
|
9185
9207
|
}
|
|
9186
9208
|
const handles = motion.easings?.[key.ease];
|
|
9187
|
-
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');
|
|
9188
9210
|
// The one channel runs 0 -> 1; the VALUE being held is the geometry, so the
|
|
9189
9211
|
// hold test compares the two keys' runs rather than those two constants.
|
|
9190
9212
|
entry.curve = easingCurve(handles, entry.time as number, keyTime(keys[index + 1].t), [0], [1], sameDeform(entry, next));
|
|
@@ -9220,7 +9242,7 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9220
9242
|
if (named.length > 1) {
|
|
9221
9243
|
throw new CompileError(
|
|
9222
9244
|
`animation "${animName}": a track names more than one target (${named.join(', ')}); the list is ` +
|
|
9223
|
-
`${targetFields.join('/')} and a track names exactly one`,
|
|
9245
|
+
`${targetFields.join('/')} and a track names exactly one`, 'CE_COMPILE_TRACK_TARGETS_MULTIPLE',
|
|
9224
9246
|
);
|
|
9225
9247
|
}
|
|
9226
9248
|
const family = constraintFamilyOf(track);
|
|
@@ -9230,12 +9252,12 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9230
9252
|
const who = direct === undefined ? `group "${String(track.group)}"` : `${label} "${direct}"`;
|
|
9231
9253
|
if (!(track.property in tracks)) {
|
|
9232
9254
|
throw new CompileError(
|
|
9233
|
-
`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',
|
|
9234
9256
|
);
|
|
9235
9257
|
}
|
|
9236
9258
|
if (direct !== undefined) return [direct];
|
|
9237
9259
|
const members = motion.groups?.[String(track.group)];
|
|
9238
|
-
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');
|
|
9239
9261
|
return members;
|
|
9240
9262
|
}
|
|
9241
9263
|
// A constraint timeline with no constraint named. Worth its own refusal: the
|
|
@@ -9246,12 +9268,12 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9246
9268
|
if (owning.length) {
|
|
9247
9269
|
throw new CompileError(
|
|
9248
9270
|
`animation "${animName}": "${track.property}" is a ${owning.map((f) => CONSTRAINT_TRACK_FAMILIES[f].label).join(' / ')} ` +
|
|
9249
|
-
`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',
|
|
9250
9272
|
);
|
|
9251
9273
|
}
|
|
9252
9274
|
const isBoneTrack = track.property in BONE_TRACKS;
|
|
9253
9275
|
if (isBoneTrack && !track.bone && !track.group) {
|
|
9254
|
-
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');
|
|
9255
9277
|
}
|
|
9256
9278
|
if (!isBoneTrack && track.bone) {
|
|
9257
9279
|
// The bone family's sibling of `compileTrack`'s slot refusal, raised here for
|
|
@@ -9266,7 +9288,7 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9266
9288
|
throw new CompileError(
|
|
9267
9289
|
`animation "${animName}" bone "${track.bone}" has no timeline "${track.property}" ` +
|
|
9268
9290
|
`(it has: ${Object.keys(BONE_TRACKS).join(', ')})` +
|
|
9269
|
-
(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',
|
|
9270
9292
|
);
|
|
9271
9293
|
}
|
|
9272
9294
|
if (track.bone) return [track.bone];
|
|
@@ -9278,7 +9300,7 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9278
9300
|
// following the part and two objects moving together, which reads as a
|
|
9279
9301
|
// composite.
|
|
9280
9302
|
const members = motion.groups?.[track.group];
|
|
9281
|
-
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');
|
|
9282
9304
|
// 🚨 The group is the one target shape whose FAMILY is decided by the
|
|
9283
9305
|
// property, and three tables decide it: `constraintFamilyOf` asks
|
|
9284
9306
|
// `property in PHYSICS_TRACKS`, this function asks `property in BONE_TRACKS`,
|
|
@@ -9304,12 +9326,12 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9304
9326
|
`animation "${animName}" group "${track.group}" has no timeline "${track.property}" ` +
|
|
9305
9327
|
`(a bone group has: ${Object.keys(BONE_TRACKS).join(', ')}; ` +
|
|
9306
9328
|
`a slot group has: ${Object.keys(SLOT_TRACKS).join(', ')}; ` +
|
|
9307
|
-
`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',
|
|
9308
9330
|
);
|
|
9309
9331
|
}
|
|
9310
9332
|
return members;
|
|
9311
9333
|
}
|
|
9312
|
-
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');
|
|
9313
9335
|
}
|
|
9314
9336
|
|
|
9315
9337
|
/**
|
|
@@ -9330,23 +9352,23 @@ function resolveTargets(track: MotionTrack, motion: MotionSpec, animName: string
|
|
|
9330
9352
|
function checkMotionGroups(motion: MotionSpec): void {
|
|
9331
9353
|
for (const [name, members] of Object.entries(motion.groups ?? {})) {
|
|
9332
9354
|
if (!Array.isArray(members)) {
|
|
9333
|
-
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');
|
|
9334
9356
|
}
|
|
9335
9357
|
if (members.length === 0) {
|
|
9336
9358
|
throw new CompileError(
|
|
9337
|
-
`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',
|
|
9338
9360
|
);
|
|
9339
9361
|
}
|
|
9340
9362
|
const seen = new Set<string>();
|
|
9341
9363
|
members.forEach((member, i) => {
|
|
9342
9364
|
if (typeof member !== 'string' || member.length === 0) {
|
|
9343
|
-
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');
|
|
9344
9366
|
}
|
|
9345
9367
|
if (seen.has(member)) {
|
|
9346
9368
|
throw new CompileError(
|
|
9347
9369
|
`group "${name}" names member "${member}" twice (at index ${members.indexOf(member)} and ${i}). ` +
|
|
9348
9370
|
'Member order is what `stagger` counts and what a per-member value map is read against, so a repeat is ' +
|
|
9349
|
-
'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',
|
|
9350
9372
|
);
|
|
9351
9373
|
}
|
|
9352
9374
|
seen.add(member);
|
|
@@ -9392,13 +9414,13 @@ function resolveMemberTrack(
|
|
|
9392
9414
|
if ((carriesMap || carriesModel) && track.group === undefined && track.bone === undefined) {
|
|
9393
9415
|
throw new CompileError(
|
|
9394
9416
|
`${at}: per-member values need a target whose members are named — put them on a track that names a "group", or ` +
|
|
9395
|
-
'a "bone" for the one-member case',
|
|
9417
|
+
'a "bone" for the one-member case', 'CE_COMPILE_MEMBER_VALUES_TARGET_UNNAMED',
|
|
9396
9418
|
);
|
|
9397
9419
|
}
|
|
9398
9420
|
if (carriesMap && targetKind === 'bone') {
|
|
9399
9421
|
throw new CompileError(
|
|
9400
9422
|
`${at}: a per-member "v" map names members, and a bone track has one target rather than members. Write the ` +
|
|
9401
|
-
'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',
|
|
9402
9424
|
);
|
|
9403
9425
|
}
|
|
9404
9426
|
|
|
@@ -9415,7 +9437,7 @@ function resolveMemberTrack(
|
|
|
9415
9437
|
throw new CompileError(
|
|
9416
9438
|
`${where}: this key carries both a "derive" model and a per-member "v" map — two answers to one question. ` +
|
|
9417
9439
|
'The model states the arithmetic and the depths that produced the numbers; the map states the numbers. ' +
|
|
9418
|
-
'Pick one.',
|
|
9440
|
+
'Pick one.', 'CE_COMPILE_MEMBER_DERIVE_AND_VALUE_MAP',
|
|
9419
9441
|
);
|
|
9420
9442
|
}
|
|
9421
9443
|
if (key.derive === undefined) {
|
|
@@ -9430,7 +9452,7 @@ function resolveMemberTrack(
|
|
|
9430
9452
|
if (!known.has(named)) {
|
|
9431
9453
|
throw new CompileError(
|
|
9432
9454
|
`${where}: the value map names "${named}", which group "${target}" does not declare ` +
|
|
9433
|
-
`(its members are: ${targets.join(', ')})`,
|
|
9455
|
+
`(its members are: ${targets.join(', ')})`, 'CE_COMPILE_MEMBER_VALUE_NAME_UNKNOWN',
|
|
9434
9456
|
);
|
|
9435
9457
|
}
|
|
9436
9458
|
}
|
|
@@ -9442,7 +9464,7 @@ function resolveMemberTrack(
|
|
|
9442
9464
|
`(it states: ${Object.keys(table).join(', ') || 'nothing'}). ` +
|
|
9443
9465
|
'A member is refused rather than defaulted: an absent value is exactly the thing a map of six is ' +
|
|
9444
9466
|
'written to make visible, and defaulting it to the identity would key that one bone with a different ' +
|
|
9445
|
-
'motion in silence.',
|
|
9467
|
+
'motion in silence.', 'CE_COMPILE_MEMBER_VALUE_MISSING',
|
|
9446
9468
|
);
|
|
9447
9469
|
}
|
|
9448
9470
|
const value = table[name];
|
|
@@ -9478,7 +9500,7 @@ function resolveMemberTrack(
|
|
|
9478
9500
|
throw new CompileError(
|
|
9479
9501
|
`${where}: derive reads member "${name}"'s setup position, and this rig declares no such bone. ` +
|
|
9480
9502
|
'A model over a group of slots or constraints has no coordinate to be evaluated at — state the values ' +
|
|
9481
|
-
'as a "v" map.',
|
|
9503
|
+
'as a "v" map.', 'CE_COMPILE_MEMBER_DERIVE_TARGET_NOT_BONE',
|
|
9482
9504
|
);
|
|
9483
9505
|
}
|
|
9484
9506
|
parents.add(bone.parent ?? '(root)');
|
|
@@ -9489,7 +9511,7 @@ function resolveMemberTrack(
|
|
|
9489
9511
|
`${where}: derive measures every member's "${coordinate}" from its parent's origin and "about" says where the ` +
|
|
9490
9512
|
`axis crosses it, so one space is what makes the members comparable. These members sit under ` +
|
|
9491
9513
|
`${parents.size} different parents (${[...parents].join(', ')}), and their coordinates are therefore ` +
|
|
9492
|
-
'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',
|
|
9493
9515
|
);
|
|
9494
9516
|
}
|
|
9495
9517
|
const report = evaluateTrackDerive(key.derive, track.property, members, onModelGrid, where);
|
|
@@ -9549,7 +9571,7 @@ function compileTrack(
|
|
|
9549
9571
|
if (shape === undefined) {
|
|
9550
9572
|
throw new CompileError(
|
|
9551
9573
|
`animation "${animName}" slot "${target}" has no timeline "${track.property}" ` +
|
|
9552
|
-
`(it has: ${Object.keys(SLOT_TRACKS).join(', ')})`,
|
|
9574
|
+
`(it has: ${Object.keys(SLOT_TRACKS).join(', ')})`, 'CE_COMPILE_SLOT_TRACK_PROPERTY_UNKNOWN',
|
|
9553
9575
|
);
|
|
9554
9576
|
}
|
|
9555
9577
|
// 🚨 Raised here, with the target and before the keys, for the same reason as
|
|
@@ -9574,10 +9596,10 @@ function compileTrack(
|
|
|
9574
9596
|
`${where}: slot "${target}" declares no setup "dark", and an "${shape}" timeline poses a slot's dark colour — ` +
|
|
9575
9597
|
'the runtime allocates one only for a slot whose setup pose has it, so applying this animation throws ' +
|
|
9576
9598
|
`instead of tinting. Give slot "${target}" a \`dark\` in the rig spec (the colour it holds at rest), or ` +
|
|
9577
|
-
`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',
|
|
9578
9600
|
);
|
|
9579
9601
|
}
|
|
9580
|
-
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');
|
|
9581
9603
|
|
|
9582
9604
|
const out: ModelKey[] = [];
|
|
9583
9605
|
for (let i = 0; i < track.keys.length; i++) {
|
|
@@ -9585,13 +9607,13 @@ function compileTrack(
|
|
|
9585
9607
|
const next = track.keys[i + 1];
|
|
9586
9608
|
const time = keyTime(key.t + shift);
|
|
9587
9609
|
if (i > 0 && time <= (out[i - 1].time as number)) {
|
|
9588
|
-
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');
|
|
9589
9611
|
}
|
|
9590
9612
|
checkKeyTime(where, time, key.t, duration);
|
|
9591
9613
|
|
|
9592
9614
|
if (shape === 'attachment') {
|
|
9593
9615
|
if (key.v !== null && typeof key.v !== 'string') {
|
|
9594
|
-
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');
|
|
9595
9617
|
}
|
|
9596
9618
|
// Resolved against every skin, not just `default` — see `SlotAttachmentIndex`.
|
|
9597
9619
|
const known = attachments.bySlot.get(target);
|
|
@@ -9606,10 +9628,10 @@ function compileTrack(
|
|
|
9606
9628
|
`(searched: ${attachments.searched.join(', ')}) — ` +
|
|
9607
9629
|
(known === undefined || known.size === 0
|
|
9608
9630
|
? 'the slot has no attachments at all'
|
|
9609
|
-
: `the slot has: ${[...known].join(', ')}`),
|
|
9631
|
+
: `the slot has: ${[...known].join(', ')}`), 'CE_COMPILE_ATTACHMENT_KEY_UNKNOWN',
|
|
9610
9632
|
);
|
|
9611
9633
|
}
|
|
9612
|
-
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');
|
|
9613
9635
|
// Attachment timelines are inherently stepped — exactly what lip-sync wants.
|
|
9614
9636
|
out.push({ time, name: key.v });
|
|
9615
9637
|
continue;
|
|
@@ -9628,7 +9650,7 @@ function compileTrack(
|
|
|
9628
9650
|
// a second thing to keep in step.
|
|
9629
9651
|
const { spelling, channels } = COLOUR_KEYS[shape];
|
|
9630
9652
|
const colourOf = (v: number[]): Record<string, string | number> => colourKey(shape, v);
|
|
9631
|
-
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');
|
|
9632
9654
|
if (shape === 'alpha' && key.v.length === 1 && !(Number.isFinite(key.v[0]) && key.v[0] >= 0 && key.v[0] <= 1)) {
|
|
9633
9655
|
// The one colour shape written as a number rather than a byte, so the one
|
|
9634
9656
|
// that is not clamped onto 0..1 by `channelHex` on the way out. The
|
|
@@ -9639,15 +9661,15 @@ function compileTrack(
|
|
|
9639
9661
|
// clamping is choosing the value.
|
|
9640
9662
|
throw new CompileError(
|
|
9641
9663
|
`${where}: key at t=${key.t} is ${String(key.v[0])}; an alpha is a number from 0 to 1 — the runtime clamps ` +
|
|
9642
|
-
'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',
|
|
9643
9665
|
);
|
|
9644
9666
|
}
|
|
9645
9667
|
const entry: ModelKey = { time, ...colourOf(key.v) };
|
|
9646
9668
|
if (key.ease !== undefined && key.curve !== undefined) {
|
|
9647
|
-
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');
|
|
9648
9670
|
}
|
|
9649
9671
|
if (key.curve !== undefined) {
|
|
9650
|
-
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');
|
|
9651
9673
|
entry.curve = rawCurve(key.curve, channels, where, String(key.t));
|
|
9652
9674
|
out.push(entry);
|
|
9653
9675
|
continue;
|
|
@@ -9657,9 +9679,9 @@ function compileTrack(
|
|
|
9657
9679
|
entry.curve = 'stepped';
|
|
9658
9680
|
} else {
|
|
9659
9681
|
const handles = motion.easings?.[key.ease];
|
|
9660
|
-
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');
|
|
9661
9683
|
if (!Array.isArray(next.v)) {
|
|
9662
|
-
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');
|
|
9663
9685
|
}
|
|
9664
9686
|
const t2 = keyTime(next.t + shift);
|
|
9665
9687
|
// 4 numbers per channel, in the format's own channel order — 16 for
|
|
@@ -9674,7 +9696,7 @@ function compileTrack(
|
|
|
9674
9696
|
entry.curve = easingCurve(handles, time, t2, key.v, next.v, held);
|
|
9675
9697
|
}
|
|
9676
9698
|
} else if (key.ease && !next) {
|
|
9677
|
-
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');
|
|
9678
9700
|
}
|
|
9679
9701
|
out.push(entry);
|
|
9680
9702
|
}
|