1+ import kotlinx.coroutines.runBlocking
2+ import org.openrndr.application
3+ import org.openrndr.color.ColorRGBa
4+ import org.openrndr.extra.force2d.Body
5+ import org.openrndr.extra.force2d.ForceSimulation
6+ import org.openrndr.extra.force2d.Link
7+ import org.openrndr.extra.force2d.Node
8+ import org.openrndr.extra.force2d.linkLengthConstraint
9+ import org.openrndr.extra.force2d.nodeRepulseForce
10+ import org.openrndr.extra.force2d.rectangularBoundsConstraint
11+ import org.openrndr.extra.kdtree.kdTree
12+ import org.openrndr.extra.noise.scatter
13+ import org.openrndr.math.Vector2
14+ import org.openrndr.shape.LineSegment
15+
16+ /* *
17+ * Demonstrates a simple force graph layout
18+ */
19+ fun main () {
20+ application {
21+ configure {
22+ width = 720
23+ height = 720
24+ }
25+ program {
26+ val pts = drawer.bounds.scatter(20.0 )
27+ val index = pts.mapIndexed { index, vector2 -> vector2 to index }.toMap()
28+ val kd = pts.kdTree()
29+
30+ val nodes = pts.map {
31+ Node (it, it, Vector2 .ZERO , radius = 4.0 )
32+ }
33+
34+ val links = pts.flatMapIndexed { source, it ->
35+ kd.findKNearest(it, 2 ).map {
36+ val idx = index.getValue(it)
37+ Link (source, idx)
38+ }
39+ }
40+
41+ val body = Body (nodes, links = links).apply {
42+ linkLengthConstraint {
43+ compliance = 1E- 1
44+
45+ }
46+ nodeRepulseForce {
47+ searchRadius = 100.0
48+ strength = 10.0
49+ }
50+ rectangularBoundsConstraint {
51+ bounds = drawer.bounds.offsetEdges(- 10.0 )
52+ }
53+ }
54+
55+ val sim = ForceSimulation (mutableListOf (body))
56+
57+ extend {
58+ runBlocking {
59+ sim.simulate(1.0 / 60.0 , 10 )
60+ }
61+
62+ drawer.stroke = ColorRGBa .PINK
63+ drawer.lineSegments(body.links.map {
64+ LineSegment (body.nodes[it.source].position, body.nodes[it.target].position)
65+ })
66+
67+ drawer.circles(body.nodes.map { it.position }, 4.0 )
68+ }
69+ }
70+ }
71+ }
0 commit comments