For a project that I am currently working on I needed to animate an object along a path.
The path is recorded as the user moves their finger over the phone over time.
If they move slowly from A to B, then when I recreate their movement I have to move slowly.
The other added gotcha, was that the movement was controlled via scrubber – Which they could scrub back and forth very quickly.
The problem here is that I cannot just record a point every frame, because if you do that – it’s much to sensitive to slight jitteriness of your hand. So now if you try to rotate an object based on the last two points, technically they can be 45 degrees apart but only because they’re so close together.
So given a value 0.0-1.0 – Figure out where they were on their path that lasted say 6 seconds, moving at different speeds along different parts of the path.
Moving along, here’s an image of the problem more or less:
If the slider is at 50% (representing total time of the animation), you cannot just place it at 50% of the path in this case, because the user moved at different speeds, so the last 10% of the path may have taken them 80% of the time.

–
So what do you do? Well I don’t know but I’ll tell you what I did.
Basically as the user moves along the path, if the distance between the last point and this one is greater than SOMENUMBER I record a “PointOnPath” – which is just an NSObject that contains a CGPoint for position and a float for time value.
Then I render it using this function I created
- (void) renderAtDelta:(float)delta { float renderTime = delta * _animationTimeTotalDuration [self renderAtTime:renderTime]; } - (void) renderAtTime:(float)renderTime { // Catch if this time is greater than our max if(renderTime >= ((PointOnPath*) [_path objectAtIndex: 0]).time ) renderTime = ((PointOnPath*) [_path objectAtIndex: 0]).time; // Find the first closest timePoint in the path int len = [_path count]; if( len < 2 ) return; // Nothing to do! int i = len-2; PointOnPath* nextAfterTime; PointOnPath* previousBeforeTime; // Loop thru backwards, until we find the first one that is greater than our time value. // Knowing that then we know that the combined with the one before it - that passed our check - then we know we are rendering ourselves between these two points for (; i > 0; i--) { nextAfterTime = (PointOnPath*) [_path objectAtIndex: i]; if( nextAfterTime.time >= renderTime ) { break; // This was the last one that happened } } previousBeforeTime = (PointOnPath*) [_path objectAtIndex: i+1]; /** * Find T in the time value between the points * offset: Figure out what our time would be if we pretended the previousBeforeTime.time was 0.00 by subtracting it from us * t: Now that we have a zero based offsetTime, and a maximum time that is also zero based (durationBetweenPoints) * we can easily figure out what offsetTime / duration. * * Example values: timeValue = 5.0f, nextPointTime = 10.0f, lastPointTime = 4.0f * result: * duration = 6.0f * offsetTime = 1.0f * t = 0.16 */ float offsetTime = renderTime - previousBeforeTime.time; float t = clampf(offsetTime / (nextAfterTime.time - previousBeforeTime.time), 0.0f, 1.0f); /** * Now that we have a T value, find T between X and Y individually and use that to create a point between them */ CGPoint interpolatedPosition = CGPointMake( 0.0f, 0.0f ); interpolatedPosition.x = (nextAfterTime.position.x - previousBeforeTime.position.x) * t/1.0 ) + previousBeforeTime.position.x; interpolatedPosition.y = (nextAfterTime.position.y - previousBeforeTime.position.y) * t/1.0 ) + previousBeforeTime.position.y; self.position = interpolatedPosition; self.rotation = atan2f(interpolatedPosition.y - nextAfterTime.position.y, nextAfterTime.position.x - interpolatedPosition.x ) * (180/M_PI); }
I basically loop through all my points, until I find the first one that is in in front of the time i WANT to render at. Knowing that this is the first one AFTER of where i want to render, I know the previous one is the last one BEFORE where i want to render.
You could stop there, and just render at before and its pretty safe but it’s not very smooth, especially when the it moves slowly on that path.
So we need to find out WHERE between those two points our object should be at right now, to do that we can offset them so that the one before is 0.0 and the one after is 1.0, then we can use that new number to interpolate between the two points.
There you have it, it can be useful for recording users movements and playing it back to them or creating interesting movement of enemies on the screen.
Either way, math wins again.
Tags: cocos2d algorithm snippet, iphone development, Uncategorized