Rotate object in world space - Raytracing

Started by
59 comments, last by ellenature 3 years, 8 months ago

ellenature said:
https://fr.wikipedia.org/wiki/Matrice_de_rotation#Les_matrices_de_base

Because i've pulled it just out of my head ignoring any conventions. Better do it like Wikipedia says, so you do it like everybody else as well. (In practice it can only mean their rotations go into the other direction, so sign of angle causes opposite results)
There are many related convention issues. Some people use column major matrices, some major column; there is left handed or right handed coordinate systems; even multiplication order is not consistent.

> How can we rotate in the opposite direction?

Make 3 more matrices with negative angle.

It makes sense to write helper functions e.g. void MakeRotationX,Y and Z (vec3[3] matrix, float angle);

Also, make helper function like vec3 RotateVector (vec3[3] matrix, vec3 *vec);

For a "diagonal" rotation (like in the video), I find the effect strange. Is it normal? If not, how can we fix it?

Somehow video runs with 1 fps for me, but it may be caused by the up vector orientation construction.

So, next you should try to replace the single normal per object with ‘vec3 orientation[3]’. Then you can control exact orientation from keyboard entry.
Having RotateVector() function becomes handy because you have to rotate all 3 of them each time.

In your intersection function, you then use those two new axis to define width and length directions of the square. (Same can be used later to orient any other shape ofc.)

So that's some work for you : )
After this works (or not) post code so we can introduce matrix multiply to reduce lines of code even more and make your basic 3D rotations toolbox complete ; )



Advertisement

JoeJ said:
Somehow video runs with 1 fps for me

Yes, because every time I press a button I redraw the picture.

I understand what needs to be done: we need to rotate all 3 axes at the same time rather than rotating one and recalculating the other two.

I don't understand exactly what the two functions do:
vec3 RotateVector(matrix vec3 [3], vec3 * vec);
and
void MakeRotationX,Y and Z(matrix vec3 [3], floating angle);

ellenature said:
vec3 RotateVector(matrix vec3 [3], vec3 * vec);

You already have those things. This code takes input vector d, rotaties by matrix rotY, and returns rotatedD0:

		t_vec3 rotatedD0 = vec_mult(d.x, rotY[0]);
		t_vec3 rotatedD1 = vec_mult(d.y, rotY[1]);
		t_vec3 rotatedD2 = vec_mult(d.z, rotY[2]);
		rotatedD0 = vec_add(rotatedD0, rotatedD1);
		rotatedD0 = vec_add(rotatedD0, rotatedD2);
		((t_square*)obj)->vec = rotatedD0;

Math guys would say this is a ‘matrix x vector multiplication’

Often matrices use data structure like float m[3][3], if you look up some example implementations. But the math is the same, using 3 vector is convenient to get the axis, and following the code you can imagine what happens geometrically.

ellenature said:
void MakeRotationX,Y and Z(matrix vec3 [3], floating angle);

This is it for rotation around x:

	t_vec3 rotX[3] = {
		(t_vec3){1,0,0},
		(t_vec3){0,cos(a),sin(a)},
		(t_vec3){0,-sin(a),cos(a)}
	};

Again, imagine how it works. If angle is zero, we get the identity matrix:

1,0,0

0,1,0

0,0,1

If you do the rotation math from above with any vector in your mind, you see it does not change. From there it is easy to imagine how a small angle slightly rotates the axis of the matrix, and how this same rotation then happens to the input vector.

t_vec3	rotate_vector(t_vec3 matrix[3], t_vec3 *vec)
{
	t_vec3 rotatedD0;
	t_vec3 rotatedD1;
	t_vec3 rotatedD2;
	
	rotatedD0 = vec_mult(vec->x, matrix[0]);
	rotatedD1 = vec_mult(vec->y, matrix[1]);
	rotatedD2 = vec_mult(vec->z, matrix[2]);
	rotatedD0 = vec_add(rotatedD0, rotatedD1);
	rotatedD0 = vec_add(rotatedD0, rotatedD2);
	return (rotatedD0);
}

t_vec3	*make_rotation_x(float angle)
{
	t_vec3	*rot_x;
	float	c;
	float	s;
	
	if (!(rot_x = malloc(sizeof(*rot_x) * 3)))
		return (NULL);
	c = cos(angle);
	s = sin(angle);
	rot_x[0] = (t_vec3){1,0,0};
	rot_x[1] = (t_vec3){0,c,-s};
	rot_x[2] = (t_vec3){0,s,c};
	return (rot_x);
}

t_vec3	*make_rotation_y(float angle)
{
	t_vec3	*rot_y;
	float	c;
	float	s;
	
	if (!(rot_y = malloc(sizeof(*rot_y) * 3)))
		return (NULL);
	c = cos(angle);
	s = sin(angle);
	rot_y[0] = (t_vec3){c,0,s};
	rot_y[1] = (t_vec3){0,1,0};
	rot_y[2] = (t_vec3){-s,0,c};
	return (rot_y);
}

t_vec3	*make_rotation_z(float angle)
{
	t_vec3	*rot_z;
	float	c;
	float	s;
	
	if (!(rot_z = malloc(sizeof(*rot_z) * 3)))
		return (NULL);
	c = cos(angle);
	s = sin(angle);
	rot_z[0] = (t_vec3){c,-s,0};
	rot_z[1] = (t_vec3){s,c,0};
	rot_z[2] = (t_vec3){0,0,1};
	return (rot_z);
}

void		move_square(void *obj, int key)
{
	t_vec3	*rot_x;
	t_vec3	*rot_y;
	t_vec3	*rot_z;

	if (key == KEY_L)
	{
		rot_y = make_rotation_y(-M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_y, &((t_square*)obj)->vec);
		free(rot_y);
	}
	else if (key == KEY_J)
	{
		rot_y = make_rotation_y(M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_y, &((t_square*)obj)->vec);
		free(rot_y);
	}
	else if (key == KEY_K)
	{
		rot_x = make_rotation_x(-M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_x, &((t_square*)obj)->vec);
		free(rot_x);
	}
	else if (key == KEY_I)
	{
		rot_x = make_rotation_x(M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_x, &((t_square*)obj)->vec);
		free(rot_x);
	}
	else if (key == KEY_U)
	{
		rot_z = make_rotation_z(-M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_z, &((t_square*)obj)->vec);
		free(rot_z);
	}
	else if (key == KEY_O)
	{
		rot_z = make_rotation_z(M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_z, &((t_square*)obj)->vec);
		free(rot_z);
	}
}

I did that. In particular, I changed the prototypes of the functions you suggested I do. Does that seem right to you ?

Now I just have to correct the problem of the up axis that gives this weird effect when rotating "diagonally" or is there something else I need to do ?

ellenature said:
Now I just have to correct the problem of the up axis that gives this weird effect when rotating

Yeah, but make sure to replace object.vec with orientation matrix, so array of 3 vectors. Then rotate all 3 of them as you did and use then to construct the quad in intersection.

To initialize this matrix (so it all starts with a proper orientation) there are some options:

  • Set matrix to identity
  • Use your make_rotation functions
  • construct it manually, e.g. using the up vector method and cross products

Notice the last option has the risk of mirroring, which happens when one vector has opposite direction than any following code assumes.
For example if you end up with a matrix like this:

1,0,0
0,1,0
0,0,-1

This can make unexpected problems. Most code may work as expected, but some will not (e.g. conversion to quaternion).

ellenature said:
if (!(rot_x = malloc(sizeof(*rot_x) * 3))) return (NULL);

Why do you allocate memory for each matrix? This is very bad practice, because frequent allocations can cause huge slow downs and memory fragmentation.
You should never do this without a real reason. Just declare the arrays in place so they live in the stack memory without a need for allocations.

void move_square(void *obj, int key)
{
	t_vec3 *rot_x;
	t_vec3 *rot_y;
	t_vec3 *rot_z;


	if (key == KEY_L)
	{
		rot_y = make_rotation_y(-M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_y, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_y, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_y, &((t_square*)obj)->up);
		free(rot_y);
	}
	else if (key == KEY_J)
	{
		rot_y = make_rotation_y(M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_y, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_y, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_y, &((t_square*)obj)->up);
		free(rot_y);
	}
	else if (key == KEY_K)
	{
		rot_x = make_rotation_x(-M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_x, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_x, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_x, &((t_square*)obj)->up);
		free(rot_x);
	}
	else if (key == KEY_I)
	{
		rot_x = make_rotation_x(M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_x, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_x, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_x, &((t_square*)obj)->up);
		free(rot_x);
	}
	else if (key == KEY_U)
	{
		rot_z = make_rotation_z(-M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_z, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_z, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_z, &((t_square*)obj)->up);
		free(rot_z);
	}
	else if (key == KEY_O)
	{
		rot_z = make_rotation_z(M_PI / 45);
		((t_square*)obj)->vec = rotate_vector(rot_z, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_z, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_z, &((t_square*)obj)->up);
		free(rot_z);
	}
}

We rotate all three axes together. I realized something: when I rotate first on one axis it works but after a second rotation on another axis it doesn't match.

For example, I rotate on the X axis (it's good) then on the y axis and there I have the impression that the rotation is made around the axis (0,1,0) instead of the modified y axis.

Example (video) with a square (0,0,1). I have defined the up (0, 1, 0) and right (-1, 0, 0) vectors without the calculations of the intersection function. By displaying in the terminal each rotation, I can see that the 3 vectors are modified correctly.

ellenature said:
I realized something: when I rotate first on one axis it works but after a second rotation on another axis it doesn't match.

Video looks good. What you describe is ‘Gimbal Lock’. This is because we do 3 rotations after each other ('Euler Angles').
You could change this order by swapping code blocks to rotate in x, y and z. Currently your order is yxz. Maybe zxy would feel more intuitive to you, or any other order.

		((t_square*)obj)->vec = rotate_vector(rot_z, &((t_square*)obj)->vec);
		((t_square*)obj)->right = rotate_vector(rot_z, &((t_square*)obj)->right);
		((t_square*)obj)->up = rotate_vector(rot_z, &((t_square*)obj)->up);

You see this code repeats often. It would be worth to add a function to rotate all 3 axis vectors at once, and you could store the 3 axis in an array of 3 vectors just like the other matrices.

Yes good idea for the function to avoid repetition of the code. For now, I don't store the 3 vectors in an array, maybe I'll do it later.

Concerning the rotation order, the code blocks are not executed in sequence but only if I press the key (if condition). That is to say that according to the keys I press I can do :

  • only x,
  • or only y,
  • or only z,
  • but also x then y,
  • x then z,
  • y then x,
  • y then z,
  • z then x,
  • z then y,
  • or again x then y then z,
  • x then z then y,
  • y then x then z,
  • y then z then x,
  • z then x then y,
  • z then y then x.

So I don't necessarily do 3 rotations one after the other.

Your'e right, and i think i can imagine how it behaves / what you want.

Actually you do: object = rotation x object.

And what you want is maybe (hopefully): object = object x rotation.

So the order of multiplication / rotation reverses. Makes no difference for 2D rotation, but for 3D it does (multiplication is not commutative).

But to try this quickly, you had to store object rotation as matrix right now already. (Multiplication is the proposed rotation function as said above)

new_square->right = vec_cross(new_square->vec, new_square->up);
normalize(&new_square->right);
new_square->up = vec_cross(new_square->right, new_square->vec);
normalize(&new_square->up);

Above is the initialization of the up and right vectors of the square.

void		apply_rotation_x(t_vec3 **orientation, float angle)
{
	t_vec3	*rot_x;

	rot_x = make_rotation_x(angle);
	(*orientation)[0] = rotate_vector(*orientation, &rot_x[0]);
	(*orientation)[1] = rotate_vector(*orientation, &rot_x[1]);
	(*orientation)[2] = rotate_vector(*orientation, &rot_x[2]);
	free(rot_x);
}

void		apply_rotation_y(t_vec3 **orientation, float angle)
{
	t_vec3	*rot_y;

	rot_y = make_rotation_y(angle);
	(*orientation)[0] = rotate_vector(*orientation, &rot_y[0]);
	(*orientation)[1] = rotate_vector(*orientation, &rot_y[1]);
	(*orientation)[2] = rotate_vector(*orientation, &rot_y[2]);
	free(rot_y);
}

void		apply_rotation_z(t_vec3 **orientation, float angle)
{
	t_vec3	*rot_z;

	rot_z = make_rotation_z(angle);
	(*orientation)[0] = rotate_vector(*orientation, &rot_z[0]);
	(*orientation)[1] = rotate_vector(*orientation, &rot_z[1]);
	(*orientation)[2] = rotate_vector(*orientation, &rot_z[2]);
	free(rot_z);
}

void		move_square(void *obj, int key)
{
	t_vec3 *orientation;

	if (!(orientation = malloc(sizeof(t_vec3) * 3)))
		return ;
	orientation[0] = ((t_square*)obj)->right;
	orientation[1] = ((t_square*)obj)->up;
	orientation[2] = ((t_square*)obj)->vec;

	if (key == KEY_UP)
		((t_square*)obj)->coord.y++;
	else if (key == KEY_DOWN)
		((t_square*)obj)->coord.y--;
	else if (key == KEY_LEFT)
		((t_square*)obj)->coord.x--;
	else if (key == KEY_RIGHT)
		((t_square*)obj)->coord.x++;
	else if (key == KEY_CTRL || key == KEY_CTRL_2)
		((t_square*)obj)->coord.z--;
	else if (key == KEY_SHIFT || key == KEY_SHIFT_2)
		((t_square*)obj)->coord.z++;
	else if (key == KEY_L)
		apply_rotation_y(&orientation, M_PI / 45);
	else if (key == KEY_J)
		apply_rotation_y(&orientation, -M_PI / 45);
	else if (key == KEY_K)
		apply_rotation_x(&orientation, -M_PI / 45);
	else if (key == KEY_I)
		apply_rotation_x(&orientation, M_PI / 45);
	else if (key == KEY_U)
		apply_rotation_z(&orientation, -M_PI / 45);
	else if (key == KEY_O)
		apply_rotation_z(&orientation, M_PI / 45);
	else if (key == KEY_P)
		((t_square*)obj)->side_size++;
	else if (key == KEY_M)
		((t_square*)obj)->side_size--;

	((t_square*)obj)->right = orientation[0];
	((t_square*)obj)->up = orientation[1];
	((t_square*)obj)->vec = orientation[2];
	normalize(&((t_square*)obj)->right);
	normalize(&((t_square*)obj)->up);
	normalize(&((t_square*)obj)->vec);
}

Wooowww it's working!!!! That's awesome! Thank you so much JoeJ !

I used the same functions for the cylinder and the camera and it works too.


I use malloc because I couldn't pass as argument a t_vec3 *[3] to the void function apply_rotation_y(t_vec3 **orientation, float angle); which expects a t_vec3 ** (by declaring t_vec3 orientation[3]).

What do you think about the way I move objects (KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_CTRL, KEY_SHIFT in the if) ?

new_cam->right = (fabs(new_cam->vec.y) > 0.7) ? vec_cross((t_vec3){0, 0, -1}, get_normalised(invert(new_cam->vec))) : vec_cross((t_vec3){0, 1, 0}, get_normalised(invert(new_cam->vec)));
new_cam->up = vec_cross(get_normalised(invert(new_cam->vec)), new_cam->right);

Above is the initialization of the up and right vectors of the cylinder.

void	move_cam(t_specs *s, int key)
{
	t_vec3 up;
	t_vec3 *orientation;

	if (!(orientation = malloc(sizeof(t_vec3) * 3)))
		return ;
	orientation[0] = s->current_cam->right;
	orientation[1] = s->current_cam->up;
	orientation[2] = s->current_cam->vec;

	up = vec_cross(s->current_cam->vec, s->current_cam->right);
	if (key == KEY_W)
		s->current_cam->coord = vec_add(s->current_cam->coord, s->current_cam->up);
	else if (key == KEY_S)
		s->current_cam->coord = vec_add(s->current_cam->coord, invert(s->current_cam->up));
	else if (key == KEY_A)
		s->current_cam->coord = vec_add(s->current_cam->coord, invert(s->current_cam->right));
	else if (key == KEY_D)
		s->current_cam->coord = vec_add(s->current_cam->coord, s->current_cam->right);
	else if (key == KEY_Q)
		s->current_cam->coord = vec_add(s->current_cam->coord, invert(s->current_cam->vec));
	else if (key == KEY_E)
		s->current_cam->coord = vec_add(s->current_cam->coord, s->current_cam->vec);
	else if (key == KEY_H)
		apply_rotation_y(&orientation, -M_PI / 45);
	else if (key == KEY_F)
		apply_rotation_y(&orientation, M_PI / 45);
	else if (key == KEY_G && s->current_cam->vec.y > -1)
		apply_rotation_x(&orientation, -M_PI / 45);
	else if (key == KEY_T && s->current_cam->vec.y < 1)
		apply_rotation_x(&orientation, M_PI / 45);
	else if (key == KEY_R)
		apply_rotation_z(&orientation, -M_PI / 45);
	else if (key == KEY_Y)
		apply_rotation_z(&orientation, M_PI / 45);

	s->current_cam->right = orientation[0];
	s->current_cam->up = orientation[1];
	s->current_cam->vec = orientation[2];
	normalize(&s->current_cam->right);
	normalize(&s->current_cam->up);
	normalize(&s->current_cam->vec);
}

This topic is closed to new replies.

Advertisement