spine-rigc 2.20.0 → 2.20.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/src/meshreduce.ts CHANGED
@@ -2,7 +2,9 @@
2
2
  * Mesh reduction and local refinement — stage B2 of the contract in
3
3
  * docs/MESH_REDUCTION.md (issue #1221, stage B: issue #1224).
4
4
  *
5
- * One operation is exported, `reduceMesh`, and it is two operations composed:
5
+ * One operation is exported, `reduceMesh` — `reduceMeshWith` is the same
6
+ * operation over art rasters the caller made — and it is two operations
7
+ * composed:
6
8
  *
7
9
  * 1. **Refinement** (`refineRegions`) inserts vertices inside the union of the
8
10
  * declared regions and their bands until §5's `L(R)` holds on every edge
@@ -22,7 +24,11 @@
22
24
  * ## What every step is held to
23
25
  *
24
26
  * Each candidate step is measured by `measureMeshQuality` (`src/meshquality.ts`)
25
- * — no row is re-implemented here — and taken only when every row the caller's
27
+ * — no row is re-implemented here; the call reads it as `measureMeshQualityWith`
28
+ * over the art's rasters taken once at admission (`src/meshrasters.ts`, issue
29
+ * #1240), and as `measureMeshQualityStep` carried from the last step's
30
+ * measurement (`StepRasters`, issue #1246), each of which returns the same
31
+ * report — and taken only when every row the caller's
26
32
  * contract requires is `pass`: the art fit at `targets.artFit`, the boundary
27
33
  * deviation from the source hull at `targets.maxBoundaryDeviation`, the minimum
28
34
  * angle when declared, orientation and degeneracy at 0, and every region's
@@ -71,8 +77,6 @@ import {
71
77
  distanceToSegment,
72
78
  earClip,
73
79
  findSelfIntersection,
74
- labelIslands,
75
- artOf,
76
80
  MeshError,
77
81
  MeshReductionError,
78
82
  meshEdges,
@@ -85,7 +89,8 @@ import {
85
89
  import {
86
90
  BAND_CONTACT_TOLERANCE,
87
91
  edgeIsHeldByRegion,
88
- measureMeshQuality,
92
+ measureMeshQualityStep,
93
+ measureMeshQualityWith,
89
94
  type AttachmentRef,
90
95
  type ArtFitBounds,
91
96
  type CandidateReport,
@@ -101,6 +106,7 @@ import {
101
106
  type SourceMesh,
102
107
  type Termination,
103
108
  } from './meshquality.ts';
109
+ import { artRastersOf, stepRastersOf, type ArtRasters, type StepRasters } from './meshrasters.ts';
104
110
 
105
111
  // ---------------------------------------------------------------------------
106
112
  // the result
@@ -711,6 +717,10 @@ interface Run {
711
717
  protectedVertices: Set<number>;
712
718
  protectedEdges: Array<[number, number]>;
713
719
  sourceEdges: Set<string>;
720
+ /** The art's rasters, taken once for the call and read by every measurement in it (issue #1240). */
721
+ rasters: ArtRasters;
722
+ /** The step state each removal and insertion is measured through, carried from the last measurement (issue #1246); null measures every step in full. */
723
+ stepRasters: StepRasters | null;
714
724
  }
715
725
 
716
726
  /** A measurement of a canonical mesh against the result's full contract. */
@@ -731,7 +741,7 @@ function measureAgainstTargets(run: Run, mesh: SourceMesh, id: string): MeshQual
731
741
  boneOrder: input.boneOrder,
732
742
  preset: input.preset,
733
743
  };
734
- return measureMeshQuality(measureInput);
744
+ return run.stepRasters === null ? measureMeshQualityWith(measureInput, run.rasters) : measureMeshQualityStep(measureInput, run.stepRasters);
735
745
  }
736
746
 
737
747
  /** The order constraints are named in when several fail on one step: structure, then shape, then art, then density. */
@@ -1167,26 +1177,61 @@ function tryRemoval(run: Run, v: number): string | null {
1167
1177
  */
1168
1178
  export function reduceMesh(input: MeshReductionInput): MeshReductionResult {
1169
1179
  validateReduction(input);
1180
+ const rasters = artRastersOf(input.art);
1181
+ return reduceValidated(input, rasters, stepRastersOf(rasters));
1182
+ }
1183
+
1184
+ /**
1185
+ * `reduceMesh` over art rasters the caller made (`artRastersOf`,
1186
+ * `src/meshrasters.ts`) — the result is `reduceMesh`'s for the same input, byte
1187
+ * for byte, and every measurement of the call reads the one object, so its
1188
+ * `tally` is the count of what the call computed. Rasters taken from another
1189
+ * art are refused at admission (`REDUCE_ART_RASTERS_MISMATCH`). Each step is
1190
+ * measured through `steps` (`stepRastersOf(rasters)` unless given), carried
1191
+ * from the last measurement (issue #1246); `null` measures every step in full,
1192
+ * which is the path the carried one is held equal to. Step rasters made over
1193
+ * another rasters object are refused by the same code.
1194
+ *
1195
+ * Internal: it is on `spine-rigc/mesh` only because that entry re-exports this
1196
+ * module with `export *`, and a symbol that is merely exported is not promised
1197
+ * (RELEASING.md, *The import surface*).
1198
+ */
1199
+ export function reduceMeshWith(input: MeshReductionInput, rasters: ArtRasters, steps: StepRasters | null = stepRastersOf(rasters)): MeshReductionResult {
1200
+ validateReduction(input);
1201
+ if (steps !== null && steps.rasters !== rasters) {
1202
+ refuse('REDUCE_ART_RASTERS_MISMATCH', `attachment ${nameOf(input.attachment)}: the step rasters were made over another rasters object; required step rasters made over the rasters passed beside them (stepRastersOf(rasters))`);
1203
+ }
1204
+ return reduceValidated(input, rasters, steps);
1205
+ }
1206
+
1207
+ /**
1208
+ * The operation, over an input `validateReduction` accepted; the art's rasters are computed at most once, in
1209
+ * `rasters`, and every refinement and removal step is measured through `steps` when it is given (issue #1246).
1210
+ */
1211
+ function reduceValidated(input: MeshReductionInput, rasters: ArtRasters, steps: StepRasters | null): MeshReductionResult {
1170
1212
  const who = `attachment ${nameOf(input.attachment)}`;
1171
1213
  const src = input.source;
1172
1214
  const sourceHull: Array<[number, number]> = src.points.slice(0, Math.max(0, src.hull)).map(([x, y]): [number, number] => [x, y]);
1173
1215
 
1174
1216
  // The source, measured against its own admissibility bounds (correction 3) — this is also what refuses a
1175
1217
  // malformed art, mesh, region list or sample floor, by measureMeshQuality's own codes.
1176
- const admit = measureMeshQuality({
1177
- id: 'source',
1178
- attachment: input.attachment,
1179
- art: input.art,
1180
- source: src,
1181
- targets: { artFit: input.sourceBounds, maxBoundaryDeviation: null, regions: input.targets.regions },
1182
- referenceHull: null,
1183
- minArtSamples: input.minArtSamples,
1184
- regionArtSamples: input.regionArtSamples,
1185
- protect: input.protect,
1186
- influences: input.influences,
1187
- boneOrder: input.boneOrder,
1188
- preset: input.preset,
1189
- });
1218
+ const admit = measureMeshQualityWith(
1219
+ {
1220
+ id: 'source',
1221
+ attachment: input.attachment,
1222
+ art: input.art,
1223
+ source: src,
1224
+ targets: { artFit: input.sourceBounds, maxBoundaryDeviation: null, regions: input.targets.regions },
1225
+ referenceHull: null,
1226
+ minArtSamples: input.minArtSamples,
1227
+ regionArtSamples: input.regionArtSamples,
1228
+ protect: input.protect,
1229
+ influences: input.influences,
1230
+ boneOrder: input.boneOrder,
1231
+ preset: input.preset,
1232
+ },
1233
+ rasters,
1234
+ );
1190
1235
  const effective = effectiveOf(input, admit.effective, sourceHull);
1191
1236
  const sourceCounts = admit.sourceCounts;
1192
1237
  const noMesh = (termination: Termination): MeshReductionResult => ({
@@ -1215,7 +1260,7 @@ export function reduceMesh(input: MeshReductionInput): MeshReductionResult {
1215
1260
  );
1216
1261
  }
1217
1262
  }
1218
- checkIslands(input, who);
1263
+ checkIslands(input, who, rasters);
1219
1264
  checkDeformBeforeWork(input);
1220
1265
 
1221
1266
  const boneRank = new Map((input.boneOrder ?? []).map((b, i) => [b, i]));
@@ -1243,6 +1288,8 @@ export function reduceMesh(input: MeshReductionInput): MeshReductionResult {
1243
1288
  keyed: keyedSourceVertices(input),
1244
1289
  ...protectionOf(input, work, sourceEdges),
1245
1290
  sourceEdges,
1291
+ rasters,
1292
+ stepRasters: steps,
1246
1293
  };
1247
1294
 
1248
1295
  const refined = refineRegions(run);
@@ -1268,11 +1315,10 @@ export function reduceMesh(input: MeshReductionInput): MeshReductionResult {
1268
1315
  }
1269
1316
 
1270
1317
  /** §4: full coverage asked of a source that touches no pixel of some art island is refused, never met by deleting art. */
1271
- function checkIslands(input: MeshReductionInput, who: string): void {
1318
+ function checkIslands(input: MeshReductionInput, who: string, rasters: ArtRasters): void {
1272
1319
  if (input.targets.artFit.minCoverage !== 1) return;
1273
- const { mask, threshold, frame } = input.art;
1274
- const bits = artOf(mask, threshold);
1275
- const { label, sizes } = labelIslands(bits, mask.width, mask.height);
1320
+ const { mask, frame } = input.art;
1321
+ const { label, sizes } = rasters.islands();
1276
1322
  const onGrid = input.source.points.map(([x, y]): [number, number] => [x * frame.pageScale, y * frame.pageScale]);
1277
1323
  const covered = rasteriseTriangles(onGrid, input.source.triangles, mask.width, mask.height);
1278
1324
  const touched = new Set<number>();