-
Notifications
You must be signed in to change notification settings - Fork 62
/
Copy pathsweep-line.js
122 lines (100 loc) · 3.37 KB
/
sweep-line.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
const Tree = require('avl')
const { arePointsEqual } = require('./flp')
const Segment = require('./segment')
/**
* NOTE: It appears if you pull out a node from the AVL tree,
* then do a remove() on the tree, the nodes can get
* messed up: https://github.com/w8r/avl/issues/15
*
* As such, the methods here are careful not to re-use
* any nodes they're holding on to after calling remove().
*
* Also, we must be careful not to change any segments while
* they are in the AVL tree. AFAIK, there's no way to tell
* the AVL tree to rebalance itself - thus before splitting
* a segment that's in the tree, we remove it from the tree,
* do the split, then re-insert it.
*/
class SweepLine {
constructor (comparator = Segment.compare) {
this.tree = new Tree(comparator)
this.segments = []
this.prevEvent = null
}
process (event) {
const segment = event.segment
const newEvents = []
const node = event.isLeft ? this._insert(segment) : this._find(segment)
const prevSeg = this._prevKey(node)
const nextSeg = this._nextKey(node)
if (event.isLeft) {
const mySplitters = []
if (prevSeg) {
const prevInters = segment.getIntersections(prevSeg)
newEvents.push(...this._possibleSplit(prevSeg, prevInters))
mySplitters.push(...prevInters.filter(pt => !segment.isAnEndpoint(pt)))
}
if (nextSeg) {
const nextInters = segment.getIntersections(nextSeg)
newEvents.push(...this._possibleSplit(nextSeg, nextInters))
mySplitters.push(...nextInters.filter(pt => !segment.isAnEndpoint(pt)))
}
if (newEvents.length > 0 || mySplitters.length > 0) {
this._remove(segment)
if (mySplitters.length > 0) {
newEvents.push(...segment.split(mySplitters))
}
// Make sure sweep line ordering is totally consistent for later
// use with the segment 'prev' pointers - re-do the current event.
newEvents.push(event)
return newEvents
}
this.segments.push(segment)
segment.registerPrev(prevSeg)
} else {
// event.isRight
if (prevSeg && nextSeg) {
const inters = prevSeg.getIntersections(nextSeg)
newEvents.push(...this._possibleSplit(prevSeg, inters))
newEvents.push(...this._possibleSplit(nextSeg, inters))
}
if (nextSeg && segment.isCoincidentWith(nextSeg)) {
segment.registerCoincidence(nextSeg)
}
this._remove(segment)
}
if (this.prevEvent && arePointsEqual(this.prevEvent.point, event.point)) {
this.prevEvent.link(event)
}
this.prevEvent = event
return newEvents
}
_possibleSplit (segment, intersections) {
const splitters = intersections.filter(pt => !segment.isAnEndpoint(pt))
const newEvents = []
if (splitters.length > 0) {
this._remove(segment)
newEvents.push(...segment.split(splitters))
this._insert(segment)
}
return newEvents
}
_insert (key) {
return this.tree.insert(key)
}
_find (key) {
return this.tree.find(key)
}
_prevKey (node) {
const prevNode = this.tree.prev(node)
return prevNode ? prevNode.key : null
}
_nextKey (node) {
const nextNode = this.tree.next(node)
return nextNode ? nextNode.key : null
}
_remove (key) {
this.tree.remove(key)
}
}
module.exports = SweepLine