how to find x,y,z of a point in a sphere if I'm in the center of a sphere?

Started by
10 comments, last by JoeJ 8 years ago

Guys, If you have played CS 1.6, I want to make my player to look around when I move the mouse up/down/left/right like in a fps game. I found the code, I spent all day trying to understand it and I still can't and I was wondering if someone can explain it to me.

Imagine that my camera is always at point ( 0, 0, 0 ). I need to set the point where the camera looks at. And I need that point to be on ( to lie on ) a sphere with radius = 1.0f. And when I move the mouse, I need the lookat point to be changed, but to lie on the sphere again. So basically I move my lookat point around a sphere with the mouse, the bigger the sphere is, the less sensitivity the mouse has.( and my coordinates are on the center on the sphere, of course). First I tried to do just left/right mouse movement, without up/down, and this was easy, just set the lookat point with ( cos(angle), y, sin(angle) ). But when I need to progress from circle to sphere, sh*t gets real, because when I move up/down, I mess up my left/right coordinates.

The code I found is this:

glm::vec3 direction(
cos(verticalAngle) * sin(horizontalAngle),
sin(verticalAngle),
cos(verticalAngle) * cos(horizontalAngle)
);

I just need to sum this direction vector with my currentPosition vector and to use it as my lookAt point and the joke is that it actually works. It is done like this:

ViewMatrix = glm::lookAt(
position, // Camera is here
position+direction, // and looks here : at the same position, plus "direction"
up // Head is up (set to 0,-1,0 to look upside-down)

);

If you got anything from what I said, I will be really grateful if you somehow manage to tell me why this really works, because this logic is just beyond me. :wacko:


EDIT: Ok, I've made some progress in trying to understand this crazy logic. Basically, when I am looking straight "forward" , "y" is 0. And as I move the mouse UP and look to the skies, the y gets bigger, for example: y = 0 goes to y = 0.677465. And as the "y" gets bigger, the x and z get smaller by the same ratio, that's why "x" and "z" coordinates depend on the "y" coordinate. And that's why there is cos(verticalAngle) in both x and y. But now I don't understand why the cos(verAngle) is multiplied by the sin(horAngle) ( I'm talking about "x" here )???

Advertisement

I will assume 'up' is y. First you do yaw as you have done, so you rotate about the y-axis. Next you want to do pitch but instead of rotating about the x-axis as you think you might you first need to rotate the x-axis about the y-axis and then rotate about the new axis if that make sense.

Do you have access to quaternions? I will use those but it's basically the same method when using matrices (just more expensive).


// How you decide to turn mouse movement into an angle is up to you, you can do your sensitivity stuff here
// but for simplicity sake I will assume it's already done
float yawAngle = ...
float pitchAngle = ...

// Do yaw first though order isn't too important
Quaternion yaw = Quaterion::FromAxisAngle(Vector::UP, yawAngle);
// Now you can't just rotate about 'Right' to do pitch, you need a new Vector to rotate about
Vector newAxis = yaw*Vector::RIGHT;
Quaternion pitch = Quaternion::FromAxisAngle(newAxis, pitchAngle);
Quaternion final = pitch*yaw;
Vector direction = final*Vector::FORWARD;

That should work. The important part to note is finding the second axis to rotate about. glm should have some similar functions for creating axis-angle matrices if you need those, it should work the same way (it's just a lot more work for the computer that way).

you could possibly do pitch first too which does seem neater:


float yawAngle = ...
float pitchAngle = ...

Quaternion pitch = Quaterion::FromAxisAngle(Vector::RIGHT, pitchAngle);
Quaternion yaw = Quaternion::FromAxisAngle(Vector::UP, yawAngle );
Quaternion final = yaw*pitch;
Vector direction = final*Vector::FORWARD;

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

I didn't formulate my question well, that's why you misunderstood me. The code I posted is actually working, but I don't quite understand all the logic behind it, but it is working, but I don't know why. And your code is working too, I'm sure, but why and how? :wacko: ( but thanks for the answer, anyways ).

In fact, the calculation of glm::vec3 direction is just an incarnation of standard conversion from so-called spherical co-ordinates into cartesian co-ordinates. The spherical co-ordinates are given by the 2 angles and the radius being 1, so that you have a point on the surface of the unit sphere. The "direction" is then the vector from the center of the sphere to the point on the surface, but now expressed as (x,y,z) length triple.

The actual magic, so to say, happens in fact when calculating the both angles from the mouse position; but that stuff isn't shown in the OP.

If you construct 2D vector (sin(angle), cos(angle)), the result is always a unit vector (length of 1) no matter what the angle is.

We just need 2 perpendicular directions (90 degrees between them), and add them multiplied by sin and cos:

vec2 (sin(angle), cos(angle))

=

vec2(1,0) * sin(angle)

+

vec2(0,1) * cos(angle)

This property is used in the first snippet to first generate a rotated unit vector in the xz plane:

glm::vec3 direction(
sin(horizontalAngle),
0,
cos(horizontalAngle)
);

This is again a unitvector pointing at some direction with unit length.

Now you want to rotate this around the vertical axis - we don't know yet what will happen with x&z,

but y is zero and have to change - it must become either sin(verticalAngle) or cos(verticalAngle):

glm::vec3 direction(
sin(horizontalAngle),
sin(verticalAngle),
cos(horizontalAngle)
);

Animating verticalAngle would just show y moving between -1 and +1.

That's no rotation because we still miss the cos(verticalAngle) part.

What should we do with it?

Now twist your brain and imagine we already have a unit vector from horizontalAngle in xz plane.

We also have another vector (0,1,0) already multiplied with sin(verticalAngle).

And also we see those two vectors are guaranteed to be perpendicular (90 degrees between).

Heureka - Going to multiply the xz direction with cos(verticalAngle), like we did with the initial 2D example:

glm::vec3 direction(
sin(horizontalAngle) * cos(verticalAngle),
sin(verticalAngle),
cos(horizontalAngle) * cos(verticalAngle)
);

That's it - we can point in any direction in 3D space and the result is always a unit vector.

Hope this helps - it's a very simple thing but hard to explain.

The second code snippet is a bit harder... let's see if you've got the first one before :D

EDIT: I think i can write a much better explantation, but no time at the moment...

haegarr:In fact, the calculation of glm::vec3 direction is just an incarnation of standard conversion from so-called spherical co-ordinates into cartesian co-ordinates. The spherical co-ordinates are given by the 2 angles and the radius being 1, so that you have a point on the surface of the unit sphere. The "direction" is then the vector from the center of the sphere to the point on the surface, but now expressed as (x,y,z) length triple.

Very true. What I'm not getting is how the angles multiplication does its magic.

JoeJ:

glm::vec3 direction(

sin(horizontalAngle),
0,
cos(horizontalAngle)

);

Ok, I think I got the first snippet. If "up" parameter is set to positive "y" and horizontalAngle is 10 degrees, then when the horizontalAngle is incremented, the X increments too, and the Z gets decremented.( in the situation when 0<horizontalAngle<90 ). And in openGL we use rightHand rule, this means that the X positive side is left and Z positive is forward . And the more bigger X gets, the more I go to the left ( again talking in 0<horAngle<90 ). This is the circle. But in order to connect the verticalAngle to the equation, we need to put it somewhere in "y". And here is your second part of the code:

glm::vec3 direction(
sin(horizontalAngle),
sin(verticalAngle),
cos(horizontalAngle)
);

And now it's going up and down, it's rotating around some circle. Correct me if I'm wrong, but I suspect that the figure we look at, when changing the verticalAngle in this code is not a circle, but a line, because the X and Z remain the same, just Y gets bigger and smaller. Is that true? ( Yes, and I missed that "heureka" moment )

I didn't formulate my question well, that's why you misunderstood me. The code I posted is actually working, but I don't quite understand all the logic behind it, but it is working, but I don't know why. And your code is working too, I'm sure, but why and how? :wacko: ( but thanks for the answer, anyways ).


There's a few things to consider with this, first there's the theory of it, then there's the maths of it and finally the implementation of it. I like to work things like this out by sticking my arm out in front of me (I call this forward(0,0, 1) but in OpenGL it would be (0, 0, -1)) and that is my look direction by default. I pretend I am in a huge sphere where my fingertips will always touch the inner surface. I think the second method I posted is easier so I'll do that. First it's doing pitch so I just rotate my arm up or down by verticalAngle, then it does yaw so I'd rotate on my chair horizontalAngle and my arm should then be pointing in the desired direction. That's the theory.

Mathematically I am just doing x-axis rotation of verticalAngle followed by y-axis rotation of horizontalAngle.

You just implement that in whatever way you choose. The code you have is doing the same thing - it is using a direction of (0, 0, 1) as forward. I don't know how they derived this but it goes like this:
x-axis rotation matrix (pitching) where V is vertical angle


1 	0 	0
0 	cos(V) 	-sin(V) 
0 	sin(V) 	cos(V) 

y-axis rotation matrix (yawing) where H is horizontal angle


cos(H)	0	sin(H)
0	1	0
-sin(H)	0	cos(H) 

You multiply those matrices to get a single matrix that does pitch followed my yaw and you get:


cos(H)	sin(V)*sin(H)	cos(V)*sin(H)
0	cos(V)		-sin(V) 
-sin(H)	sin(V)*cos(H)	cos(V)*cos(H) 

Then your original forward is (0, 0, 1) so as a matrix that is


0
0
1 

Multiply that by the final matrix and you get:


cos(V)*sin(H) 
sin(V) 
cos(V)*cos(H) 

You may recognise those terms from the code you have:


glm::vec3 direction(
cos(verticalAngle) * sin(horizontalAngle),
sin(verticalAngle),
cos(verticalAngle) * cos(horizontalAngle)
);

Whether they got it the same way as I did I can't say but it does show that this is the exact same way just done far quicker than multiplying matrices (which is good).

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.


Very true. What I'm not getting is how the angles multiplication does its magic.

Well, there is no angle multiplication. The sine and cosine functions compute the lengths of a unit-vector as projection onto cartesian axes, where the vector is rotated by the given angle. The multiplication is done because we have 3 dimensions. Again, this is what makes spherical co-ordinates. For a circle we need an angle (and a radius), and for a sphere we need 2 angles (and a radius).

Ok, I got to that pitch*yaw matrix ( or yaw*pitch, I got your result with yaw*pitch . And I multiplied by "forward" and it really works. But now another question comes, maybe I'm just tired, but why multiplying this by my original forward or backwards or +x or -x works, but if I multiply the matrix by my original "y", it doesn't work?

Ok, I got to that pitch*yaw matrix ( or yaw*pitch, I got your result with yaw*pitch . And I multiplied by "forward" and it really works. But now another question comes, maybe I'm just tired, but why multiplying this by my original forward or backwards or +x or -x works, but if I multiply the matrix by my original "y", it doesn't work?

What do you mean 'doesn't work'? It will work with forward because it is designed to work that way but if you start throwing random vectors in it's no longer guaranteed to work because what you ask of it doesn't necessarily make sense so it depends what you are asking it to do. I assume you are not but you can't just put in 'up' as your original vector and then stick that in as your camera direction, depending on the angle it might not make sense. You sometimes lose a degree of freedom with rotations too (depending on how you rotate) so something along those lines might be coming in to play too.

Think about what happens in you look up 90 degrees, now if you yaw you are still looking up. You then put that in your camera (0, 1, 0) and also you put 'up' as being (0, 1, 0) which doesn't make sense in the lookat matrix (I don't think you can use two parallel vectors). The way to solve that is to have both a forward vector and an up vector that gets rotated rather than just the forward vector.

I can't really say for sure without know what effect it has.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

This topic is closed to new replies.

Advertisement