From 227c2b5c3569422af3bccc03fe0852f642c38147 Mon Sep 17 00:00:00 2001 From: li3zhen1 Date: Tue, 10 Oct 2023 13:06:13 -0400 Subject: [PATCH 1/2] add video --- ...irectedGraph.png => ForceDirectedGraphLight.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename Assets/{ForceDirectedGraph.png => ForceDirectedGraphLight.png} (100%) diff --git a/Assets/ForceDirectedGraph.png b/Assets/ForceDirectedGraphLight.png similarity index 100% rename from Assets/ForceDirectedGraph.png rename to Assets/ForceDirectedGraphLight.png From 5fc26db4b0fad8990a4f8e83acf98d91f8e219d6 Mon Sep 17 00:00:00 2001 From: li3zhen1 Date: Tue, 10 Oct 2023 15:01:33 -0400 Subject: [PATCH 2/2] fix: collideForce --- README.md | 29 +++++++++++++++++-- .../ForceSimulation/forces/CollideForce.swift | 21 +++++++------- .../ForceSimulation/forces/LinkForce.swift | 1 + .../forces/ManyBodyForce.swift | 14 ++++----- 4 files changed, 45 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 64aa0ca..1dcba97 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,13 @@ A visualization-purposed force simulation library. -#### Examples + ![Force Directed Graph](./Assets/ForceDirectedGraph.png) +#### Examples + This is a force directed graph visualizing the data from [Force Directed Graph Component](https://observablehq.com/@d3/force-directed-graph-component), running at 120FPS on a SwiftUI Canvas. Take a closer look at the animation! https://github.com/li3zhen1/Grape/assets/45376537/0a494ca0-7b98-44d0-a917-6dcc18e2eeae @@ -27,9 +29,30 @@ https://github.com/li3zhen1/Grape/assets/45376537/0a494ca0-7b98-44d0-a917-6dcc18 | CenterForce | ✅ | | CollideForce | ✅ | | PositionForce | | -| RadialForce | | +| RadialForce | ✅ | + + +#### Usage + +```swift +import ForceSimulation + +// nodes with unique id +let nodes: [Identifiable] = ... + +// links with source and target, ID should be the same as the type of the id +let links: [(ID, ID)] = ... + +let sim = Simulation(nodes: nodes, alphaDecay: 0.0005) +sim.createManyBodyForce(strength: -30) +sim.createLinkForce(links: links, originalLength: .constant(35)) +sim.createCenterForce(center: .zero, strength: 0.1) +sim.createCollideForce(radius: .constant(5)) + +``` +See [Example](https://github.com/li3zhen1/Grape/tree/main/Examples/GrapeView) for more details. #### Perfomance -Currently iterating the example graph 120 times in release build takes 0.046 seconds on a 32GB M1 Max. (77 vertices, 254 edges, link, with manybody, center and link forces) +Currently it takes 0.046 seconds to iterate 120 times over the example graph (with 77 vertices, 254 edges, with manybody, center and link forces, release build, on a 32GB M1 Max). diff --git a/Sources/ForceSimulation/forces/CollideForce.swift b/Sources/ForceSimulation/forces/CollideForce.swift index e4e9db4..a03f425 100644 --- a/Sources/ForceSimulation/forces/CollideForce.swift +++ b/Sources/ForceSimulation/forces/CollideForce.swift @@ -118,16 +118,18 @@ extension CollideForce: Force { nodes: sim.simulationNodes.map { ($0, $0.position) }, getQuadDelegate: { MaxRadiusQuadTreeDelegate { - switch self.radius { - case .constant(let r): - return r - case .varied(let r): - return r($0) - } + // switch self.radius { + // case .constant(let r): + // return r + // case .varied(_): + // return self.calculatedRadius[$0, default: 0.0] + // } + return self.calculatedRadius[$0, default: 0.0] + // return self.calculatedRadius[$0]! } } ) - else { break } + else { return } for i in sim.simulationNodes.indices { let iNode = sim.simulationNodes[i] @@ -156,7 +158,6 @@ extension CollideForce: Force { var l = deltaPosition.jiggled().length() l = (deltaR - l) / l * self.strength - deltaPosition *= l let jR2 = jR * jR let k = jR2 / (iR2 + jR2) @@ -171,7 +172,7 @@ extension CollideForce: Force { } return - !(quadNode.quad.x0 > iPosition.x + deltaR + !(quadNode.quad.x0 > iPosition.x + deltaR /* True if no overlap */ || quadNode.quad.x1 < iPosition.x - deltaR || quadNode.quad.y0 > iPosition.y + deltaR || quadNode.quad.y1 < iPosition.y - deltaR) @@ -184,7 +185,7 @@ extension CollideForce: Force { extension Simulation { @discardableResult public func createCollideForce( - radius: CollideForce.CollideRadius, + radius: CollideForce.CollideRadius = .constant(3.0), strength: Float = 1.0, iterationsPerTick: Int = 1 ) -> CollideForce { diff --git a/Sources/ForceSimulation/forces/LinkForce.swift b/Sources/ForceSimulation/forces/LinkForce.swift index cbd319f..d6cc69c 100644 --- a/Sources/ForceSimulation/forces/LinkForce.swift +++ b/Sources/ForceSimulation/forces/LinkForce.swift @@ -76,6 +76,7 @@ final public class LinkForce: Force where N: Identifiable { var calculatedBias: [Float] = [] weak var simulation: Simulation? + var iterations: Int internal init( diff --git a/Sources/ForceSimulation/forces/ManyBodyForce.swift b/Sources/ForceSimulation/forces/ManyBodyForce.swift index 715086c..74a4c9c 100644 --- a/Sources/ForceSimulation/forces/ManyBodyForce.swift +++ b/Sources/ForceSimulation/forces/ManyBodyForce.swift @@ -195,17 +195,17 @@ final public class ManyBodyForce: Force where N: Identifiable { let quad = try QuadTree2( nodes: sim.simulationNodes.map { ($0, $0.position) } ) { - // this switch is only called on root init + // this switch is only called on root init // but it significantly slows down the performance - // - // return switch self.mass { - // case .constant(let m): - // MassQuadTreeDelegate> { _ in m } - // case .varied(_): + // + return switch self.mass { + case .constant(let m): + MassQuadTreeDelegate> { _ in m } + case .varied(_): MassQuadTreeDelegate> { self.precalculatedMass[$0, default: 0.0] } - // } + } } var forces = [Vector2f](repeating: .zero, count: sim.simulationNodes.count)