3D Help/Advice calculating the 'Up' vector of a 3D projection

fishfern

Member
Hi Everyone!

Apologies for asking what may well be a super obvious question, but I was wondering how to accurately calculate the xup, yup, and zup values for the d3d_set_projection_ext() function. All of the tutorials I have watched and read have set xup, yup, and zup to (0,0,1) or (0,0,-1) respectively, which seems to work in most cases, however doesn't appear to accurately work in my current project. I'm currently working on a 3D ray cast system (which I have almost completely working, thanks to the awesome help of some people here), however the ray itself seems to act strangely at extreme pitch/zdir values (i.e when the character is looking up or down to any great degree.

Essentially, what happens is that while the pitch of the player/camera object is kept fairly close to being 'natural', everything works perfectly fine, however as you pitch the camera upwards or downwards, the ray seems to move exponentially less. When you pitch the camera as high as it will go (80 degrees currently), the ray appears as if it is casting from two thirds down the screen, rather than the middle, conversely, when you pitch the camera fully down, the ray appears as it is casting from the top third of the screen, not the centre. It's a strange occurrence that makes gameplay feel super strange!

I received some super helpful advice in a previous thread (here), and I've been trying to work things out, but I'm still totally lost, and figured I'd be best of starting a dedicated thread to try and look for some advice on where I was going wrong.

I have tried a couple of different ways of setting up my camera projections, but many of them conflict with other aspects of my game, so at the moment I'm using the following system to set up my projection:

GML:
///Mouse-look controls

  //Calculate turning
  direction -= (display_mouse_get_x() - display_get_width() / 2) * 0.125;
  pitch -= (display_mouse_get_y() - display_get_height() / 2) * 0.1;
  dir = direction;
  //Clamp zdir
  pitch = clamp(pitch, -80, 80);
  zdir = pitch;
//Set xto, yto, zto
  var d = degtorad(direction);
  var zd = degtorad(pitch);

  xto = x + cos(d) * abs(sin(zd) + sign(-pitch));
  yto = y - sin(d) * abs(sin(zd) + sign(-pitch));
  zto = z + height + sin(zd);
  zup = z + height + sin(degtorad(zdir+90));
 

//Lock cursor
  var center_x = display_get_width() / 2;
  var center_y = display_get_height() / 2;
  display_mouse_set(center_x, center_y);

Code:
///Draw camera
var w = view_wport;
var h = view_hport;

d3d_set_projection_ext(obj_player.x, obj_player.y, obj_player.z + obj_player.height - 2, obj_player.xto, obj_player.yto, obj_player.zto - 2, 0, 0, obj_player.zup, obj_player.fov, w / h, 1, 10000000);

Any help is greatly appreciated!

Have a great night!
 

FrostyCat

Member
Roll-pitch-yaw systems have problems with gimbal lock at pitches near 90 degrees, which is likely what you're experiencing. I recommend that you calculate a "right" vector in addition to a "to" vector, then calculate the up vector as the cross product of the "right" vector with the "to" vector, scaled down to length 1.

This is why you should learn basic vector math and linear algebra before 3D graphics, instead of relying entirely on tutorials and other people. I recommend the book 3D Math Primer for Graphics and Game Development by Fletcher Dunn.
 
I don't understand the weird way that you're calculating a to vector.

Anyway, this is what I would use.

rotation order: z*y (pitch[y] then yaw[z])
look: (cz*cy, -sz*cy, sy)
up: (-cz*sy, sz*sy, cy)
right: (sz,cz,0)
c = cosine, s = sine. look is the direction the camera is looking toward.
 

Roldy

Member
I'd support @FrostyCat post.

Crossproduct is what you want to look into. Two vectors make a plane, the cross product of those vectors will be the normal of the plane. So if you have a lookAt and ROLL vector you can get an UP vector by lookAt x ROLL. If you have no roll then just make ROLL be a right angle vector to lookAt with z = 0.

Understanding vectors, dotProduct, crossProduct and basic matrix operations are crucial. Take the time to learn them.

BTW: This is a decent book: https://www.amazon.com/Math-Primer-Graphics-Game-Development-ebook-dp-B008KZU548/dp/B008KZU548/ref=mt_other?_encoding=UTF8&me=&qid=
 
Last edited:

fishfern

Member
Roll-pitch-yaw systems have problems with gimbal lock at pitches near 90 degrees, which is likely what you're experiencing. I recommend that you calculate a "right" vector in addition to a "to" vector, then calculate the up vector as the cross product of the "right" vector with the "to" vector, scaled down to length 1.

This is why you should learn basic vector math and linear algebra before 3D graphics, instead of relying entirely on tutorials and other people. I recommend the book 3D Math Primer for Graphics and Game Development by Fletcher Dunn.
Yeah, I had figured it was about time I did some refresher/learnt some new maths. I come at game dev from a design background, so I haven't touched too much in the way of maths for years. Thanks so much for the recommendation though, this is the sort of resource I was looking for! I had been hoping to find something that went into the maths and the ethos of things a bit, rather than a wrote tutorial, and this appears to be it. Thanks a bunch! :)

I've been seeing lots of mentions of sine and cosine, would you also recommend brushing up on trigonometry?

I don't understand the weird way that you're calculating a to vector.

Anyway, this is what I would use.

rotation order: z*y (pitch[y] then yaw[z])
look: (cz*cy, -sz*cy, sy)
up: (-cz*sy, sz*sy, cy)
right: (sz,cz,0)
c = cosine, s = sine. look is the direction the camera is looking toward.
Oh this looks a lot neater than mine! Just to clarify, when you specify the rotation order of (z*y) do you mean that z=pitch and y=yaw? I'm a tad confused by "(pitch[y] then yaw[z])", purely because every instinct tells me that y axis is vertical and z is yaw, but GameMaker totally messes with that!

I'd support @FrostyCat post.

Crossproduct is what you want to look into. Two vectors make a plane, the cross product of those vectors will be the normal of the plane. So if you have a lookAt and ROLL vector you can get an UP vector by lookAt x ROLL. If you have no roll then just make ROLL be a right angle vector to lookAt with z = 0.

Understanding vectors, dotProduct, crossProduct and basic matrix operations are crucial. Take the time to learn them.

BTW: This is a decent book: https://www.amazon.com/Math-Primer-Graphics-Game-Development-ebook-dp-B008KZU548/dp/B008KZU548/ref=mt_other?_encoding=UTF8&me=&qid=
Thanks for the advice! And my maths teachers said matrix would never be used! :p Looks like my hobby during iso is learning maths.

Thanks so much everyone, I really appreciate the advice and links! :)
 
Sorry, I assumed +Z was up based on what you posted already.

I had defined pitch as rotation around the y axis, and yaw as rotation around the z axis.

And performing the rotations in the order, yaw, then pitch. (z*y), you get these vectors:

look: (cz*cy, -sz*cy, sy)
up: (-cz*sy, sz*sy, cy)
right: (sz,cz,0)

In a left-handed coordinate system, looking from a negative point along the axis of rotation, these will produce clockwise rotations with positive angles.
 

FrostyCat

Member
I've been seeing lots of mentions of sine and cosine, would you also recommend brushing up on trigonometry?
Absolutely. With 2D graphics it's still possible to mask an inadequate grasp of trigonometry, but with 3D graphics there's just nowhere to hide. And most vector math and linear algebra material assume that you know basic trigonometry to start with.
 

fishfern

Member
Sorry, I assumed +Z was up based on what you posted already.

I had defined pitch as rotation around the y axis, and yaw as rotation around the z axis.

And performing the rotations in the order, yaw, then pitch. (z*y), you get these vectors:

look: (cz*cy, -sz*cy, sy)
up: (-cz*sy, sz*sy, cy)
right: (sz,cz,0)

In a left-handed coordinate system, looking from a negative point along the axis of rotation, these will produce clockwise rotations with positive angles.
Ah right, yeah you're right, I'm treating Z as 'up/down' and Y as a horizontal axis. I figured I'd double check just in case, because outside of GM I tend to naturally go to Y axis as vertical and Z as a depth axis. Thanks for the clarification. :)

Absolutely. With 2D graphics it's still possible to mask an inadequate grasp of trigonometry, but with 3D graphics there's just nowhere to hide. And most vector math and linear algebra material assume that you know basic trigonometry to start with.
Awesome, thanks for clearing that up! I'm not too worried about the linear algebra side of things, as I have looked at that far more recently (a lot of graphing and linear algebra in my undergrad). Something I am finding is that all of the maths areas that teachers in school used to struggle to provide use-cases for are all of the areas that seem to be imperative to game dev.

A huge thank you for the book recommendation, by the way, I started reading it yesterday and it's a huge eye-opener. It's also super handy for providing some context to existing GM functions too (suddenly the lengthdir_ functions make a whole lot more sense).

On a more focused note, re-reading over your original advice, you mention a 'right' vector, is this something that vector maths will help give an understanding to? I haven't actually seen the concept mentioned before, so I'm in the dark with it. :p
 

FrostyCat

Member
On a more focused note, re-reading over your original advice, you mention a 'right' vector, is this something that vector maths will help give an understanding to? I haven't actually seen the concept mentioned before, so I'm in the dark with it. :p
Yes. By "right" vector, I mean the vector that points squarely to the right when viewed from your perspective.

Imagine an analogue clock at 3:00 nailed to a wall. The "right" vector is the hour hand, the "to" vector is the nail into the wall, and the "up" vector is the minute hand.
 

fishfern

Member
Yes. By "right" vector, I mean the vector that points squarely to the right when viewed from your perspective.

Imagine an analogue clock at 3:00 nailed to a wall. The "right" vector is the hour hand, the "to" vector is the nail into the wall, and the "up" vector is the minute hand.
Oh awesome, thanks for clearing that up!

I've been reading up on vector maths in the Dunn and Parberry text you suggested (just finished the chapter), and it has given so much context to things! I think I get how the lengthdir functions work (cos(dir)*dist for X / sin(dir)*dist for Y), if my quick trig refresher worked?

Also, if I'm correctly understanding vector maths (which I hope I am), does that mean that when tutorials and 3D games set xup, yup and zup to 0,0,1 respectively, they are essentially setting a normalized z-vector of [0,0,1] as the up vector? if so, would a game functionally change if zup was given an arbitrary number so that the 'up' vector wasn't a unit vector? Apologies if I'm way off track here!

When it comes to the arguments of d3d_set_projection_ext, does each set of three arguments (xfrom, yfrom, zfrom, xto, yto, zto, and xup, yup, zup) make up the arrays of the vectors 'from', 'to', and 'from x to' respectively?

So if I understand correctly, would this be a decent starting direction for setting up my projection vectors?
1. Set up xfrom,yfrom,zfrom as (x,y,(z+height))
2. Create a 'to' vector by adding unit vectors for direction and pitch (I think, though it may not be as simple as that).
3. Use a dot product of the 'to' vector and a unit vector along the x or y axis to find the perpendicular 'right' vector
4. Find the cross product of these to get my 'up' vector

I'm so sorry for the barrage of (probably) inane and obvious questions here. I feel like after reading a little I'm having an epiphany and want to check I'm on the right track.

Seriously though, thanks so much for recommendation (@Roldy too). The text has been an immense help!
 

FrostyCat

Member
Also, if I'm correctly understanding vector maths (which I hope I am), does that mean that when tutorials and 3D games set xup, yup and zup to 0,0,1 respectively, they are essentially setting a normalized z-vector of [0,0,1] as the up vector?
Yes. You should also see by now why this is not universally applicable, and needs to be specified via a parameter.

if so, would a game functionally change if zup was given an arbitrary number so that the 'up' vector wasn't a unit vector? Apologies if I'm way off track here!
The game's underlying collisions and data model won't change, but its presentation (i.e. the graphics rendered to the screen) will be distorted.

When it comes to the arguments of d3d_set_projection_ext, does each set of three arguments (xfrom, yfrom, zfrom, xto, yto, zto, and xup, yup, zup) make up the arrays of the vectors 'from', 'to', and 'from x to' respectively?
(xto-xfrom, yto-yfrom, zto-zfrom) form the facing vector, which d3d_set_projection_ext() calculates for you based on those 6 parameters, then builds the matrix for you. (xup, yup, zup) form the up vector.

So if I understand correctly, would this be a decent starting direction for setting up my projection vectors?
1. Set up xfrom,yfrom,zfrom as (x,y,(z+height))
2. Create a 'to' vector by adding unit vectors for direction and pitch (I think, though it may not be as simple as that).
3. Use a dot product of the 'to' vector and a unit vector along the x or y axis to find the perpendicular 'right' vector
4. Find the cross product of these to get my 'up' vector
Steps 2 and 3 are off.
  • d3d_set_projection_ext() wants a target position to look at for (xto, yto, zto). You need to add the direction-and-pitch vector to your current "from" position, and in this situation it isn't always a unit vector (i.e. looking 1 unit away in a direction isn't the same field of view as looking 100 units away in a direction).
  • A dot product doesn't give you a perpendicular vector, it proves that two vectors are perpendicular when it is 0. Getting a perpendicular vector from two other vectors is cross product's job, not dot product's.
If you're using yaw and pitch anyways, you should calculate the up vector directly using your current pitch plus 90 degrees. No need to use a "right' vector.
 

fishfern

Member
A dot product doesn't give you a perpendicular vector, it proves that two vectors are perpendicular when it is 0. Getting a perpendicular vector from two other vectors is cross product's job, not dot product's.
Oops, yeah that was a mistake/typo on my part, I was thinking of cross product, but by that point my brain was sludge!

I cannot thank you enough for you help, thanks to your suggestions and insight, I actually got my system working (and fixed!) Using the code below, even my ray projects correctly at all pitches. I cannot thank you enough!
GML:
  xfrom = x;
  yfrom = y;
  zfrom = z + height;

  xto = xfrom + (lengthdir_x(1,pitch)*lengthdir_x(1,dir));
  yto = yfrom + (lengthdir_x(1,pitch)*lengthdir_y(1,dir));
  zto = zfrom + (-lengthdir_y(1,pitch));
 
  xup = 0;
  yup = 0;
  zup = zfrom + (-radius*lengthdir_y(1,pitch+90));;
There are a couple of things I don't quite though in the context of what I have read over the last couple of days. For one, why are xfrom, yfrom, and zfrom included in the 'to' vector? Removing them causes the camera to not project properly. If the facing vector is the 'from' vector + 'to' vector, but the 'to' vector is already adding to the 'from' vector, wouldn't that mean that my game is essentially running a facing vector that is: facing = from + (from + to)?

As for the 'to' vector itself, I built upon the vector maths that Yal suggested for a ray cast system. According to her the use of multiple lengthdir functions is to account for converting euler to cartesian coords, and works to use both theta and phi in the function. It looks to me as though it's almost finding a dot product, am I on the right track with that?

Seriously though, I cannot thank you all enough. Not only have you helped solve my issue, but I understand why it helped. Thanks a bunch. :)
 

Roldy

Member
There are a couple of things I don't quite though in the context of what I have read over the last couple of days.

For one, why are xfrom, yfrom, and zfrom included in the 'to' vector? Removing them causes the camera to not project properly. If the facing vector is the 'from' vector + 'to' vector, but the 'to' vector is already adding to the 'from' vector, wouldn't that mean that my game is essentially running a facing vector that is: facing = from + (from + to)?
I assume it is because you are calling the function matrix_build_lookat

Look at the documentation and see what it is expecting as arguments. And you are correct. Normally you should think of the 'lookAt' vector as a directon. However the function matric_build_lookat wants a point. So you are adding the 'look direction' to your camera position (xfrom, yfrom, zfrom) so it is a point like matric_build_lookat expects.


As for the 'to' vector itself, I built upon the vector maths that Yal suggested for a ray cast system. According to her the use of multiple lengthdir functions is to account for converting euler to cartesian coords, and works to use both theta and phi in the function. It looks to me as though it's almost finding a dot product, am I on the right track with that?
That is very insightful of you and I think you a getting it. Infact if you look at the definition of a dot product you will see the cosine.

Now think of a cosine.

  • cosine( 0 degrees) = 1.
  • cosine( 90 degrees) = 0.

When two vectors a and b are pointed:

  • same direction then dotProduct(a , b) = 1
  • perpendicular direction then dotProduct(a, b) = 0

See the relationship?

Now think about the cross-product and its relationship with sine.
 
Last edited:

fishfern

Member
Look at the documentation and see what it is expecting as arguments. And you are correct. Normally you should think of the 'lookAt' vector as a directon. However the function matric_build_lookat wants a point. So you are adding the 'look direction' to your camera position (xfrom, yfrom, zfrom) so it is a point like matric_build_lookat expects.
Ohhh, that makes sense! I think the last time I read the doc for projections (in my case d3d_set_projection_ext as I'm working in 1.4 currently) I hadn't read any vector math, so the difference between a point and a vector was lost. Thanks for clearing that up!


That is very insightful of you and I think you a getting it. Infact if you look at the definition of a dot product you will see the cosine.
Haha, it's not so much insightful as dumb luck, and looking at how things relate. I feel like the trigonometry side of vectors is something I need to work on more; I get the most basic application (i.e the idea that the cosine of the angle 'dir' is equal to the dot product of 'a' and 'b' divided by the magnitude of 'b', but I'm still a little unsure of actually taking that one step further in applications like the above. I guess though life would be boring if you got everything first time around!


Now think about the cross-product and its relationship with sine.
Hmm, I don't remember seeing that in my reading, I'll have to follow that up!
 

Roldy

Member
Ohhh, that makes sense! I think the last time I read the doc for projections (in my case d3d_set_projection_ext as I'm working in 1.4 currently) I hadn't read any vector math, so the difference between a point and a vector was lost. Thanks for clearing that up!




Haha, it's not so much insightful as dumb luck, and looking at how things relate. I feel like the trigonometry side of vectors is something I need to work on more; I get the most basic application (i.e the idea that the cosine of the angle 'dir' is equal to the dot product of 'a' and 'b' divided by the magnitude of 'b', but I'm still a little unsure of actually taking that one step further in applications like the above. I guess though life would be boring if you got everything first time around!



Hmm, I don't remember seeing that in my reading, I'll have to follow that up!
Most of this is in that book I recomended and goes into more detail. But quickly:

A cosine is an 'angles' projection on to the x-axis.
A sine is an 'angles' projection on the y-axis. (y is perpendicular x)

Like cosine, a dotProduct (a , b) is the projection of b onto a.
Like sine, a perpendicular dotProduct(a, b) is the projection of b onto the perpendicular of a (perpA).
-- perpendicular dotProduct is also known as 2d crossproduct or perpdot or perpdotproduct
-- In 2d perpA is simple.
-- But 3d perpA is on the same plane as a and b

Or in other words if you think about the vector a as being analogous of the x-axis of a local space. Then perpA is analogous of the y-axis of that local space.
-- making dotProduct(a, b) analogous to cosine of b within that space
-- making perpdot(a, b) analogous to sine of b within that space

But in 3D how would we find perpA.

Well perpA = crossProduct (crossProduct (a, b), a)
-- also known as the triple cross product
-- not to be confused with the triple product


If you read through that book, then you will get it no problem.
 
Last edited:

fishfern

Member
Most of this is in that book I recomended and goes into more detail. But quickly:

A cosine is an 'angles' projection on to the x-axis.
A sine is an 'angles' projection on the y-axis. (y is perpendicular x)

Like cosine, a dotProduct (a , b) is the projection of b onto a.
Like sine, a perpendicular dotProduct(a, b) is the projection of b onto the perpendicular of a (perpA).
-- perpendicular dotProduct is also known as 2d crossproduct or perpdot or perpdotproduct
-- In 2d perpA is simple.
-- But 3d perpA is on the same plane as a and b

Or in other words if you think about the vector a as being analogous of the x-axis of a local space. Then perpA is analogous of the y-axis of that local space.
-- making dotProduct(a, b) analogous to cosine of b within that space
-- making perpdot(a, b) analogous to sine of b within that space

But in 3D how would we find perpA.

Well perpA = crossProduct (crossProduct (a, b), a)
-- also known as the triple cross product
-- not to be confused with the triple product


If you read through that book, then you will get it no problem.
Oh right! I re-read over my notes and can see where sine fits in with the crossProduct of 'a' and 'b'.

Thanks for this extra info!, and thanks so much for your help!
 
Top