STEAM GROUP
Payday 2 Mechanics PD2mech
STEAM GROUP
Payday 2 Mechanics PD2mech
11
IN-GAME
62
ONLINE
Founded
27 September, 2015
Skwuruhl 24 Jun, 2016 @ 7:42pm
ADS Sensitivity Equation Is Wrong
Preface that yours is technically correct, but only for a 360 degree rotation. But you'll never do this while ADS so it's sort of moot. Granted it's much better than vanilla.

Your equation for sensitivity is off by up to ~19%, deviation increasing with FOV and smaller mouse movements. Using your example of 1.4 FOV multiplier and 65 hipfire and 45 ADS, you suggest that ADS sensitivity should be 59% of hipfire sensitivity. Using a correct FOV equation, it should be 50%.

http://i.imgur.com/Eci1gIY.png is basically your equation.

http://i.imgur.com/U5IOyia.png is the correct equation. Note that 3/4 is used because overkill made FOV in terms of a 4:3 aspect ratio.

In place of x approaching 0, you could just put a small number. 1 is 11.8% off, 0.1 is 0.15% off, and 0.01 is 0.0015% off.

I wish I knew my way around lua enough to give you the correct code, but yeah.

Edit: this maybe? I used 0.1 since idk how small I can make it before having to worry about loss of precision.

local fovMul = managers.user:get_setting( "fov_multiplier" ) sens = sens * ( math.atan ( 0.1 * math.tan ( math.atan ( math.tan ( ( currentState._equipped_unit:base():zoom() or 65 ) * ( fovMul+1 ) / 4) * (3 / 4) ) ) ) / math.atan ( 0.1 * math.tan ( math.atan ( math.tan ( 65*fovMul / 2 ) * ( 3 / 4 ) ) ) ) )

Edit: Way more optimized version:

local fovMul = managers.user:get_setting( "fov_multiplier" ) sens = sens * math.tan(math.rad((currentState._equipped_unit:base():zoom() or 65) * (fovMul+1) / 4)) / math.tan(math.rad(65 * fovMul / 2))

Simplified equation is simply tan(adsFOV/2)/tan(hipfireFOV/2)

Edit: forgot lua uses radians and not degrees, fixed the optmized version.
Last edited by Skwuruhl; 25 Jun, 2016 @ 11:33am
< >
Showing 1-5 of 5 comments
Frankelstner 25 Jun, 2016 @ 11:36am 
I'm having some trouble understanding the ordinary normalization right now. I've run some stuff with autohotkey, basically variations of p::DllCall("mouse_event", uint, 1, int, 100, int, 0), where I move the mouse some steps (100 in the case above) to the right.

My notes so far:

The vertical FOV does not change with aspect ratio. The horizontal FOV gets wider as the aspect ratio increases. The FOV slider increases both vertical and horizontal FOV the same. A 360° rotation contains 8000 units, regardless of resolution. More generally: A 360° rotation takes 8000/ratio units, where ratio is the current FOV divided by hipfire FOV. Number of units required to reach the edge of the screen (classic normalization vs none; FOV slider maxed, so the FOV values are actually 65*1.4 or FOV*1.2): 4:3 resolution (1024x768 and 1600x1200 yield the same results): 65: 1010 => 360° needs ~8 of these turns. This makes sense because the FOV is 91°, so each turn is 45.5°. 40: 1010 532 => 15 turns needed 20: 1010 266 => 30 turns needed 16:10 (1920x1200): 65: 1127 40: 1180 624 20: 1200 318 16:9 (1600x900 and 1920x1080, basically the same results): 65: 1190 40: 1293 680 20: 1330 351 For 4:3 resolution, everything is fine: The units to reach the edge of the screen remain constant, regardless of FOV. For other resolutions, the horizontal FOV does not match the expected values anymore (i.e. the game indeed seems to be based on 4:3 resolutions).

I'll try to sort this out before moving on to your tweaks. I do believe that the classic normalization should work as it does with 4:3 resolutions, i.e. it is correct for movement to the edge of the screen, and the validity for 360° turns should be just a byproduct of sorts.
Frankelstner 27 Jun, 2016 @ 1:10pm 
Most things fall into place now. I think a slider in the mod options should do the trick. Slider on the left means normalized for infinitely small movement. Slider on the right means normalized for movement until the edge of the screen. Slider in the middle means normalized for movement 50% across the screen.

There are also other interesting things. It's not possible to use a single value to normalize both x and y, so two values are required (I've found it rather easy to set up). Somewhat related is the problem that, e.g. for a 16:10 setup, when firing from the hip, the input to move to the right edge of the screen is not 1.6 times the input to move to the top. So the vertical sensitivity could be altered even for hipfire.

edit: The normalization is: atan(x tan ads) / atan(x tan hip)
where ads and hip are half of either the horizontal or vertical FOV, and x is the percentage of how far across the screen you need to turn until the required input is the same for any FOV. E.g. x=0.1 means that no matter the FOV, it takes the same amount of input to turn from the old crosshairs to a point that is 10% of the (half) screen away.

Special cases:

Classic normalization (x=1): ads/hip
Delta normalization (x=0): tan(ads)/tan(hip)
Last edited by Frankelstner; 27 Jun, 2016 @ 2:05pm
Frankelstner 28 Jun, 2016 @ 3:56pm 
This normalizes horizontal and vertical movement independently. Sensitivity adjusted according to aspect ratio. It also changes the function of the aiming sensitivity slider in the options to x from above. Leftmost position is your suggested change, rightmost is classic normalization.

function MenuManager:set_mouse_sensitivity(zoomed) self._controller:get_setup():get_connection("look"):set_multiplier(self._look_multiplier) -- Handle sensitivity via _pc_look_function. managers.controller:rebind_connections() local sens = managers.user:get_setting("camera_sensitivity") local xsens = sens local ysens = sens local s = math.clamp(managers.user:get_setting("camera_zoom_sensitivity"),0.001,1) * 3/4 if zoomed and alive(managers.player:player_unit()) then local currentState = managers.player:player_unit():movement():current_state() if alive(currentState._equipped_unit) then local fovMul = managers.user:get_setting("fov_multiplier") local aspectRatio = managers.viewport:aspect_ratio() local hip = math.tan(32.5*fovMul) local ads = math.tan((currentState._equipped_unit:base():zoom() or 65)*(fovMul+1)/4) xsens = xsens * math.atan(s * aspectRatio * ads) / math.atan(s* aspectRatio * hip) ysens = ysens * math.atan(s * ads) / math.atan(s * hip) end end MenuManager.xsens = xsens MenuManager.ysens = ysens end function FPCameraPlayerBase:_pc_look_function(stick_input, stick_input_multiplier, dt) mvector3.set_x(stick_input, (MenuManager.xsens or 1)*stick_input.x) mvector3.set_y(stick_input, (MenuManager.ysens or 1)*stick_input.y) return stick_input.x, stick_input.y end
Skwuruhl 28 Jun, 2016 @ 4:07pm 
Thank you. It's amazing that so many devs even mess up x/y sensitivity, as if ADS weren't bad enough. At least in payday it's fixable. I didn't really know where to go from tan(ads/2)/tan(hip/2), wasn't even aware the the different x/y sensitivities.
Frankelstner 29 Jun, 2016 @ 12:44am 
My notes:
Let fv = FOV/2 in whatever context it may pop up (it makes sense, because it has half the letters, 1.5 rounded to 2). All calculations revolve around this identity: tan fv = w/d where w is the half the (physical) width of the screen (h is also possible, referring to height) d is the (physical) distance of your head to the screen. It's rather abstract and thus eliminated from equations. The FOV slider and zoom in the game refer to the horizontal FOV given a 4:3 ratio. The game is set up in such a way that the vertical FOV is independent of aspect ratio. So the first step for the horizontal FOVs of other aspect ratios is to calculate the vertical fv (fvy): Let fv43 = horizontal fv given 4:3 ratio, i.e. essentially what the FOV slider and weapon sights modify. w43 and h43 = physical width or height of a 4:3 screen. These are abstract quantities on their own because they could refer to any screen width or height. tan fv43 = w43/d <=> 1/d = tan(fv43)/w43 => tan fvy = h43/d = h43/w43 tan(fv43) = 3/4 tan(fv43) fvy is the same regardless of aspect ratio. It depends solely on fv43, and fv43 is known, so fvy is known. fv43 is the only quantity that the user can influence, by moving the slider or using weapon sights. ==> tan fvy = 3/4 tan(fv43) Calculate the horizontal fv (fvx): Start with arbitrary width w, height h and distance d. 1/d = tan(fvy)/h = 3/4 tan(fv43)/h => tan fvx = w/d = w/h 3/4 tan(fv43) fvx depens on fv43 and the aspect ratio (w/h), both of which are known. ==> tan fvx = w/h 3/4 tan(fv43) Ingame slider and zoom: The slider and zoom change fv43. Hipfire: fv43 = 65 degrees, multiplied by 1.0 to 1.4 depending on slider. Sights: fv43 = 63, 60, 55, ... 20 degrees, multiplied by 1.0 to 1.2 depending on slider. Normalization: Let inp = mouse input, translated into degrees hip = fv when not using sights ads = fv when using sights Consider a point exactly halfway between the center and right edge of the screen. Given an fv of 40 degrees, what is the rotation required to aim at that point? tan inp = s/d where s is the distance from center (50% of the screen width in this case) tan fv = w/d => tan inp / tan fv = s/w w is unnecessary if we treat s as a dimensionless percentage or fraction. Drop w. s = tan inp / tan fv <=> inp = atan(s tan fv) So in this case, inp = 22.76 degrees. Normalization works as follows: For a certain s, make sure that the required input to reach that s is the same regardless of FOV. inp_hip = atan(s tan hip) inp_ads = atan(s tan ads) inp_hip is the plain, unmodified input. We want to modify inp_hip so that it becomes inp_ads. inp_hip * norm = inp_ads <=> norm = inp_ads/inp_hip = atan(s tan ads) / atan(s tan hip) So just multiply the normal input by norm to get the ads input. Special cases: Classic normalization (screen edge; s=1): norm = ads/hip Delta normalization (infinitely small; s=0): atan(s tan ads) / atan(s tan hip) \s->0 -> 0/0 Hopital rule: d/dx atan(s) = 1/(1+s^2) d/dx atan(s c) = c/(1 + c^2 s^2) = tan(ads)/(1+ tan(ads)^2 s^2) / tan(hip) * (1+ tan(hip)^2 s^2) \ s->0 = tan(ads)/tan(hip) *(1+tan(hip)^2 s^2)/(1+tan(ads)^2 s^2) \ s->0 = tan(ads)/tan(hip) *(1+0) / (1+0) = tan(ads)/tan(hip) norm = tan(ads)/tan(hip) ==> norm = atan(s tan ads) / atan(s tan hip) Double normalization: x and y should be normalized independently. Reminder: tan fvx = 3/4 tan(fv43) w/h => fvx = atan(3/4 tan(fv43) w/h) tan fvy = 3/4 tan(fv43) => fvy = atan(3/4 tan(fv43)) norm = atan(s tan ads) / atan(s tan hip) Consider the case s=1, 16:10 with hip=65, ads=20 (slider at 1.0): hipx = atan(3/4 tan(65) 16/10) = 68.76 adsx = atan(3/4 tan(20) 16/10) = 23.59 hipy = atan(3/4 tan(65)) = 58.13 adsy = atan(3/4 tan(20)) = 15.27 norm = ads/hip normx = adsx / hipx = atan(3/4 tan(20) 16/10) / atan(3/4 tan(65) 16/10) = 0.34 normy = adsy / hipy = atan(3/4 tan(20)) / atan(3/4 tan(65)) = 0.26 It cannot work out with a common normalizer. There is no harm doing independent normalization, so do it. Shifting FOV proportions: The data from double normalization also shows that the fvy/fvx ratios change: hipx/hipy = 68.76/58.13 = 1.18 adsx/adsy = 23.59/15.27 = 1.54 For small FOVs, the ratio approaches the aspect ratio (16:10, i.e. 1.6). For FOVs close to 180, the ratio approaches 1.0. Consider classic normalization with 16:10: For small FOVs, it takes 16 units to reach the right edge of the screen and 10 units to reach the top. For large FOVs, it takes the same number of units to reach either edge. The most sensible option would be the aspect ratio as the goal. The curious part is that hipfire is then affected. The x sensitivity should probably stay unchanged to have the least impact. The y sensitivity should adjust for hipfire: It takes roughly the same number of units to reach either edge. x should take more units than y, but x sensitivity is fixed. So y must need less units. The y sensitivity should be increased. I haven't had any personal problems with hipfire however. If I did change this, I would probably have to add a separate y sensitivity multiplier option somewhere anyway. So do not adjust. Summary: fv = FOV/2 Only fv43 is controlled by the user: Hipfire: fv43 = 65 degrees, multiplied by 1.0 to 1.4 depending on slider. Sights: fv43 = 63, 60, 55, ... 20 degrees, multiplied by 1.0 to 1.2 depending on slider. w/h = aspect ratio tan fvx = 3/4 tan(fv43) w/h tan fvy = 3/4 tan(fv43) => Let hip = 65/2 * slider ads = 63, 60 ... 20 * (slider+1)/2 tan hipx = 3/4 tan(hip) w/h tan hipy = 3/4 tan(hip) tan adsx = 3/4 tan(ads) w/h tan adsy = 3/4 tan(ads) Let user define an s between 0.001 and 1, which is the distance from the center of the screen at which the normalization should work perfectly. A value of 0.001 essentially corresponds to a single pixel offset for a display with 1920 width. normx = atan(s tan adsx) / atan(s tan hipx) = atan(s 3/4 w/h tan(ads)) / atan(s 3/4 w/h tan(hip)) normy = atan(s tan adsy) / atan(s tan hipy) = atan(s 3/4 tan(ads)) / atan(s 3/4 tan(hip)) When using sights, the x input is multiplied by normx, y input by normy.
< >
Showing 1-5 of 5 comments
Per page: 1530 50