Rotate object in world space - Raytracing

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

Whoooo! :D

(I remember i was excited too when making rotating cube work ; )

ellenature said:
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]).

void make_rotation_x(t_vec3* rot_x, float angle)
{
	float	c;
	float	s;
	
	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};
}

void multiply_matrix3x3 (t_vec3* result, t_vec3* lhs, t_vec3* rhs)
{
	result[0] = rotate_vector(lhs, rhs[0]);
	result[1] = rotate_vector(lhs, rhs[1]);
	result[2] = rotate_vector(lhs, rhs[2]);
}

void copy_matrix3x3 (t_vec3* dst, t_vec3* src)
{
	dst[0] = src[0];
	dst[1] = src[1];
	dst[2] = src[2];
}

void apply_rotation_x(t_vec3* orientation, float angle)
{
	t_vec3 matrix[3];
	t_vec3 result;
	
	make_rotation_x(matrix, angle)
;
	multiply_matrix3x3 (result, matrix, orientation);
	copy_matrix3x3 (orientation, result);
}

Just an example. You can also write ‘t_vec3[3] dst’ instead 't_vec3* dst', both give pointers not copies.

	normalize(&s->current_cam->right);
	normalize(&s->current_cam->up);
	normalize(&s->current_cam->vec);

The length of vectors does not change from rotation, so normalization is not necessary if you do it just once.

But floating point error creeps in after many operations. But then, ensuring unit length is not enough. You also need to make sure the axis remain orthonormal to each other, using series of cross products.

ellenature said:
What do you think about the way I move objects

Yeah that's good.

Though instead writing:

s->current_cam->coord = vec_add(s->current_cam->coord, invert(s->current_cam->up));

i would prefer just:

s->current_cam->coord = vec_sub(s->current_cam->coord, s->current_cam->up);

Advertisement

Yes it's really very exciting when the rotations match the axis ! :D

Instead of writing:
multiply_matrix3x3 (result, matrix, orientation);

I wrote this down or else I'd have errors:
multiply_matrix3x3(&result, orientation, matrix);

JoeJ said:
is not necessary if you do it just once

I don't understand the meaning of the above quote.


How do you do that ? :

JoeJ said:
You also need to make sure the axis remain orthonormal to each other, using series of cross products.

ellenature said:
What do you think about the way I move objects
JoeJ said:
Yeah that's good.

Okay with the camera, but are you also okay with the way I move the square ? Shouldn't we use translation matrices ?

ellenature said:
Instead of writing: multiply_matrix3x3 (result, matrix, orientation); I wrote this down or else I'd have errors: multiply_matrix3x3(&result, orientation, matrix);

Oh, probably becasue i forgot to make my ‘result’ an array, so this should fix it properly:

void apply_rotation_x(t_vec3* orientation, float angle)
{
	t_vec3 matrix[3];
	t_vec3 result[3];
	
	make_rotation_x(matrix, angle)
;
	multiply_matrix3x3 (result, matrix, orientation);
	copy_matrix3x3 (orientation, result);
}

ellenature said:
I don't understand the meaning of the above quote.

If you rotate it just once or a few times, there is no need to ortho-normalize after each rotation because error is guaranteed to be small.

If you would rotate your square a million times, it's axis vectors will no longer have exact right angles between them. Because of limited floating point accuracy. To fix it:

void ortho_normalize_matrix3x3 (t_vec3* matrix)
 // todo: fix me, i'm probably buggy
{
	normalize(matrix[0]); // keep x axis direction but ensure unit length
	matrix[1] = cross(matrix[0], 
matrix[2]); // now y is guaranteed to be orthonormal to x (right angle)
	normalize(matrix[1]);
	matrix[2] = cross(matrix[1], 
matrix[0]); // now z is guaranteed to be orthonormal to both x and y and unit legth too
}

Problem is i have to guess the order in the cross products each time and fail to memorize, so very likely the above code flips y or z axis. Reverse those until result is equal to some input to verify this.

Shouldn't we use translation matrices ?

You could, but you don't have to. Using 3x3 matrix and another vector for translation is enough and 4x4 matrix does not give any new options.

I have to invert the order of matrix and orientation (as below) as arguments to avoid faulty rotations :

mult_matrix(res, orientation, matrix);

JoeJ said:
If you would rotate your square a million times

All right, but I don't think I'm going to rotate it a million times, I think I'm going to rotate it a few hundred or a few thousand times. So it's not necessarily necessary in my case if I don't do a large number of rotations ?
However, indeed, it is interesting to know for a future use that from a large number of rotations, its vector axes will no longer have exact right angles between them.

I have to invert the order of matrix and orientation (as below) as arguments to avoid faulty rotations :

Yeah that's expected. I always need to trial and error in such cases.

I don't know how many rotations it takes until error becomes noticeable.
Within a physics simulator normalization is necessary, for example.

For a typical graphics application with user interaction, we usually have a transform hierarchy made from editable SRT values (Scale, Rotation, Transformation).
And the matrices are then recalculated from those values on a change so no drifting happens.

But it's not that important for now. If you experience issues you know what might cause them…

Thank you so much for your precious help, JoeJ.
You were able to help me on a daily basis.
You are an indispensable and very useful member of the community.

Some people wear Superman pyjama. Superman wears JoeJ pyjama.

Hey JoeJ,

I wanted to use the same method to rotate the camera but I have a problem.

When I rotate by combining two axis (on the x then y axis, or the y then x axis), the rotation is defective.

I tested hardcoding initialized camera axis and the problem occurs too :

  • vec (0, 0, -1)
  • right (1, 0, 0)
  • up (0, 1, 0)

void		move_cam(t_specs *s, int key)
{
	t_camera	*cam;
	t_vec3		orientation[3];
	float		angle;

	angle = 4.5 * (M_PI / 180);
	cam = s->current_cam;
	orientation[0] = cam->right;
	orientation[1] = cam->up;
	orientation[2] = cam->vec;
	if (key == KEY_W)
		cam->coord = vec_add(cam->coord, cam->up);
	else if (key == KEY_S)
		cam->coord = vec_sub(cam->coord, cam->up);
	else if (key == KEY_A)
		cam->coord = vec_sub(cam->coord, cam->right);
	else if (key == KEY_D)
		cam->coord = vec_add(cam->coord, cam->right);
	else if (key == KEY_Q)
		cam->coord = vec_sub(cam->coord, cam->vec);
	else if (key == KEY_E)
		cam->coord = vec_add(cam->coord, cam->vec);
	else if (key == KEY_H)
		apply_rotation_y(orientation, -angle);
	else if (key == KEY_F)
		apply_rotation_y(orientation, angle);
	else if (key == KEY_G)
		apply_rotation_x(orientation, -angle);
	else if (key == KEY_T)
		apply_rotation_x(orientation, angle);
	else if (key == KEY_R)
		apply_rotation_z(orientation, -angle);
	else if (key == KEY_Y)
		apply_rotation_z(orientation, angle);
	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);

Seems this is the opposite problem from what you've had before.

Before you did rotate objects around their local axis, but you wanted to rotate them around global space axis instead. Reversing multiplication order had fixed it, AFAIK.

And now the camera rotates around world axis, but you want to rotate tit around local camera axis (like a first person shooter game). So, reversing multiplication order again but only for camera might fix that too.

I think you could try this quickly by changing this:

void apply_rotation_x(t_vec3* orientation, float angle)
{
	t_vec3 matrix[3];
	t_vec3 result[3];
	
	make_rotation_x(matrix, angle)
;

	//multiply_matrix3x3 (result, matrix, orientation);
	multiply_matrix3x3 (result, orientation, matrix);

	copy_matrix3x3 (orientation, result);
}

And if it helps (not sure) make two functions out if it e.g. apply_rotationx_local and applyrotationx global

Hello,

After reversing the multiplication order for the camera, the rotation is not correct.

I pivot up, then left, then down.

void		move_cam(t_specs *s, int key)
{
	t_camera	*cam;
	t_vec3		orientation[3];
	float		angle;

	angle = 4.5 * (M_PI / 180);
	cam = s->current_cam;
	orientation[0] = cam->right;
	orientation[1] = cam->up;
	orientation[2] = cam->vec;
	if (key == KEY_W)
		cam->coord = vec_add(cam->coord, cam->up);
	else if (key == KEY_S)
		cam->coord = vec_sub(cam->coord, cam->up);
	else if (key == KEY_A)
		cam->coord = vec_sub(cam->coord, cam->right);
	else if (key == KEY_D)
		cam->coord = vec_add(cam->coord, cam->right);
	else if (key == KEY_Q)
		cam->coord = vec_sub(cam->coord, cam->vec);
	else if (key == KEY_E)
		cam->coord = vec_add(cam->coord, cam->vec);
	else if (key == KEY_H)
		apply_rotation_y_2(orientation, angle);
	else if (key == KEY_F)
		apply_rotation_y_2(orientation, -angle);
	else if (key == KEY_G)
		apply_rotation_x_2(orientation, angle);
	else if (key == KEY_T)
		apply_rotation_x_2(orientation, -angle);
	else if (key == KEY_R)
		apply_rotation_z_2(orientation, angle);
	else if (key == KEY_Y)
		apply_rotation_z_2(orientation, -angle);
	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);
}
void	apply_rotation_x_2(t_vec3 *orientation, float angle)
{
	t_vec3	matrix[3];
	t_vec3	res[3];

	make_rotation_x(matrix, angle);
	mult_matrix(res, matrix, orientation);
	cpy_matrix(orientation, res);
}

void	apply_rotation_y_2(t_vec3 *orientation, float angle)
{
	t_vec3	matrix[3];
	t_vec3	res[3];

	make_rotation_y(matrix, angle);
	mult_matrix(res, matrix, orientation);
	cpy_matrix(orientation, res);
}

void	apply_rotation_z_2(t_vec3 *orientation, float angle)
{
	t_vec3	matrix[3];
	t_vec3	res[3];

	make_rotation_z(matrix, angle);
	mult_matrix(res, matrix, orientation);
	cpy_matrix(orientation, res);
}

Too bad. I don't really wonder it does not work, but doing it right likely is some more work.

I'll post my own camera code below. That's just some hacky stuff so i can navigate through scene, but it behaves like anybody would expect.

Maybe the trick is to store euler angles, and reconstruct the matrix from that on every change:

		euler = orientation;
		world  = glm::eulerAngleZ(orientation[2]);
		world *= glm::eulerAngleY(orientation[1]);
		world *= glm::eulerAngleX(orientation[0]);

The order of rotation is important to get the expected FPS game behavior. For me it's ZYX, and Y is my up axis.

On mouse input or key presses, i change the euler angles and then recalculate the whole matrix.

Then there is the fact i use the inverse of the resulting matrix to setup camera projection. I'm not sure if this is necessary for you too.
Maybe you'r current approach would work if you do it like this:

matrix temp = inverse(current camera)

// apply rotations to temp, trying both multiplication orders

current camera = inverse (temp)

In practice i often need to try 4 cases, resulting from uncertainty about inversion and multiplication order. (To defend myself, my uncertainty came from using different math libs with different conventions)

Inverting a matrix means for example: matrix a transforms stuff from world to object space, then inverse(a) transforms from object to world space.

For a 3x3 rotation matrix, inversion is equivalenmt to transpose which is easy to do just by swapping rows and columns:

void TransposeMat3x3 (vec *dst, vec[3] src)
{
for (int i=0; i<3; i++) for (int j=0; j<3; j++) 
dst[i][j] = src[j][i];
}

(if your vectors allow to index dimension by number)

struct Camera
{
	glm::mat4 world;
	glm::mat4 worldInverse;
	glm::mat4 projection;
	glm::vec3 euler;

	float fov;
	float firstPersonSpeed;
	float firstPersonSensibility;
	float nearClip;
	float farClip;
	float aspect;
	

	void Init (bool fromFile = true)
	{
#if 1 // persp
		fov = 60.0f;
		firstPersonSpeed = 1.0f;
		firstPersonSensibility = 0.1f;
		aspect = 1.0f;
		nearClip = 0.01f;
		farClip = 100.0f;
#else // ortho
		fov = -2.5f;
		firstPersonSpeed = 7.0f;
		firstPersonSensibility = 0.03f;
		aspect = 1.0f;
		nearClip = 10;
		farClip = 13;
#endif

		world = glm::mat4();
		worldInverse = glm::mat4(); 
		euler = glm::vec3();
		UpdateProjection ();

		UpdateWorld (glm::vec3(1, 0, 7), glm::vec3(0, 0, 0)); // front
	}

	Camera ()
	{
		Init();
		LoadState("C:\\dev\\pengII\\data\\camDef.bin");
	}
	
	~Camera ()
	{
		SaveState("C:\\dev\\pengII\\data\\camDef.bin");
	}
	void UpdateWorld (const glm::vec3 position, const glm::vec3 orientation)
	{
		glm::vec3 pos = position;
		euler = orientation;
		world  = glm::eulerAngleZ(orientation[2]);
		world *= glm::eulerAngleY(orientation[1]);
		world *= glm::eulerAngleX(orientation[0]);
		world[3] = glm::vec4 (pos, 1.0f);

		worldInverse = glm::inverse (world);
	}

	void MoveFirstPerson (const glm::vec3 translation, glm::vec3 rotation)
	{
		UpdateWorld (
			glm::vec3(world[3] + world * glm::vec4(translation * firstPersonSpeed, 0.0f)), 
			euler + rotation * firstPersonSensibility * 0.01f);
	}

	void UpdateProjection ()
	{
		float fovY = fov / 180.0f * float(M_PI);
		if (fov > 0.0f)
		{
			if (farClip <= 0)
				projection = glm::infinitePerspective (fovY, aspect, nearClip);
			else
				projection = glm::perspective (fovY, aspect, nearClip, farClip);
		}
		else
			projection = glm::ortho (fov*aspect, -fov*aspect, fov, -fov, -nearClip, farClip);
		projection[1] *= -1.f;

		glm::mat4 mvp;
		
		
		mvp = projection * worldInverse * glm::mat4(1.f);
		*((glm::mat4*)simpleVis.MVP) = mvp;
	}

	void SaveState(char *filename)
	{
		FILE* file = fopen(filename, "wb");
		if (file)
		{
			fwrite(this, sizeof(Camera), 1, file);
			fclose(file);
		}
	};
	void LoadState(char *filename)
	{
		FILE* file = fopen(filename, "rb");
		if (file)
		{
			fread(this, sizeof(Camera), 1, file);
			fclose(file);
		}
	};
};

This topic is closed to new replies.

Advertisement