Skip to main content
Mapping curves transform filtered tracking data to final output, allowing you to customize how head movements translate to in-game view changes. This is one of OpenTrack’s most powerful features.

Mapping System Overview

OpenTrack uses spline curves to map input ranges to output ranges:
struct Map {
    axis_opts& opts;           // Axis configuration
    QString name, alt_name;    // Spline names
    spline spline_main;        // Primary spline curve
    spline spline_alt;         // Alternate spline (for negative values)
};

class Mappings {
private:
    Map axes[6];  // One map per axis
public:
    Map& operator()(int i) { return axes[i]; }
};

How Mapping Works

The pipeline applies mapping curves after filtering:
if (s.apply_mapping_curves) {
    // CAVEAT: rotation only, before reltrans
    for (int i = 3; i < 6; i++)
        value(i) = map(value(i), m(i));
}

value = apply_reltrans(value, disabled, center_ordered);

if (s.apply_mapping_curves) {
    // CAVEAT: translation only, after reltrans  
    for (int i = 0; i < 3; i++)
        value(i) = map(value(i), m(i));
}
Rotation axes are mapped before relative translation, while position axes are mapped after. This ensures proper coordinate transformations.

Map Function

double pipeline::map(double pos, const Map& axis) {
    bool altp = (pos < 0) && axis.opts.altp;
    
    axis.spline_main.set_tracking_active(!altp);
    axis.spline_alt.set_tracking_active(altp);
    
    auto& fc = altp ? axis.spline_alt : axis.spline_main;
    return fc.get_value(pos);
}

Spline Curves

OpenTrack uses Catmull-Rom splines for smooth interpolation:
class spline {
    static constexpr unsigned value_count = 16384;  // Resolution
    static constexpr double c_interp = 5;           // Interpolation multiplier
    
    mutable std::vector<double> data;               // Pre-computed values
    mutable points_t points;                        // Control points
};

Catmull-Rom Interpolation

// Catmull-Rom spline basis
const double cx[4] = {
    2 * p1_x,                           // 1
    -p0_x + p2_x,                       // t
    2*p0_x - 5*p1_x + 4*p2_x - p3_x,   // t²
    -p0_x + 3*p1_x - 3*p2_x + p3_x     // t³
};

const double cy[4] = {
    2 * p1_y,
    -p0_y + p2_y,
    2*p0_y - 5*p1_y + 4*p2_y - p3_y,
    -p0_y + 3*p1_y - 3*p2_y + p3_y
};

// Evaluate at parameter t [0,1]
const double t2 = t*t;
const double t3 = t*t*t;

x = 0.5 * (cx[0] + cx[1]*t + cx[2]*t2 + cx[3]*t3);
y = 0.5 * (cy[0] + cy[1]*t + cy[2]*t2 + cy[3]*t3);
Splines are pre-computed into a lookup table for performance. The curve is evaluated at 250Hz, so fast lookups are critical.

Axis Configuration

Each axis has configurable parameters:
struct axis_opts {
    value<double> zero;           // Zero position offset
    value<int> src;               // Source axis (-1 = disabled, 0-5 = axis)
    value<bool> invert_pre;       // Invert before centering
    value<bool> invert_post;      // Invert after mapping
    value<bool> altp;             // Use alternate spline for negative
    value<max_clamp> clamp_x_;    // Max input value
    value<max_clamp> clamp_y_;    // Max output value
};

Input Range (clamp_x_)

enum max_clamp {
    r180 = 180,   // Yaw, Roll
    r90 = 90,     // Pitch (typical)
    r60 = 60,
    r45 = 45,
    r30 = 30,
    r20 = 20,
    r15 = 15,
    r10 = 10
};
Default ranges:
  • Yaw: ±180°
  • Pitch: ±90°
  • Roll: ±180°

Output Range (clamp_y_)

Similar ranges, but can be negative to invert:
enum max_clamp {
    o_r180 = -180,  // Inverted rotation
    o_r90 = -90,
    o_t75 = -75,    // Inverted translation
    o_t100 = -100,
    // ... etc
};

Common Curve Types

Input  →  Output
──────────────────
  0    →    0
 45    →   45
 90    →   90
Use case: Direct mapping, no modificationConfiguration:
  • Two points: (0, 0) and (max_input, max_output)
  • Input range = Output range

Practical Mapping Examples

Flight Simulation

1

Yaw (Left/Right)

Input (°)  →  Output (°)
─────────────────────────
    0      →      0
   45      →     60      (1.33:1)
   90      →    110      (1.22:1)
  135      →    150      (1.11:1)
  180      →    180      (1:1)
Amplified in center for better awareness, tapers to 1:1 at extremes.
2

Pitch (Up/Down)

Input (°)  →  Output (°)
─────────────────────────
    0      →      0
   20      →     15      (0.75:1)
   45      →     40      (0.89:1)
   70      →     65      (0.93:1)
   90      →     90      (1:1)
Reduced sensitivity to prevent excessive up/down look.
3

Roll

Input (°)  →  Output (°)
─────────────────────────
    0      →      0
   45      →     22      (0.5:1)
   90      →     45      (0.5:1)
Very reduced - roll is often uncomfortable in cockpit view.
4

Translation

All axes: 0.5:1 to 0.75:1
Moderate translation to maintain cockpit reference.

Racing Simulation

1

Yaw

Heavy amplification for apex visibility:
(0,0), (30,60), (60,100), (90,140)
2

Pitch

Minimal - usually looking forward:
(0,0), (30,15), (60,30)
3

Roll

Moderate to feel car body roll:
(0,0), (30,20), (60,40)

First-Person Shooter

1

Yaw & Pitch

1:1 or slight reduction:
(0,0), (45,40), (90,80)
Need precise aiming, minimal amplification.
2

Translation

Very reduced or disabled:
Maximum 0.3:1 ratio
Translation can be disorienting in FPS games.

Alternate Splines

Use different curves for negative values:
value<bool> altp;  // Enable alternate spline

spline spline_main;  // For positive input
spline spline_alt;   // For negative input
Use cases:
  • Asymmetric workspace (easier to turn one direction)
  • Compensate for tracking issues on one side
  • Different game mechanics per direction
// Looking right (positive) - normal sensitivity
spline_main: (0,0), (45,60), (90,110)

// Looking left (negative) - reduced sensitivity
spline_alt: (0,0), (45,45), (90,90)
Useful if your tracking setup has better performance looking one direction.

Source Axis Mapping

Remap which physical axis controls each output:
value<int> src;  // -1 = disabled, 0-5 = TX,TY,TZ,Yaw,Pitch,Roll
Output  ←  Source
──────────────────
TX      ←  TX
TY      ←  TY
TZ      ←  TZ
Yaw     ←  Yaw
Pitch   ←  Pitch
Roll    ←  Roll

Zero Position Offset

Shift the zero point of an axis:
value<double> zero;  // Offset in degrees or cm

Pose pipeline::apply_zero_pos(Pose value) const {
    for (int i = 0; i < 6; i++)
        value(i) += m(i).opts.zero * (m(i).opts.invert_pre ? -1 : 1);
    return value;
}
Use cases:
  • Adjust for monitor not being centered
  • Compensate for permanent head tilt
  • Offset default view position in game

Inversion Options

value<bool> invert_pre;  // Invert before centering
Applied early in pipeline:
for (int i = 0; i < 6; i++)
    if (m(i).opts.invert_pre)
        value(i) = -value(i);
Use invert_pre for fixing tracker orientation. Use invert_post for game-specific requirements.

Spline Point Management

Manipulate curve points programmatically:
class spline {
public:
    void add_point(double x, double y);
    void add_point(QPointF pt);
    void move_point(int idx, QPointF pt);
    void remove_point(int i);
    void clear();
    
    const points_t& get_points() const;
    int get_point_count() const;
};

Example: Create Linear Curve

spline s;
s.clear();
s.add_point(0, 0);
s.add_point(90, 90);
s.save();

Example: Create S-Curve

spline s;
s.clear();
s.add_point(0, 0);
s.add_point(30, 15);   // Slow in center
s.add_point(60, 60);   // 1:1 at midpoint
s.add_point(90, 85);   // Fast at edge
s.save();

Performance Considerations

Splines are highly optimized:
static constexpr unsigned value_count = 16384;  // Lookup table size

double spline::get_value(double x) const {
    double q = x * bucket_size_coefficient(points);
    int xi = (int)q;
    double yi = get_value_internal(xi);
    double yiplus1 = get_value_internal(xi+1);
    double f = (q-xi);
    return yiplus1 * f + yi * (1 - f);  // Linear interpolation
}
The spline is pre-computed into a 16384-entry lookup table. Runtime evaluation is just a lookup and linear interpolation - very fast.

Troubleshooting

  1. Check “Apply mapping curves” is enabled:
    value<bool> apply_mapping_curves {b, "apply-mapping-curves", true};
    
  2. Verify curve has points defined
  3. Check input range (clamp_x_) covers your movement range
  4. Restart tracking after changing curves
  1. Check you’re editing the correct axis
  2. Verify curve points are sorted by X value
  3. Ensure first point is at or near (0, 0)
  4. Check for overlapping points (auto-removed but can cause confusion)
  1. Splines should be smooth - check for too-sharp corners
  2. Verify input range isn’t being clamped unexpectedly
  3. Check for axis remapping conflicts
  4. Look for points very close together
  1. Check if alternate spline is enabled but not configured
  2. Verify input range is symmetric (±X, not just 0-X)
  3. Check inversion settings
  4. Look for centering issues

Best Practices

Start simple - Begin with linear 1:1 curves, then adjust
Test incrementally - Change one axis at a time
Use S-curves - Generally provide best comfort and precision
Match your game - Racing needs different curves than flight sim
Avoid extremes - Very high amplification (>3:1) or reduction (<0.3:1) can be uncomfortable

Next Steps

Configuration

Advanced tracking settings

Filters

Smooth data before mapping

Relative Translation

Advanced translation compensation