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
};
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°
enum max_clamp {
t600 = 600 , // cm (very large)
t300 = 300 ,
t150 = 150 ,
t100 = 100 ,
t75 = 75 ,
t30 = 30 , // Default
t20 = 20 ,
t15 = 15 ,
t10 = 10
};
Default range: ±30cm for X, Y, Z
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
Linear (1:1)
Amplified (2:1)
Reduced (1:2)
S-Curve
Deadzone
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
Input → Output
──────────────────
0 → 0
30 → 60
45 → 90
Use case : Increase sensitivity, see more with less movementConfiguration :
Points: (0, 0), (45, 90)
Output = 2 × Input
High amplification can make precise aiming difficult.
Input → Output
──────────────────
0 → 0
60 → 30
90 → 45
Use case : Reduce sensitivity, more precise controlConfiguration :
Points: (0, 0), (90, 45)
Output = 0.5 × Input
Input → Output
──────────────────
0 → 0
30 → 15 (slow near center)
60 → 60 (1:1 mid-range)
90 → 85 (fast at extremes)
Use case : Precise control in center, increased range at edgesConfiguration :
Points: (0,0), (30,15), (60,60), (90,85)
Smooth curve through points
S-curves provide the best balance for most users.
Input → Output
──────────────────
0 → 0
10 → 0 (deadzone)
20 → 15
90 → 90
Use case : Ignore small movements, reduce jitterConfiguration :
Points: (0,0), (10,0), (20,15), (90,90)
Flat region at start
Practical Mapping Examples
Flight Simulation
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.
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.
Roll
Input (°) → Output (°)
─────────────────────────
0 → 0
45 → 22 (0.5:1)
90 → 45 (0.5:1)
Very reduced - roll is often uncomfortable in cockpit view.
Translation
All axes: 0.5:1 to 0.75:1
Moderate translation to maintain cockpit reference.
Racing Simulation
Yaw
Heavy amplification for apex visibility:
(0,0), (30,60), (60,100), (90,140)
Pitch
Minimal - usually looking forward:
(0,0), (30,15), (60,30)
Roll
Moderate to feel car body roll:
(0,0), (30,20), (60,40)
First-Person Shooter
Yaw & Pitch
1:1 or slight reduction:
(0,0), (45,40), (90,80)
Need precise aiming, minimal amplification.
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
Standard
Swap Axes
Disable Axis
Output ← Source
──────────────────
TX ← TX
TY ← TY
TZ ← TZ
Yaw ← Yaw
Pitch ← Pitch
Roll ← Roll
// Rotate coordinate system 90°
Output ← Source
──────────────────
TX ← TY // Right ← Forward
TY ← TX // Forward ← Right
TZ ← TZ // Up ← Up
// Disable roll output
Roll . src = - 1 ; // or 6
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);
value < bool > invert_post; // Invert after mapping
Applied at end of pipeline: for ( int i = 0 ; i < 6 ; i ++ )
if ( m (i). opts . invert_post )
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 ();
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
Check “Apply mapping curves” is enabled:
value < bool > apply_mapping_curves {b, "apply-mapping-curves" , true };
Verify curve has points defined
Check input range (clamp_x_) covers your movement range
Restart tracking after changing curves
Check you’re editing the correct axis
Verify curve points are sorted by X value
Ensure first point is at or near (0, 0)
Check for overlapping points (auto-removed but can cause confusion)
Splines should be smooth - check for too-sharp corners
Verify input range isn’t being clamped unexpectedly
Check for axis remapping conflicts
Look for points very close together
Check if alternate spline is enabled but not configured
Verify input range is symmetric (±X, not just 0-X)
Check inversion settings
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