QuatP* pivot_between_orientation(const struct Pivot* pivot1, const struct Pivot* pivot2, Quat between_rotation) { Quat inverted_from; quat_invert(pivot1->orientation, inverted_from); quat_mul(pivot2->orientation, inverted_from, between_rotation); return between_rotation; }
MatP* pivot_local_transform(const struct Pivot* pivot, Mat local_transform) { Mat rotation = {0}; quat_invert(pivot->orientation, rotation); mat_rotate(NULL, rotation, rotation); Mat translation = {0}; vec_invert(pivot->position, translation); mat_translate(NULL, translation, translation); mat_mul(translation, rotation, local_transform); return local_transform; }
bool arcball_event(struct Arcball* arcball, SDL_Event event) { static int32_t mouse_down = 0; static const float rotation_slowness_factor = 0.25f; static int32_t next_flipped = 0; // - arcball rotation is performed by dragging the mouse, so I just keep track of when // a mouse button is pressed and released between calls to this function by setting a // static variable mouse_down to the button number when a button is pressed and back // to 0 when that button is released if( event.type == SDL_MOUSEBUTTONDOWN && mouse_down == 0 ) { mouse_down = event.button.button; } else if( event.type == SDL_MOUSEBUTTONUP && mouse_down == event.button.button ) { arcball->flipped = next_flipped; mouse_down = 0; } if( mouse_down == arcball->translate_button && event.type == SDL_MOUSEMOTION ) { SDL_MouseMotionEvent mouse = event.motion; float eye_distance = arcball->camera.pivot.eye_distance; // - when an mouse motion event occurs, and mouse_down to the translation_button so we compute // a camera translation // - the camera should pan around on the x-z-plane, keeping its height and orientation Quat inverted_orientation = {0}; quat_invert(arcball->camera.pivot.orientation, inverted_orientation); // - the sideways translation is computed by taking the right_axis and orienting it with // the cameras orientation, the way I set up the lookat implementation this should always // result in a vector parallel to the x-z-plane Vec4f right_axis = RIGHT_AXIS; vec_rotate4f(right_axis, inverted_orientation, right_axis); if( mouse.xrel != 0 ) { // - then we'll just multiply the resulting axis with the mouse x relative movement, inversely // scaled by how far we are away from what we are looking at (farer means faster, nearer // means slower), the translation_factor is just a value that felt good when this was implemented Vec4f x_translation = {0}; vec_mul1f(right_axis, (float)mouse.xrel/arcball->translate_factor*eye_distance, x_translation); // - finally just add the x_translation to the target and position so that the whole arcball moves vec_add(arcball->target, x_translation, arcball->target); vec_add(arcball->camera.pivot.position, x_translation, arcball->camera.pivot.position); } // - the z translation can't be done along the orientated forward axis because that would include // the camera pitch, here, same as above, we need an axis that is parallel to the x-z-plane Vec4f up_axis = UP_AXIS; if( mouse.yrel != 0 ) { // - luckily such an axis is easily computed from the crossproduct of the orientated right_axis and // the default up_axis, the result is an axis pointing in the direction of the cameras forward axis, // while still being parallel to the x-z-plane Vec4f forward_axis; vec_cross(right_axis, up_axis, forward_axis); // - same as above Vec4f z_translation; vec_mul1f(forward_axis, (float)mouse.yrel/arcball->translate_factor*eye_distance, z_translation); // - dito vec_add(arcball->target, z_translation, arcball->target); vec_add(arcball->camera.pivot.position, z_translation, arcball->camera.pivot.position); } } else if( mouse_down == arcball->rotate_button && event.type == SDL_MOUSEMOTION ) { SDL_MouseMotionEvent mouse = event.motion; // - above case was translation, this is an rotation occuring: mouse_down is equal to the // rotation_button and the event is a mouse motion // - the camera needs to rotate around the target while keeping its height, orientation and // without _any_ rolling // - we want only yaw and pitch movement // - yaw is easy, just use the fixed up_axis and create a rotation the rotates around it // by the mouse x relative movement (converted to radians) // - the flipped value indicates if the camera is flipped over, so we'll just use that to // change the sign of the yaw to make the mouse movement on the screen always correctly // relates to the movement of the rotation Vec4f up_axis = UP_AXIS; Quat yaw_rotation = {0}; quat_from_axis_angle(up_axis, arcball->flipped * PI/180 * mouse.xrel * rotation_slowness_factor, yaw_rotation); // - pitch is a little more involved, I need to compute the orientated right axis and use // that to compute the pitch_rotation Quat inverted_orientation = {0}; quat_invert(arcball->camera.pivot.orientation, inverted_orientation); Vec4f right_axis = RIGHT_AXIS; vec_rotate4f(right_axis, inverted_orientation, right_axis); Quat pitch_rotation = {0}; quat_from_axis_angle(right_axis, -PI/180 * mouse.yrel * rotation_slowness_factor, pitch_rotation); // - combine yaw and pitch into a single rotation Quat rotation = {0}; quat_mul(yaw_rotation, pitch_rotation, rotation); // - orbit is the position translated to the coordinate root // - the yaw and pitch rotation is applied to the orbit // - orbit is translated back and replaces the camera position Vec4f orbit = {0}; vec_sub(arcball->camera.pivot.position, arcball->target, orbit); vec_rotate4f(orbit, rotation, orbit); vec_add(arcball->target, orbit, arcball->camera.pivot.position); // - after updating the position we just call lookat to compute the new // orientation, and also set the flipped state next_flipped = pivot_lookat(&arcball->camera.pivot, arcball->target); } if( event.type == SDL_MOUSEWHEEL ) { SDL_MouseWheelEvent wheel = event.wheel; // - zooming when mouse wheel event happens float* eye_distance = &arcball->camera.pivot.eye_distance; if( (*eye_distance > arcball->camera.frustum.z_near || wheel.y < 0) && (*eye_distance < arcball->camera.frustum.z_far || wheel.y > 0)) { // - just going back and forth along the oriented forward axis, using wheel // y motion inversly scaled by the eye_distance, similar to what is done // for the translation above (farer == faster zoom, nearer == slower zoom) Quat inverted_orientation = {0}; quat_invert(arcball->camera.pivot.orientation, inverted_orientation); Vec4f forward_axis = FORWARD_AXIS; vec_rotate4f(forward_axis, inverted_orientation, forward_axis); Vec4f zoom = {0}; vec_mul1f(forward_axis, wheel.y/arcball->zoom_factor*(*eye_distance), zoom); vec_add(arcball->camera.pivot.position, zoom, arcball->camera.pivot.position); // - eye_distance is kept in camera state, so we need to update it here *eye_distance = vlength(arcball->camera.pivot.position); } } return true; }
int32_t pivot_lookat(struct Pivot* pivot, const Vec3f target) { int32_t result = -1; Vec4f right_axis = RIGHT_AXIS; Vec4f up_axis = UP_AXIS; Vec4f forward_axis = FORWARD_AXIS; struct Pivot world_pivot; pivot_combine(pivot->parent, pivot, &world_pivot); Vec4f target_direction; vec_sub(target, world_pivot.position, target_direction); float dot_yaw = vec_dot(target_direction, forward_axis); Quat rotation = {0}; if( fabs(dot_yaw + 1.0f) < CUTE_EPSILON ) { // vector a and b point exactly in the opposite direction, // so it is a 180 degrees turn around the up-axis Quat rotation = {0}; quat_from_axis_angle(up_axis, PI, rotation); quat_mul(world_pivot.orientation, rotation, rotation); } else if( fabs(dot_yaw - (1.0f)) < CUTE_EPSILON ) { // vector a and b point exactly in the same direction // so we return the identity quaternion quat_copy(world_pivot.orientation, rotation); } else { // - I look at the target by turning the pivot orientation using only // yaw and pitch movement // - I always rotate the pivot from its initial orientation (up and forward // basis vectors like initialized above), so this does not incrementally // advance the orientation quat_identity(rotation); // - to find the amount of yaw I project the target_direction into the // up_axis plane, resulting in up_projection which is a vector that // points from the up_axis plane to the tip of the target_direction Vec4f up_projection = {0}; vec_mul1f(up_axis, vec_dot(target_direction, up_axis), up_projection); // - so then by subtracting the up_projection from the target_direction, // I get a vector lying in the up_axis plane, pointing towards the target Vec4f yaw_direction = {0}; vec_sub(target_direction, up_projection, yaw_direction); // - angle between yaw_direction and forward_axis is the amount of yaw we // need to point the forward_axis toward the target float yaw = 0.0f; vec_angle(yaw_direction, forward_axis, &yaw); log_assert( ! isnan(yaw), "vec_angle(%f %f %f, %f %f %f, %f);\n", yaw_direction[0], yaw_direction[1], yaw_direction[2], forward_axis[0], forward_axis[1], forward_axis[2], yaw ); // - I have to compute the cross product between yaw_direction and // forward_axis and use the resulting yaw_axis Vec4f yaw_axis = {0}; vec_cross(yaw_direction, forward_axis, yaw_axis); if( vec_nullp(yaw_axis) ) { vec_copy4f(up_axis, yaw_axis); } // - compute the yaw rotation Quat yaw_rotation = {0}; quat_from_axis_angle(yaw_axis, yaw, yaw_rotation); // - to compute, just as with the yaw, I want an axis that lies on the plane that // is spanned in this case by the right_axis, when the camera points toward the // target // - I could compute an axis, but I already have a direction vector that points // toward the target, the yaw_direction, I just have to normalize it to make it // an axis (and put the result in forward_axis, since it now is the forward_axis // of the yaw turned camera) Vec4f yaw_forward_axis = {0}; vec_normalize(yaw_direction, yaw_forward_axis); // - then use the new forward axis with the old target_direction to compute the angle // between those float pitch = 0.0f; vec_angle(target_direction, yaw_forward_axis, &pitch); log_assert( ! isnan(pitch), "vec_angle(%f %f %f, %f %f %f, %f);\n", target_direction[0], target_direction[1], target_direction[2], yaw_forward_axis[0], yaw_forward_axis[1], yaw_forward_axis[2], pitch ); // - and just as in the yaw case we compute an rotation pitch_axis Vec4f pitch_axis = {0}; vec_cross(target_direction, yaw_forward_axis, pitch_axis); if( vec_nullp(pitch_axis) ) { vec_copy4f(right_axis, pitch_axis); } // - and finally compute the pitch rotation and combine it with the yaw_rotation // in the same step Quat pitch_rotation; quat_from_axis_angle(pitch_axis, pitch, pitch_rotation); Quat yaw_pitch_rotation; quat_mul(yaw_rotation, pitch_rotation, yaw_pitch_rotation); Quat inverted_orientation = {0}; quat_invert(world_pivot.orientation, inverted_orientation); // - the int32_t I want to return indicates the cameras 'flip' status, that is, it is // one when the camera angle was pitched so much that it flipped over and its // up axis is now pointing downwards // - to find out if I am flipped over, I compute the flipped up_axis called // flip_axis and then use the dot product between the flip_axis and up_axis // to decide if I am flipped Vec4f flip_axis = {0}; vec_rotate(up_axis, inverted_orientation, flip_axis); vec_rotate(flip_axis, yaw_pitch_rotation, flip_axis); float dot_pitch = vec_dot(up_axis, flip_axis); Vec4f target_axis = {0}; vec_normalize(target_direction, target_axis); // - check if we are flipped and if we are, set result to 1 meaning we are flipped // - turn the camera around PI so that we can continue pitching, otherwise we just get // stuck when trying to flip the camera over if( dot_pitch < 0.0f ) { result = 1; quat_from_axis_angle(target_axis, PI, rotation); quat_mul(yaw_pitch_rotation, rotation, yaw_pitch_rotation); } quat_copy(yaw_pitch_rotation, rotation); } if( ! isnan(rotation[0]) && ! isnan(rotation[1]) && ! isnan(rotation[2]) && ! isnan(rotation[3]) ) { quat_copy(rotation, pivot->orientation); } pivot->eye_distance = vec_length(target_direction); return result; }