You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
577 lines
16 KiB
577 lines
16 KiB
/*
|
|
* Copyright (c) 2018-2021, 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.
|
|
*
|
|
* SPDX-FileCopyrightText: Copyright (c) 2018-2021 NVIDIA CORPORATION
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
//--------------------------------------------------------------------
|
|
|
|
#include "cameramanipulator.hpp"
|
|
#include <chrono>
|
|
#include <iostream>
|
|
#include <nvpwindow.hpp>
|
|
|
|
namespace nvh {
|
|
|
|
inline float sign(float s)
|
|
{
|
|
return (s < 0.f) ? -1.f : 1.f;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
CameraManipulator::CameraManipulator()
|
|
{
|
|
update();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Set the new camera as a goal
|
|
//
|
|
void CameraManipulator::setCamera(Camera camera, bool instantSet /*=true*/)
|
|
{
|
|
m_anim_done = true;
|
|
|
|
if(instantSet)
|
|
{
|
|
m_current = camera;
|
|
update();
|
|
}
|
|
else if(camera != m_current)
|
|
{
|
|
m_goal = camera;
|
|
m_snapshot = m_current;
|
|
m_anim_done = false;
|
|
m_start_time = getSystemTime();
|
|
findBezierPoints();
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creates a viewing matrix derived from an eye point, a reference point indicating the center of
|
|
// the scene, and an up vector
|
|
//
|
|
void CameraManipulator::setLookat(const nvmath::vec3f& eye, const nvmath::vec3f& center, const nvmath::vec3f& up, bool instantSet)
|
|
{
|
|
Camera camera{eye, center, up, m_current.fov};
|
|
setCamera(camera, instantSet);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Modify the position of the camera over time
|
|
// - The camera can be updated through keys. A key set a direction which is added to both
|
|
// eye and center, until the key is released
|
|
// - A new position of the camera is defined and the camera will reach that position
|
|
// over time.
|
|
void CameraManipulator::updateAnim()
|
|
{
|
|
auto elapse = static_cast<float>(getSystemTime() - m_start_time) / 1000.f;
|
|
|
|
// Key animation
|
|
if(m_key_vec != nvmath::vec3f(0, 0, 0))
|
|
{
|
|
m_current.eye += m_key_vec * elapse;
|
|
m_current.ctr += m_key_vec * elapse;
|
|
update();
|
|
m_start_time = getSystemTime();
|
|
return;
|
|
}
|
|
|
|
// Camera moving to new position
|
|
if(m_anim_done)
|
|
return;
|
|
|
|
float t = std::min(elapse / float(m_duration), 1.0f);
|
|
// Evaluate polynomial (smoother step from Perlin)
|
|
t = t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
|
if(t >= 1.0f)
|
|
{
|
|
m_current = m_goal;
|
|
m_anim_done = true;
|
|
return;
|
|
}
|
|
|
|
// Interpolate camera position and interest
|
|
// The distance of the camera between the interest is preserved to
|
|
// create a nicer interpolation
|
|
nvmath::vec3f vpos, vint, vup;
|
|
m_current.ctr = nvmath::lerp(t, m_snapshot.ctr, m_goal.ctr);
|
|
m_current.up = nvmath::lerp(t, m_snapshot.up, m_goal.up);
|
|
m_current.eye = computeBezier(t, m_bezier[0], m_bezier[1], m_bezier[2]);
|
|
m_current.fov = nvmath::lerp(t, m_snapshot.fov, m_goal.fov);
|
|
|
|
|
|
update();
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Get the current camera information
|
|
// position camera position
|
|
// interest camera interesting point (look at position)
|
|
// up camera up vector
|
|
//
|
|
void CameraManipulator::getLookat(nvmath::vec3f& eye, nvmath::vec3f& center, nvmath::vec3f& up) const
|
|
{
|
|
eye = m_current.eye;
|
|
center = m_current.ctr;
|
|
up = m_current.up;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
void CameraManipulator::setMatrix(const nvmath::mat4f& matrix, bool instantSet, float centerDistance)
|
|
{
|
|
Camera camera;
|
|
matrix.get_translation(camera.eye);
|
|
auto rotMat = matrix.get_rot_mat3();
|
|
camera.ctr = {0, 0, -centerDistance};
|
|
camera.ctr = camera.eye + (rotMat * camera.ctr);
|
|
camera.up = {0, 1, 0};
|
|
camera.fov = m_current.fov;
|
|
|
|
m_anim_done = instantSet;
|
|
|
|
if(instantSet)
|
|
{
|
|
m_current = camera;
|
|
}
|
|
else
|
|
{
|
|
m_goal = camera;
|
|
m_snapshot = m_current;
|
|
m_start_time = getSystemTime();
|
|
findBezierPoints();
|
|
}
|
|
update();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
void CameraManipulator::setMousePosition(int x, int y)
|
|
{
|
|
m_mouse = {static_cast<float>(x), static_cast<float>(y)};
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
void CameraManipulator::getMousePosition(int& x, int& y)
|
|
{
|
|
x = static_cast<int>(m_mouse[0]);
|
|
y = static_cast<int>(m_mouse[1]);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
void CameraManipulator::setWindowSize(int w, int h)
|
|
{
|
|
m_width = w;
|
|
m_height = h;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
// Low level function for when the camera move.
|
|
//
|
|
void CameraManipulator::motion(int x, int y, int action)
|
|
{
|
|
float dx = float(x - m_mouse[0]) / float(m_width);
|
|
float dy = float(y - m_mouse[1]) / float(m_height);
|
|
|
|
switch(action)
|
|
{
|
|
case Orbit:
|
|
orbit(dx, dy, false);
|
|
break;
|
|
case CameraManipulator::Dolly:
|
|
dolly(dx, dy);
|
|
break;
|
|
case CameraManipulator::Pan:
|
|
pan(dx, dy);
|
|
break;
|
|
case CameraManipulator::LookAround:
|
|
orbit(dx, -dy, true);
|
|
break;
|
|
}
|
|
|
|
// Resetting animation
|
|
m_anim_done = true;
|
|
|
|
update();
|
|
|
|
m_mouse[0] = static_cast<float>(x);
|
|
m_mouse[1] = static_cast<float>(y);
|
|
}
|
|
|
|
|
|
//
|
|
// Function for when the camera move with keys.
|
|
//
|
|
void CameraManipulator::keyMotion(float dx, float dy, int action)
|
|
{
|
|
if(action == NoAction)
|
|
{
|
|
m_key_vec = {0, 0, 0};
|
|
return;
|
|
}
|
|
|
|
auto d = nvmath::normalize(m_current.ctr - m_current.eye);
|
|
dx *= m_speed * 2.f;
|
|
dy *= m_speed * 2.f;
|
|
|
|
nvmath::vec3f key_vec;
|
|
if(action == Dolly)
|
|
{
|
|
key_vec = d * dx;
|
|
if(m_mode == Walk)
|
|
{
|
|
if(m_current.up.y > m_current.up.z)
|
|
key_vec.y = 0;
|
|
else
|
|
key_vec.z = 0;
|
|
}
|
|
}
|
|
else if(action == Pan)
|
|
{
|
|
auto r = nvmath::cross(d, m_current.up);
|
|
key_vec = r * dx + m_current.up * dy;
|
|
}
|
|
|
|
m_key_vec += key_vec;
|
|
|
|
// Resetting animation
|
|
m_start_time = getSystemTime();
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// To call when the mouse is moving
|
|
// It find the appropriate camera operator, based on the mouse button pressed and the
|
|
// keyboard modifiers (shift, ctrl, alt)
|
|
//
|
|
// Returns the action that was activated
|
|
//
|
|
CameraManipulator::Actions CameraManipulator::mouseMove(int x, int y, const Inputs& inputs)
|
|
{
|
|
if(!inputs.lmb && !inputs.rmb && !inputs.mmb)
|
|
{
|
|
setMousePosition(x, y);
|
|
return NoAction; // no mouse button pressed
|
|
}
|
|
|
|
Actions curAction = NoAction;
|
|
if(inputs.lmb)
|
|
{
|
|
if(((inputs.ctrl) && (inputs.shift)) || inputs.alt)
|
|
curAction = m_mode == Examine ? LookAround : Orbit;
|
|
else if(inputs.shift)
|
|
curAction = Dolly;
|
|
else if(inputs.ctrl)
|
|
curAction = Pan;
|
|
else
|
|
curAction = m_mode == Examine ? Orbit : LookAround;
|
|
}
|
|
else if(inputs.mmb)
|
|
curAction = Pan;
|
|
else if(inputs.rmb)
|
|
curAction = Dolly;
|
|
|
|
if(curAction != NoAction)
|
|
motion(x, y, curAction);
|
|
|
|
return curAction;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Trigger a dolly when the wheel change
|
|
//
|
|
void CameraManipulator::wheel(int value, const Inputs& inputs)
|
|
{
|
|
float fval(static_cast<float>(value));
|
|
float dx = (fval * fabsf(fval)) / static_cast<float>(m_width);
|
|
|
|
if(inputs.shift)
|
|
{
|
|
setFov(m_current.fov + fval);
|
|
}
|
|
else
|
|
{
|
|
dolly(dx * m_speed, dx * m_speed);
|
|
update();
|
|
}
|
|
}
|
|
|
|
// Set and clamp FOV between 0.01 and 179 degrees
|
|
void CameraManipulator::setFov(float _fov)
|
|
{
|
|
m_current.fov = std::min(std::max(_fov, 0.01f), 179.0f);
|
|
}
|
|
|
|
nvmath::vec3f CameraManipulator::computeBezier(float t, nvmath::vec3f& p0, nvmath::vec3f& p1, nvmath::vec3f& p2)
|
|
{
|
|
float u = 1.f - t;
|
|
float tt = t * t;
|
|
float uu = u * u;
|
|
|
|
nvmath::vec3f p = uu * p0; // first term
|
|
p += 2 * u * t * p1; // second term
|
|
p += tt * p2; // third term
|
|
|
|
return p;
|
|
}
|
|
|
|
void CameraManipulator::findBezierPoints()
|
|
{
|
|
nvmath::vec3f p0 = m_current.eye;
|
|
nvmath::vec3f p2 = m_goal.eye;
|
|
nvmath::vec3f p1, pc;
|
|
|
|
// point of interest
|
|
nvmath::vec3f pi = (m_goal.ctr + m_current.ctr) * 0.5f;
|
|
|
|
nvmath::vec3f p02 = (p0 + p2) * 0.5f; // mid p0-p2
|
|
float radius = (length(p0 - pi) + length(p2 - pi)) * 0.5f; // Radius for p1
|
|
nvmath::vec3f p02pi(p02 - pi); // Vector from interest to mid point
|
|
p02pi.normalize();
|
|
p02pi *= radius;
|
|
pc = pi + p02pi; // Calculated point to go through
|
|
p1 = 2.f * pc - p0 * 0.5f - p2 * 0.5f; // Computing p1 for t=0.5
|
|
p1.y = p02.y; // Clamping the P1 to be in the same height as p0-p2
|
|
|
|
m_bezier[0] = p0;
|
|
m_bezier[1] = p1;
|
|
m_bezier[2] = p2;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Pan the camera perpendicularly to the light of sight.
|
|
//
|
|
void CameraManipulator::pan(float dx, float dy)
|
|
{
|
|
if(m_mode == Fly)
|
|
{
|
|
dx *= -1;
|
|
dy *= -1;
|
|
}
|
|
|
|
nvmath::vec3f z(m_current.eye - m_current.ctr);
|
|
float length = static_cast<float>(nvmath::length(z)) / 0.785f; // 45 degrees
|
|
z = nvmath::normalize(z);
|
|
nvmath::vec3f x = nvmath::cross(m_current.up, z);
|
|
x = nvmath::normalize(x);
|
|
nvmath::vec3f y = nvmath::cross(z, x);
|
|
y = nvmath::normalize(y);
|
|
x *= -dx * length;
|
|
y *= dy * length;
|
|
|
|
m_current.eye += x + y;
|
|
m_current.ctr += x + y;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Orbit the camera around the center of interest. If 'invert' is true,
|
|
// then the camera stays in place and the interest orbit around the camera.
|
|
//
|
|
void CameraManipulator::orbit(float dx, float dy, bool invert)
|
|
{
|
|
if(dx == 0 && dy == 0)
|
|
return;
|
|
|
|
// Full width will do a full turn
|
|
dx *= nv_two_pi;
|
|
dy *= nv_two_pi;
|
|
|
|
// Get the camera
|
|
nvmath::vec3f origin(invert ? m_current.eye : m_current.ctr);
|
|
nvmath::vec3f position(invert ? m_current.ctr : m_current.eye);
|
|
|
|
// Get the length of sight
|
|
nvmath::vec3f centerToEye(position - origin);
|
|
float radius = nvmath::length(centerToEye);
|
|
centerToEye = nvmath::normalize(centerToEye);
|
|
|
|
nvmath::mat4f rot_x, rot_y;
|
|
|
|
// Find the rotation around the UP axis (Y)
|
|
nvmath::vec3f axe_z(nvmath::normalize(centerToEye));
|
|
rot_y = nvmath::mat4f().as_rot(-dx, m_current.up);
|
|
|
|
// Apply the (Y) rotation to the eye-center vector
|
|
nvmath::vec4f vect_tmp = rot_y * nvmath::vec4f(centerToEye.x, centerToEye.y, centerToEye.z, 0);
|
|
centerToEye = nvmath::vec3f(vect_tmp.x, vect_tmp.y, vect_tmp.z);
|
|
|
|
// Find the rotation around the X vector: cross between eye-center and up (X)
|
|
nvmath::vec3f axe_x = nvmath::cross(m_current.up, axe_z);
|
|
axe_x = nvmath::normalize(axe_x);
|
|
rot_x = nvmath::mat4f().as_rot(-dy, axe_x);
|
|
|
|
// Apply the (X) rotation to the eye-center vector
|
|
vect_tmp = rot_x * nvmath::vec4f(centerToEye.x, centerToEye.y, centerToEye.z, 0);
|
|
nvmath::vec3f vect_rot(vect_tmp.x, vect_tmp.y, vect_tmp.z);
|
|
if(sign(vect_rot.x) == sign(centerToEye.x))
|
|
centerToEye = vect_rot;
|
|
|
|
// Make the vector as long as it was originally
|
|
centerToEye *= radius;
|
|
|
|
// Finding the new position
|
|
nvmath::vec3f newPosition = centerToEye + origin;
|
|
|
|
if(!invert)
|
|
{
|
|
m_current.eye = newPosition; // Normal: change the position of the camera
|
|
}
|
|
else
|
|
{
|
|
m_current.ctr = newPosition; // Inverted: change the interest point
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Move the camera toward the interest point, but don't cross it
|
|
//
|
|
void CameraManipulator::dolly(float dx, float dy)
|
|
{
|
|
nvmath::vec3f z = m_current.ctr - m_current.eye;
|
|
float length = static_cast<float>(nvmath::length(z));
|
|
|
|
// We are at the point of interest, and don't know any direction, so do nothing!
|
|
if(length < 0.000001f)
|
|
return;
|
|
|
|
// Use the larger movement.
|
|
float dd;
|
|
if(m_mode != Examine)
|
|
dd = -dy;
|
|
else
|
|
dd = fabs(dx) > fabs(dy) ? dx : -dy;
|
|
float factor = m_speed * dd;
|
|
|
|
// Adjust speed based on distance.
|
|
if(m_mode == Examine)
|
|
{
|
|
// Don't move over the point of interest.
|
|
if(factor >= 1.0f)
|
|
return;
|
|
|
|
z *= factor;
|
|
}
|
|
else
|
|
{
|
|
// Normalize the Z vector and make it faster
|
|
z *= factor / length * 10.0f;
|
|
}
|
|
|
|
// Not going up
|
|
if(m_mode == Walk)
|
|
{
|
|
if(m_current.up.y > m_current.up.z)
|
|
z.y = 0;
|
|
else
|
|
z.z = 0;
|
|
}
|
|
|
|
m_current.eye += z;
|
|
|
|
// In fly mode, the interest moves with us.
|
|
if(m_mode != Examine)
|
|
m_current.ctr += z;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Return the time in fraction of milliseconds
|
|
//
|
|
double CameraManipulator::getSystemTime()
|
|
{
|
|
auto now(std::chrono::system_clock::now());
|
|
auto duration = now.time_since_epoch();
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(duration).count() / 1000.0;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Return a string which can be included in help dialogs
|
|
//
|
|
const std::string& CameraManipulator::getHelp()
|
|
{
|
|
static std::string helpText =
|
|
"LMB: rotate around the target\n"
|
|
"RMB: Dolly in/out\n"
|
|
"MMB: Pan along view plane\n"
|
|
"LMB + Shift: Dolly in/out\n"
|
|
"LMB + Ctrl: Pan\n"
|
|
"LMB + Alt: Look aroundPan\n"
|
|
"Mouse wheel: Dolly in/out\n"
|
|
"Mouse wheel + Shift: Zoom in/out\n";
|
|
return helpText;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Move the camera closer or further from the center of the the bounding box, to see it completely
|
|
//
|
|
// boxMin - lower corner of the bounding box
|
|
// boxMax - upper corner of the bounding box
|
|
// instantFit - true: set the new position, false: will animate to new position.
|
|
// tight - true: fit exactly the corner, false: fit to radius (larger view, will not get closer or further away)
|
|
// aspect - aspect ratio of the window.
|
|
//
|
|
void CameraManipulator::fit(const nvmath::vec3f& boxMin, const nvmath::vec3f& boxMax, bool instantFit /*= true*/, bool tight /*=false*/, float aspect /*=1.0f*/)
|
|
{
|
|
const nvmath::vec3f boxHalfSize = (boxMax - boxMin) * .5f;
|
|
const nvmath::vec3f boxCenter = boxMin + boxHalfSize;
|
|
|
|
float offset = 0;
|
|
float yfov = m_current.fov;
|
|
float xfov = m_current.fov * aspect;
|
|
|
|
if(!tight)
|
|
{
|
|
// Using the bounding sphere
|
|
float radius = nvmath::length(boxHalfSize);
|
|
if(aspect > 1.f)
|
|
offset = radius / sin(nv_to_rad * yfov * 0.5f);
|
|
else
|
|
offset = radius / sin(nv_to_rad * xfov * 0.5f);
|
|
}
|
|
else
|
|
{
|
|
nvmath::mat4f mView = nvmath::look_at(m_current.eye, boxCenter, m_current.up);
|
|
mView.set_translate({0, 0, 0}); // Keep only rotation
|
|
|
|
for(int i = 0; i < 8; i++)
|
|
{
|
|
nvmath::vec3f vct(i & 1 ? boxHalfSize.x : -boxHalfSize.x, i & 2 ? boxHalfSize.y : -boxHalfSize.y,
|
|
i & 4 ? boxHalfSize.z : -boxHalfSize.z);
|
|
vct = nvmath::vec3f(mView * vct);
|
|
|
|
if(vct.z < 0) // Take only points in front of the center
|
|
{
|
|
// Keep the largest offset to see that vertex
|
|
offset = std::max(fabs(vct.y) / tan(nv_to_rad * yfov * 0.5f) + fabs(vct.z), offset);
|
|
offset = std::max(fabs(vct.x) / tan(nv_to_rad * xfov * 0.5f) + fabs(vct.z), offset);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Re-position the camera
|
|
auto viewDir = nvmath::normalize(m_current.eye - m_current.ctr);
|
|
auto veye = boxCenter + viewDir * offset;
|
|
setLookat(veye, boxCenter, m_current.up, instantFit);
|
|
}
|
|
|
|
} // namespace nvh
|
|
|