Changeset 4068 for trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.Netron/3.0.2672.12446/Netron.Diagramming.Core-3.0.2672.12446/Layout/Force/NBodyForce.cs
- Timestamp:
- 07/22/10 00:44:01 (14 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/sources/HeuristicLab.ExtLibs/HeuristicLab.Netron/3.0.2672.12446/Netron.Diagramming.Core-3.0.2672.12446/Layout/Force/NBodyForce.cs
r2768 r4068 2 2 using System.Collections.Generic; 3 3 using System.Diagnostics; 4 using System.Text; 5 using Netron.Diagramming.Core.Analysis; 6 namespace Netron.Diagramming.Core.Layout.Force 7 { 8 /// <summary> 9 /// <para> 10 /// Force function which computes an n-body force such as gravity, 11 /// anti-gravity, or the results of electric charges. This function implements 12 /// the the Barnes-Hut algorithm for efficient n-body force simulations, 13 /// using a quad-tree with aggregated mass values to compute the n-body 14 /// force in O(N log N) time, where N is the number of ForceItems.</para> 15 /// 16 /// <para>The algorithm used is that of J. Barnes and P. Hut, in their research 17 /// paper <i>A Hierarchical O(n log n) force calculation algorithm</i>, Nature, 18 /// v.324, December 1986. For more details on the algorithm, see one of 19 /// the following links -- 20 /// <list type="bullet"> 21 /// 22 /// <item><a href="http://www.cs.berkeley.edu/~demmel/cs267/lecture26/lecture26.html">James Demmel's UC Berkeley lecture notes</a></item> 23 /// <item><a href="http://www.physics.gmu.edu/~large/lr_forces/desc/bh/bhdesc.html">Description of the Barnes-Hut algorithm</a></item> 24 /// <item><a href="http://www.ifa.hawaii.edu/~barnes/treecode/treeguide.html">Joshua Barnes' recent implementation</a></item> 25 /// </list></para> 26 /// </summary> 27 public class NBodyForce : AbstractForce 28 { 29 30 /* 31 The indexing scheme for quadtree child nodes goes row by row. 32 0 | 1 0 -> top left, 1 -> top right 33 ------- 34 2 | 3 2 -> bottom left, 3 -> bottom right 35 */ 36 37 #region Fields 38 private static String[] pnames = new String[] { "GravitationalConstant", 4 namespace Netron.Diagramming.Core.Layout.Force { 5 /// <summary> 6 /// <para> 7 /// Force function which computes an n-body force such as gravity, 8 /// anti-gravity, or the results of electric charges. This function implements 9 /// the the Barnes-Hut algorithm for efficient n-body force simulations, 10 /// using a quad-tree with aggregated mass values to compute the n-body 11 /// force in O(N log N) time, where N is the number of ForceItems.</para> 12 /// 13 /// <para>The algorithm used is that of J. Barnes and P. Hut, in their research 14 /// paper <i>A Hierarchical O(n log n) force calculation algorithm</i>, Nature, 15 /// v.324, December 1986. For more details on the algorithm, see one of 16 /// the following links -- 17 /// <list type="bullet"> 18 /// 19 /// <item><a href="http://www.cs.berkeley.edu/~demmel/cs267/lecture26/lecture26.html">James Demmel's UC Berkeley lecture notes</a></item> 20 /// <item><a href="http://www.physics.gmu.edu/~large/lr_forces/desc/bh/bhdesc.html">Description of the Barnes-Hut algorithm</a></item> 21 /// <item><a href="http://www.ifa.hawaii.edu/~barnes/treecode/treeguide.html">Joshua Barnes' recent implementation</a></item> 22 /// </list></para> 23 /// </summary> 24 public class NBodyForce : AbstractForce { 25 26 /* 27 The indexing scheme for quadtree child nodes goes row by row. 28 0 | 1 0 -> top left, 1 -> top right 29 ------- 30 2 | 3 2 -> bottom left, 3 -> bottom right 31 */ 32 33 #region Fields 34 private static String[] pnames = new String[] { "GravitationalConstant", 39 35 "Distance", "BarnesHutTheta" }; 40 36 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 37 public static float DEFAULT_GRAV_CONSTANT = -1.0f; 38 public static float DEFAULT_MIN_GRAV_CONSTANT = -10f; 39 public static float DEFAULT_MAX_GRAV_CONSTANT = 10f; 40 41 public static float DEFAULT_DISTANCE = -1f; 42 public static float DEFAULT_MIN_DISTANCE = -1f; 43 public static float DEFAULT_MAX_DISTANCE = 500f; 44 45 public static float DEFAULT_THETA = 0.9f; 46 public static float DEFAULT_MIN_THETA = 0.0f; 47 public static float DEFAULT_MAX_THETA = 1.0f; 48 49 public static int GRAVITATIONAL_CONST = 0; 50 public static int MIN_DISTANCE = 1; 51 public static int BARNES_HUT_THETA = 2; 52 53 private float xMin, xMax, yMin, yMax; 54 private QuadTreeNodeFactory factory = new QuadTreeNodeFactory(); 55 private QuadTreeNode root; 56 57 private Random rand = new Random(12345678); // deterministic randomness 58 59 #endregion 60 61 #region Properties 62 63 /** 68 64 * Gets whether this is a force item. 69 65 */ 70 public override bool IsItemForce 71 { 72 get 73 { 74 return true; 75 } 76 } 77 78 /// <summary> 79 /// Gets the parameter names. 80 /// </summary> 81 /// <value></value> 82 /// <returns></returns> 83 protected override String[] ParameterNames 84 { 85 get 86 { 87 return pnames; 88 } 89 } 90 #endregion 91 92 #region Constructor 93 94 95 96 /// <summary> 97 /// Create a new NBodyForce with default parameters. 98 /// </summary> 99 public NBodyForce() 100 : this(DEFAULT_GRAV_CONSTANT, DEFAULT_DISTANCE, DEFAULT_THETA) 101 { 102 103 } 104 #endregion 105 106 #region Methods 107 /// <summary> 108 /// Create a new NBodyForce. 109 /// </summary> 110 /// <param name="gravConstant">the gravitational constant to use. Nodes will attract each other if this value is positive, and will repel each other if it is negative.</param> 111 /// <param name="minDistance">the distance within which two particles will interact. If -1, the value is treated as infinite.</param> 112 /// <param name="theta">the Barnes-Hut parameter theta, which controls when an aggregated mass is used rather than drilling down to individual item mass values.</param> 113 public NBodyForce(float gravConstant, float minDistance, float theta) 114 { 115 parms = new float[] { gravConstant, minDistance, theta }; 116 minValues = new float[] { DEFAULT_MIN_GRAV_CONSTANT, 66 public override bool IsItemForce { 67 get { 68 return true; 69 } 70 } 71 72 /// <summary> 73 /// Gets the parameter names. 74 /// </summary> 75 /// <value></value> 76 /// <returns></returns> 77 protected override String[] ParameterNames { 78 get { 79 return pnames; 80 } 81 } 82 #endregion 83 84 #region Constructor 85 86 87 88 /// <summary> 89 /// Create a new NBodyForce with default parameters. 90 /// </summary> 91 public NBodyForce() 92 : this(DEFAULT_GRAV_CONSTANT, DEFAULT_DISTANCE, DEFAULT_THETA) { 93 94 } 95 #endregion 96 97 #region Methods 98 /// <summary> 99 /// Create a new NBodyForce. 100 /// </summary> 101 /// <param name="gravConstant">the gravitational constant to use. Nodes will attract each other if this value is positive, and will repel each other if it is negative.</param> 102 /// <param name="minDistance">the distance within which two particles will interact. If -1, the value is treated as infinite.</param> 103 /// <param name="theta">the Barnes-Hut parameter theta, which controls when an aggregated mass is used rather than drilling down to individual item mass values.</param> 104 public NBodyForce(float gravConstant, float minDistance, float theta) { 105 parms = new float[] { gravConstant, minDistance, theta }; 106 minValues = new float[] { DEFAULT_MIN_GRAV_CONSTANT, 117 107 DEFAULT_MIN_DISTANCE, DEFAULT_MIN_THETA }; 118 108 maxValues = new float[] { DEFAULT_MAX_GRAV_CONSTANT, 119 109 DEFAULT_MAX_DISTANCE, DEFAULT_MAX_THETA }; 120 root = factory.GetQuadTreeNode(); 121 } 122 123 124 125 126 /// <summary> 127 /// Set the bounds of the region for which to compute the n-body simulation 128 /// </summary> 129 /// <param name="xMin">the minimum x-coordinate</param> 130 /// <param name="yMin">the minimum y-coordinate/param> 131 /// <param name="xMax"> the maximum x-coordinate</param> 132 /// <param name="yMax">the maximum y-coordinate</param> 133 private void setBounds(float xMin, float yMin, float xMax, float yMax) 134 { 135 this.xMin = xMin; 136 this.yMin = yMin; 137 this.xMax = xMax; 138 this.yMax = yMax; 139 } 140 141 142 /// <summary> 143 ///Clears the quadtree of all entries. 144 /// </summary> 145 public void clear() 146 { 147 ClearHelper(root); 148 root = factory.GetQuadTreeNode(); 149 } 150 151 /// <summary> 152 /// Clearing aid 153 /// </summary> 154 /// <param name="n">The n.</param> 155 private void ClearHelper(QuadTreeNode n) 156 { 157 for (int i = 0; i < n.children.Length; i++) 158 { 159 if (n.children[i] != null) 160 ClearHelper(n.children[i]); 161 } 162 factory.Reclaim(n); 163 } 164 165 /// <summary> 166 /// Initialize the simulation with the provided enclosing simulation. After 167 /// this call has been made, the simulation can be queried for the 168 /// n-body force acting on a given item. 169 /// </summary> 170 /// <param name="fsim">the encompassing ForceSimulator</param> 171 public override void Init(ForceSimulator fsim) 172 { 173 clear(); // clear internal state 174 175 // compute and squarify bounds of quadtree 176 float x1 = float.MaxValue, y1 = float.MaxValue; 177 float x2 = float.MinValue, y2 = float.MinValue; 178 foreach (ForceItem item in fsim.Items) 179 { 180 float x = item.Location[0]; 181 float y = item.Location[1]; 182 if (x < x1) x1 = x; 183 if (y < y1) y1 = y; 184 if (x > x2) x2 = x; 185 if (y > y2) y2 = y; 186 } 187 float dx = x2 - x1, dy = y2 - y1; 188 if (dx > dy) { y2 = y1 + dx; } else { x2 = x1 + dy; } 189 setBounds(x1, y1, x2, y2); 190 191 // Insert items into quadtree 192 foreach (ForceItem item in fsim.Items) 193 { 194 Insert(item); 195 } 196 197 // calculate magnitudes and centers of mass 198 CalculateMass(root); 199 } 200 201 /// <summary> 202 /// Inserts an item into the quadtree. 203 /// </summary> 204 /// <param name="item"> the ForceItem to add.</param> 205 public void Insert(ForceItem item) 206 { 207 // Insert item into the quadtrees 208 try 209 { 210 Insert(item, root, xMin, yMin, xMax, yMax); 211 } 212 catch (StackOverflowException e) 213 { 214 // TODO: safe to remove? 215 Trace.WriteLine(e.Message); 216 } 217 } 218 219 /// <summary> 220 /// Inserts the specified force. 221 /// </summary> 222 /// <param name="p">The p.</param> 223 /// <param name="n">The n.</param> 224 /// <param name="x1">The x1.</param> 225 /// <param name="y1">The y1.</param> 226 /// <param name="x2">The x2.</param> 227 /// <param name="y2">The y2.</param> 228 private void Insert(ForceItem p, QuadTreeNode n, float x1, float y1, float x2, float y2) 229 { 230 // try to Insert particle p at node n in the quadtree 231 // by construction, each leaf will contain either 1 or 0 particles 232 if (n.hasChildren) 233 { 234 // n contains more than 1 particle 235 InsertHelper(p, n, x1, y1, x2, y2); 236 } 237 else if (n.value != null) 238 { 239 // n contains 1 particle 240 if (IsSameLocation(n.value, p)) 241 { 242 InsertHelper(p, n, x1, y1, x2, y2); 243 } 244 else 245 { 246 ForceItem v = n.value; n.value = null; 247 InsertHelper(v, n, x1, y1, x2, y2); 248 InsertHelper(p, n, x1, y1, x2, y2); 249 } 250 } 251 else 252 { 253 // n is empty, so is a leaf 254 n.value = p; 255 } 256 } 257 258 /// <summary> 259 /// Determines whether the two force are at the same location. 260 /// </summary> 261 /// <param name="f1">The f1.</param> 262 /// <param name="f2">The f2.</param> 263 /// <returns> 264 /// <c>true</c> if [is same location] [the specified f1]; otherwise, <c>false</c>. 265 /// </returns> 266 private static bool IsSameLocation(ForceItem f1, ForceItem f2) 267 { 268 float dx = Math.Abs(f1.Location[0] - f2.Location[0]); 269 float dy = Math.Abs(f1.Location[1] - f2.Location[1]); 270 return (dx < 0.01 && dy < 0.01); 271 } 272 273 /// <summary> 274 /// Inserts helper method. 275 /// </summary> 276 /// <param name="p">The p.</param> 277 /// <param name="n">The n.</param> 278 /// <param name="x1">The x1.</param> 279 /// <param name="y1">The y1.</param> 280 /// <param name="x2">The x2.</param> 281 /// <param name="y2">The y2.</param> 282 private void InsertHelper(ForceItem p, QuadTreeNode n, float x1, float y1, float x2, float y2) 283 { 284 float x = p.Location[0], y = p.Location[1]; 285 float splitx = (x1 + x2) / 2; 286 float splity = (y1 + y2) / 2; 287 int i = (x >= splitx ? 1 : 0) + (y >= splity ? 2 : 0); 288 // create new child node, if necessary 289 if (n.children[i] == null) 290 { 291 n.children[i] = factory.GetQuadTreeNode(); 292 n.hasChildren = true; 293 } 294 // update bounds 295 if (i == 1 || i == 3) x1 = splitx; else x2 = splitx; 296 if (i > 1) y1 = splity; else y2 = splity; 297 // recurse 298 Insert(p, n.children[i], x1, y1, x2, y2); 299 } 300 301 /// <summary> 302 /// Calculates the mass. 303 /// </summary> 304 /// <param name="n">The n.</param> 305 private void CalculateMass(QuadTreeNode n) 306 { 307 float xcom = 0, ycom = 0; 308 n.mass = 0; 309 if (n.hasChildren) 310 { 311 for (int i = 0; i < n.children.Length; i++) 312 { 313 if (n.children[i] != null) 314 { 315 CalculateMass(n.children[i]); 316 n.mass += n.children[i].mass; 317 xcom += n.children[i].mass * n.children[i].com[0]; 318 ycom += n.children[i].mass * n.children[i].com[1]; 319 } 320 } 321 } 322 if (n.value != null) 323 { 324 n.mass += n.value.Mass; 325 xcom += n.value.Mass * n.value.Location[0]; 326 ycom += n.value.Mass * n.value.Location[1]; 327 } 328 n.com[0] = xcom / n.mass; 329 n.com[1] = ycom / n.mass; 330 } 331 332 /** 333 * Calculates the force vector acting on the given item. 334 * @param item the ForceItem for which to compute the force 335 */ 336 public override void GetForce(ForceItem item) 337 { 338 try 339 { 340 ForceHelper(item, root, xMin, yMin, xMax, yMax); 341 } 342 catch (StackOverflowException e) 343 { 344 // TODO: safe to remove? 345 Trace.WriteLine(e.Message); 346 } 347 } 348 349 /// <summary> 350 /// Utility method. 351 /// </summary> 352 /// <param name="item">The item.</param> 353 /// <param name="n">The n.</param> 354 /// <param name="x1">The x1.</param> 355 /// <param name="y1">The y1.</param> 356 /// <param name="x2">The x2.</param> 357 /// <param name="y2">The y2.</param> 358 private void ForceHelper(ForceItem item, QuadTreeNode n, float x1, float y1, float x2, float y2) 359 { 360 float dx = n.com[0] - item.Location[0]; 361 float dy = n.com[1] - item.Location[1]; 362 float r = (float)Math.Sqrt(dx * dx + dy * dy); 363 bool same = false; 364 if (r == 0.0f) 365 { 366 // if items are in the exact same place, add some noise 367 dx = Convert.ToSingle((rand.NextDouble() - 0.5D) / 50.0D); 368 dy = Convert.ToSingle((rand.NextDouble() - 0.5D) / 50.0D); 369 r = (float)Math.Sqrt(dx * dx + dy * dy); 370 same = true; 371 } 372 bool minDist = parms[MIN_DISTANCE] > 0f && r > parms[MIN_DISTANCE]; 373 374 // the Barnes-Hut approximation criteria is if the ratio of the 375 // size of the quadtree box to the distance between the point and 376 // the box's center of mass is beneath some threshold theta. 377 if ((!n.hasChildren && n.value != item) || 378 (!same && (x2 - x1) / r < parms[BARNES_HUT_THETA])) 379 { 380 if (minDist) return; 381 // either only 1 particle or we meet criteria 382 // for Barnes-Hut approximation, so calc force 383 float v = parms[GRAVITATIONAL_CONST] * item.Mass * n.mass 384 / (r * r * r); 385 item.Force[0] += v * dx; 386 item.Force[1] += v * dy; 387 } 388 else if (n.hasChildren) 389 { 390 // recurse for more accurate calculation 391 float splitx = (x1 + x2) / 2; 392 float splity = (y1 + y2) / 2; 393 for (int i = 0; i < n.children.Length; i++) 394 { 395 if (n.children != null && n.children[i] != null) 396 { 397 ForceHelper(item, n.children[i], 398 (i == 1 || i == 3 ? splitx : x1), (i > 1 ? splity : y1), 399 (i == 1 || i == 3 ? x2 : splitx), (i > 1 ? y2 : splity)); 400 } 401 } 402 if (minDist) return; 403 if (n.value != null && n.value != item) 404 { 405 float v = parms[GRAVITATIONAL_CONST] * item.Mass * n.value.Mass 406 / (r * r * r); 407 item.Force[0] += v * dx; 408 item.Force[1] += v * dy; 409 } 410 } 411 } 412 #endregion 413 414 #region Classes 415 /// <summary> 416 /// Represents a node in the quadtree. 417 /// </summary> 418 public sealed class QuadTreeNode 419 { 420 public QuadTreeNode() 421 { 422 com = new float[] { 0.0f, 0.0f }; 423 children = new QuadTreeNode[4]; 424 } // 425 public bool hasChildren = false; 426 public float mass; // total mass held by this node 427 public float[] com; // center of mass of this node 428 public ForceItem value; // ForceItem in this node, null if node has children 429 public QuadTreeNode[] children; // children nodes 430 } 431 432 433 434 435 ///<summary> 436 ///Helper class to minimize number of object creations across multiple uses of the quadtree. 437 ///</summary> 438 public sealed class QuadTreeNodeFactory 439 { 440 #region Fields 441 private int maxNodes = 50000; 442 private List<QuadTreeNode> nodes = new List<QuadTreeNode>(); 443 #endregion 444 445 /// <summary> 446 /// Gets the quadtree node. 447 /// </summary> 448 /// <returns></returns> 449 public QuadTreeNode GetQuadTreeNode() 450 { 451 if (nodes.Count > 0) 452 { 453 QuadTreeNode node = this.nodes[nodes.Count - 1]; 454 nodes.Remove(node); 455 return node; 456 } 457 else 458 { 459 return new QuadTreeNode(); 460 } 461 } 462 /// <summary> 463 /// Reclaims the specified node. 464 /// </summary> 465 /// <param name="n">The n.</param> 466 public void Reclaim(QuadTreeNode n) 467 { 468 n.mass = 0; 469 n.com[0] = 0.0f; n.com[1] = 0.0f; 470 n.value = null; 471 n.hasChildren = false; 472 for (int i = 0; i < n.children.Length; i++) 473 { 474 n.children[i] = null; 475 } 476 //n.children = null; 477 if (nodes.Count < maxNodes) 478 nodes.Add(n); 479 } 480 } 481 #endregion 482 483 } 110 root = factory.GetQuadTreeNode(); 111 } 112 113 114 115 116 /// <summary> 117 /// Set the bounds of the region for which to compute the n-body simulation 118 /// </summary> 119 /// <param name="xMin">the minimum x-coordinate</param> 120 /// <param name="yMin">the minimum y-coordinate/param> 121 /// <param name="xMax"> the maximum x-coordinate</param> 122 /// <param name="yMax">the maximum y-coordinate</param> 123 private void setBounds(float xMin, float yMin, float xMax, float yMax) { 124 this.xMin = xMin; 125 this.yMin = yMin; 126 this.xMax = xMax; 127 this.yMax = yMax; 128 } 129 130 131 /// <summary> 132 ///Clears the quadtree of all entries. 133 /// </summary> 134 public void clear() { 135 ClearHelper(root); 136 root = factory.GetQuadTreeNode(); 137 } 138 139 /// <summary> 140 /// Clearing aid 141 /// </summary> 142 /// <param name="n">The n.</param> 143 private void ClearHelper(QuadTreeNode n) { 144 for (int i = 0; i < n.children.Length; i++) { 145 if (n.children[i] != null) 146 ClearHelper(n.children[i]); 147 } 148 factory.Reclaim(n); 149 } 150 151 /// <summary> 152 /// Initialize the simulation with the provided enclosing simulation. After 153 /// this call has been made, the simulation can be queried for the 154 /// n-body force acting on a given item. 155 /// </summary> 156 /// <param name="fsim">the encompassing ForceSimulator</param> 157 public override void Init(ForceSimulator fsim) { 158 clear(); // clear internal state 159 160 // compute and squarify bounds of quadtree 161 float x1 = float.MaxValue, y1 = float.MaxValue; 162 float x2 = float.MinValue, y2 = float.MinValue; 163 foreach (ForceItem item in fsim.Items) { 164 float x = item.Location[0]; 165 float y = item.Location[1]; 166 if (x < x1) x1 = x; 167 if (y < y1) y1 = y; 168 if (x > x2) x2 = x; 169 if (y > y2) y2 = y; 170 } 171 float dx = x2 - x1, dy = y2 - y1; 172 if (dx > dy) { y2 = y1 + dx; } else { x2 = x1 + dy; } 173 setBounds(x1, y1, x2, y2); 174 175 // Insert items into quadtree 176 foreach (ForceItem item in fsim.Items) { 177 Insert(item); 178 } 179 180 // calculate magnitudes and centers of mass 181 CalculateMass(root); 182 } 183 184 /// <summary> 185 /// Inserts an item into the quadtree. 186 /// </summary> 187 /// <param name="item"> the ForceItem to add.</param> 188 public void Insert(ForceItem item) { 189 // Insert item into the quadtrees 190 try { 191 Insert(item, root, xMin, yMin, xMax, yMax); 192 } 193 catch (StackOverflowException e) { 194 // TODO: safe to remove? 195 Trace.WriteLine(e.Message); 196 } 197 } 198 199 /// <summary> 200 /// Inserts the specified force. 201 /// </summary> 202 /// <param name="p">The p.</param> 203 /// <param name="n">The n.</param> 204 /// <param name="x1">The x1.</param> 205 /// <param name="y1">The y1.</param> 206 /// <param name="x2">The x2.</param> 207 /// <param name="y2">The y2.</param> 208 private void Insert(ForceItem p, QuadTreeNode n, float x1, float y1, float x2, float y2) { 209 // try to Insert particle p at node n in the quadtree 210 // by construction, each leaf will contain either 1 or 0 particles 211 if (n.hasChildren) { 212 // n contains more than 1 particle 213 InsertHelper(p, n, x1, y1, x2, y2); 214 } else if (n.value != null) { 215 // n contains 1 particle 216 if (IsSameLocation(n.value, p)) { 217 InsertHelper(p, n, x1, y1, x2, y2); 218 } else { 219 ForceItem v = n.value; n.value = null; 220 InsertHelper(v, n, x1, y1, x2, y2); 221 InsertHelper(p, n, x1, y1, x2, y2); 222 } 223 } else { 224 // n is empty, so is a leaf 225 n.value = p; 226 } 227 } 228 229 /// <summary> 230 /// Determines whether the two force are at the same location. 231 /// </summary> 232 /// <param name="f1">The f1.</param> 233 /// <param name="f2">The f2.</param> 234 /// <returns> 235 /// <c>true</c> if [is same location] [the specified f1]; otherwise, <c>false</c>. 236 /// </returns> 237 private static bool IsSameLocation(ForceItem f1, ForceItem f2) { 238 float dx = Math.Abs(f1.Location[0] - f2.Location[0]); 239 float dy = Math.Abs(f1.Location[1] - f2.Location[1]); 240 return (dx < 0.01 && dy < 0.01); 241 } 242 243 /// <summary> 244 /// Inserts helper method. 245 /// </summary> 246 /// <param name="p">The p.</param> 247 /// <param name="n">The n.</param> 248 /// <param name="x1">The x1.</param> 249 /// <param name="y1">The y1.</param> 250 /// <param name="x2">The x2.</param> 251 /// <param name="y2">The y2.</param> 252 private void InsertHelper(ForceItem p, QuadTreeNode n, float x1, float y1, float x2, float y2) { 253 float x = p.Location[0], y = p.Location[1]; 254 float splitx = (x1 + x2) / 2; 255 float splity = (y1 + y2) / 2; 256 int i = (x >= splitx ? 1 : 0) + (y >= splity ? 2 : 0); 257 // create new child node, if necessary 258 if (n.children[i] == null) { 259 n.children[i] = factory.GetQuadTreeNode(); 260 n.hasChildren = true; 261 } 262 // update bounds 263 if (i == 1 || i == 3) x1 = splitx; else x2 = splitx; 264 if (i > 1) y1 = splity; else y2 = splity; 265 // recurse 266 Insert(p, n.children[i], x1, y1, x2, y2); 267 } 268 269 /// <summary> 270 /// Calculates the mass. 271 /// </summary> 272 /// <param name="n">The n.</param> 273 private void CalculateMass(QuadTreeNode n) { 274 float xcom = 0, ycom = 0; 275 n.mass = 0; 276 if (n.hasChildren) { 277 for (int i = 0; i < n.children.Length; i++) { 278 if (n.children[i] != null) { 279 CalculateMass(n.children[i]); 280 n.mass += n.children[i].mass; 281 xcom += n.children[i].mass * n.children[i].com[0]; 282 ycom += n.children[i].mass * n.children[i].com[1]; 283 } 284 } 285 } 286 if (n.value != null) { 287 n.mass += n.value.Mass; 288 xcom += n.value.Mass * n.value.Location[0]; 289 ycom += n.value.Mass * n.value.Location[1]; 290 } 291 n.com[0] = xcom / n.mass; 292 n.com[1] = ycom / n.mass; 293 } 294 295 /** 296 * Calculates the force vector acting on the given item. 297 * @param item the ForceItem for which to compute the force 298 */ 299 public override void GetForce(ForceItem item) { 300 try { 301 ForceHelper(item, root, xMin, yMin, xMax, yMax); 302 } 303 catch (StackOverflowException e) { 304 // TODO: safe to remove? 305 Trace.WriteLine(e.Message); 306 } 307 } 308 309 /// <summary> 310 /// Utility method. 311 /// </summary> 312 /// <param name="item">The item.</param> 313 /// <param name="n">The n.</param> 314 /// <param name="x1">The x1.</param> 315 /// <param name="y1">The y1.</param> 316 /// <param name="x2">The x2.</param> 317 /// <param name="y2">The y2.</param> 318 private void ForceHelper(ForceItem item, QuadTreeNode n, float x1, float y1, float x2, float y2) { 319 float dx = n.com[0] - item.Location[0]; 320 float dy = n.com[1] - item.Location[1]; 321 float r = (float)Math.Sqrt(dx * dx + dy * dy); 322 bool same = false; 323 if (r == 0.0f) { 324 // if items are in the exact same place, add some noise 325 dx = Convert.ToSingle((rand.NextDouble() - 0.5D) / 50.0D); 326 dy = Convert.ToSingle((rand.NextDouble() - 0.5D) / 50.0D); 327 r = (float)Math.Sqrt(dx * dx + dy * dy); 328 same = true; 329 } 330 bool minDist = parms[MIN_DISTANCE] > 0f && r > parms[MIN_DISTANCE]; 331 332 // the Barnes-Hut approximation criteria is if the ratio of the 333 // size of the quadtree box to the distance between the point and 334 // the box's center of mass is beneath some threshold theta. 335 if ((!n.hasChildren && n.value != item) || 336 (!same && (x2 - x1) / r < parms[BARNES_HUT_THETA])) { 337 if (minDist) return; 338 // either only 1 particle or we meet criteria 339 // for Barnes-Hut approximation, so calc force 340 float v = parms[GRAVITATIONAL_CONST] * item.Mass * n.mass 341 / (r * r * r); 342 item.Force[0] += v * dx; 343 item.Force[1] += v * dy; 344 } else if (n.hasChildren) { 345 // recurse for more accurate calculation 346 float splitx = (x1 + x2) / 2; 347 float splity = (y1 + y2) / 2; 348 for (int i = 0; i < n.children.Length; i++) { 349 if (n.children != null && n.children[i] != null) { 350 ForceHelper(item, n.children[i], 351 (i == 1 || i == 3 ? splitx : x1), (i > 1 ? splity : y1), 352 (i == 1 || i == 3 ? x2 : splitx), (i > 1 ? y2 : splity)); 353 } 354 } 355 if (minDist) return; 356 if (n.value != null && n.value != item) { 357 float v = parms[GRAVITATIONAL_CONST] * item.Mass * n.value.Mass 358 / (r * r * r); 359 item.Force[0] += v * dx; 360 item.Force[1] += v * dy; 361 } 362 } 363 } 364 #endregion 365 366 #region Classes 367 /// <summary> 368 /// Represents a node in the quadtree. 369 /// </summary> 370 public sealed class QuadTreeNode { 371 public QuadTreeNode() { 372 com = new float[] { 0.0f, 0.0f }; 373 children = new QuadTreeNode[4]; 374 } // 375 public bool hasChildren = false; 376 public float mass; // total mass held by this node 377 public float[] com; // center of mass of this node 378 public ForceItem value; // ForceItem in this node, null if node has children 379 public QuadTreeNode[] children; // children nodes 380 } 381 382 383 384 385 ///<summary> 386 ///Helper class to minimize number of object creations across multiple uses of the quadtree. 387 ///</summary> 388 public sealed class QuadTreeNodeFactory { 389 #region Fields 390 private int maxNodes = 50000; 391 private List<QuadTreeNode> nodes = new List<QuadTreeNode>(); 392 #endregion 393 394 /// <summary> 395 /// Gets the quadtree node. 396 /// </summary> 397 /// <returns></returns> 398 public QuadTreeNode GetQuadTreeNode() { 399 if (nodes.Count > 0) { 400 QuadTreeNode node = this.nodes[nodes.Count - 1]; 401 nodes.Remove(node); 402 return node; 403 } else { 404 return new QuadTreeNode(); 405 } 406 } 407 /// <summary> 408 /// Reclaims the specified node. 409 /// </summary> 410 /// <param name="n">The n.</param> 411 public void Reclaim(QuadTreeNode n) { 412 n.mass = 0; 413 n.com[0] = 0.0f; n.com[1] = 0.0f; 414 n.value = null; 415 n.hasChildren = false; 416 for (int i = 0; i < n.children.Length; i++) { 417 n.children[i] = null; 418 } 419 //n.children = null; 420 if (nodes.Count < maxNodes) 421 nodes.Add(n); 422 } 423 } 424 #endregion 425 426 } 484 427 485 428 }
Note: See TracChangeset
for help on using the changeset viewer.