Skip to main content
Filters process raw tracking data to reduce noise, smooth movement, and improve tracking quality. OpenTrack includes several filter types optimized for different use cases.

Filter System Overview

Filters implement the IFilter interface:
struct OTR_API_EXPORT IFilter : module_status_mixin {
    IFilter();
    
    // Perform filtering step
    virtual void filter(const double *input, double *output) = 0;
    
    // Optionally reset filter state when centering
    virtual void center() {}
};

Module Selection

struct module_settings {
    bundle b { make_bundle("modules") };
    value<QString> filter_dll { b, "filter-dll", "accela" };
};

Available Filters

Adaptive smoothing based on head velocity. Provides strong smoothing at rest and minimal lag during fast movements.Best for: General gaming and simulationImplementation:
struct accela : IFilter {
    void filter(const double* input, double *output) override;
    void center() override { first_run = true; }
    
    spline spline_rot;   // Rotation smoothing curve
    spline spline_pos;   // Translation smoothing curve
    
private:
    double last_output[6] {};
    double deltas[6] {};
    Timer t;
    bool first_run = true;
};
How it works:
  1. Calculate velocity (change per frame)
  2. Map velocity through spline curve to get smoothing factor
  3. Apply exponential smoothing based on factor
Advantages:
  • Strong smoothing at rest (reduces jitter)
  • Minimal lag during fast movements
  • Separate curves for rotation and translation
  • Highly configurable

Filter Processing Pipeline

Filters are applied in the tracking pipeline:
void pipeline::logic() {
    // ... get tracker data ...
    
    // Apply filter
    if (libs.pFilter) {
        Pose tmp(value);
        libs.pFilter->filter(value, tmp);
        value = tmp;
    }
    
    logger.write_pose(value); // "filtered"
    
    // ... continue processing ...
}
1

Input

Receive corrected pose data (after camera offset and centering):
// input[0-5]: TX, TY, TZ, Yaw, Pitch, Roll
2

Filter Processing

Filter processes each frame:
void filter(const double *input, double *output) {
    const double dt = timer.elapsed_seconds();
    timer.start();
    
    for (int i = 0; i < 6; i++) {
        output[i] = process_axis(input[i], i, dt);
    }
}
Filters must track their own timing using a timer. Frame rate is not guaranteed to be constant.
3

State Management

Filters maintain internal state:
private:
    double last_output[6] {};  // Previous output
    double velocity[6] {};     // Current velocity
    Timer t;                   // Frame timing
    bool first_run = true;     // Skip first frame
4

Center Reset

When user centers, filter can reset:
void center() override {
    first_run = true;
    // Clear accumulated state
    memset(last_output, 0, sizeof(last_output));
}

Accela Filter Configuration

The Accela filter is the most powerful and commonly used:

Smoothing Curves

Accela uses two spline curves to map velocity to smoothing:
spline spline_rot;    // Rotation axes (Yaw, Pitch, Roll)
spline spline_pos;    // Translation axes (X, Y, Z)

Curve Design

1

X-axis: Velocity

Input velocity in degrees/second (rotation) or cm/second (translation)
  • 0: At rest (maximum smoothing)
  • 50-100: Moderate movement
  • 200+: Fast movement (minimal smoothing)
2

Y-axis: Smoothing Factor

Output smoothing strength:
  • 0.0: No smoothing (instant response)
  • 0.5: Moderate smoothing
  • 0.95: Strong smoothing (slow response)
3

Typical Curve

Velocity (°/s)  →  Smoothing Factor
─────────────────────────────────
     0          →      0.90      (strong smooth)
    50          →      0.70      (moderate)
   100          →      0.40      (light)
   200          →      0.10      (minimal)
This provides strong smoothing when still, but reduces lag during fast movements.

Rotation vs Translation

Typically needs less aggressive smoothing:
// Example rotation curve points
{ 0,   0.85 },   // At rest: strong smooth
{ 45,  0.60 },   // Slow turn
{ 90,  0.35 },   // Normal turn
{ 180, 0.15 },   // Fast turn
{ 300, 0.05 }    // Very fast: minimal lag
Head rotation is generally smoother than position tracking.
High smoothing values (>0.95) can cause significant lag. Balance smoothness with responsiveness based on your tracker quality.

EWMA Filter Configuration

Simpler than Accela, with fixed smoothing:
// Configuration
struct ewma_settings {
    value<slider_value> alpha_rot;    // Rotation smoothing
    value<slider_value> alpha_pos;    // Position smoothing
};

// Filter implementation
void ewma::filter(const double *input, double *output) {
    const double dt = t.elapsed_seconds();
    t.start();
    
    for (int i = 0; i < 6; i++) {
        const double alpha = (i < 3) ? s.alpha_pos : s.alpha_rot;
        output[i] = alpha * input[i] + (1 - alpha) * last_output[i];
        last_output[i] = output[i];
    }
}

Tuning EWMA

  • Alpha = 0.1: Maximum smoothing, high lag
  • Alpha = 0.5: Balanced
  • Alpha = 0.9: Minimal smoothing, minimal lag
Start with 0.5 and adjust. Lower values = smoother but more lag.

Filter Dialog Interface

Filters can provide live configuration:
struct OTR_API_EXPORT IFilterDialog : public BaseDialog {
    virtual void register_filter(IFilter* filter) = 0;
    virtual void unregister_filter() = 0;
};

Live Updates

class dialog_accela : public IFilterDialog {
    void register_filter(IFilter* f) override {
        filter = static_cast<accela*>(f);
        // Can now access filter splines for live preview
    }
    
    void unregister_filter() override {
        filter = nullptr;
    }
    
private:
    accela* filter = nullptr;
};

Custom Filter Development

1

Implement IFilter

class MyFilter : public IFilter {
public:
    module_status initialize() override {
        return status_ok();
    }
    
    void filter(const double *input, double *output) override {
        // Your filtering logic
    }
    
    void center() override {
        // Reset filter state
    }
};
2

Handle Timing

private:
    Timer t;
    bool first_run = true;
    
void filter(const double *input, double *output) override {
    if (first_run) {
        first_run = false;
        memcpy(output, input, sizeof(double) * 6);
        return;
    }
    
    const double dt = t.elapsed_seconds();
    t.start();
    
    // Use dt for time-based filtering
}
3

Register Plugin

class MyFilterMeta : public Metadata {
    QString name() override { return "My Filter"; }
    QIcon icon() override { return QIcon(":/filter.png"); }
};

OPENTRACK_DECLARE_FILTER(
    MyFilter,
    MyFilterDialog,
    MyFilterMeta
)

Best Practices

Do’s

Use time-based filtering - Account for variable frame rate with dt
Handle first frame - Skip or initialize carefully on first run
Reset on center - Clear accumulated state when user centers
Separate rotation/translation - Different axes need different treatment

Don’ts

Don’t assume constant frame rate - Pipeline runs at ~250Hz but varies
Don’t introduce NaN/Infinity - Check for invalid math operations
Don’t block - Filter runs in tracking thread, keep it fast

Troubleshooting

  1. Reduce smoothing factor (Accela: lower curve values)
  2. Make curve steeper (faster response at higher velocities)
  3. Try EWMA with higher alpha (0.7-0.9)
  4. Check if game has additional filtering
  1. Increase smoothing at low velocities
  2. Check tracker quality and lighting
  3. Try Hamilton filter for better noise handling
  4. Verify tracker is running at stable frame rate
  1. Verify filter is selected in modules
  2. Check filter initialization didn’t fail
  3. Restart tracking after changing filter
  4. Check OpenTrack logs for errors
Configure separate smoothing per axis:
// In custom filter
for (int i = 0; i < 6; i++) {
    const double factor = smoothing_factors[i];
    output[i] = factor * input[i] + (1-factor) * last[i];
}

Performance Impact

Filter CPU usage comparison (approximate):
  • None: 0% overhead
  • EWMA: <0.1% CPU
  • Accela: <0.5% CPU (includes spline evaluation)
  • Hamilton: 1-2% CPU (quaternion and matrix math)
All filters are designed to run efficiently at 250Hz.

Next Steps

Mapping Curves

Fine-tune response after filtering

Configuration

Advanced tracking settings

Tracker Setup

Improve tracking quality at the source

Build docs developers (and LLMs) love