|
19 | 19 | final class QuadTree { |
20 | 20 |
|
21 | 21 | private static final double MIN_DIST = 0.01; |
| 22 | + private static final double MIN_DIST_SQ = MIN_DIST * MIN_DIST; |
22 | 23 |
|
23 | 24 | private double cx, cy; // center of mass |
24 | 25 | private int mass; // number of bodies |
@@ -108,42 +109,45 @@ private void putInChild(int idx, double px, double py) { |
108 | 109 | * Computes repulsive force on body {@code i} at (px, py) from this |
109 | 110 | * quadtree node, accumulating into disp[0] (dx) and disp[1] (dy). |
110 | 111 | * |
111 | | - * @param i index of the body (skip self) |
112 | | - * @param px x-position of body i |
113 | | - * @param py y-position of body i |
114 | | - * @param k optimal distance constant |
115 | | - * @param disp displacement array to accumulate into [dx, dy] |
116 | | - * @param theta Barnes-Hut opening angle (lower = more accurate) |
| 112 | + * @param i index of the body (skip self) |
| 113 | + * @param px x-position of body i |
| 114 | + * @param py y-position of body i |
| 115 | + * @param kSq pre-computed k² (optimal distance squared) |
| 116 | + * @param disp displacement array to accumulate into [dx, dy] |
| 117 | + * @param thetaSq pre-computed theta² for the Barnes-Hut opening angle |
117 | 118 | */ |
118 | 119 | void applyRepulsion(int i, double px, double py, |
119 | | - double k, double[] disp, double theta) { |
| 120 | + double kSq, double[] disp, double thetaSq) { |
120 | 121 | if (mass == 0) return; |
121 | 122 |
|
122 | 123 | double dx = px - cx; |
123 | 124 | double dy = py - cy; |
124 | 125 | double distSq = dx * dx + dy * dy; |
125 | | - double dist = Math.sqrt(distSq); |
126 | 126 |
|
127 | 127 | if (mass == 1 && bodyIndex >= 0) { |
128 | 128 | if (bodyIndex == i) return; |
129 | | - if (dist < MIN_DIST) dist = MIN_DIST; |
130 | | - double force = (k * k) / dist; |
131 | | - disp[0] += (dx / dist) * force; |
132 | | - disp[1] += (dy / dist) * force; |
| 129 | + if (distSq < MIN_DIST_SQ) distSq = MIN_DIST_SQ; |
| 130 | + // force = k² / dist; fx = (dx/dist)*force = dx * k² / dist² |
| 131 | + double f = kSq / distSq; |
| 132 | + disp[0] += dx * f; |
| 133 | + disp[1] += dy * f; |
133 | 134 | return; |
134 | 135 | } |
135 | 136 |
|
136 | | - if (size / dist < theta) { |
137 | | - if (dist < MIN_DIST) dist = MIN_DIST; |
138 | | - double force = (k * k) * mass / dist; |
139 | | - disp[0] += (dx / dist) * force; |
140 | | - disp[1] += (dy / dist) * force; |
| 137 | + // Barnes-Hut check: size/dist < theta ⟺ size²/distSq < theta² |
| 138 | + // Avoids Math.sqrt in the common "far enough" case. |
| 139 | + double sizeSq = size * size; |
| 140 | + if (sizeSq < thetaSq * distSq) { |
| 141 | + if (distSq < MIN_DIST_SQ) distSq = MIN_DIST_SQ; |
| 142 | + double f = kSq * mass / distSq; |
| 143 | + disp[0] += dx * f; |
| 144 | + disp[1] += dy * f; |
141 | 145 | return; |
142 | 146 | } |
143 | 147 |
|
144 | | - if (nw != null) nw.applyRepulsion(i, px, py, k, disp, theta); |
145 | | - if (ne != null) ne.applyRepulsion(i, px, py, k, disp, theta); |
146 | | - if (sw != null) sw.applyRepulsion(i, px, py, k, disp, theta); |
147 | | - if (se != null) se.applyRepulsion(i, px, py, k, disp, theta); |
| 148 | + if (nw != null) nw.applyRepulsion(i, px, py, kSq, disp, thetaSq); |
| 149 | + if (ne != null) ne.applyRepulsion(i, px, py, kSq, disp, thetaSq); |
| 150 | + if (sw != null) sw.applyRepulsion(i, px, py, kSq, disp, thetaSq); |
| 151 | + if (se != null) se.applyRepulsion(i, px, py, kSq, disp, thetaSq); |
148 | 152 | } |
149 | 153 | } |
0 commit comments