#pragma once #define _USE_MATH_DEFINES #include #include "vec2.h" #include "x64-util.hpp" #include "external/tagged-union-single.h" namespace rawaccel { /// Enum to hold acceleration implementation types (i.e. types of curves.) enum class mode { noaccel, linear, classic, natural, logarithmic, sigmoid, power }; /// Struct to hold vector rotation details. struct rotator { /// Rotational vector, which points in the direction of the post-rotation positive y axis. vec2d rot_vec = { 1, 0 }; /// /// Rotates given input vector according to struct's rotational vector. /// /// Input vector to be rotated /// 2d vector of rotated input. 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 to hold clamp (min and max) details for acceleration application struct accel_scale_clamp { double lo = 0; double hi = 9; /// /// Clamps given input to min at lo, max at hi. /// /// Double to be clamped /// Clamped input as double 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; }; // Error throwing calls std libraries which are unavailable in kernel mode. #ifdef _KERNEL_MODE void error(const char*) {} #else void error(const char* s); #endif using milliseconds = double; /// Struct to hold arguments for an acceleration function. struct accel_args { 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 }; }; /// /// Struct to hold acceleration curve implementation details. /// /// Type of acceleration. template struct accel_implentation { /// First constant for use in acceleration curves. Generally, the acceleration ramp rate. double curve_constant_one = 0; /// Second constant for use in acceleration curves. Generally, the limit or exponent in the curve. double curve_constant_two = 0; /// Third constant for use in acceleration curves. The midpoint in sigmoid mode. double curve_constant_three = 0; /// The offset past which acceleration is applied. Used in power mode. double offset = 0; /// /// Initializes a new instance of the struct. /// /// /// accel_implentation(accel_args args) { curve_constant_one = args.accel; curve_constant_two = args.lim_exp - 1; curve_constant_three = args.midpoint; offset = args.offset; } /// /// Returns accelerated value of speed as a ratio of magnitude. /// /// Mouse speed at which to calculate acceleration. /// Ratio of accelerated movement magnitude to input movement magnitude. double accelerate(double speed) { return 0; } /// /// Verifies arguments as valid. Errors if not. /// /// Arguments to verified. void verify(accel_args args) { 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"); } /// /// /// /// accel_implentation() = default; }; /// Struct to hold linear acceleration implementation. struct accel_linear : accel_implentation { accel_linear(accel_args args) : accel_implentation(args) {} double accelerate(double speed) { //f(x) = mx return curve_constant_one * speed; } void verify(accel_args args) { accel_implentation::verify(args); if (args.lim_exp <= 1) error("limit must be greater than 1"); } }; /// Struct to hold "classic" (linear raised to power) acceleration implementation. struct accel_classic : accel_implentation { accel_classic(accel_args args) : accel_implentation(args) {} double accelerate(double speed) { //f(x) = (mx)^k return pow(curve_constant_one * speed, curve_constant_two); } void verify(accel_args args) { accel_implentation::verify(args); if (args.lim_exp <= 1) error("exponent must be greater than 1"); } }; /// Struct to hold "natural" (vanishing difference) acceleration implementation. struct accel_natural : accel_implentation { accel_natural(accel_args args) : accel_implentation(args) { curve_constant_one /= curve_constant_two; } double accelerate(double speed) { // f(x) = k(1-e^(-mx)) return curve_constant_two - (curve_constant_two * exp(-curve_constant_one * speed));; } void verify(accel_args args) { accel_implentation::verify(args); if (args.lim_exp <= 1) error("exponent must be greater than 1"); } }; /// Struct to hold logarithmic acceleration implementation. struct accel_logarithmic : accel_implentation { accel_logarithmic(accel_args args) : accel_implentation(args) {} double accelerate(double speed) { return log(speed * curve_constant_one + 1); } void verify(accel_args args) { accel_implentation::verify(args); if (args.lim_exp <= 1) error("exponent must be greater than 1"); } }; /// Struct to hold sigmoid (s-shaped) acceleration implementation. struct accel_sigmoid : accel_implentation { accel_sigmoid(accel_args args) : accel_implentation(args) {} double accelerate(double speed) { //f(x) = k/(1+e^(-m(c-x))) return curve_constant_two / (exp(-curve_constant_one * (speed - curve_constant_three)) + 1); } void verify(accel_args args) { accel_implentation::verify(args); if (args.lim_exp <= 1) error("exponent must be greater than 1"); } }; /// Struct to hold power (non-additive) acceleration implementation. struct accel_power : accel_implentation { accel_power(accel_args args) : accel_implentation(args) { curve_constant_two++; } double accelerate(double speed) { // f(x) = (mx)^k - 1 // The subtraction of 1 occurs with later addition of 1 in mind, // so that the input vector is directly multiplied by (mx)^k (if unweighted) return (offset > 0 && speed < 1) ? 0 : pow(speed * curve_constant_one, curve_constant_two) - 1; } void verify(accel_args args) { accel_implentation::verify(args); if (args.lim_exp <= 0) error("exponent must be greater than 0"); } }; /// Struct to hold acceleration implementation which applies no acceleration. struct accel_noaccel : accel_implentation { accel_noaccel(accel_args args) : accel_implentation(args) {} double accelerate(double speed) { return 0; } void verify(accel_args args) {} }; /// Tagged union to hold all accel implementations and allow "polymorphism" via a visitor call. using accel_implementation_t = tagged_union; /// Struct for holding acceleration application details. struct accel_function { /* 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. */ /// The minimum time period for one mouse movement. milliseconds time_min = 0.4; /// The offset past which acceleration is applied. double speed_offset = 0; /// The acceleration implementation (i.e. curve) accel_implementation_t accel; /// The weight of acceleration applied in {x, y} dimensions. vec2d weight = { 1, 1 }; /// The object which sets a min and max for the acceleration scale. vec2 clamp; accel_function(accel_args args) { switch (args.accel_mode) { case mode::linear: accel = accel_linear(args); break; case mode::classic: accel = accel_classic(args); break; case mode::natural: accel = accel_natural(args); break; case mode::logarithmic: accel = accel_logarithmic(args); break; case mode::sigmoid: accel = accel_sigmoid(args); break; case mode::power: accel = accel_power(args); } // Verification is performed by the accel_implementation object // and therefore must occur after the object has been instantiated verify(args); time_min = args.time_min; speed_offset = args.offset; weight = args.weight; clamp.x = accel_scale_clamp(args.cap.x); clamp.y = accel_scale_clamp(args.cap.y); } /// /// Applies mouse acceleration to a given speed, via visitor function to accel_implementation_t /// /// Speed from which to determine acceleration /// Acceleration as a ratio magnitudes, as a double double apply(double speed) const { return accel.visit([=](auto accel_t) { return accel_t.accelerate(speed); }); } /// /// Verifies acceleration arguments, via visitor function to accel_implementation_t /// /// Arguments to be verified void verify(accel_args args) const { return accel.visit([=](auto accel_t) { accel_t.verify(args); }); } /// /// Applies weighted acceleration to given input for given time period. /// /// 2d vector of {x, y} mouse movement to be accelerated /// Time period over which input movement was accumulated /// inline vec2d operator()(const vec2d& input, milliseconds time) 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 = apply(speed); 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) }; } accel_function() = default; }; /// Struct to hold variables and methods for modifying mouse input struct mouse_modifier { bool apply_rotate = false; bool apply_accel = false; rotator rotate; accel_function accel_fn; vec2d sensitivity = { 1, 1 }; mouse_modifier(double degrees, vec2d sens, accel_args 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; if (sens.x == 0) sens.x = 1; if (sens.y == 0) sens.y = 1; sensitivity = sens; } /// /// Applies modification without acceleration. Rotation is the only /// modification currently implemented. /// /// Input to be modified. /// 2d vector of modified input. inline vec2d modify(vec2d input) { if (apply_rotate) { return rotate(input); } return input; } /// /// Applies modification, including acceleration. /// /// Input to be modified /// Time period for determining acceleration. /// 2d vector with modified input. inline vec2d modify(vec2d input, milliseconds time) { return accel_fn(modify(input), time); } mouse_modifier() = default; }; } // rawaccel