Help with Trigonometry and Controller Axis X-Y values (controller, calculate x-y value by angle)

Hi guys I hope you are all healthy and well. My question isn’t actually regarding Unity but I found some topics like this which deals with a similar question to this one. I hope the section is fine :slight_smile:

Imagine I have a given angle which represents a thumb motion on a analog stick. Consider these two motions (arrow1, arrow2):

I’m trying to understand how the controller x and y values are calculated?

I wrote a simple program which shows current X and Y values of my controllers left stick and the angle the current current motion is representing. I noticed somewhere around 60° the X value starts to decrease until by 0° it is 0. Between like 58° and like 121° the X value is maxed. Here are some example outputs:

Max Values are between -127.996094 and 127.996094

60° motion:

90° motion:

140° motion:

Above I read actual controller X and Y values of my controller and calculated the Angle. What I want is the other way around. I want to calculate X and Y values for a given angle.

By looking at the values above I couldn’t figure out the maths. I would love any input from you guys. Stay healthy and cheers!

Simply read up on cartesian to polar coordiante conversion, and you are all set :slight_smile:

Thank you for your answer sir this looks very promising :slight_smile: I’ll read and report back

Thanks again for the link. I tried to understand it and made a simple application to calculate controller x and y values.

I use these values for demo purposes:

  • sourceX = 80
  • sourceY = 150
  • targetX = 110
  • targetY = 170

I calculate the angle for above values:

  • angle: 123.69

I calculate from Polar to Cartesian using formula from above website: x = a * cos(angle) and: y = a * sin(angle)

  • using hypothenuse as variable: a
    ** ValueX: -14.14
    ** ValueY: -33.17
  • using x_diff (targetX-sourceX) and y_diff (targetY-sourceY) as variable a:
    ** ValueX: -11.76
    ** ValueY: -18.40

From my other demo App I figure out what x-y controller values are correct for angle 123°:

  • LX: 116.953125
  • LY: 76.796875
  • L motion: 123.29

I don’t see how I would come to these correct LX, LY values via the calculated ValueX and ValueY’s above. I dont seem to understand the maths :frowning:

A should be constant for all, and is the same as Y if the angle is 0 in your original graphic. What are the maximum values for x and y? They should be the same, and they make up the four main points of your ‘compass’: [Y, 0] (12 o’clock), [0,X] (3 o’clock), [-y,0] (6 o’clock), [-x,0]. So assuming that X=Y=a (it better be), to calculate any point on that circle (as you found out) for any particular angle phi is at [a cos(phi), a sin(phi)]

Now, you may run into a little thing you haven’t been told: sin() and cos() usually take arguments in radiants, not degrees. Radiants run from 0…2Pi, while Degrees from 0…360. So all you need to do is convert degrees to rads first.

to convert from degrees to rads, simply multiply the angle by pi/180.0f → 0.0174533

Thank you very much, I understand it much better now!

Max values should be between -128 and 128. So with your instruction I would like to use:

  • [-128, 0] (12 o’clock)
  • [0,128] (3 o’clock)
  • [128,0] (6 o’clock)
  • [-128,0] (9 o’clock)

So I have changed my functions to use:

  • x = 128 * cos(radiants)
  • y = 128 * sin(radiants)

I don’t know if I did everything correctly but my results are:

  • sourceX = 80
  • sourceY = 150
  • targetX = 110
  • targetY = 170
  • Angle: 123.69
  • Radiants: 2.16
  • ValueX (LX): -71.00 (128 * cos(2.16))
  • ValueY (LY): 106.50 (128 * sin(2.16))

The correct values should be similar to these tho (values taken from my other app which shows actual controller values and calculates the angle / motion):

  • LX: 116.953125
  • LY: 76.796875
  • L motion: 123.29

I think I’m still doing something wrong :frowning:

I don’t think you’re doing something wrong. The main issue is most likely that your controller has a maxed out zone where the max value stays the same. So the range would be larger but it’s clamped within a certain range. I know this because a long time ago I actually connected an old PS1 controller to my LPT port and manually read out the analog stick values. Yes they are usually send as a signed byte so the range is about -127 to 127. However the area it traces out is not a circle but essentially a rounded rectangled. So when you move the stick all the way to the right you get 127 as x value. As you move the stick up and down x stays at 127 for quite a bit of range and only close to the corners it starts going down

Here, I just recorded the configuration dialog of my PS1 controller (attached through a PS1 to USB adapter):

Sorry for the german interface names ^^. I do a full maxed out rotation of the left stick followed by the right stick. As you can see the values do not descibe a circle but a rounded rectangle. This is an inherit property of the controller itself and that’s not necessarily the same for different controllers. So the controller input is actually larger diagonally than in one of the cardinal directions. Application may apply their own filtering on the input

Hi Bunny and thank you for participating :slight_smile:

This is exactly what I thought. Your illustration is describing what I observed in my first post:

I think this is exactly the rounded rectangle shape :slight_smile:

So we do our math correctly here but there is something missing for the rounded rectangled shape. I’m not experienced enough but I’m searching online for solutions. I’ll report back if I find something. Cheers!

Maybe i wasn’t clear enough about this. However the actual behaviour of the controller is a hardware / controller specific behaviour. There is no way to know exactly how a particular controller may behave. What’s even the point to reconstruct the controller input from the angle? If you just have an angle of course you get a circle. The exact rounded rectangle depends on the hardware / controller. Your only way to reconstruct it would require your own calibration step where the user is required to max out the sticks a couple of time so you can essentially record the exact limits of the controller.

Though I still don’t see any reason why you want to reconstruct the hardware input from the already processed input.

In order to directly reconstruct the input you would need to store the magnitude of the input vector as well as the angle. If you have both you can reconstruct the exact input.

So generally when you have an x / x input you can convert it to polar coordinates by using Atan2 and also calculate the magnitude of the vector. Essentially this:

float angle = Mathf.Atan2(y, x);
float mag = Mathf.Sqrt(x*x + y*y); // if the input is a Vector2, you can simply use .magnitude

From this information you can reconstruct the x and y like this:

float x = Mathf.Cos(angle) * mag;
float y = Mathf.Sin(angle) * mag;

edit
Note to address your original question: The x and y values are not calculated at all. Those values are the actual inputs of the controller. The exact range they span depends on the hardware, that’s all.

Just to add on to why that rounded rectangle behaviour actually happens: I just take the PS1 controller as an example as I’m the most familiar with it. I actually communicated with the PS1 controller “manually”. So I looked up the protocol it uses. In “red mode” (which means analog mode) it sends 6 bytes. 2 bytes contains all the 16 states of all buttons. The remaining 4 bytes are the x and y values of the two analog sticks. The controller actually sends out 0 for fully left and 255 for fully right. Internally the actual sensory most likely has a greater range that would exceed 255 values. Because you usually get quite a bit of jitter at the far edge they simply clamped the values down.

Just try it yourself with your controller. Open up the calibration window / property window of your controller and move the stick to the far right. If you move it very slowly back to the center you will notice that the first few mm won’t change the x position of the input. It’s still maxed out. So the physical range of the stick is larger than the range that is reported by the controller. Again, this is a design decision by the controller manufacturer and there is no way to determine those limits besides doing a manual calibration.

It is my bad. I didn’t explain my use case correctly. I try to express it better now:

  • I have an angle which is given from a different source, e.g. an input device like an eye tracker.
  • With this angle I want to calculate the controller X-Y values, just like if I wanted to simulate it. I need exact values like they are inputted with a controller, I can’t use values like 128, 128 for a right down movement.
  • I don’t use controller inputs at all. I only have a test application to validate whether my calculated values (converted values from angle) are correct when same motion is inputted into my controller by myself physically.

I hope this clarified my goal :slight_smile:

This is exactly what I figured out and what makes it hard for me to calculate / simulate a motion to concrete controller values. Here is an excellent article that talks about what you describe: https://answers.unrealengine.com/questions/226365/analog-stick-input-traces-a-square.html

Taken from there is this picture which shows the max stick throw and axis bounds:

I try to read and understand everything in this article and your post. Thank you very much! I will report back :slight_smile:

Thank you very much guys! I learned a lot with your helps and understood trigonometry + how controllers works a lot better. All the info you provided is very useful for me.

As @Bunny83 made clear to me the conversion is specific to the controller that is used. They can have different implementation as how they calculate raw thumb motion inputs. I will use static values which I will capture with a different application and do a mapping between angle to controller values. Its not elegant but enough for what I need anyway.

Thank you very much for your time and effort. Cheers and stay healthy everyone!

Actually if you just want to roughly emulate the behaviour of one of these controllers, you can simply multiply your perfect circular movement by a factor slightly larger than 1 and have each of the axes clamped back to the range you actually want. It’s easiest to work in the range -1 to 1. If you need a different range, feel free to multiply the endresult by your new range (like 127).

So first you convert your angle into a direction just the usual way:

Vector2 dir = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));

This gives you a perfect circle with radius 1. In your diagram it would be the green circle. Now we just multiply by, lets say “1.2f” which gives you the blue circle. Finally we just have to clamp the result back to the -1 to 1 range on each axis and you get your rounded rectangle. How “rounded” it is depends on your scaling factor. A factor of 1 just gives you the full green circle. A factor of square root of two (roughly 1.414) or larger would give you a perfect square without any round corners.

Vector2 dir = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle));
dir *= mFactor;
dir.x = Mathf.Clamp(dir.x, -1f, 1f);
dir.y = Mathf.Clamp(dir.y, -1f, 1f);

This should give you all the freedom you need. You just have to adjust the “mFactor” to be something between 1 and 1.414

Keep in mind that analog sticks can represent any value on the whole surface. By just specifying an angle you always assume a virtual stick movement at max magnitude.