spine-rigc 2.0.3 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +2 -2
- package/README.md +6 -6
- package/docs/AUTHORING.md +95 -14
- package/docs/FACE.md +1 -1
- package/docs/INGEST.md +6 -6
- package/package.json +1 -1
- package/src/assertions/bodies/a06.ts +19 -53
- package/src/assertions/bodies/a49.ts +81 -0
- package/src/assertions/emitted/index.ts +1 -1
- package/src/assertions/footprints.ts +367 -0
- package/src/assertions/kinds.ts +6 -3
- package/src/assertions/model/index.ts +3 -1
- package/src/assertions/reasons.ts +2 -0
- package/src/atlas.ts +882 -12
- package/src/cli/shared.ts +55 -22
- package/src/validate.ts +8 -0
package/src/cli/shared.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* anything it imports: the bodies that reach the runtime are
|
|
15
15
|
* `./spine_commands.ts`'s, which only `cli.ts` imports.
|
|
16
16
|
*/
|
|
17
|
-
import { type AtlasRegion, DEFAULT_PADDING, DEFAULT_PAGE_EDGES, DEFAULT_PAGE_SIZE, packAtlas, PAGE_EDGES, type PageEdges, pageFootprint } from '../atlas.ts';
|
|
17
|
+
import { type AtlasRegion, DEFAULT_PACK_SHAPE, DEFAULT_PADDING, DEFAULT_PAGE_EDGES, DEFAULT_PAGE_SIZE, PACK_SHAPES, packAtlas, type PackInput, packFootprints, type PackShape, PAGE_EDGES, type PageEdges, pageFootprint } from '../atlas.ts';
|
|
18
18
|
import { copyAtlasPages, plannedPageCopies } from '../emit.ts';
|
|
19
19
|
import { CLI_DEFAULT_PROFILE, VALIDATE_PROFILES } from '../assertions/report.ts';
|
|
20
20
|
import type { AssertionProfile } from '../assertions/kinds.ts';
|
|
@@ -985,6 +985,10 @@ const FLAG_MEANINGS: Record<string, string> = {
|
|
|
985
985
|
'both; free tries every width from the widest part up, takes the height the placement needs and keeps ' +
|
|
986
986
|
'the least area — a smaller page, at the cost of region attachments sampling within 1 LSB of the loose ' +
|
|
987
987
|
'build rather than exactly',
|
|
988
|
+
'pack-shape': `what two packed rectangles may share, --pack only (default ${DEFAULT_PACK_SHAPE}): rect keeps every ` +
|
|
989
|
+
'region\'s cell apart; polygon packs a region that only meshes draw by its emitted hull, so a neighbour may ' +
|
|
990
|
+
'sit inside its rectangle where the hull is not, with the padding kept between footprints (so a mesh\'s ' +
|
|
991
|
+
'rectangle is its own bytes only where it can be sampled) — a region attachment stays its rectangle',
|
|
988
992
|
'atlas-in':
|
|
989
993
|
'resolve every part against the regions of this pre-packed .atlas instead of against loose PNGs — region ' +
|
|
990
994
|
'geometry (bounds/offsets/rotate) is read from the file and the atlas is re-emitted into --out, re-anchored',
|
|
@@ -1115,6 +1119,7 @@ const FLAG_VALUES: Record<string, string> = {
|
|
|
1115
1119
|
'page-size': '<px>',
|
|
1116
1120
|
padding: '<px>',
|
|
1117
1121
|
'page-edges': 'pot|free',
|
|
1122
|
+
'pack-shape': 'rect|polygon',
|
|
1118
1123
|
'texture-from': '<path>',
|
|
1119
1124
|
poser: 'core|spine',
|
|
1120
1125
|
reference: '<dir|skeleton.json>',
|
|
@@ -1239,7 +1244,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1239
1244
|
core: {
|
|
1240
1245
|
usage: [
|
|
1241
1246
|
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|spine-html] [--copy-images] (the same files the round-tripped build writes; gated without spine-core — see build --help)',
|
|
1242
|
-
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] (parts onto shared pages, written into --out)`,
|
|
1247
|
+
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] (parts onto shared pages, written into --out)`,
|
|
1243
1248
|
'rigc build … --atlas-in <skeleton.atlas> (resolve the parts against a pack somebody already made)',
|
|
1244
1249
|
'rigc build --cut <name> --cuts <cuts.json>',
|
|
1245
1250
|
],
|
|
@@ -1263,7 +1268,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1263
1268
|
spineFormat: true,
|
|
1264
1269
|
usage: [
|
|
1265
1270
|
'rigc build --rig <path> --motion <path> --out <dir> [--manifest <path>] [--images <dir>] [--profile spine|spine-html] [--copy-images]',
|
|
1266
|
-
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] (parts onto shared pages, written into --out)`,
|
|
1271
|
+
`rigc build … --pack [--page-size ${DEFAULT_PAGE_SIZE}] [--padding ${DEFAULT_PADDING}] [--page-edges pot|free] [--pack-shape rect|polygon] (parts onto shared pages, written into --out)`,
|
|
1267
1272
|
'rigc build … --atlas-in <skeleton.atlas> (resolve the parts against a pack somebody already made)',
|
|
1268
1273
|
'rigc build --cut <name> --cuts <cuts.json>',
|
|
1269
1274
|
],
|
|
@@ -1278,6 +1283,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1278
1283
|
'page-size',
|
|
1279
1284
|
'padding',
|
|
1280
1285
|
'page-edges',
|
|
1286
|
+
'pack-shape',
|
|
1281
1287
|
'atlas-in',
|
|
1282
1288
|
'cut',
|
|
1283
1289
|
'cuts',
|
|
@@ -1298,7 +1304,7 @@ export const COMMANDS: CommandDoc[] = [
|
|
|
1298
1304
|
'this line said "the same arguments as build, minus --profile" and was false in both',
|
|
1299
1305
|
'directions (issue #697): --atlas-in was not listed here, so the one flag that lets this',
|
|
1300
1306
|
'command read what `ingest --art none` writes was reachable and undocumented, while',
|
|
1301
|
-
'--pack, --page-size, --padding, --page-edges and --copy-images are build\'s and do nothing here —',
|
|
1307
|
+
'--pack, --page-size, --padding, --page-edges, --pack-shape and --copy-images are build\'s and do nothing here —',
|
|
1302
1308
|
'they decide what is WRITTEN, and this command writes nothing. What it takes is listed',
|
|
1303
1309
|
'above, and that is now the whole of it.',
|
|
1304
1310
|
],
|
|
@@ -1813,6 +1819,42 @@ export function readPageEdges(flags: Record<string, string>): PageEdges {
|
|
|
1813
1819
|
return found;
|
|
1814
1820
|
}
|
|
1815
1821
|
|
|
1822
|
+
/**
|
|
1823
|
+
* Read `--pack-shape`, or its default — `rect` (issue #1099).
|
|
1824
|
+
*
|
|
1825
|
+
* Refused on any other value for `readPageEdges`'s reason: a typo that fell
|
|
1826
|
+
* back to `rect` would hand the caller who asked for the denser page the
|
|
1827
|
+
* looser one, green, and say nothing.
|
|
1828
|
+
*/
|
|
1829
|
+
export function readPackShape(flags: Record<string, string>): PackShape {
|
|
1830
|
+
const raw = flags['pack-shape'];
|
|
1831
|
+
if (raw === undefined) return DEFAULT_PACK_SHAPE;
|
|
1832
|
+
const found = PACK_SHAPES.find((e) => e === raw);
|
|
1833
|
+
if (!found) throw new UsageError(`--pack-shape ${JSON.stringify(raw)}; known values: ${PACK_SHAPES.join(', ')}`);
|
|
1834
|
+
return found;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/**
|
|
1838
|
+
* The parts of a compile as `packAtlas` takes them — under `polygon` each with
|
|
1839
|
+
* the footprint its attachments draw, read off the skeleton text the build
|
|
1840
|
+
* emitted (`packFootprints`); under `rect` without, which is the input every
|
|
1841
|
+
* pack had before #1099.
|
|
1842
|
+
*/
|
|
1843
|
+
function packInputs(result: CompileResult, shape: PackShape): PackInput[] {
|
|
1844
|
+
const sizes = new Map(result.images.map((img) => [img.region, { width: img.width, height: img.height }]));
|
|
1845
|
+
const footprints = shape === 'polygon' ? packFootprints(result.skeletonText, (region) => sizes.get(region)) : null;
|
|
1846
|
+
return result.images.map((img) => {
|
|
1847
|
+
const footprint = footprints?.get(img.region) ?? undefined;
|
|
1848
|
+
return {
|
|
1849
|
+
region: img.region,
|
|
1850
|
+
absPath: img.absPath,
|
|
1851
|
+
width: img.width,
|
|
1852
|
+
height: img.height,
|
|
1853
|
+
...(footprint === undefined ? {} : { footprint }),
|
|
1854
|
+
};
|
|
1855
|
+
});
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1816
1858
|
/**
|
|
1817
1859
|
* The rectangle a region occupies **on its page**, for a line that has already
|
|
1818
1860
|
* said where the region is — and the empty string where the page rectangle is
|
|
@@ -1890,7 +1932,7 @@ export function readBuildInvocation(flags: Record<string, string>): { label: str
|
|
|
1890
1932
|
// now a build like any other — and it is the only one that puts the renderer's
|
|
1891
1933
|
// own rulebook over shared-page sampling.
|
|
1892
1934
|
if (!packing) {
|
|
1893
|
-
for (const name of ['page-size', 'padding', 'page-edges'] as const) {
|
|
1935
|
+
for (const name of ['page-size', 'padding', 'page-edges', 'pack-shape'] as const) {
|
|
1894
1936
|
if (flags[name] !== undefined) throw new UsageError(`--${name} only means something with --pack`);
|
|
1895
1937
|
}
|
|
1896
1938
|
}
|
|
@@ -2103,15 +2145,10 @@ export function runBuild(flags: Record<string, string>, gate: BuildGate): void {
|
|
|
2103
2145
|
pageSize: readIntFlag(flags, 'page-size', DEFAULT_PAGE_SIZE),
|
|
2104
2146
|
padding: readIntFlag(flags, 'padding', DEFAULT_PADDING),
|
|
2105
2147
|
pageEdges: readPageEdges(flags),
|
|
2148
|
+
shape: readPackShape(flags),
|
|
2106
2149
|
pageStem: 'skeleton',
|
|
2107
2150
|
};
|
|
2108
|
-
const
|
|
2109
|
-
region: img.region,
|
|
2110
|
-
absPath: img.absPath,
|
|
2111
|
-
width: img.width,
|
|
2112
|
-
height: img.height,
|
|
2113
|
-
}));
|
|
2114
|
-
const packed = packAtlas(inputs, packOpts);
|
|
2151
|
+
const packed = packAtlas(packInputs(result, packOpts.shape), packOpts);
|
|
2115
2152
|
atlasText = packed.atlasText;
|
|
2116
2153
|
for (const page of packed.pages) {
|
|
2117
2154
|
page.plate.writePng(join(opts.outDir, page.name));
|
|
@@ -2119,7 +2156,11 @@ export function runBuild(flags: Record<string, string>, gate: BuildGate): void {
|
|
|
2119
2156
|
` .. pack: ${page.name} ${page.width}x${page.height}, ` +
|
|
2120
2157
|
`${packed.placements.filter((p) => packed.pages[p.page].name === page.name).length} region(s), ` +
|
|
2121
2158
|
`${(page.occupancy * 100).toFixed(1)}% covered, padding ${packed.padding}` +
|
|
2122
|
-
(packOpts.pageEdges === 'free' ? ', page edges free' : '')
|
|
2159
|
+
(packOpts.pageEdges === 'free' ? ', page edges free' : '') +
|
|
2160
|
+
// Appended, never inserted: a reader that takes the line whole keeps
|
|
2161
|
+
// every field it read before #1099, and the mode is named under the
|
|
2162
|
+
// default too, so no reader infers it from an absent word.
|
|
2163
|
+
`, shape ${packed.shape}`,
|
|
2123
2164
|
);
|
|
2124
2165
|
}
|
|
2125
2166
|
for (const place of packed.placements) {
|
|
@@ -2134,15 +2175,7 @@ export function runBuild(flags: Record<string, string>, gate: BuildGate): void {
|
|
|
2134
2175
|
// a region, and A18 compares a second independent compile+pack. Two gates on
|
|
2135
2176
|
// one build is the cost of shipping a second atlas shape.
|
|
2136
2177
|
console.log(' .. validate (packed atlas, pages on disk)');
|
|
2137
|
-
const packAgain = packAtlas(
|
|
2138
|
-
compile(opts).images.map((img) => ({
|
|
2139
|
-
region: img.region,
|
|
2140
|
-
absPath: img.absPath,
|
|
2141
|
-
width: img.width,
|
|
2142
|
-
height: img.height,
|
|
2143
|
-
})),
|
|
2144
|
-
packOpts,
|
|
2145
|
-
);
|
|
2178
|
+
const packAgain = packAtlas(packInputs(compile(opts), packOpts.shape), packOpts);
|
|
2146
2179
|
// The document written is spelled from the packed atlas, and this gate's A18 compares it with a
|
|
2147
2180
|
// second compile's spelled from the second, independent pack (issue #1016).
|
|
2148
2181
|
modelText = modelDocument(result.model, result.skeletonText, atlasText);
|
package/src/validate.ts
CHANGED
|
@@ -102,6 +102,7 @@ import { a22MeshUvsInUnitRange } from './assertions/bodies/a22.ts';
|
|
|
102
102
|
import { a38SkinMembersAreSkinRequired } from './assertions/bodies/a38.ts';
|
|
103
103
|
import { a06AtlasPageSizeMatchesPng } from './assertions/bodies/a06.ts';
|
|
104
104
|
import { a19OverlayPngsHaveAlpha } from './assertions/bodies/a19.ts';
|
|
105
|
+
import { a49PackedFootprintsDoNotOverlap } from './assertions/bodies/a49.ts';
|
|
105
106
|
import { a27RegionNameMatchesPageFilename } from './assertions/bodies/a27.ts';
|
|
106
107
|
import type { MeshEntry as MeshAttachmentEntry, MeshFacts } from './assertions/facts/mesh_attachments.ts';
|
|
107
108
|
import type { ClipEnd, PolygonEntry, PolygonFacts } from './assertions/facts/vertex_polygons.ts';
|
|
@@ -3200,6 +3201,13 @@ export function validate(input: ValidateInput): ValidateReport {
|
|
|
3200
3201
|
// that does not ask — the reader opted in, and the message is where they find
|
|
3201
3202
|
// out what they opted into.
|
|
3202
3203
|
check('A19_OVERLAY_PNGS_HAVE_ALPHA', () => a19OverlayPngsHaveAlpha(verdicts, { atlas }, spineStage(skeletonData), { regionAttachments }, input));
|
|
3204
|
+
// Two regions on one page whose rectangles overlap and whose footprints do
|
|
3205
|
+
// too — a mesh's hull, else the rectangle (issue #1099). The clause A06 held
|
|
3206
|
+
// about two rectangles over the same texels, read over what each region
|
|
3207
|
+
// draws; the argument and the footprint are `./assertions/bodies/a49.ts` and
|
|
3208
|
+
// `./assertions/footprints.ts`. The meshes are the loaded skeleton's, and
|
|
3209
|
+
// where it did not load every footprint is the rectangle A06 read.
|
|
3210
|
+
check('A49_PACKED_FOOTPRINTS_DO_NOT_OVERLAP', () => a49PackedFootprintsDoNotOverlap(verdicts, { atlas }, spineRegionJoins(input.atlasText, raw), skeletonData === null ? null : loadedMeshFacts()));
|
|
3203
3211
|
|
|
3204
3212
|
// -------------------------------------------------------------------------
|
|
3205
3213
|
// Archetype assertions — the invariants the RIG declares about itself.
|