Skip to content

Commit ea524bf

Browse files
committed
Encapsulating the SweepLine
Defensively programming around w8r/avl#15
1 parent 30acd02 commit ea524bf

File tree

3 files changed

+154
-49
lines changed

3 files changed

+154
-49
lines changed

src/subdivide-segments.js

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
const Tree = require('avl')
2-
const compareSegments = require('./compare-segments')
1+
const SweepLine = require('./sweep-line')
32

43
const possibleIntersection = (se1, se2) => {
54
const inters = se1.segment.getIntersections(se2.segment)
@@ -19,20 +18,17 @@ const possibleIntersection = (se1, se2) => {
1918
}
2019

2120
module.exports = eventQueue => {
22-
const sweepLine = new Tree(compareSegments)
21+
const sweepLine = new SweepLine()
2322
const sortedEvents = []
2423

2524
while (!eventQueue.isEmpty) {
2625
const event = eventQueue.pop()
2726
sortedEvents.push(event)
2827

2928
if (event.isLeft) {
30-
const eventNode = sweepLine.insert(event)
31-
const prevNode = sweepLine.prev(eventNode)
32-
const nextNode = sweepLine.next(eventNode)
33-
34-
const prevEvent = prevNode ? prevNode.key : null
35-
const nextEvent = nextNode ? nextNode.key : null
29+
const node = sweepLine.insert(event)
30+
const prevEvent = sweepLine.prevKey(node)
31+
const nextEvent = sweepLine.nextKey(node)
3632

3733
event.registerPrevEvent(prevEvent)
3834

@@ -42,9 +38,8 @@ module.exports = eventQueue => {
4238

4339
if (event.isRight) {
4440
const leftEvent = event.otherSE
45-
const leftNode = sweepLine.find(leftEvent)
46-
const nextNode = sweepLine.next(leftNode)
47-
const nextEvent = nextNode ? nextNode.key : null
41+
const node = sweepLine.find(leftEvent)
42+
const nextEvent = sweepLine.nextKey(node)
4843

4944
if (nextEvent && leftEvent.segment.isCoincidentWith(nextEvent.segment)) {
5045
leftEvent.registerCoincidentEvent(nextEvent, true)

src/sweep-line.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const Tree = require('avl')
2+
const compareSegments = require('./compare-segments')
3+
4+
/**
5+
* NOTE: It appears if you pull out a node from the AVL tree,
6+
* then do a remove() on the tree, the nodes can get
7+
* messed up: https://github.com/w8r/avl/issues/15
8+
*
9+
* As such, the methods here which accept nodes back from
10+
* the client will throw an exception if remove() has been
11+
* called since that node was first given to the client.
12+
*/
13+
14+
class SweepLine {
15+
constructor (comparator = compareSegments) {
16+
this.tree = new Tree(comparator)
17+
this.removeCounter = 1
18+
}
19+
20+
/* Returns the new node associated with the key */
21+
insert (key) {
22+
const node = this.tree.insert(key)
23+
return this._annotateNode(node)
24+
}
25+
26+
/* Returns the node associated with the key */
27+
find (key, returnNeighbors = false) {
28+
const node = this.tree.find(key)
29+
return this._annotateNode(node)
30+
}
31+
32+
prevKey (node) {
33+
this._checkNode(node)
34+
const prevNode = this.tree.prev(node)
35+
return prevNode ? prevNode.key : null
36+
}
37+
38+
nextKey (node) {
39+
this._checkNode(node)
40+
const nextNode = this.tree.next(node)
41+
return nextNode ? nextNode.key : null
42+
}
43+
44+
remove (key) {
45+
this.removeCounter++
46+
this.tree.remove(key)
47+
}
48+
49+
_checkNode (node) {
50+
/* defensively working around https://github.com/w8r/avl/issues/15 */
51+
if (node.removeCounter !== this.removeCounter) {
52+
throw new Error('Tried to use stale node')
53+
}
54+
}
55+
56+
_annotateNode (node) {
57+
if (node !== null) node.removeCounter = this.removeCounter
58+
return node
59+
}
60+
}
61+
62+
module.exports = SweepLine

test/sweep-line.test.js

Lines changed: 85 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,91 @@
11
/* eslint-env jest */
22

3-
const Tree = require('avl')
4-
const compareSegments = require('../src/compare-segments')
5-
const Segment = require('../src/segment')
3+
const SweepLine = require('../src/sweep-line')
4+
5+
const comparator = (a, b) => {
6+
if (a === b) return 0
7+
return a < b ? -1 : 1
8+
}
69

710
describe('sweep line', () => {
8-
const s = [[[16, 282], [298, 359], [153, 203.5], [16, 282]]]
9-
const c = [[[56, 181], [153, 294.5], [241.5, 229.5], [108.5, 120], [56, 181]]]
10-
11-
test('general', () => {
12-
const EF = new Segment(s[0][0], s[0][2], true).leftSE
13-
const EG = new Segment(s[0][0], s[0][1], true).leftSE
14-
15-
const tree = new Tree(compareSegments)
16-
tree.insert(EF)
17-
tree.insert(EG)
18-
19-
expect(tree.find(EF).key).toBe(EF)
20-
expect(tree.minNode().key).toBe(EF)
21-
expect(tree.maxNode().key).toBe(EG)
22-
23-
let it = tree.find(EF)
24-
expect(tree.next(it).key).toBe(EG)
25-
it = tree.find(EG)
26-
expect(tree.prev(it).key).toBe(EF)
27-
28-
const DA = new Segment(c[0][0], c[0][2], true).leftSE
29-
const DC = new Segment(c[0][0], c[0][1], true).leftSE
30-
31-
tree.insert(DA)
32-
tree.insert(DC)
33-
34-
let node = tree.minNode()
35-
expect(node.key).toBe(DA)
36-
node = tree.next(node)
37-
expect(node.key).toBe(DC)
38-
node = tree.next(node)
39-
expect(node.key).toBe(EF)
40-
node = tree.next(node)
41-
expect(node.key).toBe(EG)
11+
test('fill it up then empty it out', () => {
12+
const sl = new SweepLine(comparator)
13+
const k1 = 4
14+
const k2 = 9
15+
const k3 = 13
16+
const k4 = 44
17+
18+
let n1 = sl.insert(k1)
19+
let n2 = sl.insert(k2)
20+
let n4 = sl.insert(k4)
21+
let n3 = sl.insert(k3)
22+
23+
expect(sl.find(k1)).toBe(n1)
24+
expect(sl.find(k2)).toBe(n2)
25+
expect(sl.find(k3)).toBe(n3)
26+
expect(sl.find(k4)).toBe(n4)
27+
28+
expect(sl.prevKey(n1)).toBeNull()
29+
expect(sl.nextKey(n1)).toBe(k2)
30+
31+
expect(sl.prevKey(n2)).toBe(k1)
32+
expect(sl.nextKey(n2)).toBe(k3)
33+
34+
expect(sl.prevKey(n3)).toBe(k2)
35+
expect(sl.nextKey(n3)).toBe(k4)
36+
37+
expect(sl.prevKey(n4)).toBe(k3)
38+
expect(sl.nextKey(n4)).toBeNull()
39+
40+
sl.remove(k2)
41+
expect(sl.find(k2)).toBeNull()
42+
43+
expect(() => sl.nextKey(n1)).toThrow()
44+
expect(() => sl.nextKey(n2)).toThrow()
45+
expect(() => sl.nextKey(n3)).toThrow()
46+
expect(() => sl.nextKey(n4)).toThrow()
47+
48+
n1 = sl.find(k1)
49+
n3 = sl.find(k3)
50+
n4 = sl.find(k4)
51+
52+
expect(sl.prevKey(n1)).toBeNull()
53+
expect(sl.nextKey(n1)).toBe(k3)
54+
55+
expect(sl.prevKey(n3)).toBe(k1)
56+
expect(sl.nextKey(n3)).toBe(k4)
57+
58+
expect(sl.prevKey(n4)).toBe(k3)
59+
expect(sl.nextKey(n4)).toBeNull()
60+
61+
sl.remove(k4)
62+
expect(sl.find(k4)).toBeNull()
63+
64+
expect(() => sl.prevKey(n1)).toThrow()
65+
expect(() => sl.prevKey(n3)).toThrow()
66+
expect(() => sl.prevKey(n4)).toThrow()
67+
68+
n1 = sl.find(k1)
69+
n3 = sl.find(k3)
70+
71+
expect(sl.prevKey(n1)).toBeNull()
72+
expect(sl.nextKey(n1)).toBe(k3)
73+
74+
expect(sl.prevKey(n3)).toBe(k1)
75+
expect(sl.nextKey(n3)).toBeNull()
76+
77+
sl.remove(k1)
78+
expect(sl.find(k1)).toBeNull()
79+
80+
expect(() => sl.nextKey(n1)).toThrow()
81+
expect(() => sl.nextKey(n4)).toThrow()
82+
83+
n3 = sl.find(k3)
84+
85+
expect(sl.prevKey(n3)).toBeNull()
86+
expect(sl.nextKey(n3)).toBeNull()
87+
88+
sl.remove(k3)
89+
expect(sl.find(k3)).toBeNull()
4290
})
4391
})

0 commit comments

Comments
 (0)