summaryrefslogtreecommitdiff
path: root/common/rawaccel.hpp
blob: 1483e67794d3cb8f11986f0dce98cd5bf75d2cf9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#pragma once

#define _USE_MATH_DEFINES
#include <math.h>

#include "vec2.h"
#include "x64-util.hpp"

namespace rawaccel {   

enum class mode { noaccel, linear, classic, natural, logarithmic, sigmoid };

struct rotator {
    vec2d rot_vec = { 1, 0 };

    inline vec2d operator()(const vec2d& input) const {
        return { 
            input.x * rot_vec.x - input.y * rot_vec.y,
            input.x * rot_vec.y + input.y * rot_vec.x
        };
    }

    rotator(double degrees) {
        double rads = degrees * M_PI / 180;
        rot_vec = { cos(rads), sin(rads) };
    }

    rotator() = default;
};

struct accel_scale_clamp {
    double lo = 0;
    double hi = 9;

    inline double operator()(double scale) const {
        return clampsd(scale, lo, hi);
    }

    accel_scale_clamp(double cap) : accel_scale_clamp() {
        if (cap <= 0) {
            // use default, effectively uncapped accel
            return;
        }

        if (cap < 1) {
            // assume negative accel
            lo = cap;
            hi = 1;
        }
        else hi = cap;
    }

    accel_scale_clamp() = default;
};

void error(const char*);

struct accel_function {
    using milliseconds = double;

    /* 
    This value is ideally a few microseconds lower than 
    the user's mouse polling interval, though it should
    not matter if the system is stable.
    */
    milliseconds time_min = 0.4;
    
    double speed_offset = 0;

    // speed midpoint in sigmoid mode
    double m = 0;

    // accel ramp rate
    double b = 0; 

    // the limit for natural and sigmoid modes,
    // or the exponent for classic mode
    double k = 1; 
    
    vec2d weight = { 1, 1 };
    vec2<accel_scale_clamp> clamp;

    inline vec2d operator()(const vec2d& input, milliseconds time, mode accel_mode) const {
        double mag = sqrtsd(input.x * input.x + input.y * input.y);
        double time_clamped = clampsd(time, time_min, 100);
        double speed = maxsd(mag / time_clamped - speed_offset, 0);

        double accel_val = 0;

        switch (accel_mode) {
        case mode::linear: accel_val = b * speed; 
            break;
        case mode::classic: accel_val = pow(b * speed, k); 
            break;
        case mode::natural: accel_val = k - (k * exp(-b * speed)); 
            break;
        case mode::logarithmic: accel_val = log(speed * b + 1); 
            break;
        case mode::sigmoid: accel_val = k / (exp(-b * (speed - m)) + 1); 
            break;
        default:
            break;
        }

        double scale_x = weight.x * accel_val + 1;
        double scale_y = weight.y * accel_val + 1;

        return { 
            input.x * clamp.x(scale_x), 
            input.y * clamp.y(scale_y) 
        };
    }

    struct args_t {
        mode accel_mode = mode::noaccel;
        milliseconds time_min = 0.4;
        double offset = 0;
        double accel = 0;
        double lim_exp = 2;
        double midpoint = 0;
        vec2d weight = { 1, 1 };
        vec2d cap = { 0, 0 };
    };

    accel_function(args_t args) {
        // Preconditions to guard against division by zero and
        // ensure the C math functions can not return NaN or -Inf.
        if (args.accel < 0) error("accel can not be negative, use a negative weight to compensate");
        if (args.time_min <= 0) error("min time must be positive");
        if (args.lim_exp <= 1) {
            if (args.accel_mode == mode::classic) error("exponent must be greater than 1");
            else error("limit must be greater than 1");
        }

        time_min = args.time_min;
        m = args.midpoint;
        b = args.accel;
        k = args.lim_exp - 1;
        if (args.accel_mode == mode::natural) b /= k;
        
        speed_offset = args.offset;
        weight = args.weight;
        clamp.x = accel_scale_clamp(args.cap.x);
        clamp.y = accel_scale_clamp(args.cap.y);
    }

    accel_function() = default;
};

struct variables {
    bool apply_rotate = false;
    bool apply_accel = false;
    mode accel_mode = mode::noaccel;
    rotator rotate;
    accel_function accel_fn;
    vec2d sensitivity = { 1, 1 };

    variables(double degrees, vec2d sens, accel_function::args_t accel_args)
        : accel_fn(accel_args)
    {
        apply_rotate = degrees != 0;
        if (apply_rotate) rotate = rotator(degrees);
        else rotate = rotator();

        apply_accel = accel_args.accel_mode != mode::noaccel;
        accel_mode = accel_args.accel_mode;

        if (sens.x == 0) sens.x = 1;
        if (sens.y == 0) sens.y = 1;
        sensitivity = sens;
    }

    variables() = default;
};

} // rawaccel