/* 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 #include "render/camera.h" #include "render/gl.h" #include "render/state.h" #include "core/entity.h" #include "core/range.h" #include "math/functions.h" #include "sys/sys.h" namespace render { const float MIN_DELTA = 10e-10; const float COS_PI_4 = sqrt(2.0f) * 0.5f; Camera::Camera(const Mode mode) { _mode = mode; _distance = 1.0f; _multiplier = 1.0f; _target_entity = 0; _freelook_direction = 0.0f; _freelook_pitch = 0.0f; _movement_direction = 0.0f; _movement_pitch = 0.0f; } Camera::~Camera() { } void Camera::set_mode(const Mode mode) { _mode = mode; reset(); } void Camera::set_multiplier(const float multiplier) { _multiplier = multiplier; } void Camera::set_freelook_direction(const float angle) { _freelook_direction = angle; } void Camera::set_freelook_pitch(const float angle) { _freelook_pitch = angle; } void Camera::set_movement_direction(const float speed) { _movement_direction = speed; math::clamp(_movement_direction, -1.0f, 1.0f); } void Camera::set_movement_pitch(const float speed) { _movement_pitch = speed; math::clamp(_movement_pitch, -1.0f, 1.0f); } void Camera::cycle_mode_next() { switch (mode()) { case Free: set_mode(Track); break; case Track: set_mode(Cockpit); break; case Cockpit: set_mode(Free); break; default: break; } } void Camera::cycle_mode_previous() { switch (mode()) { case Cockpit: set_mode(Track); break; case Free: set_mode(Cockpit); break; case Track: set_mode(Free); break; default: break; } } void Camera::reset() { if (target()) { _target_location.assign(target()->location()); _target_axis.assign(target()->axis()); _distance = 0.0f; } else { _location.clear(); _target_axis.clear(); _distance = 0.0f; } _axis.assign(_target_axis); if (mode() == Free) { _target_axis.clear(); } _freelook_direction = 0.0f; _freelook_pitch = 0.0f; _movement_direction = 0.0f; _movement_pitch = 0.0f; } void Camera::set_target(const core::Entity *entity) { _target_entity = entity; } void Camera::frame(const float elapsed) { const float ROTATESPEED = 25.0f * elapsed; switch(mode()) { case Track: { math::Axis desired_axis; // 3rd person view if (target()) { _target_location.assign(target()->location()); if (target()->model()) { const float modelscale = target()->radius() / target()->model()->radius(); _target_location += target()->axis().up() * target()->model()->box().max().z() * modelscale; } else { _target_location += target()->axis().up() * target()->radius(); } desired_axis.assign(target()->axis()); _distance = target()->radius() * _multiplier * 2.0f; } else { _target_location.assign(0.0f, 0.0f, 1.0f); _distance = _multiplier * 2.0f; } // FIXME Bad solution below math::Vector3f n (math::crossproduct(_target_axis.forward(), desired_axis.forward())); float l = n.length(); float d = math::dotproduct(_target_axis.forward(), desired_axis.forward()); float a = (d > 0.0f ? 1.0f - d : 1.0f); if ((a > MIN_DELTA) && (l > MIN_DELTA)) { n.normalize(); _target_axis.rotate(n, -ROTATESPEED * a); } n.assign (math::crossproduct(_target_axis.up(), desired_axis.up())); l = n.length(); d = math::dotproduct(_target_axis.up(), desired_axis.up()); a = (d > 0.0f ? 1.0f - d : 1.0f); if ((a > MIN_DELTA) && (l > MIN_DELTA)) { n.normalize(); _target_axis.rotate(n, -ROTATESPEED * a); } _axis.assign(_target_axis); _axis.change_direction(_freelook_direction); _axis.change_pitch(_freelook_pitch); break; } case Cockpit: { // 1st person view if (target()) { _target_location.assign(target()->location()); _target_axis.assign(target()->axis()); _distance = 0.0f; } else { _target_location.clear(); _target_axis.clear(); _distance = 0.0f; } _axis.assign(_target_axis); _axis.change_direction(_freelook_direction); _axis.change_pitch(_freelook_pitch); break; } case Free: { // look at self if (target()) { _target_location.assign(target()->location()); _axis.assign(target()->axis()); _distance = target()->radius() * _multiplier * 2.0f; } else { _target_location.clear(); _axis.clear(); _distance = _multiplier * 2.0f; } _target_axis.rotate(math::Vector3f(0.0f, 0.0f, 1.0f), -M_PI * _movement_direction * elapsed); _target_axis.change_pitch(180.0f * _movement_pitch * elapsed); _axis.assign(_axis * _target_axis); _axis.change_direction(_freelook_direction); _axis.change_pitch(_freelook_pitch); break; } case Overview: { if (target()) { _target_location.assign(target()->location()); _target_axis.assign(target()->axis()); _distance = 2.0f * target()->radius() * _multiplier; _target_axis.change_direction(180.0f); // default pitch angle _target_axis.change_pitch(-5.0f); } else { _target_location.clear(); _target_axis.clear(); _distance = 2.0f * _multiplier; } _axis.assign(_target_axis); break; } } _distance += FRUSTUMFRONT / WORLDSCALE; _location.assign(_target_location - _axis.forward() * _distance); } void Camera::draw() { // Change to the projection matrix and set our viewing volume large enough for the skysphere gl::matrixmode(GL_PROJECTION); gl::loadidentity(); gl::frustum(-FRUSTUMSIZE, FRUSTUMSIZE, -FRUSTUMSIZE / State::aspect(), FRUSTUMSIZE / State::aspect(), FRUSTUMFRONT, FARPLANE); gl::matrixmode(GL_MODELVIEW); gl::loadidentity(); // map world coordinates to opengl coordinates gl::rotate(90.0f, 0.0f, 1.0f, 0.0f); gl::rotate(-90.0f, 1.0f , 0.0f, 0.0f); // apply the transpose of the axis transformation (the axis is orhtonormal) math::Matrix4f matrix(_axis); gl::multmatrix(matrix.transpose()); // apply world scale gl::scale(WORLDSCALE, WORLDSCALE, WORLDSCALE); // apply camera eye translation gl::translate(-1.0f * _location); } void Camera::draw(const float center_x, const float center_y) { // Change to the projection matrix and set our viewing volume large enough for the skysphere gl::matrixmode(GL_PROJECTION); gl::loadidentity(); // move projection center to (cx, cy) // note: the factor 2.0f probably has to be 1.0f/frustum_size gl::translate(2.0f*(-State::width() * 0.5f + center_x) / State::width() , 2.0f * (State::height() * 0.5f - center_y) / State::height(), 0.0f); gl::frustum(-FRUSTUMSIZE, FRUSTUMSIZE, -FRUSTUMSIZE / State::aspect(), FRUSTUMSIZE / State::aspect(), FRUSTUMFRONT, 1023.0f); gl::matrixmode(GL_MODELVIEW); gl::loadidentity(); // map world coordinates to opengl coordinates gl::rotate(90.0f, 0.0f, 1.0f, 0.0f); gl::rotate(-90.0f, 1.0f , 0.0f, 0.0f); // apply the transpose of the axis transformation (the axis is orhtonormal) math::Matrix4f matrix(_axis); gl::multmatrix(matrix.transpose()); // apply camera eye translation gl::translate(-1.0f * _location); } void Camera::ortho() { // switch to orthographic projection gl::matrixmode(GL_PROJECTION); gl::loadidentity(); glOrtho(0, State::width(), State::height(), 0, -16.0f, 16.0f); gl::matrixmode(GL_MODELVIEW); gl::loadidentity(); } } // namespace render