Advertisement

Rotating object to face in direction of movement causing roll

Started by November 03, 2023 12:11 AM
4 comments, last by JoeJ 1 year ago

I'm trying to get a model of aeroplane to fly a circular path defined by cos(x) and sin(y). I'm having a lot of trouble trying to get the aeroplane model to face in the direction of the movement of where it's going. I thought using the same approach that I do for camera where we construct a view matrix would allow me to do this since I can pass a position, direction and it'll look the correct way however, when applying this, my model seems to just rotate like its performing a roll and does not do a circular motion path. I am quite confused as to where I am going wrong. Quite new to game development and graphics and would appreciate any insight

vec3 circlepos = vec3(0,0,0);
previousPosition = position;
       
float x = radius * cos(angle);
float y = radius * sin(angle);

position = circlepos + vec3(x, 3000.0f, y);

direction = position - previousPosition;
direction = direction.unit();

auto rotationMatrix = Matrix::ViewMatrix(position, position + direction, vec3(0, 1, 0));

auto angularSpeed = speed / radius;

angle += angularSpeed * dt;

if(angle > 2 * M_PI)
{
    angle -= 2 * M_PI;
}

if(angle < 0)
{
    angle += 2 * M_PI;
}
// worldMatrix is a matrix that performs a 90 degree rotation CCW since my assets have y as forward and z as up and down so this rotation brings it back into OpenGL coordinates
auto matrix = camera_viewMatrix * Matrix::Translate(position) * worldMatrix * rotationMatrix * Matrix::Scale(vec3(scale, scale, scale));

snoken said:
Quite new to game development and graphics and would appreciate any insight

As a gfx dev you can visualize what your code is doing, to verify assumptions and quickly see where they break.
I can't spot a mistake in your code, so that's the next step i would do:

vec3 circlepos = vec3(0,0,0);
previousPosition = position;
       
float x = radius * cos(angle);
float y = radius * sin(angle);

position = circlepos + vec3(x, 3000.0f, y);

	RenderPoint(position, 1,0,0); // visualize a red dot to see if it moves along the circle as expected, even if the final render of the plane does not

direction = position - previousPosition;
direction = direction.unit();

	RenderVector(position, direction, 0,1,0); // render a green line to see the direction 

auto rotationMatrix = Matrix::ViewMatrix(position, position + direction, vec3(0, 1, 0));

	RenderVector(rotationMatrix[3], rotationMatrix[0], 1,0,0); render a red line to show x axis 

	RenderVector(rotationMatrix[3], rotationMatrix[1], 0,1,0); render a green line to show y axis 

	RenderVector(rotationMatrix[3], rotationMatrix[2], 0,0,1); render a blue line to show z axis, so basically we show a gizmo like a 3D modelling tool does to illustrate a transform 

I do this a lot, and it's very helpful. It can help to understand very complex things very quickly, because our visual processing unit in the brain seems much more powerful than assumptions and conclusions on the 5 things our brain can keep in mind at a moment otherwise.

It's a little work ofc. Personally i mostly use functions to render points, lines and vectors at given color. I can do this from anywhere just by including some debugVis.h. It's a singleton to buffer all the drawn stuff, and then i do a call each frame to render the recorded primitives, then deleting the buffer for the next frame.

It's worth the time. Once you have such little visualization tool, you can use it each time such uncertainties come up, and they come up often.

snoken said:
auto matrix = camera_viewMatrix * Matrix::Translate(position) * worldMatrix * rotationMatrix * Matrix::Scale(vec3(scale, scale, scale));

This one triggers my matrix multiplication order paranoia. It's a lot multiplies in one line, and even knowing how the compiler executes them, i remain doubtful. Maybe the culprit is here.
Some thoughts:

We have differing conventions with matrices: Row major vs. column major, plus personal taste on multiplication order.
Initially i have used only my own convention. But after working with other libraries here and there, i am no no longer sure if i want AxB or BxA. I'm never sure and always have to do trial and error, and so i always use only two operands and never chain multiple multiplications in one line. So the more experience i've got, the more uncertainty in practice.

snoken said:
// worldMatrix is a matrix that performs a 90 degree rotation CCW since my assets have y as forward and z as up and down so this rotation brings it back into OpenGL coordinates

This makes me think you want to handle the scale with this matrix too then. Currently rotation is left and scale is right in your chain, but i assume they should be kept together. Likely this does not matter, but never sure.
You should rename the variable imo. If you call it worldMatrix, everybody will think it's the transform of the plane in worldspace. Such things make your code hard to maintain for others, but also for yourself after some time.
I see more such issues:

snoken said:
vec3 circlepos = vec3(0,0,0);

That's not where the center of the circle is. It should be at (0,3000,0), if i interpret it right. And you could rename it to circleCenter to clarify it's on a point in the circle maybe, but it's center / origin.

There is one math issue i see, but it unlikely causes the problem. IT's about how you calculate ‘direction’, (which i would rename to ‘front or forwards’ to be more clear).

What you do is like this:

You use the difference from of two points on a curved trajectory, which is an approximation. The larger your time step, the larger your error. (black arrow)
For an accurate result, you would use the circle tangent instead. (blue arrow)

To calculate the tangent, you could do just:

direction = vec3(sin(angle), 0, -cos(angle));

You already have sine and cos, so the correct direction is even cheaper to calculate. (If the up vector would be arbitrary, we would use a cross product)

Final nitpick:

auto matrix = camera_viewMatrix * 

Usually you want to store the worldspace matrix of the object, and there is no need to involve camera transform.
The camera transfrom usually only happens on GPU. Having world space matrices is more intuitive and useful, and where the camera is does not matter for things liek physics or game logic.
So i would keep that separated.

Advertisement

Thanks for this incredibly amount of insight and help, means a lot!

I've been reading this over for the past 4 hours and I feel like I have made some progress. My issue is now that while the plane goes around the circle, the direction using the tangent seems to weirdly rotate the plane. As it goes around its constantly spinning and not “flying” in a direction

Plane spinning around while following the path defined by the radius

Current code:

 void Update(...)
    {
        vec3 circleCenter = vec3(0, position.y, 0);
        
        float x = radius * cos(angle);
        float y = radius * sin(angle);

        auto angularSpeed = speed / radius;

        angle += angularSpeed * dt;

        if(angle > 2 * M_PI)
        {
            angle -= 2 * M_PI;
        }

        if(angle < 0)
        {
            angle += 2 * M_PI;
        }
        position = circleCenter + vec3(x, 0.0f, y);

        direction = vec3(sin(angle), 0, -cos(angle));
        direction = direction.unit();

        auto rotationMatrix = Matrix::Look(position, position + direction, Cartesian3(0, 1, 0));
		
		// Right to left
        modelMatrix = viewMatrix * 
        Matrix::Translate(position) * rotationMatrix * 
        Matrix::Scale(Cartesian3(1, -1, 1)) * worldMatrix * 
        Matrix::Scale(Cartesian3(scale, scale, scale));
    }

I have a different approach, one that may be simplier. It's common for moving game objects to have three vectors associated with them: Forward, Right, and Up. Whenever the object changes orientation (rotates) these vectors can be updated by multiplying them by an rotation matrix.

Anyway to address your problem: I would have the plane only fly in the direction of the Forward vector. The trick is to change the orientation of your airplane so the forward vector changes (along with the Right and maybe the Up). The larger the rotation, the smaller your circle will be. If you're using DirectX, there's a handy function called XMMatrixRotationAxis that takes an axis of rotation (XYZ) and an angle in radians. In this case the axis of rotation would be the plane's Up vector.

So this solves two problems: The airplane is changing its path AND always facing the correct direction.

Also: Say you want to have the airplane climb or dive. Just calculate a rotation matrix based of the Right vector. If you want to have the plane roll, calculate a rotation matrix using its Forward vector.

Good luck with the game!

-Scott

Hmm, looks like spinning in the wrong direction. First guess: My ‘direction = vec3(sin(angle), 0, -cos(angle))’ is wrong. You could do trial and error on flipping signs and swapping axis which gives 4 options to try:

direction = vec3(sin(angle), 0, -cos(angle));
direction = vec3(-sin(angle), 0, cos(angle));
direction = vec3(cos(angle), 0, -sin(angle));
direction = vec3(-cos(angle), 0, sin(angle));

I would bet one of the other 3 options works for you, and such trial and error session when working with sin and cos happens pretty often, to me at least.

But this time i would be really sure the direction as is must be either forwards or backwards, but it should not spin the wrong way.
And because i can't see why it could be wrong, i start to speculate that maybe Matrix::Look() does not do what i think, or (due to paranoia) matrix rotation order is wrong, so you accidentally do rotation in object space instead world space, or vice versa.

However, neither seems very probable and i have no clue what's wrong. But i can show how to avoid all those uncertainties by just constructing the matrix geometrically and in place. That's what i would do anyway for such simple problem, also because it should me much faster:


matrix4x4 CaclualteCirclingAeroplaneTransform (vec3 circleCenter, float circleRadius, float angle, float modelScale)
{

 	// construct basis vectors to define orientation (same thing your Matrix::Look() does, i assume)

 	
	vec3 front = vec3(sin(angle), 0, -cos(angle));

	

	// using dot product to subtract the part of up which points along the front direction. (In other words, we project up to the plane of front.)

	// This way we enforce our final up is perpendicular to front. 

	// todo: Requires extra check in case upVector and front form a line, so up becomes zero

	vec3 up = normalized( upVector - front * dot(front, upVector) ); 

	

	// using cross product to get a side vector which is perpendicular to both front and up

	vec3 side = front.Cross(up); 

	

	// calculate position

	

	vec3 pos = circleCenter + side * circleRadius;

	

	// make a transformation matrix, and apply scaling and your model orientation offset

	

	matrix4x4 mat;

	

	// my assets have y as forward and z as up // todo: rotate your assets within your asset pipeline, or at import, so we can write more general code. or handle this elsewhere.

	mat[1] = vec4(modelScale * front, 0.f);

	mat[2] = vec4(modelScale * up, 0.f);

	mat[0] = vec4(-modelScale * side, 0.f); // todo: i'm guessing here - maybe (if the model appears flipped) there is no need to negate.

	

	mat[3] = vec4(pos, 1.f);

	

	return matrix; // meant to apply viewMatrix on this returned result

}



There may be some bugs, and some trial and error to orient the final model correctly might be needed, but notice i do not need a single matrix multiplication.
It's more code, but once you're familiar with those typical dot and cross product applications, it's not really harder to read.

But that just said to give some alternative example. In any case, if you can not imagine what this code is doing, that's the kind of stuff which makes a lot of daily work from game and gfx dev. Should be on top of your to-learn list, if so.

This topic is closed to new replies.

Advertisement