@tscircuit/rectdiff 0.0.21 → 0.0.22
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/dist/index.js +30 -6
- package/lib/solvers/RectDiffSeedingSolver/computeInverseRects.ts +37 -1
- package/lib/utils/expandRectFromSeed.ts +11 -5
- package/package.json +1 -1
- package/test-assets/bugreport-c7537683-stalling.json +1107 -0
- package/tests/bugreport-stalling.test.ts +102 -0
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +2 -2
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +2 -2
- package/tests/solver/bugreport26-66b0b2/__snapshots__/bugreport26-66b0b2.snap.svg +2 -2
- package/tests/solver/bugreport27-dd3734/__snapshots__/bugreport27-dd3734.snap.svg +2 -2
- package/tests/solver/bugreport28-18a9ef/__snapshots__/bugreport28-18a9ef.snap.svg +2 -2
- package/tests/solver/bugreport29-7deae8/__snapshots__/bugreport29-7deae8.snap.svg +2 -2
- package/tests/solver/bugreport30-2174c8/__snapshots__/bugreport30-2174c8.snap.svg +2 -2
- package/tests/solver/bugreport33-213d45/__snapshots__/bugreport33-213d45.snap.svg +2 -2
- package/tests/solver/bugreport34-e9dea2/__snapshots__/bugreport34-e9dea2.snap.svg +2 -2
- package/tests/solver/bugreport35-191db9/__snapshots__/bugreport35-191db9.snap.svg +2 -2
- package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +1 -1
package/dist/index.js
CHANGED
|
@@ -749,11 +749,31 @@ function isPointInPolygon(p, polygon) {
|
|
|
749
749
|
}
|
|
750
750
|
|
|
751
751
|
// lib/solvers/RectDiffSeedingSolver/computeInverseRects.ts
|
|
752
|
+
function simplifyPolygon(polygon, precision) {
|
|
753
|
+
const round = (v) => Math.round(v / precision) * precision;
|
|
754
|
+
const seen = /* @__PURE__ */ new Set();
|
|
755
|
+
const result = [];
|
|
756
|
+
for (const p of polygon) {
|
|
757
|
+
const rx = round(p.x);
|
|
758
|
+
const ry = round(p.y);
|
|
759
|
+
const key = `${rx},${ry}`;
|
|
760
|
+
if (!seen.has(key)) {
|
|
761
|
+
seen.add(key);
|
|
762
|
+
result.push({ x: rx, y: ry });
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
752
767
|
function computeInverseRects(bounds, polygon) {
|
|
753
768
|
if (!polygon || polygon.length < 3) return [];
|
|
769
|
+
const MAX_POLYGON_POINTS = 100;
|
|
770
|
+
const workingPolygon = polygon.length > MAX_POLYGON_POINTS ? simplifyPolygon(
|
|
771
|
+
polygon,
|
|
772
|
+
Math.max(bounds.width, bounds.height) / MAX_POLYGON_POINTS
|
|
773
|
+
) : polygon;
|
|
754
774
|
const xs = /* @__PURE__ */ new Set([bounds.x, bounds.x + bounds.width]);
|
|
755
775
|
const ys = /* @__PURE__ */ new Set([bounds.y, bounds.y + bounds.height]);
|
|
756
|
-
for (const p of
|
|
776
|
+
for (const p of workingPolygon) {
|
|
757
777
|
xs.add(p.x);
|
|
758
778
|
ys.add(p.y);
|
|
759
779
|
}
|
|
@@ -1113,34 +1133,38 @@ function expandRectFromSeed(params) {
|
|
|
1113
1133
|
continue;
|
|
1114
1134
|
}
|
|
1115
1135
|
for (const b of blockers) if (overlaps(r, b)) continue STRATS;
|
|
1136
|
+
const MIN_EXPANSION = 1e-6;
|
|
1137
|
+
const MAX_ITERATIONS = 1e3;
|
|
1116
1138
|
let improved = true;
|
|
1117
|
-
|
|
1139
|
+
let iterations = 0;
|
|
1140
|
+
while (improved && iterations < MAX_ITERATIONS) {
|
|
1141
|
+
iterations++;
|
|
1118
1142
|
improved = false;
|
|
1119
1143
|
const commonParams = { bounds, blockers, maxAspect: maxAspectRatio };
|
|
1120
1144
|
collectBlockers(searchStripRight({ rect: r, bounds }));
|
|
1121
1145
|
const eR = maxExpandRight({ ...commonParams, r });
|
|
1122
|
-
if (eR >
|
|
1146
|
+
if (eR > MIN_EXPANSION) {
|
|
1123
1147
|
r = { ...r, width: r.width + eR };
|
|
1124
1148
|
collectBlockers(r);
|
|
1125
1149
|
improved = true;
|
|
1126
1150
|
}
|
|
1127
1151
|
collectBlockers(searchStripDown({ rect: r, bounds }));
|
|
1128
1152
|
const eD = maxExpandDown({ ...commonParams, r });
|
|
1129
|
-
if (eD >
|
|
1153
|
+
if (eD > MIN_EXPANSION) {
|
|
1130
1154
|
r = { ...r, height: r.height + eD };
|
|
1131
1155
|
collectBlockers(r);
|
|
1132
1156
|
improved = true;
|
|
1133
1157
|
}
|
|
1134
1158
|
collectBlockers(searchStripLeft({ rect: r, bounds }));
|
|
1135
1159
|
const eL = maxExpandLeft({ ...commonParams, r });
|
|
1136
|
-
if (eL >
|
|
1160
|
+
if (eL > MIN_EXPANSION) {
|
|
1137
1161
|
r = { x: r.x - eL, y: r.y, width: r.width + eL, height: r.height };
|
|
1138
1162
|
collectBlockers(r);
|
|
1139
1163
|
improved = true;
|
|
1140
1164
|
}
|
|
1141
1165
|
collectBlockers(searchStripUp({ rect: r, bounds }));
|
|
1142
1166
|
const eU = maxExpandUp({ ...commonParams, r });
|
|
1143
|
-
if (eU >
|
|
1167
|
+
if (eU > MIN_EXPANSION) {
|
|
1144
1168
|
r = { x: r.x, y: r.y - eU, width: r.width, height: r.height + eU };
|
|
1145
1169
|
collectBlockers(r);
|
|
1146
1170
|
improved = true;
|
|
@@ -6,6 +6,31 @@ import {
|
|
|
6
6
|
} from "../../utils/rectdiff-geometry"
|
|
7
7
|
import { isPointInPolygon } from "./isPointInPolygon"
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Simplify a polygon by reducing coordinate precision to avoid excessive grid cells.
|
|
11
|
+
* This rounds coordinates to a grid and removes duplicates.
|
|
12
|
+
*/
|
|
13
|
+
function simplifyPolygon(
|
|
14
|
+
polygon: Array<{ x: number; y: number }>,
|
|
15
|
+
precision: number,
|
|
16
|
+
): Array<{ x: number; y: number }> {
|
|
17
|
+
const round = (v: number) => Math.round(v / precision) * precision
|
|
18
|
+
const seen = new Set<string>()
|
|
19
|
+
const result: Array<{ x: number; y: number }> = []
|
|
20
|
+
|
|
21
|
+
for (const p of polygon) {
|
|
22
|
+
const rx = round(p.x)
|
|
23
|
+
const ry = round(p.y)
|
|
24
|
+
const key = `${rx},${ry}`
|
|
25
|
+
if (!seen.has(key)) {
|
|
26
|
+
seen.add(key)
|
|
27
|
+
result.push({ x: rx, y: ry })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
/**
|
|
10
35
|
* Decompose the empty space inside 'bounds' but outside 'polygon' into rectangles.
|
|
11
36
|
* This uses a coordinate grid approach, ideal for rectilinear polygons.
|
|
@@ -16,10 +41,21 @@ export function computeInverseRects(
|
|
|
16
41
|
): XYRect[] {
|
|
17
42
|
if (!polygon || polygon.length < 3) return []
|
|
18
43
|
|
|
44
|
+
// Simplify polygon if it has too many points to avoid O(n^2) performance issues
|
|
45
|
+
// A polygon with 350+ points (like rounded corners) creates too many grid cells
|
|
46
|
+
const MAX_POLYGON_POINTS = 100
|
|
47
|
+
const workingPolygon =
|
|
48
|
+
polygon.length > MAX_POLYGON_POINTS
|
|
49
|
+
? simplifyPolygon(
|
|
50
|
+
polygon,
|
|
51
|
+
Math.max(bounds.width, bounds.height) / MAX_POLYGON_POINTS,
|
|
52
|
+
)
|
|
53
|
+
: polygon
|
|
54
|
+
|
|
19
55
|
// 1. Collect unique sorted X and Y coordinates
|
|
20
56
|
const xs = new Set<number>([bounds.x, bounds.x + bounds.width])
|
|
21
57
|
const ys = new Set<number>([bounds.y, bounds.y + bounds.height])
|
|
22
|
-
for (const p of
|
|
58
|
+
for (const p of workingPolygon) {
|
|
23
59
|
xs.add(p.x)
|
|
24
60
|
ys.add(p.y)
|
|
25
61
|
}
|
|
@@ -286,14 +286,20 @@ export function expandRectFromSeed(params: {
|
|
|
286
286
|
for (const b of blockers) if (overlaps(r, b)) continue STRATS
|
|
287
287
|
|
|
288
288
|
// greedy expansions in 4 directions
|
|
289
|
+
// Use a minimum expansion threshold to avoid infinitesimal improvements
|
|
290
|
+
// that can occur with mixed-precision floating point coordinates
|
|
291
|
+
const MIN_EXPANSION = 1e-6
|
|
292
|
+
const MAX_ITERATIONS = 1000
|
|
289
293
|
let improved = true
|
|
290
|
-
|
|
294
|
+
let iterations = 0
|
|
295
|
+
while (improved && iterations < MAX_ITERATIONS) {
|
|
296
|
+
iterations++
|
|
291
297
|
improved = false
|
|
292
298
|
const commonParams = { bounds, blockers, maxAspect: maxAspectRatio }
|
|
293
299
|
|
|
294
300
|
collectBlockers(searchStripRight({ rect: r, bounds }))
|
|
295
301
|
const eR = maxExpandRight({ ...commonParams, r })
|
|
296
|
-
if (eR >
|
|
302
|
+
if (eR > MIN_EXPANSION) {
|
|
297
303
|
r = { ...r, width: r.width + eR }
|
|
298
304
|
collectBlockers(r)
|
|
299
305
|
improved = true
|
|
@@ -301,7 +307,7 @@ export function expandRectFromSeed(params: {
|
|
|
301
307
|
|
|
302
308
|
collectBlockers(searchStripDown({ rect: r, bounds }))
|
|
303
309
|
const eD = maxExpandDown({ ...commonParams, r })
|
|
304
|
-
if (eD >
|
|
310
|
+
if (eD > MIN_EXPANSION) {
|
|
305
311
|
r = { ...r, height: r.height + eD }
|
|
306
312
|
collectBlockers(r)
|
|
307
313
|
improved = true
|
|
@@ -309,7 +315,7 @@ export function expandRectFromSeed(params: {
|
|
|
309
315
|
|
|
310
316
|
collectBlockers(searchStripLeft({ rect: r, bounds }))
|
|
311
317
|
const eL = maxExpandLeft({ ...commonParams, r })
|
|
312
|
-
if (eL >
|
|
318
|
+
if (eL > MIN_EXPANSION) {
|
|
313
319
|
r = { x: r.x - eL, y: r.y, width: r.width + eL, height: r.height }
|
|
314
320
|
collectBlockers(r)
|
|
315
321
|
improved = true
|
|
@@ -317,7 +323,7 @@ export function expandRectFromSeed(params: {
|
|
|
317
323
|
|
|
318
324
|
collectBlockers(searchStripUp({ rect: r, bounds }))
|
|
319
325
|
const eU = maxExpandUp({ ...commonParams, r })
|
|
320
|
-
if (eU >
|
|
326
|
+
if (eU > MIN_EXPANSION) {
|
|
321
327
|
r = { x: r.x, y: r.y - eU, width: r.width, height: r.height + eU }
|
|
322
328
|
collectBlockers(r)
|
|
323
329
|
improved = true
|