Vortex Studio SDK - Using Vortex Integration



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 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 Materials Table
  • dataStore: An absolute path to the data provider path of the Vortex Application. For more information, see Director User Guide 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.

ExVHLIntegration
....
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()

  1. 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.
  2. 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 0

  • VortexGetApplicationMode(): Get the current application mode

  • VortexSetApplicationMode(): Set the application mode.

  • VortexIsPaused(): Indicates if the Vortex Simulation is paused.

  • VortexPause(): Set whether or not the simulation is paused

  • VortexStepOnce(): 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 valid

  • VortexGetAvailableMaterials(): Get the available materials passed to VortexCreateApplication().

  • 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

Example:Loading a scene
...
// 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
Getting data from content
...
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 while it return rather than reproducing the terrain in a Vortex scene. A common use case would be simulating a vehicle driving on a terrain. The vehicle is fully simulated in Vortex, but the terrain comes from you own application. It is possible to simulate the ground and a few objects, such as trees of building.

The structure contains these parameters:

  • VortexTerrainProvider: a user object, you decide what to put in it. This object will be given as a parameters to the callbacks
  • VortexTerrainProviderQuery: A callback that will be called when Vortex needs more terrain information. It contains a request and a response. The user responsibility to to provide a response. This function can be called multiple times during an update
  • VortexTerrainProviderPostQuery: This callback is called after all VortexTerrainProviderQuery of an update where completed. You can do some cleanup at that moment,
  • VortexTerrainProviderOnDestroy: This callback will be called when destroying Vortex.

Basic Usage

In order to proceed, you must provide a material table and a VortexTerrainProviderInfo structure when creating the application.

This will create a special paged terrain extension into the Vortex Application. As you load mechanism in Vortex, the paged terrain extension will detect that terrain is needed under these objects. It will use the callback so that the user can provide terrain for a given region. Depending on the direction and speed of object, the extension will invoke the callbacks during the VortexUpdateApplication() call. As Vortex is running, it may remove some terrain that it no longer needs at the moment, but it is possible, if the object returns to that location, that the tile will be queried again.

Processing a query

When there is a need for a terrain tile, the callback VortexTerrainProviderQuery will be invoked. Along the user object, the function as 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 objects 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 meters
  • cellSizeY;  Length of a cell along the Y axis, in meters
  • nbVerticesX: Number of vertices along the X axis
  • nbVerticesY Number of vertices along the Y axis
  • heights:  Array of heights for each vertices. The heights are listed by row (X axis). The size should be nbVerticesX * nbVerticesY
  • materials: Dictionary of all used materials in this heightfield
  • materialsCount: size of materials
  • materials0: 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.

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

Example:Loading a scene
// Unload the scene
if (mScene != nullptr)
{
	VortexUnloadScene(mScene);
	mScene = nullptr;
}


Debugging a Vortex Integration

User can use the Vortex Remote Debugger to debug the integration. The remote access must be enabled. See the Vortex Remote Debugger user guide.