StarSim

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

commit ad2e5e1d47919f10a81b33d49a13ba0ec977e06b
parent 998f16dd6aef77607c0fc3118f889f5d30d2de60
Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com>
Date:   Tue,  2 Jul 2019 07:45:06 +0100

Input handling was completely refactored into a separate class, body shape generation was improved through the use of delegates, and orbit tracers now work as intended.

Diffstat:
MStarSim/StarSim/Program.cs | 2+-
MStarSim/StarSimLib/Constants.cs | 2+-
MStarSim/StarSimLib/Graphics/Drawer.cs | 51++++++++++++++++++++++++++++++++++++++++++---------
MStarSim/StarSimLib/Physics/Body.cs | 48+++++++++++++++++++++++++++++++++++++++++++++---
MStarSim/StarSimLib/Physics/BodyGenerator.cs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++----
MStarSim/StarSimLib/StarSimLib.xml | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
MStarSim/StarSimLib/UI/InputHandler.cs | 33++++++++++++++++++++++++++++++++-
7 files changed, 259 insertions(+), 21 deletions(-)

diff --git a/StarSim/StarSim/Program.cs b/StarSim/StarSim/Program.cs @@ -68,7 +68,7 @@ namespace StarSim bodyPositionUpdater = BodyUpdater.UpdateBodiesBruteForce; #endif bodyDrawer = new Drawer(window, ref bodies, ref bodyShapeMap); - inputHandler = new InputHandler(ref bodyDrawer); + inputHandler = new InputHandler(ref bodies, ref bodyDrawer); Rng = new Random(); } diff --git a/StarSim/StarSimLib/Constants.cs b/StarSim/StarSimLib/Constants.cs @@ -60,7 +60,7 @@ namespace StarSimLib /// <summary> /// The number of previous positions that will be stored by a body /// </summary> - public const int StoredPreviousPositionCount = 100; + public const int StoredPreviousPositionCount = 30; /// <summary> /// The time step for the simulation. diff --git a/StarSim/StarSimLib/Graphics/Drawer.cs b/StarSim/StarSimLib/Graphics/Drawer.cs @@ -94,6 +94,12 @@ namespace StarSimLib.Graphics private readonly Dictionary<Body, CircleShape> managedBodyShapeMap; /// <summary> + /// Maps a <see cref="Body"/> to the <see cref="VertexArray"/> that holds its orbit tracer vertices, and + /// is drawn to the screen behind the <see cref="Body"/> instance. + /// </summary> + private readonly Dictionary<Body, VertexArray> managedBodyTracerVertexArrayMap; + + /// <summary> /// The offset that has to be applied to the positions of a <see cref="CircleShape"/>, so that they appear /// to be in the centre of the render target and not the top left corner. /// </summary> @@ -190,6 +196,14 @@ namespace StarSimLib.Graphics managedBodies = bodies; managedBodyShapeMap = bodyShapeMap; + managedBodyTracerVertexArrayMap = new Dictionary<Body, VertexArray>(); + + foreach (Body body in bodies) + { + // create a vertex array to store orbit tracer points for this body + managedBodyTracerVertexArrayMap.Add(body, + new VertexArray(PrimitiveType.LineStrip, Constants.StoredPreviousPositionCount)); + } } /// <summary> @@ -314,20 +328,39 @@ namespace StarSimLib.Graphics // which is used as the new position of the shape CircleShape shape = managedBodyShapeMap[body]; - foreach (Vector4d previousPosition in body.PreviousPositions) + // get the vertex array used to store orbit tracer points for this body + VertexArray orbitTracerVertexArray = managedBodyTracerVertexArrayMap[body]; + + // we need to clear the vertex array, to ensure that no garbage values are present. otherwise + // there are lines from the origin to the first actually desired position + orbitTracerVertexArray.Clear(); + + // we only attempt to draw tracers if the body instance in question should record its previous position + // otherwise, we skip the relatively expensive rendering code + if (body.RecordPreviousPositions) { - // project previous position onto the screen - Vector4d pointScreenPosition = ProjectPoint(previousPosition); + Vector4d[] orbitTracerPositions = body.PreviousPositions.ToArray(); - CircleShape previousPositionShape = new CircleShape(0.5f) + for (uint i = 0; i < orbitTracerPositions.Length; i++) { - Position = new Vector2f( + Vector4d previousPosition = orbitTracerPositions[i]; + + // project previous position onto the screen + Vector4d pointScreenPosition = ProjectPoint(previousPosition); + + // convert the final position into a Vector2f, useable by the SFML.NET Vertex struct + Vector2f finalOrbitTracerPosition = new Vector2f( (float)(pointScreenPosition.X * renderTarget.Size.X / 2 + originOffset.X), - (float)(pointScreenPosition.Y * renderTarget.Size.Y / 2 + originOffset.Y)), - FillColor = Color.Cyan - }; + (float)(pointScreenPosition.Y * renderTarget.Size.Y / 2 + originOffset.Y)); + + // append the new vertex to the orbit tracer array + orbitTracerVertexArray.Append(new Vertex(finalOrbitTracerPosition, Color.Cyan)); + } - previousPositionShape.Draw(renderTarget, RenderStates.Default); + // single call to VertexArray.Draw() makes use of hardware acceleration without causing delays, + // as modern graphics processing units are optimised to render many vertices simultaneously, instead + // of rendering many vertices sequentially + orbitTracerVertexArray.Draw(renderTarget, RenderStates.Default); } Vector4d screenPosition = ProjectPoint(body.Position); diff --git a/StarSim/StarSimLib/Physics/Body.cs b/StarSim/StarSimLib/Physics/Body.cs @@ -16,6 +16,12 @@ namespace StarSimLib.Physics "Body {0,2}.{1,-4}: Pos-{2}, Vel-{3} Mass-{4,3}"; /// <summary> + /// Sample rate for the previous position. Used to improve performance and get a longer orbit tracer tail + /// for less computation. The previous position will be saved once every 20 sampling opportunities. + /// </summary> + private const int PositionSampleRate = 20; + + /// <summary> /// Backing field for the <see cref="PreviousPositions"/> property. /// </summary> private readonly Queue<Vector4d> previousPositions; @@ -36,6 +42,12 @@ namespace StarSimLib.Physics private Vector4d position; /// <summary> + /// Counts the number of sampling opportunities that have gone by since the last position sample. Resets once + /// it reaches the value of <see cref="PositionSampleRate"/>. + /// </summary> + private int positionSampleCounter = 0; + + /// <summary> /// Backing field for the <see cref="Velocity"/> property. /// </summary> private Vector4d velocity; @@ -105,6 +117,11 @@ namespace StarSimLib.Physics } /// <summary> + /// Whether to record previous positions of this instance, to render an orbit tracer behind it. + /// </summary> + public bool RecordPreviousPositions { get; set; } + + /// <summary> /// The current velocity of the <see cref="Body"/> in 3D space. /// </summary> public Vector4d Velocity @@ -115,16 +132,23 @@ namespace StarSimLib.Physics /// <summary> /// Enqueues the current position on the <see cref="previousPositions"/> queue, to save it. /// Will dequeue positions from the queue if the number of stored positions exceeds the - /// value in <see cref="Constants.StoredPreviousPositionCount"/>. + /// value in <see cref="Constants.StoredPreviousPositionCount"/>. Will only enqueue the + /// current position if the <see cref="PositionSampleRate"/> is met. /// </summary> private void EnqueuePosition() { + // if the sample rate limit has not yet been met, don't sample a position + if (++positionSampleCounter < PositionSampleRate) return; + previousPositions.Enqueue(position); if (previousPositions.Count > Constants.StoredPreviousPositionCount) { previousPositions.Dequeue(); } + + // reset the counter + positionSampleCounter = 0; } /// <summary> @@ -166,6 +190,14 @@ namespace StarSimLib.Physics } /// <summary> + /// Clears the <see cref="Queue{T}"/> holding the previous positions. + /// </summary> + public void ClearPreviousPositionQueue() + { + previousPositions.Clear(); + } + + /// <summary> /// Collides this instance with the given <see cref="Body"/> instance. /// </summary> /// <param name="otherBody">The other instance with which to collide.</param> @@ -210,7 +242,12 @@ namespace StarSimLib.Physics public void Update(double deltaTime) { velocity += deltaTime * force / mass; - EnqueuePosition(); + + if (RecordPreviousPositions) + { + EnqueuePosition(); + } + position += deltaTime * velocity; } @@ -222,7 +259,12 @@ namespace StarSimLib.Physics public void Update(Vector4d forceVector, double deltaTime) { velocity += deltaTime * forceVector / mass; - EnqueuePosition(); + + if (RecordPreviousPositions) + { + EnqueuePosition(); + } + position += deltaTime * velocity; } diff --git a/StarSim/StarSimLib/Physics/BodyGenerator.cs b/StarSim/StarSimLib/Physics/BodyGenerator.cs @@ -7,6 +7,20 @@ using StarSimLib.Data_Structures; namespace StarSimLib.Physics { /// <summary> + /// Takes the mass of a <see cref="Body"/> instance and returns a colour for the <see cref="CircleShape"/> + /// that will represent the <see cref="Body"/> of the given mass. + /// </summary> + /// <returns>The colour of the <see cref="CircleShape"/> for a <see cref="Body"/> of the given mass.</returns> + public delegate Color MassToColourDelegate(double mass); + + /// <summary> + /// Takes the mass of a <see cref="Body"/> instance and returns a radius for the <see cref="CircleShape"/> + /// that will represent the <see cref="Body"/> of the given mass. + /// </summary> + /// <returns>The radius of the <see cref="CircleShape"/> for a <see cref="Body"/> of the given mass.</returns> + public delegate float MassToRadiusDelegate(double mass); + + /// <summary> /// Provides methods for generating <see cref="Body"/> instances. /// </summary> public static class BodyGenerator @@ -17,6 +31,18 @@ namespace StarSimLib.Physics private static readonly Random Rng = new Random(); /// <summary> + /// Default implementation of the <see cref="MassToColourDelegate"/>. + /// </summary> + public static readonly MassToColourDelegate DefaultColourDelegate = + mass => mass >= Constants.CentralBodyMass ? Color.Red : Color.White; + + /// <summary> + /// Default implementation of the <see cref="MassToRadiusDelegate"/>. + /// </summary> + public static readonly MassToRadiusDelegate DefaultRadiusDelegate = + mass => mass >= Constants.CentralBodyMass ? 4f : 2f; + + /// <summary> /// The current generation of <see cref="Body"/> instances that the generator is on. /// </summary> public static uint CurrentGeneration { get; private set; } @@ -62,15 +88,36 @@ namespace StarSimLib.Physics /// <returns> /// A <see cref="Dictionary{TKey,TValue}"/> mapping the given <see cref="Body"/> instances to their <see cref="CircleShape"/> instances. /// </returns> - public static Dictionary<Body, CircleShape> GenerateShapes(IEnumerable<Body> bodies) + public static Dictionary<Body, CircleShape> GenerateShapes(IEnumerable<Body> bodies) => + GenerateShapes(bodies, DefaultRadiusDelegate, DefaultColourDelegate); + + /// <summary> + /// Generates a <see cref="Dictionary{TKey,TValue}"/> mapping each given <see cref="Body"/> to a + /// <see cref="CircleShape"/> that represents the body instance in 2D space. + /// </summary> + /// <param name="bodies">The <see cref="Body"/> instances for which to generate the shapes.</param> + /// <param name="massToRadiusDelegate"> + /// The function to use to derive the radius of the <see cref="CircleShape"/> that will represent a <see cref="Body"/> + /// instance from its mass. + /// </param> + /// <param name="massToColourDelegate"> + /// The function to use to derive the colour of the <see cref="CircleShape"/> that will represent a <see cref="Body"/> + /// instance from its mass. + /// </param> + /// <returns> + /// A <see cref="Dictionary{TKey,TValue}"/> mapping the given <see cref="Body"/> instances to their <see cref="CircleShape"/> instances. + /// </returns> + public static Dictionary<Body, CircleShape> GenerateShapes(IEnumerable<Body> bodies, + MassToRadiusDelegate massToRadiusDelegate, MassToColourDelegate massToColourDelegate) { Dictionary<Body, CircleShape> bodyCircleShapeMap = new Dictionary<Body, CircleShape>(); foreach (Body body in bodies) { - CircleShape shape = body.Mass >= Constants.CentralBodyMass - ? new CircleShape(4) { FillColor = Color.Red } - : new CircleShape(1) { FillColor = Color.White }; + CircleShape shape = new CircleShape(massToRadiusDelegate(body.Mass)) + { + FillColor = massToColourDelegate(body.Mass) + }; shape.Origin = new Vector2f(shape.Radius, shape.Radius); diff --git a/StarSim/StarSimLib/StarSimLib.xml b/StarSim/StarSimLib/StarSimLib.xml @@ -602,6 +602,12 @@ screen at the <see cref="T:StarSimLib.Physics.Body"/>s position. </summary> </member> + <member name="F:StarSimLib.Graphics.Drawer.managedBodyTracerVertexArrayMap"> + <summary> + Maps a <see cref="T:StarSimLib.Physics.Body"/> to the <see cref="T:SFML.Graphics.VertexArray"/> that holds its orbit tracer vertices, and + is drawn to the screen behind the <see cref="T:StarSimLib.Physics.Body"/> instance. + </summary> + </member> <member name="F:StarSimLib.Graphics.Drawer.originOffset"> <summary> The offset that has to be applied to the positions of a <see cref="T:SFML.Graphics.CircleShape"/>, so that they appear @@ -727,6 +733,12 @@ Describes how the <see cref="T:StarSimLib.Physics.Body"/> instance should be formatted as a <see cref="T:System.String"/>. </summary> </member> + <member name="F:StarSimLib.Physics.Body.PositionSampleRate"> + <summary> + Sample rate for the previous position. Used to improve performance and get a longer orbit tracer tail + for less computation. The previous position will be saved once every 20 sampling opportunities. + </summary> + </member> <member name="F:StarSimLib.Physics.Body.previousPositions"> <summary> Backing field for the <see cref="P:StarSimLib.Physics.Body.PreviousPositions"/> property. @@ -747,6 +759,12 @@ Backing field for the <see cref="P:StarSimLib.Physics.Body.Position"/> property. </summary> </member> + <member name="F:StarSimLib.Physics.Body.positionSampleCounter"> + <summary> + Counts the number of sampling opportunities that have gone by since the last position sample. Resets once + it reaches the value of <see cref="F:StarSimLib.Physics.Body.PositionSampleRate"/>. + </summary> + </member> <member name="F:StarSimLib.Physics.Body.velocity"> <summary> Backing field for the <see cref="P:StarSimLib.Physics.Body.Velocity"/> property. @@ -792,6 +810,11 @@ A <see cref="T:System.Collections.Generic.Queue`1"/> containing previous positions of the body. </summary> </member> + <member name="P:StarSimLib.Physics.Body.RecordPreviousPositions"> + <summary> + Whether to record previous positions of this instance, to render an orbit tracer behind it. + </summary> + </member> <member name="P:StarSimLib.Physics.Body.Velocity"> <summary> The current velocity of the <see cref="T:StarSimLib.Physics.Body"/> in 3D space. @@ -801,7 +824,8 @@ <summary> Enqueues the current position on the <see cref="F:StarSimLib.Physics.Body.previousPositions"/> queue, to save it. Will dequeue positions from the queue if the number of stored positions exceeds the - value in <see cref="F:StarSimLib.Constants.StoredPreviousPositionCount"/>. + value in <see cref="F:StarSimLib.Constants.StoredPreviousPositionCount"/>. Will only enqueue the + current position if the <see cref="F:StarSimLib.Physics.Body.PositionSampleRate"/> is met. </summary> </member> <member name="M:StarSimLib.Physics.Body.GetForceBetween(StarSimLib.Physics.Body,StarSimLib.Physics.Body)"> @@ -819,6 +843,11 @@ </summary> <param name="otherBody">The other body to calculate the force between.</param> </member> + <member name="M:StarSimLib.Physics.Body.ClearPreviousPositionQueue"> + <summary> + Clears the <see cref="T:System.Collections.Generic.Queue`1"/> holding the previous positions. + </summary> + </member> <member name="M:StarSimLib.Physics.Body.Collide(StarSimLib.Physics.Body)"> <summary> Collides this instance with the given <see cref="T:StarSimLib.Physics.Body"/> instance. @@ -855,6 +884,20 @@ <member name="M:StarSimLib.Physics.Body.ToString"> <inheritdoc /> </member> + <member name="T:StarSimLib.Physics.MassToColourDelegate"> + <summary> + Takes the mass of a <see cref="T:StarSimLib.Physics.Body"/> instance and returns a colour for the <see cref="T:SFML.Graphics.CircleShape"/> + that will represent the <see cref="T:StarSimLib.Physics.Body"/> of the given mass. + </summary> + <returns>The colour of the <see cref="T:SFML.Graphics.CircleShape"/> for a <see cref="T:StarSimLib.Physics.Body"/> of the given mass.</returns> + </member> + <member name="T:StarSimLib.Physics.MassToRadiusDelegate"> + <summary> + Takes the mass of a <see cref="T:StarSimLib.Physics.Body"/> instance and returns a radius for the <see cref="T:SFML.Graphics.CircleShape"/> + that will represent the <see cref="T:StarSimLib.Physics.Body"/> of the given mass. + </summary> + <returns>The radius of the <see cref="T:SFML.Graphics.CircleShape"/> for a <see cref="T:StarSimLib.Physics.Body"/> of the given mass.</returns> + </member> <member name="T:StarSimLib.Physics.BodyGenerator"> <summary> Provides methods for generating <see cref="T:StarSimLib.Physics.Body"/> instances. @@ -865,6 +908,16 @@ Caches a random number generator to use for all randomised positions and velocities. </summary> </member> + <member name="F:StarSimLib.Physics.BodyGenerator.DefaultColourDelegate"> + <summary> + Default implementation of the <see cref="T:StarSimLib.Physics.MassToColourDelegate"/>. + </summary> + </member> + <member name="F:StarSimLib.Physics.BodyGenerator.DefaultRadiusDelegate"> + <summary> + Default implementation of the <see cref="T:StarSimLib.Physics.MassToRadiusDelegate"/>. + </summary> + </member> <member name="P:StarSimLib.Physics.BodyGenerator.CurrentGeneration"> <summary> The current generation of <see cref="T:StarSimLib.Physics.Body"/> instances that the generator is on. @@ -888,6 +941,24 @@ A <see cref="T:System.Collections.Generic.Dictionary`2"/> mapping the given <see cref="T:StarSimLib.Physics.Body"/> instances to their <see cref="T:SFML.Graphics.CircleShape"/> instances. </returns> </member> + <member name="M:StarSimLib.Physics.BodyGenerator.GenerateShapes(System.Collections.Generic.IEnumerable{StarSimLib.Physics.Body},StarSimLib.Physics.MassToRadiusDelegate,StarSimLib.Physics.MassToColourDelegate)"> + <summary> + Generates a <see cref="T:System.Collections.Generic.Dictionary`2"/> mapping each given <see cref="T:StarSimLib.Physics.Body"/> to a + <see cref="T:SFML.Graphics.CircleShape"/> that represents the body instance in 2D space. + </summary> + <param name="bodies">The <see cref="T:StarSimLib.Physics.Body"/> instances for which to generate the shapes.</param> + <param name="massToRadiusDelegate"> + The function to use to derive the radius of the <see cref="T:SFML.Graphics.CircleShape"/> that will represent a <see cref="T:StarSimLib.Physics.Body"/> + instance from its mass. + </param> + <param name="massToColourDelegate"> + The function to use to derive the colour of the <see cref="T:SFML.Graphics.CircleShape"/> that will represent a <see cref="T:StarSimLib.Physics.Body"/> + instance from its mass. + </param> + <returns> + A <see cref="T:System.Collections.Generic.Dictionary`2"/> mapping the given <see cref="T:StarSimLib.Physics.Body"/> instances to their <see cref="T:SFML.Graphics.CircleShape"/> instances. + </returns> + </member> <member name="T:StarSimLib.Physics.UpdateDelegate"> <summary> Updates the given <see cref="T:StarSimLib.Physics.Body"/> collections individual bodies, using the given delta time. @@ -940,10 +1011,18 @@ The renderer used to display the <see cref="T:StarSimLib.Physics.Body"/> instances on the screen. </summary> </member> - <member name="M:StarSimLib.UI.InputHandler.#ctor(StarSimLib.Graphics.Drawer@)"> + <member name="F:StarSimLib.UI.InputHandler.managedBodies"> + <summary> + Holds all the <see cref="T:StarSimLib.Physics.Body"/> instances that should be simulated. + </summary> + </member> + <member name="M:StarSimLib.UI.InputHandler.#ctor(StarSimLib.Physics.Body[]@,StarSimLib.Graphics.Drawer@)"> <summary> Initialises a new instance of the <see cref="T:StarSimLib.UI.InputHandler"/> class. </summary> + <param name="bodies"> + A reference to the <see cref="T:StarSimLib.Physics.Body"/> instances which should be managed by this instance. + </param> <param name="drawer"> A reference to the renderer used to display the <see cref="T:StarSimLib.Physics.Body"/> instances on the screen. </param> @@ -953,6 +1032,12 @@ Whether the simulation is paused at any given time. </summary> </member> + <member name="P:StarSimLib.UI.InputHandler.RecordOrbitTracers"> + <summary> + Whether to record previous <see cref="T:StarSimLib.Physics.Body"/> instance positions, in order to render an orbit tracer + behind each body instance. + </summary> + </member> <member name="M:StarSimLib.UI.InputHandler.HandleKeyPressed(System.Object,SFML.Window.KeyEventArgs)"> <summary> Handles key presses. diff --git a/StarSim/StarSimLib/UI/InputHandler.cs b/StarSim/StarSimLib/UI/InputHandler.cs @@ -16,13 +16,22 @@ namespace StarSimLib.UI private readonly Drawer bodyDrawer; /// <summary> + /// Holds all the <see cref="Body"/> instances that should be simulated. + /// </summary> + private readonly Body[] managedBodies; + + /// <summary> /// Initialises a new instance of the <see cref="InputHandler"/> class. /// </summary> + /// <param name="bodies"> + /// A reference to the <see cref="Body"/> instances which should be managed by this instance. + /// </param> /// <param name="drawer"> /// A reference to the renderer used to display the <see cref="Body"/> instances on the screen. /// </param> - public InputHandler(ref Drawer drawer) + public InputHandler(ref Body[] bodies, ref Drawer drawer) { + managedBodies = bodies; bodyDrawer = drawer; } @@ -32,6 +41,12 @@ namespace StarSimLib.UI public bool IsSimulationPaused { get; set; } /// <summary> + /// Whether to record previous <see cref="Body"/> instance positions, in order to render an orbit tracer + /// behind each body instance. + /// </summary> + public bool RecordOrbitTracers { get; set; } + + /// <summary> /// Handles key presses. /// </summary> /// <param name="sender">The <see cref="Window"/> that sent the event.</param> @@ -47,6 +62,22 @@ namespace StarSimLib.UI IsSimulationPaused = !IsSimulationPaused; break; + case Keyboard.Key.T: + // toggle orbit tracer recording + RecordOrbitTracers = !RecordOrbitTracers; + + // every simulated body must be individually set to record tracers + foreach (Body body in managedBodies) + { + // we want to clear the screen of any orbit tracers that still exist when we turn orbit tracing + // off, so we must clear the previous position queues of every managed object. this has the + // effect of clearing the vertex arrays holding the orbit tracer vertices + body.RecordPreviousPositions = RecordOrbitTracers; + body.ClearPreviousPositionQueue(); + } + + break; + case Keyboard.Key.W: // rotate the view north bodyDrawer.Rotate(RotationDirection.North, Constants.EulerRotationStep);