├── .gitignore ├── .travis.yml ├── Assets └── .gitkeep ├── ForceDirectedScene.podspec ├── LICENSE ├── README.md ├── Sources ├── .gitkeep ├── ForceDirectedGraph.swift └── quadtree │ └── QuadTree.swift └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | 22 | # Bundler 23 | .bundle 24 | 25 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 26 | # Carthage/Checkouts 27 | 28 | Carthage/Build 29 | 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control 33 | # 34 | # Note: if you ignore the Pods directory, make sure to uncomment 35 | # `pod install` in .travis.yml 36 | # 37 | # Pods/ 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * https://www.objc.io/issues/6-build-tools/travis-ci/ 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | osx_image: xcode7.3 6 | language: objective-c 7 | # cache: cocoapods 8 | # podfile: Example/Podfile 9 | # before_install: 10 | # - gem install cocoapods # Since Travis is not always on latest version 11 | # - pod install --project-directory=Example 12 | script: 13 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace Example/ForceDirectedScene.xcworkspace -scheme ForceDirectedScene-Example -sdk iphonesimulator9.3 ONLY_ACTIVE_ARCH=NO | xcpretty 14 | - pod lib lint 15 | -------------------------------------------------------------------------------- /Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knightcode/ForceDirectedScene/16ed7569320087aa8c964a4c10126677b0b4baf6/Assets/.gitkeep -------------------------------------------------------------------------------- /ForceDirectedScene.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint ForceDirectedScene.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'ForceDirectedScene' 11 | s.version = '1.0.1' 12 | s.summary = 'A SceneKit compatible Force Directed Graph Implementation' 13 | 14 | s.description = <<-DESC 15 | Adds many body particle physics simulation to a SpriteKit Scene targeted 16 | at supporting the display of a forced directed graph. It minimally interacts 17 | with SpriteKit to apply a force to each body as an accumlated force from other 18 | nodes in the simulation, such that each node can still be subjected any other 19 | forces, collisions, and contacts in SpriteKit's physics simulation. 20 | DESC 21 | 22 | s.homepage = 'https://github.com/knightcode/ForceDirectedScene' 23 | s.license = { :type => 'MIT', :file => 'LICENSE' } 24 | s.author = { 'knightcode' => 'knightcode1@yahoo.com' } 25 | s.source = { :git => 'https://github.com/knightcode/ForceDirectedScene.git', :tag => s.version.to_s } 26 | s.social_media_url = 'https://instagram.com/knightcode' 27 | 28 | s.ios.deployment_target = '13.0' 29 | s.swift_version = '5.1' 30 | 31 | s.source_files = 'Sources/**/*' 32 | 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | MIT License 3 | 4 | Copyright (c) 2019 Dylan Knight 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ForceDirectedScene 2 | 3 | [![CI Status](https://img.shields.io/travis/knightcode/ForceDirectedScene.svg?style=flat)](https://travis-ci.org/knightcode/ForceDirectedScene) 4 | [![Version](https://img.shields.io/cocoapods/v/ForceDirectedScene.svg?style=flat)](https://cocoapods.org/pods/ForceDirectedScene) 5 | [![License](https://img.shields.io/cocoapods/l/ForceDirectedScene.svg?style=flat)](https://cocoapods.org/pods/ForceDirectedScene) 6 | [![Platform](https://img.shields.io/cocoapods/p/ForceDirectedScene.svg?style=flat)](https://cocoapods.org/pods/ForceDirectedScene) 7 | 8 | A solution for the n-body problem on a collection of nodes. This library computes the necessary forces to produce a force directed graph in a SpriteKit scene. Only the repulsive/attractive forces amongst the charges on each node are simulated. `SKPhysicsJointSpring` should be used to add spring forces for links. 9 | 10 | ## Installation 11 | 12 | ForceDirectedScene is available through [CocoaPods](https://cocoapods.org). To install 13 | it, simply add the following line to your Podfile: 14 | 15 | ```ruby 16 | pod 'ForceDirectedScene' 17 | ``` 18 | ## Usage 19 | 20 | ### Step 1: Implement ForceBody Protocol 21 | 22 | Implement the `ForceBody` protocol on your data model. We ship this protocol so that you're no required to provide a list of `SKNode`s with attached `SKPhysicsBody`s 23 | 24 | ```swift 25 | protocol ForceBody { 26 | var position: CGPoint { get } 27 | var charge: CGFloat { get } 28 | func applyForce(force: CGVector) 29 | } 30 | ``` 31 | 32 | Example: say your nodes are instances of a class `MyNode`, which stores an `SKNode` in a property, `skNode`, to render in your SpriteKit scene. Then you could implement the protocol as: 33 | 34 | ```swift 35 | extension MyNode: ForceBody { 36 | 37 | public var position: CGPoint { 38 | get { 39 | return self.skNode.position 40 | } 41 | } 42 | public var charge: CGFloat { 43 | get { 44 | if let physics = self.skNode.physicsBody { 45 | return physics.charge 46 | } 47 | return 0.0 48 | } 49 | } 50 | 51 | public func applyForce(force: CGVector) { 52 | self.skNode.physicsBody?.applyForce(force) 53 | } 54 | } 55 | ``` 56 | 57 | ### Step 2: Setup the physicsBody on each node 58 | 59 | You're free to set up your scene using all the tools SpriteKit has to offer. If you share the charge property of the physicsBody through the protocol to ForceDirectedGraph, your nodes can simultaneously be affected by electric fields and their mutual replusion or attraction. Also, the barnes-hut algorithm uses a quad tree to speed up simulation, which requires the bounds of your forced directed graph to be explicitly defined, so that we recommend setting constraints to confine the movement of your nodes to whatever bounds you define. 60 | 61 | We also suggest setting a strong `linearDamping` property. 62 | 63 | ```swift 64 | for node in mynodes { 65 | node.skNode = SKShapeNode( ... ) 66 | node.skNode.positition = CGPoint( ... ) 67 | node.skNode.physicsBody = SKPhysicsBody(circleOfRadius: 10.0) 68 | node.skNode.physicsBody?.isDynamic = true 69 | node.physicsBody?.charge = 5.5 70 | node.physicsBody?.linearDamping = 1.3 71 | 72 | node.skNode.constraints = [ 73 | SKConstraint.positionX(SKRange(lowerLimit: 0, upperLimit: self.view.bounds.width)), 74 | SKConstraint.positionY(SKRange(lowerLimit: 0, upperLimit: self.view.bounds.height)) 75 | ] 76 | 77 | scene.addChild(node.skNode) 78 | } 79 | ``` 80 | 81 | ### Step 3: Setup Spring Joints for Links 82 | 83 | You can model the links in your force directed graph with spring joints. For example: 84 | ```swift 85 | for link in links { 86 | let src = link.sourceSKNode 87 | let dest = link.destinationSKNode 88 | let spring = SKPhysicsJointSpring.joint(withBodyA: src.physicsBody!, bodyB: dest.physicsBody!, anchorA: src.position, anchorB: dest.position) 89 | spring.damping = 10.0 90 | spring.frequency = 0.25 91 | 92 | scene.physicsWorld.add(spring) 93 | } 94 | ``` 95 | (Note that you probably also want to create an `SKShapeNode` for each link to render a line between src and dest) 96 | 97 | ### Step 4: Create the ForceDirectedGraph in your SKSceneDelegate 98 | 99 | The `ForceDirectedGraph` constructor has two required arguments, (1) the bounds of graph's display area and (2) an array of objects conforming to the `ForceBody` protocol 100 | ```swift 101 | fdGraph = ForceDirectedGraph(bounds: self.view.bounds, nodes: mynodes) 102 | ``` 103 | 104 | Then, call the graph's update method in the `SKSceneDelegate`'s update method: 105 | ```swift 106 | func update(_ currentTime: TimeInterval, for scene: SKScene) { 107 | fdGraph.update() 108 | } 109 | ``` 110 | 111 | It's also probable that you'll want to update the position of your links' `SKShapeNode`s. We suggest doing that in the `didApplyConstraints` method: 112 | ```swift 113 | func didApplyConstraints(for scene: SKScene) { 114 | var path: UIBezierPath 115 | for link in links { 116 | let src = link.sourceSKNode 117 | let dest = link.destinationSKNode 118 | let lineNode = link.skNode 119 | path = UIBezierPath() 120 | path.move(to: src.position) 121 | path.addLine(to: dest.position) 122 | 123 | lineNode.path = path.cgPath 124 | } 125 | } 126 | ``` 127 | 128 | ## API 129 | ### ForceBody 130 | 131 | Property | Description 132 | ---------|------------ 133 | **position** | a getter that must return the node's current position in the scene 134 | **charge** | a getter that must return the charge of the current node. This does not have to be the same charge of SKPhysicsBody. It can be postive or negative. Similar charges repel one another. Dissimilar charges attract. 135 | **applyForce** | a function that must ultimately pass the supplied force to the `applyForce` method of the SKPhysicsBody for each node 136 | 137 | ### ForceDirectedGraph 138 | 139 | Public Property | Description 140 | ---------|------------ 141 | **theta** | Threshold value for Barnes-Hut algorithm. Low values improve simulation accuracy at higher computational cost. High values speed up simulation. 142 | **maxDistance** | The maximum distance at which two nodes can have an affect upon one another. Nodes farther apart than this distance will artificially no longer influence the force applied to each other. 143 | **minDistance** | The minimum distance required between two nodes for each to affect the other. Nodes closer than this distance will stop having an affect upon each other. 144 | **center?** | a point around which all nodes should attempt to cluster. When not nil, causes the application of an additional constant force to each node, directing the node to the defined center. If either of the `x` or `y` coordinates of `center` are set such that `CGFloat.isFinite` is false, the force will not be applied along that cardinal direction, e.g. a center of CGPoint(x: 50.0, y: CGFloat.infinity) will cause the nodes to cluster around the vertical line at x = 50.0. 145 | **centeringStrength** | defines the strength of the force applied to each node while moving it to its clustering point/line 146 | **bounds** | a CGRect that defines the maximum boundaries of the force directed graph scene. Undefined behavior will result if your scene pushes the nodes beyond these bounds. 147 | **update()** | method that must be called periodically to update the simuation, usually in an SKSceneDelegate method. 148 | **init()** | accepts initializers for each of these properties. See below. 149 | 150 | Each of these can be passed to the constructor as well. The default values defined in the signature are: 151 | ```swift 152 | public init(bounds: CGRect, 153 | nodes: Array, 154 | theta: CGFloat = 0.5, 155 | min: CGFloat = 0.0, 156 | max: CGFloat = CGFloat.infinity, 157 | center: CGPoint? = nil, 158 | centeringStrength: CGFloat = 0.0002) 159 | ``` 160 | 161 | ## Author 162 | 163 | Dylan Knight, knightcode1@yahoo.com 164 | 165 | ## License 166 | 167 | ForceDirectedScene is available under the MIT license. See the LICENSE file for more info. 168 | -------------------------------------------------------------------------------- /Sources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/knightcode/ForceDirectedScene/16ed7569320087aa8c964a4c10126677b0b4baf6/Sources/.gitkeep -------------------------------------------------------------------------------- /Sources/ForceDirectedGraph.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForceDirectedGraph.swift 3 | // ForceDirectedScene 4 | // 5 | // Created by Dylan Knight on 2/05/10. 6 | // Copyright © 2019 Dylan Knight. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public protocol ForceBody: QuadTreeElement { 12 | var position: CGPoint { get } 13 | var charge: CGFloat { get } 14 | 15 | func applyForce(force: CGVector) 16 | } 17 | 18 | func == (lhs: ForceBodyNode, rhs: ForceBodyNode) -> Bool { 19 | return lhs === rhs 20 | } 21 | 22 | public func == (lhs: QuadTreeElement, rhs: QuadTreeElement) -> Bool { 23 | return lhs === rhs 24 | } 25 | 26 | class ForceBodyNode: QuadTreeElement, Equatable { 27 | var force: CGVector 28 | var data: ForceBody 29 | 30 | var position: CGPoint { 31 | get { 32 | return data.position 33 | } 34 | } 35 | var charge: CGFloat { 36 | get { 37 | return data.charge 38 | } 39 | } 40 | 41 | init(data: ForceBody) { 42 | self.data = data 43 | force = CGVector(dx: 0, dy: 0) 44 | } 45 | } 46 | 47 | public class ForceDirectedGraph { 48 | 49 | private static let JIGGLE_THRESHOLD: CGFloat = 0.1 50 | 51 | public var centeringStrength: CGFloat 52 | public var theta: CGFloat 53 | public var maxDistance: CGFloat 54 | public var minDistance: CGFloat 55 | public var center: CGPoint? 56 | 57 | private var nodes: Array 58 | private var quadTree: QuadTree 59 | private var toRestructure: Array 60 | 61 | public var bounds: CGRect { 62 | get { 63 | return quadTree.root.bounds 64 | } 65 | } 66 | 67 | public init(bounds: CGRect, nodes: Array, theta: CGFloat = 0.5, min: CGFloat = 0.0, max: CGFloat = CGFloat.infinity, 68 | center: CGPoint? = nil, centeringStrength: CGFloat = 0.0002) { 69 | self.centeringStrength = centeringStrength 70 | self.theta = theta 71 | self.minDistance = min 72 | self.maxDistance = max 73 | self.center = center 74 | self.nodes = [] 75 | self.nodes.reserveCapacity(nodes.count) 76 | self.toRestructure = [] 77 | self.toRestructure.reserveCapacity(nodes.count) 78 | for n in nodes { 79 | self.nodes.append(ForceBodyNode(data: n)) 80 | } 81 | quadTree = QuadTree(bounds: bounds, bodies: self.nodes) 82 | } 83 | 84 | public func update () { 85 | 86 | quadTree.computeCenters() 87 | runBarnesHut() 88 | 89 | toRestructure.removeAll(keepingCapacity: true) 90 | quadTree.forEach { (node: ForceBodyNode, quad: Quad) in 91 | node.data.applyForce(force: node.force) 92 | if !quad.bounds.contains(node.position) { 93 | toRestructure.append(node) 94 | } 95 | } 96 | 97 | for node in toRestructure { 98 | quadTree.remove(element: node) 99 | } 100 | for node in toRestructure { 101 | quadTree.add(element: node) 102 | } 103 | } 104 | 105 | public func forEach (iterator: (ForceBody) -> Void) { 106 | for node in quadTree { 107 | let _ = iterator(node.data) 108 | } 109 | } 110 | 111 | private func runBarnesHut () { 112 | var db: CGVector 113 | var strength: CGFloat 114 | 115 | for node in nodes { 116 | node.force.dx = 0.0 117 | node.force.dy = 0.0 118 | runBarnesHut(node: node, quad: quadTree.root) 119 | 120 | if let center = self.center { 121 | db = center - node.position 122 | strength = self.centeringStrength 123 | if db.dx.isFinite { 124 | node.force.dx += (0.5 * strength * (db.dx < 0.0 ? -1.0 : 1.0)) 125 | } 126 | if db.dy.isFinite { 127 | node.force.dy += (0.5 * strength * (db.dy < 0.0 ? -1.0 : 1.0)) 128 | } 129 | } 130 | } 131 | } 132 | 133 | private func runBarnesHut (node: ForceBodyNode, quad: Quad) { 134 | let s = (quad.bounds.width + quad.bounds.height) / 2 135 | let d = (quad.center - node.position).magnitude 136 | var db: CGVector 137 | var distance: CGFloat 138 | var direction: CGVector 139 | var strength: CGFloat 140 | 141 | if s / d > theta { 142 | if let children = quad.children { 143 | for child in children { 144 | runBarnesHut(node: node, quad: child) 145 | } 146 | } else if let elem = quad.element as? ForceBodyNode { 147 | if elem == node { 148 | // same node 149 | return 150 | } 151 | db = node.position - elem.position // direction matters 152 | while db.dx <= ForceDirectedGraph.JIGGLE_THRESHOLD && db.dx >= -ForceDirectedGraph.JIGGLE_THRESHOLD || 153 | db.dy <= ForceDirectedGraph.JIGGLE_THRESHOLD && db.dy >= -ForceDirectedGraph.JIGGLE_THRESHOLD { 154 | jiggle(vector: &db) 155 | } 156 | distance = db.magnitude 157 | direction = db.normalized 158 | 159 | if distance >= minDistance && distance <= maxDistance { 160 | strength = (elem.charge * node.charge) / (distance * distance) // * 9e9 161 | direction *= strength 162 | node.force += direction 163 | } 164 | } 165 | } else { 166 | db = node.position - quad.center // direction matters 167 | while db.dx <= ForceDirectedGraph.JIGGLE_THRESHOLD && db.dx >= -ForceDirectedGraph.JIGGLE_THRESHOLD || 168 | db.dy <= ForceDirectedGraph.JIGGLE_THRESHOLD && db.dy >= -ForceDirectedGraph.JIGGLE_THRESHOLD { 169 | jiggle(vector: &db) 170 | } 171 | distance = db.magnitude 172 | direction = db.normalized 173 | if distance >= minDistance && distance <= maxDistance { 174 | strength = (quad.charge * node.charge) / (distance * distance) // * 9e9 175 | direction *= strength 176 | node.force += direction 177 | } 178 | } 179 | } 180 | 181 | private func jiggle (vector: inout CGVector) { 182 | // exaggerate small differences 183 | if vector.dx < 0.0 { 184 | vector.dx += CGFloat.random(in: -1.0..<0.0) 185 | } else { 186 | vector.dx += CGFloat.random(in: 0.0..<1.0) 187 | } 188 | if vector.dy < 0.0 { 189 | vector.dy += CGFloat.random(in: -1.0..<0.0) 190 | } else { 191 | vector.dy += CGFloat.random(in: 0.0..<1.0) 192 | } 193 | } 194 | } 195 | 196 | public extension CGVector { 197 | var magnitude: CGFloat { 198 | return sqrt(dx*dx + dy*dy) 199 | } 200 | var normalized: CGVector { 201 | return CGVector(dx: dx, dy: dy) / sqrt(dx*dx + dy*dy) 202 | } 203 | } 204 | 205 | @inline(__always) 206 | func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 207 | return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 208 | } 209 | @inline(__always) 210 | public func - (lhs: CGPoint, rhs: CGPoint) -> CGVector { 211 | return CGVector(dx: lhs.x - rhs.x, dy: lhs.y - rhs.y) 212 | } 213 | @inline(__always) 214 | func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 215 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 216 | } 217 | @inline(__always) 218 | func += (lhs: inout CGPoint, rhs: CGPoint) { 219 | lhs.x += rhs.x 220 | lhs.y += rhs.y 221 | } 222 | @inline(__always) 223 | func / (lhs: CGPoint, rhs: CGFloat) -> CGPoint { 224 | return CGPoint(x: lhs.x / rhs, y: lhs.y / rhs) 225 | } 226 | @inline(__always) 227 | func /= (lhs: inout CGPoint, rhs: CGFloat) { 228 | lhs.x /= rhs 229 | lhs.y /= rhs 230 | } 231 | @inline(__always) 232 | func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint { 233 | return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs) 234 | } 235 | 236 | @inline(__always) 237 | func + (lhs: CGVector, rhs: CGVector) -> CGVector { 238 | return CGVector(dx: lhs.dx + rhs.dx, dy: lhs.dy + rhs.dy) 239 | } 240 | @inline(__always) 241 | func - (lhs: CGVector, rhs: CGVector) -> CGVector { 242 | return CGVector(dx: lhs.dx - rhs.dx, dy: lhs.dy - rhs.dy) 243 | } 244 | @inline(__always) 245 | func / (lhs: CGVector, rhs: CGFloat) -> CGVector { 246 | return CGVector(dx: lhs.dx / rhs, dy: lhs.dy / rhs) 247 | } 248 | @inline(__always) 249 | func * (lhs: CGVector, rhs: CGFloat) -> CGVector { 250 | return CGVector(dx: lhs.dx * rhs, dy: lhs.dy * rhs) 251 | } 252 | @inline(__always) 253 | func += (lhs: inout CGVector, rhs: CGVector) { 254 | lhs.dx += rhs.dx 255 | lhs.dy += rhs.dy 256 | } 257 | @inline(__always) 258 | func *= (lhs: inout CGVector, rhs: CGFloat) { 259 | lhs.dx *= rhs 260 | lhs.dy *= rhs 261 | } 262 | -------------------------------------------------------------------------------- /Sources/quadtree/QuadTree.swift: -------------------------------------------------------------------------------- 1 | // 2 | // QuadTree.swift 3 | // ForceDirectedScene 4 | // 5 | // Created by PJ Dillon on 2/6/19. 6 | // 7 | 8 | import Foundation 9 | 10 | public protocol QuadTreeElement: AnyObject { 11 | var position: CGPoint { get } 12 | var charge: CGFloat { get } 13 | } 14 | 15 | public class Quad { 16 | public let bounds: CGRect 17 | public var center: CGPoint 18 | public var charge: CGFloat 19 | 20 | private(set) public var count: Int 21 | 22 | var element: T? 23 | var children: Array? 24 | 25 | public init (bounds: CGRect) { 26 | self.bounds = bounds 27 | self.center = bounds.midpoint 28 | self.count = 0 29 | self.charge = 0 30 | } 31 | 32 | public init (bounds: CGRect, element: T) { 33 | self.bounds = bounds 34 | self.center = element.position 35 | self.element = element 36 | self.count = 1 37 | self.charge = 0 38 | } 39 | 40 | public func add (element: T) { 41 | guard bounds.contains(element.position) else { 42 | print("trying add: element outside of my bounds: \(bounds), position: \(element.position)") 43 | return 44 | } 45 | if self.element == nil && self.children == nil { 46 | // empty node at the start 47 | self.element = element 48 | count = 1 49 | return 50 | } 51 | if let elem = self.element { 52 | subdivide() 53 | self.element = nil 54 | count = 0 // will be incremented in recursive call 55 | 56 | // recursively insert old, existing element, then continue adding new 57 | add(element: elem) 58 | } 59 | guard let children = self.children else { return } 60 | for child in children { 61 | if child.bounds.contains(element.position) { 62 | child.add(element: element) 63 | break 64 | } 65 | } 66 | count += 1 67 | } 68 | 69 | public func remove (element: T) -> Bool { 70 | if let elem = self.element, elem == element { 71 | self.element = nil 72 | count = 0 73 | return true 74 | } 75 | guard let children = self.children else { return false } 76 | var found = false 77 | for child in children { 78 | if child.remove(element: element) { 79 | count -= 1 80 | found = true 81 | } 82 | 83 | } 84 | if count == 1 { 85 | // consolidate 86 | for child in children { 87 | if child.element != nil { 88 | self.element = child.element 89 | } 90 | } 91 | self.children = nil 92 | } 93 | return found 94 | } 95 | 96 | public func computeCenter () -> CGPoint { 97 | if let elem = self.element { 98 | center = elem.position // position may have changed from acting forces 99 | charge = elem.charge 100 | return center 101 | } 102 | guard let children = self.children else { return center } 103 | center.x = 0.0 104 | center.y = 0.0 105 | charge = 0.0 106 | for child in children { 107 | center += child.computeCenter() 108 | charge += child.charge 109 | } 110 | center /= CGFloat(count) 111 | return center 112 | } 113 | 114 | private func subdivide () { 115 | let midX = bounds.size.width / 2 116 | let midY = bounds.size.height / 2 117 | let nw = CGRect( 118 | x: bounds.origin.x, 119 | y: bounds.origin.y + midY, 120 | width: midX, 121 | height: midY) 122 | let ne = CGRect( 123 | x: bounds.origin.x + midX, 124 | y: bounds.origin.y + midY, 125 | width: midX, 126 | height: midY) 127 | let sw = CGRect( 128 | x: bounds.origin.x, 129 | y: bounds.origin.y, 130 | width: midX, 131 | height: midY) 132 | let se = CGRect( 133 | x: bounds.origin.x + midX, 134 | y: bounds.origin.y, 135 | width:midX, 136 | height: midY) 137 | self.children = [ 138 | Quad(bounds: nw), 139 | Quad(bounds: ne), 140 | Quad(bounds: sw), 141 | Quad(bounds: se) 142 | ] 143 | } 144 | } 145 | 146 | public class QuadTree: Sequence { 147 | let root: Quad 148 | 149 | public init(bounds: CGRect, bodies: [T]? = nil) { 150 | root = Quad(bounds: bounds) 151 | if let elems = bodies { 152 | for elem in elems { 153 | root.add(element: elem) 154 | } 155 | } 156 | } 157 | 158 | public func add (element: T) { 159 | root.add(element: element) 160 | } 161 | public func remove (element: T) { 162 | let _ = root.remove(element: element) 163 | } 164 | public func computeCenters () { 165 | let _ = root.computeCenter() 166 | } 167 | 168 | public func makeIterator() -> QuadTreeIterator { 169 | return QuadTreeIterator(quad: root) 170 | } 171 | 172 | public func forEach(iter: (T, Quad) -> Void) { 173 | recursiveforEach(node: root, iter: iter) 174 | } 175 | 176 | private func recursiveforEach(node: Quad, iter: (T, Quad) -> Void) { 177 | if node.element == nil && node.children == nil { 178 | return 179 | } 180 | if let elem = node.element { 181 | iter(elem, node) 182 | return 183 | } 184 | if let quads = node.children { 185 | for q in quads { 186 | recursiveforEach(node: q, iter: iter) 187 | } 188 | } 189 | } 190 | } 191 | 192 | public class QuadTreeIterator: IteratorProtocol { 193 | 194 | var stack: Array> 195 | init (quad: Quad) { 196 | stack = [quad] 197 | } 198 | public func next() -> T? { 199 | while let quad = stack.popLast() { 200 | if let elem = quad.element { 201 | return elem 202 | } 203 | if let children = quad.children { 204 | for child in children { 205 | stack.append(child) 206 | } 207 | } 208 | } 209 | return nil 210 | } 211 | } 212 | 213 | public extension CGRect { 214 | var midpoint: CGPoint{ 215 | return CGPoint(x: midX, y: midY) 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------