From 24b47f6f02b2902e5aed9a3aa86fcd604072a0a7 Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Fri, 5 Jan 2024 12:42:08 +0300 Subject: [PATCH 1/9] Tweak pitch limit on FreeCamera to avoid warping --- src/graphics/freecamera.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/graphics/freecamera.cpp b/src/graphics/freecamera.cpp index 8c2fbf39..7f03d230 100644 --- a/src/graphics/freecamera.cpp +++ b/src/graphics/freecamera.cpp @@ -30,12 +30,13 @@ FreeCamera::FreeCamera() : _clearDirection(false), _clearRotation(false) {} void FreeCamera::update(float delta) { + constexpr double cameraPitchLimit = M_PI_2 - 1e-5; _rotationAttitude.x += glm::radians(_movementRotation.x * delta * _rotationFactor); _rotationAttitude.y -= glm::radians(_movementRotation.y * delta * _rotationFactor); // roll is not used so far - _rotationAttitude.z += glm::radians(_movementRotation.z * delta * _rotationFactor); + // _rotationAttitude.z += glm::radians(_movementRotation.z * delta * _rotationFactor); // limit pitch to -90..90 degree range - _rotationAttitude.y = glm::clamp(double(_rotationAttitude.y), -M_PI_2, M_PI_2); + _rotationAttitude.y = glm::clamp(double(_rotationAttitude.y), -cameraPitchLimit, cameraPitchLimit); _direction.x = cos(_rotationAttitude.y) * cos(_rotationAttitude.x); _direction.y = sin(_rotationAttitude.y); _direction.z = sin(_rotationAttitude.x) * cos(_rotationAttitude.y); From bbc84fa581773c0cf16c509889b57d7091ae065e Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Fri, 5 Jan 2024 13:50:36 +0300 Subject: [PATCH 2/9] Add raycastStatic() method for PhysicsMan --- src/physics/physicsman.cpp | 7 +++++++ src/physics/physicsman.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/physics/physicsman.cpp b/src/physics/physicsman.cpp index 03d0025a..2c0b6716 100644 --- a/src/physics/physicsman.cpp +++ b/src/physics/physicsman.cpp @@ -83,4 +83,11 @@ void PhysicsManager::remove(btActionInterface *actionInterface) { _world->removeAction(actionInterface); } +btCollisionWorld::ClosestRayResultCallback PhysicsManager::raycastStatic(btVector3& from, btVector3& to) { + btCollisionWorld::ClosestRayResultCallback results(from, to); + results.m_collisionFilterMask = btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter; + _world->rayTest(from, to, results); + return results; +} + } diff --git a/src/physics/physicsman.h b/src/physics/physicsman.h index 771c573d..4fb54934 100644 --- a/src/physics/physicsman.h +++ b/src/physics/physicsman.h @@ -44,6 +44,7 @@ class PhysicsManager : public Common::Singleton { void remove(btCollisionObject *collisionObject); void remove(btRigidBody *collisionObject); void remove(btActionInterface * actionInterface); + btCollisionWorld::ClosestRayResultCallback raycastStatic(btVector3& from, btVector3& to); private: bool _debugDraw; From 8a15aca2102a06afa4dce59c427b2605c92d9b78 Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Fri, 5 Jan 2024 13:53:10 +0300 Subject: [PATCH 3/9] Add ControlledOrbitalCamera Adds OrbitalCamera and ControlledOrbitalCamera classes. Other changes: - Add CharacterController.getUpperPosition(); - Add _fov field and getFOV() for base Camera class; - Tweak Renderer to use camera's FOV in calculations --- src/controlledorbitalcamera.cpp | 112 ++++++++++++++++++++++++++ src/controlledorbitalcamera.h | 50 ++++++++++++ src/engine.cpp | 14 +++- src/game.cpp | 9 +-- src/graphics/camera.h | 3 + src/graphics/orbitalcamera.cpp | 119 ++++++++++++++++++++++++++++ src/graphics/orbitalcamera.h | 77 ++++++++++++++++++ src/graphics/renderer.cpp | 9 ++- src/graphics/renderer.h | 1 + src/physics/charactercontroller.cpp | 5 ++ src/physics/charactercontroller.h | 8 ++ 11 files changed, 399 insertions(+), 8 deletions(-) create mode 100644 src/controlledorbitalcamera.cpp create mode 100644 src/controlledorbitalcamera.h create mode 100644 src/graphics/orbitalcamera.cpp create mode 100644 src/graphics/orbitalcamera.h diff --git a/src/controlledorbitalcamera.cpp b/src/controlledorbitalcamera.cpp new file mode 100644 index 00000000..97f3c5bc --- /dev/null +++ b/src/controlledorbitalcamera.cpp @@ -0,0 +1,112 @@ +/* OpenAWE - A reimplementation of Remedys Alan Wake Engine + * + * OpenAWE is the legal property of its developers, whose names + * can be found in the AUTHORS file distributed with this source + * distribution. + * + * OpenAWE is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * OpenAWE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenAWE. If not, see . + */ + +#include "src/common/crc32.h" + +#include "src/events/eventman.h" + +#include "src/physics/charactercontroller.h" +#include "src/physics/physicsman.h" + +#include "src/controlledorbitalcamera.h" + +static constexpr uint32_t kRotateMouse = Common::crc32("ORBITALCAM_ROTATE_MOUSE"); +static constexpr uint32_t kRotateGamepad = Common::crc32("ORBITALCAM_ROTATE_GAMEPAD"); + +static constexpr uint32_t kSwitchShoulder = Common::crc32("ORBITALCAM_SWITCH_SHOULDER"); +static constexpr uint32_t kAimWeapon = Common::crc32("ORBITALCAM_AIM_WEAPON"); +static constexpr uint32_t kFocusCamera = Common::crc32("ORBITALCAM_FOCUS_CAMERA"); + +ControlledOrbitalCamera::ControlledOrbitalCamera() { + Events::EventCallback callbackRotate = [this](auto && PH1) { handleRotation(std::forward(PH1)); }; + Events::EventCallback callbackStates = [this](auto && PH1) { handleStates(std::forward(PH1)); }; + + EventMan.setActionCallback( + {kSwitchShoulder, kAimWeapon, kFocusCamera}, + callbackStates + ); + + EventMan.setActionCallback( + {kRotateMouse, kRotateGamepad}, + callbackRotate + ); + + EventMan.addBinding(kSwitchShoulder, Events::kKeyTab); + EventMan.addBinding(kAimWeapon, Events::kMouseLeft); + EventMan.addBinding(kFocusCamera, Events::kKeyF); + + EventMan.addBinding(kSwitchShoulder, Events::kGamepadButtonLeftBumper); + EventMan.addBinding(kAimWeapon, Events::kGamepadButtonRightBumper); + EventMan.addBinding(kFocusCamera, Events::kGamepadButtonA); + + EventMan.add2DAxisBinding(kRotateMouse, Events::kMousePosition); + EventMan.add2DAxisBinding(kRotateGamepad, Events::kGamepadAxisRight); +} + +void ControlledOrbitalCamera::handleRotation(const Events::Event &event) { + const Events::AxisEvent axisEvent = std::get>(event.data); + if (event.action == kRotateMouse) { + _movementRotation.x = axisEvent.delta.x; + _movementRotation.y = axisEvent.delta.y; + } else if (event.action == kRotateGamepad) { + _movementRotation.x = axisEvent.absolute.x * 10.0f; + _movementRotation.y = axisEvent.absolute.y * 10.0f; + } +} + +void ControlledOrbitalCamera::handleStates(const Events::Event &event) { + const Events::KeyEvent keyEvent = std::get(event.data); + if (event.action == kAimWeapon) { + if (keyEvent.state == Events::kPress && _state == Graphics::kCameraDefault) { + setAiming(true); + } else if (keyEvent.state == Events::kRelease && _state == Graphics::kCameraAiming) { + setAiming(false); + } + } else if (keyEvent.state == Events::kPress) { + if (event.action == kSwitchShoulder) { + switchShoulder(); + } else if (event.action == kFocusCamera) { + if (_state == Graphics::kCameraDefault) { + focusOn(glm::vec3(1000, 1000, 1000)); + } else if (_state == Graphics::kCameraFocused) { + unfocus(); + } + } + } +} + +void ControlledOrbitalCamera::attachTo(Physics::CharacterControllerPtr object) { + _followedObject = object; + setOrbitOrigin(_followedObject->getUpperPosition()); + _orbitOriginCurrent = _orbitOriginTarget; + _rotationDirection = _direction = glm::vec3(0.0f, 0.0f, -1.0f) * object->getRotation(); + _position = _orbitOriginCurrent + _rotationDirection * _orbitRadiusCurrent; +} + +void ControlledOrbitalCamera::update(float delta) { + setOrbitOrigin(_followedObject->getUpperPosition()); + Graphics::OrbitalCamera::update(delta); + // Resolving colisions with static objects + btVector3 from = btVector3(_orbitOriginCurrent.x, _orbitOriginCurrent.y, -_orbitOriginCurrent.z); + btVector3 to = btVector3(_position.x, _position.y, -_position.z); + btCollisionWorld::ClosestRayResultCallback ray = PhysicsMan.raycastStatic(from, to); + if (ray.hasHit()) + snapRadius((ray.m_hitPointWorld - from).length()); +} diff --git a/src/controlledorbitalcamera.h b/src/controlledorbitalcamera.h new file mode 100644 index 00000000..b4b0881b --- /dev/null +++ b/src/controlledorbitalcamera.h @@ -0,0 +1,50 @@ +/* OpenAWE - A reimplementation of Remedys Alan Wake Engine + * + * OpenAWE is the legal property of its developers, whose names + * can be found in the AUTHORS file distributed with this source + * distribution. + * + * OpenAWE is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * OpenAWE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenAWE. If not, see . + */ + +#ifndef OPENAWE_CONTROLLEDORBITALCAMERA_H +#define OPENAWE_CONTROLLEDORBITALCAMERA_H + +#include + +#include "src/events/event.h" + +#include "src/graphics/orbitalcamera.h" + +#include "src/physics/charactercontroller.h" + +class ControlledOrbitalCamera : public Graphics::OrbitalCamera { +public: + ControlledOrbitalCamera(); + + void attachTo(Physics::CharacterControllerPtr object); + + void update(float delta) override; + +private: + void handleRotation(const Events::Event &event); + void handleStates(const Events::Event &event); + + Physics::CharacterControllerPtr _followedObject; +}; + +typedef std::shared_ptr ControlledOrbitalCameraPtr; + + +#endif //OPENAWE_CONTROLLEDORBITALCAMERA_H diff --git a/src/engine.cpp b/src/engine.cpp index b491df8d..07dc95e9 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -23,11 +23,14 @@ #include "src/common/strutil.h" #include "src/common/threadpool.h" +#include "src/physics/charactercontroller.h" + #include "src/video/playerprocess.h" #include "src/graphics/gfxman.h" #include "src/graphics/animationcontroller.h" +#include "src/controlledorbitalcamera.h" #include "src/engine.h" #include "src/task.h" #include "src/utils.h" @@ -149,8 +152,15 @@ void Engine::initEpisode() { if (!task.isActiveOnStartup()) continue; - if (!task.getPlayerCharacter().isNil()) - _playerController.setPlayerCharacter(getEntityByGID(_registry, task.getPlayerCharacter())); + if (!task.getPlayerCharacter().isNil()) { + auto playerEntity = getEntityByGID(_registry, task.getPlayerCharacter()); + ControlledOrbitalCameraPtr cam + = _registry.emplace(playerEntity) + = std::make_shared(); + cam->attachTo(_registry.get(playerEntity)); + _playerController.setPlayerCharacter(playerEntity); + + } spdlog::debug("Firing OnTaskActivate on {} {:x}", gid.type, gid.id); bytecode->run(*_context, "OnTaskActivate", item); diff --git a/src/game.cpp b/src/game.cpp index 652a152b..38ec6582 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -56,7 +56,7 @@ #include "src/sound/soundman.h" #include "src/game.h" -#include "src/controlledfreecamera.h" +#include "src/controlledorbitalcamera.h" #include "src/task.h" #include "src/timerprocess.h" #include "src/transform.h" @@ -275,9 +275,8 @@ void Game::init() { void Game::start() { spdlog::info("Starting AWE..."); - ControlledFreeCamera freeCamera; - - GfxMan.setCamera(freeCamera); + ControlledOrbitalCameraPtr camera = _registry.get(_registry.view().back()); + GfxMan.setCamera(*(camera.get())); Graphics::Text text; text.setText(u"OpenAWE - v0.0.1"); @@ -376,7 +375,7 @@ void Game::start() { _registry.get(lightEntity).setTransform(transform.getTransformation()); } - freeCamera.update(time - lastTime); + camera->update(time - lastTime); PhysicsMan.update(time - lastTime); GfxMan.drawFrame(); diff --git a/src/graphics/camera.h b/src/graphics/camera.h index 1ddd473a..804c3066 100644 --- a/src/graphics/camera.h +++ b/src/graphics/camera.h @@ -54,10 +54,13 @@ class Camera { virtual void update(float time); + float getFOV() { return _fov; }; + protected: glm::vec3 _position; glm::vec3 _direction; glm::vec3 _up; + float _fov = 45.f; }; } // End of namespace Graphics diff --git a/src/graphics/orbitalcamera.cpp b/src/graphics/orbitalcamera.cpp new file mode 100644 index 00000000..33ef791c --- /dev/null +++ b/src/graphics/orbitalcamera.cpp @@ -0,0 +1,119 @@ +/* OpenAWE - A reimplementation of Remedys Alan Wake Engine + * + * OpenAWE is the legal property of its developers, whose names + * can be found in the AUTHORS file distributed with this source + * distribution. + * + * OpenAWE is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * OpenAWE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenAWE. If not, see . + */ + +#include +#include + +#include "src/graphics/orbitalcamera.h" + +namespace Graphics { + + +OrbitalCamera::OrbitalCamera() : + _rotationFactor(10.0), _movementRotation(0.0f), + _rotationDirection(glm::vec3(0.0f, 0.0f, 1.0f)), _rotationAttitude(0.0f) { + _orbitOriginCurrent = _orbitOriginTarget = glm::zero(); + _shoulderCurrent = _shoulderTarget = 1.0f; + _orbitRadiusCurrent = _orbitRadiusTarget = _orbitRadiusBase = 4.0f; +} + +void OrbitalCamera::calcOrbitPosition() { + _rotationDirection = glm::normalize(glm::vec3( + cos(_rotationAttitude.y) * cos(_rotationAttitude.x), + sin(_rotationAttitude.y), + sin(_rotationAttitude.x) * cos(_rotationAttitude.y))); + + glm::vec3 right = glm::cross(_up, _rotationDirection); + + // Add orbit and shoulder + _position = _orbitOriginCurrent + (right * _shoulderCurrent * _shoulderCoef - _rotationDirection) * _orbitRadiusCurrent; +} + +void OrbitalCamera::update(float delta) { + // Lerp values + const float lerpCoef = 1.0f - glm::pow(0.03125f, delta); + _orbitOriginCurrent = glm::mix(_orbitOriginCurrent, _orbitOriginTarget, lerpCoef); + _shoulderCurrent = glm::mix(_shoulderCurrent, _shoulderTarget, lerpCoef); + _orbitRadiusCurrent = glm::mix(_orbitRadiusCurrent, _orbitRadiusTarget, lerpCoef); + const float fovTarget = _state == kCameraAiming ? _fovBase * _fovAimMultiplier : _fovBase; + _fov = glm::mix(_fov, fovTarget, lerpCoef); + + // Get target look direction + // roll (_rotationAttitude.z) is not used so far + _rotationAttitude.x += glm::radians(_movementRotation.x * delta * _rotationFactor); + _rotationAttitude.y -= glm::radians(_movementRotation.y * delta * _rotationFactor); + // limit pitch to -80..80 degree range + constexpr float cameraPitchLimit = M_PI_2 / 9 * 8; + _rotationAttitude.y = glm::clamp(_rotationAttitude.y, -cameraPitchLimit, cameraPitchLimit); + + calcOrbitPosition(); + + glm::vec3 target = + _state == kCameraFocused ? _focusTarget : + _orbitOriginCurrent + _rotationDirection * _orbitRadiusCurrent * 10.0f; + + target = glm::normalize(target - _position); + if (glm::distance(_direction, target) > 1e-3) + _direction = glm::slerp(_direction, target, lerpCoef); + + // Always clear rotation for mouse/gamepad input + _movementRotation = glm::zero(); +} + +void OrbitalCamera::snapRadius(const float &radius) { + _orbitRadiusCurrent = radius; + calcOrbitPosition(); +} + +float OrbitalCamera::getRotationFactor() const { + return _rotationFactor; +} + +void OrbitalCamera::setRotationFactor(float rotationFactor) { + _rotationFactor = rotationFactor; +} + +void OrbitalCamera::focusOn(glm::vec3 target) { + _state = kCameraFocused; + _focusTarget = target; +} + +void OrbitalCamera::unfocus() { + _state = kCameraDefault; +} + +void OrbitalCamera::switchShoulder() { + _shoulderTarget = -_shoulderTarget; +} + +void OrbitalCamera::setOrbitOrigin(const glm::vec3 &newOrigin) { + _orbitOriginTarget = newOrigin; + _orbitOriginTarget.z = -_orbitOriginTarget.z; +} + +void OrbitalCamera::setAiming(bool aiming) { + _state = aiming ? kCameraAiming : kCameraDefault; +} + +glm::vec3 OrbitalCamera::getRotationPlane() { + return glm::normalize(glm::vec3(_rotationDirection.x, 0.0f, -_rotationDirection.z)); +}; + +} // End of namespace Graphics diff --git a/src/graphics/orbitalcamera.h b/src/graphics/orbitalcamera.h new file mode 100644 index 00000000..f5dae0b0 --- /dev/null +++ b/src/graphics/orbitalcamera.h @@ -0,0 +1,77 @@ +/* OpenAWE - A reimplementation of Remedys Alan Wake Engine + * + * OpenAWE is the legal property of its developers, whose names + * can be found in the AUTHORS file distributed with this source + * distribution. + * + * OpenAWE is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * OpenAWE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenAWE. If not, see . + */ + +#ifndef OPENAWE_ORBITALCAMERA_H +#define OPENAWE_ORBITALCAMERA_H + +#include "src/graphics/camera.h" + +namespace Graphics { + +enum OrbitalCameraStates { + kCameraDefault, + kCameraAiming, + kCameraFocused +}; + +class OrbitalCamera : public Camera { +public: + OrbitalCamera(); + + float getRotationFactor() const; + + void setRotationFactor(float rotationFactor); + + void setOrbitOrigin(const glm::vec3 &newOrigin); + + void snapRadius(const float &radius); + + void switchShoulder(); + + void focusOn(glm::vec3 target); + + void unfocus(); + + void setAiming(bool aiming); + + void update(float delta) override; + + glm::vec3 getRotationPlane(); + +protected: + float _rotationFactor; + glm::vec3 _movementRotation; + glm::vec3 _rotationDirection; + glm::vec3 _rotationAttitude; // yaw, pitch and roll + float _orbitRadiusBase, _orbitRadiusCurrent, _orbitRadiusTarget; + glm::vec3 _orbitOriginCurrent, _orbitOriginTarget; + float _shoulderCurrent, _shoulderTarget; + static constexpr float _shoulderCoef = 0.2f; + glm::vec3 _focusTarget; + float _fovBase = _fov, _fovAimMultiplier = 0.5f; + OrbitalCameraStates _state = kCameraDefault; + + void calcOrbitPosition(); + void calcOrbitDirection(const glm::vec3 &to, const float &lerpCoef); +}; + +} // End of namespace Graphics + +#endif //OPENAWE_ORBITALCAMERA_H diff --git a/src/graphics/renderer.cpp b/src/graphics/renderer.cpp index 66e62e78..8a30ff1f 100644 --- a/src/graphics/renderer.cpp +++ b/src/graphics/renderer.cpp @@ -24,7 +24,8 @@ Graphics::Renderer::Renderer() { // Setup initial projection matrix - _projection = glm::perspectiveFov(45.0f, 1920.0f, 1080.0f, 1.0f, 10000.0f); + _fov = 60.0f; + _projection = glm::perspectiveFov(glm::radians(_fov), 1920.0f, 1080.0f, 1.0f, 10000.0f); // Initialize frustrum with projection matrix _frustrum.setProjectionMatrix(_projection); @@ -129,5 +130,11 @@ void Graphics::Renderer::setSky(Graphics::SkyPtr sky) { void Graphics::Renderer::update() { _view = _camera ? (*_camera).get().getLookAt() : glm::identity(); + const float currentFOV = (*_camera).get().getFOV(); + if (_fov != currentFOV) { + _fov = currentFOV; + _projection = glm::perspectiveFov(glm::radians(_fov), 1920.0f, 1080.0f, 1.0f, 10000.0f); + _frustrum.setProjectionMatrix(_projection); + } _frustrum.setViewMatrix(_view); } diff --git a/src/graphics/renderer.h b/src/graphics/renderer.h index a5382703..b0ffbc6b 100644 --- a/src/graphics/renderer.h +++ b/src/graphics/renderer.h @@ -139,6 +139,7 @@ class Renderer { } }; + float _fov; glm::mat4 _view; glm::mat4 _projection; diff --git a/src/physics/charactercontroller.cpp b/src/physics/charactercontroller.cpp index fcd613f5..c6cdc4b5 100644 --- a/src/physics/charactercontroller.cpp +++ b/src/physics/charactercontroller.cpp @@ -68,6 +68,11 @@ glm::vec3 CharacterController::getPosition() { return glm::vec3(origin.x(), origin.y() - _groundOffset, origin.z()); } +glm::vec3 CharacterController::getUpperPosition() { + btVector3 origin = _ghostObject->getWorldTransform().getOrigin(); + return glm::vec3(origin.x(), origin.y(), origin.z()); +} + glm::mat3 CharacterController::getRotation() { btQuaternion rotation = _ghostObject->getWorldTransform().getRotation(); return glm::toMat3(glm::quat(rotation.w(), rotation.x(), rotation.y(), rotation.z())); diff --git a/src/physics/charactercontroller.h b/src/physics/charactercontroller.h index 810e3887..aa8132ed 100644 --- a/src/physics/charactercontroller.h +++ b/src/physics/charactercontroller.h @@ -77,6 +77,14 @@ class CharacterController : public CollisionObject { */ glm::vec3 getPosition(); + /*! + * Get the current position of the character + * including its ground offset. + * + * \return The current character position as vec3 + */ + glm::vec3 getUpperPosition(); + /*! * Get the current rotation of the character * From 924ae8ab4d2fc39a884a6a6c92c771c79e7f3530 Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Tue, 23 Jan 2024 13:15:14 +0300 Subject: [PATCH 4/9] Move near clipping plane closer --- src/graphics/renderer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphics/renderer.cpp b/src/graphics/renderer.cpp index 8a30ff1f..ee63064d 100644 --- a/src/graphics/renderer.cpp +++ b/src/graphics/renderer.cpp @@ -25,7 +25,7 @@ Graphics::Renderer::Renderer() { // Setup initial projection matrix _fov = 60.0f; - _projection = glm::perspectiveFov(glm::radians(_fov), 1920.0f, 1080.0f, 1.0f, 10000.0f); + _projection = glm::perspectiveFov(glm::radians(_fov), 1920.0f, 1080.0f, 0.1f, 10000.0f); // Initialize frustrum with projection matrix _frustrum.setProjectionMatrix(_projection); @@ -133,7 +133,7 @@ void Graphics::Renderer::update() { const float currentFOV = (*_camera).get().getFOV(); if (_fov != currentFOV) { _fov = currentFOV; - _projection = glm::perspectiveFov(glm::radians(_fov), 1920.0f, 1080.0f, 1.0f, 10000.0f); + _projection = glm::perspectiveFov(glm::radians(_fov), 1920.0f, 1080.0f, 0.1f, 10000.0f); _frustrum.setProjectionMatrix(_projection); } _frustrum.setViewMatrix(_view); From a498f6f478c5b3383b545d75e040214190977ffb Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Tue, 23 Jan 2024 13:16:22 +0300 Subject: [PATCH 5/9] Add PhysicsMan::shapeCastStatic method --- src/physics/physicsman.cpp | 7 +++++++ src/physics/physicsman.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/physics/physicsman.cpp b/src/physics/physicsman.cpp index 2c0b6716..03d12f75 100644 --- a/src/physics/physicsman.cpp +++ b/src/physics/physicsman.cpp @@ -90,4 +90,11 @@ btCollisionWorld::ClosestRayResultCallback PhysicsManager::raycastStatic(btVecto return results; } +btCollisionWorld::ClosestConvexResultCallback PhysicsManager::shapeCastStatic(btConvexShape* castShape, btTransform& from, btTransform& to) { + btCollisionWorld::ClosestConvexResultCallback results(from.getOrigin(), to.getOrigin()); + results.m_collisionFilterMask = btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter; + _world->convexSweepTest(castShape, from, to, results); + return results; +} + } diff --git a/src/physics/physicsman.h b/src/physics/physicsman.h index 4fb54934..c3868ebd 100644 --- a/src/physics/physicsman.h +++ b/src/physics/physicsman.h @@ -45,6 +45,7 @@ class PhysicsManager : public Common::Singleton { void remove(btRigidBody *collisionObject); void remove(btActionInterface * actionInterface); btCollisionWorld::ClosestRayResultCallback raycastStatic(btVector3& from, btVector3& to); + btCollisionWorld::ClosestConvexResultCallback shapeCastStatic(btConvexShape* castShape, btTransform& from, btTransform& to); private: bool _debugDraw; From 10ffbc19f4598d59ded869e4b15acc6f289b7dbb Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Tue, 23 Jan 2024 16:10:01 +0300 Subject: [PATCH 6/9] Improve ControlledOrbitalCamera's behaviour - Use sphere to check for camera collisions - Accumulate and smooth user input - Aiming moves camera closer to the character - Shoulder gets longer with the higher pitch - Improve default camera orientation - Adjust defaults to match AWAN's camera better --- src/controlledorbitalcamera.cpp | 48 ++++++++++++++++++-------- src/controlledorbitalcamera.h | 4 +++ src/graphics/orbitalcamera.cpp | 60 ++++++++++++++++++++------------- src/graphics/orbitalcamera.h | 9 ++--- 4 files changed, 80 insertions(+), 41 deletions(-) diff --git a/src/controlledorbitalcamera.cpp b/src/controlledorbitalcamera.cpp index 97f3c5bc..b19fb439 100644 --- a/src/controlledorbitalcamera.cpp +++ b/src/controlledorbitalcamera.cpp @@ -34,7 +34,7 @@ static constexpr uint32_t kSwitchShoulder = Common::crc32("ORBITALCAM_SWITC static constexpr uint32_t kAimWeapon = Common::crc32("ORBITALCAM_AIM_WEAPON"); static constexpr uint32_t kFocusCamera = Common::crc32("ORBITALCAM_FOCUS_CAMERA"); -ControlledOrbitalCamera::ControlledOrbitalCamera() { +ControlledOrbitalCamera::ControlledOrbitalCamera() : _castSphere(0.75f) { Events::EventCallback callbackRotate = [this](auto && PH1) { handleRotation(std::forward(PH1)); }; Events::EventCallback callbackStates = [this](auto && PH1) { handleStates(std::forward(PH1)); }; @@ -63,11 +63,11 @@ ControlledOrbitalCamera::ControlledOrbitalCamera() { void ControlledOrbitalCamera::handleRotation(const Events::Event &event) { const Events::AxisEvent axisEvent = std::get>(event.data); if (event.action == kRotateMouse) { - _movementRotation.x = axisEvent.delta.x; - _movementRotation.y = axisEvent.delta.y; + _movementRotation.x += axisEvent.delta.x; + _movementRotation.y += axisEvent.delta.y; } else if (event.action == kRotateGamepad) { - _movementRotation.x = axisEvent.absolute.x * 10.0f; - _movementRotation.y = axisEvent.absolute.y * 10.0f; + _movementRotation.x += axisEvent.absolute.x * 10.0f; + _movementRotation.y += axisEvent.absolute.y * 10.0f; } } @@ -94,19 +94,39 @@ void ControlledOrbitalCamera::handleStates(const Events::Event &event) { void ControlledOrbitalCamera::attachTo(Physics::CharacterControllerPtr object) { _followedObject = object; - setOrbitOrigin(_followedObject->getUpperPosition()); + glm::vec3 newOrigin = _followedObject->getUpperPosition(); + newOrigin.y += 0.6f; + setOrbitOrigin(newOrigin); _orbitOriginCurrent = _orbitOriginTarget; - _rotationDirection = _direction = glm::vec3(0.0f, 0.0f, -1.0f) * object->getRotation(); - _position = _orbitOriginCurrent + _rotationDirection * _orbitRadiusCurrent; + _rotationAttitude = glm::vec3(-M_PI, 0.0f, 0.0f) * object->getRotation(); + _position = calcOrbitPosition(_orbitRadiusCurrent); + _direction = _rotationDirection; } void ControlledOrbitalCamera::update(float delta) { - setOrbitOrigin(_followedObject->getUpperPosition()); + glm::vec3 newOrigin = _followedObject->getUpperPosition(); + newOrigin.y += 0.6f; + setOrbitOrigin(newOrigin); Graphics::OrbitalCamera::update(delta); // Resolving colisions with static objects - btVector3 from = btVector3(_orbitOriginCurrent.x, _orbitOriginCurrent.y, -_orbitOriginCurrent.z); - btVector3 to = btVector3(_position.x, _position.y, -_position.z); - btCollisionWorld::ClosestRayResultCallback ray = PhysicsMan.raycastStatic(from, to); - if (ray.hasHit()) - snapRadius((ray.m_hitPointWorld - from).length()); + const btMatrix3x3 castIdentity{ + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f, + 0.f, 0.f, 1.f + }; + const glm::vec3 basePoint = calcOrbitPosition(_orbitRadiusTargetFull); + const btVector3 cameraOrigin{_orbitOriginCurrent.x, _orbitOriginCurrent.y, -_orbitOriginCurrent.z}; + btTransform + from{castIdentity, cameraOrigin}, + to{castIdentity, btVector3(basePoint.x, basePoint.y, -basePoint.z)}; + btCollisionWorld::ClosestConvexResultCallback camCast = PhysicsMan.shapeCastStatic(&_castSphere, from, to); + if (camCast.hasHit()) { + // Since hit point can be anywhere on a sphere, we should project hit point onto movement ray + const glm::vec3 hitPoint{camCast.m_hitPointWorld.x(), camCast.m_hitPointWorld.y(), -camCast.m_hitPointWorld.z()}; + const glm::vec3 origVec = _position - _orbitOriginCurrent; + const glm::vec3 hitVec = hitPoint - _orbitOriginCurrent; + // derived from formula: + // glm::length(hitProjection), where hitProjection = glm::dot(hitVec, origVec) / glm::dot(origVec, origVec) * origVec + snapRadius(glm::dot(hitVec, origVec) / glm::length(origVec)); + }; } diff --git a/src/controlledorbitalcamera.h b/src/controlledorbitalcamera.h index b4b0881b..ee119ebf 100644 --- a/src/controlledorbitalcamera.h +++ b/src/controlledorbitalcamera.h @@ -23,6 +23,8 @@ #include +#include + #include "src/events/event.h" #include "src/graphics/orbitalcamera.h" @@ -42,6 +44,8 @@ class ControlledOrbitalCamera : public Graphics::OrbitalCamera { void handleStates(const Events::Event &event); Physics::CharacterControllerPtr _followedObject; + + btSphereShape _castSphere; }; typedef std::shared_ptr ControlledOrbitalCameraPtr; diff --git a/src/graphics/orbitalcamera.cpp b/src/graphics/orbitalcamera.cpp index 33ef791c..42030755 100644 --- a/src/graphics/orbitalcamera.cpp +++ b/src/graphics/orbitalcamera.cpp @@ -18,6 +18,7 @@ * along with OpenAWE. If not, see . */ +#include #include #include @@ -27,59 +28,71 @@ namespace Graphics { OrbitalCamera::OrbitalCamera() : - _rotationFactor(10.0), _movementRotation(0.0f), - _rotationDirection(glm::vec3(0.0f, 0.0f, 1.0f)), _rotationAttitude(0.0f) { + _rotationFactor(2.0), _movementRotation(0.0f), + _rotationDirection(glm::vec3(0.0f, 0.0f, 1.0f)), _rotationAttitude(0.0f), _orbitRadiusBase(2.5f), + _radiusOverride(false) { _orbitOriginCurrent = _orbitOriginTarget = glm::zero(); - _shoulderCurrent = _shoulderTarget = 1.0f; - _orbitRadiusCurrent = _orbitRadiusTarget = _orbitRadiusBase = 4.0f; + _shoulderCurrent = _shoulderTarget = 1.25f; + _orbitRadiusCurrent = _orbitRadiusTarget = _orbitRadiusTargetFull = _orbitRadiusBase; } -void OrbitalCamera::calcOrbitPosition() { +glm::vec3 OrbitalCamera::calcOrbitPosition(float radius) { _rotationDirection = glm::normalize(glm::vec3( cos(_rotationAttitude.y) * cos(_rotationAttitude.x), sin(_rotationAttitude.y), sin(_rotationAttitude.x) * cos(_rotationAttitude.y))); - glm::vec3 right = glm::cross(_up, _rotationDirection); + const glm::vec3 right = glm::cross(_up, _rotationDirection); - // Add orbit and shoulder - _position = _orbitOriginCurrent + (right * _shoulderCurrent * _shoulderCoef - _rotationDirection) * _orbitRadiusCurrent; + constexpr float cameraPitchLimit = M_PI_2 / 3 * 2; + const float shoulderCoef = _shoulderCoef * (1 + std::abs(_rotationAttitude.y / cameraPitchLimit)); + + // Add orbit, shoulder, and orbit lowering + return _orbitOriginCurrent + + (right * _shoulderCurrent * shoulderCoef - _rotationDirection) * radius - + _up * radius / _orbitRadiusBase * 0.75f; } void OrbitalCamera::update(float delta) { // Lerp values - const float lerpCoef = 1.0f - glm::pow(0.03125f, delta); - _orbitOriginCurrent = glm::mix(_orbitOriginCurrent, _orbitOriginTarget, lerpCoef); - _shoulderCurrent = glm::mix(_shoulderCurrent, _shoulderTarget, lerpCoef); - _orbitRadiusCurrent = glm::mix(_orbitRadiusCurrent, _orbitRadiusTarget, lerpCoef); + const float lerpCoefSmooth = 1.0f - glm::pow(0.1f, delta); + const float lerpCoefSnappy = 1.0f - glm::pow(1e-4, delta); + const float lerpCoefExtraSnappy = 1.0f - glm::pow(1e-6, delta); + _shoulderCurrent = glm::mix(_shoulderCurrent, _shoulderTarget, lerpCoefSmooth); + if (!_radiusOverride) { + _orbitRadiusTarget = glm::mix(_orbitRadiusTarget, _orbitRadiusTargetFull, lerpCoefSnappy); + } else { + _radiusOverride = false; + } + _orbitRadiusCurrent = glm::mix(_orbitRadiusCurrent, _orbitRadiusTarget, lerpCoefSnappy); + _orbitOriginCurrent = glm::mix(_orbitOriginCurrent, _orbitOriginTarget, lerpCoefSnappy); const float fovTarget = _state == kCameraAiming ? _fovBase * _fovAimMultiplier : _fovBase; - _fov = glm::mix(_fov, fovTarget, lerpCoef); + _fov = glm::mix(_fov, fovTarget, lerpCoefSnappy); // Get target look direction // roll (_rotationAttitude.z) is not used so far _rotationAttitude.x += glm::radians(_movementRotation.x * delta * _rotationFactor); _rotationAttitude.y -= glm::radians(_movementRotation.y * delta * _rotationFactor); - // limit pitch to -80..80 degree range - constexpr float cameraPitchLimit = M_PI_2 / 9 * 8; + // limit pitch to -60..60 degree range + constexpr float cameraPitchLimit = M_PI_2 / 3 * 2; _rotationAttitude.y = glm::clamp(_rotationAttitude.y, -cameraPitchLimit, cameraPitchLimit); - calcOrbitPosition(); + _position = calcOrbitPosition(_orbitRadiusCurrent); glm::vec3 target = - _state == kCameraFocused ? _focusTarget : - _orbitOriginCurrent + _rotationDirection * _orbitRadiusCurrent * 10.0f; + _state == kCameraFocused ? _focusTarget : _rotationDirection * 25.0f + _position; target = glm::normalize(target - _position); if (glm::distance(_direction, target) > 1e-3) - _direction = glm::slerp(_direction, target, lerpCoef); + _direction = glm::slerp(_direction, target, lerpCoefExtraSnappy); - // Always clear rotation for mouse/gamepad input - _movementRotation = glm::zero(); + // Always clear rotation for mouse/gamepad input in some way + _movementRotation = glm::mix(_movementRotation, glm::zero(), lerpCoefExtraSnappy); } void OrbitalCamera::snapRadius(const float &radius) { - _orbitRadiusCurrent = radius; - calcOrbitPosition(); + _orbitRadiusTarget = std::clamp(radius, 0.1f, _orbitRadiusTargetFull); + _radiusOverride = true; } float OrbitalCamera::getRotationFactor() const { @@ -110,6 +123,7 @@ void OrbitalCamera::setOrbitOrigin(const glm::vec3 &newOrigin) { void OrbitalCamera::setAiming(bool aiming) { _state = aiming ? kCameraAiming : kCameraDefault; + _orbitRadiusTargetFull = aiming ? _orbitRadiusBase / 2 : _orbitRadiusBase; } glm::vec3 OrbitalCamera::getRotationPlane() { diff --git a/src/graphics/orbitalcamera.h b/src/graphics/orbitalcamera.h index f5dae0b0..40706ca3 100644 --- a/src/graphics/orbitalcamera.h +++ b/src/graphics/orbitalcamera.h @@ -60,16 +60,17 @@ class OrbitalCamera : public Camera { glm::vec3 _movementRotation; glm::vec3 _rotationDirection; glm::vec3 _rotationAttitude; // yaw, pitch and roll - float _orbitRadiusBase, _orbitRadiusCurrent, _orbitRadiusTarget; + const float _orbitRadiusBase; + float _orbitRadiusCurrent, _orbitRadiusTarget, _orbitRadiusTargetFull; glm::vec3 _orbitOriginCurrent, _orbitOriginTarget; float _shoulderCurrent, _shoulderTarget; static constexpr float _shoulderCoef = 0.2f; glm::vec3 _focusTarget; - float _fovBase = _fov, _fovAimMultiplier = 0.5f; + float _fovBase = _fov, _fovAimMultiplier = 0.75f; OrbitalCameraStates _state = kCameraDefault; + bool _radiusOverride; - void calcOrbitPosition(); - void calcOrbitDirection(const glm::vec3 &to, const float &lerpCoef); + glm::vec3 calcOrbitPosition(float radius); }; } // End of namespace Graphics From 4e64b07fe1aa0a3b1d5e624f8bf98accb69b0911 Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Fri, 22 Mar 2024 18:07:27 +0300 Subject: [PATCH 7/9] Change return type of casting methods to use glm values --- src/controlledorbitalcamera.cpp | 44 ++++++++++-------------- src/physics/physicsman.cpp | 61 ++++++++++++++++++++++++++++----- src/physics/physicsman.h | 11 ++++-- 3 files changed, 81 insertions(+), 35 deletions(-) diff --git a/src/controlledorbitalcamera.cpp b/src/controlledorbitalcamera.cpp index b19fb439..b93858e3 100644 --- a/src/controlledorbitalcamera.cpp +++ b/src/controlledorbitalcamera.cpp @@ -27,12 +27,12 @@ #include "src/controlledorbitalcamera.h" -static constexpr uint32_t kRotateMouse = Common::crc32("ORBITALCAM_ROTATE_MOUSE"); -static constexpr uint32_t kRotateGamepad = Common::crc32("ORBITALCAM_ROTATE_GAMEPAD"); +static constexpr uint32_t kRotateMouse = Common::crc32("ORBITALCAM_ROTATE_MOUSE"); +static constexpr uint32_t kRotateGamepad = Common::crc32("ORBITALCAM_ROTATE_GAMEPAD"); -static constexpr uint32_t kSwitchShoulder = Common::crc32("ORBITALCAM_SWITCH_SHOULDER"); -static constexpr uint32_t kAimWeapon = Common::crc32("ORBITALCAM_AIM_WEAPON"); -static constexpr uint32_t kFocusCamera = Common::crc32("ORBITALCAM_FOCUS_CAMERA"); +static constexpr uint32_t kSwitchShoulder = Common::crc32("ORBITALCAM_SWITCH_SHOULDER"); +static constexpr uint32_t kAimWeapon = Common::crc32("ORBITALCAM_AIM_WEAPON"); +static constexpr uint32_t kFocusCamera = Common::crc32("ORBITALCAM_FOCUS_CAMERA"); ControlledOrbitalCamera::ControlledOrbitalCamera() : _castSphere(0.75f) { Events::EventCallback callbackRotate = [this](auto && PH1) { handleRotation(std::forward(PH1)); }; @@ -109,24 +109,18 @@ void ControlledOrbitalCamera::update(float delta) { setOrbitOrigin(newOrigin); Graphics::OrbitalCamera::update(delta); // Resolving colisions with static objects - const btMatrix3x3 castIdentity{ - 1.f, 0.f, 0.f, - 0.f, 1.f, 0.f, - 0.f, 0.f, 1.f + const glm::vec4 + mat1(1.f, 0.f, 0.f, 0.f), + mat2(0.f, 1.f, 0.f, 0.f), + mat3(0.f, 0.f, 1.f, 0.f); + const glm::vec3 basePoint{calcOrbitPosition(_orbitRadiusTargetFull)}; + const glm::vec4 cameraOrigin{_orbitOriginCurrent.x, _orbitOriginCurrent.y, -_orbitOriginCurrent.z, 1.f}; + glm::mat4 + from{mat1, mat2, mat3, cameraOrigin}, + to{mat1, mat2, mat3, glm::vec4(basePoint.x, basePoint.y, -basePoint.z, 1.f)}; + Physics::CastResult camCast = PhysicsMan.shapeCastStatic(&_castSphere, from, to); + if (camCast.hasHit) { + float rad = glm::length(camCast.rayHitPoint - glm::vec3(_orbitOriginCurrent.x, _orbitOriginCurrent.y, -_orbitOriginCurrent.z)); + snapRadius(rad); }; - const glm::vec3 basePoint = calcOrbitPosition(_orbitRadiusTargetFull); - const btVector3 cameraOrigin{_orbitOriginCurrent.x, _orbitOriginCurrent.y, -_orbitOriginCurrent.z}; - btTransform - from{castIdentity, cameraOrigin}, - to{castIdentity, btVector3(basePoint.x, basePoint.y, -basePoint.z)}; - btCollisionWorld::ClosestConvexResultCallback camCast = PhysicsMan.shapeCastStatic(&_castSphere, from, to); - if (camCast.hasHit()) { - // Since hit point can be anywhere on a sphere, we should project hit point onto movement ray - const glm::vec3 hitPoint{camCast.m_hitPointWorld.x(), camCast.m_hitPointWorld.y(), -camCast.m_hitPointWorld.z()}; - const glm::vec3 origVec = _position - _orbitOriginCurrent; - const glm::vec3 hitVec = hitPoint - _orbitOriginCurrent; - // derived from formula: - // glm::length(hitProjection), where hitProjection = glm::dot(hitVec, origVec) / glm::dot(origVec, origVec) * origVec - snapRadius(glm::dot(hitVec, origVec) / glm::length(origVec)); - }; -} +} \ No newline at end of file diff --git a/src/physics/physicsman.cpp b/src/physics/physicsman.cpp index 03d12f75..48a284e0 100644 --- a/src/physics/physicsman.cpp +++ b/src/physics/physicsman.cpp @@ -17,6 +17,11 @@ * You should have received a copy of the GNU General Public License * along with OpenAWE. If not, see . */ +#define GLM_ENABLE_EXPERIMANTAL +#include + +#include "LinearMath/btQuaternion.h" +#include "LinearMath/btVector3.h" #include "physicsman.h" #include "src/physics/debugdraw.h" @@ -83,18 +88,58 @@ void PhysicsManager::remove(btActionInterface *actionInterface) { _world->removeAction(actionInterface); } -btCollisionWorld::ClosestRayResultCallback PhysicsManager::raycastStatic(btVector3& from, btVector3& to) { - btCollisionWorld::ClosestRayResultCallback results(from, to); +const CastResult PhysicsManager::raycastStatic(glm::vec3& from, glm::vec3& to) { + btVector3 bFrom{from.x, from.y, from.z}, bTo{to.x, to.y, to.z}; + btCollisionWorld::ClosestRayResultCallback results(bFrom, bTo); results.m_collisionFilterMask = btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter; - _world->rayTest(from, to, results); - return results; + + _world->rayTest(bFrom, bTo, results); + if (results.hasHit()) { + glm::vec3 rayHitPoint{ + results.m_hitPointWorld.x(), + results.m_hitPointWorld.y(), + results.m_hitPointWorld.z() + }; + return CastResult{ + true, + rayHitPoint, + rayHitPoint + }; + }; + + return CastResult{ + false, + glm::zero(), + glm::zero() + }; } -btCollisionWorld::ClosestConvexResultCallback PhysicsManager::shapeCastStatic(btConvexShape* castShape, btTransform& from, btTransform& to) { - btCollisionWorld::ClosestConvexResultCallback results(from.getOrigin(), to.getOrigin()); +const CastResult PhysicsManager::shapeCastStatic(btConvexShape* castShape, glm::mat4& from, glm::mat4& to) { + // To make btTransform, we'll need to get orientation quaternion and translation vector + // glm::decompose API returns a lot of params, so we'll try to ignore some + glm::vec3 null3; + glm::vec4 null4; + glm::vec3 fromTranslation, toTranslation; + glm::quat fromRotation, toRotation; + glm::decompose(from, null3, fromRotation, fromTranslation, null3, null4); + glm::decompose(to, null3, toRotation, toTranslation, null3, null4); + const btVector3 bFrom{fromTranslation.x, fromTranslation.y, fromTranslation.z}, bTo{toTranslation.x, toTranslation.y, toTranslation.z}; + const btQuaternion bFromQuat{fromRotation.x, fromRotation.y, fromRotation.z, fromRotation.w}, bToQuat{toRotation.x, toRotation.y, toRotation.z, toRotation.w}; + const btTransform btFrom{bFromQuat, bFrom}, btTo{bToQuat, bTo}; + btCollisionWorld::ClosestConvexResultCallback results(bFrom, bTo); results.m_collisionFilterMask = btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter; - _world->convexSweepTest(castShape, from, to, results); - return results; + + _world->convexSweepTest(castShape, btFrom, btTo, results); + // Since hit point can be anywhere on the shape, we should project hit point onto movement ray + const glm::vec3 hitPoint{results.m_hitPointWorld.x(), results.m_hitPointWorld.y(), results.m_hitPointWorld.z()}; + const glm::vec3 origVec = toTranslation - fromTranslation; + const glm::vec3 hitVec = hitPoint - fromTranslation; + const glm::vec3 projectedHit = glm::dot(hitVec, origVec) / glm::dot(origVec, origVec) * origVec; + return CastResult { + results.hasHit(), + projectedHit + fromTranslation, + hitPoint + }; } } diff --git a/src/physics/physicsman.h b/src/physics/physicsman.h index c3868ebd..e4fa6140 100644 --- a/src/physics/physicsman.h +++ b/src/physics/physicsman.h @@ -23,12 +23,19 @@ #include +#include #include #include "src/common/singleton.h" namespace Physics { +struct CastResult { + const bool hasHit; + const glm::vec3 rayHitPoint; + const glm::vec3 exactHitPoint; +}; + class PhysicsManager : public Common::Singleton { public: PhysicsManager(); @@ -44,8 +51,8 @@ class PhysicsManager : public Common::Singleton { void remove(btCollisionObject *collisionObject); void remove(btRigidBody *collisionObject); void remove(btActionInterface * actionInterface); - btCollisionWorld::ClosestRayResultCallback raycastStatic(btVector3& from, btVector3& to); - btCollisionWorld::ClosestConvexResultCallback shapeCastStatic(btConvexShape* castShape, btTransform& from, btTransform& to); + const CastResult raycastStatic(glm::vec3& from, glm::vec3& to); + const CastResult shapeCastStatic(btConvexShape* castShape, glm::mat4& from, glm::mat4& to); private: bool _debugDraw; From 1bd9208433b84d1ecde3bf4711a3f96565801ca5 Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Fri, 22 Mar 2024 18:09:01 +0300 Subject: [PATCH 8/9] Add comments for OrbitalCamera class --- src/controlledorbitalcamera.cpp | 2 +- src/graphics/orbitalcamera.cpp | 51 ++++++++++++++++++-------- src/graphics/orbitalcamera.h | 63 +++++++++++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/controlledorbitalcamera.cpp b/src/controlledorbitalcamera.cpp index b93858e3..d94cd3ce 100644 --- a/src/controlledorbitalcamera.cpp +++ b/src/controlledorbitalcamera.cpp @@ -98,7 +98,7 @@ void ControlledOrbitalCamera::attachTo(Physics::CharacterControllerPtr object) { newOrigin.y += 0.6f; setOrbitOrigin(newOrigin); _orbitOriginCurrent = _orbitOriginTarget; - _rotationAttitude = glm::vec3(-M_PI, 0.0f, 0.0f) * object->getRotation(); + _rotationOrientation = glm::vec3(-M_PI, 0.0f, 0.0f) * object->getRotation(); _position = calcOrbitPosition(_orbitRadiusCurrent); _direction = _rotationDirection; } diff --git a/src/graphics/orbitalcamera.cpp b/src/graphics/orbitalcamera.cpp index 42030755..9d2fcbc5 100644 --- a/src/graphics/orbitalcamera.cpp +++ b/src/graphics/orbitalcamera.cpp @@ -29,7 +29,7 @@ namespace Graphics { OrbitalCamera::OrbitalCamera() : _rotationFactor(2.0), _movementRotation(0.0f), - _rotationDirection(glm::vec3(0.0f, 0.0f, 1.0f)), _rotationAttitude(0.0f), _orbitRadiusBase(2.5f), + _rotationDirection(glm::vec3(0.0f, 0.0f, 1.0f)), _rotationOrientation(0.0f), _orbitRadiusBase(2.5f), _radiusOverride(false) { _orbitOriginCurrent = _orbitOriginTarget = glm::zero(); _shoulderCurrent = _shoulderTarget = 1.25f; @@ -37,56 +37,79 @@ OrbitalCamera::OrbitalCamera() : } glm::vec3 OrbitalCamera::calcOrbitPosition(float radius) { + // Convert camera direction from pitch and yaw + // to a 3D vector _rotationDirection = glm::normalize(glm::vec3( - cos(_rotationAttitude.y) * cos(_rotationAttitude.x), - sin(_rotationAttitude.y), - sin(_rotationAttitude.x) * cos(_rotationAttitude.y))); + cos(_rotationOrientation.y) * cos(_rotationOrientation.x), + sin(_rotationOrientation.y), + sin(_rotationOrientation.x) * cos(_rotationOrientation.y))); const glm::vec3 right = glm::cross(_up, _rotationDirection); constexpr float cameraPitchLimit = M_PI_2 / 3 * 2; - const float shoulderCoef = _shoulderCoef * (1 + std::abs(_rotationAttitude.y / cameraPitchLimit)); - - // Add orbit, shoulder, and orbit lowering + /* Shoulder offset gets calculated relative to camera's yaw, + so with higher upper angle shoulder would get wider, + keeping the position between player's character and the center of the screen + relatively consistent. */ + const float shoulderCoef = _shoulderCoef * (1 + std::abs(_rotationOrientation.y / cameraPitchLimit)); + + /* Camera's position consists of 4 main components: + 1. Smoothed out followed object's position; + 2. Orbit radius opposite to view direction; + 3. Shoulder offset parallel to view direction; + 4. Extra rise in elevation for cases when camera + gets close to player's character to mimic + original game's behavior. */ return _orbitOriginCurrent + (right * _shoulderCurrent * shoulderCoef - _rotationDirection) * radius - _up * radius / _orbitRadiusBase * 0.75f; } void OrbitalCamera::update(float delta) { - // Lerp values + // Define multiple frame-independent lerping coefficients + // to smooth out various camera movements const float lerpCoefSmooth = 1.0f - glm::pow(0.1f, delta); const float lerpCoefSnappy = 1.0f - glm::pow(1e-4, delta); const float lerpCoefExtraSnappy = 1.0f - glm::pow(1e-6, delta); + // Original games would change shoulder offset rather slowly, + // so we use a smaller coefficient _shoulderCurrent = glm::mix(_shoulderCurrent, _shoulderTarget, lerpCoefSmooth); if (!_radiusOverride) { + // When overriding radius, _orbitRadiusTarget gets smaller, + // so we smooth it back to full when not overriding _orbitRadiusTarget = glm::mix(_orbitRadiusTarget, _orbitRadiusTargetFull, lerpCoefSnappy); } else { _radiusOverride = false; } _orbitRadiusCurrent = glm::mix(_orbitRadiusCurrent, _orbitRadiusTarget, lerpCoefSnappy); _orbitOriginCurrent = glm::mix(_orbitOriginCurrent, _orbitOriginTarget, lerpCoefSnappy); + // Smooth out FOV changes during and after aiming const float fovTarget = _state == kCameraAiming ? _fovBase * _fovAimMultiplier : _fovBase; _fov = glm::mix(_fov, fovTarget, lerpCoefSnappy); - // Get target look direction + // Get target look direction as pitch and yaw values // roll (_rotationAttitude.z) is not used so far - _rotationAttitude.x += glm::radians(_movementRotation.x * delta * _rotationFactor); - _rotationAttitude.y -= glm::radians(_movementRotation.y * delta * _rotationFactor); - // limit pitch to -60..60 degree range + _rotationOrientation.x += glm::radians(_movementRotation.x * delta * _rotationFactor); + _rotationOrientation.y -= glm::radians(_movementRotation.y * delta * _rotationFactor); + // limit pitch to -60..60 degree range similar to original games constexpr float cameraPitchLimit = M_PI_2 / 3 * 2; - _rotationAttitude.y = glm::clamp(_rotationAttitude.y, -cameraPitchLimit, cameraPitchLimit); + _rotationOrientation.y = glm::clamp(_rotationOrientation.y, -cameraPitchLimit, cameraPitchLimit); _position = calcOrbitPosition(_orbitRadiusCurrent); + // When focused, our look target is overriden, but usually we look in front of the character glm::vec3 target = _state == kCameraFocused ? _focusTarget : _rotationDirection * 25.0f + _position; target = glm::normalize(target - _position); + // Here, we use spherical interpolation to smoothly rotate + // our camera towards desired direction if (glm::distance(_direction, target) > 1e-3) _direction = glm::slerp(_direction, target, lerpCoefExtraSnappy); - // Always clear rotation for mouse/gamepad input in some way + // Always clear rotation for mouse/gamepad input in some way. + // We use glm::mix to leave a bit of inertia from inputs, + // making camera movement way smoother to the eye _movementRotation = glm::mix(_movementRotation, glm::zero(), lerpCoefExtraSnappy); } diff --git a/src/graphics/orbitalcamera.h b/src/graphics/orbitalcamera.h index 40706ca3..353232b5 100644 --- a/src/graphics/orbitalcamera.h +++ b/src/graphics/orbitalcamera.h @@ -25,12 +25,19 @@ namespace Graphics { -enum OrbitalCameraStates { +enum OrbitalCameraState { kCameraDefault, kCameraAiming, kCameraFocused }; +/*! + * \brief Class containing logic for the orbital camera + * + * This class expands Camera logic to handle rotation + * around a defined point in space that is aimed to + * be used for the player controlled character + */ class OrbitalCamera : public Camera { public: OrbitalCamera(); @@ -39,37 +46,87 @@ class OrbitalCamera : public Camera { void setRotationFactor(float rotationFactor); + /*! + * Sets a new origin point to rotate around. + * @param newOrigin Point position as a 3D vector + */ void setOrbitOrigin(const glm::vec3 &newOrigin); + /*! + * Temporarily (for this frame) + * lower rotation orbit radius within + * (0.1 .. 1) * (default radius) range + * @param radius Radius value to lower to + */ void snapRadius(const float &radius); + /*! + * Make the camera look from another + * shoulder by flipping the shoulder's sign. + */ void switchShoulder(); + /*! + * Make the camera look at a specific point + * on a scene instead of in front of the character. + * @param target Look target position as a 3D vector + */ void focusOn(glm::vec3 target); + /*! + * Remove focus from a specific point on a scene + * and make the camera look in front of the character. + */ void unfocus(); + /*! + * Start or stop aiming mode that changes the FOV + * as well as the camera's orbit radius + * @param aiming Whether to aim or stop aiming. + */ void setAiming(bool aiming); void update(float delta) override; + /*! + * Get camera's look direction projected onto the XZ plane. + * Useful to determine where should the player character face. + */ glm::vec3 getRotationPlane(); protected: float _rotationFactor; glm::vec3 _movementRotation; glm::vec3 _rotationDirection; - glm::vec3 _rotationAttitude; // yaw, pitch and roll + glm::vec3 _rotationOrientation; // yaw, pitch and roll + /* Orbit is important and complicated. + We have the base radius as the default, + Current full radius for situations when radius is purposefully shortened + (i.e. aiming), current radius when radius is temporarily shortened + (i.e. when terrain obstructs the view) and current radius to smooth from. */ const float _orbitRadiusBase; float _orbitRadiusCurrent, _orbitRadiusTarget, _orbitRadiusTargetFull; + // We smooth followed object's position to create a bit of + // pleasant camera inertia while moving glm::vec3 _orbitOriginCurrent, _orbitOriginTarget; + /* Shoulder coefficient currently depends on orbit radius to mimic + original game's behavior when camera's orbit radius is shortened + due to scenery obstructing view */ float _shoulderCurrent, _shoulderTarget; static constexpr float _shoulderCoef = 0.2f; glm::vec3 _focusTarget; + // Since FOV while aiming depends on a weapon, we add in a FOV multiplier float _fovBase = _fov, _fovAimMultiplier = 0.75f; - OrbitalCameraStates _state = kCameraDefault; + OrbitalCameraState _state = kCameraDefault; + // _radiusOverride bool is meant for situations when + // orbit radius should be temporarily shorter (like during terrain collision) bool _radiusOverride; + /*! + * Calculate current camera's position based off + * its current radius and shoulder. + * @param radius Radius to calculate the position from + */ glm::vec3 calcOrbitPosition(float radius); }; From 27e216763a94d5a4a3f73ab5414026a70e5f0d80 Mon Sep 17 00:00:00 2001 From: Maaxxaam Date: Fri, 22 Mar 2024 18:11:16 +0300 Subject: [PATCH 9/9] Add sensitivity fields for ControlledOrbitalCamera --- src/controlledorbitalcamera.cpp | 8 ++++---- src/controlledorbitalcamera.h | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/controlledorbitalcamera.cpp b/src/controlledorbitalcamera.cpp index d94cd3ce..6daf2343 100644 --- a/src/controlledorbitalcamera.cpp +++ b/src/controlledorbitalcamera.cpp @@ -63,11 +63,11 @@ ControlledOrbitalCamera::ControlledOrbitalCamera() : _castSphere(0.75f) { void ControlledOrbitalCamera::handleRotation(const Events::Event &event) { const Events::AxisEvent axisEvent = std::get>(event.data); if (event.action == kRotateMouse) { - _movementRotation.x += axisEvent.delta.x; - _movementRotation.y += axisEvent.delta.y; + _movementRotation.x += axisEvent.delta.x * _mouseSensitivity; + _movementRotation.y += axisEvent.delta.y * _mouseSensitivity; } else if (event.action == kRotateGamepad) { - _movementRotation.x += axisEvent.absolute.x * 10.0f; - _movementRotation.y += axisEvent.absolute.y * 10.0f; + _movementRotation.x += axisEvent.absolute.x * _gamepadSensitivity; + _movementRotation.y += axisEvent.absolute.y * _gamepadSensitivity; } } diff --git a/src/controlledorbitalcamera.h b/src/controlledorbitalcamera.h index ee119ebf..d46af33b 100644 --- a/src/controlledorbitalcamera.h +++ b/src/controlledorbitalcamera.h @@ -43,6 +43,8 @@ class ControlledOrbitalCamera : public Graphics::OrbitalCamera { void handleRotation(const Events::Event &event); void handleStates(const Events::Event &event); + float _mouseSensitivity = 1.0f, _gamepadSensitivity = 10.0f; + Physics::CharacterControllerPtr _followedObject; btSphereShape _castSphere;