spine-rigc 1.6.1 → 1.6.2
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/docs/AUTHORING.md +4 -1
- package/package.json +1 -1
- package/src/render.ts +58 -13
package/docs/AUTHORING.md
CHANGED
|
@@ -235,7 +235,10 @@ spine-core's own `SkeletonClipping`, in spine-webgl's call order — so a slot f
|
|
|
235
235
|
the clip's through its `end` slot draws only what falls inside the polygon, in
|
|
236
236
|
`render`, `check` and every other picture rigc's own rasteriser draws (`preview`
|
|
237
237
|
plays in Spine's player, which always clipped); the framing box still counts
|
|
238
|
-
what the clip removes, so adding or keying a mask moves no pixel it leaves drawn
|
|
238
|
+
what the clip removes, so adding or keying a mask moves no pixel it leaves drawn,
|
|
239
|
+
and it never counts a slot whose bone the posed skin leaves unposed (a `"skin": true`
|
|
240
|
+
bone the skin does not name, or any bone below one, §3.4.1), which draws no pixel;
|
|
241
|
+
when every drawn slot is such a slot, the render is refused by name (§3.4). Three commands take `--out`: a directory for `render`
|
|
239
242
|
(default `render/`), the `.html` file for `preview` (default `preview.html`) and
|
|
240
243
|
for `vote` (default `ballot.html`).
|
|
241
244
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-rigc",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "AI-authored Spine 2D rigging and animation, verified before it is written. rigc compiles a rig spec into Spine 4.3 skeleton data, round-trips it through spine-core and named assertions, and emits nothing that fails. Output imports into the Spine editor. Ships as an agent skill.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/render.ts
CHANGED
|
@@ -2147,7 +2147,8 @@ export interface SkinRoster {
|
|
|
2147
2147
|
* The skin roster of a parsed Spine file, as the runtime flags it —
|
|
2148
2148
|
* `unposedBones` over a fresh skeleton's `active` under each skin, so Spine's
|
|
2149
2149
|
* activation rule is the runtime's and is not restated here. Nothing is posed
|
|
2150
|
-
* until a question is asked
|
|
2150
|
+
* until a question is asked: the framing asks one (the skin it frames under,
|
|
2151
|
+
* `slotsOnUnposedBones`) and the refusal asks one per skin.
|
|
2151
2152
|
*/
|
|
2152
2153
|
export function skinRosterOf(data: SkeletonData): SkinRoster {
|
|
2153
2154
|
return {
|
|
@@ -2164,6 +2165,28 @@ function rosterBehind(source: PoseSource, roster: SkinRoster | undefined): SkinR
|
|
|
2164
2165
|
return source instanceof SkeletonData ? skinRosterOf(source) : roster;
|
|
2165
2166
|
}
|
|
2166
2167
|
|
|
2168
|
+
/**
|
|
2169
|
+
* ⭐ **The one derivation of "a drawn slot on an unposed bone"** (issues #997,
|
|
2170
|
+
* #1000): the names of the slots of `slots` whose bone `roster` says `skin`
|
|
2171
|
+
* leaves unposed — inactive itself, or below an inactive bone. Both posers draw
|
|
2172
|
+
* such a slot through the zero matrix, so it draws no pixel and every vertex of
|
|
2173
|
+
* it sits at the origin. `framingViewport` takes these slots off the box
|
|
2174
|
+
* (#1000) and `unframeableSentence` refuses a pose that drew nothing else
|
|
2175
|
+
* (#997), from this one set.
|
|
2176
|
+
*
|
|
2177
|
+
* Empty without a roster: a bare `Poser` carries no skin structure, and what
|
|
2178
|
+
* cannot be read is not guessed — every slot then counts, as it did before.
|
|
2179
|
+
*/
|
|
2180
|
+
export function slotsOnUnposedBones(
|
|
2181
|
+
slots: ReadonlyArray<{ name: string; bone: string }>,
|
|
2182
|
+
skin: string | undefined,
|
|
2183
|
+
roster: SkinRoster | undefined,
|
|
2184
|
+
): Set<string> {
|
|
2185
|
+
if (roster === undefined) return new Set();
|
|
2186
|
+
const unposed = roster.unposedUnder(skin);
|
|
2187
|
+
return new Set(slots.filter((slot) => unposed.has(slot.bone)).map((slot) => slot.name));
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2167
2190
|
/** How many drawn slots a refusal names before it says how many more there are. */
|
|
2168
2191
|
const UNFRAMEABLE_NAMED = 3;
|
|
2169
2192
|
|
|
@@ -2185,9 +2208,10 @@ const UNFRAMEABLE_NAMED = 3;
|
|
|
2185
2208
|
* collapsed their attachments (a world scale of 0 does).
|
|
2186
2209
|
*
|
|
2187
2210
|
* `roster` is the skin structure of the Spine file the poser poses
|
|
2188
|
-
* (`skinRosterOf`), which is what says whether a bone is unposed
|
|
2189
|
-
*
|
|
2190
|
-
* cannot be told from the second, and
|
|
2211
|
+
* (`skinRosterOf`), which is what says whether a bone is unposed
|
|
2212
|
+
* (`slotsOnUnposedBones`, the set the framing box leaves out) and which skins
|
|
2213
|
+
* pose it. Without it the first sentence cannot be told from the second, and
|
|
2214
|
+
* the second is what is said.
|
|
2191
2215
|
*
|
|
2192
2216
|
* Distinct from *nothing to draw*: that is a skeleton that posed no vertex at
|
|
2193
2217
|
* all, and its fix is art; this one posed vertices, and they have no place.
|
|
@@ -2213,8 +2237,8 @@ export function unframeableSentence(
|
|
|
2213
2237
|
const named = (describe: (slot: { name: string; bone: string }) => string): string =>
|
|
2214
2238
|
drawn.slice(0, UNFRAMEABLE_NAMED).map(describe).join('; ') + more;
|
|
2215
2239
|
if (roster !== undefined) {
|
|
2216
|
-
const
|
|
2217
|
-
if (drawn.length > 0 && drawn.every((slot) =>
|
|
2240
|
+
const offBox = slotsOnUnposedBones(slots, skin, roster);
|
|
2241
|
+
if (drawn.length > 0 && drawn.every((slot) => offBox.has(slot.name))) {
|
|
2218
2242
|
const bySkin = roster.skins.map((name) => ({ name, unposed: roster.unposedUnder(name) }));
|
|
2219
2243
|
const posers = (bone: string): string => {
|
|
2220
2244
|
const names = bySkin.filter((k) => !k.unposed.has(bone)).map((k) => JSON.stringify(k.name));
|
|
@@ -2250,11 +2274,21 @@ export function unframeableSentence(
|
|
|
2250
2274
|
* every vertex sits at one point is refused by an `UnframeablePoseError`
|
|
2251
2275
|
* (`unframeableSentence`, issue #997), never framed at a scale of Infinity.
|
|
2252
2276
|
*
|
|
2277
|
+
* The box is over the slots that POSE (issue #1000): a drawn slot whose bone
|
|
2278
|
+
* the skin leaves unposed (`slotsOnUnposedBones`) draws no pixel — both posers
|
|
2279
|
+
* put it through the zero matrix — so its vertices at the origin are not part
|
|
2280
|
+
* of the shot, and counting them framed every frame of a rig that carries one
|
|
2281
|
+
* to a point nothing is drawn at (256x94 where the posed slot alone gives
|
|
2282
|
+
* 256x55). When every drawn slot is such a slot there is no posed box at all,
|
|
2283
|
+
* and that is #997's refusal, never an empty or invented one.
|
|
2284
|
+
*
|
|
2253
2285
|
* `roster` is the skin roster behind a `Poser` source (`skinRosterOf`) — what
|
|
2254
|
-
*
|
|
2255
|
-
* source is its own.
|
|
2286
|
+
* says which bones the skin leaves unposed, and lets the refusal name the skins
|
|
2287
|
+
* that pose one. Spine data as the source is its own. A bare `Poser` with no
|
|
2288
|
+
* roster cannot tell an unposed bone from a posed one, so every drawn slot
|
|
2289
|
+
* counts there; every CLI caller passes the roster, so both posers frame alike.
|
|
2256
2290
|
*/
|
|
2257
|
-
export function framingViewport(source: PoseSource, maxSide: number, opts?: PoseOptions,
|
|
2291
|
+
export function framingViewport(source: PoseSource, maxSide: number, opts?: PoseOptions, rosterGiven?: SkinRoster): Viewport | null {
|
|
2258
2292
|
const poser = poserOf(source);
|
|
2259
2293
|
// The skin belongs here as much as in the frames: the union box is over the
|
|
2260
2294
|
// attachments that POSE, and two skins fill a slot with art of different sizes
|
|
@@ -2285,9 +2319,20 @@ export function framingViewport(source: PoseSource, maxSide: number, opts?: Pose
|
|
|
2285
2319
|
// this, the first case's `null` covered both, and a single overflowing bone
|
|
2286
2320
|
// among finite ones reached neither: its box was finite on one side, and
|
|
2287
2321
|
// `render` wrote a NaN-by-NaN frame set with exit 0.
|
|
2288
|
-
const
|
|
2289
|
-
|
|
2290
|
-
|
|
2322
|
+
const drewAny = (frameSets: ReadonlyArray<readonly Frame[]>): boolean =>
|
|
2323
|
+
frameSets.some((frames) => frames.some((frame) => frame.pieces.some((piece) => piece.world.length > 0)));
|
|
2324
|
+
if (!drewAny(sets)) return null;
|
|
2325
|
+
// ⭐ The box is over the slots that pose (issue #1000). A drawn slot on a bone
|
|
2326
|
+
// the skin leaves unposed is taken off — it draws no pixel — unless nothing
|
|
2327
|
+
// else drew, in which case every set is kept whole so the pose reaches #997's
|
|
2328
|
+
// refusal below exactly as it did before this: the same inputs, the same
|
|
2329
|
+
// sentence, never an empty box framed to something.
|
|
2330
|
+
const roster = rosterBehind(source, rosterGiven);
|
|
2331
|
+
const offBox = slotsOnUnposedBones(poser.slots, framed.skin, roster);
|
|
2332
|
+
const posedSets =
|
|
2333
|
+
offBox.size === 0 ? sets : sets.map((frames) => frames.map((frame) => ({ ...frame, pieces: frame.pieces.filter((piece) => !offBox.has(piece.slot)) })));
|
|
2334
|
+
const boxed = drewAny(posedSets) ? posedSets : sets;
|
|
2335
|
+
const box = unionBounds(boxed);
|
|
2291
2336
|
if (![box.minX, box.minY, box.maxX, box.maxY].every(Number.isFinite)) {
|
|
2292
2337
|
const found = nonFinitePoseOf(
|
|
2293
2338
|
poser,
|
|
@@ -2309,7 +2354,7 @@ export function framingViewport(source: PoseSource, maxSide: number, opts?: Pose
|
|
|
2309
2354
|
// A finite box over one point (issue #997): every drawn slot on a bone the
|
|
2310
2355
|
// skin leaves unposed, or every vertex collapsed. Framed, it is a scale of
|
|
2311
2356
|
// Infinity and a frame of NaN by NaN pixels, written as 0x0 with exit 0.
|
|
2312
|
-
const unframeable = unframeableSentence(
|
|
2357
|
+
const unframeable = unframeableSentence(boxed, poser.slots, framed.skin, roster);
|
|
2313
2358
|
if (unframeable !== null) throw new UnframeablePoseError(unframeable);
|
|
2314
2359
|
const pad = Math.max(box.maxX - box.minX, box.maxY - box.minY) * PAD;
|
|
2315
2360
|
return viewportFor(box.minX - pad, box.minY - pad, box.maxX + pad, box.maxY + pad, maxSide);
|