diff --git a/src/controlledorbitalcamera.cpp b/src/controlledorbitalcamera.cpp new file mode 100644 index 0000000..6daf234 --- /dev/null +++ b/src/controlledorbitalcamera.cpp @@ -0,0 +1,126 @@ +/* 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() : _castSphere(0.75f) { + 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 * _mouseSensitivity; + _movementRotation.y += axisEvent.delta.y * _mouseSensitivity; + } else if (event.action == kRotateGamepad) { + _movementRotation.x += axisEvent.absolute.x * _gamepadSensitivity; + _movementRotation.y += axisEvent.absolute.y * _gamepadSensitivity; + } +} + +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; + glm::vec3 newOrigin = _followedObject->getUpperPosition(); + newOrigin.y += 0.6f; + setOrbitOrigin(newOrigin); + _orbitOriginCurrent = _orbitOriginTarget; + _rotationOrientation = glm::vec3(-M_PI, 0.0f, 0.0f) * object->getRotation(); + _position = calcOrbitPosition(_orbitRadiusCurrent); + _direction = _rotationDirection; +} + +void ControlledOrbitalCamera::update(float delta) { + glm::vec3 newOrigin = _followedObject->getUpperPosition(); + newOrigin.y += 0.6f; + setOrbitOrigin(newOrigin); + Graphics::OrbitalCamera::update(delta); + // Resolving colisions with static objects + 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); + }; +} \ No newline at end of file diff --git a/src/controlledorbitalcamera.h b/src/controlledorbitalcamera.h new file mode 100644 index 0000000..d46af33 --- /dev/null +++ b/src/controlledorbitalcamera.h @@ -0,0 +1,56 @@ +/* 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 + +#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); + + float _mouseSensitivity = 1.0f, _gamepadSensitivity = 10.0f; + + Physics::CharacterControllerPtr _followedObject; + + btSphereShape _castSphere; +}; + +typedef std::shared_ptr ControlledOrbitalCameraPtr; + + +#endif //OPENAWE_CONTROLLEDORBITALCAMERA_H diff --git a/src/engine.cpp b/src/engine.cpp index b491df8..07dc95e 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 652a152..38ec658 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 1ddd473..804c306 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/freecamera.cpp b/src/graphics/freecamera.cpp index 8c2fbf3..7f03d23 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); diff --git a/src/graphics/orbitalcamera.cpp b/src/graphics/orbitalcamera.cpp new file mode 100644 index 0000000..9d2fcbc --- /dev/null +++ b/src/graphics/orbitalcamera.cpp @@ -0,0 +1,156 @@ +/* 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 + +#include "src/graphics/orbitalcamera.h" + +namespace Graphics { + + +OrbitalCamera::OrbitalCamera() : + _rotationFactor(2.0), _movementRotation(0.0f), + _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; + _orbitRadiusCurrent = _orbitRadiusTarget = _orbitRadiusTargetFull = _orbitRadiusBase; +} + +glm::vec3 OrbitalCamera::calcOrbitPosition(float radius) { + // Convert camera direction from pitch and yaw + // to a 3D vector + _rotationDirection = glm::normalize(glm::vec3( + 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; + /* 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) { + // 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 as pitch and yaw values + // roll (_rotationAttitude.z) is not used so far + _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; + _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. + // 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); +} + +void OrbitalCamera::snapRadius(const float &radius) { + _orbitRadiusTarget = std::clamp(radius, 0.1f, _orbitRadiusTargetFull); + _radiusOverride = true; +} + +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; + _orbitRadiusTargetFull = aiming ? _orbitRadiusBase / 2 : _orbitRadiusBase; +} + +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 0000000..353232b --- /dev/null +++ b/src/graphics/orbitalcamera.h @@ -0,0 +1,135 @@ +/* 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 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(); + + float getRotationFactor() const; + + 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 _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; + 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); +}; + +} // End of namespace Graphics + +#endif //OPENAWE_ORBITALCAMERA_H diff --git a/src/graphics/renderer.cpp b/src/graphics/renderer.cpp index 66e62e7..ee63064 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, 0.1f, 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, 0.1f, 10000.0f); + _frustrum.setProjectionMatrix(_projection); + } _frustrum.setViewMatrix(_view); } diff --git a/src/graphics/renderer.h b/src/graphics/renderer.h index a538270..b0ffbc6 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 fcd613f..c6cdc4b 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 810e388..aa8132e 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 * diff --git a/src/physics/physicsman.cpp b/src/physics/physicsman.cpp index 03d0025..48a284e 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,4 +88,58 @@ void PhysicsManager::remove(btActionInterface *actionInterface) { _world->removeAction(actionInterface); } +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(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() + }; +} + +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, 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 771c573..e4fa614 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,6 +51,8 @@ class PhysicsManager : public Common::Singleton { void remove(btCollisionObject *collisionObject); void remove(btRigidBody *collisionObject); void remove(btActionInterface * actionInterface); + const CastResult raycastStatic(glm::vec3& from, glm::vec3& to); + const CastResult shapeCastStatic(btConvexShape* castShape, glm::mat4& from, glm::mat4& to); private: bool _debugDraw;