Skip to content

Commit 5b64790

Browse files
authored
Merge pull request #65 from JacobPalecki/Directional
Directionality Features + Graph Fidelity
2 parents c1cd666 + 6311b56 commit 5b64790

37 files changed

+1439
-456
lines changed

common/accel-base.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ namespace rawaccel {
1717
double speed_cap = 0;
1818
};
1919

20+
struct domain_args {
21+
vec2d domain_weights = { 1, 1 };
22+
double lp_norm = 2;
23+
};
24+
2025
template <typename Func>
2126
struct accel_val_base {
2227
bool legacy_offset = false;

common/accel-motivity.hpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,28 +55,40 @@ namespace rawaccel {
5555
inline void fill(si_pair* lookup) const
5656
{
5757
double lookup_speed = 0;
58+
double integral_interval = 0;
5859
double gain_integral_speed = 0;
60+
double gain_integral_speed_prev = 0;
5961
double gain = 0;
6062
double intercept = 0;
6163
double output = 0;
64+
double output_prev = 0;
6265
double x = -2;
6366

67+
double logarithm_interval = 0.01;
68+
double integral_intervals_per_speed = 10;
69+
double integral_interval_factor = pow(10, logarithm_interval) / integral_intervals_per_speed;
70+
6471
lookup[0] = {};
6572

6673
for (size_t i = 1; i < LUT_SIZE; i++)
6774
{
68-
x += 0.01;
75+
x += logarithm_interval;
6976

77+
// Each lookup speed will be 10^0.01 = 2.33% higher than the previous
78+
// To get 10 integral intervals per speed, set interval to 0.233%
7079
lookup_speed = pow(10, x);
80+
integral_interval = lookup_speed * integral_interval_factor;
7181

7282
while (gain_integral_speed < lookup_speed)
7383
{
74-
gain_integral_speed += 0.001;
84+
output_prev = output;
85+
gain_integral_speed_prev = gain_integral_speed;
86+
gain_integral_speed += integral_interval;
7587
gain = operator()(gain_integral_speed);
76-
output += gain * 0.001;
88+
output += gain * integral_interval;
7789
}
7890

79-
intercept = output - gain * lookup_speed;
91+
intercept = output_prev - gain_integral_speed_prev * gain;
8092

8193
lookup[i] = { gain, intercept };
8294
}

common/rawaccel-settings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ namespace rawaccel {
2424
vec2<accel_args> argsv;
2525
vec2d sens = { 1, 1 };
2626
vec2d dir_multipliers = {};
27+
domain_args domain_args = {};
28+
vec2d range_weights = { 1, 1 };
2729
milliseconds time_min = DEFAULT_TIME_MIN;
2830
wchar_t device_id[MAX_DEV_ID_LEN] = {0};
2931
};

common/rawaccel-version.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22

33
#define RA_VER_MAJOR 1
4-
#define RA_VER_MINOR 3
4+
#define RA_VER_MINOR 4
55
#define RA_VER_PATCH 0
66

77
#define RA_MIN_OS "Win7"
@@ -20,7 +20,7 @@ namespace rawaccel {
2020
};
2121

2222
#ifndef _KERNEL_MODE
23-
inline constexpr version_t min_driver_version = { 1, 3, 0 };
23+
inline constexpr version_t min_driver_version = { 1, 4, 0 };
2424
#endif
2525

2626
}

common/rawaccel.hpp

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,90 @@ namespace rawaccel {
225225
accelerator() = default;
226226
};
227227

228+
struct weighted_distance {
229+
double p = 2.0;
230+
double p_inverse = 0.5;
231+
bool lp_norm_infinity = false;
232+
double sigma_x = 1.0;
233+
double sigma_y = 1.0;
234+
235+
weighted_distance(domain_args args)
236+
{
237+
sigma_x = args.domain_weights.x;
238+
sigma_y = args.domain_weights.y;
239+
if (args.lp_norm <= 0)
240+
{
241+
lp_norm_infinity = true;
242+
p = 0.0;
243+
p_inverse = 0.0;
244+
}
245+
else
246+
{
247+
lp_norm_infinity = false;
248+
p = args.lp_norm;
249+
p_inverse = 1 / args.lp_norm;
250+
}
251+
}
252+
253+
double calculate(double x, double y)
254+
{
255+
double abs_x = fabs(x);
256+
double abs_y = fabs(y);
257+
258+
if (lp_norm_infinity)
259+
{
260+
return abs_x > abs_y ? abs_x : abs_y;
261+
}
262+
263+
double x_scaled = abs_x * sigma_x;
264+
double y_scaled = abs_y * sigma_y;
265+
return pow(pow(x_scaled, p) + pow(y_scaled, p), p_inverse);
266+
}
267+
268+
weighted_distance() = default;
269+
};
270+
271+
struct direction_weight {
272+
double diff = 0.0;
273+
double start = 1.0;
274+
bool should_apply = false;
275+
276+
direction_weight(vec2d thetas)
277+
{
278+
diff = thetas.y - thetas.x;
279+
start = thetas.x;
280+
281+
if (diff != 0)
282+
{
283+
should_apply = true;
284+
}
285+
else
286+
{
287+
should_apply = false;
288+
}
289+
}
290+
291+
inline double atan_scale(double x, double y)
292+
{
293+
return M_2_PI * atan2(fabs(y), fabs(x));
294+
}
295+
296+
double apply(double x, double y)
297+
{
298+
return atan_scale(x, y) * diff + start;
299+
}
300+
301+
direction_weight() = default;
302+
};
303+
228304
/// <summary> Struct to hold variables and methods for modifying mouse input </summary>
229305
struct mouse_modifier {
230306
bool apply_rotate = false;
231307
bool apply_accel = false;
232308
bool combine_magnitudes = true;
233309
rotator rotate;
310+
weighted_distance distance;
311+
direction_weight directional;
234312
vec2<accelerator> accels;
235313
vec2d sensitivity = { 1, 1 };
236314
vec2d directional_multipliers = {};
@@ -257,6 +335,10 @@ namespace rawaccel {
257335

258336
accels.x = accelerator(args.argsv.x, args.modes.x, luts.x);
259337
accels.y = accelerator(args.argsv.y, args.modes.y, luts.y);
338+
339+
distance = weighted_distance(args.domain_args);
340+
directional = direction_weight(args.range_weights);
341+
260342
apply_accel = true;
261343
}
262344

@@ -276,9 +358,15 @@ namespace rawaccel {
276358
milliseconds time = time_supp();
277359

278360
if (combine_magnitudes) {
279-
double mag = sqrtsd(movement.x * movement.x + movement.y * movement.y);
361+
double mag = distance.calculate(movement.x, movement.y);
280362
double speed = mag / time;
281363
double scale = accels.x.apply(speed);
364+
365+
if (directional.should_apply)
366+
{
367+
scale = (scale - 1)*directional.apply(movement.x, movement.y) + 1;
368+
}
369+
282370
movement.x *= scale;
283371
movement.y *= scale;
284372
}

doc/Guide.md

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ Visit the [Releases page](https://github.com/a1xd/rawaccel/releases) and navigat
1919
The Raw Accel driver and GUI's workings and exposed parameters are based on our understanding of mouse acceleration. Our understanding includes the concepts of "gain", "whole vs by component", and "anisotropy." For clarity, we will outline this understanding here. Those uninterested can skip to Features below.
2020

2121
### Measurements from Input Speed
22-
Raw Accel, like any mouse modification program, works by acting on a passed-in (x,y) input in order to pass back out an (x,y) output. The GUI program creates charts by feeding a set of (x,y) inputs to the driver code to receive a set of (x,y) outputs. The following measurements, available as charts in Raw Accel, are then found from the outputs:
22+
Raw Accel, like any mouse modification program, works by acting on a passed-in (x,y) input in order to pass back out an (x,y) output. The GUI program creates charts by feeding a set of (x,y) inputs and times to the driver code to receive a set of (x,y) outputs. The following measurements, available as charts in Raw Accel, are then found from the outputs:
2323

24-
- **Sensitivity**: The ratio of the output speed to the input speed. The "sensitivity" parameter in the program is a multiplier used on the post-calculation output vector.
24+
- **Sensitivity**: The ratio of the output speed to the input speed. The "sensitivity multiplier" parameter in the program is a multiplier used on the post-calculation output vector.
2525
- **(Output) Velocity**: The speed of the final output vector. The output vs input velocity curve is perhaps the most important relationship in a particular setup because it directly describes the output for any given input. (We use "speed" and "velocity" interchangeably, and are aware of the difference elsewhere.)
2626
- **Gain**: The slope of the output vs input velocity curve. It answers the question: "if I move my hand a little faster, how much faster will my cursor move?" The relationship between gain and sensitivity is that if gain is continuous, so is sensitivity. The reverse is not necessarily true, so keeping the gain "nice" ensures "nice" sensitivity but not vice versa.
2727
- For the mathematically inclined: for input speed "v" and Output Velocity f(v), Sensitivity is f(v)/v and Gain is f'(v) = d/dv(f(v)).
@@ -40,17 +40,44 @@ Then our input speed is sqrt(30^2 + 40^2) = 50 counts/ms. Our accelerated sensit
4040
![SensVelocityGainExample](images/accel_readme_example.png)
4141

4242
### Horizontal and Vertical
43-
If you wish, horizontal and vertical components of a mouse movement can be treated completely separately in Raw Accel. In the above example, they are not treated separately; rather they are "combined" by using the magnitude if the input vector: *sqrt(30^2 + 40^2) = 50 counts/ms*. This is called "Whole" application because the whole speed of the input is used. Application styles include:
43+
Due to the mechanics of holding a mouse on a desk, users generally move their mouses horizontally (left and right) differently than they move them vertically (forward and back), with more freedom for the wrist and\or arm to move the mouse horizontally than vertically. A user may then desire for various aspects of their output to change based on the direction of their input. For settings which allow this we have co-opted the term "anisotropy", which is "the property of a material which allows it to change or assume different properties in different directions."
4444

45-
- Whole: magnitude of vector is input to sensitivity calculation, and applied to whole vector, as in example above.
46-
- (out_x, out_y) = (in_x\*sens_x, in_y\*sens_y) \* f(sqrt(in_x^2 + in_y^2)), where f(v) is our sensitvity function
47-
- Separate horizontal and vertical sensitivites still feel great in this mode.
48-
- By Component: The horizontal components are separated and each is given as input to the sensitivity calculation to multiplied by itself before being recombined at output.
45+
In the above "example" section, the x and y inputs are not treated separately; rather they are "combined" by using the magnitude if the input vector: *sqrt(30^2 + 40^2) = 50 counts/ms*. This is called "Whole" application because the whole speed of the input is used and the result is applied to the whole vector. Application styles include:
46+
47+
#### ***Whole***
48+
In this case, the magnitude of the input vector is input to sensitivity calculation, and applied to whole vector, as in example above.
49+
- (out_x, out_y) = (in_x\*sens_x, in_y\*sens_y) \* f(sqrt(in_x^2 + in_y^2)), where f(v) is our sensitivity function
50+
51+
Separate horizontal and vertical sensitivites still feel great in this mode. (For the mathematically inclined, that's because differing horizontal and vertical sensitivities create a vector field without curl or other oddities.)
52+
53+
There are anisotropic settings for whole mode.
54+
- **Range**. This scales the range of the sensitivity curve around 1 for the horizontal or vertical direction.
55+
- If a given curve varies from 1 to 2 sensitivity, then a range_y of 0.5 would mean that vertical movements vary from 1 to 1.5 sensitivity instead.
56+
- **Domain**. This scales the domain of curve around 0 for the horizontal or vertical direction.
57+
- If a given curve has an offset at 5 count/ms and a cap that is hit at 15 counts/ms, then a domain_y of 2 would mean that vertical movements hit the offset at 2.5 counts/ms and the cap at 7.5 counts/ms instead.
58+
- **Lp Norm**. The distance calculation can be generalized to ((in_x)^p + (in_y)^p)^(1/p)), bringing the calculation into [Lp space](https://en.wikipedia.org/wiki/Lp_space).
59+
- p = 2 is then the "real world" value, yielding the pythagorean theorem as the distance calculation.
60+
- Increasing p makes distances for diagonal movements (where in_x and in_y are close) smaller, and increases the dominance of the larger of the two in determining the distance.
61+
- We recommend almost everyone leave this at 2.
62+
63+
![AnisotropyExample](images/anisotropy_example.png)
64+
65+
With all anisotropic settings considered, the full formula looks like:
66+
- (out_x, out_y) = (in_x\*sens_x, in_y\*sens_y) \* ((f(((domain_x\*in_x)^p + (domain_y\*in_y)^p)^(1/p)) - 1) \* ((2 / Pi) \* arctan(abs(in_y/in_x)) \* (range_y - range_x) + range_x) + 1, where f(v) is our sensitivity function
67+
68+
This can be more easily understood as
69+
- (out_x, out_y) = (in_x\*sens_x, in_y\*sens_y) \* ((f( domain-weighted lp-space speed) - 1) \* (directional weight) + 1), where f(v) is our sensitivity function
70+
71+
This formula gaurantees the smooth transition from the horizontal to vertical curve and vice versa as the user moves their hand diagonally.
72+
73+
#### ***By Component***
74+
In this case, the horizontal components are separated and each is given as input to the sensitivity calculation to multiplied by itself before being recombined at output.
4975
- (out_x, out_y) = (in_x \* f(in_x) \* sens_x, in_y \* f(in_y) \* sens_y))
50-
- Anisotropy: This is a sub-mode of By Component in which the x and y can have separate sensitivity curves.
51-
- (out_x, out_y) = (in_x \* f(in_x) \* sens_x, in_y \* g(in_y) \* sens_y)) where g(v) is some other sensitivity function.
76+
- You can also do: (out_x, out_y) = (in_x \* f(in_x) \* sens_x, in_y \* g(in_y) \* sens_y)) where g(v) is some other sensitivity function.
77+
78+
All anisotropic effects for By Component mode can be achieved by setting different x and y curves.
5279

53-
The authors of this program feel that Whole is the best style for most users, but that users who play games which have very separate horizontal and vertical actions to manage (such as tracking an enemy and controlling recoil) might benefit from trying By Component or Anisotropy.
80+
The authors of this program feel that Whole is the best style for most users, but that users who play games which have very separate horizontal and vertical actions to manage (such as tracking an enemy and controlling recoil) might benefit from trying By Component. By Component may seem more flexible, but it is precisely the restrictions imposed by Whole (no curl) that make it smoother.
5481

5582

5683
## Features
@@ -66,14 +93,14 @@ Our acceleration functions generally have sensitivity functions that start at 1
6693

6794
Weight is primarily a quick and dirty way to test a new curve. It also can be given a negative value to allow negative acceleration. Most acceleration styles could just change the parameters to have the same affect as setting a weight. Some curves, like the logarithm style, can achieve a greater range of shapes by changing weight.
6895

69-
### By Component & Anisotropy
70-
See "Horizontal and Vertical" in the philosophy section to understand what these do.
96+
### Anisotropy
97+
See "Horizontal and Vertical" in the philosophy section to understand what these options do.
7198

7299
### Last Mouse Move
73100
The Raw Accel GUI reads the output of the raw input stream, and thus the output of the Raw Accel Driver, and displays on the graphs red points corresponding to the last mouse movements. These calulations should be fast and your graph responsive, but it comes at the cost of higher CPU usage due to needing to refresh the graph often. This feature can be turned off in the "Charts" menu.
74101

75102
### Scale by DPI and Poll Rate
76-
This option does not scale your acceleration curve in any way. Rather, it scales the set of points used to graph your curve, and shows you a window of input speed relevant for your chosen DPI and Poll Rate. The poll rate is also used to determine the Last Mouse Move points and therefore should be set for accuracy in that measurement.
103+
This option does not scale your acceleration curve in any way. Rather, DPI scales the set of points used to graph your curve, and shows you a window of input speed relevant for your chosen DPI. The poll rate is used as a safeguard for the Last Mouse Move points and therefore should be set for accuracy in that measurement.
77104

78105
## Acceleration Styles
79106
The examples of various types below show some typical settings, without a cap or offset, for a mouse at 1200 DPI and 1000 hz.

doc/images/anisotropy_example.png

60.6 KB
Loading

grapher/Constants/Constants.cs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,10 @@ public static class Constants
1414
public const int DefaultPollRate = 1000;
1515

1616
/// <summary> Resolution of chart calulation. </summary>
17-
public const int Resolution = 100;
17+
public const int Resolution = 500;
1818

1919
/// <summary> Multiplied by DPI over poll rate to find rough max expected velocity. </summary>
20-
public const double MaxMultiplier = 85;
21-
22-
/// <summary> Ratio of max (X, Y) used in "by component" calulations to those used in "whole vector" calculations. </summary>
23-
public const double XYToCombinedRatio = 1.4;
20+
public const double MaxMultiplier = .05;
2421

2522
/// <summary> Separation between X and Y active value labels, in pixels. </summary>
2623
public const int ActiveLabelXYSeparation = 2;
@@ -46,6 +43,12 @@ public static class Constants
4643
/// <summary> Horizontal separation between left side of single dropdown and left side of labels beneath dropdown </summary>
4744
public const int DropDownLeftSeparation = 10;
4845

46+
/// <summary> Height of sensitivity chart when displayed alone. </summary>
47+
public const int SensitivityChartAloneHeight = 450;
48+
49+
/// <summary> Height of sensitivity chart when displayed alongside Velocity and Gain charts. </summary>
50+
public const int SensitivityChartTogetherHeight = 328;
51+
4952
/// <summary> Width of charts when widened </summary>
5053
public const int WideChartWidth = 723;
5154

@@ -61,8 +64,14 @@ public static class Constants
6164
/// <summary> Vertical placement of write button above bottom of sensitivity graph </summary>
6265
public const int ButtonVerticalOffset = 60;
6366

67+
/// <summary> Padding between directionality title and containing panel </summary>
68+
public const int DirectionalityTitlePad = 8;
69+
6470
public const float SmallButtonSizeFactor = 0.666f;
6571

72+
/// <summary> Number of divisions between 0 and 90 degrees for directional lookup. For 19: 0, 5, 10... 85, 90.</summary>
73+
public const int AngleDivisions = 19;
74+
6675
/// <summary> Format string for shortened x and y textboxes. </summary>
6776
public const string ShortenedFormatString = "0.###";
6877

@@ -111,6 +120,12 @@ public static class Constants
111120
/// <summary> Default name of settings file. </summary>
112121
public const string DefaultSettingsFileName = @"settings.json";
113122

123+
/// <summary> Text to direcitonality panel title when panel is closed. </summary>
124+
public const string DirectionalityTitleClosed = "Anisotropy \u25BC";
125+
126+
/// <summary> Text to direcitonality panel title when panel is open. </summary>
127+
public const string DirectionalityTitleOpen = "Anisotropy \u25B2";
128+
114129
/// <summary> Style used by System.Double.Parse </summary>
115130
public const NumberStyles FloatStyle = NumberStyles.Float | NumberStyles.AllowThousands;
116131

0 commit comments

Comments
 (0)