Tutorial


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

Helper functions.

 


 

The Gravel SDK Demo

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.


 

Step 1: Create a Gravel scene

/***********************************************************************************************
    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.


 

Step 2: Add astronomical objects to the Gravel scene

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;
}

Define the Gravel objects

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.

Calculate the satellite's initial orbital velocity & location

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.

Add the objects to the Gravel scene

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.


 

Step 3: Run the simulation

/***********************************************************************************************
    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;
}

Calculate the timeframe factor

The timeframe factor is the number of seconds per timeunit.

Calculate acceleration on the surface of the earth

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.

Run the simulation 28 days

Our satellite, in a circular orbit around the earth, will take 14 days to complete one orbit.

Tell the satellite to accelerate at 1(m/s)/s for 24 hours

After the second completed orbit, the satellite is instructed to accelerate at 90 degrees to its orbital plane.

Run the simulation 7 days

The satellite flies off into space.

Reset the simulation

The Gravel scene is reset to its initial state.


 

Step 4: Delete the Gravel scene

/***********************************************************************************************
    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.


 

Helpers

/***********************************************************************************************
    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;
        }
    }
}

 

Valid HTML 4.01