Vortex Studio SDK: Customizing Vortex

This section is aimed at developers who want to extend Vortex® with more functionality or communicate from Vortex® to and from an external system.

Topics

About Plugins

Vortex plugins allow developers to add more functionality to Vortex.

Plugins are actually dynamics libraries with a VXP extension publishing specific interfaces to allow the plugin manager to gather information and instantiate modules and extensions.

The following methods have to be implemented in order to define a plugin:

Plugin.cpp
#ifdef _MSC_VER
#define SYMBOL __declspec(dllexport)
#else
#define SYMBOL
#endif

extern "C" SYMBOL bool GetPluginInfo(VxPluginSystem::VxPluginInfo & pluginInfo)
{
	pluginInfo.setDisplayName("My Plugin Name");
	pluginInfo.setVendor("My Vendor Name");
	pluginInfo.setDescription("A description of my plugin");
	return true;
}

extern "C" SYMBOL bool InitializePlugin(VxPluginSystem::VxPluginManager & pluginManager, int /*unused*/, char ** /*unused*/, std::string * /*unused*/)
{
	return VxSim::VxExtensionFactory::registerType<MyExtension>(MyExtension::kFactoryKey, pluginManager.getCurrentVXP());
}

extern "C" SYMBOL bool UninitializePlugin(VxPluginSystem::VxPluginManager & pluginManager, std::string * /*unused*/)
{
	return VxSim::VxExtensionFactory::unregisterType(MyExtension::kFactoryKey);
}

GetPluginInfo is used by the plugin manager to collect information on the plugin so that it can be displayed, e.g., in a selection page of the Editor.

InitializePlugin is called by the plugin manager to give the opportunity to the plugin to register modules, extensions or enumerations.

UninitializePlugin is called by the plugin manager when unloading the plugin.

A recommended practice is to code the methods in a dedicated cpp file, e.g., plugin.cpp, inside the project of your plugin.

Specific implementation of modules and extensions would have their own cpp and header files.

Extensions

Extensions and modules are first class objects of Vortex (see Introduction).

In Vortex Toolkit, everything you see in the Editor is an extension: scenes, parts, Graphics Galleries, graphic nodes and materials, source and listeners, Python scripts, etc. Even the objects in the Setup Editor are extensions; modules are also extensions.

Knowing how to create extensions is essential for extending Vortex.

Factory Key

Each object is characterized by a Factory Key. A factory key uniquely defines a type of extension with a universally unique identifier (UUID).

The UUID is simply a 128-bit value that shall never change once the extension is used in content: it will be used when any content persists.

Several tools can be used to generate a UUID (e.g., Visual Studio). Never copy a UUID of an extension to be used for another one as this will confuse the object management.

The Factory key also has additional members that help to define the extension with human readable information.

FactoryKey
/// Information that allows access to a plugin through the VxExtensionFactory
///
class VxFactoryKey
{
public:
	/// UUID for this key
	///
	VxUuid uuid;
	/// Category name
	///
	std::string categoryName;
	/// Feature name
	///
	std::string featureName;
	/// VXP, or DLL, file name if applicable without the extension
	///
	std::string vxpName;
};

The extension factory can be created locally when registering the extension in the plugin. However, the key is also useful when creating or filtering from outside of the plugin so the SDK often publishes the key in a header.

VxDynamicsModuleICD.h
/// Factory Key for the VxDynamicsModule
///
const VxSim::VxFactoryKey kFactoryKey(VxSim::VxUuid("{3a6c7474-6a01-5f06-ac36-c3a0d7cad4bf}"), "Dynamics", "Engine", "VxDynamicsPlugin");

Extensions, Objects

An extension is a C++ class that implements IExtension and some other interfaces depending on the target functionality.

The interfaces that are implemented by the extensions will be called at the appropriate moment by Vortex to achieve the desired functionality.

There are three major types of extensions; any extension must derive from one and only one of these interfaces:

  • VxSim::IExtension: An extension that adds features to content (mechanism, scene) or to the application.
  • VxSim::IObject: An object that has the same features as IExtension, but can also have child extensions (useful to create more complex systems for content).
  • VxSim::ISimulatorModule: A module manages certain types of extensions and calls their methods at the appropriate moment during the application update. Modules can only be added to VxApplication or ApplicationConfig.

IExtension Interface

IExtension is the most basic required interface for extensions. It is necessary to support basic functionalities of Vortex Toolkit like serialization, network, and key frames. It should generally be used when implementing simple extensions. The extension's data is contained in VxSim::VxExtension that can be obtained with getProxy().

IExtension has a single constructor:

IExtension::IExtension(VxSim::VxExtension* proxy, int version = 0);

It must be used by all extensions. The proxy will be provided by VxExtensionFactory but the version needs to be provided by the actual implementation. Versioning will be used when loading and saving the extension.

The constructor of an extension always has the following basic form:

const int kCurrentVersion = 20160601;

class MyExtension : public VxSim::IExtension
{
	public:
		MyExtension(VxSim::VxExtension* proxy): VxSim::IExtension(proxy, kCurrentVersion)
		{
		}
};
Note
A good practice is to use the date of development as the version number. This reduces the risk of problems if several people work on the same code simultaneously.

IObject Interface

IObject is an extended version of IExtension. In addition to all the functionalities of IExtension, it adds support for sub-extensions. It is generally used when implementing complex extension systems. The object's data is contained in VxSim::VxObject that can be obtained via the getProxy() method.

IObject has a single constructor that is similar to the constructor of IExtension.

IObject::IObject(VxSim::VxObject* proxy, int version = 0);

It must be used by all objects. The proxy will be provided by VxExtensionFactory but the version needs to be provided by the actual implementation. Versioning will be used when loading and saving the object.

The constructor of an object always has this basic form:

const int kCurrentVersion = 20160601;

class MyObject : public VxSim::IObject
{
	public: MyObject (VxSim::VxObject* proxy) : VxSim::IObject(proxy, kCurrentVersion)
	{
	}
};

Additional Interfaces for Extensions

In addition to IExtension or IObject, an extension usually implements one or several interfaces. The choice of interface depends on the desired functionality. The following is a list of useful interfaces with the modules that manage them.

Warning
The simulator module that manages an interface must be present in VxApplication in order for the functionality to work. If it is not present, the extension will be inactive.

Functionalities

Functionality Interface Name Managed By Notes
Application helper VxSim::IApplicationListener VxApplication Mainly used to be notified of an application mode change.
Object with a position VxSim::IMobile   This interface represents an object that can be transformed with a position/orientation/scale.
Dynamics VxSim::IDynamics VxDynamicsModule IDynamics is the interface for all the extensions that need to be synchronized with the dynamics engine.
VxDynamics::ICollidable VxDynamicsModule ICollidable adds the collision groups, which allows setting up collision rules with the extension.
Profiler VxSim::Monitoring::IProfilingDataCollector VxApplication Collector of profiling data.
Devices
VxSim::IJoystick VxJoystickModule Receive the raw gamepad/joystick device events. Use of ControlPreset is prefered over doing a custom IJoystick extension.
VxSim::IKeyboard VxGraphicsModule Receive the raw key event.
VxSim::IMouse VxGraphicsModule Receive the raw mouse event.
Graphics VxGraphics::IGraphic VxGraphicsModule IGraphic is a generic interface to implement a custom graphic feature. IGraphic is an ILocalExtension, IMechanismAddOn, ISceneAddOn.
VxGraphics::IAccessory VxGraphicsModule This interface provides display functionality meant for User Interfaces. IAccessory are not meant to be visible during the simulation, but are useful while editing and building the simulation objects.
Metrics VxSim::IMetricsCollector VxMetricsModule For any extension that wishes to collect metric data, metrics module will send collected metrics every frame.

Behavior Modifiers

Functionality Interface Name Notes
Network VxSim::ILocalExtension In a network simulation, the outputs of an extension that implements ILocalExtension will not be transferred to other computers. The data will be transferred locally with connections, but not through the network. If an extension is managed on several computers in the network, this interface will prevent issues with the network.
Role & Seat VxSim::IRoleFeature Only the extensions that implement IRoleFeature can be added to a role.
Content Management VxDynamics::IMechanismAddOn By default, Scene and Mechanism only accept IExtension, but not IObject. An extension that is an IObject can be added to a Mechanism when it implements IMechanismAddOn.
VxContent::ISceneAddOn ISceneAddOn has the same effect on Scene, as IMechanismAddOn for Mechanism.

Modules

ISimulatorModule Interface

An ISimulatorModule is a special kind of extension that can only be added to VxApplication (with VxApplication::insertModule) or to ApplicationConfig. When implemented, it will be contained in a VxSim::VxSimulatorModule that is returned with getProxy().

A module will manage its extension and execute at each update either code based on its data or callbacks on its implementation.

If you develop an extension already based on a predefined interface, e.g., IDynamics, a module will be available already and you will not need to develop your own.

If you develop an extension based on a new interface, e.g., your own device, you will also need to code a module to manage the extensions of that type.

ISimulatorModule has two constructors that are similar to the constructor of IExtension.

ISimulatorModule::ISimulatorModule(VxSim::VxSimulatorModule* proxy, int version = 0);
ISimulatorModule::ISimulatorModule(VxSim::VxSimulatorModule *proxy, int preUpdatePriority, int updatePriority, int postUpdatePriority, int version = 0);

The proxy will be provided by the VxExtensionFactory but the version needs to be provided by the actual implementation. Versioning will be used when loading and saving the module. The priorities can also be given to the constructor; they control the order in which the VxApplication calls onPreUdpate(), onUpdate() and onPostUpdate() on all its modules.

Note
The default priorities of updating in ISimulatorModule are correct in the vast majority of cases.

The constructor of a module usually has this basic form:

const int kCurrentVersion = 20160601;

class MyModule: public VxSim::ISimulatorModule
{
	public: MyObject (VxSim::VxSimulatorModule * proxy) : VxSim::ISimulatorModule(proxy, kCurrentVersion)
	{
	}
};

Extension Management Support

The VxApplication notifies all ISimulatorModule of all extensions that are added to it through the onExtensionAdded method. It is there that each ISimulatorModule decides if it will manage the new extension or not.

virtual void VxSim::ISimulatorModule::onExtensionAdded(VxSim::VxExtension* extension);
virtual void VxSim::ISimulatorModule::onExtensionRemoved(VxSim::VxExtension* extension);
bool VxSim::ISimulatorModule::_addManagedExtension(VxSim::VxExtension* e);
bool VxSim::ISimulatorModule::_removeManagedExtension(VxSim::VxExtension* e);
Warning
When ISimulatorModule receives an extension that it will manage, it is mandatory to notify the application by calling _addManagedExtension. Otherwise, the extension may not work as expected. Likewise, if a managed extension is received in onExtensionRemoved, the method _removeManagedExtension must be called. If _addManagedExtension and _onRemovedExtension() are not called, the observers on the extension will not work.

For example, the module MyModule needs to manage all MyExtension instances that are put in the content.

Example of a module managing a type of extension
const int kCurrentVersion = 20160601;

class MyModule : public VxSim::ISimulatorModule
{
	void _prepareExtension(VxSim::VxSmartInterface<MyExtension> my);
	void _cleanupExtension(VxSim::VxSmartInterface<MyExtension> my);

public:
	MyModule(VxSim::VxSimulatorModule* proxy) : VxSim::ISimulatorModule(proxy, kCurrentVersion )
	{
	}

	virtual void onExtensionAdded(VxSim::VxExtension* extension)
	{
		VxSim::VxSmartInterface<MyExtension> my = extension;

		if ( my.valid() )
		{
			_addManagedExtension(extension);
			_prepareExtension(my);
		}
	}

	virtual void onExtensionRemoved(VxSim::VxExtension* extension)
	{
		VxSim::VxSmartInterface<MyExtension> my = extension;
		if ( my.valid() )
		{
			_cleanupExtension(my);
			_removeManagedExtension(extension);
		}
	}
};

Simulation Management Support

Several methods can be overridden to implement an ISimulatorModule-specific functionality.

virtual void VxSim::ISimulatorModule::onAddToApplication(VxApplication* application);
virtual void VxSim::ISimulatorModule::onRemoveFromApplication(VxApplication* application);

onAddToApplication is called just after the ISimulatorModule is added to the VxApplication. This is the right moment to perform any initialization required by the module: starting a thread, connecting to a network socket, loading some internal data, etc. This is usually done very early in the application start up, but will not be done when building an ApplicationConfig. Any initialization that was done must be undone in onRemoveFromApplication.

Warning
The constructor of the derived ISimulatorModule is not a good place to do any resource initialization, as there may be more than one instance of the same class being created at the same time. Initialization should be done in onAddToApplication.
virtual void VxSim::ISimulatorModule::onPreUpdate(VxSim::eApplicationMode mode);
virtual void VxSim::ISimulatorModule::onUpdate(VxSim::eApplicationMode mode);
virtual void VxSim::ISimulatorModule::onPostUpdate(VxSim::eApplicationMode mode);
void VxSim::ISimulatorModule::_sortManagedExtensions();

These methods are the heartbeat of the simulation. They are called in the VxApplication::update() loop. This is where you want to do the work that the module is designed to do. onPreUdpate is called before the dynamics, onPostUpdate is called after the dynamics but before the graphics. onUpdate is called in between the two. Generally, onUpdate is not used. All those methods receive the current application mode of the VxApplication.

_sortManagedExtensions() will make sure that the managed extensions are sorted according to their dependency as given by the connection layout.

Note
When the module is managing several extensions that are likely to be interconnected, it is necessary to call _sortManagedExtensions() to process the extension in the right order. The dependencies between extensions are exclusively deduced from the connections between extensions, i.e., if extension A's output is connected to extension B's input, extension A will be before extension B when sorted.
Example of a module update its extensions in the right sequence
class MyExtension : public VxSim::IExtension 
{
	[...]
	
	void doOneStep(); 
};

class MyModule : public VxSim::ISimulatorModule
{
	[...]

	virtual void onPreUpdate(VxSim::eApplicationMode mode)
	{
		if ( mode == VxSim::kModeSimulating )
		{ 
			// make sure that the extension are sorted according to the connection graph layout 
			// if already sorted, no time is spent here _sortManagedExtensions();
			auto& extensions = getProxy()->getManagedExtensions(); 
			for (auto it = extensions.begin(); it != extensions.end(); ++it)
			{
				VxSim::VxSmartInterface<MyExtension> my = *it;
				my->doOneStep(); 
			} 
		} 
	} 
};
virtual void VxSim::ISimulatorModule::onApplicationModeChange(VxSim::eApplicationMode previous, VxSim::eApplicationMode current);

onApplicationModeChange is called whenever the mode of the VxApplication is changing. Use this callback to do any special processing necessary when transitioning from one mode to another.

Note
This method is useful when some special initialization is necessary when going into simulation.

Helper Functions

ISimulatorModule has a few helper functions:

Vx::VxSmartPtr and VxSim::VxSmartInterface

Vortex objects are reference-counted via a special smart pointer class, VxSmartPtr<T>. When you need to keep a VxExtension alive, you should keep it in a VxSmartPtr<T>. Vortex internal's system uses those smart pointers to keep extensions alive. In some rare cases, a developer might want to keep a pointer on a VxExtension without increasing the reference count. In this case, a VxWeakPtr<T> should be used. A weak pointer will become invalid when the reference extension is destroyed.

Unless a developer needs to do generic work on extensions, using VxSmartInterface<T> should be favored. A VxSmartInterface<T> is both a VxSmartPtr to the extension and a pointer to the implementation.

When a developer needs to query, test or use a specific interface, the safest, simplest way is to use a VxSmartInterface<T> object. A VxExtension can contain any kind of implementation: extension or module. VxSmartInterface<T> can be used to query for a specific type of interface, if the interface is found, the extension will be referenced (thus will never be deleted) as long as the VxSmartInterface<T> has it. VxSmartInterface<T> can be initialized with a VxExtension*, a VxSmartPtr<VxExtension>, or with any kind of VxSmartInterface<U>. VxSmartInterface<T> also contains its VxExtension via function getExtension().

Example of using VxSmartInterface to find specific interfaces
void doSomethingWithAIMobile(VxSim::VxExtension* extension)
{
	VxSim::VxSmartInterface<VxSim::IMobile> imobile = extension;
	if ( imobile.valid() )  // check if the IMobile interface was found in the extension
	{
		// by using a VxSmartInterface, we are guaranteed that extension will not be deleted during the call to _getTheNewPosition()
		imobile->inputLocalTransform = _getTheNewPosition();

		// check if there is also the IGraphic interface (in addition to IMobile)
		VxSim::VxSmartInterface<VxGraphics::IGraphic> graphicAndMobile = imobile;
		if ( graphicAndMobile.valid() )
		{
			_doSomethingSpecial(graphicAndMobile);
		}
	}
}

VxWeakInterface<T> is very similar but it does NOT increase the ref-count of the VxExtension. There are very few cases where VxWeakInterface<T> should be used instead of a VxSmartInterface<T>. The most important exception is when a class needs a member and does NOT want to keep it alive. Basically, you would only use VxWeakInterface<T> if you would use a VxWeakPtr<VxExtension>.

Inputs, Outputs and Parameters

The extension data is stored in VxData::Field<T> of the appropriate type.

The use of fields gives us a high level of standardization, which simplifies Vortex Toolkit and improves overall performance.

There are three major field containers in every extension: inputs, outputs and parameters.

  • Inputs: Data that is fed to the extension. It can change anytime in the simulation.
  • Outputs: Data produced by the extension. Outputs of one extension can be connected to inputs of another extension.
  • Parameters: Constant data in the simulation. It can be changed while editing the object, but not during the simulation.

All inputs, outputs and parameters in an extension are automatically serialized. They are written to file when saving, and put back in the extension when loaded.

Note
The decision to put a value as a parameter or an input has profound impact on the use of the extension. An input can be changed anytime during simulation and editing; the extension must be ready to react to a change at any time. A parameter should only change in while editing. Changing a parameter can trigger extensive recalculations that may not be suitable while simulating.
Warning
An extension should not modify its own inputs or parameters. Inputs and parameters are the API from which the extension is controlled. When the extension needs to deliver some results, it should have an output.

The header file for an extension with three fields (one input, one output, and one parameter) would look like the following:

class MyExtension : public VxSim::IExtension, public VxSim::IDynamics
{
	public:
		MyExtension(VxSim::VxExtension* proxy);

		// declaration of the fields
		VxData::Field<bool> inputEnable;
		VxData::Field<Math::Vector3> outputScaleFactor;
		VxData::Field<VxDynamics::Part*> parameterTargetPart;
};
Note
The fields are part of the API of your extension.
The fields are inherently public, since they can always be accessed from the VxExtension.
It is usually simpler to make them public, instead of creating getters/setters.

The constructor must declare the name for the fields, whether they are inputs, outputs or parameters, and optionally a default value.

It is also good practice to set the physical dimensions of the values. When this is done, the units will be displayed in the Editor.

MyExtension::MyExtension(VxSim::VxExtension* proxy)
	: VxSim::IDynamics(proxy)
	, VxSim::IExtension(proxy, kCurrentVersion)
	, inputEnable(false, "Enable", &proxy->getInputContainer())
	, outputScaleFactor(Math::Vector3(1,1,1), "Scale", &proxy->getOutputContainer())
	, parameterTargetPart(nullptr, "Target", &proxy->getParameterContainer())
{
	outputScaleFactor.setPhysicalDimension(Vx::VxPhysicalDimension::kScale);
}

VxData::Field<T>

VxData::Field Types

Field<T> represents a data value that has a type (from the template parameter), a name (from the constructor), and a value.

NumericalVxData::Field<int> 
VxData::Field<unsigned int> 
VxData::Field<short> 
VxData::Field<unsigned short> 
VxData::Field<long> 
VxData::Field<unsigned long> 
VxData::Field<double>same as VxData::Field<Vx::VxReal>
VxData::Field<bool> 
Compound ValuesVxData::Field<Math::Vector2> 
VxData::Field<Math::Vector3>same as VxData::Field<Vx::VxVector3>
VxData::Field<Math::Vector4> 
VxData::Field<Math::Matrix44> 
VxData::Field<Vx::VxTransform> 
VxData::Field<VxMath::Quaternion>same as VxData::Field<Vx::VxQuaternion>
VxData::Field<Vx::VxColor> 
StringVxData::Field<std::string> 
VxData::Field<Vx::VxFilename>Contains the path to a file or directory.
ObjectVxData::Field<VxSim::VxExtension*>Contains a reference to a VxSim::VxExtension
 VxData::Field<Vx::VxMaterial*>Contains a reference to a Vx::VxMaterial
 VxData::Field<VxDynamics::Part*> Contains a reference to a VxDynamics::Part
 VxData::Field<VxDynamics::Assembly*>Contains a reference to a VxDynamics::Assembly
 VxData::Field<VxDynamics::Attachment*>Contains a reference to a VxDynamics::Attachment
 VxData::Field<VxDynamics::AttachmentPoint*>Contains a reference to a VxDynamics::AttachmentPoint
 VxData::Field<VxDynamics::CollisionGeometry*>Contains a reference to a VxDynamics::CollisionGeometry
 VxData::Field<VxDynamics::Mechanism*>Contains a reference to a VxDynamics::Mechanism
 VxData::Field<VxGraphics::Node*> Contains a reference to a VxGraphics::Node
 VxData::Field<VxGraphics::Mesh*>Contains a reference to a VxGraphics::Mesh
 VxData::Field<VxGraphics::Material*>Contains a reference to a VxGraphics::Material
 VxData::Field<VxGraphics::Texture*>Contains a reference to a VxGraphics::Texture
Note
All fields of pointer types have an additional feature. If the object pointed to by the Field<T*> is deleted, the value in the field will automatically become nullptr in order to avoid dangling pointers.

Using Field<T> in an Extension

Using Field<T> is as simple as using a variable of type T.

To avoid any ambiguity, Field<T> also has the method getValue() and setValue().

class MyExtension : public VxSim::IExtension, public VxSim::IDynamics
{
	public:
		MyExtension(VxSim::VxExtension* proxy);

		// declaration of the fields
		VxData::Field<double> inputScale;
		VxData::Field<Math::Vector3> outputScaleFactor;
		VxData::Field<VxDynamics::Part*> parameterTargetPart;

		[...]

	private:
		void _handleScale(double s)	
		inline void _processInput()	
		{
			// Field<double> is used just like a double
			if ( inputScale > 0 )
			{
				_handleScale( inputScale );
			}
			else if ( inputScale > 0 )
			{
				_handleScale( -inputScale.getValue() );	
			}
		}

		inline void _processOutput
		{
			// Fields with compound values like VxData::Field<Math::Vector3> are used in the same way
			Math::Vector3 v(inputScale, inputScale, inputScale);
			outputScaleFactor = v;
		}

		inline void _processParameter()
		{
			// VxData::Field<VxDynamics::Part*> can be used like a VxDynamics::Part*
			VxDynamics::Part* p = parameterTargetPart;
			if ( parameterTargetPart != nullptr )
			{
				parameterTargetPart->resetInertia();
			}
		}
};

Field of Enumeration Type - VxData::Field<eType>

Fields of enumeration type are more simple to use than before.

// in header file

class MyExtension : public VxSim::IExtension, public VxGraphics::IGraphic
{
	public:
		enum eType { kDog, kCat, kElephant, kSpider };

	public:
		MyExtension(VxSim::VxExtension* proxy);

		VxData::Field<eType> parameterType;
};

// in cpp file
MyExtension::MyExtension(VxSim::VxExtension* proxy)
	: VxSim::IExtension(proxy, 0)
	, VxGraphics::IGraphic(proxy)
	, parameterType(kSpider , "Animal", &proxy->getParameterContainer())
{
}

// it is used like an enum
void MyExtension::_draw()
{
	if ( parameterType == kSpider )
	{
		[...]
	}
}

void MyExtension::useBigAnimal()
{
	parameterType = kElephant;
}

In order to have Vortex Toolkit process the enum type information correctly, the enum must be registered to the VxEnum class. This can be done in the plugin InitializePlugin/UninitializePlugin functions.

// in main plugin file

extern "C" SYMBOL bool InitializePlugin(VxPluginSystem::VxPluginManager & pluginManager, int /*argc*/, char ** /*argv*/, std::string * /*error*/)
{
	// register the enum to be able to use Field<MyExtension::eType>
	// give the enum a name that must be unique, it will be used instead of typeid(MyExtension::eType)
	Vx::VxEnum::registerEnumeration<MyExtension::eType>("MyExtension_AnimalType")
		.addValue(MyExtension::kDog, "Dog")
		.addValue(MyExtension::kCat, "Cat")
		.addValue(MyExtension::kElephant, "Elephant")
		.addValue(MyExtension::kSpider , "Spider");

	return VxSim::VxExtensionFactory::registerType<MyExtension>(kFactoryKey, pluginManager.getCurrentVXP());
}

extern "C" SYMBOL bool UninitializePlugin(VxPluginSystem::VxPluginManager & pluginManager, std::string * /*error*/)
{
	// when plugin is unloaded, enum MUST be unregistered
	Vx::VxEnum::unregisterEnumeration<MyExtension::eType>();

	return VxSim::VxExtensionFactory::unregisterType(kFactoryKey);
}
Note
The name used to identify the enum type will not be visible anywhere, it could be a UUID to make sure it is unique. The names associated with each enum value will be visible in the editor.
Warning
Failure to properly register and unregister the enumeration will result in undefined behavior when the Field is used. Enumeration must be registered only once.

Parsing content for Enumeration fields

In earlier versions, enumeration fields were all defined with Field<VxEnum>. Under the hood, this is still the case. The Field<VxEnum> is only valid when you are dealing with the IExtension, what is really stored within the Container is a Field<VxEnum> Field<VxEnum>. When parsing content using VxExtension* and VxData::Container in a generic way, the FieldBase is a Field<VxEnum>. Calling getType() on the field will yield a type VxData::Types::Type_Enum.

The following example shows the differences.

Field<enum> vs. Field<VxEnum
// The mechanism below has several extension, one of which is of type MyExtension
// But since the code is parsing all extensions, it is done in a generic way
for (auto it = mechanism->getExtensions().begin(); it != mechanism->getExtensions().end(); ++it)
{
	const VxData::Container& parameters = it->getExtension()->getParameterContainer();
	for (auto it = parameters .begin(); it != parameters .end(); ++it)
	{
		VxData::FieldBase& field = *it;
		// Parsing by type
		...
		else if(field.getType() == Type_VxEnum)
		{
			// you get a generic VxEnum
			VxEnum val = field.getValue<VxEnum>();
			int enumValue = val.getValue();

			// the type is known, this can be done
			MyExtension eType = static_cast<MyExtension eType>(enumValue);
			// or directly
			MyExtension eType = field.getValue<MyExtension::eType>();
		}
		...
}

Field of Interface Type - VxData::Field<IExtension*>

To refer to other extensions, user should use Field<VxExtension*>. However, when you know the actual interface of the extension that you want to refer to, you could consider using a field of interface.

Those special field are an interface over a Field<VxExtension*> that allows using the field like a VxSmartInterface<I*>, allowing the user to get access to the API directly from the field.

Fields do not normally allow Field of IExtension. In a previous version of Vortex, some fields of specific interfaces were introduced:

  • Field<VxDynamics::Part>
  • Field<VxDynamics::Assembly>
  • Field<VxDynamics::Mechanism>
  • Field<VxDynamics::Attachement>
  • Field<VxDynamics::AttachementPoint>
  • Field<VxDynamics::Constraint>
  • Field<VxDynamics::CollisionGeometry>
  • Field<VxGraphics:Node>
  • Field<VxGraphics:Mesh>
  • Field<VxGraphics:Material>
  • Field<VxGraphics:Texture>
  • Field<VxGraphics:Geometry>
  • Field<VxGraphics:Skeleton>
  • Field<VxSim::ISimulatorModule>

Vortex Studio may introduce new types as it evolves, but they all work the same way.

// in header file

class MyOtherExtension : public VxSim::IExtension
{
	public:
		MyExtension(VxSim::VxExtension* proxy);

		VxData::Field<VxDynamics::Part> parameterPart;
};

// in cpp file
MyOtherExtension::MyOtherExtension(VxSim::VxExtension* proxy)
	: VxSim::IExtension(proxy, 0)
	, parameterPart(nullptr, "Animal", &proxy->getParameterContainer()) // Value is to be set by code or UI.
{
}

// it is used like an smart interface
void MyOtherExtension::_update()
{
	if(paramterPart.getValue() != nullptr)
	{
		// using it like a VxSmartInterface<Part>
		if(parameterPart->outputLinearVelocity.getValue > kMaxVelocity)
		{
			...
		}
	}
}

Creating your Field<Interface*>

Not all interfaces can be put in a field; you can create fields of their own type.

There are some requirements to be able to make a Field of your own interface.

If you created your own IExtension and you want to create a Field<T*> where T is your interface, the following steps must be taken:

  • Use macro VX_DATA_ALLOW_INTERFACE_FIELD(Interface)
  • Define a const static VxID named kFieldTypeId, this will be the id of your interface.
  • Register your field type ID
class MyExtension :: public IExtension
{
	...
const static Vx::VxID kFieldTypeId = = VxData::Types::registerInterfaceFieldType<MyExtension>("MyExtension", "UI name for MyExtension");
};
VX_DATA_ALLOW_INTERFACE_FIELD(MyExtension)

// then the field can be defined in another extension
class MyOtherExtension
{
	...
	Field<MyExtension> parameterMyExt; // Used like the Part in the example above.
};

Parsing Content for Interface fields

Like enumeration fields, interface fields are only valid when you are dealing with an IExtension interface containing them; what is really stored within the Container is a Field<VxExtension*>. When parsing content using VxExtension* and VxData::Container in a generic way, the FieldBase is a Field<VxExtension*>.

Calling getType() on the field will yield a type VxData::Types::Type_ExtensionPtr. To know the interface type, the fieldbase should be cast into a Field<VxExtension*> and getInterfaceType() will give the interface type identifier.

Warning
Prior to version 2017b, calling getType() would give a different type for each of the predefined Interfaces above.
  • Field<VxDynamics::Part> -> VxData::Types::Type_Part
  • Field<VxDynamics::Assembly> -> VxData::Types::Type_Assembly
  • Field<VxDynamics::Mechanism> -> VxData::Types::Type_Mechanism
  • Field<VxDynamics::Attachement> -> VxData::Types::Type_Attachment
  • Field<VxDynamics::AttachementPoint> -> VxData::Types::Type_AttachementPoint
  • Field<VxDynamics::Constraint> -> VxData::Types::Type_Constraint
  • Field<VxDynamics::CollisionGeometry> -> VxData::Types::Type_CollisionGeometry
  • Field<VxGraphics:Node> -> VxData::Types::Type_GraphicsNode
  • Field<VxGraphics:Mesh> -> VxData::Types::Type_GraphicsMesh
  • Field<VxGraphics:Material> -> VxData::Types::Type_GraphicsMaterial
  • Field<VxGraphics:Texture> -> VxData::Types::Type_GraphicsTexture
  • Field<VxSim::ISimulatorModule> -> VxData::Types::Type_SimulatorModule
In order to allow for more Field<Interface*>, the various enum are no longer used in favor of VxData::Types::Type_ExtensionPtr. Should the actual type be needed, the FieldBase needs to be casted into a Field<VxExtension*> and getInterfaceType() should be called, the function will return Interface::kFieldTypeId e.g., a Part will return Part::kFieldTypeId.

The following example shows generic parsing of extension fields.

// The mechanism below has several extension, one of which is of type MyOtherExtension
// But since the code is parsing all extensions, it is done in a generic way
for (auto it = mechanism->getExtensions().begin(); it != mechanism->getExtensions().end(); ++it)
{
	const VxData::Container& parameters = it->getExtension()->getParameterContainer();
	for (auto it = parameters .begin(); it != parameters .end(); ++it)
	{
		VxData::FieldBase& field = *it;
		// Parsing by type
		...
		else if(field.getType() == Type_VxExtensionPtr)
		{
			// you get a generic VxExtension
			VxExtension* val = field.getValue<VxExtension*>();

			// If you need to look at the type
			auto interfaceType = (static_cast<Field<Part>&>(field)).getInterfaceType();

			// interfaceType == Part::kFieldTypeId;
		}
		...
}

 

Next topic: Integrating the Application