Using the SDK demo application as an example, this brief tutorial explains the four steps involved in writing an application which uses the Gravel v3 Physics Engine. See also the overview and reference sections.
The Gravel SDK Demo
Step 1: Create a Gravel scene
Step 2: Add astronomical objects to the Gravel scene
Step 3: Run the simulation
Step 4: Delete the Gravel scene
This function is called by WinMain and forms the main body of this demo simulation.
/***********************************************************************************************
runGravelSdkDemo
***********************************************************************************************/
bool8_t runGravelSdkDemo
(
void
)
{
static const GravelAPITimeframe timeframes[3] =
{
gravelTimeframeSeconds, gravelTimeframeMinutes, gravelTimeframeHours
};
static const size_t timeframesCount = sizeof(timeframes) / sizeof(timeframes[0]);
bool8_t result = false;
size_t timeframeIndex = 0;
printToGravelLog(NULL);
result = true;
for(timeframeIndex = 0; result && timeframeIndex < timeframesCount; ++timeframeIndex)
{
GravelAPISceneHandle demoScene = NULL;
if(0 != timeframeIndex) printToGravelLog("\n---+---\n\n");
result = result && createGravelDemoScene(timeframes[timeframeIndex], &demoScene);
result = result && populateGravelDemoScene(demoScene);
result = result && runGravelSimulation(demoScene);
deleteGravelDemoScene(demoScene);
demoScene = NULL;
}
return result;
}
|
Each supported timeframe is used in turn:
A Gravel scene is created and populated. The Gravel simulation is then run. Finally the Gravel scene is deleted.
/***********************************************************************************************
createGravelDemoScene
***********************************************************************************************/
bool8_t createGravelDemoScene
(
GravelAPITimeframe inTimeframe,
GravelAPISceneHandle* outGravelScene
)
{
bool8_t result = false;
char name[] = "Gravel v3 SDK Demo";
if(gravelResultOk != createGravelSceneWithData(inTimeframe,
(const uint8_t*)name, sizeof(name),
outGravelScene))
{
printToGravelLog("ERROR: Failed to create demo scene!\n");
}
else
{
printToGravelLog("Demo scene created successfully.\n");
result = true;
}
return result;
}
|
A blank Gravel scene is created, using the given timeframe.
The text string "Gravel v3 SDK Demo" is used as the scene's generic data.
In this demo, the Gravel scene consists of two objects: The earth and a small satellite.
/***********************************************************************************************
populateGravelDemoScene
***********************************************************************************************/
bool8_t populateGravelDemoScene
(
GravelAPISceneHandle inGravelScene
)
{
bool8_t result = false;
uint64_t timeframeFactor = 0;
uint64_t oneDay = 0;
float64_t orbitRadius = 0.0;
float64_t orbitSpeed = 0.0;
/*** Define the Gravel objects ***/
char earthName[] = "Earth";
GravelAPIObject earth =
{
5.9736e+24, /* mass (kilo-grams) */
6378.14e3, /* equatorialRadius (meters) */
6378.14e3 * 2.0, /* polarDiameter (meters) */
{0.0, 0.0, 0.0}, /* location (meters) */
{0.0, 0.0, 0.0}, /* velocity (meters/timeunit) */
NULL, /* data */
0 /* dataLength */
};
char satelliteName[] = "Satellite";
GravelAPIObject satellite =
{
1000.0, /* mass (kilo-grams) */
1.0, /* equatorialRadius (meters) */
2.0, /* polarDiameter (meters) */
{0.0, 0.0, 0.0}, /* location (meters) */
{0.0, 0.0, 0.0}, /* velocity (meters/timeunit) */
NULL, /* data */
0 /* dataLength */
};
earth.data = (const uint8_t*)earthName;
earth.dataLength = sizeof(earthName);
satellite.data = (const uint8_t*)satelliteName;
satellite.dataLength = sizeof(satelliteName);
/*** Calculate the satellite's initial orbital velocity & location ***/
/* Satellite takes two weeks to orbit the earth. */
timeframeFactor = calcTimeframeFactor(inGravelScene);
oneDay = (60 * 60 * 24) / timeframeFactor;
orbitRadius = calculateOrbitRadius(inGravelScene, earth.mass, 14 * oneDay);
orbitSpeed = calculateOrbitSpeed( inGravelScene, earth.mass, orbitRadius);
if(0.0 == orbitSpeed)
{
printToGravelLog("ERROR: Failed to calculate orbit speed!\n");
}
else
{
satellite.location = earth.location;
satellite.velocity = earth.velocity;
satellite.location.y += orbitRadius;
satellite.velocity.x += orbitSpeed;
/*** Add the objects to the Gravel scene ***/
if( gravelResultOk == addGravelObject(inGravelScene, &earth) &&
gravelResultOk == addGravelObject(inGravelScene, &satellite))
{
result = true;
}
else
{
printToGravelLog("ERROR: Failed to add Gravel object!\n");
}
}
return result;
}
|
Astronomical objects are described using the GravelAPIObject structure.
The earth has a mass of 5.97*10^24 kg and a radius of 6378 km.
We place the earth at the simulation's origin (0,0,0) with an initial speed of 0 m/s.
The text string "earth" is used as the object's generic data.
The satellite has a mass of 1000 kg and a radius of 1 m.
As we need to calculate the starting location & velocity of the satellite, we initially place it at the simulation's origin (0,0,0) with an initial speed of 0 m/s.
The text string "satellite" is used as the object's generic data.
The satellite shall orbit the earth once every two weeks.
The satellite's orbit radius is calculated from the earth's mass and the desired orbit period.
The satellite's orbital speed is calculated from the earth's mass and the orbit radius.
The satellite's relative location and velocity need to be added to those of the earth.
When a Gravel object is added to a Gravel scene, a copy of the Gravel object is stored inside the Gravel scene, and the original Gravel object can be discarded.
/***********************************************************************************************
runGravelSimulation
***********************************************************************************************/
bool8_t runGravelSimulation
(
GravelAPISceneHandle inGravelScene
)
{
bool8_t result = false;
uint64_t timeframeFactor = 0;
uint64_t oneDay = 0;
size_t days = 0;
logGravelSceneInfo(inGravelScene);
/*** Calculate the timeframe factor ***/
timeframeFactor = calcTimeframeFactor(inGravelScene);
if(timeframeFactor)
{
result = true;
}
else
{
printToGravelLog("ERROR: Failed to calculate timeframe factor!\n");
}
/*** Calculate acceleration on the surface of the earth ***/
if(result)
{
GravelAPIVector loc = {6378.14e3, 0.0, 0.0};
GravelAPIVector acc = {0.0, 0.0, 0.0};
if(gravelResultOk == calculateLocalGravelAcceleration(inGravelScene, &loc, &acc))
{
printToGravelLog("Acceleration on earth: %lf (m/s)/s\n\n", getGravelVectorLength(&acc) / (timeframeFactor * timeframeFactor));
}
else
{
printToGravelLog("ERROR: Failed to calculate acceleration on earth!\n");
return false;
}
}
/*** Run the simulation 28 days ***/
logGravelSimulationState(inGravelScene);
oneDay = (60 * 60 * 24) / timeframeFactor;
for(days = 0; result && days < 28; ++days)
{
bool8_t collisions = false;
if(gravelResultOk == advanceGravelTime(inGravelScene, oneDay, &collisions))
{
logGravelSimulationState(inGravelScene);
}
else
{
printToGravelLog("ERROR: Failed to advance Gravel time!\n");
result = false;
}
}
/*** Tell the satellite to accelerate at 1(m/s)/s for 24 hours ***/
if(result)
{
GravelAPIVector acceleration;
acceleration.x = 0.0;
acceleration.y = 0.0;
acceleration.z = 1.0 * (timeframeFactor * timeframeFactor);
if(gravelResultOk == accelerateGravelObject(inGravelScene, 1, &acceleration, oneDay))
{
printToGravelLog("\nAcceleration initiated successfully.\n\n");
}
else
{
printToGravelLog("ERROR: Failed to initiate acceleration!\n");
result = false;
}
}
/*** Run the simulation 7 days ***/
for(days = 0; result && days < 7; ++days)
{
bool8_t collisions = false;
if(gravelResultOk == advanceGravelTime(inGravelScene, oneDay, &collisions))
{
logGravelSimulationState(inGravelScene);
}
else
{
printToGravelLog("ERROR: Failed to advance Gravel time!\n");
result = false;
}
}
/*** Reset the simulation ***/
logGravelSceneInfo(inGravelScene);
printToGravelLog("Resetting the simulation\n");
if(gravelResultOk == resetGravelScene(inGravelScene))
{
logGravelSimulationState(inGravelScene);
}
else
{
printToGravelLog("ERROR: Failed to reset the simulation!\n");
result = false;
}
logGravelSceneInfo(inGravelScene);
return result;
}
|
The timeframe factor is the number of seconds per timeunit.
As the earth is at location (0,0,0) and the earth's radius is 6378.14 km, the acceleration at location (6378.14e3, 0, 0) is the acceleration on the surface of the earth.
Our satellite, in a circular orbit around the earth, will take 14 days to complete one orbit.
After the second completed orbit, the satellite is instructed to accelerate at 90 degrees to its orbital plane.
The satellite flies off into space.
The Gravel scene is reset to its initial state.
/***********************************************************************************************
deleteGravelDemoScene
***********************************************************************************************/
void deleteGravelDemoScene
(
GravelAPISceneHandle inGravelScene
)
{
if(gravelResultOk != deleteGravelScene(inGravelScene))
{
printToGravelLog("ERROR: Failed to delete demo scene!\n");
}
else
{
printToGravelLog("Demo scene deleted successfully.\n");
}
}
|
It is important to delete the Gravel scene before the application exits.
/***********************************************************************************************
calculateOrbitRadius
***********************************************************************************************/
float64_t calculateOrbitRadius
(
GravelAPISceneHandle inGravelScene,
float64_t inCentralMass,
uint64_t inOrbitPeriod
)
{
float64_t result = 0.0;
/* Note: 'g' is directly linked to the scene's timeframe. */
float64_t g = 0.0;
if(gravelResultOk == getGravelConstantOfGravity(inGravelScene, &g))
{
float64_t pi = atan(1.0) * 4.0;
float64_t period = (float64_t)inOrbitPeriod;
float64_t r3 = (period * period * g * inCentralMass) / (4.0 * pi * pi);
result = pow(r3, 1.0 / 3.0);
}
return result;
}
|
/***********************************************************************************************
calculateOrbitSpeed
***********************************************************************************************/
float64_t calculateOrbitSpeed
(
GravelAPISceneHandle inGravelScene,
float64_t inCentralMass,
float64_t inOrbitRadius
)
{
float64_t result = 0.0;
/* Note: 'g' is directly linked to the scene's timeframe. */
float64_t g = 0.0;
if(gravelResultOk == getGravelConstantOfGravity(inGravelScene, &g))
{
result = sqrt( (g * inCentralMass) / inOrbitRadius);
}
return result;
}
|
/***********************************************************************************************
getGravelVectorLength
***********************************************************************************************/
float64_t getGravelVectorLength
(
const GravelAPIVector* inVector
)
{
return sqrt(inVector->x * inVector->x +
inVector->y * inVector->y +
inVector->z * inVector->z);
}
|
/***********************************************************************************************
calcTimeframeFactor
***********************************************************************************************/
uint64_t calcTimeframeFactor
(
GravelAPISceneHandle inGravelScene
)
{
uint64_t result = 0;
GravelAPITimeframe timeframe = gravelTimeframeForce32BitEnum;
if(gravelResultOk == getGravelTimeframe(inGravelScene, &timeframe))
{
switch(timeframe)
{
case gravelTimeframeSeconds:
{
result = 1;
}
break;
case gravelTimeframeMinutes:
{
result = 60;
}
break;
case gravelTimeframeHours:
{
result = 60 * 60;
}
break;
}
}
return result;
}
|
/***********************************************************************************************
logGravelSceneInfo
***********************************************************************************************/
void logGravelSceneInfo
(
GravelAPISceneHandle inGravelScene
)
{
GravelAPITimeframe timeframe = gravelTimeframeForce32BitEnum;
bool8_t is3D = false;
size_t numOfObjs = 0;
char* name = NULL;
size_t nameLen = 0;
if(gravelResultOk == getGravelSceneData(inGravelScene, NULL, &nameLen))
{
name = (char*) malloc(nameLen);
if(NULL != name)
{
if(gravelResultOk != getGravelSceneData(inGravelScene, (uint8_t*)name, &nameLen))
{
free(name);
name = NULL;
}
}
}
if(NULL == name)
{
printToGravelLog("ERROR: Failed to get scene data!\n");
return;
}
if( gravelResultOk == getGravelTimeframe(inGravelScene, &timeframe) &&
gravelResultOk == getGravelSceneIs3D(inGravelScene, &is3D) &&
gravelResultOk == getNumberOfGravelObjects(inGravelScene, &numOfObjs))
{
printToGravelLog("\nGravel scene info:\n\n");
printToGravelLog("Name: %s\n", name);
printToGravelLog("Timeframe: %d\n", timeframe);
printToGravelLog("3D scene: %d\n", (int)is3D);
printToGravelLog("#objects: %u\n", numOfObjs);
printToGravelLog("\n\n\n");
}
else
{
printToGravelLog("ERROR: Failed to get scene info!\n");
}
free(name);
name = NULL;
}
|
/***********************************************************************************************
logGravelSimulationState
***********************************************************************************************/
void logGravelSimulationState
(
GravelAPISceneHandle inGravelScene
)
{
const uint64_t timeframeFactor = calcTimeframeFactor(inGravelScene);
if(0 == timeframeFactor)
{
printToGravelLog("ERROR: Failed to calculate timeframe factor!\n");
return;
}
else
{
size_t numOfObjs = 0;
if(gravelResultOk != getNumberOfGravelObjects(inGravelScene, &numOfObjs) || 2 != numOfObjs)
{
printToGravelLog("ERROR: Failed to verify number of Gravel objects!\n");
}
else
{
const GravelAPIObject* earth = NULL;
const GravelAPIObject* satellite = NULL;
if( gravelResultOk != getGravelObjectCPointer(inGravelScene, 0, &earth) ||
gravelResultOk != getGravelObjectCPointer(inGravelScene, 1, &satellite))
{
printToGravelLog("ERROR: Failed to get pointers to Gravel objects!\n");
}
else
{
uint64_t time = 0;
float64_t speed = 0.0;
GravelAPIVector relativeLocation;
GravelAPIVector relativeVelocity;
relativeLocation.x = satellite->location.x - earth->location.x;
relativeLocation.y = satellite->location.y - earth->location.y;
relativeLocation.z = satellite->location.z - earth->location.z;
relativeVelocity.x = satellite->velocity.x - earth->velocity.x;
relativeVelocity.y = satellite->velocity.y - earth->velocity.y;
relativeVelocity.z = satellite->velocity.z - earth->velocity.z;
speed = getGravelVectorLength(&relativeVelocity);
time = 0;
if(gravelResultOk != getElapsedGravelTime(inGravelScene, &time))
{
printToGravelLog("ERROR: Failed to get elapsed Gravel time!\n");
}
else
{
if(0 == time)
{
printToGravelLog("\n\t%s\t%s\n\n", earth->data, satellite->data);
printToGravelLog("\n\tDays\t\tX\tY\tZ\t\tSpeed (m/s)\n");
}
printToGravelLog("\t%u\t\t%5.3e\t%5.3e\t%5.3e\t\t%5.3e\n", (uint32_t)(time / ((60 * 60 * 24) / timeframeFactor)),
relativeLocation.x, relativeLocation.y, relativeLocation.z,
speed / timeframeFactor);
}
}
}
}
}
|
/***********************************************************************************************
printToGravelLog
***********************************************************************************************/
void printToGravelLog
(
const char* inFormat,
...
)
{
static const char* const logFilename = "gravel-log.txt";
if(NULL == inFormat)
{
/* Creat a new log file */
FILE* file = fopen(logFilename, "w");
if(NULL != file)
{
fclose(file);
file = NULL;
}
}
else
{
/* Add to the existing log file */
FILE* file = fopen(logFilename, "a+");
if(NULL != file)
{
va_list vaList;
va_start(vaList, inFormat);
vfprintf(file, inFormat, vaList);
va_end(vaList);
fclose(file);
file = NULL;
}
}
}
|