-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
/
Copy pathFindIntersections.js
199 lines (178 loc) · 6.18 KB
/
FindIntersections.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
/**
* @typedef {Object} Point
* @property {number} x - The x-coordinate of the point.
* @property {number} y - The y-coordinate of the point.
*/
/**
* @typedef {Object} Segment
* @property {Point} start - The start point of the segment.
* @property {Point} end - The end point of the segment.
*/
/**
* @typedef {Object} Event
* @property {Point} point - The point where the event occurs.
* @property {Array<string>} types - Types of the event ('left', 'right', or 'intersection').
* @property {Array<Segment>} segments - Segments associated with this event.
*/
/**
* @typedef {Object} Intersection
* @property {Segment} segment1 - First segment of the intersection.
* @property {Segment} segment2 - Second segment of the intersection.
* @property {Point} point - The point of intersection.
*/
/**
* Creates a new point.
* @param {number} x - The x-coordinate.
* @param {number} y - The y-coordinate.
* @returns {Point} The created point.
*/
export const createPoint = (x, y) => ({ x, y })
/**
* Creates a new event.
* @param {Point} point - The point where the event occurs.
* @param {Array<string>} types - Types of the event.
* @param {Array<Segment>} segments - Segments associated with this event.
* @returns {Event} The created event.
*/
export const createEvent = (point, types, segments) => ({
point,
types,
segments
})
/**
* Compares two points lexicographically.
* @param {Point} a - The first point.
* @param {Point} b - The second point.
* @returns {number} Negative if a < b, positive if a > b, zero if equal.
*/
export const comparePoints = (a, b) => {
if (a.x !== b.x) return a.x - b.x
return a.y - b.y
}
/**
* Compares two segments based on their y-coordinate at the current x-coordinate of the sweep line.
* @param {Segment} a - The first segment.
* @param {Segment} b - The second segment.
* @returns {number} Negative if a < b, positive if a > b, zero if equal.
*/
export const compareSegmentsY = (a, b) => a.start.y - b.start.y
/**
* Calculates the intersection point of two line segments.
* @param {Segment} seg1 - First segment.
* @param {Segment} seg2 - Second segment.
* @returns {Point|null} The intersection point, or null if segments are parallel.
*/
export const calculateIntersectionPoint = (seg1, seg2) => {
const x1 = seg1.start.x,
y1 = seg1.start.y
const x2 = seg1.end.x,
y2 = seg1.end.y
const x3 = seg2.start.x,
y3 = seg2.start.y
const x4 = seg2.end.x,
y4 = seg2.end.y
const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if (Math.abs(denom) < Number.EPSILON) return null // parallel lines
const x =
((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / denom
const y =
((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / denom
return createPoint(x, y)
}
/**
* Handles potential intersection between two segments.
* @param {Segment} seg1 - First segment.
* @param {Segment} seg2 - Second segment.
* @param {Array<Event>} events - Array of events to update.
* @param {Array<Intersection>} intersections - Array of intersections to update.
*/
export const handlePotentialIntersection = (
seg1,
seg2,
events,
intersections
) => {
const intersectionPoint = calculateIntersectionPoint(seg1, seg2)
if (intersectionPoint) {
events.push(createEvent(intersectionPoint, ['intersection'], [seg1, seg2]))
intersections.push({
segment1: seg1,
segment2: seg2,
point: intersectionPoint
})
}
}
/**
* Finds all intersections among a set of line segments using the Bentley-Ottmann algorithm.
* @param {Array<Segment>} segments - Array of line segments to check for intersections.
* @returns {Array<Intersection>} Array of all intersections found.
*/
const findIntersections = (segments) => {
const events = []
const intersections = []
let sweepLineStatus = []
// Create initial events
segments.forEach((segment) => {
events.push(createEvent(segment.start, ['left'], [segment]))
events.push(createEvent(segment.end, ['right'], [segment]))
})
// Sort events
events.sort((a, b) => {
const pointCompare = comparePoints(a.point, b.point)
if (pointCompare !== 0) return pointCompare
// If points are the same, prioritize: intersection > left > right
const typePriority = { intersection: 0, left: 1, right: 2 }
return (
Math.min(...a.types.map((t) => typePriority[t])) -
Math.min(...b.types.map((t) => typePriority[t]))
)
})
// Process events
events.forEach((event) => {
if (event.types.includes('left')) {
event.segments.forEach((segment) => {
sweepLineStatus.push(segment)
sweepLineStatus.sort(compareSegmentsY)
const index = sweepLineStatus.indexOf(segment)
const lower = index > 0 ? sweepLineStatus[index - 1] : null
const upper =
index < sweepLineStatus.length - 1 ? sweepLineStatus[index + 1] : null
if (lower)
handlePotentialIntersection(segment, lower, events, intersections)
if (upper)
handlePotentialIntersection(segment, upper, events, intersections)
})
}
if (event.types.includes('right')) {
event.segments.forEach((segment) => {
const index = sweepLineStatus.indexOf(segment)
const lower = index > 0 ? sweepLineStatus[index - 1] : null
const upper =
index < sweepLineStatus.length - 1 ? sweepLineStatus[index + 1] : null
sweepLineStatus = sweepLineStatus.filter((s) => s !== segment)
if (lower && upper)
handlePotentialIntersection(lower, upper, events, intersections)
})
}
if (event.types.includes('intersection')) {
// Re-check all pairs of segments at this x-coordinate for intersections
const segmentsAtX = sweepLineStatus.filter(
(s) =>
Math.min(s.start.x, s.end.x) <= event.point.x &&
Math.max(s.start.x, s.end.x) >= event.point.x
)
for (let i = 0; i < segmentsAtX.length; i++) {
for (let j = i + 1; j < segmentsAtX.length; j++) {
handlePotentialIntersection(
segmentsAtX[i],
segmentsAtX[j],
events,
intersections
)
}
}
}
})
return intersections
}
export default findIntersections