Integrating Vortex Studio using the VortexIntegration API
This section targets developers who wants to use the Vortex Application as a part of a bigger application. This section describes how to configure Vortex, load content into it and get it running from an external application using VortexIntegration C API.
Then how to feed Vortex with inputs, as well as extracting data to feed the non vortex application.
The user is invited to look into the tutorials ExVHLIntegration. This section will refer to it as an example.
Using VortexIntegration
The VortexIntegration library is a simple to use C API. The interface is Vortex agnostic and is presented in 3 headers files, located under <CM-Labs installation folder>/include/VortexIntegration.
- VortexIntegrationLibrary.h: Wrapper for the Windows platform __declspec() import or import.
- Structs.h: The interface being Vortex agnostic, these data structures represents Vortex Data as generic types.
- VortexIntegration.h: The only header you need (it includes the other 2). It is the C API to integrate Vortex.
Your application will only need to link with the VortexIntegration library file, located under <CM-Labs installation folder>/lib.
The library is named VortexIntegration.lib (or VortexIntegrationd.lib in debug, available with the Vortex Studio Debug Kit)
At runtime, the library needs a Vortex Studio Installation to run properly.
The Vortex Integration relies on Vortex Standard object to run properly. You will need to setup your application with a setup file, load mechanism and scenes.
Communication is bi-directional. Value can be read from Vortex using content's VHL and control interfaces. Value can also be set the same way. It is also possible to feed terrain data as a tiled height fields.
Example: ExVHLIntegration
Tutorials ExVHLIntegration is a small game using SDL for rendering and inputs. It uses Vortex for the physics aspect of the game. Code can be found under <CM-Labs installation folder>/tutorials/ExVHLIntegration.
Setting up a Vortex Application
The Vortex Application is the central object that contains modules and extensions; At every application update, those modules will read content objects and update them.
Function VortexCreateApplication()
is your entry point. It takes the following parameters:
- setupDocument: An absolute path to the setup document (.vxc) to apply on the Vortex Application. Setup <CM-Labs installation folder>/resources/config/VortexIntegration.vxc is available for basic integration. For more information, see Simulator Setup User Guide
- materialTable: An absolute path to the material table document (.vxmaterials) to add to the Vortex Application. Can be nullptr if you already have a material table in a scene that will be loaded. For more information, see Creating and Saving a New Material Table
- dataStore: An absolute path to the data provider path of the Vortex Application. For more information, see Vortex Studio Director under Simulator Data Store Path
- optionalLogFilepath: Will set the
LogFile
parameter of the setup file, but only if the parameter is not set in the setup document. It should be a full path prefixed to the log file which will be created by the application. Leaving this field's value empty ("") or not specifying this field in your setup will resolve to a default value equal to your system's temp folder (%TEMP% on Windows, /tmp on Linux). terrainProviderInfo: A structure with all the callbacks to use the Terrain Provider API.
The vortex application will be kept alive until you no longer need it. To release the Vortex Application, invoke VortexDestroyApplication()
.
Example: ExVHLIntegration
This tutorial comes with its own setup file <CM-Labs installation folder>/tutorials/Resources/ExVHLIntegration. The material table is not loaded with function VortexCreateApplication
since it is part of the scene loaded after.
.... mVortexCreated = VortexCreateApplication("../resources/ExVHLIntegration/ExVHLIntegration.vxc", nullptr, nullptr, nullptr, {}); if (!mVortexCreated) { throw std::runtime_error("Cannot create Vortex Application with setup \"../resources/ExVHLIntegration/ExVHLIntegration.vxc\"."); } ... // Cleanup if (mVortexCreated) { VortexDestroyApplication(); mVortexCreated = false; }
Running Vortex
The Vortex Application needs to be ran manually. In order to perform an update, simply call VortexUpdateApplication()
VortexUpdateApplication()
will sleep so that it takes no less than a time step in execution time, unless the application's synchronization mode is set to none or VSync.VortexUpdateApplication()
returns false when the application needs to stop.
Considering that this section is dedicated to integrating Vortex, you should set the synchronization mode to None and call update manually.
Function VortexUpdateApplication()
should be called once per frame, no matter the simulation mode and also when the simulation is paused, as modules requires updates no matter the mode or the pause state.
Application Modes
Application mode is used by Modules and Extensions to make some decisions. Modules and extensions are aware of what the application is doing and can adapt their behavior.
There are 3 application modes in Vortex :
- Editing mode (kVortexModeEditing): The simulation is not computed at every steps. That's when a user usually changes object's parameters, move objects, add or remove objects etc.
- Simulating mode (kVortexModeSimulating): The simulation is computed at every steps, objects parameters should not be modified. The simulation can be paused and resumed.
- Playback mode (kVortexModePlayingBack): The simulation is not computed, objects are moved from the playback source so that content is replayed “graphically”.
The application mode is represented by the enum VortexApplicationMode. Setting the mode can take more than one update to be set, as some modules may requires additional update before mode can be changed. The Vortex Studio applications, such as the Editor and the Player, are using those modes.
Function VortexSetApplicationMode()
can be used to set the Application Mode. The function is asynchronous and the mode will change when possible. You can either set the parameter waitForModeToBeApplied
to true
, in which case the function will perform several update until the mode is changed or 60 updates were done. Alternatively, set waitForModeToBeApplied
to false, but
you will need to call VortexUpdateApplication
() several time and validate the current mode using VortexGetApplicationMode
().
Here are some example of the application mode:
- The Editor starts in editing mode. The user edits his content. When the user hits the play button, it goes is simulating mode. On stops, it goes back to editing.
- The Player also starts in editing mode. The user selects his content. When the user hits the play button, it goes is simulating mode. On stop, it goes back to editing. It the user loads a recording, the player goes in playback mode.
- The default mode for a Vortex Application is simulating, it can be set to editing in the Application Setup.
Pausing the simulation
If the mode is set to simulation, the user can pause the simulation by calling VortexPause(true)
.
VortexUpdateApplication() should still be called, but the physics object will not move.
To simulate a single step while paused, use function VortexStepOnce
(). After step once, the simulation will be paused.
Simulation can be resumed be calling VortexPause(false)
.
Current pause state can be fetched by calling VortexIsPaused().
Synchronization Modes
Synchronization mode indicates the application how to synchronize the simulation time with the real time. The synchronization mode is represented by the enum VortexSynchronizationMode.
There are 4 modes available:
- No synchronization (kVortexSyncNone) : All nodes go as fast as possible.
- Vertical synchronization (kVortexSyncVSync) : Application is synchronized with the monitor's refresh rate. VSync must be activated on the graphics card. Vortex Application without a graphics module are synchronized as if there was no synchronization.
- Software synchronization (kVortexSyncSoftware): Synchronization is done by software. The Vortex Application will wait between update to respect its frame rate.
- Software and vertical synchronization (kVortexSyncSoftwareAndVSync) : Combines Vortex Application software synchronization with vertical synchronization. This the default mode.
The current mode set in the setup document can be fetched using VortexGetSynchronizationMode
().
Application helpers
There are few helpers to get and set basic information about the on-going simulation.
VortexResetSimulationTime
(): Resets the simulation time to 0 and the simulation frame to 0VortexGetApplicationMode
(): Get the current application modeVortexSetApplicationMode
(): Set the application mode.VortexIsPaused
(): Indicates if the Vortex Simulation is paused.VortexPause
(): Set whether or not the simulation is pausedVortexStepOnce
(): Un-pauses the simulation, perform a simulation update and pauses.VortexGetSimulationFrameRate
(): Get the simulation frame rate, as set in the setup document.VortexHasValidLicense
(): Indicates if the Vortex license is validVortexGetAvailableMaterials
(): Get the available materials passed toVortexCreateApplication
().VortexCreateDefaultOffscreenGraphics
(): Initialize the offscreen rendering backend. Normally used with Depth Sensor extension(s).VortexDestroyDefaultOffscreenGraphics
(): Shutdown the offscreen rendering backend. Should be done before destroying the application.
Using Vortex Objects
When integrating Vortex, what a user usually want to do is to feed Vortex via its inputs, execute an update, then read back the outputs.
So the first thing to do is to get a reference to objects that are required to be driven and objects to read data from.
Loading Content
Content must be loaded into the application so that it simulate something.
Usually it preferable to set the application in editing mode. Setup <CM-Labs installation folder>/resources/config/VortexIntegration.vxc starting mode starting mode is editing.
References to objects are obtained from loading scenes and mechanisms
Use either VortexLoadScene
() or VortexLoadMechanism
() and keep the returned handle to access the fields of the scenes or mechanisms.
When loading a mechanism, the object can be positionned by giving it a position and a rotation, or by calling VortexSetWorldTransform
() at any time after loading.
Mechanism within a scene do not need to be loaded independently, use VortexGetChildByName
() after loading a scene to get the mechanism's handle.
Example: ExVHLIntegration
... // Identifiers for the Vortex object const char* kPaddle = "Paddle"; const char* kBall = "Ball"; ... // Load the scene, keep the handle for the Unload mScene = VortexLoadScene("../resources/ExVHLIntegration/Design/ExVHLIntegration.vxscene"); if (mScene == nullptr) { throw std::runtime_error("Couldn't load scene \"../resources/ExVHLIntegration/Design/ExVHLIntegration.vxscene\"."); } // Keep the mechanism references for easier manipulation and rendering. // Ball mechanism mBall = VortexGetChildByName(mScene, kBall); if (mBall == nullptr) { throw std::runtime_error("Couldn't find Ball Mechanism in scene \"../resources/ExVHLIntegration/Design/ExVHLIntegration.vxscene\"."); } // Paddle mechanism mPaddle = VortexGetChildByName(mScene, kPaddle); if (mPaddle == nullptr) { throw std::runtime_error("Couldn't find Paddle Mechanism in scene \"../resources/ExVHLIntegration/Design/ExVHLIntegration.vxscene\"."); }
Updating inputs and reading outputs
Using an object handle, users can use the specific interface to read or write data. Users needs to expose the data it wants to use via a VxVHLInterface or a control interface.
Once the reference to the required objects have been established, the external application basically need to update the inputs, execute an update and read the values from the outputs.
Reading Outputs
Values can be read using these getters. They each needs the mechanism/scene handle, the name of the VHL or Control interface, as well as the field name. The function returns false when the field cannot be found. Some of these functions need to provide buffers with the appropiate size. See the header file for details.
- VortexGetOutputBoolean(): Get a boolean value
- VortexGetOutputInt(): Get an integer value
- VortexGetOutputReal(): Get a double value
- VortexGetOutputVector2(): Get a vector of 2 doubles
- VortexGetOutputVector3(): Get a vector of 3 doubles
- VortexGetOutputVector4(): Get a vector of 4 doubles
- VortexGetOutputMatrix(): Get a transformation matrix, represented as a translation vector and a rotation quaternion
- VortexGetOutputString(): Get a string value. Memory for value must be allocated by the user. The size parameter will indicate the size of the string.
- VortexGetOutputIMobile():Get the transformation matrix, represented as a translation vector and a rotation quaternion, of an extension exposed.
- VortexGetOutputHeightField(): Get a height field data container of an extension exposed via a VHL interface.
- VortexGetOutputTiledHeightField(): Get a tiled height field of an extension exposed via a VHL interface. Structure
VortexTiledHeightField
must be valid. User must allocate memory for tiles/stitchTiles. The tileCount/stitchTileCount parameters indicates the number of tiles. - VortexGetOutputParticles(): Get a particles data container from of an extension exposed via a VHL interface.
- VortexGetOutputGraphicsMesh(): Get a mesh from of an extension exposed via a VHL interface. Memory for meshDataArray must be allocated by the user. The meshDataCount parameter will indicate the size of the string.
- VortexGetOutputGraphicsMaterial(): Get a graphics material from of an extension exposed via a VHL interface.
- VortexGetOutputDepthSensor(): Get a depth value array (floating point) from an extension exposed via a VHL interface. Memory for the array must be allocated by the user. The count parameter will indicate the size of the array.
Writing Inputs
Values can be written using these setters. They each needs the mechanism/scene handle, the name of the VHL or Control interface, as well as the field name. The function returns false when the field cannot be found.
- VortexSetInputBoolean(): Write a boolean
- VortexSetInputInt(): Write a integer
- VortexSetInputReal(): Write a double
- VortexSetInputVector2(): Write a vector of 2 doubles
- VortexSetInputVector3(): Write a vector of 3 doubles
- VortexSetInputVector4(): Write a vector of 4 doubles
- VortexSetInputMatrix(): Write a transformation matrix, represented as a translation vector and a rotation quaternion
- VortexSetInputString(): Write a string
Getting the Status of a Field
To help investigate problems in accessing the fields of a VHL or control interface, the Vortex Integration API exposes the VortexGetFieldStatus
function.
Call the function with the same object handle as a field setter or getter function, the VHL or control interface name, the field name, the field type (input or output) and the expected data type.
The function validates that the field exists and is of the correct type. If all is well it returns kVortexFieldStatusOK
. Otherwise it returns the appropriate value of the VortexFieldStatus
enum.
Example:
VortexFieldStatus status = VortexGetFieldStatus(handle, "MyControlInterface", "OutputRPM", kVortexFieldTypeOutput, kVortexDataTypeInt); if (status != kVortexFieldStatusOK) ...
Example: ExVHLIntegration
... const char* kInterface = "Interface"; const char* kPositionId = "Position"; const char* kVelocityId = "Velocity"; ... // Utility function that extract the Vortex coordinates into easy to use x,y,z coordinates. struct VortexCoordinates { double x; double y; double z; }; VortexCoordinates _getPosition(VortexObjectHandle handle, const char* interfaceName, const char* outputName) { double pos[3] = { 0., 0., 0. }; double rot[4] = { 0., 0., 0., 0. }; VortexGetOutputMatrix(handle, interfaceName, outputName, pos, rot); return VortexCoordinates{pos[0], pos[1], pos[2]}; } ... VortexSetInputReal(mPaddle, kInterface, kVelocityId, 4.0 * dir); // Updates the Vortex application. if ( !VortexUpdateApplication() ) { mRunning = false; // The simulation was terminated, we're done. } // Get the ball's position that by its interface Position output value. auto pos = _getPosition(mBall, kInterface, kPositionId); ...
Manipulating graphics nodes
It's possible to fetch the graphics node of a Vortex Object, should you need them in your application. Vortex Integration provide several utility functions:
- Graphics Node
- VortexGetGraphicsNodeHandles(): Retrieves the graphics node handles from a mechanism handle.
- VortexGetGraphicNodeData(): Retrieves a graphics node data from a graphics node handle
- VortexGetParentTransform(): Retrieves a graphics node parent transform from a graphics node handle
- Graphics Meshes
- VortexGetGraphicsMeshHandles(): Retrieves the graphics mesh handles from a handle.
- VortexGetGraphicsMeshData(): Retrieves the graphics mesh data from a graphics mesh handle.
- Graphics Textures
- VortexGetGraphicsTextureHandles(): Retrieves the graphics texture handles from a handle.
- VortexGetGraphicsTextureData(): Retrieves the graphics texture data from a graphics texture handle.
- VortexGetGraphicsTextureCopy(): Get a copy of the uncompressed texture from a graphics texture handle.
- Graphics Material
- VortexGetGraphicsMaterialHandles(): Retrieves the graphics Material handles from a handle.
- VortexGetGraphicsMaterialData(): Retrieves the graphics Material data from a graphics Material handle.
Using a Terrain Provider
When integrating Vortex, you may want to feed your own application's terrain to Vortex rather than reproducing the terrain in a Vortex scene. A common use case would be the simulation of a vehicle driving on a large terrain. The vehicle is fully simulated in Vortex, but the terrain is provided by an external data source, such as a game engine environment. Commonly, the terrain contains both the ground surface with different ground material and various obstacles, such as trees or buildings.
The VortexTerrainProviderInfo
structure can be used to configure the Vortex terrain paging system which uses queries of the external terrain data source around the moving objects that are simulated in Vortex in order to produce the necessary collision geometries and add them to the Vortex simulation environment. This structure contains the following parameters:
VortexTerrainProvider terrainProvider
: an optional user object, that can be used as context for the terrain data source. This object will be provided as input parameters in each of the following terrain query callbacks.VortexTerrainProviderQuery terrainProviderQuery
: A callback that will be called when Vortex needs more terrain information. It contains a request and a response. The user is responsibility to provide a response. Note that this function can be called multiple times during an update.VortexTerrainProviderPostQuery terrainProviderPostQuery
: This callback is called once all theVortexTerrainProviderQuery
calls of a single Vortex simulation step (VortexUpdateApplication()
) have been issued. This is a good point in time to perform some clean up work (see section 3603253381 below).VortexTerrainProviderOnDestroy terrainProviderOnDestroy
: This callback will be called when destroying theVortexIntegration
.double terrainProviderTileSizeUV:
For efficiency reasons and in order to support simulation of various vehicles interacting simultaneously with the same terrain, the terrain paging system queries terrain elements in square tiles of regular size. The side length of these tiles, along both the local U and the local V axis (which in the VortexIntegration correspond to the global X and Y axes of the simulation environment) is defined by this parameter.
Default is 50 meters.double terrainProviderLookAheadTime:
The terrain paging system uses this parameter value with units in seconds in order to estimate how many terrain tiles need to be queried at any given point in time to provide enough terrain for the specified time period and based on the current object velocity and motion pattern. This parameter can be combined with an asynchronous terrain query implementation (defined by theterrainProviderQuery
function) which produces terrain tiles in parallel while the simulation system is still stepping. In this case, the duration specified by this parameter can allow an asynchronous terrain query implementation to query the likely required terrain tiles ahead of time.
Default is 1 second.double terrainProviderSafetyBandSize:
This parameter specifies a safety band in meters around the query region for every object, and can be used to ensure that the required terrain elements surrounding on object are produced. This is specifically relevant for cases in which a vehicle or any other moving object changes motion direction and velocity rapidly, which can lead to the look ahead time being an insufficient estimator to ensure that sufficient terrain elements around said moving object are produced.
Default is 1 meter.
Basic Usage
In order to make use of the terrain paging system, in your VortexIntegration
you must provide a material table and a VortexTerrainProviderInfo
structure when creating the application (see VortexCreateApplication()
).
This will create a special paged terrain extension which is automatically added to the underlying Vortex application. As you load mechanisms in Vortex, the paged terrain extension will detect that terrain is needed under these objects as described above. This system will call the callback functions provided by the VortexTerrainProviderInfo
and thus allow you to provide terrain for the appropriate regions as required. Depending on the location, direction and speed of object, the extension will invoke the query callbacks during every simulation step (which means during any call to the VortexUpdateApplication()
function). The queried elements are cached for reuse, tile by tile, for efficiency and to ensure that no duplicate collision geometries are queried for multiple moving objects that occupy the same or similar simulation space. As Vortex is running, the system may occasionally decide to remove some terrain elements (tiles) that are no longer needed at the moment, and the corresponding tiles will be queried again if objects return to the corresponding terrain locations.
Processing a query
When there is a need for a terrain tile, the callback VortexTerrainProviderQuery
will be invoked. Along with an optional user object (which can be used as context for the terrain data source), the function has 2 parameters, a request, and a response.
The request contains a bounding box in World coordinates.
The user is expected to fill the response with the collision geometries in the bounding box.
The pointer to parameter response (VortexTerrainProviderResponse)
is valid. The user must allocate the memory for the field objects (VortexTerrainProviderObject*)
and keep it valid until the call to VortexTerrainProviderPostQuery
. The member objectCount must reflect the number of objects in member objects.
An object has the following members:
- name: Your object name.
- uniqueID: the identifier of your object. To facilitate memory management, users can call
VortexContainsTerrain
() and passing it the identifier to check if Vortex is still using that object. - position of the object, in Vortex World coordinates
- rotation quaternion of the object, in Vortex World coordinates
- shapeCount, the number of shape in the object. There is a maximum of 8 shape per object.
- shapes. An array of 8 shapes, but the paged terrain will only process shapeCount.
Vortex supports simple shapes, such as boxes (VortexBox
), spheres (VortexSphere
),and capsules (VortexCapsule
), that can be used for trees, fences etc.. More complexes object can be represented as Convex Meshes (VortexConvex
) or triangle Meshes (VortexTriangleMesh
). Each of these objects also have a VortexMaterial
member, that are referenced by name into the given material table in Vortex.
A terrain tile can also be represented as an height field. A height field is composed of cells, each cell is made of 2 triangles (4 vertices), each vertices has an height and each triangle has a material.
The data is represented with the following parameters:
cellSizeX
: Length of a cell along the X axis, in meterscellSizeY
; Length of a cell along the Y axis, in metersnbVerticesX
: Number of vertices along the X axisnbVerticesY
Number of vertices along the Y axisheights
: Array of heights for each vertices. The heights are listed by row (X axis). The size should be nbVerticesX * nbVerticesYmaterials
: Dictionary of all used materials in this heightfieldmaterialsCount
: size of materialsmaterials0
: Array of materials for each cells. There are 2 triangles per cell and this array is the material on the first triangle. The size should be (nbVerticesX - 1) * (nbVerticesY - 1)materials1
: Array of materials for each cells. There are 2 triangles per cell and this array is the material on the second triangle. The size should be (nbVerticesX - 1) * (nbVerticesY - 1)
The function could be called several time per update and the execution time will affect Vortex as this is called during an update.
Processing a post query
Once all queries for an update were processed, the post query function will be called. At this point, the memory that was allocated to fill the response (Array of VortexTerrainProviderObject
) can be released.
Processing a destroy call back.
The VortexTerrainProviderOnDestroy
callback is invoked when the paged terrain extension is being destroyed or reset. This can occurs while the Vortex Application Mode change from simulating to editing, and when the user call VortexDestroyApplication.
Using multiple paged terrain extensions
When the terrain as big height fields and a lot of small collision geometries like rocks and trees, it could be a good idea to use two paged terrain extension, one for the height fields and one for the collision geometries.
Use VortexSetTerrainProviderInfoListSize()
to increase the number of paged terrain extension, default size is one.
Then assign another VortexTerrainProviderInfo
to the new paged terrain extension with VortexSetTerrainProviderInfoAtIndex()
.
Each terrain provider will have a specialized query function, one for the height fields and the other for the collision geometries. The terrainProviderTileSizeUV
parameter should be smaller for the terrain provider responsible of the collision geometries.
Unloading Content
Once you are done with objects, they should be unloaded. Unloading a mechanism or a scene will remove it from Vortex and invalidates the handles.
Like loading, scenes and mechanisms can be unloaded using VortexUnloadScene
() and VortexUnloadMechanism
(). Mechanism within a scene do not need to be unloaded independently.
Example: ExVHLIntegration
// Unload the scene if (mScene != nullptr) { VortexUnloadScene(mScene); mScene = nullptr; }
Debugging a Vortex Integration
You can use the Vortex Remote Debugger to debug your integration. Note that remote access must be enabled. See the Vortex Remote Debugger user guide for details.