The flaw here in Android's VelocityTracker
class that if your velocity in the X direction exceeds the maxVelocity it is changed equal maxVelocity and the same for the Y. But, that means that if we are going at 20° angle and at a speed of 200, and our maxVelocity is 20. Our velocity is changed to be 20*sqrt (2) at a 45° angle. The correct answer is to scale the mXVelocity and mYVeloicity by the ratio of the actual velocity and maxVelocity.
My question is do I have to resort to two square-roots to fix this error?
Velocity is the direction and speed of an object. Changing the direction because of the maximum velocity was reached must be considered a defect. This also clearly causes a flaw in that a diagonal velocity is faster than the orthogonal one.
The problematic bit of code akin to:
mXVelocity = accumX < 0.0f ? Math.max(accumX, -maxVelocity) : Math.min(accumX, maxVelocity);
mYVelocity = accumY < 0.0f ? Math.max(accumY, -maxVelocity) : Math.min(accumY, maxVelocity);
To work around this I use:
tracker.computeCurrentVelocity(units); //maxVelocity
double velocityX = tracker.getXVelocity();
double velocityY = tracker.getYVelocity();
double actualVelocitySq = velocityX * velocityX + velocityY * velocityY;
double maxVelocitySq = maxVelocity * maxVelocity;
if (actualVelocitySq > maxVelocitySq) {
//double excessFactor = Math.sqrt(maxVelocitySq) / Math.sqrt(actualVelocitySq);
double excessFactor = Math.sqrt(maxVelocitySq/actualVelocitySq); //leewz's optimization
velocityX *= excessFactor;
velocityY *= excessFactor;
}
Is there some way to do avoid the double square-root? Or some other thing that otherwise fixes this screwy bug?
Update:
The answer seems to be scale both components according to the one component that exceeds the maximum velocity by the most. This isn't strictly scaling according to the actual velocity but it fixes the bulk of the problem with easy math.
double scale;
double vectorX = Math.abs(velocityX);
double vectorY = Math.abs(velocityY);
if (vectorX > maxVelocity) {
scale = maxVelocity / vectorX;
if (vectorY > maxVelocity) {
scale = Math.min(scale, maxVelocity / vectorY);
}
velocityX *= scale;
velocityY *= scale;
} else {
if (vectorY > maxVelocity) {
scale = maxVelocity / vectorY;
velocityX *= scale;
velocityY *= scale;
}
}
You can cut away
Math.sqrt(maxVelocitySq)
, because you knowmaxVelocitySq = maxVelocity * maxVelocity
. Even without that, you can use a singlesqrt()
by doing the division first:Mathematically, I believe taking a square root is required, but how you take the square root is still open to you. In particular, Fast Inverse Square Root works for your usecase. Here's the code using
fisr()
.Here's a Java implementation of FISR.
Caveat: FISR was designed for an older generation of Intel CPUs which were slow at floating point operations (especially division, which the above code still uses), not a JIT'd virtual machine (such as Java's) running on ARM (a common architecture for Android). Remember to profile your code to see if the square root cost is significant enough to optimize, and then to measure whether FISR gives a worthwhile improvement.