#pragma once #define _USE_MATH_DEFINES #include #include "x64-util.hpp" #include "tagged-union-single.h" #include "accel-linear.hpp" #include "accel-classic.hpp" #include "accel-natural.hpp" #include "accel-logarithmic.hpp" #include "accel-sigmoid.hpp" #include "accel-power.hpp" #include "accel-noaccel.hpp" namespace rawaccel { using milliseconds = double; /// Struct to hold vector rotation details. struct rotator { /// Rotational vector, which points in the direction of the post-rotation positive x 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; }; /// Tagged union to hold all accel implementations and allow "polymorphism" via a visitor call. using accel_impl_t = tagged_union; /// Struct to hold information about applying a gain cap. struct velocity_gain_cap { // The minimum speed past which gain cap is applied. double threshold = 0; // The gain at the point of cap double slope = 0; // The intercept for the line with above slope to give continuous velocity function double intercept = 0; // Whether or not velocity gain cap is enabled bool cap_gain_enabled = false; /// /// Initializes a velocity gain cap for a certain speed threshold /// by estimating the slope at the threshold and creating a line /// with that slope for output velocity calculations. /// /// The speed at which velocity gain cap will kick in /// The offset applied in accel calculations /// The accel implementation used in the containing accel_fn velocity_gain_cap(double speed, double offset, accel_impl_t accel) { if (speed <= 0) return; // Estimate gain at cap point by taking line between two input vs output velocity points. // First input velocity point is at cap; for second pick a velocity a tiny bit larger. double speed_second = 1.001 * speed; double speed_diff = speed_second - speed; // Return if by glitch or strange values the difference in points is 0. if (speed_diff == 0) return; cap_gain_enabled = true; // Find the corresponding output velocities for the two points. // Subtract offset for acceleration, like in accel_fn() double out_first = accel.visit([=](auto&& impl) { double accel_val = impl.accelerate(speed-offset); return impl.scale(accel_val); }).x * speed; double out_second = accel.visit([=](auto&& impl) { double accel_val = impl.accelerate(speed_second-offset); return impl.scale(accel_val); }).x * speed_second; // Calculate slope and intercept from two points. slope = (out_second - out_first) / speed_diff; intercept = out_first - slope * speed; threshold = speed; } /// /// Applies velocity gain cap to speed. /// Returns scale value by which to multiply input to place on gain cap line. /// /// Speed to be capped /// Scale multiplier for input inline double operator()(double speed) const { return slope + intercept / speed; } /// /// Whether gain cap should be applied to given speed. /// /// Speed to check against threshold. /// Whether gain cap should be applied. inline bool should_apply(double speed) const { return cap_gain_enabled && speed > threshold; } velocity_gain_cap() = default; }; struct accel_fn_args { accel_args acc_args; int accel_mode = accel_impl_t::id; milliseconds time_min = 0.4; vec2d cap = { 0, 0 }; double gain_cap = 0; }; /// 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_impl_t accel; /// The object which sets a min and max for the acceleration scale. vec2 clamp; velocity_gain_cap gain_cap = velocity_gain_cap(); accel_function(const accel_fn_args& args) { if (args.time_min <= 0) bad_arg("min time must be positive"); if (args.acc_args.offset < 0) bad_arg("offset must not be negative"); accel.tag = args.accel_mode; accel.visit([&](auto& impl) { impl = { args.acc_args }; }); time_min = args.time_min; speed_offset = args.acc_args.offset; clamp.x = accel_scale_clamp(args.cap.x); clamp.y = accel_scale_clamp(args.cap.y); gain_cap = velocity_gain_cap(args.gain_cap, speed_offset, accel); } /// /// 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 raw_speed = mag / time_clamped; vec2d scale; // gain_cap needs raw speed for velocity line calculation if (gain_cap.should_apply(raw_speed)) { double gain_cap_scale = gain_cap(raw_speed); scale = { gain_cap_scale, gain_cap_scale }; } else { scale = accel.visit([=](auto&& impl) { double accel_val = impl.accelerate(maxsd(mag / time_clamped - speed_offset, 0)); return impl.scale(accel_val); }); } return { input.x * clamp.x(scale.x), input.y * clamp.y(scale.y) }; } accel_function() = default; }; struct modifier_args { double degrees = 0; vec2d sens = { 1, 1 }; accel_fn_args acc_fn_args; }; /// 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(const modifier_args& args) : accel_fn(args.acc_fn_args) { apply_rotate = args.degrees != 0; if (apply_rotate) rotate = rotator(args.degrees); else rotate = rotator(); apply_accel = args.acc_fn_args.accel_mode != 0 && args.acc_fn_args.accel_mode != accel_impl_t::id; if (args.sens.x == 0) sensitivity.x = 1; else sensitivity.x = args.sens.x; if (args.sens.y == 0) sensitivity.y = 1; else sensitivity.y = args.sens.y; } /// /// Applies modification without acceleration. /// /// Input to be modified. /// 2d vector of modified input. inline vec2d modify_without_accel(vec2d input) { if (apply_rotate) { input = rotate(input); } input.x *= sensitivity.x; input.y *= sensitivity.y; return input; } /// /// Applies modification, including acceleration. /// /// Input to be modified /// Time period for determining acceleration. /// 2d vector with modified input. inline vec2d modify_with_accel(vec2d input, milliseconds time) { if (apply_rotate) { input = rotate(input); } input = accel_fn(input, time); input.x *= sensitivity.x; input.y *= sensitivity.y; return input; } mouse_modifier() = default; }; } // rawaccel