/* render/camera.cc This file is part of the Osirion project and is distributed under the terms and conditions of the GNU General Public License version 2 */ #include "core/core.h" #include "math/mathlib.h" #include "math/matrix4f.h" #include "render/camera.h" #include "render/gl.h" #include "sys/sys.h" using math::degrees360f; using math::degrees180f; namespace render { const float MIN_DELTA = 10e-10; const float pitch_track = -15.0f; const float pitch_overview = -75.0f; float Camera::camera_aspect = 1.0f; math::Vector3f Camera::camera_eye; math::Vector3f Camera::camera_target; math::Axis Camera::camera_axis; Camera::Mode Camera::camera_mode; // current and target yaw angle in XZ plane, positive is looking left float Camera::direction_current; float Camera::direction_target; float Camera::target_direction; // current and target pitch angle in XY, positive is looking up float Camera::pitch_current; float Camera::pitch_target; float Camera::target_pitch; float Camera::distance; void Camera::init() { camera_aspect = 1.0f; direction_current = 0; direction_target = 0; pitch_current = pitch_track * 2; pitch_target = pitch_track; target_pitch = 0.0f; target_direction = 0.0f; distance = 0.4f; set_mode(Track); camera_axis.clear(); camera_eye.clear(); camera_target.clear(); } void Camera::shutdown() { } void Camera::set_aspect(float aspect) { camera_aspect = aspect; } void Camera::set_mode(Mode newmode) { direction_target = 0; direction_current = direction_target; pitch_target = pitch_track; pitch_current = pitch_target; target_direction = 0.0f; target_pitch = 0.0f; distance = 0.4f; camera_axis.clear(); switch(newmode) { case Track: // switch camera to Track mode camera_mode = Track; if (core::localcontrol()) { if (core::localcontrol()->state()) camera_axis.assign(core::localcontrol()->state()->axis()); else camera_axis.assign(core::localcontrol()->axis()); } break; case Free: // switch camera to Free mode camera_mode = Free; pitch_target = 2.0 * pitch_track; pitch_current = pitch_target; break; case Cockpit: camera_mode = Cockpit; break; case Overview: // switch camera to Overview mode camera_mode = Overview; default: break; } } void Camera::next_mode() { if (!core::localcontrol()) { set_mode(Overview); return; } switch(camera_mode) { case Free: // switch camera to Track mode set_mode(Track); con_print << "camera mode: track" << std::endl; break; case Track: // switch camera to Cockpit mode set_mode(Cockpit); con_print << "camera mode: cockpit" << std::endl; break; case Cockpit: // switch camera to Free mode set_mode(Free); con_print << "camera mode: free" << std::endl; break; default: break; } } void Camera::draw(float seconds) { math::Matrix4f matrix; math::Axis target_axis; float d = 0; if (!core::localcontrol()) { if (camera_mode != Overview) { set_mode(Overview); } camera_eye.clear(); camera_target.clear(); camera_axis.clear(); pitch_current = pitch_overview; camera_axis.change_pitch(pitch_current); distance = 20.0f; } else { if (mode() == Overview) set_mode(Track); if (core::localcontrol()->state()) { camera_target.assign(core::localcontrol()->state()->location()); target_axis.assign(core::localcontrol()->state()->axis()); } else { camera_target.assign(core::localcontrol()->location()); target_axis.assign(core::localcontrol()->axis()); } if (core::localcontrol()->model()) { distance = core::localcontrol()->model()->radius(); } else { distance = 1.0f; } if (mode() == Track) { float cosangle; float angle; float side; float u; const float cam_speed = seconds; math::Vector3f n; math::Vector3f p; // camera axis: pitch // project target_axis.up() into the plane with axis->left() normal n = camera_axis.left(); p = target_axis.up(); u = p[0]*n[0] + p[1]*n[1] + p[2]*n[2] / (-n[0]*n[0] - n[1]*n[1] - n[2] * n[2]); p = target_axis.up() + u * n; side = camera_axis.forward().x * p.x + camera_axis.forward().y * p.y + camera_axis.forward().z * p.z; if ((fabs(side) - MIN_DELTA > 0)) { cosangle = math::dotproduct(p, camera_axis.up()); if (fabs(cosangle) + MIN_DELTA < 1 ) { angle = acos(cosangle) * 180.0f / M_PI; angle = math::sgnf(side) * angle * cam_speed; camera_axis.change_pitch(-angle); } } // camera axis: direction // project target_axis.forward() into the plane with axis.up() normal n = camera_axis.up(); p = target_axis.forward(); u = p[0]*n[0] + p[1]*n[1] + p[2]*n[2] / (-n[0]*n[0] - n[1]*n[1] - n[2] * n[2]); p = target_axis.forward() + u * n; side = camera_axis.left().x * p.x + camera_axis.left().y * p.y + camera_axis.left().z * p.z; if ((fabs(side) - MIN_DELTA > 0)) { cosangle = math::dotproduct(p, camera_axis.forward()); if (fabs(cosangle) + MIN_DELTA < 1 ) { angle = acos(cosangle) * 180.0f / M_PI; angle = math::sgnf(side) * angle * cam_speed; camera_axis.change_direction(angle); } } // camera axis: roll // project target_axis.up() into the plane with axis.forward() normal n = camera_axis.forward(); p = target_axis.up(); u = p[0]*n[0] + p[1]*n[1] + p[2]*n[2] / (-n[0]*n[0] - n[1]*n[1] - n[2] * n[2]); p = target_axis.up() + u * n; side = camera_axis.left().x * p.x + camera_axis.left().y * p.y + camera_axis.left().z * p.z; if ((fabs(side) - MIN_DELTA > 0)) { cosangle = math::dotproduct(p, camera_axis.up()); if (fabs(cosangle) + MIN_DELTA < 1 ) { angle = acos(cosangle) * 180.0f / M_PI; angle = math::sgnf(side) * angle * cam_speed; camera_axis.change_roll(angle); } } if (core::localcontrol()->model()) { camera_target -= (core::localcontrol()->model()->maxbbox().x + 0.1f) * camera_axis.forward(); camera_target += (core::localcontrol()->model()->maxbbox().z + 0.1f ) * camera_axis.up(); } } else if (mode() == Free) { camera_axis.assign(target_axis); direction_target = direction_current - 90 * target_direction; pitch_target = pitch_current - 90 * target_pitch; // adjust direction d = degrees180f(direction_current - direction_target); direction_current = degrees360f( direction_current - d * seconds); camera_axis.change_direction(direction_current); // adjust pitch d = degrees180f(pitch_current - pitch_target); pitch_current = degrees360f(pitch_current - d * seconds); camera_axis.change_pitch(pitch_current); } else if (mode() == Cockpit) { camera_axis.assign(target_axis); if (core::localcontrol()->state() && core::localcontrol()->model()) camera_target += (core::localcontrol()->model()->maxbbox().x+0.05) * core::localcontrol()->state()->axis().forward(); distance = 0.0f; } } // Change to the projection matrix and set our viewing volume. gl::matrixmode(GL_PROJECTION); gl::loadidentity(); const float frustum_size = 0.5f; const float frustum_front = 1.0f; distance += frustum_front; gl::frustum(-frustum_size*aspect(), frustum_size*aspect(), -frustum_size, frustum_size, frustum_front, 1024.0f); // model view gl::matrixmode(GL_MODELVIEW); gl::loadidentity(); // map world coordinates to opengl coordinates gl::rotate(90.0f, 0, 1.0, 0); gl::rotate(-90.0f, 1.0f , 0, 0); // assign transformation matrix matrix.assign(camera_axis); // apply the transpose of the axis transformation (the axis is orhtonormal) gl::multmatrix(matrix.transpose()); // match the camera with the current target gl::translate(-1.0f * camera_target); // apply camera offset gl::translate(distance * camera_axis.forward()); // calculate eye position camera_eye = camera_target - (distance * camera_axis.forward()); } void Camera::set_direction(float direction) { target_direction = direction; math::clamp(target_direction, -1.0f, 1.0f); } void Camera::set_pitch(float pitch) { target_pitch = pitch; math::clamp(target_pitch, -1.0f, 1.0f); } void Camera::reset() { set_mode(camera_mode); } }