250 lines
9.1 KiB
C++
250 lines
9.1 KiB
C++
/*
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef CAMUTILS_FREEFLIGHT_MANIPULATOR_H
|
|
#define CAMUTILS_FREEFLIGHT_MANIPULATOR_H
|
|
|
|
#include <camutils/Manipulator.h>
|
|
|
|
#include <math/scalar.h>
|
|
#include <math/mat3.h>
|
|
#include <math/mat4.h>
|
|
#include <math/quat.h>
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
namespace filament {
|
|
namespace camutils {
|
|
|
|
using namespace filament::math;
|
|
|
|
template<typename FLOAT>
|
|
class FreeFlightManipulator : public Manipulator<FLOAT> {
|
|
public:
|
|
using vec2 = filament::math::vec2<FLOAT>;
|
|
using vec3 = filament::math::vec3<FLOAT>;
|
|
using vec4 = filament::math::vec4<FLOAT>;
|
|
using Bookmark = filament::camutils::Bookmark<FLOAT>;
|
|
using Base = Manipulator<FLOAT>;
|
|
using Config = typename Base::Config;
|
|
|
|
FreeFlightManipulator(Mode mode, const Config& props) : Base(mode, props) {
|
|
setProperties(props);
|
|
Base::mEye = Base::mProps.flightStartPosition;
|
|
const auto pitch = Base::mProps.flightStartPitch;
|
|
const auto yaw = Base::mProps.flightStartYaw;
|
|
mTargetEuler = {pitch, yaw};
|
|
updateTarget(pitch, yaw);
|
|
}
|
|
|
|
void setProperties(const Config& props) override {
|
|
Config resolved = props;
|
|
|
|
if (resolved.flightPanSpeed == vec2(0, 0)) {
|
|
resolved.flightPanSpeed = vec2(0.01, 0.01);
|
|
}
|
|
if (resolved.flightMaxSpeed == 0.0) {
|
|
resolved.flightMaxSpeed = 10.0;
|
|
}
|
|
if (resolved.flightSpeedSteps == 0) {
|
|
resolved.flightSpeedSteps = 80;
|
|
}
|
|
|
|
Base::setProperties(resolved);
|
|
}
|
|
|
|
void updateTarget(FLOAT pitch, FLOAT yaw) {
|
|
Base::mTarget = Base::mEye + (mat3::eulerZYX(0, yaw, pitch) * vec3(0.0, 0.0, -1.0));
|
|
}
|
|
|
|
void grabBegin(int x, int y, bool strafe) override {
|
|
mGrabWin = {x, y};
|
|
mGrabbing = true;
|
|
mGrabEuler = mTargetEuler;
|
|
}
|
|
|
|
void grabUpdate(int x, int y) override {
|
|
if (!mGrabbing) {
|
|
return;
|
|
}
|
|
|
|
const vec2 del = mGrabWin - vec2{x, y};
|
|
|
|
const auto& grabPitch = mGrabEuler.x;
|
|
const auto& grabYaw = mGrabEuler.y;
|
|
auto& pitch = mTargetEuler.x;
|
|
auto& yaw = mTargetEuler.y;
|
|
|
|
constexpr double EPSILON = 0.001;
|
|
|
|
auto panSpeed = Base::mProps.flightPanSpeed;
|
|
constexpr FLOAT minPitch = (-F_PI_2 + EPSILON);
|
|
constexpr FLOAT maxPitch = ( F_PI_2 - EPSILON);
|
|
pitch = clamp(grabPitch + del.y * -panSpeed.y, minPitch, maxPitch);
|
|
yaw = fmod(grabYaw + del.x * panSpeed.x, 2.0 * F_PI);
|
|
|
|
updateTarget(pitch, yaw);
|
|
}
|
|
|
|
void grabEnd() override {
|
|
mGrabbing = false;
|
|
}
|
|
|
|
void keyDown(typename Base::Key key) override {
|
|
mKeyDown[(int) key] = true;
|
|
}
|
|
|
|
void keyUp(typename Base::Key key) override {
|
|
mKeyDown[(int) key] = false;
|
|
}
|
|
|
|
void scroll(int x, int y, FLOAT scrolldelta) override {
|
|
const FLOAT halfSpeedSteps = Base::mProps.flightSpeedSteps / 2;
|
|
mScrollWheel = clamp(mScrollWheel + scrolldelta, -halfSpeedSteps, halfSpeedSteps);
|
|
// Normalize the scroll position from -1 to 1 and calculate the move speed, in world
|
|
// units per second.
|
|
mScrollPositionNormalized = (mScrollWheel + halfSpeedSteps) / halfSpeedSteps - 1.0;
|
|
mMoveSpeed = pow(Base::mProps.flightMaxSpeed, mScrollPositionNormalized);
|
|
}
|
|
|
|
void update(FLOAT deltaTime) override {
|
|
|
|
auto getLocalDirection = [this]() -> vec3 {
|
|
vec3 directionLocal{ 0.0, 0.0, 0.0 };
|
|
if (mKeyDown[(int)Base::Key::FORWARD]) {
|
|
directionLocal += vec3{ 0.0, 0.0, -1.0 };
|
|
}
|
|
if (mKeyDown[(int)Base::Key::LEFT]) {
|
|
directionLocal += vec3{ -1.0, 0.0, 0.0 };
|
|
}
|
|
if (mKeyDown[(int)Base::Key::BACKWARD]) {
|
|
directionLocal += vec3{ 0.0, 0.0, 1.0 };
|
|
}
|
|
if (mKeyDown[(int)Base::Key::RIGHT]) {
|
|
directionLocal += vec3{ 1.0, 0.0, 0.0 };
|
|
}
|
|
return directionLocal;
|
|
};
|
|
|
|
auto getWorldDirection = [this](vec3 directionLocal) -> vec3 {
|
|
const mat4 orientation = mat4::lookAt(Base::mEye, Base::mTarget, Base::mProps.upVector);
|
|
vec3 directionWorld = (orientation * vec4{ directionLocal, 0.0f }).xyz;
|
|
if (mKeyDown[(int)Base::Key::UP]) {
|
|
directionWorld += vec3{ 0.0, 1.0, 0.0 };
|
|
}
|
|
if (mKeyDown[(int)Base::Key::DOWN]) {
|
|
directionWorld += vec3{ 0.0, -1.0, 0.0 };
|
|
}
|
|
return directionWorld;
|
|
};
|
|
|
|
vec3 const localDirection = getLocalDirection();
|
|
vec3 const worldDirection = getWorldDirection(localDirection);
|
|
|
|
// unit of dampingFactor is [1/s]
|
|
FLOAT const dampingFactor = Base::mProps.flightMoveDamping;
|
|
if (dampingFactor == 0.0) {
|
|
// Without damping, we simply treat the force as our velocity.
|
|
vec3 const speed = worldDirection * mMoveSpeed;
|
|
mEyeVelocity = speed;
|
|
vec3 const positionDelta = mEyeVelocity * deltaTime;
|
|
Base::mEye += positionDelta;
|
|
Base::mTarget += positionDelta;
|
|
} else {
|
|
auto dt = deltaTime / 16.0;
|
|
for (size_t i = 0; i < 16; i++) {
|
|
// Note: the algorithm below doesn't work well for large time steps because
|
|
// we're not using a closed form for updating the position, so we need
|
|
// to loop a few times. We could make this better by having a dynamic
|
|
// loop count. What we're really doing is evaluation the solution to
|
|
// a differential equation numerically.
|
|
|
|
// Kinetic friction is a force opposing velocity and proportional to it.:
|
|
// F = -kv
|
|
// F = ma
|
|
// ==> ma = -kv
|
|
// a = -vk/m [m.s^-2] = [m/s] * [Kg/s] / [Kg]
|
|
// ==> dampingFactor = k/m [1/s] = [Kg/s] / [Kg]
|
|
//
|
|
// The velocity update for dt due to friction is then:
|
|
// v = v + a.dt
|
|
// = v - v * dampingFactor * dt
|
|
// = v * (1.0 - dampingFactor * dt)
|
|
mEyeVelocity = mEyeVelocity * saturate(1.0 - dampingFactor * dt);
|
|
|
|
// We also undergo an acceleration proportional to the distance to the target speed
|
|
// (the closer we are the less we accelerate, similar to a car).
|
|
// F = k * (target_v - v)
|
|
// F = ma
|
|
// ==> ma = k * (target_v - v)
|
|
// a = k/m * (target_v - v) [m.s^-2] = [Kg/s] / [Kg] * [m/s]
|
|
//
|
|
// The velocity update for dt due to the acceleration (the gas basically) is then:
|
|
// v = v + a.dt
|
|
// = v + k/m * (target_v - v).dt
|
|
// We're using the same dampingFactor here, but we don't have to.
|
|
auto const accelerationFactor = dampingFactor;
|
|
vec3 const acceleration = worldDirection *
|
|
(accelerationFactor * std::max(mMoveSpeed - length(mEyeVelocity), FLOAT(0)));
|
|
mEyeVelocity += acceleration * dt;
|
|
vec3 const positionDelta = mEyeVelocity * dt;
|
|
Base::mEye += positionDelta;
|
|
Base::mTarget += positionDelta;
|
|
}
|
|
}
|
|
}
|
|
|
|
Bookmark getCurrentBookmark() const override {
|
|
Bookmark bookmark;
|
|
bookmark.flight.position = Base::mEye;
|
|
bookmark.flight.pitch = mTargetEuler.x;
|
|
bookmark.flight.yaw = mTargetEuler.y;
|
|
return bookmark;
|
|
}
|
|
|
|
Bookmark getHomeBookmark() const override {
|
|
Bookmark bookmark;
|
|
bookmark.flight.position = Base::mProps.flightStartPosition;
|
|
bookmark.flight.pitch = Base::mProps.flightStartPitch;
|
|
bookmark.flight.yaw = Base::mProps.flightStartYaw;
|
|
return bookmark;
|
|
}
|
|
|
|
void jumpToBookmark(const Bookmark& bookmark) override {
|
|
Base::mEye = bookmark.flight.position;
|
|
mTargetEuler.x = bookmark.flight.pitch;
|
|
mTargetEuler.y = bookmark.flight.yaw;
|
|
updateTarget(bookmark.flight.pitch, bookmark.flight.yaw);
|
|
}
|
|
|
|
private:
|
|
vec2 mGrabWin;
|
|
vec2 mTargetEuler; // (pitch, yaw)
|
|
vec2 mGrabEuler; // (pitch, yaw)
|
|
bool mKeyDown[(int) Base::Key::COUNT] = {false};
|
|
bool mGrabbing = false;
|
|
FLOAT mScrollWheel = 0.0f;
|
|
FLOAT mScrollPositionNormalized = 0.0f;
|
|
FLOAT mMoveSpeed = 1.0f;
|
|
vec3 mEyeVelocity;
|
|
};
|
|
|
|
} // namespace camutils
|
|
} // namespace filament
|
|
|
|
#endif /* CAMUTILS_FREEFLIGHT_MANIPULATOR_H */
|