diff --git a/scenes/Main.tscn b/scenes/Main.tscn index 1051db3..c0c8a4f 100644 --- a/scenes/Main.tscn +++ b/scenes/Main.tscn @@ -1,7 +1,7 @@ [gd_scene load_steps=3 format=2] [ext_resource path="res://scenes/OrbitCamera.tscn" type="PackedScene" id=3] -[ext_resource path="res://scenes/OrbitSystem.tscn" type="PackedScene" id=4] +[ext_resource path="res://scenes/orbits/OrbitSystem.tscn" type="PackedScene" id=4] [node name="Main" type="Node2D"] @@ -11,3 +11,7 @@ transform = Transform( 0.515898, 0.606099, -0.605386, -0.393123, 0.795389, 0.461 [node name="OrbitCamera" parent="." instance=ExtResource( 3 )] [node name="Orbit System" parent="." instance=ExtResource( 4 )] +transform = Transform( 0.856869, 0.3292, -0.396741, 0.0949996, 0.655565, 0.749139, 0.506706, -0.679604, 0.53046, -0.599122, 0, 0 ) +SemiMajorAxis = 6.166 +Eccentricity = 0.239 +_speed = 0.877 diff --git a/scenes/OrbitSystem.tscn b/scenes/OrbitSystem.tscn deleted file mode 100644 index b0cb744..0000000 --- a/scenes/OrbitSystem.tscn +++ /dev/null @@ -1,15 +0,0 @@ -[gd_scene load_steps=2 format=2] - -[ext_resource path="res://scripts/orbits/OrbitSystem.cs" type="Script" id=1] - -[node name="Orbit System" type="Spatial"] -script = ExtResource( 1 ) -_a = NodePath("A") -_b = NodePath("B") -_barycenter = NodePath("Barycenter") - -[node name="A" type="Spatial" parent="."] - -[node name="B" type="Spatial" parent="."] - -[node name="Barycenter" type="Spatial" parent="."] diff --git a/scenes/orbits/OrbitSystem.tscn b/scenes/orbits/OrbitSystem.tscn new file mode 100644 index 0000000..0806b26 --- /dev/null +++ b/scenes/orbits/OrbitSystem.tscn @@ -0,0 +1,21 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://scripts/orbits/OrbitSystem.cs" type="Script" id=1] +[ext_resource path="res://scenes/orbits/Planet.tscn" type="PackedScene" id=2] + +[node name="Orbit System" type="Spatial"] +script = ExtResource( 1 ) +SemiMajorAxis = 5.994 +Eccentricity = 0.518 +_a = NodePath("Planet A") +_b = NodePath("Planet B") +_barycenter = NodePath("Barycenter") + +[node name="Barycenter" type="Spatial" parent="."] + +[node name="Planet A" parent="." instance=ExtResource( 2 )] +transform = Transform( 0.999977, 0.00603502, 0.00319089, -0.00604282, 0.999979, 0.0024369, -0.00317609, -0.00245615, 0.999992, 0, 0, 0 ) + +[node name="Planet B" parent="." instance=ExtResource( 2 )] +transform = Transform( 0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, -6.57314, 0, 4.18169 ) +Mass = 0.125 diff --git a/scenes/orbits/Planet.tscn b/scenes/orbits/Planet.tscn new file mode 100644 index 0000000..4ddb444 --- /dev/null +++ b/scenes/orbits/Planet.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://scripts/orbits/Planet.cs" type="Script" id=1] + +[node name="Planet" type="Spatial"] +script = ExtResource( 1 ) +Mass = 1.0 + +[node name="CSGSphere" type="CSGSphere" parent="."] +radial_segments = 20 +rings = 20 diff --git a/scripts/orbits/ILocation.cs b/scripts/orbits/ILocation.cs index 50c1af4..b13f214 100644 --- a/scripts/orbits/ILocation.cs +++ b/scripts/orbits/ILocation.cs @@ -1,7 +1,6 @@ -using Godot; -using System; +using Vim.Math3d; public interface ILocation { - Vector3 Position { get; } + DVector3 Position { get; set; } } diff --git a/scripts/orbits/IPointMass.cs b/scripts/orbits/IPointMass.cs new file mode 100644 index 0000000..c7c1e78 --- /dev/null +++ b/scripts/orbits/IPointMass.cs @@ -0,0 +1,3 @@ +public interface IPointMass : IMassive, ILocation +{ +} diff --git a/scripts/orbits/Orbit.cs b/scripts/orbits/Orbit.cs new file mode 100644 index 0000000..e47600b --- /dev/null +++ b/scripts/orbits/Orbit.cs @@ -0,0 +1,48 @@ +using Godot; +using System; +using Vim.Math3d; + +public class Orbit +{ + // the position of the primary body relative to the ellipse - the + // orbit itself is presented as being centred on the primary, but + // all our math is ellipse stuff + private DVector2 PrimaryPosition => Ellipse.Focus0; + + public Ellipse Ellipse { get; set; } + + public DVector3 GetPosition(float t) + { + var p = PrimaryPosition + Ellipse.GetPosition(t); + return new DVector3(p.X, 0, p.Y); + } + + public void Draw(ImmediateGeometry geo) + { + geo.Clear(); + geo.Begin(Mesh.PrimitiveType.LineLoop); + DrawEllipse(geo); + geo.End(); + } + + private void DrawEllipse(ImmediateGeometry geo) + { + geo.SetColor(new Color(1, 0, 0)); + + int steps = 100; + + for (int i = 0; i < steps; i++) + { + float t = i / (float)steps; + float a = t * Mathf.Tau; + + var v2 = Ellipse.Focus0 + Ellipse.GetPosition(a); + + geo.AddVertex(new Godot.Vector3 + { + x = (float)v2.X, + z = (float)v2.Y + }); + } + } +} diff --git a/scripts/orbits/OrbitSystem.cs b/scripts/orbits/OrbitSystem.cs index 358ad53..e60b2db 100644 --- a/scripts/orbits/OrbitSystem.cs +++ b/scripts/orbits/OrbitSystem.cs @@ -1,79 +1,73 @@ using Godot; using System; +using Vim.Math3d; +[Tool] public class OrbitSystem : Node, IMassive, ILocation { [Export] private NodePath _a; [Export] private NodePath _b; [Export] private NodePath _barycenter; - private readonly PointMass[] _pointMasses = new PointMass[2]; - - public Vector3 Position => Barycenter; - + private double _semiMajorAxis; [Export] - public Vector3 Barycenter + private double SemiMajorAxis + { + get => _semiMajorAxis; + set + { + _semiMajorAxis = value; + + if (!Engine.EditorHint) return; + + InvalidateGeometry(); + } + } + + + private double _eccentricity; + [Export(PropertyHint.Range, "0,1")] + private double Eccentricity + { + get => _eccentricity; + set + { + _eccentricity = value; + + if (!Engine.EditorHint) return; + + InvalidateGeometry(); + } + } + + #region Point Masses + private IPointMass Primary { get { - var p0 = _pointMasses[0].Position; - var p1 = _pointMasses[1].Position; - - return p0.LinearInterpolate(p1, .5f); - } - set => _ = value; - } - - public float Mass { get; } = 1; - - private ImmediateGeometry _orbit; - - public override void _Ready() - { - InitPointMasses(); - InitGeometry(); - } - - public override void _Process(float delta) - { - DrawOrbit(); - } - - private void DrawOrbit() - { - int steps = 100; - - _orbit.Clear(); - _orbit.Begin(Mesh.PrimitiveType.LineLoop); - _orbit.SetColor(new Color(1, 0, 0)); - for (int i = 0; i < steps; i++) - { - float t = i / (float)steps; - float a = t * Mathf.Tau; - - _orbit.AddVertex(new Vector3 + if (_pointMasses[0] == null) { - x = Mathf.Sin(a), - z = Mathf.Cos(a) - }); + InitPointMasses(); + } + return _pointMasses[0]; } - _orbit.End(); } - - private void InitGeometry() + private IPointMass Secondary { - _orbit = new ImmediateGeometry(); - var m = new SpatialMaterial(); - m.VertexColorUseAsAlbedo = true; - m.FlagsUnshaded = true; - _orbit.MaterialOverride = m; - AddChild(_orbit); + get + { + if (_pointMasses[1] == null) + { + InitPointMasses(); + } + return _pointMasses[1]; + } } - + private readonly IPointMass[] _pointMasses = new IPointMass[2]; private void InitPointMasses() { - var a = GetPointMass(_a); - var b = GetPointMass(_b); + var a = GetNode(_a); + var b = GetNode(_b); if (a.Mass > b.Mass) { @@ -86,12 +80,74 @@ public class OrbitSystem : Node, IMassive, ILocation _pointMasses[1] = a; } } + #endregion - private PointMass GetPointMass(NodePath path) + public float Mass => Primary.Mass + Secondary.Mass; + public DVector3 Position { - var spatial = GetNode(path); - //var massive = node.GetChild(0); + get => Barycenter; + set => Barycenter = value; + } - return new PointMass(spatial, 1f); + public DVector3 Barycenter + { + get + { + var p0 = Primary.Position; + var p1 = Secondary.Position; + return p0.Lerp(p1, .5f); + } + // TODO - make setting the berycenter do something sensible? + set => _ = value; + } + + private Orbit _orbit = null; + private Orbit Orbit + { + get + { + if (_orbit == null) + { + _orbit = new Orbit(); + } + return _orbit; + } + } + + private ImmediateGeometry _orbitGeometry = null; + public ImmediateGeometry OrbitGeometry + { + get + { + if (_orbitGeometry == null) + { + _orbitGeometry = new ImmediateGeometry(); + var m = new SpatialMaterial(); + m.VertexColorUseAsAlbedo = true; + m.FlagsUnshaded = true; + _orbitGeometry.MaterialOverride = m; + AddChild(_orbitGeometry); + } + return _orbitGeometry; + } + } + + private float _time = 0; + + [Export] + private float _speed = 3f; + + public override void _Process(float delta) + { + _time += delta * _speed; + InvalidateGeometry(); + } + + private void InvalidateGeometry() + { + Orbit.Ellipse = new Ellipse(SemiMajorAxis, Eccentricity); + Orbit.Draw(OrbitGeometry); + + Secondary.Position = Orbit.GetPosition(_time); } } diff --git a/scripts/orbits/Planet.cs b/scripts/orbits/Planet.cs index c93a33a..b12c0bc 100644 --- a/scripts/orbits/Planet.cs +++ b/scripts/orbits/Planet.cs @@ -1,18 +1,32 @@ using Godot; using System; +using Vim.Math3d; -public class Planet : Node +[Tool] +public class Planet : Spatial, IPointMass { [Export] public float Mass { get; set; } - public override void _Ready() + private DVector3? _position; + public DVector3 Position { + get + { + if (_position.HasValue) return _position.Value; + var t = Translation; + return new DVector3(t.x, t.y, t.z); + } + set + { + _position = value; + Translation = new Godot.Vector3 + { + x = (float)value.X, + y = (float)value.Y, + z = (float)value.Z + }; + } } - - // public override void _Process(float delta) - // { - // - // } -} +} \ No newline at end of file diff --git a/scripts/orbits/PointMass.cs b/scripts/orbits/PointMass.cs deleted file mode 100644 index bb277c1..0000000 --- a/scripts/orbits/PointMass.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Godot; - -struct PointMass : IMassive, ILocation -{ - public float Mass => _massive == null ? _mass : _massive.Mass; - public Vector3 Position => _spatial.Translation; - - private IMassive _massive; - private Spatial _spatial; - private float _mass; - - public PointMass(Spatial spatial, IMassive massive) - { - _spatial = spatial; - _massive = massive; - _mass = 0; - } - - public PointMass(Spatial spatial, float mass) - { - _spatial = spatial; - _massive = null; - _mass = mass; - } -} diff --git a/scripts/orbits/math/Ellipse.cs b/scripts/orbits/math/Ellipse.cs new file mode 100644 index 0000000..e1fff2e --- /dev/null +++ b/scripts/orbits/math/Ellipse.cs @@ -0,0 +1,63 @@ +using Godot; +using System; +using Vim.Math3d; + +public struct Ellipse +{ + public Ellipse(double a = 1, double e = 0) + { + SemiMajorAxis = a; + Eccentricity = e; + + // TODO: this is an immutable struct, so initialise everything else + // in the constructor to avoid recalculating properties whenever + // they are accessed + } + + /// + /// Semi-major axis + /// + public double a => SemiMajorAxis; + public double SemiMajorAxis { get; } + + /// + /// Eccentricity + /// + public double e => Eccentricity; + public double Eccentricity { get; } + + /// + /// Semi-minor axis + /// + public double b => SemiMinorAxis; + public double SemiMinorAxis => Math.Sqrt(Apisides.max * Apisides.min); + + /// + /// Get a position on the auxiliary circle + /// + /// Angle in radians around the circle + /// 2D position on the circle scaled by the semi-major axis + public DVector2 GetAuxiliaryPosition2D(double t = 0) => + new DVector2(Math.Cos(t), Math.Sin(t)) * a; + + private Apisides Apisides => new Apisides(a, e); + + public DVector2 GetPosition(double t) => + new DVector2(Math.Cos(t) * a, Math.Sin(t) * b); + + public DVector2 Focus0 => new DVector2(-GetFocusDistance(), 0); + public DVector2 Focus1 => new DVector2(GetFocusDistance(), 0); + private double GetFocusDistance() => Math.Sqrt(a * a - b * b); +} + +public struct Apisides +{ + public readonly double min; + public readonly double max; + + public Apisides(double a, double e) + { + min = a * (1 - e); + max = a * (1 + e); + } +} \ No newline at end of file