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/package.json +1 -1
- package/src/mesh.ts +84 -23
- package/src/meshquality.ts +275 -81
- package/src/meshrasters.ts +621 -0
- package/src/meshreduce.ts +71 -25
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
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,
|
|
1274
|
-
const
|
|
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>();
|