StarSim

StarSim.git
git clone git://git.lenczewski.org/StarSim.git
Log | Files | Refs | README | LICENSE

commit 6e4dc27862e4114bd8141524b1d2258af3b77458
parent 73d0407a413799f3075c25516674e761756ba5d0
Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com>
Date:   Thu,  4 Jul 2019 21:23:49 +0100

Finished initial Barnes-Hut algorithm implementation.

Diffstat:
MStarSim/StarSimLib/Constants.cs | 7+++++++
MStarSim/StarSimLib/Data Structures/Body.cs | 7++++---
MStarSim/StarSimLib/Data Structures/OctantTree.cs | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
MStarSim/StarSimLib/Physics/BodyUpdater.cs | 32++++++++++++++++++++++++--------
4 files changed, 91 insertions(+), 17 deletions(-)

diff --git a/StarSim/StarSimLib/Constants.cs b/StarSim/StarSimLib/Constants.cs @@ -69,6 +69,13 @@ namespace StarSimLib public const double TimeStep = SecondsPerTick * (SimulationRate / (float)FrameRate) * SimulationRate; /// <summary> + /// The tolerance of the mass grouping approximation in the simulation. A + /// body is only accelerated when the ratio of the tree's width to the + /// distance (from the tree's center of mass to the body) is less than this. + /// </summary> + public const double TreeTheta = 0.5; + + /// <summary> /// The maximum radius within which <see cref="Body"/>s will be placed (1e18f). /// </summary> public const double UniverseSize = 1e18f; diff --git a/StarSim/StarSimLib/Data Structures/Body.cs b/StarSim/StarSimLib/Data Structures/Body.cs @@ -159,8 +159,9 @@ namespace StarSimLib.Data_Structures /// <param name="otherBody">The other instance with which to collide.</param> public void Collide(Body otherBody) { - mass += otherBody.Mass; - velocity += otherBody.Velocity; + mass += otherBody.mass; + velocity += otherBody.velocity; + force += otherBody.force; } /// <summary> @@ -186,7 +187,7 @@ namespace StarSimLib.Data_Structures /// </summary> /// <param name="octant">The octant to check.</param> /// <returns>Whether this <see cref="Body"/> instance is inside the given <see cref="Octant"/> instance.</returns> - public bool InOctant(Octant octant) + public bool IsInOctant(Octant octant) { return octant.ContainsPoint(position); } diff --git a/StarSim/StarSimLib/Data Structures/OctantTree.cs b/StarSim/StarSimLib/Data Structures/OctantTree.cs @@ -77,16 +77,36 @@ namespace StarSimLib.Data_Structures // this is an empty instance that has not yet had any bodies added to it. body = newBody; } + else if (IsExternal()) + { + // this instance is 'external' and contains another body. figure out where the new body should go and + // create a new octant tree instance to hold the new body + foreach (OctantTree tree in childTrees) + { + if (body.IsInOctant(tree.octant)) + { + tree.AddBody(body); + } + } + + AddBody(newBody); + } else if (!IsExternal()) { // this instance already has a body to represent it, and it is not an 'external' tree instance, that is // it has child trees of its own. figure out in which child tree the new body should be stored and update - // any further child nodes with recursion - } - else if (IsExternal()) - { - // this instance is 'external' and contains another body. figure out where the new body should go and - // create a new octant tree instance to hold the new body. no recursion is necessary + // any further child nodes + + // make the held body an aggregate body + body.Collide(newBody); + + foreach (OctantTree tree in childTrees) + { + if (newBody.IsInOctant(tree.octant)) + { + tree.AddBody(newBody); + } + } } } @@ -134,5 +154,35 @@ namespace StarSimLib.Data_Structures "The given specifier was outside of the valid range."); } } + + /// <summary> + /// Recursively updates the forces on each <see cref="Body"/> instance held in this tree, with respect to the + /// given reference <see cref="Body"/> instance. + /// </summary> + /// <param name="referenceBody">The body instance against which force updates are made.</param> + public void UpdateForces(Body referenceBody) + { + if (IsExternal()) + { + // since this tree instance is 'external' it has no children. we can treat it as a single body + if (body != referenceBody) + { + referenceBody?.AddForce(body); + } + } + else if (octant.Length / body.DistanceTo(referenceBody, out _) < Constants.TreeTheta) + { + // otherwise if the octant length divided by the distance to the body (the width to distance ratio) is + // within a defined tolerance, we consider the tree to be effectively a single massive body + referenceBody?.AddForce(body); + } + else + { + foreach (OctantTree tree in childTrees) + { + tree?.UpdateForces(referenceBody); + } + } + } } } \ No newline at end of file diff --git a/StarSim/StarSimLib/Physics/BodyUpdater.cs b/StarSim/StarSimLib/Physics/BodyUpdater.cs @@ -17,6 +17,12 @@ namespace StarSimLib.Physics public static class BodyUpdater { /// <summary> + /// Represents the main universe. Anything outside this octant of space is not updated when using the + /// <see cref="UpdateBodiesBarnesHut"/> update method. + /// </summary> + public static readonly Octant UniverseOctant = new Octant(new Vector4(), Constants.UniverseSize); + + /// <summary> /// Updates the positions of all the given <see cref="Body"/>s with O(n^2) time complexity, with the given time step. /// </summary> /// <param name="bodies">The collection of <see cref="Body"/>s whose positions to update.</param> @@ -48,19 +54,29 @@ namespace StarSimLib.Physics public static void UpdateBodiesBruteForce(IEnumerable<Body> bodies, double deltaTime) { IEnumerable<Body> bodyEnumerable = bodies as Body[] ?? bodies.ToArray(); - Vector4 forceVector = new Vector4(); + OctantTree barnesHutTree = new OctantTree(UniverseOctant); + // we construct our barnes-hut octant tree foreach (Body body in bodyEnumerable) { - // resets the force vector to avoid another instantiation and allocation - forceVector.X = 0; - forceVector.Y = 0; - forceVector.Z = 0; + if (body.IsInOctant(UniverseOctant)) + { + // only update a body's position if it is within the bounds of the universe + barnesHutTree.AddBody(body); + } + } - // use LINQ expression as it is more concise; sum attraction vectors for all other bodies - forceVector = bodyEnumerable.Where(b => b != body).Aggregate(forceVector, (current, b) => current + Body.GetForceBetween(body, b)); + // we update the positions of the bodies in the populated tree + foreach (Body body in bodyEnumerable) + { + body.ResetForce(); - body.Update(forceVector, deltaTime); + if (body.IsInOctant(UniverseOctant)) + { + barnesHutTree.UpdateForces(body); + + body.Update(deltaTime); + } } } }