Convert any number range to -1/1 scale - c#

I need to convert any range to a -1 to 1 scale. I have multiple ranges that I am using and therefore need this equation to be dynamic. Below is my current equation. It works great for ranges where 0 is the centerpoint. I.E. 200 to -200. I have another range however that isn't converting nicely. 6000 to 4000. I've also tested 0 to 360 and it works.
var offset = -1 + ((2 / yMax) * (point.Y));
One of the major issues that I may have is that sometimes I'll get a value that is outside the range, and as such, the converted value needs to also be outside the -1 to 1 range.
What this is for is to take a value that is a real world value, and I need to be able to plot it into an OpenGL point. I'm using .NET 4.0 and the Tao Framework.

rescaled = -1 + 2 * (point.Y - yMin) / (yMax - yMin);
However, in OpenGL you can do this with a projection matrix (or matrix multiply inside a shader). Read about glTranslatef and glScalef for how to use them or how to duplicate them with matrix multiply.

Related

Is there a way to compress Y rotation axis to one byte?

I am making a Unity Multiplayer game, and I wanted to compress the Y rotation axis from sending the whole Quaternion to just sending one byte.
My first compression attempt:
Instead of sending Quaternion, I have just sent a Y-axis float value
Result: 16 bytes -> 4 bytes (12 bytes saved overall)
Second compression attempt:
I have cached lastSentAxis variable (float) which contains the last Y-axis value that has been sent to the server
When a player changes their rotation (looks right/left), then a new Y-axis is compared to the cached one, and a delta value is prepared (delta is guaranteed to be less than 255).
Then, I create a new sbyte - which contains rotation way (-1, if turned left, 1, if turned right)
Result: 4 bytes -> 2 bytes (2 bytes saved, 14 overall)
Third compression attempt (failed)
Define a byte flag instead of creating a separated byte mentioned before (1 - left, 2 - right)
Get a delta rotation value (as mentioned previously), but add it to the byte flag
PROBLEM: I have looped through 0 to 255 to find which numbers will collide with the byte flag.
POTENTIAL SOLUTION: Check if flag + delta is in the colliding number list. If yes, don't send a rotation request.
Every X requests, send a correction float value
Potential result: 2 bytes -> 1 byte (1 byte saved, 15 overall)
My question is, is it possible to make a third compression attempt in a more... proper way or my potential solution is only possible thing I can achieve?
I would not claim that you saved overall 15 bytes ^^
If you only need one component of the rotation anyway then the first step of syncing a single float (4 bytes) seems actually pretty obvious ;)
I would also say that going beyond that sounds a bit like an unnecessary micro optimization.
The delta sync is quite clever and at first glance is a 100% improvement from 4 bytes to 2 bytes.
But
it is also quite error prone and could go desync if only one single transmission fails.
this of course lowers the precision down to 1 degree integer steps instead of a full float value.
Honestly I would stick to the 4 bytes just for stability and precision.
2 bytes - about 0.0055° precision
With 2 bytes you can actually go way better than your attempt!
Why waste an entire byte just for the sign of the value?
use a short
uses a single bit for the sign
still has 15 bits left for the value!
You just would have to map your floating point range of -180 to 180 to the range -32768 to 32767.
Sending
// your delta between -180 and 180
float actualAngleDelta;
var shortAngleDelta = (short)Mathf.RondToInt(actualAngleDelta / 180f * shortMaxValue);
var sendBytes = BitConverter.GetBytes(shortAngleDelta);
Receiving
short shortAngleDelta = BitConverter.ToInt16(receivedBytes);
float actualAngleDelta = (float) shortAngleDelta / (float)short.MaxValue * 360f;
But honestly then you should rather not sync the delta but the actual value.
So, use a ushort!
It covers values from 0 to 65535 so just map the possible 360 degrees on that. Sure you lose a little bit on precision but not down to full degrees ;)
// A value between 0 and 360
float actualAngle;
ushort ushortAngle = (ushort) Mathf.RoundToInt((actualAngle % 360f) / 360f * ushort.MaxValue);
byte[] sendBytes = BitConverter.GetBytes(ushortAngle);
Receiving
ushort ushortAngle = BitConverter.ToUInt16(receivedBytes, 0);
float actualAngle = (float)ushortAngle / (float)ushort.MaxValue * 360f;
Both maintains a precision down to about 0.0055 (= 360/65535) degrees!
Single byte - about 1.41° precision
If a lower precision is an option for you anyway you could however go totally fancy and say you don't sync every exact rotation angle in degrees but rather divide a circle not by 360 but by 256 steps.
Then you could map the delta to your lesser grained "degree" angles and could cover the entire circle in a single byte:
Sending
byte sendByte = (byte)Mathf.RoundToInt((actualAngle % 360f) / 360f * (float)byte.MaxValue);
receiving
float actualAngle = receivedByte / (float)byte.MaxValue * 360f;
which would have a precision of about 1.4 degrees.
BUT honestly, is all this forth and back calculations really worth the 2/3 saved bytes?

Create random double, unified over whole range of valid doubles

There is Best way to generate a random float in C# for float, and Generating a Random Decimal in C# for decimal, however, there is no question for double yet which covers "the full range of double".
How to properly generate a random double with a uniform distribution over all valid (i.e., non-NaN/infinite/...) values?
Your scaling wouldn't work, because the scaling factor would be actually bigger than double.MaxValue which doesn't work.
var number = r.NextDouble() * 2d - 1d; // r in [-1, 1]
return number * double.MaxValue;
This code snippet first generates a uniform distributed random number in the range [-1,1].
Than it transforms it into your desired range.
This only works, when both ranges are equal, which is true for your use-case ( double.MaxValue == Math.Abs(double.MinValue))
Note: actually the upper bound of Random.NextDouble() is 0.99999999999999978 according to https://learn.microsoft.com/en-us/dotnet/api/system.random.nextdouble?view=netframework-4.7)
According to your question, how to get a range where 1 is included, you could implement the random.NextDouble() method on your own.
The reference implementation (http://referencesource.microsoft.com/#mscorlib/system/random.cs,bb77e610694e64ca) uses the Random.Next() method and maps it to a double, where the Random.Next() method has a range [0, Int32.MaxValue - 1]. So just use r.Next() * (1.0 / (Int32.MaxValue - 1)) which would return double values in the range [0, 1].
By the way, I don't think, the docs are correct, since the maximum value, Random.NextDouble() can generate is 0.999999999534339, which is (Int32.MaxValue - 1) * (1.0 / Int32.MaxValue) and not 0.99999999999999978.

Float Values Either Defaulting To 1 or 0

I am trying to work out something for CellSize in Unity.
This is for the GridLayoutGroup component. I found something that when done with a calculator works perfectly fine: Screen.Width/1280 , Screen.Height/720.
Since I am using 720p as the base resolution and everything looks fine on there I use it as a base. 100x100 looks great for cell size on 720p.
For example their resolution is 1280:1024
It should come out (100, 142) However it always comes out (1.0, 1.0).
If the resolution is tiny... such as 320x200 it comes out (0.0, 0.0).
Here is my code:
SlotPanel.GetComponent<GridLayoutGroup>().cellSize = new Vector2((Cam.pixelWidth / 1280) * 100, (Cam.pixelHeight / 720) * 100);
Cam is the Camera that the player uses.
Another code that I tried was:
SlotPanel.GetComponenet<GridLayoutGroup>().cellSize = new Vector2((Screen.Width / 1280) * 100, (Screen.Height / 720) * 100);
Both result in the same issue. At this point I am at a loss of words for how annoyed I am. I don't understand why the math is right, but it does not work right.
Cam.pixelWidth / 1280 &c. is evaluated in integer arithmetic; any remainder is discarded.
Rewrite to 100.0 * Cam.pixelWidth / 1280 to ensure the evaluation takes place in floating point (due to promotion of the two integral arguments). There are other ways, but I find this one to be clearest since changing the first coefficient tells a reader of your code from the get-go what you want to do.
(If you require the type of the expression to be a single precision floating point type then use 100f in place of 100.0).
This is one of those cases where using excess parentheses is actually harmful.

Same mathematical expression returns different result

I am baffled by this one.
So I have a console application doing a lot of calculations (trust me, thousands of them ). In one method, I have some parameters that need calculating, in different situations. For one of them, the mathematical expression is basically the same, only one difference in a term. Here is the code snippet along with all the lines between the 2 formulas in question, the Nq1 and Nq2 ones ( first formula of the code and last one to be more easy ):
//drained conditions
Nq1 = Math.Round((Math.Pow(Math.E, Math.PI * Math.Tan(studiu.Fi * Constants.ConversionToDeg)) * Math.Pow((Math.Tan(45 + studiu.Fi / 2.00) * Constants.ConversionToDeg), 2)), 2);
//Combination 2
studiu.Fi = FiAfectat;
//drained conditions
Nq2 = Math.Round((Math.Pow(Math.E, Math.PI * Math.Tan(studiu.Fi * Constants.ConversionToDeg)) * Math.Pow((Math.Tan(45 + studiu.Fi / 2.00) * Constants.ConversionToDeg), 2)), 2);
The first formula returns 18.04 but the second one returns 0.01. How is this possible ? Only the studiu.Fi term differs, but not by that much ( 32 in the first case and 27 in the second ).
How can Nq1 be 18 and Nq2 be 0.01 ? Am I missing something here ?
Tan(x) is a periodic function that will change drastically for a small change in x. Since the two formulas only differ by a term inside the Tan function, this is likely your problem.
Also, it is likely that you should be using radians instead of degrees. If this is the case you should use a command that converts real numbers to radians instead of one that converts to degrees.

Why Math.Atan(Math.Tan(x)) != x?

If tan(x) = y and atan(y) = x why Math.Atan(Math.Tan(x)) != x?
I´m trying to calculate x in something like:
tan(2/x +3) = 5
so
atan(tan(2/x + 3) = atan(5)
and so on... but I´ve tried this:
double d = Math.Atan(Math.Tan(10));
and d != 10. Why?
The tangent function is periodic with period pi, and is invertible only if you restrict it to a subset of its domain over which it is injective. Usually the choice of such set is the open interval ]-pi/2, pi/2[, hence the arctan function will always return a point in that interval. In your case, 10 = 3*pi + 0.57522... Thus, the arctan of the tangent of 10 will return 0.57522...
Note that the arctan function, defined as above, is injective and defined over all the real numbers, hence the converse of your problem math.tan(math.atan(x)) == x
indeed holds for each x (except for numerical errors).
In order to deal with numerical errors, you should never perform comparisons between the results of floating point computations using == or !=. Use abs(number1 - number2) < epsilon // ==
abs(number1 - number2) >= epsilon // !=
instead, where epsilon is a small positive constant.
A graph might help explain why you are not getting the result you expected.
(source: wolfram.com)
http://mathworld.wolfram.com/Tangent.html
That shows the graph of Tan, but if you imagine reading off a value of x for a given y, (e.g. y = 0) then depending on which "strand" of Tan you read, you will get a different answer (-pi, 0, pi...). That's the point about Arctan(x) having more than one solution.
If arctan was restricted to only one of those strands, e.g. -pi/2 < x < pi/2 then Arctan(tan(x)) will return x providing you have accounted for floating point errors.
EDIT: However, according to http://msdn.microsoft.com/en-us/library/system.math.atan.aspx, the atan method already returns -pi/2 < x < pi/2 or NaN if your input is undefined. So the problem must soley be down to floating point rounding.
EDIT (F.R.): Added figure
I dont know any C#, but maths says that tan is not invertable, only in a small intervall.
e.g. tan(pi) = 0 and tan(0) = 0. When asking for atan(0) it could be 0 or pi (or every multiple of pi), so the result is in the range from -pi/2 .. pi/2.
Even if you start with an x in the invertable range i doesnt has to work, because of rounding errors with the floating points (it has not unlimmited precision).
tan-1(tan(x)) == x for all x in (-PI/2, PI/2).
Because the tangent function is periodic we need to normalize input angle. Math.Atan returns an angle, θ, measured in radians, such that -π/2 ≤ θ ≤ π/2, so it makes sense to normalize to that range (since it obviously won't anything within that range anyway):
double normalizedAngle = (angle + Math.PI / 2) % Math.PI - Math.PI / 2;
Doubles should be compared with some error margin. But in fact for this case Double.Epsilon is too small and "If you create a custom algorithm that determines whether two floating-point numbers can be considered equal, you must use a value that is greater than the Epsilon constant to establish the acceptable absolute margin of difference for the two values to be considered equal. (Typically, that margin of difference is many times greater than Epsilon.)" For instance, Math.Atan(Math.Tan(-0.49999632679501449)) + 0.49999632679501449 will be greater than Double.Epsilon for 1.1235582092889474E+307 times.
It might be helpful if you posted what you are trying to accomplish. I have recollections of discovering trig functions that handled the issue if what quadrant the inputs were in for me when I tried playing with angles, for example.
In general, when you are dealing with floating point numbers, you are dealing with approximations. There are numbers that cannot be represented exactly, and the tan and arctan operations are themselves only approximate.
If you want to compare floating point numbers, you need to ask if they are nearly equal, or equivalently, if the difference is less than some small value, and think carefully what you are doing.
Here is are some FAQS (for c++, but the idea is the same), that talk a bit about some of the oddities of floating point numbers:
FAQ 29.16
FAQ 29.17
FAQ 29.18
Edit: Looking at the other answers, I realise that the main problem is probably that tan isn't invertible, but the approximation issue is worth considering too, whenever you test floating point numbers for equality.
Looking at the .net documentation for Math.Atan, atan produces a value between -π/2 and ≤ π/2, which doesn't include 10. That I think is the usual range for arctan.
double d = Math.Atan(1) * (180 / Math.PI);
so d will be 45 in degrees

Categories

Resources