// Copyright(c) 2019, NVIDIA CORPORATION. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "CameraManipulator.hpp" #include #include namespace vk { namespace su { const float trackballSize = 0.8f; //----------------------------------------------------------------------------- // MATH functions // template bool isZero( const T & _a ) { return fabs( _a ) < std::numeric_limits::epsilon(); } template bool isOne( const T & _a ) { return areEqual( _a, (T)1 ); } inline float sign( float s ) { return ( s < 0.f ) ? -1.f : 1.f; } CameraManipulator::CameraManipulator() { update(); } glm::vec3 const & CameraManipulator::getCameraPosition() const { return m_cameraPosition; } glm::vec3 const & CameraManipulator::getCenterPosition() const { return m_centerPosition; } glm::mat4 const & CameraManipulator::getMatrix() const { return m_matrix; } CameraManipulator::Mode CameraManipulator::getMode() const { return m_mode; } glm::ivec2 const & CameraManipulator::getMousePosition() const { return m_mousePosition; } float CameraManipulator::getRoll() const { return m_roll; } float CameraManipulator::getSpeed() const { return m_speed; } glm::vec3 const & CameraManipulator::getUpVector() const { return m_upVector; } glm::u32vec2 const & CameraManipulator::getWindowSize() const { return m_windowSize; } CameraManipulator::Action CameraManipulator::mouseMove( glm::ivec2 const & position, MouseButton mouseButton, ModifierFlags & modifiers ) { Action curAction = Action::None; switch ( mouseButton ) { case MouseButton::Left: if ( ( ( modifiers & ModifierFlagBits::Ctrl ) && ( modifiers & ModifierFlagBits::Shift ) ) || ( modifiers & ModifierFlagBits::Alt ) ) { curAction = m_mode == Mode::Examine ? Action::LookAround : Action::Orbit; } else if ( modifiers & ModifierFlagBits::Shift ) { curAction = Action::Dolly; } else if ( modifiers & ModifierFlagBits::Ctrl ) { curAction = Action::Pan; } else { curAction = m_mode == Mode::Examine ? Action::Orbit : Action::LookAround; } break; case MouseButton::Middle: curAction = Action::Pan; break; case MouseButton::Right: curAction = Action::Dolly; break; default: assert( false ); } assert( curAction != Action::None ); motion( position, curAction ); return curAction; } void CameraManipulator::setLookat( const glm::vec3 & cameraPosition, const glm::vec3 & centerPosition, const glm::vec3 & upVector ) { m_cameraPosition = cameraPosition; m_centerPosition = centerPosition; m_upVector = upVector; update(); } void CameraManipulator::setMode( Mode mode ) { m_mode = mode; } void CameraManipulator::setMousePosition( glm::ivec2 const & position ) { m_mousePosition = position; } void CameraManipulator::setRoll( float roll ) { m_roll = roll; update(); } void CameraManipulator::setSpeed( float speed ) { m_speed = speed; } void CameraManipulator::setWindowSize( glm::ivec2 const & size ) { m_windowSize = size; } void CameraManipulator::wheel( int value ) { float fValue = static_cast( value ); float dx = ( fValue * abs( fValue ) ) / static_cast( m_windowSize[0] ); glm::vec3 z = m_cameraPosition - m_centerPosition; float length = z.length() * 0.1f; length = length < 0.001f ? 0.001f : length; dx *= m_speed; dolly( glm::vec2( dx, dx ) ); update(); } void CameraManipulator::dolly( glm::vec2 const & delta ) { glm::vec3 z = m_centerPosition - m_cameraPosition; float length = glm::length( z ); // We are at the point of interest, and don't know any direction, so do nothing! if ( isZero( length ) ) { return; } // Use the larger movement. float dd; if ( m_mode != Mode::Examine ) { dd = -delta[1]; } else { dd = fabs( delta[0] ) > fabs( delta[1] ) ? delta[0] : -delta[1]; } float factor = m_speed * dd / length; // Adjust speed based on distance. length /= 10; length = length < 0.001f ? 0.001f : length; factor *= length; // Don't move to or through the point of interest. if ( 1.0f <= factor ) { return; } z *= factor; // Not going up if ( m_mode == Mode::Walk ) { if ( m_upVector.y > m_upVector.z ) { z.y = 0; } else { z.z = 0; } } m_cameraPosition += z; // In fly mode, the interest moves with us. if ( m_mode != Mode::Examine ) { m_centerPosition += z; } } void CameraManipulator::motion( glm::ivec2 const & position, Action action ) { glm::vec2 delta( float( position[0] - m_mousePosition[0] ) / float( m_windowSize[0] ), float( position[1] - m_mousePosition[1] ) / float( m_windowSize[1] ) ); switch ( action ) { case Action::Orbit: if ( m_mode == Mode::Trackball ) { orbit( delta, true ); // trackball(position); } else { orbit( delta, false ); } break; case Action::Dolly: dolly( delta ); break; case Action::Pan: pan( delta ); break; case Action::LookAround: if ( m_mode == Mode::Trackball ) { trackball( position ); } else { orbit( glm::vec2( delta[0], -delta[1] ), true ); } break; default: break; } update(); m_mousePosition = position; } void CameraManipulator::orbit( glm::vec2 const & delta, bool invert ) { if ( isZero( delta[0] ) && isZero( delta[1] ) ) { return; } // Full width will do a full turn float dx = delta[0] * float( glm::two_pi() ); float dy = delta[1] * float( glm::two_pi() ); // Get the camera glm::vec3 origin( invert ? m_cameraPosition : m_centerPosition ); glm::vec3 position( invert ? m_centerPosition : m_cameraPosition ); // Get the length of sight glm::vec3 centerToEye( position - origin ); float radius = glm::length( centerToEye ); centerToEye = glm::normalize( centerToEye ); // Find the rotation around the UP axis (Y) glm::vec3 zAxis( centerToEye ); glm::mat4 yRotation = glm::rotate( -dx, m_upVector ); // Apply the (Y) rotation to the eye-center vector glm::vec4 tmpVector = yRotation * glm::vec4( centerToEye.x, centerToEye.y, centerToEye.z, 0.0f ); centerToEye = glm::vec3( tmpVector.x, tmpVector.y, tmpVector.z ); // Find the rotation around the X vector: cross between eye-center and up (X) glm::vec3 xAxis = glm::cross( m_upVector, zAxis ); xAxis = glm::normalize( xAxis ); glm::mat4 xRotation = glm::rotate( -dy, xAxis ); // Apply the (X) rotation to the eye-center vector tmpVector = xRotation * glm::vec4( centerToEye.x, centerToEye.y, centerToEye.z, 0 ); glm::vec3 rotatedVector( tmpVector.x, tmpVector.y, tmpVector.z ); if ( sign( rotatedVector.x ) == sign( centerToEye.x ) ) { centerToEye = rotatedVector; } // Make the vector as long as it was originally centerToEye *= radius; // Finding the new position glm::vec3 newPosition = centerToEye + origin; if ( !invert ) { m_cameraPosition = newPosition; // Normal: change the position of the camera } else { m_centerPosition = newPosition; // Inverted: change the interest point } } void CameraManipulator::pan( glm::vec2 const & delta ) { glm::vec3 z( m_cameraPosition - m_centerPosition ); float length = static_cast( glm::length( z ) ) / 0.785f; // 45 degrees z = glm::normalize( z ); glm::vec3 x = glm::normalize( glm::cross( m_upVector, z ) ); glm::vec3 y = glm::normalize( glm::cross( z, x ) ); x *= -delta[0] * length; y *= delta[1] * length; if ( m_mode == Mode::Fly ) { x = -x; y = -y; } m_cameraPosition += x + y; m_centerPosition += x + y; } double CameraManipulator::projectOntoTBSphere( const glm::vec2 & p ) { double z; double d = length( p ); if ( d < trackballSize * 0.70710678118654752440 ) { // inside sphere z = sqrt( trackballSize * trackballSize - d * d ); } else { // on hyperbola double t = trackballSize / 1.41421356237309504880; z = t * t / d; } return z; } void CameraManipulator::trackball( glm::ivec2 const & position ) { glm::vec2 p0( 2 * ( m_mousePosition[0] - m_windowSize[0] / 2 ) / double( m_windowSize[0] ), 2 * ( m_windowSize[1] / 2 - m_mousePosition[1] ) / double( m_windowSize[1] ) ); glm::vec2 p1( 2 * ( position[0] - m_windowSize[0] / 2 ) / double( m_windowSize[0] ), 2 * ( m_windowSize[1] / 2 - position[1] ) / double( m_windowSize[1] ) ); // determine the z coordinate on the sphere glm::vec3 pTB0( p0[0], p0[1], projectOntoTBSphere( p0 ) ); glm::vec3 pTB1( p1[0], p1[1], projectOntoTBSphere( p1 ) ); // calculate the rotation axis via cross product between p0 and p1 glm::vec3 axis = glm::cross( pTB0, pTB1 ); axis = glm::normalize( axis ); // calculate the angle float t = glm::length( pTB0 - pTB1 ) / ( 2.f * trackballSize ); // clamp between -1 and 1 if ( t > 1.0f ) { t = 1.0f; } else if ( t < -1.0f ) { t = -1.0f; } float rad = 2.0f * asin( t ); { glm::vec4 rot_axis = m_matrix * glm::vec4( axis, 0 ); glm::mat4 rot_mat = glm::rotate( rad, glm::vec3( rot_axis.x, rot_axis.y, rot_axis.z ) ); glm::vec3 pnt = m_cameraPosition - m_centerPosition; glm::vec4 pnt2 = rot_mat * glm::vec4( pnt.x, pnt.y, pnt.z, 1 ); m_cameraPosition = m_centerPosition + glm::vec3( pnt2.x, pnt2.y, pnt2.z ); glm::vec4 up2 = rot_mat * glm::vec4( m_upVector.x, m_upVector.y, m_upVector.z, 0 ); m_upVector = glm::vec3( up2.x, up2.y, up2.z ); } } void CameraManipulator::update() { m_matrix = glm::lookAt( m_cameraPosition, m_centerPosition, m_upVector ); if ( !isZero( m_roll ) ) { glm::mat4 rot = glm::rotate( m_roll, glm::vec3( 0, 0, 1 ) ); m_matrix = m_matrix * rot; } } } // namespace su } // namespace vk