Blueprint MapRangedClamped inplementation in C++

Hi all,

I’m trying to convert a Blueprint to C++ (mainly as a learning exercise)… i’m at the MapRangeClamped node and am looking in the docs, searching Google and trawling //GenericPlatformMath.h etc but can’t find an engine C++ equivalent.

Any assistance with a C++ equivalent function would be greatly received!!

247067-mapclampedrange.png

Many thanks in advance,
Matt.

As this is your learning exercise I will not give you any code. Just the way it is supposed to works and a little hint at the end.

Lets say you input:

Value = 150;
InRangeA = 250;
InRangeB = 50;
OutRangeA = -5;
OutRangeB = 5;

  • Your Value (150) is half way between
    InRangeA (250) and InRangeB (50) on
    the number line.
  • The ReturnValue should be halfway
    between OutRangeA (-5) and OutRangeB
    (5) on the number line which is
    exactly 0.0.

Notice that InRange numbers are in reverse order so closer to 250 your Value is the closer to -5 your ReturnValue should be. Your function though should work with ranges in any order.

“Clamped” just means that if your Value is outside of the InRange (say 2000) it will treat it as the maximum or minimum value (in this case max = 250) and return the corresponding OutRange value (-5 in this case).


The hint: It might be obvious by now that you should go through a intermediary normalized number line. One that is from 0.0 to 1.0. “0.0” means “return OutRangeA”, “1.0” means “return OutRangeB” and 0.3 means return the number that is 30% of the way from OutRangeA to OutrangeB.


I really hope I managed to explain it and not make it more confusing.

Hi dZh0,

I can’t thank you enough for firstly replying and secondly for not just giving me the code; that would not have helped my learning as you pointed out!

Your reply helped me so much, so many thanks once again. I’ve followed your lead and produced the following code. It seems to work, but I’m wondering if you could look it over and basically check it (I’ve tried as many tests as possible but I’m still mindful of edge cases i’ve not thought about) + any obvious optimizations that could be had.

/* Return a value based on 2 inputted ranges (ratio)*/
float AUniverse_StarGenerator::MapRangedClamped(float value, float inRangeA, float inRangeB, float outRangeA, float outRangeB)
{
	float maprangedclamped = 0.0f;

	// Find the value as a ration of the inRangeA & B
	/*
	range = max - min
	correctedStartValue = input - min
	percentage = (correctedStartValue * 100) / range 
	*/
	float clampedValue = FMath::Clamp(value, inRangeB, inRangeA);

	float inRange = inRangeA - inRangeB;
	float correctedInValue = clampedValue - inRangeB;
	float inPercentage = (correctedInValue) / inRange;

	// Find the inPercentage of the outRange
	/*
	val = ((percent * (max - min) / 100) + min
	*/
	maprangedclamped = (inPercentage * (outRangeA - outRangeB)) + outRangeB;

	UE_LOG(LogTemp, Warning, TEXT("value: %f, inRangeA: %f, inRangeB: %f, inPercentage: %f :::: outRangeValue: %f"), value, inRangeA, inRangeB, inPercentage, maprangedclamped);


	return maprangedclamped;
}

This is trickier than it seems at glance :slight_smile:

You are on the right path but your code “assumes” that inRangeB < inRangeA which will fail in this case:

(1, 0, 4, 10, 50) should return 20 because 1 is 1/4 (25%) the way from 0 to 4

Also, before you divide here float inPercentage = (correctedInValue) / inRange; you MUST make sure that inRange is not 0:

(4, 4, 4, 10, 50) = ? //You might show an error message or just default to some value but never, ever go through a division that might not be defined.

As for optimization - this is a very, very short code so it needs none. I would probably make a few “early returns” before going to the body of the method. Doing this would also let me get rid of the FMath::Clamp() call.

Thanks again dZd0, your help has been so invaluable.

I fear in this case you may have to, if you can, offer up some code. My current thoughts are around the idea of if statements (i.e if (inRangeB > inRangeA) then swapping the variables around A is always greater than B etc). I’m sure there is a far more elegant way than this?!

Kindest regards in advance,
Matt.

We will imagine that we have an input number line with the value, inRangeA and inRangeB. We will start to modify it until the value becomes what we need:

float AUniverse_StarGenerator::MapRangedClamped(float value, float inRangeA, float inRangeB, float outRangeA, float outRangeB)
{
//First, the early returns to get the inconvenient edge cases out of the way
	if (outRangeA == outRangeB) return outRangeA; // The output range is a single value so return it. No matter what you input is it will produce this value.
	if (inRangeA == inRangeB) UE_LOG(YourLog, Fatal, TEXT("inRangeA == inRangeB which will produce one to many mapping")) ; // The inRange is a single value so it can't map to a range unless it is also a single value (which we covered in the line above)
	
// MOVE: First we move all the input numbers until inRangeA reaches 0:
	// inRangeA -= inRangeA; We don't calculate this because we know that X-X=0
	value -= inRangeA;
	inRangeB -= inRangeA;
	// Note that we don't care if A is bigger or smaller than B or if it's negative or positive - it will always end up 0

// SCALE: Now we scale all the input numbers until inRangeB is 1
	// We KNOW for fact that inRangeB is not 0 because inRangeA is zero and we made sure inRangeA and inRangeB are different numbers
	// inRangeA /= inRangeB; We don't calculate this as 0/X=0
	value /= inRangeB;
	// inRangeB /= inRangeB; We don't calculate this as X/X=1
	// Note that we don't care if B is negative or positive - it will always end up 1

// Now  we make 2 more early returns if the value is out of the range
	if (value < 0.0f) return outRangeA; // 0 = inRangeA
	if (value > 1.0f) return outRangeB; // 1 = inRanbeB

// With every case out of the way we can calculate the value:
	return outRangeA + value * (outRangeB - outRangeA);
}

Now the code above is mostly comments. As you have noticed we only calculate value and inRangeB but inRangeB is only used to modify value so we can get this in one line. Also it is considered “rude” by some programmers (not me!) to modify the input values. So if we remove all the comments and collapse the lines with value and inRangeB the code gets to just 6 lines:

float AUniverse_StarGenerator::MapRangedClamped(float value, float inRangeA, float inRangeB, float outRangeA, float outRangeB)
{
	if (outRangeA == outRangeB) return outRangeA;
	if (inRangeA == inRangeB) UE_LOG(YourLog,Fatal,TEXT("inRangeA == inRangeB which will produce one to many mapping"));
	float inPercentage = (value - inRangeA)/(inRangeB - inRangeA);
	if (inPercentage  < 0.0f ) return outRangeA;
	if (inPercentage  > 1.0f ) return outRangeB;
	return outRangeA + inPercentage * (outRangeB - outRangeA);
}

Many thanks indeed. It was good to see your final code implementation; and really great as a learning exercise!

Cheers!,
Matt.

Note that I am NOT a professional programmer. There might be a better solution.