blob: 2101015c1284f400318170a97eadefd4a799692c [file] [log] [blame]
/*
* Copyright 2017 The Cobalt Authors. 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 "cobalt/input/camera_3d_input_poller.h"
#include <algorithm>
#include <cmath>
#include "cobalt/input/create_default_camera_3d.h"
#include "third_party/glm/glm/gtc/matrix_transform.hpp"
#include "third_party/glm/glm/gtc/quaternion.hpp"
#include "third_party/glm/glm/gtx/transform.hpp"
namespace cobalt {
namespace input {
Camera3DInputPoller::Camera3DInputPoller(
const scoped_refptr<input::InputPoller>& input_poller)
: roll_in_radians_(0.0f),
pitch_in_radians_(0.0f),
yaw_in_radians_(0.0f),
input_poller_(input_poller),
width_to_height_aspect_ratio_(16.0f / 9.0f),
vertical_fov_(60.0f) {}
void Camera3DInputPoller::CreateKeyMapping(int keycode, uint32 camera_axis,
float degrees_per_second) {
base::AutoLock lock(mutex_);
keycode_map_[keycode] = KeycodeMappingInfo(camera_axis, degrees_per_second);
}
void Camera3DInputPoller::ClearKeyMapping(int keycode) {
base::AutoLock lock(mutex_);
keycode_map_.erase(keycode);
}
void Camera3DInputPoller::ClearAllKeyMappings() {
base::AutoLock lock(mutex_);
keycode_map_.clear();
}
glm::quat Camera3DInputPoller::orientation() const {
return glm::angleAxis(-roll_in_radians_, glm::vec3(0, 0, 1)) *
glm::angleAxis(-pitch_in_radians_, glm::vec3(1, 0, 0)) *
glm::angleAxis(-yaw_in_radians_, glm::vec3(0, 1, 0));
}
glm::quat Camera3DInputPoller::GetOrientation() const {
base::AutoLock lock(mutex_);
return orientation();
}
base::CameraTransform
Camera3DInputPoller::GetCameraTransformAndUpdateOrientation() {
base::AutoLock lock(mutex_);
AccumulateOrientation();
glm::mat4 view_matrix = glm::mat4_cast(orientation());
const float kNearZ = 0.01f;
const float kFarZ = 1000.0f;
glm::mat4 projection_matrix = glm::perspectiveRH(
vertical_fov_, width_to_height_aspect_ratio_, kNearZ, kFarZ);
return base::CameraTransform(projection_matrix, view_matrix);
}
void Camera3DInputPoller::UpdatePerspective(float width_to_height_aspect_ratio,
float vertical_fov) {
width_to_height_aspect_ratio_ = width_to_height_aspect_ratio;
vertical_fov_ = vertical_fov;
}
void Camera3DInputPoller::Reset() {
base::AutoLock lock(mutex_);
roll_in_radians_ = 0.0f;
pitch_in_radians_ = 0.0f;
yaw_in_radians_ = 0.0f;
}
void Camera3DInputPoller::AccumulateOrientation() {
if (!input_poller_) {
// Nothing to do if no input poller was provided.
return;
}
base::TimeTicks now = base::TimeTicks::Now();
if (last_update_) {
base::TimeDelta delta = now - *last_update_;
// Cap out the maximum time delta that we will accumulate changes over, to
// avoid a random extra long time delta that completely changes the camera
// orientation.
const base::TimeDelta kMaxTimeDelta = base::TimeDelta::FromMilliseconds(40);
if (delta > kMaxTimeDelta) {
delta = kMaxTimeDelta;
}
// Accumulate new rotation from all mapped inputs.
for (KeycodeMap::const_iterator iter = keycode_map_.begin();
iter != keycode_map_.end(); ++iter) {
// If the key does not have analog output, the AnalogInput() method will
// always return 0.0f, so check this first, and if it is indeed 0,
// fallback to a check to see if the button is digital and pressed.
float value = input_poller_->AnalogInput(static_cast<SbKey>(iter->first));
if (value == 0.0f) {
value = input_poller_->IsPressed(static_cast<SbKey>(iter->first))
? 1.0f
: 0.0f;
}
// Get a pointer to the camera axis angle that this key is bound to.
float* target_angle;
switch (iter->second.axis) {
case kCameraRoll:
target_angle = &roll_in_radians_;
break;
case kCameraPitch:
target_angle = &pitch_in_radians_;
break;
case kCameraYaw:
target_angle = &yaw_in_radians_;
break;
}
// Apply the angle adjustment from the key.
*target_angle += value *
DegreesToRadians(iter->second.degrees_per_second) *
static_cast<float>(delta.InSecondsF());
}
// Clamp the angles to ensure that they remain in the valid range.
ClampAngles();
}
last_update_ = now;
}
void Camera3DInputPoller::ClampAngles() {
float kTwoPi = static_cast<float>(M_PI * 2);
float kPiOverTwo = static_cast<float>(M_PI / 2);
pitch_in_radians_ =
std::max(-kPiOverTwo, std::min(kPiOverTwo, pitch_in_radians_));
roll_in_radians_ = fmod(roll_in_radians_, kTwoPi);
if (roll_in_radians_ < 0) {
roll_in_radians_ += kTwoPi;
}
yaw_in_radians_ = fmod(yaw_in_radians_, kTwoPi);
if (yaw_in_radians_ < 0) {
yaw_in_radians_ += kTwoPi;
}
}
scoped_refptr<Camera3D> CreatedDefaultCamera3D(
SbWindow window, const scoped_refptr<InputPoller>& input_poller) {
return new Camera3DInputPoller(input_poller);
}
} // namespace input
} // namespace cobalt