176 karma · joined July 23, 2022
And obviously it would only apply for the underdamped case.
And even if you are to calculate the integration factors based on dynamic frame rates, they only need to be computed just once per frame for all objects that share the same damping configuration.
m * x’’ + c * x’ + k * x = f(t)
See https://web.archive.org/web/20230604215211/https://esporttoy...https://www.desmos.com/calculator/mu80ttc9aa
function sprung_response(t,pos,vel,k,c,m)
local decay = c/2/m
local omega = math.sqrt(k/m)
local resid = decay*decay-omega*omega
local scale = math.sqrt(math.abs(resid))
local T1,T0 = t , 1
if resid<0 then
T1,T0 = math.sin( scale*t)/scale , math.cos( scale*t)
elseif resid>0 then
T1,T0 = math.sinh(scale*t)/scale , math.cosh(scale*t)
end
local dissipation = math.exp(-decay*t)
local evolved_pos = dissipation*( pos*(T0+T1*decay) + vel*( T1 ) )
local evolved_vel = dissipation*( pos*(-T1*omega^2) + vel*(T0-T1*decay) )
return evolved_pos , evolved_vel
end
For anticipation, just add an extra initial velocity in the opposite direction and let the closed-form solution handle the time evolution. The main trick here is to keep both position and velocity as state. There is no need to “step through the simulation”.The following is the complete solution in Lua:
function sprung_response(t,pos,vel,k,c,m)
local decay = c/2/m
local omega = math.sqrt(k/m)
local resid = decay*decay-omega*omega
local scale = math.sqrt(math.abs(resid))
local T1,T0 = t , 1
if resid<0 then
T1,T0 = math.sin( scale*t)/scale , math.cos( scale*t)
elseif resid>0 then
T1,T0 = math.sinh(scale*t)/scale , math.cosh(scale*t)
end
local dissipation = math.exp(-decay*t)
local evolved_pos = dissipation*( pos*(T0+T1*decay) + vel*( T1 ) )
local evolved_vel = dissipation*( pos*(-T1*omega^2) + vel*(T0-T1*decay) )
return evolved_pos , evolved_vel
end
[0] https://esporttoys.pages.dev/2022/11/21/dampedAlso, mouse_event is just a wrapper around what's basically SendInput(mouse)
The gist of it is that the solution to a linearly damped particle is a linear system, so the x and y components can be calculated completely independently and the analytic solution is just an exponential of time.
It is a special case of the timestep-independent damped harmonic oscillator, which I previously wrote a blogpost about [10].
Namely it is the "unsprung" special case under "Overdamped"
The very same formulation is also what I used to implement LibreScroll[11] to add inertial scrolling to any mouse.
[0] https://github.com/EsportToys/TPMouse
[1] https://www.reddit.com/r/Trackballs/comments/ym9q2t/tpmouse_...
winget install Logitech.LGSIn the meantime, for the current version of TPMouse on the dev branch, to change the remapping of arrows and mouse buttons, you need to edit `keybinds.au3` in three spots:
1. the virtual-key constant at the static array at the top
2. the hotkey string at the static array at the top
3. (this is really poor UX I know) in the static struct declaration inside the respective callback function, change the referenced virtual-key constant.
The list of virtual key codes can be found in `vkeys.au3`:
https://github.com/EsportToys/TPMouse/blob/dev/vkeys.au3
So for example, to remap mouse1 from F to A, I need to do the following:
Line 10:
<<< before >>>
$mb1 = [ $VK_F , '{f}' , callback_f ] , _
<<< after >>>
$mb1 = [ $VK_A , '{a}' , callback_f ] , _
Lin 68: <<< before >>>
Local Static $struct = DllStructCreate('ushort MakeCode;ushort Flags;ushort VKey;'), $vkey = DllStructSetData($struct,'VKey',$VK_F)
<<< after >>>
Local Static $struct = DllStructCreate('ushort MakeCode;ushort Flags;ushort VKey;'), $vkey = DllStructSetData($struct,'VKey',$VK_A)
As for changing the activation hotkeys, if you wish to use a different modifier other than CapsLk, change the vkey code on line 74, 90, and line 107 of TPMouse.au3; for example, to change it from CapsLk to Alt:Line 74:
<<< before >>>
Case $VK_CAPS
<<< after >>>
Case $VK_ALT
Line 90: <<< before >>>
If $VK_Q = $struct.VKey And Not ( $sks($VK_CAPS) Or ($sks($VK_LSHIFT) And $sks($VK_RSHIFT)) ) Then Return
<<< after >>>
If $VK_Q = $struct.VKey And Not ( $sks($VK_ALT) Or ($sks($VK_LSHIFT) And $sks($VK_RSHIFT)) ) Then Return
Line 107: <<< before >>>
If $sks($VK_CAPS) Or ($sks($VK_LSHIFT) And $sks($VK_RSHIFT)) Then
<<< after >>>
If $sks($VK_ALT) Or ($sks($VK_LSHIFT) And $sks($VK_RSHIFT)) ThenAlthough it didn't get as much attention as my other utility, it's actually the app that I'm most happy about having made.
I thought that needing to choose out of four quadrants at once is a bit overwhelming on cognitive load for something that is meant to be a subconscious extension of your hands, so instead I just unified it with the arrow keys as a "shrink in this direction" choice rather than "choose one of four quadrants".
Changing it to bisection/binary partitioning also means that don't need any visual aids as it's exceedingly simple to see which edge your target is the closest to.
Consider the following square matrix:
TSLA APPL GOOG MSFT
Alice | 100 5 0 1
Bob | 0 30 100 5
Carol | 2 2 2 2
Dan | 0 0 0 1000
An input vector of stock prices gives an output vector of net worths. However, that is about the only way you can use this matrix. You cannot transform the table arbitrarily and still have it make sense, such as applying a rotation matrix -- it is nonsensical to speak of a rotation from Tesla-coordinates to Google-coordinates. The input and output vectors lacks tensor transformation symmmetries, so they are not tensors.This is also why Principal Component Analysis and other data science notions in the same vein are pseudoscience (unless you evaluate the logarithm of the quantities, but nobody seems to recognize the significance of unit dimensions and multiplicative vs additive quantities)
Basically, it is an independent subscriber to RawInput messages that only keeps track of whether or not to send three-finger drag, and posts emulated mouse messages using SendInput. I have a few other scripts that each run as independent userland processes that only monitors their own trigger and nothing else.
Tangentially, my TPMouse[1] script implemented inertia in a framerate-independent way so that it uses very little resource while having perfect simulation stability.
A previous discussion where I explained the analytic derivation for this low-resource exact-solution damped inertia can be seen in [10]
[0] https://github.com/EsportToys/PrecisionThreeFingerDrag/blob/...
[1] https://github.com/EsportToys/TPMouse
[10] https://old.reddit.com/r/Trackballs/comments/ym9q2t/tpmouse_...
Basically, any application that uses the Raw Input API can request to receive raw device events even when the application is not running in the foreground, by using the RIDEV_INPUTSINK flag.
The app will then receive every raw device input packet that the hardware sends to the system, replete with timestamps[0] and your mouse position when the event happened[1].
In the case of keyboards it would provide the virtuak-key codes and scancodes[10].
Rawinput is used by modern FPS game like Valorant[11], so if you leave it running in the background it may potentially be able to observe your every single keystroke while you use your browser, enter passwords, etc.
TPMouse, my opensource trackball-emulation script that lets you use the homerow as a trackball for your cursor[100], uses Raw Input with the RIDEV_INPUTSINK option so that it runs entirely in userspace without needing to hook to low level drivers.
It is certainly a double-edged sword -- for open source it's a convenience blessing since what you're running can be inspected directly, but in the case of close-sourced games like Valorant you're relying on your trust of Riot Games's intentions and competence.
[0] https://learn.microsoft.com/en-us/windows/win32/api/winuser/...
[1] https://learn.microsoft.com/en-us/windows/win32/api/winuser/...
[10] https://learn.microsoft.com/en-us/windows/win32/api/winuser/...
[11] https://playvalorant.com/en-gb/news/game-updates/valorant-pa...