2

I'm building a knob that can only be turned to some fixed zones.. Now I'm using math clamp for one of these zones:

float clampedAngle = Mathf.Clamp(angle, -250f, 0f);

I want it to work for multiple zones, like this:

clampedAngle = Mathf.Clamp(angle, -250f, -230f);
clampedAngle = Mathf.Clamp(angle, -100f, -45f);
clampedAngle = Mathf.Clamp(angle, -30f, 0f);

Unfortunately the code above does not work, as it will clamp to the last value. How can I clamp a value to multiple valid zones?

sigmaxf
  • 7,998
  • 15
  • 65
  • 125

1 Answers1

0

The tricky part of this problem is determining which range to clamp to. One approach is to find the nearest min/max bound to the value, then clamp according to the corresponding range.

Assuming that your ranges are represented as an array of value pairs (two-value arrays), we can join them together and find the nearest min/max value by adapting the approach in this answer. Then, it's fairly easy to work backwards to determine which range the min/max value is from, and clamping accordingly:

// Clamps given value to nearest of given min/max pairs
private float ClampToNearestRange(float value, float[][] ranges)
{            
    // First, let's flatten the values into a single list
    List<float> flattenedRanges = ranges.SelectMany(item => item).ToList();

    // Now, we'll find the closest value in the list, and then get its index
    float nearestValue = flattenedRanges.Aggregate((x,y) => Mathf.Abs(x-value) < Mathf.Abs(y-value) ? x : y);
    int valueIndex = flattenedRanges.IndexOf(nearestValue);

    // With the value index, we can deduce the corresponding range index
    int rangeIndex = (valueIndex % 2 == 0) ? valueIndex / 2 : (valueIndex - 1) / 2;

    // Finally, we'll clamp according to the range we selected
    return Mathf.Clamp(value, ranges[rangeIndex][0], ranges[rangeIndex][1]);
}

You would then use the method like so:

// First, declaring your ranges somewhere in your class
float[] range1 = new float[]{0, 60};
float[] range2 = new float[]{80, 100};
float[] range3 = new float[]{150, 200};
float[][] ranges;

Start ()
{
    ranges = new float[][]{range1, range2, range3};
    float clampedAngle1 = ClampToNearestRange(120, ranges); // returns 100
    float clampedAngle2 = ClampToNearestRange(126, ranges); // returns 150
    float clampedAngle3 = ClampToNearestRange(170, ranges); // returns 170
}

Note: This does use LINQ, meaning if you need to do this often you might want to expand the logic into more Unity-friendly loops. Won't be as succinct, but it could affect your game performance.

Hope this helps! Let me know if you have any questions.

Community
  • 1
  • 1
Serlite
  • 12,130
  • 5
  • 38
  • 49