Skip to main content

Introduction

OpenTrack provides a flexible plugin system that allows developers to extend functionality through three main plugin types:
  • Trackers: Capture head pose data from hardware devices or computer vision
  • Protocols: Output tracking data to games and applications
  • Filters: Process and smooth raw tracking data
All plugins are dynamically loaded at runtime and communicate through standardized C++ interfaces.

Plugin Types

Tracker Plugins

Tracker plugins implement the ITracker interface to provide pose data from various sources:
  • Computer vision trackers (face tracking, point tracking)
  • Hardware devices (VR headsets, sensor hats, joysticks)
  • Network-based trackers (UDP, proprietary protocols)
Key responsibilities:
  • Initialize and configure tracking hardware/algorithms
  • Continuously provide 6DOF pose data (X, Y, Z, Yaw, Pitch, Roll)
  • Handle centering/reset operations

Protocol Plugins

Protocol plugins implement the IProtocol interface to transmit tracking data to games:
  • Game-specific protocols (FreeTrack, TrackIR, FlightGear)
  • Generic protocols (UDP, shared memory)
  • Virtual device emulation
Key responsibilities:
  • Receive pose data at 250Hz
  • Transform and transmit data to target applications
  • Report connected game name

Filter Plugins

Filter plugins implement the IFilter interface to process tracking data:
  • Smoothing filters (EWMA, Kalman)
  • Motion filters (acceleration-based)
  • Custom transformation pipelines
Key responsibilities:
  • Filter input pose data to reduce jitter
  • Apply deadzone and smoothing algorithms
  • Handle centering events

Core Data Types

Pose Representation

using Pose = Mat<double, 6, 1>;
Pose data is represented as a 6-element vector with the following axis ordering:
enum Axis : int
{
    TX = 0,    // Translation X (left/right)
    TY = 1,    // Translation Y (up/down)
    TZ = 2,    // Translation Z (forward/back)
    Yaw = 3,   // Rotation around Y axis
    Pitch = 4, // Rotation around X axis
    Roll = 5   // Rotation around Z axis
};

Module Status

struct module_status
{
    QString error;
    bool is_ok() const;
};
Used to report initialization success or failure:
// Success
return status_ok();

// Failure with error message
return error("Camera not found");

Plugin Registration

Each plugin must implement three components:
  1. Implementation class: Implements the interface (ITracker, IProtocol, or IFilter)
  2. Dialog class: Provides UI for configuration
  3. Metadata class: Provides name and icon

Registration Macros

Plugins are registered using macros that export the required factory functions:
// Tracker registration
OPENTRACK_DECLARE_TRACKER(tracker_class, dialog_class, metadata_class)

// Protocol registration
OPENTRACK_DECLARE_PROTOCOL(protocol_class, dialog_class, metadata_class)

// Filter registration
OPENTRACK_DECLARE_FILTER(filter_class, dialog_class, metadata_class)

Metadata Interface

class Metadata : public TR, public Metadata_
{
    Q_OBJECT
public:
    virtual QString name() = 0;  // Plugin display name
    virtual QIcon icon() = 0;    // Plugin icon (or empty)
};
Example implementation:
class MyTrackerMetadata : public Metadata
{
    Q_OBJECT
    QString name() override { return tr("My Tracker"); }
    QIcon icon() override { return QIcon(":/images/icon.png"); }
};

Dialog Base Class

class BaseDialog : public QDialog
{
    Q_OBJECT
public:
    virtual bool embeddable() noexcept;        // Can embed in main UI
    virtual void set_buttons_visible(bool x);  // Show/hide OK/Cancel
    virtual void save();                       // Save settings
    virtual void reload();                     // Reload settings
signals:
    void closing();                            // Dialog closing
};
All dialog classes inherit from ITrackerDialog, IProtocolDialog, or IFilterDialog, which extend BaseDialog.

Threading Model

Plugin methods are called from different threads. Proper synchronization is required.

Tracker Thread Safety

  • start_tracker(): Called from UI thread
  • data(): Called at ~250Hz from pipeline thread
  • center(): Called from UI thread
Use mutexes to protect shared state:
void Tracker::data(double* data)
{
    QMutexLocker lock(&data_mutex);
    // Copy pose data safely
    for (int i = 0; i < 6; i++)
        data[i] = current_pose[i];
}

Protocol Thread Safety

  • initialize(): Called from UI thread
  • pose(): Called at 250Hz from pipeline thread
  • game_name(): Called from UI thread

Filter Thread Safety

  • initialize(): Called from UI thread
  • filter(): Called at 250Hz from pipeline thread
  • center(): Called from UI thread

Best Practices

  • Keep data(), pose(), and filter() methods fast (called at 250Hz)
  • Use separate threads for expensive computations
  • Avoid blocking operations in data path
  • Pre-allocate buffers where possible
  • Return descriptive error messages from initialize()
  • Use module_status::error() for initialization failures
  • Log errors using qDebug() for runtime issues
  • Validate all user inputs in dialog classes
  • Initialize resources in initialize() or start_tracker()
  • Clean up in destructor
  • Use RAII for automatic cleanup
  • Release hardware devices properly
  • Use options::opts base class for settings
  • Use options::value<T> for individual settings
  • Connect to bundle_::saving and bundle_::reloading signals
  • Apply settings atomically with mutex protection

Build System Integration

Plugins are built as shared libraries and must:
  1. Link against opentrack-api to avoid vtable errors
  2. Export symbols using OTR_PLUGIN_EXPORT
  3. Be placed in the appropriate directory at runtime
CMakeLists.txt example:
opentrack_boilerplate(
    opentrack-tracker-myplugin
    SOURCES tracker.cpp tracker.h dialog.cpp
    LINK opentrack-api opentrack-video opentrack-cv
)

target_link_libraries(opentrack-tracker-myplugin opentrack-api)

Next Steps

Tracker Interface

Complete ITracker API reference

Protocol Interface

Complete IProtocol API reference

Filter Interface

Complete IFilter API reference