@rig-c/parts 1.9.0 → 1.10.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 +5 -4
- package/cli.ts +128 -15
- package/docs/AUTHORING.md +128 -25
- package/docs/STABILITY.md +16 -7
- package/package.json +1 -1
- package/src/assemble.ts +100 -22
- package/src/autoenvelope.ts +2 -1
- package/src/automotion.ts +41 -0
- package/src/build.ts +69 -12
- package/src/check.ts +152 -33
- package/src/config.ts +52 -6
- package/src/contour.ts +40 -2
- package/src/headframe.ts +2 -1
- package/src/instruments.ts +9 -5
- package/src/layers.ts +12 -7
- package/src/length.ts +21 -0
- package/src/meshdraft.ts +887 -0
- package/src/parts.ts +41 -10
- package/src/propose.ts +184 -68
- package/src/requirements.ts +24 -8
- package/src/rig.ts +4 -3
- package/src/skeleton.ts +2 -1
- package/src/structure.ts +5 -4
- package/src/tags.ts +86 -0
- package/src/turn.ts +31 -0
package/src/headframe.ts
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import type { Point } from './config.ts';
|
|
35
35
|
import { cropToSpineY } from './coords.ts';
|
|
36
36
|
import type { Mask } from './raster/types.ts';
|
|
37
|
+
import { edgeLength } from './length.ts';
|
|
37
38
|
|
|
38
39
|
/**
|
|
39
40
|
* The eye line's angle in Spine degrees (counter-clockwise, y up) through two
|
|
@@ -167,7 +168,7 @@ export function headFrame(
|
|
|
167
168
|
const [l, r] = eyes[0][0] < eyes[1][0] ? [eyes[0], eyes[1]] : [eyes[1], eyes[0]];
|
|
168
169
|
const dx = r[0] - l[0];
|
|
169
170
|
const dy = r[1] - l[1];
|
|
170
|
-
const bar =
|
|
171
|
+
const bar = edgeLength(dx, dy);
|
|
171
172
|
const u: [number, number] = [dx / bar, dy / bar];
|
|
172
173
|
// The bar's perpendicular toward the chin: u turned a quarter clockwise as drawn (y down).
|
|
173
174
|
const n: [number, number] = [-u[1], u[0]];
|
package/src/instruments.ts
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import type { StageBox, Viewport } from './check.ts';
|
|
25
25
|
import { cropToSpineY } from './coords.ts';
|
|
26
|
+
import { edgeLength } from './length.ts';
|
|
26
27
|
import { OPAQUE_ALPHA_ABOVE } from './layers.ts';
|
|
27
28
|
import { type PartRecord, type PartsFile, tagOf } from './parts.ts';
|
|
28
29
|
import type { Raster } from './raster/types.ts';
|
|
@@ -446,7 +447,7 @@ export function halfTravels(art: ArtMask, box: { x: number; y: number; w: number
|
|
|
446
447
|
for (const f of frames) {
|
|
447
448
|
const c = f === null ? null : centroidIn(f, ...band);
|
|
448
449
|
if (c === null) continue;
|
|
449
|
-
max = Math.max(max,
|
|
450
|
+
max = Math.max(max, edgeLength(c[0] - c0[0], c[1] - c0[1]));
|
|
450
451
|
}
|
|
451
452
|
return max;
|
|
452
453
|
};
|
|
@@ -508,7 +509,7 @@ export function readSine(keys: readonly Key[], duration: number): SineReading |
|
|
|
508
509
|
}
|
|
509
510
|
a *= 2 / n;
|
|
510
511
|
b *= 2 / n;
|
|
511
|
-
const mag =
|
|
512
|
+
const mag = edgeLength(a, b);
|
|
512
513
|
if (mag > best.mag) best = { j, a, b, mag };
|
|
513
514
|
}
|
|
514
515
|
if (best.j === 0) return null;
|
|
@@ -780,6 +781,9 @@ export function stretchSeverity(ratio: number): number {
|
|
|
780
781
|
return ratio >= 1 ? ratio : 1 / ratio;
|
|
781
782
|
}
|
|
782
783
|
|
|
784
|
+
/** An edge's length: {@link edgeLength} of `src/length.ts` (issues #270, #279), re-exported where the stretch reader first used it. */
|
|
785
|
+
export { edgeLength } from './length.ts';
|
|
786
|
+
|
|
783
787
|
/**
|
|
784
788
|
* For every mesh in `rest`, every triangle's three edges in every frame that
|
|
785
789
|
* shows the attachment: deformed length over rest length, the largest and the
|
|
@@ -792,7 +796,7 @@ export function stretchFigures(rest: readonly MeshRest[], frames: readonly Geome
|
|
|
792
796
|
return rest.map((m) => {
|
|
793
797
|
const t = m.triangles;
|
|
794
798
|
const v0 = m.vertices;
|
|
795
|
-
const len = (v: readonly number[], a: number, b: number): number =>
|
|
799
|
+
const len = (v: readonly number[], a: number, b: number): number => edgeLength(v[2 * a] - v[2 * b], v[2 * a + 1] - v[2 * b + 1]);
|
|
796
800
|
const out: MeshStretch = { slot: m.slot, attachment: m.attachment, triangles: t.length / 3, frames: 0, max: null, min: null, degenerate: [] };
|
|
797
801
|
const restLen: number[] = [];
|
|
798
802
|
for (let i = 0; i < t.length; i += 3) {
|
|
@@ -1032,7 +1036,7 @@ export function stillReading(art: ArtMask, stage: StageBox, frames: ReadonlyArra
|
|
|
1032
1036
|
const e2x = d[2 * l] - x0;
|
|
1033
1037
|
const e2y = d[2 * l + 1] - y0;
|
|
1034
1038
|
for (const bary of pc.pieces[t]) {
|
|
1035
|
-
for (let q = 0; q < bary.length; q += 2) fmax = Math.max(fmax,
|
|
1039
|
+
for (let q = 0; q < bary.length; q += 2) fmax = Math.max(fmax, edgeLength(x0 + bary[q] * e1x + bary[q + 1] * e2x, y0 + bary[q] * e1y + bary[q + 1] * e2y));
|
|
1036
1040
|
}
|
|
1037
1041
|
const cx = x0 + pc.centroid[2 * t] * e1x + pc.centroid[2 * t + 1] * e2x;
|
|
1038
1042
|
const cy = y0 + pc.centroid[2 * t] * e1y + pc.centroid[2 * t + 1] * e2y;
|
|
@@ -1101,7 +1105,7 @@ export function crossingOf(art: ArtMask, stage: StageBox, frames: ReadonlyArray<
|
|
|
1101
1105
|
if (Math.max(Math.abs(u - u0), Math.abs(v - v0)) !== r || inR(u, v)) continue;
|
|
1102
1106
|
const sx0 = stage.x + u;
|
|
1103
1107
|
const sy1 = stage.y + cropToSpineY(v, H);
|
|
1104
|
-
best = Math.min(best,
|
|
1108
|
+
best = Math.min(best, edgeLength(Math.max(sx0 - x, 0, x - sx0 - 1), Math.max(sy1 - 1 - y, 0, y - sy1)));
|
|
1105
1109
|
}
|
|
1106
1110
|
}
|
|
1107
1111
|
}
|
package/src/layers.ts
CHANGED
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
* and for a PSD it is the stacking order itself, with `depth` null because a
|
|
26
26
|
* PSD carries none. Every refusal is collected and thrown once, so one run
|
|
27
27
|
* names every missing file.
|
|
28
|
+
*
|
|
29
|
+
* A layer is named by a See-through v3 tag or, for a layer the author made
|
|
30
|
+
* by hand and added to the run (issue #256), by `author:<label>`
|
|
31
|
+
* (`src/tags.ts`, `AUTHOR_PREFIX`): read with `group` "author", it has no
|
|
32
|
+
* role, and its place in the draw order is read like any other layer's.
|
|
28
33
|
*/
|
|
29
34
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
30
35
|
import { basename, dirname, join } from 'node:path';
|
|
@@ -32,7 +37,7 @@ import { initializeCanvas, type Layer as PsdLayer, readPsd } from 'ag-psd';
|
|
|
32
37
|
import { type Problem, refuseIfAny } from './errors.ts';
|
|
33
38
|
import { decodePngBytes } from './raster/png.ts';
|
|
34
39
|
import type { Raster } from './raster/types.ts';
|
|
35
|
-
import {
|
|
40
|
+
import { type LayerTagReading, layerTagRequirement, readLayerTag } from './tags.ts';
|
|
36
41
|
|
|
37
42
|
/** Alpha strictly above this counts as painted. The reference uses the same cut for its ghost clean-up and its sheets. */
|
|
38
43
|
export const OPAQUE_ALPHA_ABOVE = 8;
|
|
@@ -45,9 +50,9 @@ export const OPAQUE_ALPHA_ABOVE = 8;
|
|
|
45
50
|
export const NEAR_WHITE_MIN = 235;
|
|
46
51
|
|
|
47
52
|
export interface Layer {
|
|
48
|
-
/** As the input names it
|
|
53
|
+
/** As the input names it: a v3 tag, possibly with a `-r`/`-l` side, or an author-made layer's `author:<label>`. */
|
|
49
54
|
name: string;
|
|
50
|
-
tag:
|
|
55
|
+
tag: LayerTagReading;
|
|
51
56
|
/** The file the pixels came from, or null for a PSD layer. */
|
|
52
57
|
file: string | null;
|
|
53
58
|
/** The layer's own pixels, `(right-left)x(bottom-top)`, straight alpha. */
|
|
@@ -197,9 +202,9 @@ export function readWrapperLayers(path: string): LayerSet {
|
|
|
197
202
|
return;
|
|
198
203
|
}
|
|
199
204
|
const name = e.name;
|
|
200
|
-
const tag =
|
|
205
|
+
const tag = readLayerTag(name);
|
|
201
206
|
if (tag === null) {
|
|
202
|
-
refuse('LAYERS_TAG_KNOWN', `layer "${name}"`,
|
|
207
|
+
refuse('LAYERS_TAG_KNOWN', `layer "${name}"`, layerTagRequirement(name));
|
|
203
208
|
ok = false;
|
|
204
209
|
}
|
|
205
210
|
for (const key of ['left', 'top', 'right', 'bottom'] as const) {
|
|
@@ -369,9 +374,9 @@ export function readPsdLayers(path: string): LayerSet {
|
|
|
369
374
|
const layers: Layer[] = [];
|
|
370
375
|
leaves.forEach((layer) => {
|
|
371
376
|
const name = layer.name ?? '';
|
|
372
|
-
const tag =
|
|
377
|
+
const tag = readLayerTag(name);
|
|
373
378
|
if (tag === null) {
|
|
374
|
-
refuse('LAYERS_TAG_KNOWN', `PSD layer "${name}"`,
|
|
379
|
+
refuse('LAYERS_TAG_KNOWN', `PSD layer "${name}"`, layerTagRequirement(name));
|
|
375
380
|
return;
|
|
376
381
|
}
|
|
377
382
|
if (seen.has(name)) {
|
package/src/length.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one length this package writes (issues #270, #279): `sqrt(dx * dx + dy * dy)`.
|
|
3
|
+
*
|
|
4
|
+
* Not `Math.hypot`. The language does not promise how `Math.hypot` rounds, and
|
|
5
|
+
* Bun 1.4.2 rounds it differently on two platforms: on the demo's `earring`,
|
|
6
|
+
* edge 11-10 at idle frame 10 (dx 34.26716181006313, dy 0.5713103861783111),
|
|
7
|
+
* it is 34.27192399143071 on macOS and 34.27192399143072 on Linux, measured,
|
|
8
|
+
* with the posed vertices bit-identical on both. That last bit decided which of
|
|
9
|
+
* two edges 2 ulp apart held a mesh's largest ratio, so `TEXTURE_STRETCH`'s
|
|
10
|
+
* `max_at` named one triangle on one machine and another on the other (#276).
|
|
11
|
+
* A multiply, an add and a square root are each correctly rounded by IEEE 754,
|
|
12
|
+
* so this gives the same bits everywhere (34.27192399143071 on both, measured).
|
|
13
|
+
* At pixel scale `Math.hypot`'s guard against overflow is not needed: a
|
|
14
|
+
* coordinate would have to pass about 1e154 for the squares to overflow.
|
|
15
|
+
*
|
|
16
|
+
* The tree rule `TY15` refuses a call of `Math.hypot` anywhere under `src/` and in
|
|
17
|
+
* `cli.ts`, so every length is this one.
|
|
18
|
+
*/
|
|
19
|
+
export function edgeLength(dx: number, dy: number): number {
|
|
20
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
21
|
+
}
|