Skip to content

Commit 204cb10

Browse files
author
Oleksandr Glagoliev
committed
Add Watcher
1 parent 22554f7 commit 204cb10

File tree

5 files changed

+184
-52
lines changed

5 files changed

+184
-52
lines changed

Sources/FortunesAlgorithm/Beachline.swift

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -298,31 +298,7 @@ extension Beachline {
298298
func updateSweeplineY(_ y: Double) {
299299
sweeplineY = y
300300
}
301-
302-
// private func arcAbovePoint(_ p: Point) -> Arc {
303-
// var x: Arc = root!
304-
// var found = false
305-
// while !found {
306-
// assert(x.point != nil)
307-
// let (l, r) = x.bounds(sweeplineY)
308-
// if p.x < l {
309-
// x = x.left!
310-
// } else if p.x > r {
311-
// x = x.right!
312-
// }
313-
// else if abs(p.x - l) < eps {
314-
// x = x.prev!
315-
// found = true
316-
// } else if abs(p.x - r) < eps {
317-
// found = true
318-
// }
319-
// else {
320-
// found = true
321-
// }
322-
// }
323-
// return x
324-
// }
325-
301+
326302
func addAsLeftChild(_ x: Arc, _ y: Arc) {
327303
y.left = x
328304
x.parent = y

Sources/FortunesAlgorithm/Diagram.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ public class Diagram {
4949
cells.removeAll()
5050
vertices.removeAll()
5151
}
52-
5352
}
5453

5554

5655
/// The vertex record of a vertex v stores the coordinates of v.
5756
/// It also stores a pointer IncidentEdge(v) to an arbitrary half‐edge that has v as its origin
5857
public typealias Vertex = Site
58+
public typealias Point = Site
5959

6060

6161
/// Stores pointer to:
@@ -79,7 +79,6 @@ public class Cell {
7979

8080
public extension Cell {
8181

82-
8382
/// Returns hell vertices of the cell
8483
func hullVerticesCCW() -> [Vertex] {
8584
var vertices: [Vertex] = []

Sources/FortunesAlgorithm/FortuneSweep.swift

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,45 @@ public class FortuneSweep {
2727
/// Service Data Structures
2828
private var eventQueue: PriorityQueue<Event>!
2929
private var beachline: Beachline!
30-
private var sweepLineY: Double = 0
30+
private var sweepLineY: Double = 0 {
31+
didSet {
32+
watcher?.updateSweepline(y: sweepLineY)
33+
}
34+
}
3135
private var firstSiteY: Double?
3236

33-
private var container: Rectangle!
37+
private var container: Rectangle! {
38+
didSet {
39+
watcher?.updateContainer(rectangle: container)
40+
}
41+
}
3442
private var clipper: Rectangle!
3543

3644
/// Debug Data structures
3745
private var logger: FortuneSweepLogging?
38-
private var currentStep: Int = 0
46+
private var watcher: FortuneSweepProgressWatching?
47+
private var currentStep: Int = 0 {
48+
didSet {
49+
watcher?.step = currentStep
50+
}
51+
}
3952
/// Result Data Structure
4053
private(set) var diagram: Diagram!
4154

42-
public init(logger: FortuneSweepLogging? = nil) {
43-
self.logger = logger
55+
56+
/// Initialises new FortuneSweep instance
57+
/// NB: `logger` and especially `watcher` will affect performance!
58+
/// Use them only for debug purposes!
59+
///
60+
/// - Parameters:
61+
/// - logger: Optional. Calls `log` function when importand events happen
62+
/// - watcher: Optional. **Affects performance!** Tracks diagram building process. May be used for visualisation as it includes useful scaffolding.
63+
public init(
64+
logger: FortuneSweepLogging? = nil,
65+
intermediate: FortuneSweepProgressWatching? = nil
66+
) {
67+
self.logger = nil//logger
68+
self.watcher = intermediate
4469
}
4570

4671
public func compute(
@@ -50,12 +75,15 @@ public class FortuneSweep {
5075
) {
5176
self.diagram = diagram
5277
self.clipper = clippingRect
53-
let filtered = sites
54-
.filter { clipper.contains($0) }
55-
.map { Event(point: $0) }
56-
78+
let filteredSites = sites.filter { clipper.contains($0) }
79+
80+
let events = filteredSites.map { Event(point: $0) }
81+
82+
/// Intermediate state
83+
watcher?.prepare(sites: filteredSites, clipper: clippingRect)
84+
5785
/// Diagram is a whole plane. Do nothing
58-
if filtered.isEmpty {
86+
if events.isEmpty {
5987
logger?.log("Computation done. No sites inside defined area!", level: .info)
6088
return
6189
}
@@ -66,10 +94,10 @@ public class FortuneSweep {
6694

6795
eventQueue = PriorityQueue(
6896
ascending: true,
69-
startingValues: filtered
97+
startingValues: events
7098
)
7199

72-
logger?.log("Computation started!", level: .info)
100+
logger?.log("\n\nComputation started!", level: .info)
73101
var finished = false
74102
while !finished {
75103
step()
@@ -85,7 +113,7 @@ public class FortuneSweep {
85113
/// 2. Check the event type and process the event appropriately
86114
private func step() {
87115
currentStep += 1
88-
logger?.log("Step: \(currentStep)", level: .info)
116+
logger?.log("\nStep: \(currentStep)", level: .info)
89117

90118
if let event = eventQueue.pop() {
91119
switch event.kind {
@@ -102,6 +130,7 @@ public class FortuneSweep {
102130
/// - Parameter event: **Site Event** to process
103131
private func processSiteEvent(_ event: Event) {
104132
logger?.log("Site Event: \(event.point)", level: .info)
133+
watcher?.siteEvent(site: event.point)
105134

106135
/// #Step 1:
107136
/// Update **Sweepline** position
@@ -125,6 +154,7 @@ public class FortuneSweep {
125154
/// Create new **Cell** record in **Voronoi Diagram**
126155
container.expandToContainPoint(event.point)
127156
diagram.createCell(root)
157+
watcher?.createCell(cell: root.cell)
128158
return
129159
}
130160

@@ -144,6 +174,7 @@ public class FortuneSweep {
144174

145175
/// 1. Create new **Cell** record in **Voronoi Diagram**
146176
diagram.createCell(arc)
177+
watcher?.createCell(cell: arc.cell)
147178

148179
/// 2. Create proper *HalfEdge* records. Arc with the same *y* can only appear
149180
/// to the right of existing one as in case of *y* coordinate is shared they are sorted by *x*
@@ -172,8 +203,17 @@ public class FortuneSweep {
172203
/// Create new **Cell** record in **Voronoi Diagram**
173204
container.expandToContainPoint(event.point)
174205
diagram.createCell(newArc)
175-
176-
206+
watcher?.createCell(cell: newArc.cell)
207+
208+
/// Notify `intermediate` if needed
209+
if let intermediate = self.watcher {
210+
let parabola = Parabola(focus: newArc.prev!.point!, directrixY: sweepLineY)
211+
let breakPoint = Point(
212+
x: event.point.x,
213+
y: parabola.resolve(x: event.point.x)
214+
)
215+
intermediate.updateCurentBreakpoint(point: breakPoint)
216+
}
177217

178218
/// #Step 3:
179219
/// If the arc we broke has circle event, than this event is false-alarm and has to be removed
@@ -246,6 +286,10 @@ public class FortuneSweep {
246286
newArc.rightHalfEdge = newArc.leftHalfEdge
247287
next.leftHalfEdge = prev.rightHalfEdge
248288
}
289+
290+
if let intermediate = self.watcher {
291+
intermediate.updateBeachline(arcs: beachline.getArcs())
292+
}
249293
}
250294

251295

@@ -297,13 +341,16 @@ public class FortuneSweep {
297341
nextArc.leftHalfEdge = nextLHE
298342

299343
makeTwins(prevArc.rightHalfEdge, nextArc.leftHalfEdge)
344+
345+
watcher?.createVertex(vertex: vertex)
300346
}
301347

302348

303349
/// Processes circle event and performs all the necessary actions
304350
/// - Parameter event: **Circle Event**
305351
private func processCircleEvent(_ event: Event) {
306352
logger?.log("Circle Event: \(event.point)", level: .info)
353+
watcher?.circleEvent(point: event.point)
307354

308355
guard let arc = event.arc,
309356
let left = arc.prev,
@@ -350,8 +397,6 @@ public class FortuneSweep {
350397
/// - arc: **Beachline** arc to add event
351398
/// - circle: Circle represented by three points
352399
private func createCircleEvent(_ arc: Arc) {
353-
logger?.log("Create Circle Event for Arc: \(arc.point!)", level: .info)
354-
355400
let left = arc.prev
356401
let right = arc.next
357402
guard let circle = checkCircleEvent(left: left, mid: arc, right: right) else {
@@ -366,6 +411,9 @@ public class FortuneSweep {
366411
arc.event = event
367412

368413
eventQueue.push(event)
414+
415+
logger?.log("Create Circle Event for Arc: \(arc.point!)", level: .info)
416+
watcher?.addUpcomingCircleEvent(circle: event.circle!)
369417
}
370418

371419
/// Removes circle event from the **Priority Queue** and removes event from the **Arc**
@@ -379,10 +427,11 @@ public class FortuneSweep {
379427
return
380428
}
381429

430+
watcher?.removeFalseAlarmCircleEvent(circle: event.circle!)
431+
logger?.log("Remove Circle Event for Arc: \(arc.point!)", level: .info)
432+
382433
eventQueue.removeAll(event)
383434
arc.event = nil
384-
385-
logger?.log("Remove Circle Event for Arc: \(arc.point!)", level: .info)
386435
}
387436

388437

@@ -714,3 +763,28 @@ extension Vector2D {
714763
Site(x: dx, y: dy)
715764
}
716765
}
766+
767+
public struct BeachlineSegment {
768+
let parabola: Parabola
769+
let lBoundX: Double
770+
let rBoundX: Double
771+
}
772+
773+
extension Beachline {
774+
public func getArcs() -> [BeachlineSegment] {
775+
var arcs = [BeachlineSegment]()
776+
var arc = minimum
777+
while arc != nil {
778+
let (lb, rb) = arc!.bounds(sweeplineY)
779+
arcs.append(
780+
BeachlineSegment(
781+
parabola: Parabola(focus: arc!.point!, directrixY: sweeplineY),
782+
lBoundX: lb,
783+
rBoundX: rb
784+
)
785+
)
786+
arc = arc?.next
787+
}
788+
return arcs
789+
}
790+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2020 Oleksandr Glagoliev
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
24+
25+
import Foundation
26+
27+
public protocol FortuneSweepProgressWatching: class {
28+
29+
/// Depicts current step of an algorithm
30+
var step: Int { get set }
31+
32+
/// Called when algorithm is ready to work
33+
/// - Parameter sites: Same as Fortunes Algorithm input
34+
/// - Parameter clipper: Clipping rectangle
35+
func prepare(sites: Set<Site>, clipper: Rectangle)
36+
37+
38+
/// Called when sweepline changes position
39+
/// - Parameter y: New sweepline Y position
40+
func updateSweepline(y: Double)
41+
42+
/// Called when Site Event occured
43+
/// - Parameter site: Visited site
44+
func siteEvent(site: Site)
45+
46+
/// Called when Circle Event occured
47+
/// - Parameter site: Visited site
48+
func circleEvent(point: Point)
49+
50+
51+
/// Called when bounding rectangle is changed
52+
/// - Parameter rectangle: New Bounding rectangle
53+
func updateContainer(rectangle: Rectangle)
54+
55+
56+
/// Called when new breakpoint occured
57+
/// - Parameter point: Breakpoint
58+
func updateCurentBreakpoint(point: Point?)
59+
60+
61+
/// Called when beachline is updated
62+
/// - Parameter arcs: Arcs, currently present in the Beachline
63+
func updateBeachline(arcs: [BeachlineSegment])
64+
65+
66+
/// Called when upcoming circle event is added to the Event Queue
67+
/// - Parameter circle: Circle event circle
68+
func addUpcomingCircleEvent(circle: Circle)
69+
70+
71+
/// Called when Circle event is removed from Event Queue
72+
/// - Parameter circle: Circle event circle
73+
func removeFalseAlarmCircleEvent(circle: Circle)
74+
75+
76+
/// Called when new Vertex is created
77+
/// - Parameter vertex: Vertex
78+
func createVertex(vertex: Vertex)
79+
80+
/// Called when new Cell is added to the diagram
81+
/// - Parameter cell: Cell
82+
func createCell(cell: Cell)
83+
}

0 commit comments

Comments
 (0)