I have a goal of at least one post per month, but I haven't had much time to devote to my projects lately (not to mention most of my tools are packed in moving boxes). So for now I'm going to jump back to the Orbital Aero Model where the posts are way behind the actual coding. Watching SpaceX land rockets and reading The Martian are good motivators as well!
TypeDefinitions.h
Defining a variable as "unsigned long long" throughout the code would be pretty awful, so I keep around a bunch of type definitions in my projects so it's easier for me to track. Nothing too amazing here, but you would need these typedefs to compile my code!
#pragma once
typedef char int8;
typedef unsigned char uint8;
typedef short int16;
typedef unsigned short uint16;
typedef int int32;
typedef unsigned int uint32;
typedef unsigned long long uint64;
typedef float float32;
typedef double double64;
Body Model
I've already gone over the VectorModel which will aid in the math routines. Next up is the BodyModel which is the basis for each simulated entity.
BodyModel.h
#pragma once
#include "math.h"
#include "TypeDefinitions.h"
#include "VectorModel.h"
class BodyModel
{
public:
BodyModel(void);
~BodyModel(void);
uint64 index; // Unique identification of this body.
bool stationary; // Whether or not this body can move in space.
bool active; // Whether or not this body is to be included in calculations.
Vector forceDirect_newtons;
Vector forceIndirect_newtons;
Vector forceTotal_newtons();
Vector location_meters;
Vector velocity_metersPerSecond;
Vector acceleration_metersPerSecond2;
double massBase_kilograms;
double massTotal_kilograms();
// 3-D shape for collision detection
double radius_meters;
// 3-D Coefficient of Drag
double dragReferenceArea_meters2;
double dragCoefficient;
// Atmosphere
int atmosphereDensityCount;
double atmosphereDensityInterval_meters;
double atmosphereMaximumAltitudeMSL_meters();
double atmosphereDensityMSL_kilogramsPerMeter3[120];
double getAtmosphereDensityMSL_kilogramsPerMeter3(double altitudeMSL_meters);
private:
};
The index variable is the unique identification of the body; no two bodies will share the same index. When forces are calculated, they are stored in the 2 force vectors: forceDirect and forceIndirect. The location, velocity, and acceleration are referenced from an arbitrary static location in the world - not any particular body in space (such as the Earth or Sun). The other fields keep track of the physical properties of the body so the physics interactions between bodies can be calculated.
BodyModel.cpp
And the function calls for that class...
// Body Constructor BodyModel::BodyModel(void)
{
this->stationary = false;
this->active = false;
this->radius_meters = 0.0;
this->atmosphereDensityCount = 0;
this->atmosphereDensityInterval_meters = 0.0;
this->atmosphereDensityMSL_kilogramsPerMeter3[0] = 0.0;
this->location_meters = 0.0;
this->velocity_metersPerSecond = 0.0;
this->acceleration_metersPerSecond2 = 0.0;
this->massBase_kilograms = 0.0;
this->dragReferenceArea_meters2 = 0.0;
this->dragCoefficient = 0.0;
return;
}
// Body Destructor BodyModel::~BodyModel(void)
{
return;
}
// Return the total force acting on the body.
Vector BodyModel::forceTotal_newtons()
{
return (this->forceIndirect_newtons + this->forceDirect_newtons);
}
// Return the total mass of the body.
double BodyModel::massTotal_kilograms()
{
return massBase_kilograms; // + fuel/payload/etc masses
}
// Get the highest altitude with significant atmosphere for this body. double BodyModel::atmosphereMaximumAltitudeMSL_meters()
{
return (double)(atmosphereDensityCount - 1) * atmosphereDensityInterval_meters;
}
// Get the atmospheric density for an altitude relative to this body. double BodyModel::getAtmosphereDensityMSL_kilogramsPerMeter3(double altitudeMSL_meters)
{
if (atmosphereDensityInterval_meters > 0.0 && altitudeMSL_meters >= 0.0)
{
// Interpolate
int altitudeIndex = (int)floor(altitudeMSL_meters / atmosphereDensityInterval_meters);
if (altitudeIndex < atmosphereDensityCount)
{
return atmosphereDensityMSL_kilogramsPerMeter3[altitudeIndex];
}
}
return 0.0;
}
Orbital Aero Model
The OrbitalAeroModel manages the physics and kinematics of all the bodies; expect this class to be majorly overhauled as the software progresses.
Of note:
- A single "OrbtialAeroModel" class is created that hosts the entire simulation.
- Public functions from the class are available to create bodies, remove bodies, update properties of bodies, and propogate the simulation.
- Private functions within the class handle the physics and kinematics of the simulation as well as entity record keeping.
- Each body has a unique 64-bit unsigned integer for identification. 18,446,744,073,709,551,616 entities should be enough ;-)
OrbitalAeroModel.h
using std::map;
class OrbitalAeroModel
{
public:
OrbitalAeroModel(void);
~OrbitalAeroModel(void);
// Body Management
uint64 createBody();
bool removeBody(uint64 index);
BodyModel &OrbitalAeroModel::getBody(uint64 index);
// Primary Update for Simulation Processing
bool update(double timeStep_seconds);
private:
// Counter to ensure unique identifications for bodies.
uint64 indexCounter;
// Table to hold all the bodies.
map <uint64, BodyModel> tableBodies;
// Specific Physics Processing
bool processIndirectForces();
bool processDirectForces();
bool processKinematics(double timeStep_seconds);
bool processCollisions(double timeStep_seconds);
// Table Management
bool doesBodyExistInTable(uint64 index);
BodyModel &getBodyFromBodiesTable(uint64 index);
bool updateBodyInBodiesTable(uint64 index, BodyModel bodyObject);
bool removeBodyFromBodiesTable(uint64 index);
bool clearBodiesTable();
};
Before I dive into the processing of the bodies, let's briefly cover the management of them.
The header defines a tableBodies which holds all bodies in the simulation. The key of the table is the unique 64-bit unsigned integer of the body, and the value of the table is a BodyModel containing all the body's data.
Tables.cpp
5 private helper functions handle the table record keeping:
// Return whether or not a given body exists in the simulation.
bool OrbitalAeroModel::doesBodyExistInTable(uint64 index)
{
// Search through the table for a given index.
if (tableBodies.find(index) != tableBodies.end())
{
// Record found, return true.
return true;
}
else
{
// Record not found, return false.
return false;
}
}
// Get a reference to a body from its unique ID.
BodyModel &OrbitalAeroModel::getBodyFromBodiesTable(uint64 index)
{
// Define an iterator to store values from the record table.
map <uint64, BodyModel>::iterator iteratorBodyObject;
// Search through the table for a given index and store in the iterator.
iteratorBodyObject = tableBodies.find(index);
// Return value portion (the record) of the table iterator.
return iteratorBodyObject->second;
}
// Update a body into the simulation.
bool OrbitalAeroModel::updateBodyInBodiesTable(uint64 index, BodyModel bodyObject)
{
// Update the table with the given record.
tableBodies[index] = bodyObject;
return true;
}
// Remove a body from the simulation.
bool OrbitalAeroModel::removeBodyFromBodiesTable(uint64 index)
{
// Remove the given index from the table.
tableBodies.erase(index);
return true;
}
// Clear all bodies in the simulation.
bool OrbitalAeroModel::clearBodiesTable()
{
// Clear the table.
tableBodies.clear();
return true;
}
OrbitalAeroModel.cpp
3 public functions gives access to the bodies:
// Create a new body to act in the simulation.
uint64 OrbitalAeroModel::createBody()
{
BodyModel thisBody;
indexCounter ++;
thisBody.index = indexCounter;
thisBody.active = true;
updateBodyInBodiesTable(thisBody.index, thisBody);
return thisBody.index;
}
// Remove a body from the simulation.
bool OrbitalAeroModel::removeBody(uint64 index)
{
if (doesBodyExistInTable(index))
{
return removeBodyFromBodiesTable(index);
}
return false;
}
// Get a simulation body.
BodyModel &OrbitalAeroModel::getBody(uint64 index)
{
return getBodyFromBodiesTable(index);
}
A single update function is called to do all processing of bodies within the simulation. A time step is passed in so it knows how far to propagate the entities. The shorter the time step the closer the simulation will match reality, but it also means it's more computational intensive.
// Primary function to process the physics and kinematics all bodies.
bool OrbitalAeroModel::update(double timeStep_seconds)
{
// It's important all forces are calculated before all positions are updated before all collisions
// are checked, etc. That way the order in which bodies are looped through does not affect how they
// affect other bodies in space.
// First calculate indirect forces, such as those from gravity and atmospheric drag.
// Currently includes the thrust of engines, but that may be separated eventually.
processIndirectForces();
// Next add frictional forces.
// This must be done after gravity/atmospheric forces to know the normal force for friction.
processDirectForces();
// Can now translate and rotate each body.
processKinematics(timeStep_seconds);
// After moving the body, check for collisions and adjust positions/velocities as appropriate.
processCollisions(timeStep_seconds);
return true;
}
Each of those process functions perform the actual physics on the bodies. Rather than try to cover them all in this post, I'll devote individual posts to each one.
To cover everything there is on the record management side, I will show how each of those process functions loops through bodies:
bool OrbitalAeroModel::process######()
{
map <uint64, BodyModel>::iterator iteratorPrimaryBody = tableBodies.begin();
while (iteratorPrimaryBody != tableBodies.end())
{
BodyModel &primaryBody = iteratorPrimaryBody->second;
if (primaryBody.active)
{
map <uint64, BodyModel>::iterator iteratorSecondaryBody = iteratorPrimaryBody;
iteratorSecondaryBody++;
while (iteratorSecondaryBody != tableBodies.end())
{
BodyModel &secondaryBody = iteratorSecondaryBody->second;
if (secondaryBody.active && (&primaryBody != &secondaryBody))
{
// Do some math!
} // ((secondaryBody.active) && (primaryBody.index != secondaryBody.index))
iteratorSecondaryBody++;
} // (iteratorSecondaryBody != tableBodies.end())
} // (primaryBody.active)
iteratorPrimaryBody++;
} // (iteratorPrimaryBody != tableBodies.end())
return true;
}