Introduction
It has been a while since I last added a new post entry. For the past year (pandemic at full) I did lots of testing regarding my physics-based gravity project.This entry is the first of a series of 3 in which I will update what have been done so far:
- Gravity Part 1: An update
- Gravity Part 2: Orbit predictions (One-Body problem)
- Gravity Part 3: Create a force map and display it (CPU, Compute Shader and Fragment Shader)
The update
The code described here has basically the same functionality as the original Physics-based Solar System Lessons post. The foundations, insights and issues are described in detail in the mentioned blog entry. So, I highly recommend checking it out for references.I did not want to change that post so I decided to create a new one with cleaner code, explaining the major changes and calculations.
I did a major code refactoring to have everything clearer and more cohesive. The code is divided into 2 blocks:
- Generic:
- GameManger.cs: Controls the time and space scale logic.
- Constants.cs: Controls generic constants/ enums used in the code.
- Gravity implementation:
- SpaceObject.cs: This is the core logic used by the objects.
- Gravity.cs This is the core to calculate gravity.
Generic:
The GameManager.cs class has changed quite a bit on content to only use what is important:- To modify the fixed delta time, everything else was not necessary and generated issues when used.
- To calculate a “new” gravitational constant. Previously I did all the force calculations using the original units N*m^2/kg^2. Now, the distance and time is calculated using the scaled units defined in GameManager.cs. The advantage is that we would never run out of precision, regardless of the actual values (just imagine if we want to use this to simulate gravity movement in the Milky Way).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | using System; using UnityEngine; public class GameManager : MonoBehaviour { [Header("Scaling options")] [SerializeField] [Tooltip("1 unit = SpaceScale kilometers")] private float _spaceScale = 6000; [SerializeField] [Tooltip("1 unit = TimeScale seconds")] private float _timeScale = 100000; //1 unity unit = timeScale seconds [SerializeField] [Tooltip("Fixed update loop time (default = 0.02)")] private float _modifiedFixedDeltaTime = 0.001f; public float SpaceScaleKm => _spaceScale; public float TimeScale => _timeScale; public double GravitationalConstantUnityScaled { get; private set; } public float ModifiedDeltaTime => _modifiedFixedDeltaTime; private void Awake() { Time.fixedDeltaTime = ModifiedDeltaTime; GravitationalConstantUnityScaled = Constants.GRAVITATIONAL_CONSTANT_KM * Math.Pow(TimeScale, 2) / Math.Pow(SpaceScaleKm, 3); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public static class Constants { public static readonly double GRAVITATIONAL_CONSTANT_KM = 6.67430e-20; //N*km^2/kg^2 public static readonly double EPSILON = 0.01; public const int FREE_VELOCITY = 0; public const int CIRCULAR_ORBIT_VELOCITY = 1; public const int ESCAPE_ORBIT_VELOCITY = 2; public enum InitialVelocity { Free = FREE_VELOCITY, CircularOrbit = CIRCULAR_ORBIT_VELOCITY, EscapeOrbit = ESCAPE_ORBIT_VELOCITY } } |
Gravity Implementation:
Originally, I had 3 different classes (GravitationalForces, SpaceObject and Satellite). Of these, “ Satellite” is a specific case of SpaceObject and the logic between the other two is entangled. So now everything has been refactored into 2 classes:- Gravity.cs: The logic that does all the gravity calculation.
- SpaceObject.cs: Any object that affects or is affected by gravitation.
Tag that enables the object to be affected by gravity |
The implementation of Gravity.cs class now only takes care of functions to obtain gravitation pull force and velocities from gravitational situations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 68 | using System; using UnityEngine; public class Gravity : MonoBehaviour { protected GameObject[] _spaceObjects; protected GameManager _gameManager; private double _massSource; /// <summary> /// Get gravitational pull force from this object, depending on the distance from it /// </summary> /// <param name="distanceFromObject"> distance between 2 objects in unity units </param> /// <returns></returns> public double GetGravitationalPullForce(double distanceFromObject) { return _gameManager.GravitationalConstantUnityScaled * _massSource / Math.Pow(distanceFromObject, 2); } /// <summary> /// Get the needed velocity to have a stable circular orbit around a gravitational object /// </summary> /// <param name="distanceFromObject"> distance between 2 objects in unity units </param> /// <returns></returns> public double GetVelocityForCircularOrbit(double distanceFromObject) { var velocity = Math.Sqrt(_gameManager.GravitationalConstantUnityScaled * _massSource / distanceFromObject); return velocity; } /// <summary> /// Get the needed velocity to escape a gravitational object's gravity /// </summary> /// <param name="distanceFromObject"> distance between 2 objects in unity units </param> /// <returns></returns> public double GetEscapeVelocity(double distanceFromObject) { return Math.Sqrt(2 * _gameManager.GravitationalConstantUnityScaled * _massSource / distanceFromObject); } protected void InitializeGravity(GameObject[] spaceObjects, double mass) { _spaceObjects = spaceObjects; _massSource = mass; _gameManager = GameObject.Find("GameManager").GetComponent<GameManager>(); } protected Vector3 GetExternalNetGravityVector() { var netForce = new Vector3(); foreach (var spaceObjects in _spaceObjects) { netForce += GetGravity(spaceObjects) * Time.fixedDeltaTime; } return netForce; } private Vector3 GetGravity(GameObject spaceObjects) { var direction = spaceObjects.transform.position - gameObject.transform.position; var gravity = spaceObjects.GetComponent<SpaceObject>().GetGravitationalPullForce(direction.magnitude); return direction.normalized * (float)gravity; } } |
The class SpaceObject.cs on the other hand takes care of the initial changes to the object (initial velocities) and updating the forces applied to the object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | using System; using System.Linq; using UnityEngine; using static Constants; public class SpaceObject : Gravity { #region variables [Header("Velocity settings")] [SerializeField] private Vector3 _initialDirection = new Vector3(0, 0, 1); [SerializeField] private InitialVelocity _velocityType = InitialVelocity.Free; [SerializeField] private double _initialVelocity = 0; // In standard km/s [Header("SpaceObject Properties")] [SerializeField] [Tooltip("external gravity forces do not affect this object")] private bool _disableGravityAffected = false; [SerializeField] [Tooltip("Kilograms")] private double _mass = 0; //Kg [SerializeField] [Tooltip("Kilometers")] private double _radius = 0; //km [SerializeField] [Tooltip("Kilometers. Distance at which entry to atmosphere happens")] private double _entryInterfaceDistance = 0; //Km. distance at which entry to atmosphere happens private Rigidbody _rigidbody; private GameObject _orbitAround; private bool initializationComplete = false; [Header("Debug")] [SerializeField] private double _currentVelocity = 0; // In standard km/s #endregion #region Properties public double Mass => _mass; public double CollisionDistance { get => (_radius + _entryInterfaceDistance) / _gameManager.SpaceScaleKm; } public double CurrentVelocity { get { return _currentVelocity * _gameManager.TimeScale / _gameManager.SpaceScaleKm; } set { _currentVelocity = value * _gameManager.SpaceScaleKm / _gameManager.TimeScale; } } public bool IsVelocityIncreasing { get; private set; } = true; #endregion void Awake() { var spaceObjects = GameObject.FindGameObjectsWithTag("SpaceObject").Where(o => o.gameObject != gameObject).ToArray(); _rigidbody = GetComponent<Rigidbody>(); InitializeGravity(spaceObjects, _mass); } void Update() { } void FixedUpdate() { try { if (!initializationComplete) { Initialization(); } if (!_disableGravityAffected) { _rigidbody.velocity += GetExternalNetGravityVector(); } UpdateVelocityStatus(); } catch (Exception e) { throw new SystemException("Problem obtaining gravity: " + e); } } private void UpdateVelocityStatus() { if (CurrentVelocity != _rigidbody.velocity.magnitude) { IsVelocityIncreasing = (CurrentVelocity < _rigidbody.velocity.magnitude) ? true : false; } CurrentVelocity = _rigidbody.velocity.magnitude; } private void Initialization() { _initialVelocity = GetInitialVelocity(_velocityType); _rigidbody.velocity = _initialDirection.normalized * (float)_initialVelocity; _initialVelocity = _initialVelocity * _gameManager.SpaceScaleKm / _gameManager.TimeScale; initializationComplete = true; } private double GetInitialVelocity(InitialVelocity initialVelocityType) { if (_spaceObjects.Length == 0) { throw new SystemException("No SpaceObjects found. Need at least 1 to get velocity"); } _orbitAround = _spaceObjects .OrderByDescending(o => o.GetComponent<SpaceObject>().GetGravitationalPullForce( (o.transform.position - transform.position).magnitude)) .First(); var distance = (_orbitAround.transform.position - transform.position).magnitude; var velocity = 0.0; switch (initialVelocityType) { case InitialVelocity.CircularOrbit: velocity = _orbitAround.GetComponent<SpaceObject>().GetVelocityForCircularOrbit(distance) + _orbitAround.GetComponent<Rigidbody>().velocity.magnitude; break; case InitialVelocity.EscapeOrbit: velocity = _orbitAround.GetComponent<SpaceObject>().GetEscapeVelocity(distance); break; default: //Free velocity velocity = _initialVelocity * _gameManager.TimeScale / _gameManager.SpaceScaleKm; break; } return velocity; } } |
These 2 classes have taken a complete refactoring to have a better segregation at what each class is supposed to do.
Sample project
A sample project can be found on this download link with all the relevant code.
Unity version: 2020.2.7f1
No comments:
Post a Comment