Mapping coordinates from one range to another in ActionScript 3.0

I am very fond of Processing, and the way it easily makes it possible to experiment with design ideas. One of the powerful features are it’s map() function, that takes a value and remaps it from one range to another. I haven’t seen a similar function in the ActionScript 3.0 reference, so I decided to make one to my own utils-library, because it is something the can come in handy in many situations. First I looked at using the Point() class, and interpolation to describe the ratios, but actually found it easier to just do it with regular math.

I haven’t looked at performance yet, and there is possibly a lot of ways to optimize this code – this article can be a start for further fine tuning. The helper function looks like this:

function map(val:Number, inMin:Number, inMax:Number, outMin:Number, outMax:Number, decimals:uint=0):Number
{
var returnVal:Number;
 
// Sets the total range for both in and out
var inRange:Number = inMax - inMin;
var outRange:Number = outMax - outMin;
 
//calculates the ratio for the value in relation to the in range
var ratio:Number = (val - inMin)/inRange;
//makes sure that the value is within range
val = Math.max(inMin, val);
val = Math.min(inMax, val);
//Finds the same ratio in the out range and shift it into place
returnVal = (outRange * ratio) + outMin;
 
//rounding of the decimals
var factor:int = Math.pow(10,decimals);
returnVal = Math.round(returnVal*factor)/factor;
 
return returnVal;
}

The function takes the the following arguments:

  • val: the number to be remapped
  • inMin and inMax: The original range, in which the value is measures in relation to.
  • outMin and outMax: The destination range, that the value will be re-mapped to
  • decimals: sets the precision in decimals (optional)
First it calculates the ranges with
var inRange:Number = inMax - inMin;
var outRange:Number = outMax - outMin;
These variables are defining the total range for both the incoming number, and the range of number is eventually will be placed within.
val = Math.max(inMin, val);
val = Math.min(inMax, val);
In this function I have decided to make sure, that the value is within the boundaries of the inRange. If i took the value 4, and sets the range of number to be within 10 and 100, I would get some unexpected results. You could treat this differently, or even make it possible by omitting these lines.
var ratio:Number = (val - inMin)/inRange;
ratio holds a number between 0 and 1 that defines where in the original range it is located. The closer to zero it gets, the lower the value – closer to one goes towards inMax.
returnVal = (outRange * ratio) + outMin;
Then the number is multiplied to the outRange to find the same relative point in the target range, and finally shifted into place by adding the outMin value. Negative values are treated fine in these ranges – when you add -200 to something, it is actually subtracted from the number.
var factor:int = Math.pow(10,decimals);
returnVal = Math.round(returnVal*factor)/factor;
The last part of the function is meant to round of the values. For performance, this could easily be removed, but it may be useful in some cases to have odd long numbers rounded and presented with only a few digits precition. First, the variable factor holds the value to be used in the rounding proces. For 1 digit it should be “10″, 2 digits is “100″, 3 is “1000″ and so forth. That value is used in the line below, where it first multiplies the final value by the factor and then rounds it to an integer. Then it divides with the factor again. It goes something like this:
  1. factor is 1000 (3 digits)
  2. value is 134.744637
  3. Multiplication results in 134744.637
  4. Rounding returns 134745
  5. divided by 1000 gives the result 134.745
Finally the value is returned as a Number.

Optimization

There are a lot of code shortening, that can produce a speed improvement. You could also work with int or uint exclusively to gain some performance i suppose. Finally, you can remove the decimals and the lines that check if the number is in range. I have left the to show what functionality that could come in handy.

An example

As a practical example of the map() function you can  copy the following code into a Flash document. Here the map() fuction is directly inside the main code, but you could start building your own utils-library with the helper functions as public static function for easier access.
The code draws two squares with different sizes. When the mouse moves over the small square, it draw lines in the larger square, while maintaining the relative coordinates inside the squares.
import flash.display.Sprite;
import flash.events.MouseEvent;
 
//make the small square and color it dark grey
var inArea:Sprite = new Sprite();
inArea.graphics.beginFill(0x333333);
inArea.graphics.drawRect(0,0,100, 100);
inArea.graphics.endFill();
addChild(inArea);
 
//make the large square and color it black
var outArea:Sprite = new Sprite();
outArea.graphics.beginFill(0x000000);
outArea.graphics.drawRect(0,0,400,400);
outArea.graphics.endFill();
outArea.graphics.lineStyle(1, 0xFFFFFF, 0.3);
addChild(outArea);
outArea.x = inArea.width;
 
//adds a listener for the small square, that listens for mouse movement
inArea.addEventListener(MouseEvent.MOUSE_MOVE, updateDrawing, false, 0, true);
function updateDrawing(event:MouseEvent):void
{
	//Map the local coordinates from the mouse movement to the size of the large square
	var targetX:int = map(event.localX, 0, event.target.width, 0, outArea.width);
	var targetY:int = map(event.localY, 0, event.target.height, 0, outArea.height);
 
	//Draws the line in the large square.
	outArea.graphics.lineTo(targetX, targetY);
}
 
//Helper function
function map(val:Number, inMin:Number, inMax:Number, outMin:Number, outMax:Number, decimals:uint=0):Number
{
	var returnVal:Number;
	// Sets the total range for both in and out
	var inRange:Number = inMax - inMin;
	var outRange:Number = outMax - outMin;
 
	//makes sure that the value is within range
	val = Math.max(inMin,val);
	val = Math.min(inMax,val);
 
	//calculates the ratio for the value in relation to the in range
	var ratio:Number = (val - inMin) / inRange;
 
	//Finds the same ratio in the out range and shift it into place
	returnVal = (outRange * ratio) + outMin;
 
	//rounding of the decimals
	var factor:int = Math.pow(10,decimals);
	returnVal = Math.round(returnVal*factor)/factor;
 
	return returnVal;
}


Leave a Comment