Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: CollideForce calculation #5

Merged
merged 3 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
A visualization-purposed force simulation library.


#### Examples


<img width="712" alt="ForceDirectedGraphLight" src="https://github.com/li3zhen1/Grape/assets/45376537/e0e8049d-25c2-4e5c-9623-6bf43ddddfa5">

#### 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/5f57c223-0126-428a-a72d-d9a3ed38059d





#### Features

| Feature | Status |
Expand All @@ -27,9 +27,30 @@ https://github.com/li3zhen1/Grape/assets/45376537/5f57c223-0126-428a-a72d-d9a3ed
| 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).
21 changes: 11 additions & 10 deletions Sources/ForceSimulation/forces/CollideForce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -184,7 +185,7 @@ extension CollideForce: Force {
extension Simulation {
@discardableResult
public func createCollideForce(
radius: CollideForce<N>.CollideRadius,
radius: CollideForce<N>.CollideRadius = .constant(3.0),
strength: Float = 1.0,
iterationsPerTick: Int = 1
) -> CollideForce<N> {
Expand Down
1 change: 1 addition & 0 deletions Sources/ForceSimulation/forces/LinkForce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ final public class LinkForce<N>: Force where N: Identifiable {
var calculatedBias: [Float] = []
weak var simulation: Simulation<N>?


var iterations: Int

internal init(
Expand Down
14 changes: 7 additions & 7 deletions Sources/ForceSimulation/forces/ManyBodyForce.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,17 @@ final public class ManyBodyForce<N>: 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<SimulationNode<N.ID>> { _ in m }
// case .varied(_):
//
return switch self.mass {
case .constant(let m):
MassQuadTreeDelegate<SimulationNode<N.ID>> { _ in m }
case .varied(_):
MassQuadTreeDelegate<SimulationNode<N.ID>> {
self.precalculatedMass[$0, default: 0.0]
}
// }
}
}

var forces = [Vector2f](repeating: .zero, count: sim.simulationNodes.count)
Expand Down