- Home /
Solar System Simulator Orbits Not Working
Hi. I am working on a simple solar system simulator and am trying to get 1 sphere to orbit another. this works fine for a few seconds at about simspeed = 400 before having a physics freakout (jittering) and teleporting before orbiting at a further distance. any help or even ideas as to what could be going wrong are much appreciated. I have an empty scene with a gamemanager object and 2 spheres, as well as a camera and light of course. my gamemanager has the scripts gamemanager and commonfuncs on it, while the spheres have a mesh renderer and filter plus a rigidbody and collider and finally the gravitybody script. the orbiting gravitybody has a start velocity of (-0.632,0.632,0.632) and a mass of 1 while the one being orbited has no start velocity and a mass of 20. sorry for the code dump but i cannot narrow this down.
CommonFuncs (Vector Math in here):
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CommonFuncs : MonoBehaviour
{
public float pythagHypot3(Vector3 vector) //Get the hypotenuse using 3d pythagoras
{
float a = vector.x; float b = vector.y; float c = vector.z;
float hsq = a*a + b*b + c*c;
return(Mathf.Sqrt(hsq));
}
public Vector3 unitVector(Vector3 vector) //Get the unit vector of any vector3
{
float magnitude = pythagHypot3(vector);
return(vector / magnitude);
}
public Vector3 vectorSq(Vector3 vector)
{
float x = Mathf.Pow(vector.x,2);
float y = Mathf.Pow(vector.y,2);
float z = Mathf.Pow(vector.z,2);
return(new Vector3(x,y,z));
}
public Vector3 divByVector(float numerator, Vector3 divisor)
{
float x = numerator / divisor.x;
float y = numerator / divisor.y;
float z = numerator / divisor.z;
return(new Vector3(x,y,z));
}
public Vector3 vectorMult(Vector3 a, Vector3 b)
{
float x = a.x * b.x;
float y = a.y * b.y;
float z = a.z * b.z;
return(new Vector3(x,y,z));
}
public Vector3 absVector(Vector3 vector)
{
float x = Mathf.Abs(vector.x);
float y = Mathf.Abs(vector.y);
float z = Mathf.Abs(vector.z);
return(new Vector3(x,y,z));
}
}
And finally gravitybody:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GravityBody : MonoBehaviour
{
Gamemanager gamemanager;
Rigidbody rigidbody;
public List<GravityBody> attractedTo = new List<GravityBody>();
public Vector3 velocity;
Vector3 lastForce;
public bool hasOwnGravity;
public float mass;
void Awake()
{
//Get Components
gamemanager = GameObject.Find("GameManager").GetComponent<Gamemanager>();
rigidbody = GetComponent<Rigidbody>();
//Init part 1
if(hasOwnGravity){ //Add to ownGravityBodies if applicable
gamemanager.ownGravityBodies.Add(this);}
rigidbody.velocity = velocity; //Apply Starting Velocity
rigidbody.mass = mass; //Apply Mass
lastForce = Vector3.zero; //Set First Last Force
}
void Start()
{
//Init part 2
foreach(GravityBody ownGravityBody in gamemanager.ownGravityBodies)
{
if(ownGravityBody != this)
{
attractedTo.Add(ownGravityBody);
}
}
}
void FixedUpdate()
{
UpdateForce();
}
void UpdateForce()
{
//Calculate Force
Vector3 currentForce = new Vector3(0f,0f,0f);
foreach(GravityBody body in attractedTo)
{
currentForce += GetForceOneBody(body);
}
currentForce *= gamemanager.simSpeed;
//Apply Force
rigidbody.AddForce(-lastForce);
rigidbody.AddForce(currentForce);
lastForce = currentForce;
}
Vector3 GetDistance(GravityBody _otherPoint)
{
Transform otherPoint = _otherPoint.gameObject.transform;
Transform thisPoint = this.gameObject.transform;
float x = otherPoint.position.x - thisPoint.position.x;
float y = otherPoint.position.y - thisPoint.position.y;
float z = otherPoint.position.z - thisPoint.position.z;
return(new Vector3(x,y,z));
}
Vector3 GetForceOneBody(GravityBody otherBody)
{
CommonFuncs funcs = gamemanager.funcs;
Vector3 distance = GetDistance(otherBody);
Vector3 unitVector = funcs.unitVector(distance);
distance = funcs.absVector(distance); //Obtain distance, after raw value is used for unit vector
Vector3 fractionPart = funcs.divByVector(otherBody.mass * mass,funcs.vectorSq(distance)); //The section of the equation that is a fraction
Vector3 forceVector = gamemanager.gravityConstant * funcs.vectorMult(fractionPart,unitVector);
return(forceVector);
}
void OnCollisionEnter()
{
rigidbody.isKinematic = true;
}
}
Answer by andrew-lukasik · Nov 08, 2020 at 04:50 PM
- Rigidbody class is NOT designed nor fit for solar system simulations.
Hardcoded value limits (like 1e9 kg for mass) alone will render those calculation impractical. Rigidbody
component is not for all the physics but just for very game-specific simple mechanical physics i.e.: approximating dynamics between similar (+-human) sized objects.
- Code above is full of little math mistakes and unnecessary calculations; those probably compound.
However, here's fixed version of your code. It works but it's a dead end for many reasons.
using System.Collections.Generic;
using UnityEngine;
[RequireComponent( typeof(Rigidbody) )]
[RequireComponent( typeof(SphereCollider) )]
public class GravityBody : MonoBehaviour
{
[SerializeField] float _initialSpeed = 0.1f;
[SerializeField][Range(1,1e9f)] float _initialmass = 1e9f;
static List<GravityBody> _instances = new List<GravityBody>(10);
Rigidbody _rigidbody;
Vector3 _resultantForce;
void Awake ()
{
_instances.Add( this );
_rigidbody = GetComponent<Rigidbody>();
_rigidbody.useGravity = false;
_rigidbody.isKinematic = false;
_rigidbody.drag = 0;
_rigidbody.angularDrag = 0;
_rigidbody.mass = _initialmass;
_rigidbody.velocity = transform.forward * _initialSpeed;
}
void FixedUpdate ()
{
_resultantForce = default(Vector3);
float m1 = _rigidbody.mass;
Vector3 p1 = transform.position;
foreach( GravityBody otherBody in _instances )
{
Vector3 p2 = otherBody.transform.position;
Vector3 forceDir = p2 - p1;
float distSq = forceDir.sqrMagnitude;
if( distSq>0f )
{
float m2 = otherBody._rigidbody.mass;
_resultantForce += forceDir.normalized * ( 6.6743e-11f * ( (m1*m2) / distSq ) );
}
}
_rigidbody.AddForce( _resultantForce , ForceMode.Force );
}
}
No Rigidbody version
Requires com.unity.mathematics
package.
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
public class GravityBody : MonoBehaviour
{
[Header("Body:")]
[SerializeField][Tooltip("[m/s]")] float _initialSpeed = 0.1f;
[SerializeField][Tooltip("[kg]")] float _mass = 5.972E24f;
[SerializeField][Tooltip("[kg/m^3]")] float _density = 5000f;
[Header("Read Only:")]
[SerializeField][Tooltip("[N]")] double3 _resultantForce;
[SerializeField][Tooltip("[kg*m/s]")] double3 _momentum;
[SerializeField][Tooltip("[m/s]")] double3 _velocity;
[SerializeField][Tooltip("[m]")] float _radius = 0;
[SerializeField][Tooltip("[{m,m,m}]")] double3 _position;
static List<GravityBody> _instances = new List<GravityBody>(10);
#if UNITY_EDITOR
void OnValidate ()
{
_velocity = (double3)(float3)( transform.forward * _initialSpeed );
_momentum = _mass * _velocity;
_radius = SphereRadius( volume: _mass/_density );
_position = (double3)(float3) transform.position;
}
#endif
void OnEnable () => _instances.Add( this );
void OnDisable () => _instances.Remove( this );
void FixedUpdate ()
{
// calculate resultant force;
float m1 = this._mass;
_resultantForce = default(double3);
for( int i=_instances.Count-1 ; i!=-1 ; i-- )
{
GravityBody other = _instances[i];
double3 forceDir = other._position - _position;
double distSq = math.lengthsq( forceDir );
if( distSq>0d )
{
float m2 = other._mass;
if( distSq > math.pow(math.max(_radius,other._radius),2f) )
_resultantForce += math.normalize(forceDir) * Force( m1:m1 , m2:m2, distSq:distSq );
else
{
// collision!
_radius = SphereRadius( SphereVolume(_radius) + SphereVolume(other._radius) );
_mass += m2;
_momentum += other._momentum;
_position = math.lerp( _position , other._position , math.saturate(m2/(m1+m2)) );
transform.position = (Vector3)(float3) _position;
Debug.Log( $"<b>{name}</b> destroys <b>{other.name}</b> in collision, distSq:{distSq}" , gameObject );
_instances.RemoveAt(i);
Destroy( other.gameObject );
}
}
}
// calculate new velocity:
double dt = Time.fixedDeltaTime;
_momentum += _resultantForce * dt;
_velocity = _momentum / _mass;
// update object position:
_position += _velocity * dt;
transform.position = (Vector3)(float3) _position;
}
static double Force ( double m1 , double m2 , double distSq ) => 6.6743e-11f * ( (m1*m2) / distSq );
static float SphereVolume ( float radius ) => 4f/3f * math.PI * math.pow(radius,3f);
static float SphereRadius ( float volume ) => math.pow( ( 3f * volume )/( 4f*math.PI ) , 1f/3f );
#if UNITY_EDITOR
void OnDrawGizmos () => Gizmos.DrawSphere( transform.position , _radius );
[ContextMenu("Randomize parameters")]
void RandomizeParameters ()
{
_initialSpeed *= UnityEngine.Random.Range( 0.6f , 1.4f );
_mass *= UnityEngine.Random.Range( 0.6f , 1.4f );
_density *= UnityEngine.Random.Range( 0.6f , 1.4f );
OnValidate();
}
[ContextMenu("Randomize initial speed")] void RandomizeInitialSpeed () { _initialSpeed *= UnityEngine.Random.Range( 0.6f , 1.4f ); OnValidate(); }
[ContextMenu("Randomize mass")] void RandomizeMass () { _mass *= UnityEngine.Random.Range( 0.6f , 1.4f ); OnValidate(); }
[ContextMenu("Randomize density")] void RandomizeDensity () { _density *= UnityEngine.Random.Range( 0.6f , 1.4f ); OnValidate(); }
[ContextMenu("Validate parameters")] void ValidateParameters () => OnValidate();
#endif
}
Answer by Spemble · Nov 06, 2020 at 11:18 PM
Does rigidbody.AddForce() only apply the force for the current physics step? In the example in the docs on Rigidbody.AddForce(), it shows the force being added each FixedUpdate but not subtracting the force from last frame.
Try taking out the rigidbody.AddForce(-lastForce); in gravitybody on line 57.
Thanks for your answer. It certainly fixed that bug but now everything is rather odd so i suspect this might not be the answer. thanks though!