blob: 90a502e8affe17450dc8a50c76437a3cc37b6d44 [file] [log] [blame]
/*
* Copyright 2017 Google Inc. 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/dom/camera_3d_impl.h"
#include <algorithm>
#include <cmath>
#include "third_party/glm/glm/gtc/matrix_transform.hpp"
#include "third_party/glm/glm/gtx/transform.hpp"
namespace cobalt {
namespace dom {
Camera3DImpl::Camera3DImpl(
const scoped_refptr<input::InputPoller>& input_poller)
: input_poller_(input_poller) {}
void Camera3DImpl::CreateKeyMapping(int keycode, uint32 camera_axis,
float degrees_per_second) {
base::AutoLock lock(mutex_);
keycode_map_[keycode] = KeycodeMappingInfo(camera_axis, degrees_per_second);
}
void Camera3DImpl::ClearKeyMapping(int keycode) {
base::AutoLock lock(mutex_);
keycode_map_.erase(keycode);
}
void Camera3DImpl::ClearAllKeyMappings() {
base::AutoLock lock(mutex_);
keycode_map_.clear();
}
void Camera3DImpl::Reset() {
base::AutoLock lock(mutex_);
orientation_ = Orientation();
}
namespace {
const float kPiF = static_cast<float>(M_PI);
float DegreesToRadians(float degrees) { return (degrees / 360.0f) * 2 * kPiF; }
} // namespace
glm::mat4 Camera3DImpl::QueryViewPerspectiveMatrix(
float width_over_height_aspect_ratio, float max_horizontal_fov_rad,
float max_vertical_fov_rad) {
base::AutoLock lock(mutex_);
AccumulateOrientation();
// Setup a temporary demo camera animation to show that this functionality
// works. This should eventually be replaced by camera adjustments driven
// by input. Note that we invert the rotation angles since this matrix is
// applied to the objects in our scene, and if the camera moves right, the
// objects, relatively, would move right.
glm::mat4 camera_rotations =
glm::rotate(-DegreesToRadians(orientation_.roll), glm::vec3(0, 0, 1)) *
glm::rotate(-DegreesToRadians(orientation_.pitch), glm::vec3(1, 0, 0)) *
glm::rotate(-DegreesToRadians(orientation_.yaw), glm::vec3(0, 1, 0));
// Setup a (right-handed) perspective projection matrix.
float vertical_fov_rad =
std::min(max_vertical_fov_rad,
2 * static_cast<float>(atan(tan(max_horizontal_fov_rad * 0.5f) /
width_over_height_aspect_ratio)));
const float kNearZ = 0.01f;
const float kFarZ = 1000.0f;
glm::mat4 projection = glm::perspectiveRH(
vertical_fov_rad, width_over_height_aspect_ratio, kNearZ, kFarZ);
return projection * camera_rotations;
}
void Camera3DImpl::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;
}
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 = &orientation_.roll;
break;
case kCameraPitch:
target_angle = &orientation_.pitch;
break;
case kCameraYaw:
target_angle = &orientation_.yaw;
break;
}
// Apply the angle adjustment from the key.
*target_angle += value * iter->second.degrees_per_second *
static_cast<float>(delta.InSecondsF());
// Apply any clamping or wrapping to the resulting camera angles.
if (iter->second.axis == kCameraPitch) {
*target_angle = std::min(90.0f, std::max(-90.0f, *target_angle));
} else {
*target_angle = static_cast<float>(fmod(*target_angle, 360));
if (*target_angle < 0) {
*target_angle += 360;
}
}
}
}
last_update_ = now;
}
} // namespace dom
} // namespace cobalt