-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Description
The simulation in FrictionSimulation doesn't correctly model the physics it's described as modeling, when constantDeceleration is nonzero. This is the simulation used for fling-scrolling in BouncingScrollPhysics / BouncingScrollSimulation, which is used by default on macOS.
(On iOS we also use BouncingScrollSimulation by default, but with a zero constantDeceleration, which doesn't trigger this issue.)
This has two consequences:
-
I suspect that the actual macOS behavior this simulation is intended to match does correctly model the physics. If so, then this is a fidelity issue.
-
The physically-incorrect behavior is not ballistic/restartable. (For context on that invariant, see Scroll protocol assumes ballistic scroll physics, and ClampingScrollPhysics isn't ballistic #120338.) The excess deceleration compared to the real physics starts at zero and grows over time, so restarting the simulation makes it decelerate less, and go farther in total.
- This is the inverse of the problem seen with ClampingScrollSimulation / on Android and other non-Apple platforms, in that other issue.
When the simulation is restarted very frequently, its behavior will closely approximate the real physics. If my suspicion above is correct, then this means frequent restarts actually give this scroll physics nearly the desired behavior, and it's when it doesn't get restarted that it behaves incorrectly.
Steps to Reproduce
I haven't yet tested this out empirically or compared to the native macOS scroll behavior, because I wanted to get this issue written down and posted for comparison with the related issues.
Here's why I say it doesn't correctly model the physics it's described as modeling:
- This class models "a particle affected by fluid drag." If the particle's current velocity is
$v$ , then one way that could behave is that its acceleration is$-kv$ , where$k$ is a drag coefficient.- (I think it's typically much steeper than that with real fluids, growing much faster than linearly in the velocity, like
$- k v^4$ . But let's leave that aside.)
- (I think it's typically much steeper than that with real fluids, growing much faster than linearly in the velocity, like
- With
constantDeceleration: 0, this is indeed whatFrictionSimulationdoes:-
dx(time)is_v * math.pow(_drag, time), or$x'(t) = v_0 e^{-kt}$ , where$v_0$ is the initial velocity. - Taking the derivative, we find
$x''(t) = -k v_0 e^{-kt}$ . - These satisfy
$x''(t) = - k x'(t)$ .
-
- With a positive
constantDeceleration… well, this parameter isn't really documented. But presumably it should do what its name says: it adds a constant amount of deceleration. - This means we should have
$x''(t) = -k x'(t) - c$ , where$c$ isconstantDeceleration. - But the actual deceleration is greater than that (meaning
$x''(t)$ is less than that):-
Now
dx(time)is_v * math.pow(_drag, time) - _constantDeceleration * time, or$x'(t) = v_0 e^{-kt} - c t$ . -
So
$x''(t) = - k v_0 e^{-kt} - c$ . -
But we were supposed to have an acceleration of
$-k x'(t) - c$ , which, calling it$x''_*$ , would be$x''_*(t) = - k v_0 e^{-kt} + k c t - c$ . -
So the deceleration is too big, by
$x''_*(t) - x''(t) = k c t$ , i.e._constantDeceleration * (-_dragLog) * time.
-
The crux of the bug is that we've made it so that the effect of constantDeceleration is to make the deceleration at time t be a constant amount more than it would otherwise have been at time t.
But the physics doesn't care how long the simulation has been running: from a physical perspective, adding a constant deceleration means the deceleration at time t should be a constant amount more than it would otherwise have been at velocity dx(t). (And position x(t), but this simulation isn't affected by position.)
Because over time the velocity and deceleration both decrease, this means that at time t we're starting from the deceleration that a simulation with zero constantDeceleration would have had at time t… when that simulation would have been going faster than this one is at time t, and therefore decelerating harder than it will when it later gets down to this one's current speed… which means that we take too large a deceleration as the baseline, and add constantDeceleration to that.