@tscircuit/schematic-trace-solver 0.0.67 → 0.0.69
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 +75 -23
- package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +42 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +12 -3
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +11 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +13 -8
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts +54 -20
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example03.snap.svg +66 -66
- package/tests/examples/__snapshots__/example16.snap.svg +5 -9
- package/tests/examples/__snapshots__/example20.snap.svg +4 -7
- package/tests/examples/__snapshots__/example22.snap.svg +5 -9
- package/tests/examples/__snapshots__/example32.snap.svg +108 -93
- package/tests/repros/__snapshots__/repro-bq24074-battery-charger.snap.svg +396 -0
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +3 -3
- package/tests/repros/assets/repro-bq24074-battery-charger.input.json +219 -0
- package/tests/repros/repro-bq24074-battery-charger.test.ts +18 -0
- package/tests/repros/repro130-bq27441-fuel-gauge-trace-through-c1.test.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -2799,19 +2799,32 @@ var detectTraceLabelOverlap = ({
|
|
|
2799
2799
|
};
|
|
2800
2800
|
|
|
2801
2801
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/violation.ts
|
|
2802
|
-
var findTraceViolationZone = (
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2802
|
+
var findTraceViolationZone = ({
|
|
2803
|
+
path,
|
|
2804
|
+
labelBounds,
|
|
2805
|
+
paddedBounds = labelBounds
|
|
2806
|
+
}) => {
|
|
2807
|
+
let firstCollidingSeg = -1;
|
|
2808
|
+
let lastCollidingSeg = -1;
|
|
2809
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
2810
|
+
if (segmentIntersectsRect2(path[i], path[i + 1], labelBounds)) {
|
|
2811
|
+
if (firstCollidingSeg === -1) firstCollidingSeg = i;
|
|
2812
|
+
lastCollidingSeg = i;
|
|
2812
2813
|
}
|
|
2813
2814
|
}
|
|
2814
|
-
|
|
2815
|
+
if (firstCollidingSeg === -1) {
|
|
2816
|
+
return { firstInsideIndex: -1, lastInsideIndex: -1 };
|
|
2817
|
+
}
|
|
2818
|
+
const isInsidePadded = (p) => p.x > paddedBounds.minX && p.x < paddedBounds.maxX && p.y > paddedBounds.minY && p.y < paddedBounds.maxY;
|
|
2819
|
+
let entryIndex = firstCollidingSeg;
|
|
2820
|
+
while (entryIndex > 0 && isInsidePadded(path[entryIndex])) {
|
|
2821
|
+
entryIndex--;
|
|
2822
|
+
}
|
|
2823
|
+
let exitIndex = lastCollidingSeg + 1;
|
|
2824
|
+
while (exitIndex < path.length - 1 && isInsidePadded(path[exitIndex])) {
|
|
2825
|
+
exitIndex++;
|
|
2826
|
+
}
|
|
2827
|
+
return { firstInsideIndex: entryIndex + 1, lastInsideIndex: exitIndex - 1 };
|
|
2815
2828
|
};
|
|
2816
2829
|
|
|
2817
2830
|
// lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts
|
|
@@ -2917,20 +2930,21 @@ var generateFourPointDetourCandidates = ({
|
|
|
2917
2930
|
}
|
|
2918
2931
|
exitPoint = initialTrace.tracePath[exitIndex];
|
|
2919
2932
|
} else {
|
|
2920
|
-
let
|
|
2933
|
+
let firstCollidingSeg = -1;
|
|
2934
|
+
let lastCollidingSeg = -1;
|
|
2921
2935
|
for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
|
|
2922
2936
|
if (segmentIntersectsRect(
|
|
2923
2937
|
initialTrace.tracePath[i],
|
|
2924
2938
|
initialTrace.tracePath[i + 1],
|
|
2925
|
-
{ ...
|
|
2939
|
+
{ ...labelBounds, chipId: "temp-obstacle" }
|
|
2926
2940
|
)) {
|
|
2927
|
-
|
|
2928
|
-
|
|
2941
|
+
if (firstCollidingSeg === -1) firstCollidingSeg = i;
|
|
2942
|
+
lastCollidingSeg = i;
|
|
2929
2943
|
}
|
|
2930
2944
|
}
|
|
2931
|
-
if (
|
|
2932
|
-
entryIndex =
|
|
2933
|
-
exitIndex =
|
|
2945
|
+
if (firstCollidingSeg === -1) return [];
|
|
2946
|
+
entryIndex = firstCollidingSeg;
|
|
2947
|
+
exitIndex = lastCollidingSeg + 1;
|
|
2934
2948
|
entryPoint = initialTrace.tracePath[entryIndex];
|
|
2935
2949
|
exitPoint = initialTrace.tracePath[exitIndex];
|
|
2936
2950
|
}
|
|
@@ -3039,10 +3053,18 @@ var generateRerouteCandidates = ({
|
|
|
3039
3053
|
paddingBuffer,
|
|
3040
3054
|
detourCount
|
|
3041
3055
|
});
|
|
3042
|
-
const
|
|
3043
|
-
|
|
3044
|
-
labelBounds
|
|
3045
|
-
|
|
3056
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer;
|
|
3057
|
+
const paddedLabelBounds = {
|
|
3058
|
+
minX: labelBounds.minX - effectivePadding,
|
|
3059
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
3060
|
+
minY: labelBounds.minY - effectivePadding,
|
|
3061
|
+
maxY: labelBounds.maxY + effectivePadding
|
|
3062
|
+
};
|
|
3063
|
+
const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone({
|
|
3064
|
+
path: initialTrace.tracePath,
|
|
3065
|
+
labelBounds,
|
|
3066
|
+
paddedBounds: paddedLabelBounds
|
|
3067
|
+
});
|
|
3046
3068
|
const snipReconnectCandidates = generateSnipAndReconnectCandidates({
|
|
3047
3069
|
initialTrace,
|
|
3048
3070
|
firstInsideIndex,
|
|
@@ -3131,7 +3153,11 @@ var SingleOverlapSolver = class extends BaseSolver {
|
|
|
3131
3153
|
this._tried++;
|
|
3132
3154
|
const nextCandidatePath = this.queuedCandidatePaths.shift();
|
|
3133
3155
|
const simplifiedPath = simplifyPath(nextCandidatePath);
|
|
3134
|
-
|
|
3156
|
+
const stillOverlapsLabel = detectTraceLabelOverlap({
|
|
3157
|
+
traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
|
|
3158
|
+
netLabels: [this.label]
|
|
3159
|
+
}).length > 0;
|
|
3160
|
+
if (!stillOverlapsLabel && !isPathCollidingWithObstacles(simplifiedPath, this.obstacles) && !doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)) {
|
|
3135
3161
|
this.solvedTracePath = simplifiedPath;
|
|
3136
3162
|
this.solved = true;
|
|
3137
3163
|
}
|
|
@@ -4550,6 +4576,7 @@ var visualizeCollision = (collisionInfo) => {
|
|
|
4550
4576
|
// lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts
|
|
4551
4577
|
var UntangleTraceSubsolver = class extends BaseSolver {
|
|
4552
4578
|
input;
|
|
4579
|
+
chipObstacleSpatialIndex;
|
|
4553
4580
|
lShapesToProcess = [];
|
|
4554
4581
|
visualizationMode = "l_shapes";
|
|
4555
4582
|
currentLShape = null;
|
|
@@ -4570,6 +4597,10 @@ var UntangleTraceSubsolver = class extends BaseSolver {
|
|
|
4570
4597
|
super();
|
|
4571
4598
|
this.input = solverInput;
|
|
4572
4599
|
this.visualizationMode = "l_shapes";
|
|
4600
|
+
this.chipObstacleSpatialIndex = this.input.inputProblem._chipObstacleSpatialIndex ?? new ChipObstacleSpatialIndex(this.input.inputProblem.chips);
|
|
4601
|
+
if (!this.input.inputProblem._chipObstacleSpatialIndex) {
|
|
4602
|
+
this.input.inputProblem._chipObstacleSpatialIndex = this.chipObstacleSpatialIndex;
|
|
4603
|
+
}
|
|
4573
4604
|
for (const trace of this.input.allTraces) {
|
|
4574
4605
|
const lShapes = findAllLShapedTurns(trace.tracePath);
|
|
4575
4606
|
this.lShapesToProcess.push(
|
|
@@ -4696,6 +4727,12 @@ var UntangleTraceSubsolver = class extends BaseSolver {
|
|
|
4696
4727
|
this.input.allTraces,
|
|
4697
4728
|
this.currentLShape.traceId
|
|
4698
4729
|
);
|
|
4730
|
+
if (!collisionResult?.isColliding && this._doesCandidateCrossChip(currentCandidate)) {
|
|
4731
|
+
this.lastCollision = null;
|
|
4732
|
+
this.collidingCandidate = currentCandidate;
|
|
4733
|
+
this.currentCandidateIndex++;
|
|
4734
|
+
return;
|
|
4735
|
+
}
|
|
4699
4736
|
if (!collisionResult?.isColliding) {
|
|
4700
4737
|
this.bestRouteFound = currentCandidate;
|
|
4701
4738
|
this.lastCollision = null;
|
|
@@ -4706,6 +4743,21 @@ var UntangleTraceSubsolver = class extends BaseSolver {
|
|
|
4706
4743
|
this.currentCandidateIndex++;
|
|
4707
4744
|
}
|
|
4708
4745
|
}
|
|
4746
|
+
/**
|
|
4747
|
+
* Returns true if any segment of the candidate reroute passes through a
|
|
4748
|
+
* schematic component (chip) body.
|
|
4749
|
+
*/
|
|
4750
|
+
_doesCandidateCrossChip(candidate) {
|
|
4751
|
+
for (let i = 0; i < candidate.length - 1; i++) {
|
|
4752
|
+
if (this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip([
|
|
4753
|
+
candidate[i],
|
|
4754
|
+
candidate[i + 1]
|
|
4755
|
+
])) {
|
|
4756
|
+
return true;
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
return false;
|
|
4760
|
+
}
|
|
4709
4761
|
_applyBestRoute(bestRoute) {
|
|
4710
4762
|
this.bestRoute = bestRoute;
|
|
4711
4763
|
this.collidingCandidate = null;
|
|
@@ -2,6 +2,7 @@ import { BaseSolver } from "../../BaseSolver/BaseSolver"
|
|
|
2
2
|
import type { InputProblem } from "../../../types/InputProblem"
|
|
3
3
|
import type { SolvedTracePath } from "../../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
4
|
import type { NetLabelPlacement } from "../../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
|
+
import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
|
|
5
6
|
|
|
6
7
|
import { findAllLShapedTurns, type LShape } from "./findAllLShapedTurns"
|
|
7
8
|
import { getTraceObstacles } from "./getTraceObstacles"
|
|
@@ -57,6 +58,7 @@ type VisualizationMode =
|
|
|
57
58
|
*/
|
|
58
59
|
export class UntangleTraceSubsolver extends BaseSolver {
|
|
59
60
|
private input: UntangleTraceSubsolverInput
|
|
61
|
+
private chipObstacleSpatialIndex: ChipObstacleSpatialIndex
|
|
60
62
|
private lShapesToProcess: LShape[] = []
|
|
61
63
|
private visualizationMode: VisualizationMode = "l_shapes"
|
|
62
64
|
|
|
@@ -86,6 +88,14 @@ export class UntangleTraceSubsolver extends BaseSolver {
|
|
|
86
88
|
this.input = solverInput
|
|
87
89
|
this.visualizationMode = "l_shapes"
|
|
88
90
|
|
|
91
|
+
this.chipObstacleSpatialIndex =
|
|
92
|
+
this.input.inputProblem._chipObstacleSpatialIndex ??
|
|
93
|
+
new ChipObstacleSpatialIndex(this.input.inputProblem.chips)
|
|
94
|
+
if (!this.input.inputProblem._chipObstacleSpatialIndex) {
|
|
95
|
+
this.input.inputProblem._chipObstacleSpatialIndex =
|
|
96
|
+
this.chipObstacleSpatialIndex
|
|
97
|
+
}
|
|
98
|
+
|
|
89
99
|
for (const trace of this.input.allTraces) {
|
|
90
100
|
const lShapes = findAllLShapedTurns(trace.tracePath)
|
|
91
101
|
this.lShapesToProcess.push(
|
|
@@ -232,6 +242,20 @@ export class UntangleTraceSubsolver extends BaseSolver {
|
|
|
232
242
|
this.currentLShape!.traceId,
|
|
233
243
|
)
|
|
234
244
|
|
|
245
|
+
// Untangling must never move a trace through a component body. The candidate
|
|
246
|
+
// only covers the rerouted corner (not the pin-terminal segments), so reject
|
|
247
|
+
// any candidate that crosses a chip; the original (component-clear) path is
|
|
248
|
+
// kept instead, so this stage only ever improves or leaves the trace valid.
|
|
249
|
+
if (
|
|
250
|
+
!collisionResult?.isColliding &&
|
|
251
|
+
this._doesCandidateCrossChip(currentCandidate)
|
|
252
|
+
) {
|
|
253
|
+
this.lastCollision = null
|
|
254
|
+
this.collidingCandidate = currentCandidate
|
|
255
|
+
this.currentCandidateIndex++
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
|
|
235
259
|
if (!collisionResult?.isColliding) {
|
|
236
260
|
this.bestRouteFound = currentCandidate
|
|
237
261
|
this.lastCollision = null
|
|
@@ -243,6 +267,24 @@ export class UntangleTraceSubsolver extends BaseSolver {
|
|
|
243
267
|
}
|
|
244
268
|
}
|
|
245
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Returns true if any segment of the candidate reroute passes through a
|
|
272
|
+
* schematic component (chip) body.
|
|
273
|
+
*/
|
|
274
|
+
private _doesCandidateCrossChip(candidate: Point[]): boolean {
|
|
275
|
+
for (let i = 0; i < candidate.length - 1; i++) {
|
|
276
|
+
if (
|
|
277
|
+
this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip([
|
|
278
|
+
candidate[i]!,
|
|
279
|
+
candidate[i + 1]!,
|
|
280
|
+
])
|
|
281
|
+
) {
|
|
282
|
+
return true
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return false
|
|
286
|
+
}
|
|
287
|
+
|
|
246
288
|
private _applyBestRoute(bestRoute: Point[]) {
|
|
247
289
|
this.bestRoute = bestRoute
|
|
248
290
|
this.collidingCandidate = null
|
|
@@ -55,10 +55,19 @@ export const generateRerouteCandidates = ({
|
|
|
55
55
|
detourCount,
|
|
56
56
|
})
|
|
57
57
|
|
|
58
|
-
const
|
|
59
|
-
|
|
58
|
+
const effectivePadding = paddingBuffer + detourCount * paddingBuffer
|
|
59
|
+
const paddedLabelBounds = {
|
|
60
|
+
minX: labelBounds.minX - effectivePadding,
|
|
61
|
+
maxX: labelBounds.maxX + effectivePadding,
|
|
62
|
+
minY: labelBounds.minY - effectivePadding,
|
|
63
|
+
maxY: labelBounds.maxY + effectivePadding,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { firstInsideIndex, lastInsideIndex } = findTraceViolationZone({
|
|
67
|
+
path: initialTrace.tracePath,
|
|
60
68
|
labelBounds,
|
|
61
|
-
|
|
69
|
+
paddedBounds: paddedLabelBounds,
|
|
70
|
+
})
|
|
62
71
|
|
|
63
72
|
const snipReconnectCandidates = generateSnipAndReconnectCandidates({
|
|
64
73
|
initialTrace,
|
|
@@ -9,6 +9,7 @@ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/Schemati
|
|
|
9
9
|
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
10
10
|
import { generateRerouteCandidates } from "../../rerouteCollidingTrace"
|
|
11
11
|
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
12
|
+
import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
|
|
12
13
|
|
|
13
14
|
interface SingleOverlapSolverInput {
|
|
14
15
|
trace: SolvedTracePath
|
|
@@ -141,7 +142,17 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
141
142
|
const nextCandidatePath = this.queuedCandidatePaths.shift()!
|
|
142
143
|
const simplifiedPath = simplifyPath(nextCandidatePath)
|
|
143
144
|
|
|
145
|
+
// A candidate is only valid if it actually clears the label it is meant to
|
|
146
|
+
// avoid. Without this check a "detour" that still grazes the label (e.g. one
|
|
147
|
+
// built around the wrong segment) would be accepted as solved.
|
|
148
|
+
const stillOverlapsLabel =
|
|
149
|
+
detectTraceLabelOverlap({
|
|
150
|
+
traces: [{ ...this.initialTrace, tracePath: simplifiedPath }],
|
|
151
|
+
netLabels: [this.label],
|
|
152
|
+
}).length > 0
|
|
153
|
+
|
|
144
154
|
if (
|
|
155
|
+
!stillOverlapsLabel &&
|
|
145
156
|
!isPathCollidingWithObstacles(simplifiedPath, this.obstacles) &&
|
|
146
157
|
!doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)
|
|
147
158
|
) {
|
|
@@ -71,25 +71,30 @@ export const generateFourPointDetourCandidates = ({
|
|
|
71
71
|
}
|
|
72
72
|
exitPoint = initialTrace.tracePath[exitIndex]
|
|
73
73
|
} else {
|
|
74
|
-
// STRATEGY 1: Single Label -
|
|
75
|
-
|
|
74
|
+
// STRATEGY 1: Single Label - reroute around the contiguous span of segments
|
|
75
|
+
// that actually overlap the label. Detection uses the *unpadded* bounds so
|
|
76
|
+
// a short pin stub that merely pokes into the padding zone is not mistaken
|
|
77
|
+
// for the real crossing (which would leave the offending segment in place
|
|
78
|
+
// and create a useless loop).
|
|
79
|
+
let firstCollidingSeg = -1
|
|
80
|
+
let lastCollidingSeg = -1
|
|
76
81
|
for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
|
|
77
82
|
if (
|
|
78
83
|
segmentIntersectsRect(
|
|
79
84
|
initialTrace.tracePath[i],
|
|
80
85
|
initialTrace.tracePath[i + 1],
|
|
81
|
-
{ ...
|
|
86
|
+
{ ...labelBounds, chipId: "temp-obstacle" },
|
|
82
87
|
)
|
|
83
88
|
) {
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
if (firstCollidingSeg === -1) firstCollidingSeg = i
|
|
90
|
+
lastCollidingSeg = i
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
|
|
89
|
-
if (
|
|
94
|
+
if (firstCollidingSeg === -1) return []
|
|
90
95
|
|
|
91
|
-
entryIndex =
|
|
92
|
-
exitIndex =
|
|
96
|
+
entryIndex = firstCollidingSeg
|
|
97
|
+
exitIndex = lastCollidingSeg + 1
|
|
93
98
|
entryPoint = initialTrace.tracePath[entryIndex]
|
|
94
99
|
exitPoint = initialTrace.tracePath[exitIndex]
|
|
95
100
|
}
|
|
@@ -1,25 +1,59 @@
|
|
|
1
|
-
import type { Point } from "@tscircuit/math-utils"
|
|
1
|
+
import type { Point, Bounds } from "@tscircuit/math-utils"
|
|
2
|
+
import { segmentIntersectsRect } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Locates the portion of a trace that must be rerouted to clear a label.
|
|
6
|
+
*
|
|
7
|
+
* Detection is segment-based against the (unpadded) label bounds so it also
|
|
8
|
+
* catches traces that cross the label edge-to-edge, or run *along* one of its
|
|
9
|
+
* edges, without ever placing a vertex strictly inside it (the previous
|
|
10
|
+
* vertex-only test missed both cases).
|
|
11
|
+
*
|
|
12
|
+
* The returned range describes the vertices to remove: the reroute reconnects
|
|
13
|
+
* `path[firstInsideIndex - 1]` to `path[lastInsideIndex + 1]`. Those two anchor
|
|
14
|
+
* vertices are pushed outward until they sit outside the padded label so the
|
|
15
|
+
* detour has room to pivot without immediately re-entering it.
|
|
16
|
+
*/
|
|
17
|
+
export const findTraceViolationZone = ({
|
|
18
|
+
path,
|
|
19
|
+
labelBounds,
|
|
20
|
+
paddedBounds = labelBounds,
|
|
21
|
+
}: {
|
|
22
|
+
path: Point[]
|
|
23
|
+
labelBounds: Bounds
|
|
24
|
+
paddedBounds?: Bounds
|
|
25
|
+
}) => {
|
|
26
|
+
let firstCollidingSeg = -1
|
|
27
|
+
let lastCollidingSeg = -1
|
|
28
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
29
|
+
if (segmentIntersectsRect(path[i]!, path[i + 1]!, labelBounds)) {
|
|
30
|
+
if (firstCollidingSeg === -1) firstCollidingSeg = i
|
|
31
|
+
lastCollidingSeg = i
|
|
32
|
+
}
|
|
33
|
+
}
|
|
12
34
|
|
|
13
|
-
|
|
14
|
-
|
|
35
|
+
if (firstCollidingSeg === -1) {
|
|
36
|
+
return { firstInsideIndex: -1, lastInsideIndex: -1 }
|
|
37
|
+
}
|
|
15
38
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
39
|
+
const isInsidePadded = (p: Point) =>
|
|
40
|
+
p.x > paddedBounds.minX &&
|
|
41
|
+
p.x < paddedBounds.maxX &&
|
|
42
|
+
p.y > paddedBounds.minY &&
|
|
43
|
+
p.y < paddedBounds.maxY
|
|
44
|
+
|
|
45
|
+
// Anchor before the crossing: walk back to the first vertex clear of the
|
|
46
|
+
// padded label.
|
|
47
|
+
let entryIndex = firstCollidingSeg
|
|
48
|
+
while (entryIndex > 0 && isInsidePadded(path[entryIndex]!)) {
|
|
49
|
+
entryIndex--
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Anchor after the crossing.
|
|
53
|
+
let exitIndex = lastCollidingSeg + 1
|
|
54
|
+
while (exitIndex < path.length - 1 && isInsidePadded(path[exitIndex]!)) {
|
|
55
|
+
exitIndex++
|
|
23
56
|
}
|
|
24
|
-
|
|
57
|
+
|
|
58
|
+
return { firstInsideIndex: entryIndex + 1, lastInsideIndex: exitIndex - 1 }
|
|
25
59
|
}
|