@tscircuit/schematic-trace-solver 0.0.43 → 0.0.45
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.d.ts +17 -3
- package/dist/index.js +809 -86
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +0 -1
- package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +83 -36
- package/lib/solvers/TraceCleanupSolver/hasCollisions.ts +16 -4
- package/lib/solvers/TraceCleanupSolver/is4PointRectangle.ts +17 -0
- package/lib/solvers/TraceCleanupSolver/isSegmentAnEndpointSegment.ts +36 -0
- package/lib/solvers/TraceCleanupSolver/mergeGraphicsObjects.ts +28 -0
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +18 -1
- package/lib/solvers/TraceCleanupSolver/recognizeStairStepPattern.ts +56 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/UntangleTraceSubsolver.ts +370 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/findAllLShapedTurns.ts +48 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/findIntersectionsWithObstacles.ts +36 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateLShapeRerouteCandidates.ts +107 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/generateRectangleCandidates.ts +55 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/getTraceObstacles.ts +25 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/isPathColliding.ts +58 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCandidates.ts +33 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeCollision.ts +20 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeIntersectionPoints.ts +26 -0
- package/lib/solvers/TraceCleanupSolver/sub-solver/visualizeLSapes.ts +33 -0
- package/lib/solvers/TraceCleanupSolver/turnMinimization.ts +50 -56
- package/lib/solvers/TraceCleanupSolver/visualizeTightRectangle.ts +24 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +83 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example16.snap.svg +4 -4
- package/tests/examples/__snapshots__/example29.snap.svg +7 -7
|
@@ -200,7 +200,6 @@ export class SchematicTracePipelineSolver extends BaseSolver {
|
|
|
200
200
|
{
|
|
201
201
|
inputProblem: instance.inputProblem,
|
|
202
202
|
allTraces: traces,
|
|
203
|
-
targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
|
|
204
203
|
allLabelPlacements: labelMergingOutput.netLabelPlacements,
|
|
205
204
|
mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
|
|
206
205
|
paddingBuffer: 0.1,
|
|
@@ -7,74 +7,120 @@ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/Sche
|
|
|
7
7
|
import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
|
|
8
8
|
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Defines the input structure for the TraceCleanupSolver.
|
|
12
|
+
*/
|
|
10
13
|
interface TraceCleanupSolverInput {
|
|
11
14
|
inputProblem: InputProblem
|
|
12
15
|
allTraces: SolvedTracePath[]
|
|
13
|
-
targetTraceIds: Set<string>
|
|
14
16
|
allLabelPlacements: NetLabelPlacement[]
|
|
15
17
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
16
18
|
paddingBuffer: number
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver"
|
|
22
|
+
import { is4PointRectangle } from "./is4PointRectangle"
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
|
-
*
|
|
23
|
-
|
|
25
|
+
* Represents the different stages or steps within the trace cleanup pipeline.
|
|
26
|
+
*/
|
|
27
|
+
type PipelineStep =
|
|
28
|
+
| "minimizing_turns"
|
|
29
|
+
| "balancing_l_shapes"
|
|
30
|
+
| "untangling_traces"
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The TraceCleanupSolver is responsible for improving the aesthetics and readability of schematic traces.
|
|
34
|
+
* It operates in a multi-step pipeline:
|
|
35
|
+
* 1. **Untangling Traces**: It first attempts to untangle any overlapping or highly convoluted traces using a sub-solver.
|
|
36
|
+
* 2. **Minimizing Turns**: After untangling, it iterates through each trace to minimize the number of turns, simplifying their paths.
|
|
37
|
+
* 3. **Balancing L-Shapes**: Finally, it balances L-shaped trace segments to create more visually appealing and consistent layouts.
|
|
38
|
+
* The solver processes traces one by one, applying these cleanup steps sequentially to refine the overall trace layout.
|
|
24
39
|
*/
|
|
25
40
|
export class TraceCleanupSolver extends BaseSolver {
|
|
26
41
|
private input: TraceCleanupSolverInput
|
|
27
42
|
private outputTraces: SolvedTracePath[]
|
|
28
43
|
private traceIdQueue: string[]
|
|
29
44
|
private tracesMap: Map<string, SolvedTracePath>
|
|
30
|
-
private pipelineStep: PipelineStep = "
|
|
45
|
+
private pipelineStep: PipelineStep = "untangling_traces"
|
|
31
46
|
private activeTraceId: string | null = null // New property
|
|
47
|
+
override activeSubSolver: BaseSolver | null = null
|
|
32
48
|
|
|
33
49
|
constructor(solverInput: TraceCleanupSolverInput) {
|
|
34
50
|
super()
|
|
35
51
|
this.input = solverInput
|
|
36
52
|
this.outputTraces = [...solverInput.allTraces]
|
|
37
53
|
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]))
|
|
38
|
-
this.traceIdQueue = Array.from(
|
|
54
|
+
this.traceIdQueue = Array.from(
|
|
55
|
+
solverInput.allTraces.map((e) => e.mspPairId),
|
|
56
|
+
)
|
|
39
57
|
}
|
|
40
58
|
|
|
41
59
|
override _step() {
|
|
42
|
-
if (
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
|
|
60
|
+
if (this.activeSubSolver) {
|
|
61
|
+
this.activeSubSolver.step()
|
|
62
|
+
if (this.activeSubSolver.solved) {
|
|
63
|
+
const output = (
|
|
64
|
+
this.activeSubSolver as UntangleTraceSubsolver
|
|
65
|
+
).getOutput()
|
|
66
|
+
this.outputTraces = output.traces
|
|
67
|
+
this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]))
|
|
68
|
+
this.activeSubSolver = null
|
|
69
|
+
this.pipelineStep = "minimizing_turns"
|
|
70
|
+
} else if (this.activeSubSolver.failed) {
|
|
71
|
+
this.activeSubSolver = null
|
|
72
|
+
this.pipelineStep = "minimizing_turns"
|
|
73
|
+
}
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
switch (this.pipelineStep) {
|
|
78
|
+
case "untangling_traces":
|
|
79
|
+
this._runUntangleTracesStep()
|
|
80
|
+
break
|
|
81
|
+
case "minimizing_turns":
|
|
82
|
+
this._runMinimizeTurnsStep()
|
|
83
|
+
break
|
|
84
|
+
case "balancing_l_shapes":
|
|
85
|
+
this._runBalanceLShapesStep()
|
|
86
|
+
break
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private _runUntangleTracesStep() {
|
|
91
|
+
this.activeSubSolver = new UntangleTraceSubsolver({
|
|
92
|
+
...this.input,
|
|
93
|
+
allTraces: Array.from(this.tracesMap.values()),
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private _runMinimizeTurnsStep() {
|
|
98
|
+
if (this.traceIdQueue.length === 0) {
|
|
46
99
|
this.pipelineStep = "balancing_l_shapes"
|
|
47
|
-
this.traceIdQueue = Array.from(
|
|
100
|
+
this.traceIdQueue = Array.from(
|
|
101
|
+
this.input.allTraces.map((e) => e.mspPairId),
|
|
102
|
+
)
|
|
103
|
+
return
|
|
48
104
|
}
|
|
49
105
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
106
|
+
this._processTrace("minimizing_turns")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private _runBalanceLShapesStep() {
|
|
110
|
+
if (this.traceIdQueue.length === 0) {
|
|
54
111
|
this.solved = true
|
|
55
112
|
return
|
|
56
113
|
}
|
|
57
114
|
|
|
115
|
+
this._processTrace("balancing_l_shapes")
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private _processTrace(step: "minimizing_turns" | "balancing_l_shapes") {
|
|
58
119
|
const targetMspConnectionPairId = this.traceIdQueue.shift()!
|
|
59
|
-
this.activeTraceId = targetMspConnectionPairId
|
|
120
|
+
this.activeTraceId = targetMspConnectionPairId
|
|
60
121
|
const originalTrace = this.tracesMap.get(targetMspConnectionPairId)!
|
|
61
122
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Skip if the trace is a perfect rectangle
|
|
65
|
-
const is4PointRectangle = (path: typeof tracePath): boolean => {
|
|
66
|
-
if (path.length !== 4) return false
|
|
67
|
-
const [p0, p1, p2, p3] = path
|
|
68
|
-
// H-V-H "C" shape
|
|
69
|
-
const isHVHC =
|
|
70
|
-
p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x
|
|
71
|
-
// V-H-V "C" shape
|
|
72
|
-
const isVHVC =
|
|
73
|
-
p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y
|
|
74
|
-
return isHVHC || isVHVC
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (is4PointRectangle(tracePath)) {
|
|
123
|
+
if (is4PointRectangle(originalTrace.tracePath)) {
|
|
78
124
|
return
|
|
79
125
|
}
|
|
80
126
|
|
|
@@ -82,7 +128,7 @@ export class TraceCleanupSolver extends BaseSolver {
|
|
|
82
128
|
|
|
83
129
|
let updatedTrace: SolvedTracePath
|
|
84
130
|
|
|
85
|
-
if (
|
|
131
|
+
if (step === "minimizing_turns") {
|
|
86
132
|
updatedTrace = minimizeTurnsWithFilteredLabels({
|
|
87
133
|
...this.input,
|
|
88
134
|
targetMspConnectionPairId,
|
|
@@ -96,10 +142,7 @@ export class TraceCleanupSolver extends BaseSolver {
|
|
|
96
142
|
})
|
|
97
143
|
}
|
|
98
144
|
|
|
99
|
-
// Update the state in the central map
|
|
100
145
|
this.tracesMap.set(targetMspConnectionPairId, updatedTrace)
|
|
101
|
-
|
|
102
|
-
// Update the output for visualization
|
|
103
146
|
this.outputTraces = Array.from(this.tracesMap.values())
|
|
104
147
|
}
|
|
105
148
|
|
|
@@ -110,6 +153,10 @@ export class TraceCleanupSolver extends BaseSolver {
|
|
|
110
153
|
}
|
|
111
154
|
|
|
112
155
|
override visualize(): GraphicsObject {
|
|
156
|
+
if (this.activeSubSolver) {
|
|
157
|
+
return this.activeSubSolver.visualize()
|
|
158
|
+
}
|
|
159
|
+
|
|
113
160
|
const graphics = visualizeInputProblem(this.input.inputProblem, {
|
|
114
161
|
chipAlpha: 0.1,
|
|
115
162
|
connectionAlpha: 0.1,
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import type { Point } from "
|
|
2
|
-
import {
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { segmentToBoxMinDistance } from "@tscircuit/math-utils"
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a given path (series of segments) collides with any of the provided obstacles.
|
|
6
|
+
* It iterates through each segment of the path and checks for intersection with each obstacle.
|
|
7
|
+
*/
|
|
4
8
|
export const hasCollisions = (
|
|
5
9
|
pathSegments: Point[],
|
|
6
|
-
obstacles:
|
|
10
|
+
obstacles: Array<{ minX: number; maxX: number; minY: number; maxY: number }>,
|
|
7
11
|
): boolean => {
|
|
8
12
|
// Check each segment of the path
|
|
9
13
|
for (let i = 0; i < pathSegments.length - 1; i++) {
|
|
@@ -12,7 +16,15 @@ export const hasCollisions = (
|
|
|
12
16
|
|
|
13
17
|
// Check collision with each obstacle
|
|
14
18
|
for (const obstacle of obstacles) {
|
|
15
|
-
|
|
19
|
+
const box = {
|
|
20
|
+
center: {
|
|
21
|
+
x: obstacle.minX + (obstacle.maxX - obstacle.minX) / 2,
|
|
22
|
+
y: obstacle.minY + (obstacle.maxY - obstacle.minY) / 2,
|
|
23
|
+
},
|
|
24
|
+
width: obstacle.maxX - obstacle.minX,
|
|
25
|
+
height: obstacle.maxY - obstacle.minY,
|
|
26
|
+
}
|
|
27
|
+
if (segmentToBoxMinDistance(p1, p2, box) <= 0) {
|
|
16
28
|
return true
|
|
17
29
|
}
|
|
18
30
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a given path of four points forms a rectangle with horizontal and vertical segments.
|
|
5
|
+
* It verifies if the path forms either an H-V-H "C" shape or a V-H-V "C" shape.
|
|
6
|
+
*/
|
|
7
|
+
export const is4PointRectangle = (path: Point[]): boolean => {
|
|
8
|
+
if (path.length !== 4) return false
|
|
9
|
+
const [p0, p1, p2, p3] = path
|
|
10
|
+
// H-V-H "C" shape
|
|
11
|
+
const isHVHC =
|
|
12
|
+
p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x
|
|
13
|
+
// V-H-V "C" shape
|
|
14
|
+
const isVHVC =
|
|
15
|
+
p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y
|
|
16
|
+
return isHVHC || isVHVC
|
|
17
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Point } from "graphics-debug"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a given segment (p1-p2) is either the first or the last segment of an original path.
|
|
5
|
+
* This is useful for identifying segments that are at the extremities of a trace.
|
|
6
|
+
*/
|
|
7
|
+
export const isSegmentAnEndpointSegment = (
|
|
8
|
+
p1: Point,
|
|
9
|
+
p2: Point,
|
|
10
|
+
originalPath: Point[],
|
|
11
|
+
): boolean => {
|
|
12
|
+
if (originalPath.length < 2) return false
|
|
13
|
+
|
|
14
|
+
const originalStart = originalPath[0]
|
|
15
|
+
const originalEnd = originalPath[originalPath.length - 1]
|
|
16
|
+
|
|
17
|
+
// Check if p1-p2 is the first segment of the original path
|
|
18
|
+
if (
|
|
19
|
+
p1.x === originalStart.x &&
|
|
20
|
+
p1.y === originalStart.y &&
|
|
21
|
+
p2.x === originalPath[1].x &&
|
|
22
|
+
p2.y === originalPath[1].y
|
|
23
|
+
) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
// Check if p1-p2 is the last segment of the original path
|
|
27
|
+
if (
|
|
28
|
+
p1.x === originalPath[originalPath.length - 2].x &&
|
|
29
|
+
p1.y === originalPath[originalPath.length - 2].y &&
|
|
30
|
+
p2.x === originalEnd.x &&
|
|
31
|
+
p2.y === originalEnd.y
|
|
32
|
+
) {
|
|
33
|
+
return true
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Merges multiple GraphicsObject instances into a single GraphicsObject.
|
|
5
|
+
* It combines all lines, points, rectangles, circles, and texts from the input objects.
|
|
6
|
+
*/
|
|
7
|
+
export const mergeGraphicsObjects = (
|
|
8
|
+
objects: (GraphicsObject | undefined)[],
|
|
9
|
+
): GraphicsObject => {
|
|
10
|
+
const merged: GraphicsObject = {
|
|
11
|
+
lines: [],
|
|
12
|
+
points: [],
|
|
13
|
+
rects: [],
|
|
14
|
+
circles: [],
|
|
15
|
+
texts: [],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
for (const obj of objects) {
|
|
19
|
+
if (!obj) continue
|
|
20
|
+
if (obj.lines) merged.lines!.push(...obj.lines)
|
|
21
|
+
if (obj.points) merged.points!.push(...obj.points)
|
|
22
|
+
if (obj.rects) merged.rects!.push(...obj.rects)
|
|
23
|
+
if (obj.circles) merged.circles!.push(...obj.circles)
|
|
24
|
+
if (obj.texts) merged.texts!.push(...obj.texts)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return merged
|
|
28
|
+
}
|
|
@@ -4,6 +4,13 @@ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/Sche
|
|
|
4
4
|
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
5
5
|
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Minimizes the turns of a target trace while considering other traces and labels as obstacles.
|
|
9
|
+
* This function first identifies the target trace and separates it from other traces, which are then treated as obstacles.
|
|
10
|
+
* It also filters out labels that belong to the target trace's net, so they don't act as obstacles.
|
|
11
|
+
* The function then combines static obstacles (from the input problem) with the other traces and filtered labels to create a comprehensive set of obstacles.
|
|
12
|
+
* Finally, it uses a turn minimization algorithm to find a new path for the target trace that avoids these combined obstacles.
|
|
13
|
+
*/
|
|
7
14
|
export const minimizeTurnsWithFilteredLabels = ({
|
|
8
15
|
targetMspConnectionPairId,
|
|
9
16
|
traces,
|
|
@@ -44,7 +51,16 @@ export const minimizeTurnsWithFilteredLabels = ({
|
|
|
44
51
|
}),
|
|
45
52
|
)
|
|
46
53
|
|
|
47
|
-
const
|
|
54
|
+
const staticObstaclesRaw = getObstacleRects(inputProblem)
|
|
55
|
+
const PADDING = 0.01
|
|
56
|
+
const staticObstacles = staticObstaclesRaw.map((obs) => ({
|
|
57
|
+
...obs,
|
|
58
|
+
minX: obs.minX - PADDING,
|
|
59
|
+
minY: obs.minY - PADDING,
|
|
60
|
+
maxX: obs.maxX + PADDING,
|
|
61
|
+
maxY: obs.maxY + PADDING,
|
|
62
|
+
}))
|
|
63
|
+
|
|
48
64
|
const combinedObstacles = [...staticObstacles, ...traceObstacles]
|
|
49
65
|
|
|
50
66
|
const originalPath = targetTrace.tracePath
|
|
@@ -67,6 +83,7 @@ export const minimizeTurnsWithFilteredLabels = ({
|
|
|
67
83
|
path: originalPath,
|
|
68
84
|
obstacles: combinedObstacles,
|
|
69
85
|
labelBounds,
|
|
86
|
+
originalPath: originalPath,
|
|
70
87
|
})
|
|
71
88
|
|
|
72
89
|
return {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Point } from "graphics-debug"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recognizes a "stair-step" pattern within a given path of points starting from a specified index.
|
|
5
|
+
* A stair-step pattern is characterized by alternating horizontal and vertical segments.
|
|
6
|
+
* The function checks for a sequence of at least three segments where the orientation (horizontal/vertical) alternates.
|
|
7
|
+
* It returns the end index of the recognized stair-step pattern if found, otherwise -1.
|
|
8
|
+
*/
|
|
9
|
+
export const recognizeStairStepPattern = (
|
|
10
|
+
pathToCheck: Point[],
|
|
11
|
+
startIdx: number,
|
|
12
|
+
): number => {
|
|
13
|
+
if (startIdx >= pathToCheck.length - 3) return -1
|
|
14
|
+
|
|
15
|
+
let endIdx = startIdx
|
|
16
|
+
let isStairStep = true
|
|
17
|
+
|
|
18
|
+
for (let i = startIdx; i < pathToCheck.length - 2 && i < startIdx + 10; i++) {
|
|
19
|
+
if (i + 2 >= pathToCheck.length) break
|
|
20
|
+
|
|
21
|
+
const p1 = pathToCheck[i]
|
|
22
|
+
const p2 = pathToCheck[i + 1]
|
|
23
|
+
const p3 = pathToCheck[i + 2]
|
|
24
|
+
|
|
25
|
+
const seg1Vertical = p1.x === p2.x
|
|
26
|
+
const seg2Vertical = p2.x === p3.x
|
|
27
|
+
|
|
28
|
+
if (seg1Vertical === seg2Vertical) {
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const seg1Direction = seg1Vertical
|
|
33
|
+
? Math.sign(p2.y - p1.y)
|
|
34
|
+
: Math.sign(p2.x - p1.x)
|
|
35
|
+
|
|
36
|
+
if (i > startIdx) {
|
|
37
|
+
const prevP = pathToCheck[i - 1]
|
|
38
|
+
const prevSegVertical = prevP.x === p1.x
|
|
39
|
+
const prevDirection = prevSegVertical
|
|
40
|
+
? Math.sign(p1.y - prevP.y)
|
|
41
|
+
: Math.sign(p1.x - prevP.x)
|
|
42
|
+
|
|
43
|
+
if (
|
|
44
|
+
(seg1Vertical && prevSegVertical && seg1Direction !== prevDirection) ||
|
|
45
|
+
(!seg1Vertical && !prevSegVertical && seg1Direction !== prevDirection)
|
|
46
|
+
) {
|
|
47
|
+
isStairStep = false
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
endIdx = i + 2
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return isStairStep && endIdx - startIdx >= 3 ? endIdx : -1
|
|
56
|
+
}
|