commit 41fc6f39d7b79bef1375d0e5af80488fd3814ef3
parent 1359975637532a7180e3a333224f484534975d55
Author: Mikolaj Lenczewski <33129490+EnderRifter@users.noreply.github.com>
Date: Wed, 19 Jun 2019 12:01:44 +0100
Working now
Diffstat:
7 files changed, 871 insertions(+), 4 deletions(-)
diff --git a/StarSim/StarSim/Program.cs b/StarSim/StarSim/Program.cs
@@ -1,12 +1,171 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using SFML.Graphics;
+using SFML.System;
+using SFML.Window;
+using StarSimLib;
namespace StarSim
{
- class Program
+ internal class Program
{
- static void Main(string[] args)
+ /// <summary>
+ /// Caches a random number generator to use for all randomised positions and velocities.
+ /// </summary>
+ private static readonly Random RNG = new Random();
+
+ /// <summary>
+ /// The current generation of <see cref="Body"/>s that we have. Increments each time the bodies are regenerated
+ /// via <see cref="GenerateBodies(int, bool)"/>.
+ /// </summary>
+ private static uint _generation;
+
+ /// <summary>
+ /// Holds all the <see cref="Body"/> instances that should be simulated.
+ /// </summary>
+ private static Body[] _bodies;
+
+ /// <summary>
+ /// Maps a <see cref="Body"/> to the <see cref="CircleShape"/> that represents it, and is drawn to the
+ /// screen at the <see cref="Body"/>s position.
+ /// </summary>
+ private static Dictionary<Body, CircleShape> BodyShapeMap;
+
+ private static void Main(string[] args)
{
Console.WriteLine("Hello World!");
+
+ GenerateBodies(10, true);
+
+ Console.ReadLine();
+
+ RenderWindow window = new RenderWindow(VideoMode.DesktopMode, "N-Body Simulator", Styles.Default);
+ window.SetFramerateLimit(Constants.FrameRate);
+ window.Closed += (sender, eventArgs) => ((RenderWindow)sender).Close();
+ window.KeyPressed += HandleKeyPressed;
+
+ // caches the delta between the screen origin (top left corner) and the world origin,
+ // which should be the centre of the screen
+ uint wx = window.Size.X, wy = window.Size.Y;
+ uint dx = wx / 2, dy = wy / 2;
+ Vector2u originOffset = new Vector2u(dx, dy);
+
+ DrawBodies(_bodies, window, originOffset);
+
+ while (window.IsOpen)
+ {
+ window.Clear();
+ window.DispatchEvents();
+
+ UpdateBodiesBruteForce(_bodies, Constants.TimeStep);
+ DrawBodies(_bodies, window, originOffset);
+
+ window.Display();
+ }
+ }
+
+ private static void GenerateBodies(int bodyCount = 2, bool centralAttractor = false)
+ {
+ _generation++;
+
+ uint id = 0;
+ _bodies = new Body[bodyCount];
+ BodyShapeMap = new Dictionary<Body, CircleShape>();
+
+ Console.WriteLine($"=========== Generation {_generation} ===========");
+ for (int i = 0; i < _bodies.Length; i++)
+ {
+ float mass = (float)(RNG.NextDouble() * Constants.SolarMass);
+
+ Vector3d position = OrbitGenerator.RandomPosition();
+ Vector3d velocity = new Vector3d(); //OrbitGenerator.RandomOrbit(position);
+
+ _bodies[i] = new Body(position, velocity, mass, _generation, id);
+ BodyShapeMap.Add(_bodies[i], new CircleShape(4) { FillColor = Color.White });
+
+ Console.WriteLine(_bodies[i]);
+ id++;
+ }
+
+ if (centralAttractor && bodyCount >= 2)
+ {
+ BodyShapeMap.Remove(_bodies[0]);
+ _bodies[0] = new Body(new Vector3d(), new Vector3d(), Constants.CentralBodyMass, _generation, 0);
+ BodyShapeMap.Add(_bodies[0], new CircleShape(4) { FillColor = Color.Red });
+ }
+ }
+
+ /// <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>
+ /// <param name="deltaTime">The time step.</param>
+ private static void UpdateBodiesBruteForce(IEnumerable<Body> bodies, double deltaTime)
+ {
+ IEnumerable<Body> bodyEnumerable = bodies as Body[] ?? bodies.ToArray();
+ Vector3d forceVector = new Vector3d();
+
+ foreach (Body body in bodyEnumerable)
+ {
+ // resets the force vector to avoid another instantiation and allocation
+ forceVector.X = 0;
+ forceVector.Y = 0;
+ forceVector.Z = 0;
+
+ // 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));
+
+ body.Update(forceVector, deltaTime);
+ }
+ }
+
+ /// <summary>
+ /// Draws each <see cref="Body"/> in the given <see cref="IEnumerable{T}"/> to the given window.
+ /// </summary>
+ /// <param name="bodies">The collection of <see cref="Body"/>s to draw.</param>
+ /// <param name="renderTarget">The target to draw the <see cref="Body"/>s to.</param>
+ /// <param name="originOffset">
+ /// The offset that has to be applied to the positions of the <see cref="CircleShape"/>, so that they appear
+ /// to be in the centre of the screen and not the top left corner.
+ /// </param>
+ private static void DrawBodies(IEnumerable<Body> bodies, RenderWindow renderTarget, Vector2u originOffset)
+ {
+ foreach (Body body in bodies)
+ {
+ // get the cached shape and projects the current bodies 3D position down to a 2D position,
+ // which is used as the new position of the shape.
+ CircleShape shape = BodyShapeMap[body];
+
+ shape.Position = new Vector2f(
+ (float)(body.Position.X * Constants.UniverseScalingFactor + originOffset.X),
+ (float)(body.Position.Y * Constants.UniverseScalingFactor + originOffset.Y)
+ );
+
+ shape.Draw(renderTarget, RenderStates.Default);
+ }
+ }
+
+ /// <summary>
+ /// Handles key presses.
+ /// </summary>
+ /// <param name="sender">The <see cref="RenderWindow"/> that sent the event.</param>
+ /// <param name="eventArgs">The <see cref="KeyEventArgs"/> about the key press.</param>
+ private static void HandleKeyPressed(object sender, KeyEventArgs eventArgs)
+ {
+ switch (eventArgs.Code)
+ {
+ case Keyboard.Key.Space:
+ UpdateBodiesBruteForce(_bodies, Constants.TimeStep);
+ break;
+
+ case Keyboard.Key.G:
+ GenerateBodies(10, true);
+ break;
+
+ default:
+ break;
+ }
}
}
-}
+}
+\ No newline at end of file
diff --git a/StarSim/StarSimLib/Body.cs b/StarSim/StarSimLib/Body.cs
@@ -1,6 +1,144 @@
-namespace StarSimLib
+using SFML.System;
+using System;
+using StarSimLib.Extensions;
+
+namespace StarSimLib
{
+ /// <summary>
+ /// Represents a stellar body.
+ /// </summary>
public class Body
{
+ #region Variables
+
+ /// <summary>
+ /// Describes how the <see cref="Body"/> instance should be formatted as a <see cref="string"/>.
+ /// </summary>
+ private const string BodyFormatString =
+ "Body {0,2}.{1,-4}: Pos-({2:D,-3}, {3:D,-3}, {4:D,-3}), Vel-({5:D,-3}, {6:D,-3}, {7:D,-3}), Mass-{8:D,3}";
+
+ /// <summary>
+ /// The generation that this body belongs to.
+ /// </summary>
+ public readonly uint Generation;
+
+ /// <summary>
+ /// The unique id for this body.
+ /// </summary>
+ public readonly uint Id;
+
+ #endregion Variables
+
+ #region Properties
+
+ /// <summary>
+ /// The current position of the <see cref="Body"/> in 3D space.
+ /// </summary>
+ public Vector3d Position { get; private set; }
+
+ /// <summary>
+ /// The current velocity of the <see cref="Body"/> in 3D space.
+ /// </summary>
+ public Vector3d Velocity { get; private set; }
+
+ /// <summary>
+ /// The mass of the <see cref="Body"/>.
+ /// </summary>
+ public double Mass { get; private set; }
+
+ #endregion Properties
+
+ #region Constructors
+
+ /// <summary>
+ /// Initialises a new instance of the <see cref="Body"/> class.
+ /// </summary>
+ /// <param name="position">The starting position of the <see cref="Body"/>.</param>
+ /// <param name="velocity">The starting velocity of the <see cref="Body"/>.</param>
+ /// <param name="mass">The starting mass of the <see cref="Body"/>.</param>
+ /// <param name="generation">The generation that this <see cref="Body"/> belongs to.</param>
+ /// <param name="id">The unique id for this body.</param>
+ public Body(Vector3d position, Vector3d velocity, double mass, uint generation = 1, uint id = 1)
+ {
+ Generation = generation;
+ Id = id;
+
+ Position = position;
+ Velocity = velocity;
+ Mass = mass;
+ }
+
+ #endregion Constructors
+
+ #region Methods
+
+ #region Overrides of Object
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ return string.Format(BodyFormatString, Generation, Id, Position.X, Position.Y, Position.Z, Velocity.X, Velocity.Y, Velocity.Z, Mass);
+ }
+
+ #endregion Overrides of Object
+
+ /// <summary>
+ /// Updates the <see cref="Position"/> of the current body using the given force vector and time step.
+ /// </summary>
+ /// <param name="forceVector">The sum vector of all forces due to other <see cref="Body"/>s.</param>
+ /// <param name="deltaTime">The time step.</param>
+ public void Update(Vector3d forceVector, double deltaTime)
+ {
+ Velocity += deltaTime * forceVector / Mass;
+ Position += deltaTime * Velocity;
+ }
+
+ /// <summary>
+ /// Finds and returns the distance between the current <see cref="Body"/> instance and the given <see cref="Body"/>.
+ /// In other words, it finds the magnitude of the translation vector between the two <see cref="Body"/> instances.
+ /// </summary>
+ /// <param name="body">The other <see cref="Body"/> instance to which to calculate the distance.</param>
+ /// <param name="displacement">The vector showing the displacement of the current body from the given one.</param>
+ /// <returns>The distance to the other <see cref="Body"/> as a <see cref="float"/>.</returns>
+ public double DistanceTo(Body body, out Vector3d displacement)
+ {
+ double dpx = body.Position.X - Position.X,
+ dpy = body.Position.Y - Position.Y,
+ dpz = body.Position.Z - Position.Z;
+
+ displacement = new Vector3d(dpx, dpy, dpz);
+
+ return Math.Sqrt(dpx * dpx + dpy * dpy + dpz * dpz);
+ }
+
+ /// <summary>
+ /// Gets the force vector for the attraction between <see cref="Body"/> A and <see cref="Body"/> B.
+ /// </summary>
+ /// <param name="a">The first <see cref="Body"/> instance.</param>
+ /// <param name="b">The second <see cref="Body"/> instance.</param>
+ /// <returns></returns>
+ public static Vector3d GetForceBetween(Body a, Body b)
+ {
+ // Inlines the Body.DistanceTo(Body) as the position deltas need to be cached for later,
+ // as well as to gain a small performance increase
+ double dx = b.Position.X - a.Position.X,
+ dy = b.Position.Y - a.Position.Y,
+ dz = b.Position.Z - a.Position.Z;
+
+ // The distance between two bodies can be found via taking the magnitude of their displacements,
+ // as shown here via pythagoras
+ double distance = Math.Sqrt(dx * dx + dy * dy + dz * dz);
+
+ double numerator = Constants.G * a.Mass * b.Mass;
+ double denominator = distance * distance + Constants.SofteningFactor2;
+
+ // Using the equation Force = Gravitational Constant * Mass(a) * Mass(b) / distance(a, b)^2
+ // with a softening factor, we get the attraction force vector between the 2 bodies
+ double force = numerator / denominator;
+
+ return new Vector3d(force * dx / distance, force * dy / distance, force * dz / distance);
+ }
+
+ #endregion Methods
}
}
\ No newline at end of file
diff --git a/StarSim/StarSimLib/Constants.cs b/StarSim/StarSimLib/Constants.cs
@@ -0,0 +1,60 @@
+using SFML.Graphics;
+
+namespace StarSimLib
+{
+ /// <summary>
+ /// Holds common constants and helper functions.
+ /// </summary>
+ public static class Constants
+ {
+ /// <summary>
+ /// The frame rate limit for the simulation.
+ /// </summary>
+ public const uint FrameRate = 60;
+
+ /// <summary>
+ /// The number of seconds that each frame represents.
+ /// </summary>
+ public const double SecondsPerFrame = 1e7f;
+
+ /// <summary>
+ /// The default time step for the simulation.
+ /// </summary>
+ public const double TimeStep = SecondsPerFrame * FrameRate;
+
+ /// <summary>
+ /// The gravitational constant (m^3 kg^-1 s^-2).
+ /// </summary>
+ public const double G = 6.673e-11f;
+
+ /// <summary>
+ /// The mass of the sun (1.98892e30f).
+ /// </summary>
+ public const double SolarMass = 1.98892e30f;
+
+ /// <summary>
+ /// Softens the force between <see cref="Body"/>s to avoid infinities.
+ /// </summary>
+ public const double SofteningFactor = 3e4f;
+
+ /// <summary>
+ /// The square of the <see cref="SofteningFactor"/>.
+ /// </summary>
+ public const double SofteningFactor2 = SofteningFactor * SofteningFactor;
+
+ /// <summary>
+ /// The maximum radius within which <see cref="Body"/>s will be placed (1e18f).
+ /// </summary>
+ public const double UniverseSize = 1e18f;
+
+ /// <summary>
+ /// Factor by which to multiply the position of <see cref="Body"/>s to scale them to the screen for displaying.
+ /// </summary>
+ public const double UniverseScalingFactor = 2500 / UniverseSize;
+
+ /// <summary>
+ /// The mass of the central body, if it is included.
+ /// </summary>
+ public const double CentralBodyMass = SolarMass * 1e6f;
+ }
+}
+\ No newline at end of file
diff --git a/StarSim/StarSimLib/ExampleBody.cs b/StarSim/StarSimLib/ExampleBody.cs
@@ -0,0 +1,68 @@
+using System;
+
+namespace StarSimLib
+{
+ public class ExampleBody
+ {
+ private static readonly double G = 6.673e-11; // gravitational constant
+ private static readonly double solarmass = 1.98892e30;
+
+ public double pos_x, pos_y;
+ public double vel_x, vel_y;
+ public double force_x, force_y;
+ public double mass;
+
+ public ExampleBody(double px, double py, double vx, double vy, double mass)
+ {
+ pos_x = px;
+ pos_y = py;
+ vel_x = vx;
+ vel_y = vy;
+ this.mass = mass;
+ }
+
+ public void update(double delta_time)
+ {
+ vel_x += delta_time * force_x / mass;
+ vel_y += delta_time * force_y / mass;
+ pos_x += delta_time * vel_x;
+ pos_y += delta_time * vel_y;
+ }
+
+ public double distanceTo(ExampleBody b)
+ {
+ double dx = pos_x - b.pos_x;
+ double dy = pos_y - b.pos_y;
+ return Math.Sqrt(dx * dx + dy * dy);
+ }
+
+ public void resetForce()
+ {
+ force_x = 0.0;
+ force_y = 0.0;
+ }
+
+ public void addForce(ExampleBody b)
+ {
+ ExampleBody a = this;
+ double softening_factor = 3E4;
+ double dx = b.pos_x - a.pos_x;
+ double dy = b.pos_y - a.pos_y;
+ double dist = Math.Sqrt(dx * dx + dy * dy);
+ double force = (G * a.mass * b.mass) / (dist * dist + softening_factor * softening_factor);
+
+ a.force_x += force * dx / dist;
+ a.force_y += force * dy / dist;
+ }
+
+ #region Overrides of Object
+
+ /// <inheritdoc />
+ public override string ToString()
+ {
+ return $"Body @ ({pos_x}, {pos_y}) with velocity ({vel_x}, {vel_y})";
+ }
+
+ #endregion Overrides of Object
+ }
+}
+\ No newline at end of file
diff --git a/StarSim/StarSimLib/Extensions/Vector3fExtensions.cs b/StarSim/StarSimLib/Extensions/Vector3fExtensions.cs
@@ -0,0 +1,17 @@
+using System;
+using SFML.System;
+
+namespace StarSimLib.Extensions
+{
+ /// <summary>
+ /// Provides additional functionality to the <see cref="Vector3f"/> struct.
+ /// </summary>
+ public static class Vector3fExtensions
+ {
+ public static float Magnitude(this Vector3f vector)
+ {
+ // returns the magnitude of the vector, via pythagoras
+ return (float)Math.Sqrt(vector.X * vector.X + vector.Y * vector.Y + vector.Z + vector.Z);
+ }
+ }
+}
+\ No newline at end of file
diff --git a/StarSim/StarSimLib/OrbitGenerator.cs b/StarSim/StarSimLib/OrbitGenerator.cs
@@ -0,0 +1,57 @@
+using System;
+using SFML.System;
+using StarSimLib.Extensions;
+
+namespace StarSimLib
+{
+ /// <summary>
+ /// Initialises positions, velocities, and orbits for bodies.
+ /// </summary>
+ public static class OrbitGenerator
+ {
+ /// <summary>
+ /// Random number generator.
+ /// </summary>
+ private static readonly Random Rng = new Random();
+
+ /// <summary>
+ /// Returns a random position within the universe sphere (see <see cref="Constants.UniverseSize"/>).
+ /// </summary>
+ /// <returns>A 3D position vector.</returns>
+ public static Vector3d RandomPosition()
+ {
+ double RandomPos()
+ {
+ return Constants.UniverseSize * Math.Exp(-1.8) * (.5 - Rng.NextDouble());
+ }
+
+ return new Vector3d(RandomPos(), RandomPos(), RandomPos());
+ }
+
+ /// <summary>
+ /// Returns a randomised orbit around a central, heavy mass.
+ /// </summary>
+ /// <param name="positionVector">The position vector for the body for which to generate the orbit.</param>
+ /// <returns>The magnitude of the velocity of the orbit.</returns>
+ public static Vector3d RandomOrbit(Vector3d positionVector)
+ {
+ Vector3d position = positionVector;
+
+ // F = G m1 - m2 / distance
+ double distanceToCentralBody = positionVector.Magnitude();
+ double numerator = Constants.G * Constants.SolarMass;
+ double velocityMagnitude = Math.Sqrt(numerator / distanceToCentralBody);
+
+ double absAngle = Math.Atan(Math.Abs(position.X / position.Y));
+ double velocityTheta = Math.PI / 2 - absAngle;
+ double velocityPhi = Rng.NextDouble() * Math.PI;
+
+ double vx = -1 * Math.Sign(position.Y) * Math.Cos(velocityTheta) * velocityMagnitude;
+ double vy = Math.Sign(position.X) * Math.Sin(velocityTheta) * velocityMagnitude;
+ double vz = 0;
+
+ // Randomly orient the orbit
+ return !(Rng.NextDouble() <= 0.5f) ? new Vector3d(vx, vy, vz) : new Vector3d(-vx, -vy, -vz);
+ }
+ }
+}
+\ No newline at end of file
diff --git a/StarSim/StarSimLib/Vector3d.cs b/StarSim/StarSimLib/Vector3d.cs
@@ -0,0 +1,362 @@
+using System;
+using System.Data.Common;
+using System.Transactions;
+
+namespace StarSimLib
+{
+ /// <summary>
+ /// A 3D vector with <see cref="double"/> components.
+ /// </summary>
+ public struct Vector3d
+ {
+ /// <summary>
+ /// The x component of the vector.
+ /// </summary>
+ public double X;
+
+ /// <summary>
+ /// The y component of the vector.
+ /// </summary>
+ public double Y;
+
+ /// <summary>
+ /// The z component of the vector.
+ /// </summary>
+ public double Z;
+
+ /// <summary>
+ /// Initialises a new instance of the <see cref="Vector3d"/> struct. Sets Z to 0.
+ /// </summary>
+ /// <param name="x">The x component.</param>
+ /// <param name="y">The y component.</param>
+ public Vector3d(double x, double y)
+ {
+ X = x;
+ Y = y;
+ Z = 0;
+ }
+
+ /// <summary>
+ /// Initialises a new instance of the <see cref="Vector3d"/> struct.
+ /// </summary>
+ /// <param name="x">The x component.</param>
+ /// <param name="y">The y component.</param>
+ /// <param name="z">The z component.</param>
+ public Vector3d(double x, double y, double z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ #region Operator Overloads
+
+ /// <summary>
+ /// Implements the addition operator for 2 <see cref="Vector3d"/>s.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The other <see cref="Vector3d"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator +(Vector3d vector, Vector3d vector2)
+ {
+ vector.X += vector2.X;
+ vector.Y += vector2.Y;
+ vector.Z += vector2.Z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the addition operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator +(Vector3d vector, (double X, double Y, double Z) vector2)
+ {
+ (double x, double y, double z) = vector2;
+
+ vector.X += x;
+ vector.Y += y;
+ vector.Z += z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the addition operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator +((double X, double Y, double Z) vector2, Vector3d vector) => vector + vector2;
+
+ /// <summary>
+ /// Implements the addition operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator +(Vector3d vector, double scalar)
+ {
+ vector.X += scalar;
+ vector.Y += scalar;
+ vector.Z += scalar;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the addition operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator +(double scalar, Vector3d vector) => vector + scalar;
+
+ /// <summary>
+ /// Implements the subtraction operator for 2 <see cref="Vector3d"/>s.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The other <see cref="Vector3d"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator -(Vector3d vector, Vector3d vector2)
+ {
+ vector.X -= vector2.X;
+ vector.Y -= vector2.Y;
+ vector.Z -= vector2.Z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the subtraction operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator -(Vector3d vector, (double X, double Y, double Z) vector2)
+ {
+ (double x, double y, double z) = vector2;
+
+ vector.X -= x;
+ vector.Y -= y;
+ vector.Z -= z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the subtraction operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator -((double X, double Y, double Z) vector2, Vector3d vector) => vector - vector2;
+
+ /// <summary>
+ /// Implements the subtraction operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator -(Vector3d vector, double scalar)
+ {
+ vector.X -= scalar;
+ vector.Y -= scalar;
+ vector.Z -= scalar;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the subtraction operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator -(double scalar, Vector3d vector) => vector - scalar;
+
+ /// <summary>
+ /// Implements the multiplication operator for 2 <see cref="Vector3d"/>s.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The other <see cref="Vector3d"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator *(Vector3d vector, Vector3d vector2)
+ {
+ vector.X *= vector2.X;
+ vector.Y *= vector2.Y;
+ vector.Z *= vector2.Z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the multiplication operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator *(Vector3d vector, (double X, double Y, double Z) vector2)
+ {
+ (double x, double y, double z) = vector2;
+
+ vector.X *= x;
+ vector.Y *= y;
+ vector.Z *= z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the multiplication operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator *((double X, double Y, double Z) vector2, Vector3d vector) => vector * vector2;
+
+ /// <summary>
+ /// Implements the multiplication operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator *(Vector3d vector, double scalar)
+ {
+ vector.X *= scalar;
+ vector.Y *= scalar;
+ vector.Z *= scalar;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the multiplication operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator *(double scalar, Vector3d vector) => vector * scalar;
+
+ /// <summary>
+ /// Implements the division operator for 2 <see cref="Vector3d"/>s.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The other <see cref="Vector3d"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator /(Vector3d vector, Vector3d vector2)
+ {
+ vector.X /= vector2.X;
+ vector.Y /= vector2.Y;
+ vector.Z /= vector2.Z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the division operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator /(Vector3d vector, (double X, double Y, double Z) vector2)
+ {
+ (double x, double y, double z) = vector2;
+
+ vector.X /= x;
+ vector.Y /= y;
+ vector.Z /= z;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the division operator for a <see cref="Vector3d"/> and a (X, Y, Z) tuple.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="vector2">The 3D tuple.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator /((double X, double Y, double Z) vector2, Vector3d vector) => vector / vector2;
+
+ /// <summary>
+ /// Implements the division operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator /(Vector3d vector, double scalar)
+ {
+ vector.X /= scalar;
+ vector.Y /= scalar;
+ vector.Z /= scalar;
+
+ return vector;
+ }
+
+ /// <summary>
+ /// Implements the division operator for a <see cref="Vector3d"/> and a scalar <see cref="double"/> value.
+ /// </summary>
+ /// <param name="vector">The original <see cref="Vector3d"/>.</param>
+ /// <param name="scalar">The scalar <see cref="double"/>.</param>
+ /// <returns>The new <see cref="Vector3d"/>.</returns>
+ public static Vector3d operator /(double scalar, Vector3d vector) => vector / scalar;
+
+ #endregion Operator Overloads
+
+ /// <summary>
+ /// Returns the absolute value of this <see cref="Vector3d"/>.
+ /// </summary>
+ /// <returns>The absolute value (magnitude) of this <see cref="Vector3d"/>, as a <see cref="double"/>.</returns>
+ public double Abs()
+ {
+ return Math.Sqrt(X * X + Y * Y + Z * Z);
+ }
+
+ /// <summary>
+ /// Returns the magnitude of this <see cref="Vector3d"/>.
+ /// </summary>
+ /// <returns>The magnitude (absolute value) of this <see cref="Vector3d"/>, as a <see cref="double"/>.</returns>
+ public double Magnitude()
+ {
+ return Abs();
+ }
+
+ /// <summary>
+ /// Holds unit vectors.
+ /// </summary>
+ public static class UnitVectors
+ {
+ /// <summary>
+ /// The positive unit vector for the X axis.
+ /// </summary>
+ public static readonly Vector3d Left = new Vector3d(1, 0, 0);
+
+ /// <summary>
+ /// The positive unit vector for the Y axis.
+ /// </summary>
+ public static readonly Vector3d Up = new Vector3d(0, 1, 0);
+
+ /// <summary>
+ /// The positive unit vector for the Z axis.
+ /// </summary>
+ public static readonly Vector3d Forwards = new Vector3d(0, 0, 1);
+
+ /// <summary>
+ /// The negative unit vector for the X axis.
+ /// </summary>
+ public static readonly Vector3d Right = new Vector3d(-1, 0, 0);
+
+ /// <summary>
+ /// The negative unit vector for the Y axis.
+ /// </summary>
+ public static readonly Vector3d Down = new Vector3d(0, -1, 0);
+
+ /// <summary>
+ /// The negative unit vector for the Z axis.
+ /// </summary>
+ public static readonly Vector3d Backwards = new Vector3d(0, 0, -1);
+ }
+ }
+}
+\ No newline at end of file