1+ package org.openrndr.extra.force2d
2+
3+ import org.openrndr.extra.force2d.quadtree.QuadTreeNode
4+ import org.openrndr.math.Vector2
5+ import org.openrndr.math.Vector2.Axis
6+ import org.openrndr.shape.Rectangle
7+ import kotlin.math.sqrt
8+ import kotlin.random.Random
9+
10+ private fun Rectangle.outerSquare (): Rectangle {
11+ return if (this .majorAxis == Axis .X ) {
12+ Rectangle (x, y, width, width)
13+ } else {
14+ Rectangle (x, y, height, height)
15+ }
16+ }
17+
18+
19+ class ManyNodeForce (val body : Body ): Force {
20+
21+ var theta = 1.0
22+ val random = Random (0 )
23+ lateinit private var quadTree: QuadTreeNode
24+
25+ var strength: (Node ) -> Double = { - 0.0 }
26+
27+ private var strengths = DoubleArray (body.nodes.size)
28+
29+
30+ override suspend fun initializeFrame (body : Body ) {
31+ body.updateBounds()
32+ val bounds = body.bounds.outerSquare()
33+ quadTree = QuadTreeNode (bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height)
34+ for (i in body.nodes.indices) {
35+ strengths[i] = strength(body.nodes[i])
36+ }
37+
38+ for ((index, node) in body.nodes.withIndex()) {
39+ quadTree.add(node, strengths[index])
40+ }
41+
42+ }
43+
44+ private fun applyForce (node : Node , delta : Vector2 , distance : Double , distance2 : Double , strength : Double , alpha : Double ) {
45+ node.velocity + = delta * (strength * alpha / (distance2 * distance))
46+ }
47+ private fun accumulateForce (node : Node , octNode : QuadTreeNode , theta2 : Double , distanceMin2 : Double , distanceMax2 : Double , alpha : Double ) {
48+ val delta = octNode.centerOfMass - node.position
49+ val d2 = delta.dot(delta)
50+ val w = octNode.xmax - octNode.xmin
51+
52+ if (w * w / theta2 < d2) {
53+ if (d2 < distanceMax2) {
54+ if (d2 < distanceMin2) {
55+ applyForce(node, delta, sqrt(d2), distanceMin2, octNode.totalStrength, alpha)
56+ } else {
57+ applyForce(node, delta, sqrt(d2), d2, octNode.totalStrength, alpha)
58+ }
59+ }
60+ } else {
61+ if (octNode.children != null ) {
62+ for (child in octNode.children!! ) {
63+ if (child != null ) {
64+ accumulateForce(node, child, theta2, distanceMin2, distanceMax2, alpha)
65+ }
66+ }
67+ } else if (octNode.node != null && octNode.node != node) {
68+ if (d2 < distanceMax2) {
69+ var dist2 = d2
70+ var dist = sqrt(d2)
71+ if (d2 < 0.00001 ) { // Jitter for coincident nodes
72+ val rx = (random.nextDouble() - 0.5 ) * 0.01
73+ val ry = (random.nextDouble() - 0.5 ) * 0.01
74+ val jitteredDelta = Vector2 (rx, ry)
75+ dist2 = jitteredDelta.dot(jitteredDelta)
76+ dist = sqrt(dist2)
77+ applyForce(node, jitteredDelta, dist, maxOf(dist2, distanceMin2), octNode.totalStrength, alpha)
78+ } else {
79+ applyForce(node, delta, dist, maxOf(dist2, distanceMin2), octNode.totalStrength, alpha)
80+ }
81+ }
82+ }
83+ }
84+ }
85+
86+ override suspend fun apply (body : Body , dt : Double ) {
87+
88+ val theta2 = theta * theta
89+ for (i in body.nodes.indices) {
90+ val node = body.nodes[i]
91+ accumulateForce(node, quadTree, theta2, 0.0 , 360.0 * 360.0 , 1.0 )
92+ }
93+
94+ }
95+ }
96+
97+ fun Body.manyNodeForce (configure : ManyNodeForce .() -> Unit ) {
98+ forces.add(ManyNodeForce (this ).apply (configure))
99+ }
0 commit comments