280 lines
10 KiB
C++
280 lines
10 KiB
C++
//----------------------------------------------------------------------------//
|
|
// //
|
|
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation //
|
|
// and distributed under the MIT License (MIT). //
|
|
// //
|
|
// Copyright (c) Guillaume Blanc //
|
|
// //
|
|
// Permission is hereby granted, free of charge, to any person obtaining a //
|
|
// copy of this software and associated documentation files (the "Software"), //
|
|
// to deal in the Software without restriction, including without limitation //
|
|
// the rights to use, copy, modify, merge, publish, distribute, sublicense, //
|
|
// and/or sell copies of the Software, and to permit persons to whom the //
|
|
// Software is furnished to do so, subject to the following conditions: //
|
|
// //
|
|
// The above copyright notice and this permission notice shall be included in //
|
|
// all copies or substantial portions of the Software. //
|
|
// //
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL //
|
|
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING //
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER //
|
|
// DEALINGS IN THE SOFTWARE. //
|
|
// //
|
|
//----------------------------------------------------------------------------//
|
|
|
|
#define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers.
|
|
|
|
#include "camera.h"
|
|
|
|
#include "ozz/base/log.h"
|
|
#include "ozz/base/maths/box.h"
|
|
#include "ozz/base/maths/math_constant.h"
|
|
#include "ozz/base/maths/math_ex.h"
|
|
#include "ozz/base/platform.h"
|
|
|
|
#include "framework/application.h"
|
|
#include "framework/imgui.h"
|
|
|
|
#include "renderer_impl.h"
|
|
|
|
using ozz::math::Float2;
|
|
using ozz::math::Float3;
|
|
using ozz::math::Float4x4;
|
|
|
|
namespace ozz {
|
|
namespace sample {
|
|
namespace internal {
|
|
|
|
// Declares camera navigation constants.
|
|
const float kDefaultDistance = 8.f;
|
|
const Float3 kDefaultCenter = Float3(0.f, .5f, 0.f);
|
|
const Float2 kDefaultAngle =
|
|
Float2(-ozz::math::kPi * 1.f / 12.f, ozz::math::kPi * 1.f / 5.f);
|
|
const float kAngleFactor = .01f;
|
|
const float kDistanceFactor = .1f;
|
|
const float kScrollFactor = .03f;
|
|
const float kPanFactor = .05f;
|
|
const float kKeyboardFactor = 100.f;
|
|
const float kNear = .01f;
|
|
const float kFar = 1000.f;
|
|
const float kFovY = ozz::math::kPi / 3.f;
|
|
const float kFrameAllZoomOut = 1.3f; // 30% bigger than the scene.
|
|
|
|
// Setups initial values.
|
|
Camera::Camera()
|
|
: projection_(Float4x4::identity()),
|
|
projection_2d_(Float4x4::identity()),
|
|
view_(Float4x4::identity()),
|
|
view_proj_(Float4x4::identity()),
|
|
angles_(kDefaultAngle),
|
|
center_(kDefaultCenter),
|
|
distance_(kDefaultDistance),
|
|
mouse_last_x_(0),
|
|
mouse_last_y_(0),
|
|
mouse_last_wheel_(0),
|
|
auto_framing_(true) {}
|
|
|
|
Camera::~Camera() {}
|
|
|
|
void Camera::Update(const math::Box& _box, float _delta_time,
|
|
bool _first_frame) {
|
|
// Frame the scene according to the provided box.
|
|
if (_box.is_valid()) {
|
|
if (auto_framing_ || _first_frame) {
|
|
center_ = (_box.max + _box.min) * .5f;
|
|
if (_first_frame) {
|
|
const float radius = Length(_box.max - _box.min) * .5f;
|
|
distance_ = radius * kFrameAllZoomOut / tanf(kFovY * .5f);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update manual controls.
|
|
const Controls controls = UpdateControls(_delta_time);
|
|
|
|
// Disable autoframing according to inputs.
|
|
auto_framing_ &=
|
|
!controls.panning && !controls.zooming && !controls.zooming_wheel;
|
|
}
|
|
|
|
void Camera::Update(const math::Float4x4& _transform, const math::Box& _box,
|
|
float _delta_time, bool _first_frame) {
|
|
// Extract distance and angles such that theu are coherent when switching out
|
|
// of auto_framing_.
|
|
if (_box.is_valid()) {
|
|
if (auto_framing_ || _first_frame) {
|
|
// Extract components from the view martrix.
|
|
ozz::math::Float3 camera_dir;
|
|
ozz::math::Store3PtrU(-ozz::math::Normalize3(_transform.cols[2]),
|
|
&camera_dir.x);
|
|
ozz::math::Float3 camera_pos;
|
|
ozz::math::Store3PtrU(_transform.cols[3], &camera_pos.x);
|
|
|
|
// Arbitrary decides that distance (focus point) is from camera to scene
|
|
// center.
|
|
const ozz::math::Float3 box_center_ = (_box.max + _box.min) * .5f;
|
|
distance_ = Length(box_center_ - camera_pos);
|
|
center_ = camera_pos + camera_dir * distance_;
|
|
angles_.x = asinf(camera_dir.y);
|
|
angles_.y = atan2(-camera_dir.x, -camera_dir.z);
|
|
}
|
|
}
|
|
|
|
// Update manual controls.
|
|
const Controls controls = UpdateControls(_delta_time);
|
|
|
|
// Disable autoframing according to inputs.
|
|
auto_framing_ &= !controls.panning && !controls.rotating &&
|
|
!controls.zooming && !controls.zooming_wheel;
|
|
|
|
if (auto_framing_) {
|
|
view_ = Invert(_transform);
|
|
}
|
|
}
|
|
|
|
Camera::Controls Camera::UpdateControls(float _delta_time) {
|
|
Controls controls;
|
|
controls.zooming = false;
|
|
controls.zooming_wheel = false;
|
|
controls.rotating = false;
|
|
controls.panning = false;
|
|
|
|
// Mouse wheel + SHIFT activates Zoom.
|
|
if (glfwGetKey(GLFW_KEY_LSHIFT) == GLFW_PRESS) {
|
|
const int w = glfwGetMouseWheel();
|
|
const int dw = w - mouse_last_wheel_;
|
|
mouse_last_wheel_ = w;
|
|
if (dw != 0) {
|
|
controls.zooming_wheel = true;
|
|
distance_ *= 1.f + -dw * kScrollFactor;
|
|
}
|
|
} else {
|
|
mouse_last_wheel_ = glfwGetMouseWheel();
|
|
}
|
|
|
|
// Fetches current mouse position and compute its movement since last frame.
|
|
int x, y;
|
|
glfwGetMousePos(&x, &y);
|
|
const int mdx = x - mouse_last_x_;
|
|
const int mdy = y - mouse_last_y_;
|
|
mouse_last_x_ = x;
|
|
mouse_last_y_ = y;
|
|
|
|
// Finds keyboard relative dx and dy commmands.
|
|
const int timed_factor =
|
|
ozz::math::Max(1, static_cast<int>(kKeyboardFactor * _delta_time));
|
|
const int kdx =
|
|
timed_factor * (glfwGetKey(GLFW_KEY_LEFT) - glfwGetKey(GLFW_KEY_RIGHT));
|
|
const int kdy =
|
|
timed_factor * (glfwGetKey(GLFW_KEY_DOWN) - glfwGetKey(GLFW_KEY_UP));
|
|
const bool keyboard_interact = kdx || kdy;
|
|
|
|
// Computes composed keyboard and mouse dx and dy.
|
|
const int dx = mdx + kdx;
|
|
const int dy = mdy + kdy;
|
|
|
|
// Mouse right button activates Zoom, Pan and Orbit modes.
|
|
if (keyboard_interact ||
|
|
glfwGetMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) {
|
|
if (glfwGetKey(GLFW_KEY_LSHIFT) == GLFW_PRESS) { // Zoom mode.
|
|
controls.zooming = true;
|
|
|
|
distance_ += dy * kDistanceFactor;
|
|
} else if (glfwGetKey(GLFW_KEY_LALT) == GLFW_PRESS) { // Pan mode.
|
|
controls.panning = true;
|
|
|
|
const float dx_pan = -dx * kPanFactor;
|
|
const float dy_pan = -dy * kPanFactor;
|
|
|
|
// Moves along camera axes.
|
|
math::Float4x4 transpose = Transpose(view_);
|
|
math::Float3 right_transpose, up_transpose;
|
|
math::Store3PtrU(transpose.cols[0], &right_transpose.x);
|
|
math::Store3PtrU(transpose.cols[1], &up_transpose.x);
|
|
center_ = center_ + right_transpose * dx_pan + up_transpose * dy_pan;
|
|
} else { // Orbit mode.
|
|
controls.rotating = true;
|
|
|
|
angles_.x = fmodf(angles_.x - dy * kAngleFactor, ozz::math::k2Pi);
|
|
angles_.y = fmodf(angles_.y - dx * kAngleFactor, ozz::math::k2Pi);
|
|
}
|
|
}
|
|
|
|
// Build the model view matrix components.
|
|
const Float4x4 center = Float4x4::Translation(
|
|
math::simd_float4::Load(center_.x, center_.y, center_.z, 1.f));
|
|
const Float4x4 y_rotation = Float4x4::FromAxisAngle(
|
|
math::simd_float4::y_axis(), math::simd_float4::Load1(angles_.y));
|
|
const Float4x4 x_rotation = Float4x4::FromAxisAngle(
|
|
math::simd_float4::x_axis(), math::simd_float4::Load1(angles_.x));
|
|
const Float4x4 distance =
|
|
Float4x4::Translation(math::simd_float4::Load(0.f, 0.f, distance_, 1.f));
|
|
|
|
// Concatenate view matrix components.
|
|
view_ = Invert(center * y_rotation * x_rotation * distance);
|
|
|
|
return controls;
|
|
}
|
|
|
|
void Camera::Reset(const math::Float3& _center, const math::Float2& _angles,
|
|
float _distance) {
|
|
center_ = _center;
|
|
angles_ = _angles;
|
|
distance_ = _distance;
|
|
}
|
|
|
|
void Camera::OnGui(ImGui* _im_gui) {
|
|
const char* controls_label =
|
|
"-RMB: Rotate\n"
|
|
"-Shift + Wheel: Zoom\n"
|
|
"-Shift + RMB: Zoom\n"
|
|
"-Alt + RMB: Pan\n";
|
|
_im_gui->DoLabel(controls_label, ImGui::kLeft, false);
|
|
|
|
_im_gui->DoCheckBox("Automatic", &auto_framing_);
|
|
}
|
|
|
|
void Camera::Bind3D() {
|
|
// Updates internal vp matrix.
|
|
view_proj_ = projection_ * view_;
|
|
}
|
|
|
|
void Camera::Bind2D() {
|
|
// Updates internal vp matrix. View matrix is identity.
|
|
view_proj_ = projection_2d_;
|
|
}
|
|
|
|
void Camera::Resize(int _width, int _height) {
|
|
// Handle empty windows.
|
|
if (_width <= 0 || _height <= 0) {
|
|
projection_ = ozz::math::Float4x4::identity();
|
|
projection_2d_ = ozz::math::Float4x4::identity();
|
|
return;
|
|
}
|
|
|
|
// Compute the 3D projection matrix.
|
|
const float ratio = 1.f * _width / _height;
|
|
const float h = tan(kFovY * .5f) * kNear;
|
|
const float w = h * ratio;
|
|
|
|
projection_.cols[0] = math::simd_float4::Load(kNear / w, 0.f, 0.f, 0.f);
|
|
projection_.cols[1] = math::simd_float4::Load(0.f, kNear / h, 0.f, 0.f);
|
|
projection_.cols[2] =
|
|
math::simd_float4::Load(0.f, 0.f, -(kFar + kNear) / (kFar - kNear), -1.f);
|
|
projection_.cols[3] = math::simd_float4::Load(
|
|
0.f, 0.f, -(2.f * kFar * kNear) / (kFar - kNear), 0.f);
|
|
|
|
// Computes the 2D projection matrix.
|
|
projection_2d_.cols[0] = math::simd_float4::Load(2.f / _width, 0.f, 0.f, 0.f);
|
|
projection_2d_.cols[1] =
|
|
math::simd_float4::Load(0.f, 2.f / _height, 0.f, 0.f);
|
|
projection_2d_.cols[2] = math::simd_float4::Load(0.f, 0.f, -2.0f, 0.f);
|
|
projection_2d_.cols[3] = math::simd_float4::Load(-1.f, -1.f, 0.f, 1.f);
|
|
}
|
|
} // namespace internal
|
|
} // namespace sample
|
|
} // namespace ozz
|