Skip to content

Commit 0b93a48

Browse files
committed
Quadtree
1 parent 7dfe6b2 commit 0b93a48

File tree

9 files changed

+861
-0
lines changed

9 files changed

+861
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
import Foundation
3+
4+
let tree = QuadTree(rect: Rect(origin: Point(0, 0), size: Size(xLength: 10, yLength: 10)))
5+
6+
for _ in 0..<40 {
7+
let randomX = Double(arc4random_uniform(100)) / 10
8+
let randomY = Double(arc4random_uniform(100)) / 10
9+
let point = Point(randomX, randomY)
10+
tree.add(point: point)
11+
}
12+
13+
print(tree)
14+
print(tree.points(inRect: Rect(origin: Point(1, 1), size: Size(xLength: 5, yLength: 5))))
Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
2+
public struct Point {
3+
let x: Double
4+
let y: Double
5+
6+
public init(_ x: Double, _ y: Double) {
7+
self.x = x
8+
self.y = y
9+
}
10+
}
11+
12+
extension Point: CustomStringConvertible {
13+
public var description: String {
14+
return "Point(\(x), \(y))"
15+
}
16+
}
17+
18+
public struct Size: CustomStringConvertible {
19+
var xLength: Double
20+
var yLength: Double
21+
22+
public init(xLength: Double, yLength: Double) {
23+
precondition(xLength >= 0, "xLength can not be negative")
24+
precondition(yLength >= 0, "yLength can not be negative")
25+
self.xLength = xLength
26+
self.yLength = yLength
27+
}
28+
29+
var half: Size {
30+
return Size(xLength: xLength / 2, yLength: yLength / 2)
31+
}
32+
33+
public var description: String {
34+
return "Size(\(xLength), \(yLength))"
35+
}
36+
}
37+
38+
public struct Rect {
39+
// left top vertice
40+
var origin: Point
41+
var size: Size
42+
43+
public init(origin: Point, size: Size) {
44+
self.origin = origin
45+
self.size = size
46+
}
47+
48+
var minX: Double {
49+
return origin.x
50+
}
51+
52+
var minY: Double {
53+
return origin.y
54+
}
55+
56+
var maxX: Double {
57+
return origin.x + size.xLength
58+
}
59+
60+
var maxY: Double {
61+
return origin.y + size.yLength
62+
}
63+
64+
func containts(point: Point) -> Bool {
65+
return (minX <= point.x && point.x <= maxX) &&
66+
(minY <= point.y && point.y <= maxY)
67+
}
68+
69+
var leftTopRect: Rect {
70+
return Rect(origin: origin, size: size.half)
71+
}
72+
73+
var leftBottomRect: Rect {
74+
return Rect(origin: Point(origin.x, origin.y + size.half.yLength), size: size.half)
75+
}
76+
77+
var rightTopRect: Rect {
78+
return Rect(origin: Point(origin.x + size.half.xLength, origin.y), size: size.half)
79+
}
80+
81+
var rightBottomRect: Rect {
82+
return Rect(origin: Point(origin.x + size.half.xLength, origin.y + size.half.yLength), size: size.half)
83+
}
84+
85+
func intersects(rect: Rect) -> Bool {
86+
87+
func lineSegmentsIntersect(lStart: Double, lEnd: Double, rStart: Double, rEnd: Double) -> Bool {
88+
return max(lStart, rStart) <= min(lEnd, rEnd)
89+
}
90+
// to intersect, both horizontal and vertical projections need to intersect
91+
// horizontal
92+
if !lineSegmentsIntersect(lStart: minX, lEnd: maxX, rStart: rect.minX, rEnd: rect.maxX) {
93+
return false
94+
}
95+
96+
// vertical
97+
return lineSegmentsIntersect(lStart: minY, lEnd: maxY, rStart: rect.minY, rEnd: rect.maxY)
98+
}
99+
}
100+
101+
extension Rect: CustomStringConvertible {
102+
public var description: String {
103+
return "Rect(\(origin), \(size))"
104+
}
105+
}
106+
107+
protocol PointsContainter {
108+
func add(point: Point) -> Bool
109+
func points(inRect rect: Rect) -> [Point]
110+
}
111+
112+
class QuadTreeNode {
113+
114+
enum NodeType {
115+
case leaf
116+
case `internal`(children: Children)
117+
}
118+
119+
struct Children: Sequence {
120+
let leftTop: QuadTreeNode
121+
let leftBottom: QuadTreeNode
122+
let rightTop: QuadTreeNode
123+
let rightBottom: QuadTreeNode
124+
125+
init(parentNode: QuadTreeNode) {
126+
leftTop = QuadTreeNode(rect: parentNode.rect.leftTopRect)
127+
leftBottom = QuadTreeNode(rect: parentNode.rect.leftBottomRect)
128+
rightTop = QuadTreeNode(rect: parentNode.rect.rightTopRect)
129+
rightBottom = QuadTreeNode(rect: parentNode.rect.rightBottomRect)
130+
}
131+
132+
struct ChildrenIterator: IteratorProtocol {
133+
private var index = 0
134+
private let children: Children
135+
136+
init(children: Children) {
137+
self.children = children
138+
}
139+
140+
mutating func next() -> QuadTreeNode? {
141+
142+
defer { index += 1 }
143+
144+
switch index {
145+
case 0: return children.leftTop
146+
case 1: return children.leftBottom
147+
case 2: return children.rightTop
148+
case 3: return children.rightBottom
149+
default: return nil
150+
}
151+
}
152+
}
153+
154+
public func makeIterator() -> ChildrenIterator {
155+
return ChildrenIterator(children: self)
156+
}
157+
}
158+
159+
var points: [Point] = []
160+
let rect: Rect
161+
var type: NodeType = .leaf
162+
163+
static let maxPointCapacity = 3
164+
165+
init(rect: Rect) {
166+
self.rect = rect
167+
}
168+
169+
var recursiveDescription: String {
170+
return recursiveDescription(withTabCount: 0)
171+
}
172+
173+
private func recursiveDescription(withTabCount count: Int) -> String {
174+
let indent = String(repeating: "\t", count: count)
175+
var result = "\(indent)" + description + "\n"
176+
switch type {
177+
case .internal(let children):
178+
for child in children {
179+
result += child.recursiveDescription(withTabCount: count + 1)
180+
}
181+
default:
182+
break
183+
}
184+
return result
185+
}
186+
}
187+
188+
extension QuadTreeNode: PointsContainter {
189+
190+
@discardableResult
191+
func add(point: Point) -> Bool {
192+
if !rect.containts(point: point) {
193+
return false
194+
}
195+
196+
switch type {
197+
case .internal(let children):
198+
// pass the point to one of the children
199+
for child in children {
200+
if child.add(point: point) {
201+
return true
202+
}
203+
}
204+
205+
fatalError("rect.containts evaluted to true, but none of the children added the point")
206+
case .leaf:
207+
points.append(point)
208+
// if the max capacity was reached, become an internal node
209+
if points.count == QuadTreeNode.maxPointCapacity {
210+
subdivide()
211+
}
212+
}
213+
return true
214+
}
215+
216+
private func subdivide() {
217+
switch type {
218+
case .leaf:
219+
type = .internal(children: Children(parentNode: self))
220+
case .internal:
221+
preconditionFailure("Calling subdivide on an internal node")
222+
}
223+
}
224+
225+
func points(inRect rect: Rect) -> [Point] {
226+
227+
// if the node's rect and the given rect don't intersect, return an empty array,
228+
// because there can't be any points that lie the node's (or its children's) rect and
229+
// in the given rect
230+
if !self.rect.intersects(rect: rect) {
231+
return []
232+
}
233+
234+
var result: [Point] = []
235+
236+
// collect the node's points that lie in the rect
237+
for point in points {
238+
if rect.containts(point: point) {
239+
result.append(point)
240+
}
241+
}
242+
243+
switch type {
244+
case .leaf:
245+
break
246+
case .internal(let children):
247+
// recursively add children's points that lie in the rect
248+
for childNode in children {
249+
result.append(contentsOf: childNode.points(inRect: rect))
250+
}
251+
}
252+
253+
return result
254+
}
255+
}
256+
257+
extension QuadTreeNode: CustomStringConvertible {
258+
var description: String {
259+
switch type {
260+
case .leaf:
261+
return "leaf \(rect) Points: \(points)"
262+
case .internal:
263+
return "parent \(rect) Points: \(points)"
264+
}
265+
}
266+
}
267+
268+
public class QuadTree: PointsContainter {
269+
270+
let root: QuadTreeNode
271+
272+
public init(rect: Rect) {
273+
self.root = QuadTreeNode(rect: rect)
274+
}
275+
276+
@discardableResult
277+
public func add(point: Point) -> Bool {
278+
return root.add(point: point)
279+
}
280+
281+
public func points(inRect rect: Rect) -> [Point] {
282+
return root.points(inRect: rect)
283+
}
284+
}
285+
286+
extension QuadTree: CustomStringConvertible {
287+
public var description: String {
288+
return "Quad tree\n" + root.recursiveDescription
289+
}
290+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='ios'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

QuadTree/QuadTree.playground/playground.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)