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; }
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 main(int32_t argc, char *argv[]) { printf("<<watchlist//>>\n"); if( init_sdl2() ) { return 1; } int32_t width = 1280; int32_t height = 720; SDL_Window* window; sdl2_window("cute3d: " __FILE__, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, &window); SDL_GLContext* context; sdl2_glcontext(3, 2, window, &context); if( init_shader() ) { return 1; } if( init_vbo() ) { return 1; } if( init_canvas(1280,720) ) { return 1; } canvas_create("global_dynamic_canvas", &global_dynamic_canvas); canvas_create("global_static_canvas", &global_static_canvas); struct Vbo vbo = {0}; vbo_create(&vbo); vbo_add_buffer(&vbo, SHADER_ATTRIBUTE_VERTEX, 3, GL_FLOAT, GL_STATIC_DRAW); vbo_add_buffer(&vbo, SHADER_ATTRIBUTE_VERTEX_NORMAL, 3, GL_FLOAT, GL_STATIC_DRAW); vbo_add_buffer(&vbo, SHADER_ATTRIBUTE_VERTEX_COLOR, 4, GL_UNSIGNED_BYTE, GL_STATIC_DRAW); struct Ibo ibo = {0}; ibo_create(GL_TRIANGLES, GL_UNSIGNED_INT, GL_STATIC_DRAW, &ibo); struct CollisionEntity entity_a = {0}; entity_create("red", (Color){ 255, 0, 0, 255 }, &vbo, &ibo, &entity_a); /* quat_mul_axis_angle(entity_a.pivot.orientation, (Vec4f)UP_AXIS, PI/4, entity_a.pivot.orientation); */ /* quat_mul_axis_angle(entity_a.pivot.orientation, (Vec4f)RIGHT_AXIS, PI/2 + 0.2, entity_a.pivot.orientation); */ vec_add(entity_a.pivot.position, (Vec4f){0.2, 0.15, 0.8, 1.0}, entity_a.pivot.position); struct CollisionEntity entity_b = {0}; entity_create("green", (Color){ 0, 255, 0, 255 }, &vbo, &ibo, &entity_b); quat_mul_axis_angle(entity_b.pivot.orientation, (Vec4f)RIGHT_AXIS, PI/4 - 0.2, entity_b.pivot.orientation); quat_mul_axis_angle(entity_b.pivot.orientation, (Vec4f)UP_AXIS, PI/2 + 0.0, entity_b.pivot.orientation); struct Shader flat_shader = {0}; shader_create(&flat_shader); shader_attach(&flat_shader, GL_VERTEX_SHADER, "prefix.vert", 1, "flat_shading.vert"); shader_attach(&flat_shader, GL_FRAGMENT_SHADER, "prefix.frag", 1, "flat_shading.frag"); shader_make_program(&flat_shader, SHADER_DEFAULT_NAMES, "flat_shader"); Vec4f light_direction = { 0.2, -0.5, -1.0 }; shader_set_uniform_3f(&flat_shader, flat_shader.program, SHADER_UNIFORM_LIGHT_DIRECTION, 3, GL_FLOAT, light_direction); Color ambiance = { 65, 25, 50, 255 }; shader_set_uniform_4f(&flat_shader, flat_shader.program, SHADER_UNIFORM_AMBIENT_LIGHT, 4, GL_UNSIGNED_BYTE, ambiance); struct Arcball arcball = {0}; arcball_create(width, height, (Vec4f){5.0, 3.0, 5.0, 1.0}, (Vec4f){0.0, 0.0, 0.0, 1.0}, 1.0, 1000.0, &arcball); size_t num_entities = 2; struct PickingSphere* picking_spheres[2]; picking_spheres[0] = &entity_a.picking_sphere; picking_spheres[1] = &entity_b.picking_sphere; struct CollisionEntity* picking_entities[2]; picking_entities[0] = &entity_a; picking_entities[1] = &entity_b; struct GameTime time = {0}; gametime_create(1.0f / 60.0f, &time); draw_grid(&global_static_canvas, 0, (Mat)IDENTITY_MAT, (Color){127, 127, 127, 127}, 0.01f, 12.0f, 12.0f, 12); while (true) { SDL_Event event; while( sdl2_poll_event(&event) ) { if( sdl2_handle_quit(event) ) { goto done; } sdl2_handle_resize(event); if( picking_sphere_drag_event(&arcball.camera, num_entities, picking_spheres, event) ) { struct CollisionEntity* selected_entity = NULL; float nearest = -FLT_MIN; for( size_t i = 0; i < num_entities; i++ ) { if( picking_spheres[i]->picked && ( picking_spheres[i]->front < nearest || nearest < 0.0f ) ) { nearest = picking_spheres[i]->front; selected_entity = picking_entities[i]; } } static int32_t last_x = -1; static int32_t last_y = -1; if( selected_entity != NULL ) { if( last_x > -1 && last_y > -1 ) { float distance = selected_entity->picking_sphere.front; Vec4f a = {0}; camera_ray(&arcball.camera, CAMERA_PERSPECTIVE, last_x, last_y, a); vec_mul1f(a, distance, a); Vec4f b = {0}; camera_ray(&arcball.camera, CAMERA_PERSPECTIVE, event.motion.x, event.motion.y, b); vec_mul1f(b, distance, b); Vec4f move = {0}; vec_sub(b, a, move); float length = vec_length(move); move[1] = 0.0f; vec_normalize(move, move); vec_mul1f(move, length, move); vec_add(selected_entity->pivot.position, move, selected_entity->pivot.position); } last_x = event.motion.x; last_y = event.motion.y; } if( event.type == SDL_MOUSEBUTTONUP ) { last_x = -1; last_y = -1; } } else { arcball_handle_resize(&arcball, event); arcball_handle_mouse(&arcball, event); } } sdl2_gl_set_swap_interval(0); gametime_advance(&time, sdl2_time_delta()); ogl_debug( glClearDepth(1.0f); glClearColor(.0f, .0f, .0f, 1.0f); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); ); Mat projection_mat = {0}; Mat view_mat = {0}; camera_matrices(&arcball.camera, CAMERA_PERSPECTIVE, projection_mat, view_mat); Mat identity = {0}; mat_identity(identity); Mat transform_a = {0}; pivot_world_transform(&entity_a.pivot, transform_a); //vbo_mesh_render(&entity_a.vbo_mesh, &flat_shader, &arcball.camera, transform_a); draw_halfedgemesh_wire(&global_dynamic_canvas, 0, transform_a, (Color){255, 0, 0, 255}, 0.02f, &entity_a.hemesh); Mat transform_b = {0}; pivot_world_transform(&entity_b.pivot, transform_b); //vbo_mesh_render(&entity_b.vbo_mesh, &flat_shader, &arcball.camera, transform_b); draw_halfedgemesh_wire(&global_dynamic_canvas, 0, transform_b, (Color){0, 255, 0, 255}, 0.02f, &entity_b.hemesh); Mat between_transform = {0}; pivot_between_transform(&entity_a.pivot, &entity_b.pivot, between_transform); Vec3f foo = {0}; mat_mul_vec(between_transform, entity_a.hemesh.vertices.array[0].position, foo); struct CollisionConvexConvex collision = {0}; struct CollisionParameter collision_parameter = { .face_tolerance = 0.9, .edge_tolerance = 0.95, .absolute_tolerance = 0.025 }; collision_create_convex_convex(&entity_a.hemesh, &entity_a.pivot, &entity_b.hemesh, &entity_b.pivot, collision_parameter, &collision); if( collision_test_convex_convex(&collision) ) { collision_contact_convex_convex(&collision); //printf("//collision: %d\n", collision_counter); const struct Contacts* contacts = &collision.contacts; VecP* m = contacts->points[contacts->num_contacts-1]; for( int32_t i = 0; i < contacts->num_contacts; i++ ) { VecP* n = contacts->points[i]; draw_line(&global_dynamic_canvas, 0, transform_b, (Color){255, 255, 255, 255}, 0.08f, m, n); m = n; } } gametime_integrate(&time); Vec4f screen_cursor = {0,0,0,1}; text_show_fps(&global_dynamic_canvas, 0, screen_cursor, 0, 0, (Color){255, 255, 255, 255}, 20.0, "default_font", time.frame); canvas_render_layers(&global_static_canvas, 0, 0, &arcball.camera, (Mat)IDENTITY_MAT); canvas_render_layers(&global_dynamic_canvas, 0, 0, &arcball.camera, (Mat)IDENTITY_MAT); canvas_clear(&global_dynamic_canvas); sdl2_gl_swap_window(window); } done: SDL_Quit(); printf("done\n"); return 0; }