Navigating within a loop
My loop class
I have come up against this a few times, where I needed to create a series of numbers, usually integers, that are in loop. The first time I came across
This was for the salted herring portfolio website. I needed to create these fish/cursors that displayed some basic flocking behaviour. Along with the standard boid setup, they had to face either left or right. They would only point up or down when they were moving that direction. The change in direction also needed to be smooth, which leads us to our problem of averaging angles. If you do the average of 0 and 60 degrees, you can do some simple arithmetic and you get the average of 30. However if you average 350 and 10, trigonometry would give us 0 where as basic arithmetic would give us 180. The reason is that our equation doesn’t know how that 10 is closer to 350 if you go the other direction.
This is how I dealt with that…
I converted both the angles to their x and y components, and averaged those. Then I did an aTan on the averaged x and y values, giving me the average of the two angles.
This is the simplified formula in a function that would get the average of angles a and b (in radians).
private function averageNums($a:Number, $b:Number):Number {
var avg:Number = (Math.atan2( Math.sin($a) + Math.sin($b) , Math.cos($a) + Math.cos($b) ));
return avg;
}
This worked for the fish, but there is a problem with this equation. If you average 0 and 180 degrees you will not get the right average.
I noticed this when I worked on another project that was looping images and I thought I could use this simple system, where instead of using 360 degrees total, I could use the set number of frames I had and round to the closest int. After spending a bit of time with the trig way of doing this, I found a much more versatile way of doing this by simply looking for the value that’s closer and finding the distance. In addition to getting a bit more reliability here, I’m now able to interpolate between the two values.
Here is the code:
/**
* LoopNavigator - stack size has to be assigned
* functions:
* getDistance
* travel
* interpolate
*/
package loopNavigator {
/**
* @author Daniel Poda / 2009
*/
public class LoopNavigator {
private var _stackSize:int;
public function LoopNavigator($stackSize:int) {_stackSize = $stackSize;}
/**
* Get distance from $a to $b
* @param $a
* @param $b
* @return
*/
public function getDistance($a:int, $b:int):int {
// get all distances and find the smallest
var norm$B:int = $b - $a; // normalize
var dist:int;
var dist2:int;
dist = norm$B;
dist2 = _stackSize-((_stackSize - norm$B)%_stackSize);
if (dist == dist2) {
dist2 = (norm$B - _stackSize) % _stackSize;
}
if (abs(dist) > abs(dist2)) { dist = dist2 }
return dist;
}
/**
* Travel $distance from $start
* @param $start - staring int
* @param $dist - direction(+/-) and distance to travel
* @return distance and direction of travel
*/
public function travel($start:int, $dist:int):int {
var b:int = ($start + $dist)%_stackSize;
if (b < 0) { b = _stackSize + b };
return b;
}
/**
* Interpolate the value between $a and $b
* @param $a
* @param $b
* @param $i interpolate value defaults to 0.5 - halfway
* @return interpolated value
*/
public function interpolate($a:int, $b:int, $i:Number=0.5):int {
// get distance
var dist:int = Math.round(getDistance($a, $b) * $i);
return travel($a, dist);
}
// for a small speed boost
private function abs($num:Number):Number {
if ($num < 0) { return -$num }; return $num;
};
}
}