Golaem Simulation Cache API

Golaem Crowd is shipped with glm_crowd.h, a single-file library for C/C++. It is strongly advised to use the API provided by this file to handle Golaem Simulation Cache data. 

Including Golaem Simulation Cache API file in a project

The Golaem Simulation Cache API can be added to a project, by dropping the file in your project include path, and adding the following lines to your project:

#define GLMC_IMPLEMENTATION // use this define only once to define the implementation in one file
#include "glm_crowd.h" // can be included several times alone, but only once with GLMC_IMPLEMENTATION 

Some macros can be overriden to fit custom error handling and memory allocation :

  • assert macro defined by GLMC_ASSERT, default to assert (using assert.h)
  • malloc macro defined by GLMC_MALLOC, default to standard malloc()
  • realloc macro defined by GLMC_REALLOC, default to standard relloc()
  • free macro defined by GLMC_FREE, default to standard free()
The dependancy for loading and saving modification history in the JSON format can be removed by defining GLMC_NO_JSON.

Using the API to load cache files, read and write data

Load an existing cache

Loading is done in 2 steps.

The first step is to load a GlmSimulationData from a gscs file. It will read shared data valid for all the cache frame, such as sizes and types. There is one gscs (simulation) file per CrowdField to load. These files are loaded via the command :

GlmSimulationData* simulationData;
GlmSimulationCacheStatus status = glmCreateAndReadSimulationData(&simulationData, "cacheFilePath/cacheFileName.gscs");

The command returns a status GSC_SUCCESS on success, or an error from the table below.

Once a GlmSimulationData has been loaded, each frame data can be loaded via :

GlmFrameData* frameData;
glmCreateFrameData(&frameData, simulationData);
GlmSimulationCacheStatus status = glmReadFrameData(frameData, simulationData, "cacheFilePath/cacheFileName.gscf");

The read command returns a status GSC_SUCCESS on success, or an error amongst:

GSC_SUCCESS No Error, everything went fine.
GSC_FILE_OPEN_FAILED File does not exist, or have access rights issues, or is already opened by another process
GSC_FILE_MAGIC_NUMBER_ERROR File header does not begin by 0x65CF or 0x65CF, this is not a Golaem Simulation Cache
GSC_FILE_VERSION_ERROR Incorrect version, could be a newer version of the Golaem Simulation Cache
GSC_FILE_FORMAT_ERROR Incorrect format, could be a newer version of the Golaem Simulation Cache
GSC_SIMULATION_FILE_DOES_NOT_MATCH The GlmSimulationData, used to read that frame file, does not match the one used at the export (the size of some items differ : entity count, etc.)

Accessing/Modifying Cache data

To see how to access data once the cache is loaded, you can refer to glmCrowdSimulationCacheReaderPlugin source provided in the devkit directory of your Golaem Crowd installation directory. 

Writing a cache 

Once cache data has been modified, it can be written back if required :

GlmSimulationCacheStatus status = glmWriteFrameData("cacheFilePath/cacheFileName.gscf", frameData, simulationData);

Clean-up allocated memory

Note that GlmFrameData can be kept allocated to edit several frames. Once done with reading./editing, each Simulation and frame data must be destroyed by calling the commands :

glmDestroyFrameData(&frameData, simulationData);

then

glmDestroySimulationData(&simulationData);

Basic usage sample code

GlmSimulationCacheStatus status;
GlmSimulationData* simulationData;
status = glmCreateAndReadSimulationData(&simulationData, gscsFileName);
if (status != GSC_SUCCESS) ...
GlmFrameData* frameData;
glmCreateFrameData(&frameData, simulationData);
status = glmReadFrameData(frameData, simulationData, gscfFileName);
if (status != GSC_SUCCESS) ...
...
status = glmReadFrameData(frameData, simulationData, gscfFileName);
if (status != GSC_SUCCESS) ...
...
glmDestroyFrameData(&frameData);
glmDestroySimulationData(&simulationData);

Using the API to modify cache files

Loading modification history file

Modification history can be allocated to be filled later by the user :

GlmHistory* history;
glmCreateHistory(&history, transformCount, entityCount, totalPostureCount, totalPostureBoneCount, totalEntityTypesBoneCount, totalMeshAssetsOverride, duplicatedEntityCount, totalEntityTypeCount, expandCount);

Or by loading a JSON formated file :

GlmSimulationCacheStatus status;
status = glmCreateAndReadHistoryJSON(&history, "cacheFilePath/cacheModificationsFileName.gscl");
if (status != GSC_SUCCESS) ...

Both functions create a GlmHistory that must be destroyed by calling

glmDestroyHistory(&history);

Applying modifications

Modifications must be applied on GlmSimulationData and GlmFrameData. The modification is a 3 times process:

  • create GlmEntityTransform array from GlmHistory
  • use GlmEntityTransform array to modify GlmSimulationData and GlmFrameData
  • free GlmEntityTransform and GlmHistory

Step 1 is done by calling the functions

GlmEntityTransform* entityTransforms;
int entityTransformCount;
glmCreateEntityTransforms(simulationData, history, &entityTransforms, &
entityTransformCount);
Step 2 is done by calling for the GlmSimulationData and GlmFrameData respectively:
GlmSimulationData *simulationDataDestination;
GlmFrameData *frameDataDestination;
glmCreateModifiedSimulationData(simulationDataSource, entityTransforms, entityTransformCount, & simulationDataDestination);
glmCreateModifiedFrameData(simulationDataSource, frameDataSource, entityTransforms, entityTransformCount, history, simulationDataDestination, &frameDataDestination, currentFrame);
Step 3 is done thru GlmDestroyHistory and
glmDestroyEntityTransforms(&entityTransforms, entityTransformCount);

Terrain raycasting and ground adaptation

Modification might need the characters to be adapted on ground. When a modification translates characters on an even terrain by example. To achieve this adaptation, API user must provide a ray cast C callback function that will be called when needed.

int RaycastClosest(void *terrain, const float* rayOrigin, const float* rayEnd, float *collisionPoint, float *collisionNormal)
{
...
// return 1 if the ray is casted by geometry
}
Set the function pointer to be used by the API:

glmRaycastClosest = RaycastClosest;

The terrain parameter is a user value that is set in _terrainMesh  in the GlmHistory instance.

Basic usage sample code

GlmSimulationCacheStatus gsclStatus = glmCreateAndReadHistoryJSON(&history, simulationTransformHistoryFilePath.c_str());
 
if (gsclStatus != GSC_SUCCESS) ...
 
history->_terrainMesh = myTerrainMesh;
glmRaycastClosest = RaycastClosest;
 
glmCreateEntityTransforms(simulationData, history, &entityTransforms, &entityTransformCount);
 
GlmSimulationData* simulationDataOut;
GlmFrameData* frameDataOut;
glmCreateModifiedSimulationData(simulationData, entityTransforms, entityTransformCount, &simulationDataOut);
glmCreateModifiedFrameData(simulationData, frameData, entityTransforms, entityTransformCount, history, simulationDataOut, &frameDataOut, currentFrame);
 
// replace previous simulation & frame data
glmDestroyFrameData(&frameData, simulationData);
glmDestroySimulationData(&simulationData);
 
frameData = frameDataOut;
simulationData = simulationDataOut;