Re-map a number from one range to another?

What would be the closest thing to this in unity? (js or c#)

from other language:
//Re-maps a number from one range to another.
float m = map(value, low1, high1, low2, high2);

Visual Explanation:
800341--773578--upload_2021-1-14_13-8-33.png
reference: Godot Engine: How to remap a range of numbers to another range – visually explained – Victor Karp

*nevermind, found this (not tested yet)…

// c#
float map(float s, float a1, float a2, float b1, float b2)
{
    return b1 + (s-a1)*(b2-b1)/(a2-a1);
}
10 Likes

That code works fine, but you could write it more clearly. You can make an extension method to float:

public static class ExtensionMethods {

public static float Remap (this float value, float from1, float to1, float from2, float to2) {
	return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
}
	
}

That will allow this pretty decent syntax:

Debug.Log(2.Remap(1, 3, 0, 10));	// 5
65 Likes

I would use “Map” instead of “ReMap” to be consistent with other tools and use the argument names inputFrom, inputTo, outputFrom, outputTo, to be more clear.

But thanks for the function!

3 Likes

In case if any body could not understand upper code.

 public static float Remap (this float from, float fromMin, float fromMax, float toMin,  float toMax)
    {
        var fromAbs  =  from - fromMin;
        var fromMaxAbs = fromMax - fromMin;       
       
        var normal = fromAbs / fromMaxAbs;

        var toMaxAbs = toMax - toMin;
        var toAbs = toMaxAbs * normal;

        var to = toAbs + toMin;
       
        return to;
    }
10 Likes

Testing the above methods… Raza’s code worked when changing the range to include negative numbers, but Jessy’s method, as far as I can tell, does not.

1 Like

They both work okay for me: C# Online Compiler | .NET Fiddle

The algebra is the same in the two methods, they just use different names and Raza shows his work step by step. Basically, start by normalizing your position in the first vector and then multiply the normalized position by the second vector and finally add the second vector’s starting position.

test code

using System;
					
public class Program
{
	public static void Main()
	{
		var r = new Random();
		for (var i = 0; i < 100; i ++)
		{			
			var x1 = r.Next(int.MinValue, int.MaxValue);
			var x2 = r.Next(int.MinValue, int.MaxValue);
			var y1 = r.Next(int.MinValue, int.MaxValue);
			var y2 = r.Next(int.MinValue, int.MaxValue);
			var p = r.Next(int.MinValue, int.MaxValue);
			
			var jessy = JessyMap(p, x1, x2, y1, y2);
			var razza = RazzaMap(p, x1, x2, y1, y2);
			
			if(jessy == razza)
				Console.WriteLine($"Same result for arguments {p}, {x1}, {x2}, {y1}, {y2}");
			else				
				Console.WriteLine($"Bad result for arguments {p}, {x1}, {x2}, {y1}, {y2}");
		}
	}
	
	public static float JessyMap (float value, float from1, float to1, float from2, float to2) {
    	return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
	}
	
	public static float RazzaMap (float from, float fromMin, float fromMax, float toMin,  float toMax)
	{
        var fromAbs  =  from - fromMin;
        var fromMaxAbs = fromMax - fromMin;       
       
        var normal = fromAbs / fromMaxAbs;
 
        var toMaxAbs = toMax - toMin;
        var toAbs = toMaxAbs * normal;
 
        var to = toAbs + toMin;
       
        return to;
    }
}
2 Likes

Here’s another way of looking at the same problem. Imagine drawing both ranges on a number line but then rotating the second range to be perpendicular, so you have two number lines: an x an y dimension.

You might be able to see that in order to translate from one range to the other, we can use a linear equation:
y = mx + c
where:
m = the slope of the line (the ratio of range2 / range1)
c = the y intercept of the line (the difference between where range2 and range1 start, multiplied by the slope)
y = dependent variable
x = independent variable

public static float Map (this float x, float x1, float x2, float y1,  float y2)
{
  var m = (y2 - y1) / (x2 - x1);
  var c = y1 - m * x1; // point of interest: c is also equal to y2 - m * x2, though float math might lead to slightly different results.
 
  return m * x + c;
}
3 Likes

If you want to use unity methods:

float aValue;
float normal = Mathf.InverseLerp(aLow, aHigh, value);
float bValue = Mathf.Lerp(bLow, bHigh, normal);
48 Likes

Jessy’s code totally confused me until i realised that what he meant by ‘from’ and ‘to’ were totally non-intuitive to me. I thought it meant ‘range we’re mapping from’ and ‘range we’re mapping to’.

but actually we’re using ‘from1…to1’ to ‘from2…to2’, instead of ‘from1…from2’ to ‘to1…to2’.
Just in case someone else might have come here to save thinking time and made a mistake from it, since this result comes up first on google.

8 Likes

Sheer elegance

Put it in a utilityScript class and make it static, so you can use it in any script by calling

mappedValue = utilityScript.remap(5, 1, 10, -50, 100);

public static float remap(float val, float in1, float in2, float out1, float out2)
    {
        return out1 + (val - in1) * (out2 - out1) / (in2 - in1);
    }

(p.s. Mathf, still waiting…)

6 Likes

Overloaded version with clamping on the input and output values. The original one has no clamping on anything, so you can input and output values outside the ranges (may not be what you want).

// Full version, clamping settable on all 4 range elements (in1, in2, out1, out2)
    public static float remap(float val, float in1, float in2, float out1, float out2,
        bool in1Clamped, bool in2Clamped, bool out1Clamped, bool out2Clamped)
    {
        if (in1Clamped == true && val < in1) val = in1;
        if (in2Clamped == true && val > in2) val = in2;

        float result = out1 + (val - in1) * (out2 - out1) / (in2 - in1);

        if (out1Clamped == true && result < out1) result = out1;
        if (out2Clamped == true && result > out2) result = out2;

        return result;
    }
3 Likes

This marks the 1000th time I’ve gone on here to copy paste this code.

Can someone from Unity please put this in Mathf?

10 Likes

I prefer my super-readable-even-if-not-as-performant way:

    public static float Remap(float input, float oldLow, float oldHigh, float newLow, float newHigh) {
        float t = Mathf.InverseLerp(oldLow, oldHigh, input):
        return Mathf.Lerp(newLow, newHigh, t);
    }

Edit: I’m not the first one in this thread, oops ;D

2 Likes

I originally used names like “low” and “high” as well, but I found it unintuitive, because the ranges can be reversed. (Though my “clamped” version disallows reversed ranges.)
+1000 for adding at least a basic version to Mathf…

4 Likes

Freya Holmer talks about remapping in her Indiecade talk (@ 21 minutes):

4 Likes

Right, generally when you want a clamped version, it’s usually best to just clamp the normalized value. The normalized value is always in the range 0 to +1, so clamping there is trivial. For example imagine you want to map the range
“20 to 10” and your input value is “12”. It means we subtract the “first” bounds and divide by the difference. So we get

(12-20) / (10-20) ---> (-8) / (-10) ---> 0.8

So clamping the normalized value between 0 and 1 would give you the right result. That means if you want to write a single method that does a remap that clamps, you can simply do

public static float RemapClamped(this float aValue, float aIn1, float aIn2, float aOut1, float aOut2)
{
    float t = (aValue - aIn1) / (aIn2 - aIn1);
    t = Mathf.Clamp(t);
    return aOut1 + (aOut2 - aOut1) * t;
}

Or without any Mathf dependency by inlining the clamp

public static float RemapClamped(this float aValue, float aIn1, float aIn2, float aOut1, float aOut2)
{
    float t = (aValue - aIn1) / (aIn2 - aIn1);
    if (t > 1f)
        return aOut2;
    if(t < 0f)
        return aOut1;
    return aOut1 + (aOut2 - aOut1) * t;
}

It doesn’t really make much sense to clamp the inputs / outputs seperately as clamping one would automatically clamp the other as well since we make a mapping between those ranges. So if you clamp the input to stay in the input range, the output will also be in the output range since that’s the definition of our remap function.

In my second version we could set “t” to 1 when it’s larger than 1. However since we know that the final equation will evaluate to, we can directly return the correct result. This is also better for numerical stability. At dodgy range values, clamping t to 1 may not yield “aOut2” because even aOut1 and (-aOut1) mathematically cancel each other, due to floating point rounding issues the result may be slightly off. So directly returning the proper upper / lower bounds is not only faster, but also more stable. So such an expression would 100% of the time evaluate to true if we run into the upper bounds of the output range

float val =  7;
if (val.RemapClamped(20,10, 5, 200) == 200)

likewise this would also work reliably

float val =  21;
if (val.RemapClamped(20,10, 5, 200) == 5)
2 Likes
  1. create a utility class that contains general-use code that you use on most projects
  2. put this code there (and any other you use often)
  3. import said script into all new projects.
  4. profit? (actually yeah, you save time and time = money)
1 Like

Agreed, and that’s what I do, including some list-based methods like randomize. But at some point it would just be nice to have them in the library…several other languages (Processing, Max/MSP) do, so they’re very straightforward to use. Also just having them readily available means you’re aware of them, maybe before realizing you even need them :wink:

1 Like

That was actually a beautiful description of NURBS math! Quite elegant and informative (right after the remap discussion)…