Search Unity

Re-map a number from one range to another?

Discussion in 'Scripting' started by mgear, Jan 14, 2012.

Thread Status:
Not open for further replies.
  1. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,435
    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);

    http://processing.org/reference/map_.html

    Visual Explanation:
    upload_2021-1-14_13-8-33.png
    reference: https://victorkarp.wordpress.com/20...-numbers-to-another-range-visually-explained/

    *nevermind, found this (not tested yet)..

    Code (csharp):
    1.  
    2. // c#
    3. float map(float s, float a1, float a2, float b1, float b2)
    4. {
    5.     return b1 + (s-a1)*(b2-b1)/(a2-a1);
    6. }
    7.  
     
    Last edited: Apr 21, 2022
    Fenikkel, Ruslank100, raul3d and 6 others like this.
  2. Jessy

    Jessy

    Joined:
    Jun 7, 2007
    Posts:
    7,325
    That code works fine, but you could write it more clearly. You can make an extension method to float:
    Code (csharp):
    1. public static class ExtensionMethods {
    2.  
    3. public static float Remap (this float value, float from1, float to1, float from2, float to2) {
    4.     return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
    5. }
    6.    
    7. }
    That will allow this pretty decent syntax:
    Code (csharp):
    1. Debug.Log(2.Remap(1, 3, 0, 10));    // 5
     
    Last edited: Jan 14, 2012
  3. Justice777

    Justice777

    Joined:
    Jan 7, 2018
    Posts:
    2
    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!
     
    VeganBurrito86 and iGAMONIC like this.
  4. RazaTech

    RazaTech

    Joined:
    Feb 27, 2015
    Posts:
    178
    In case if any body could not understand upper code.

    Code (CSharp):
    1.  public static float Remap (this float from, float fromMin, float fromMax, float toMin,  float toMax)
    2.     {
    3.         var fromAbs  =  from - fromMin;
    4.         var fromMaxAbs = fromMax - fromMin;      
    5.        
    6.         var normal = fromAbs / fromMaxAbs;
    7.  
    8.         var toMaxAbs = toMax - toMin;
    9.         var toAbs = toMaxAbs * normal;
    10.  
    11.         var to = toAbs + toMin;
    12.        
    13.         return to;
    14.     }
     
  5. brando_slc

    brando_slc

    Joined:
    Jan 4, 2017
    Posts:
    7
    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.
     
  6. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    They both work okay for me: https://dotnetfiddle.net/4zTuVI

    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.

    Code (csharp):
    1. using System;
    2.                    
    3. public class Program
    4. {
    5.     public static void Main()
    6.     {
    7.         var r = new Random();
    8.         for (var i = 0; i < 100; i ++)
    9.         {          
    10.             var x1 = r.Next(int.MinValue, int.MaxValue);
    11.             var x2 = r.Next(int.MinValue, int.MaxValue);
    12.             var y1 = r.Next(int.MinValue, int.MaxValue);
    13.             var y2 = r.Next(int.MinValue, int.MaxValue);
    14.             var p = r.Next(int.MinValue, int.MaxValue);
    15.            
    16.             var jessy = JessyMap(p, x1, x2, y1, y2);
    17.             var razza = RazzaMap(p, x1, x2, y1, y2);
    18.            
    19.             if(jessy == razza)
    20.                 Console.WriteLine($"Same result for arguments {p}, {x1}, {x2}, {y1}, {y2}");
    21.             else               
    22.                 Console.WriteLine($"Bad result for arguments {p}, {x1}, {x2}, {y1}, {y2}");
    23.         }
    24.     }
    25.    
    26.     public static float JessyMap (float value, float from1, float to1, float from2, float to2) {
    27.         return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
    28.     }
    29.    
    30.     public static float RazzaMap (float from, float fromMin, float fromMax, float toMin,  float toMax)
    31.     {
    32.         var fromAbs  =  from - fromMin;
    33.         var fromMaxAbs = fromMax - fromMin;      
    34.        
    35.         var normal = fromAbs / fromMaxAbs;
    36.  
    37.         var toMaxAbs = toMax - toMin;
    38.         var toAbs = toMaxAbs * normal;
    39.  
    40.         var to = toAbs + toMin;
    41.        
    42.         return to;
    43.     }
    44. }
     
    Bunny83 and ToroidGames like this.
  7. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    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

    Code (csharp):
    1. public static float Map (this float x, float x1, float x2, float y1,  float y2)
    2. {
    3.   var m = (y2 - y1) / (x2 - x1);
    4.   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.
    5.  
    6.   return m * x + c;
    7. }
     
  8. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    If you want to use unity methods:
    Code (csharp):
    1. float aValue;
    2. float normal = Mathf.InverseLerp(aLow, aHigh, value);
    3. float bValue = Mathf.Lerp(bLow, bHigh, normal);
     
  9. fizzd

    fizzd

    Joined:
    Jul 30, 2013
    Posts:
    21
    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.
     
  10. harm-zesbaans

    harm-zesbaans

    Joined:
    Mar 21, 2013
    Posts:
    3
    Sheer elegance
     
  11. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    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);

    Code (CSharp):
    1. public static float remap(float val, float in1, float in2, float out1, float out2)
    2.     {
    3.         return out1 + (val - in1) * (out2 - out1) / (in2 - in1);
    4.     }

    (p.s. Mathf, still waiting...)
     
    laurajhchen, d2clon, crb471 and 3 others like this.
  12. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    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).

    Code (CSharp):
    1. // Full version, clamping settable on all 4 range elements (in1, in2, out1, out2)
    2.     public static float remap(float val, float in1, float in2, float out1, float out2,
    3.         bool in1Clamped, bool in2Clamped, bool out1Clamped, bool out2Clamped)
    4.     {
    5.         if (in1Clamped == true && val < in1) val = in1;
    6.         if (in2Clamped == true && val > in2) val = in2;
    7.  
    8.         float result = out1 + (val - in1) * (out2 - out1) / (in2 - in1);
    9.  
    10.         if (out1Clamped == true && result < out1) result = out1;
    11.         if (out2Clamped == true && result > out2) result = out2;
    12.  
    13.         return result;
    14.     }
     
  13. crb471

    crb471

    Joined:
    Jul 8, 2013
    Posts:
    11
    This marks the 1000th time I've gone on here to copy paste this code.

    Can someone from Unity please put this in Mathf?
     
  14. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    I prefer my super-readable-even-if-not-as-performant way:
    Code (CSharp):
    1.     public static float Remap(float input, float oldLow, float oldHigh, float newLow, float newHigh) {
    2.         float t = Mathf.InverseLerp(oldLow, oldHigh, input):
    3.         return Mathf.Lerp(newLow, newHigh, t);
    4.     }
    Edit: I'm not the first one in this thread, oops ;D
     
    Eristen and seejayjames like this.
  15. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    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...
     
  16. Riplyn

    Riplyn

    Joined:
    Oct 3, 2018
    Posts:
    2
    Freya Holmer talks about remapping in her Indiecade talk (@ 21 minutes):
     
  17. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,998
    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

    Code (CSharp):
    1. public static float RemapClamped(this float aValue, float aIn1, float aIn2, float aOut1, float aOut2)
    2. {
    3.     float t = (aValue - aIn1) / (aIn2 - aIn1);
    4.     t = Mathf.Clamp(t);
    5.     return aOut1 + (aOut2 - aOut1) * t;
    6. }
    Or without any Mathf dependency by inlining the clamp

    Code (CSharp):
    1. public static float RemapClamped(this float aValue, float aIn1, float aIn2, float aOut1, float aOut2)
    2. {
    3.     float t = (aValue - aIn1) / (aIn2 - aIn1);
    4.     if (t > 1f)
    5.         return aOut2;
    6.     if(t < 0f)
    7.         return aOut1;
    8.     return aOut1 + (aOut2 - aOut1) * t;
    9. }
    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


    Code (CSharp):
    1. float val =  7;
    2. if (val.RemapClamped(20,10, 5, 200) == 200)
    likewise this would also work reliably

    Code (CSharp):
    1. float val =  21;
    2. if (val.RemapClamped(20,10, 5, 200) == 5)
     
    Greenhapi and seejayjames like this.
  18. SparrowGS

    SparrowGS

    Joined:
    Apr 6, 2017
    Posts:
    2,536
    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)
     
    PraetorBlue likes this.
  19. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    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 ;)
     
    Eristen likes this.
  20. dvelasco

    dvelasco

    Joined:
    Jul 23, 2014
    Posts:
    7
    That was actually a beautiful description of NURBS math! Quite elegant and informative (right after the remap discussion)..
     
  21. Blubber5463

    Blubber5463

    Joined:
    May 1, 2022
    Posts:
    1
    This works, however it clamps the return value between bLow and bHigh. The processing map function allows you to enter a value that exceeds the range of aLow and aHigh, returning the desired value.
     
  22. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,998
    Yes, sure. Most map function actually do clamp or some libraries may provide both a clamped and an unclamped version. Have a look at my implementaion above. You can simply remove the clamp and you get the unclamped versions.

    Like this:
    Code (CSharp):
    1.  
    2.     // unclamped
    3.     public static float Remap(this float aValue, float aIn1, float aIn2, float aOut1, float aOut2)
    4.     {
    5.         float t = (aValue - aIn1) / (aIn2 - aIn1);
    6.         return aOut1 + (aOut2 - aOut1) * t;
    7.     }
    As it was mentioned already, this is all elementary school math
     
  23. curbol

    curbol

    Joined:
    Oct 29, 2015
    Posts:
    8
    I turned this into an extension method with tuple parameters which I think makes it more clear:

    Code (CSharp):
    1.   public static float Remap(this float value, (float min, float max) from, (float min, float max) to)
    2.     => (value - from.min) / (from.max - from.min) * (to.max - to.min) + to.min;
     
    CDCappa and Bunny83 like this.
  24. Tomsterk

    Tomsterk

    Joined:
    Nov 23, 2012
    Posts:
    10
    Welcome back everyone. If you're here for this method, Unity has added it to their Mathematics package, which i believe is default

    https://docs.unity3d.com/Packages/com.unity.mathematics@1.2/api/Unity.Mathematics.math.remap.html

    usage is quite simple

    Code (CSharp):
    1. using Unity.Mathematics;
    2.  
    3. //...
    4.  
    5. //Returns the result of a non-clamping linear remapping of a value x from [a, b] to [c, d].
    6. float newValue = math.remap(a, b, c, d, x);
    So, if i wanted to remap value 50 from range 0-100 onto the new range 20-40, it would be

    Code (CSharp):
    1. float newValue = math.remap(0, 100, 20, 40, 50);
     
  25. Ziplock9000

    Ziplock9000

    Joined:
    Jan 26, 2016
    Posts:
    360
    Unfortunately they only have a non-clamping version.

    Here's a version that clamps the input range
    Code (CSharp):
    1.     float clamped_remap(float input_min, float input_max, float output_min, float output_max, float value)
    2.     {
    3.         if (value < input_min)
    4.         {
    5.             return output_min;
    6.         }
    7.         else if (value > input_max)
    8.         {
    9.             return output_max;
    10.         }
    11.         else
    12.         {
    13.             return (value - input_min) / (input_max - input_min) * (output_max - output_min) + output_min;
    14.         }
    15.     }
     
    Last edited: Jul 10, 2023
    Celezt, Bauschen and mgear like this.
Thread Status:
Not open for further replies.