I'm trying to detect the speed of touch movement and i'm not always getting the results I'd expect. (added: Speed spikes around too much) Can anyone spot if i'm doing something funky or suggest a better way of doing it ?
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
self.previousTimestamp = event.timestamp;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];
CGPoint prevLocation = [touch previousLocationInView:self.view];
CGFloat distanceFromPrevious = distanceBetweenPoints(location,prevLocation);
NSTimeInterval timeSincePrevious = event.timestamp - self.previousTimestamp;
CGFloat speed = distanceFromPrevious/timeSincePrevious;
self.previousTimestamp = event.timestamp;
NSLog(@"dist %f | time %f | speed %f",distanceFromPrevious, timeSincePrevious, speed);
}
You could try (zero out distanceSinceStart and timeSinceStart in touchesBegan):
distanceSinceStart = distanceSinceStart + distanceFromPrevious;
timeSinceStart = timeSincestart + timeSincePrevious;
speed = distanceSinceStart/timeSinceStart;
which will give you the average speed since you started the touch (total distance/total time).
Or you could do a moving average of the speed, perhaps an exponential moving average:
const float lambda = 0.8f; // the closer to 1 the higher weight to the next touch
newSpeed = (1.0 - lambda) * oldSpeed + lambda* (distanceFromPrevious/timeSincePrevious);
oldSpeed = newSpeed;
You can adjust lambda to values near 1 if you want to give more weight to recent values.
The main problem is that the speed calculation will be very inaccurate when timeSincePrevious
is very small (a few milliseconds). To see this, let's say that timeSincePrevious
is 1ms. Then the calculated speed will be 0 if the distanceFromPrevious
is 0, and 1000 if the distanceFromZero
is 1.
For this reason I suggest the following value of lambda:
const float labmda = (timeSincePrevious>0.2? 1: timeSincePrevious/0.2);
That is to say, we use a tiny lambda when the timeSincePrevious
is small.
The filter suggestion might be ok, but it doesn't solve the problem: the peak will be smoothed out, but remain.
If you logged out the touch events, these peaks will look like a touch with very little time delta from previous (0.001215 ms), preceding by touch with large time delta.
distance = 17.269917, timeDelta = 0.016132, speed = 1070.504639 distance = 15.206906, timeDelta = 0.017494, speed = 869.251709 distance = 15.882380, timeDelta = 0.017583, speed = 903.297546 distance = 14.983324, timeDelta = 0.030101, speed = 497.771088 //low peak distance = 15.435349, timeDelta = 0.001215, speed = 12703.991211 //high peak! distance = 15.882380, timeDelta = 0.017343, speed = 915.795898 distance = 15.890248, timeDelta = 0.016302, speed = 974.742249 distance = 16.560495, timeDelta = 0.016468, speed = 1005.606445 distance = 16.101242, timeDelta = 0.017291, speed = 931.201050
What I do is compute an average time delta between recent touch events, and if there is a touch with abnormal time delta (±30%), I ignore its speed (keeping the speed of the previous event)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With