blob: 2c00abddc1e4c9049f3bd8719ca97fa458d6697e [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/ui/root_presenter/perspective_demo_mode.h"
#include <array>
#if defined(countof)
// Workaround for compiler error due to Zircon defining countof() as a macro.
// Redefines countof() using GLM_COUNTOF(), which currently provides a more
// sophisticated implementation anyway.
#undef countof
#include <glm/glm.hpp>
#define countof(X) GLM_COUNTOF(X)
#else
// No workaround required.
#include <glm/glm.hpp>
#endif
#include <glm/ext.hpp>
//#include <glm/gtc/type_ptr.hpp>
#include "garnet/bin/ui/root_presenter/presentation.h"
namespace root_presenter {
namespace {
constexpr float kPi = glm::pi<float>();
} // namespace
PerspectiveDemoMode::PerspectiveDemoMode() {}
bool PerspectiveDemoMode::OnEvent(const fuchsia::ui::input::InputEvent& event,
Presentation* presenter) {
if (event.is_pointer()) {
const fuchsia::ui::input::PointerEvent& pointer = event.pointer();
if (animation_state_ == kThreeQuarters ||
animation_state_ == kPerspective) {
if (pointer.phase == fuchsia::ui::input::PointerEventPhase::DOWN) {
// If we're not already panning/rotating the camera, then start, but
// only if the touch-down is in the bottom 10% of the screen.
if (!trackball_pointer_down_ &&
pointer.y > 0.9f * presenter->actual_display_info().height_in_px) {
trackball_pointer_down_ = true;
trackball_device_id_ = pointer.device_id;
trackball_pointer_id_ = pointer.pointer_id;
trackball_previous_x_ = pointer.x;
}
} else if (pointer.phase == fuchsia::ui::input::PointerEventPhase::MOVE) {
// If the moved pointer is the one that is currently panning/rotating
// the camera, then update the camera position.
if (trackball_pointer_down_ &&
trackball_device_id_ == pointer.device_id &&
trackball_device_id_ == pointer.device_id) {
float rate = -2.5f / presenter->actual_display_info().width_in_px;
float change = rate * (pointer.x - trackball_previous_x_);
trackball_previous_x_ = pointer.x;
if (animation_state_ == kThreeQuarters) {
target_camera_pan_ += change;
target_camera_pan_ = glm::clamp(target_camera_pan_, -1.f, 1.f);
} else if (animation_state_ == kPerspective) {
target_camera_zoom_ += change;
target_camera_zoom_ = glm::clamp(target_camera_zoom_, 0.0f, 1.0f);
float fov =
360.f * ComputeHalfFov(presenter, target_camera_zoom_) / kPi;
FXL_LOG(INFO) << "Current perspective fov is " << fov << "degrees";
}
}
}
}
// Pointer release should be handled no matter which state we are in.
if (pointer.phase == fuchsia::ui::input::PointerEventPhase::UP) {
// The pointer was released.
if (trackball_pointer_down_ &&
trackball_device_id_ == pointer.device_id &&
trackball_device_id_ == pointer.device_id) {
trackball_pointer_down_ = false;
}
}
} else if (event.is_keyboard()) {
// Alt-Backspace cycles through modes.
const fuchsia::ui::input::KeyboardEvent& kbd = event.keyboard();
if ((kbd.modifiers & fuchsia::ui::input::kModifierAlt) &&
kbd.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED &&
kbd.code_point == 0 && kbd.hid_usage == 42 &&
!trackball_pointer_down_) {
HandleAltBackspace(presenter);
return true;
}
}
return false;
}
void PerspectiveDemoMode::HandleAltBackspace(Presentation* presenter) {
switch (animation_state_) {
case kOrthographic:
target_camera_pan_ = 0.0f;
target_camera_zoom_ = 0.0f;
animation_state_ = kAnimateToThreeQuarters;
animation_start_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC);
break;
case kThreeQuarters:
animation_state_ = kAnimateToPerspective;
animation_start_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC);
break;
case kPerspective:
animation_state_ = kAnimateToOrthographic;
animation_start_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC);
default:
return;
}
UpdateAnimation(presenter, animation_start_time_);
}
float PerspectiveDemoMode::ComputeHalfFov(Presentation* presenter,
float zoom) const {
// The default camera emulates an orthographic camera, by creating a .1-degree
// half angle camera, at the appropriate distance.
constexpr float kMinHalfFov = .1f * kPi / 180.f;
// TODO(MZ-194): The maximum half fov is determined by the minimum camera
// distance. This distance matches the hard coded behavior from
// escher::Camera::NewOrtho() and scenic::gfx::Layer::GetViewingVolume(). For
// a 1600px height display, this works out to ~76 degrees.
float max_half_fov =
atan(presenter->actual_display_info().height_in_px * 0.5f / 1010.f);
return glm::lerp(kMinHalfFov, max_half_fov, zoom);
}
void PerspectiveDemoMode::UpdateCamera(Presentation* presenter, float pan_param,
float zoom_param) {
const float half_width = presenter->actual_display_info().width_in_px * 0.5f;
const float half_height =
presenter->actual_display_info().height_in_px * 0.5f;
// Always look at the middle of the stage.
const float target[3] = {half_width, half_height, 0};
// Ease-in/ease-out for the animation.
pan_param = glm::smoothstep(0.f, 1.f, pan_param);
zoom_param = glm::smoothstep(0.f, 1.f, zoom_param);
// The target camera takes into account the current authored pan and zoom
// requests.
float zoom = glm::lerp(0.f, target_camera_zoom_, zoom_param);
float half_fovy = ComputeHalfFov(presenter, zoom);
float eye_dist = half_height / tan(half_fovy);
glm::vec3 eye_start(half_width, half_height, eye_dist);
constexpr float kMaxCameraPan = kPi / 4;
float eye_end_x =
sin(glm::lerp(0.f, kMaxCameraPan, target_camera_pan_)) * eye_dist +
half_width;
float eye_end_y =
cos(glm::lerp(0.f, kMaxCameraPan, target_camera_pan_)) * eye_dist +
half_height;
float three_quarters_distance = 0.75f * eye_dist;
glm::vec3 eye_end(eye_end_x, eye_end_y, three_quarters_distance);
// Halfway point for the pan animation is further out than the starting point,
// to get a cool zoom out->zoom in effect.
glm::vec3 eye_mid = glm::mix(eye_start, eye_end, 0.4f);
eye_mid.z = 1.5f * eye_dist;
// Quadratic bezier.
glm::vec3 eye = glm::mix(glm::mix(eye_start, eye_mid, pan_param),
glm::mix(eye_mid, eye_end, pan_param), pan_param);
glm::vec3 glm_up =
glm::mix(glm::vec3(0, 1.f, 0.f), glm::vec3(0, 0.1f, -0.9f), pan_param);
glm_up = glm::normalize(glm_up);
float up[3] = {glm_up[0], glm_up[1], glm_up[2]};
presenter->camera()->SetTransform(glm::value_ptr(eye), target, up);
presenter->camera()->SetProjection(2.f * half_fovy);
}
bool PerspectiveDemoMode::UpdateAnimation(Presentation* presenter,
uint64_t presentation_time) {
if (animation_state_ == kOrthographic) {
return false;
}
double secs = static_cast<double>(presentation_time - animation_start_time_) /
1'000'000'000;
constexpr double kAnimationDuration = 1.3;
float time_param = secs / kAnimationDuration;
if (time_param >= 1.f) {
time_param = 1.f;
switch (animation_state_) {
case kAnimateToThreeQuarters:
animation_state_ = kThreeQuarters;
break;
case kAnimateToPerspective:
animation_state_ = kPerspective;
break;
case kAnimateToOrthographic: {
animation_state_ = kOrthographic;
const float half_width =
presenter->actual_display_info().width_in_px * 0.5f;
const float half_height =
presenter->actual_display_info().height_in_px * 0.5f;
// Always look at the middle of the stage.
const float target[3] = {half_width, half_height, 0};
glm::vec3 glm_up(0, 1.f, 0.f);
glm_up = glm::normalize(glm_up);
float up[3] = {glm_up[0], glm_up[1], glm_up[2]};
// Switch back to ortho view, and re-enable clipping.
// TODO(MZ-194): This distance matches the hard coded behavior from
// escher::Camera::NewOrtho() and
// scenic::gfx::Layer::GetViewingVolume().
float ortho_eye[3] = {half_width, half_height, 1010.f};
presenter->camera()->SetTransform(ortho_eye, target, up);
presenter->camera()->SetProjection(0.f);
return true;
}
default:
break;
}
}
float pan_param;
float zoom_param;
switch (animation_state_) {
case kAnimateToThreeQuarters:
pan_param = time_param;
zoom_param = 0.f;
break;
case kAnimateToPerspective:
pan_param = 1.f - time_param;
zoom_param = time_param;
break;
case kAnimateToOrthographic:
pan_param = 0.f;
zoom_param = 1.f - time_param;
break;
case kThreeQuarters:
pan_param = 1.f;
zoom_param = 0.f;
break;
case kPerspective:
pan_param = 0.f;
zoom_param = 1.f;
break;
default:
FXL_DCHECK(false);
return false;
}
UpdateCamera(presenter, pan_param, zoom_param);
return true;
}
} // namespace root_presenter