diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index f86f24310a..625758d640 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -64,6 +64,7 @@ namespace MWRender { class Animation; class Camera; + class RenderingManager; } namespace MWMechanics @@ -664,6 +665,8 @@ namespace MWBase virtual std::vector getAll(const std::string& id) = 0; virtual Misc::Rng::Generator& getPrng() = 0; + + virtual MWRender::RenderingManager* getRenderingManager() = 0; }; } diff --git a/apps/openmw/mwlua/camerabindings.cpp b/apps/openmw/mwlua/camerabindings.cpp index abbd806cef..4e865374cf 100644 --- a/apps/openmw/mwlua/camerabindings.cpp +++ b/apps/openmw/mwlua/camerabindings.cpp @@ -1,6 +1,10 @@ #include "luabindings.hpp" +#include +#include + #include "../mwrender/camera.hpp" +#include "../mwrender/renderingmanager.hpp" namespace MWLua { @@ -10,6 +14,7 @@ namespace MWLua sol::table initCameraPackage(const Context& context) { MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera(); + MWRender::RenderingManager* renderingManager = MWBase::Environment::get().getWorld()->getRenderingManager(); sol::table api(context.mLua->sol(), sol::create); api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with( @@ -77,6 +82,31 @@ namespace MWLua api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); }; api["instantTransition"] = [camera]() { camera->instantTransition(); }; + api["getCollisionType"] = [camera]() { return camera->getCollisionType(); }; + api["setCollisionType"] = [camera](int collisionType) { camera->setCollisionType(collisionType); }; + + api["getBaseFieldOfView"] = []() + { + return osg::DegreesToRadians(std::clamp(Settings::Manager::getFloat("field of view", "Camera"), 1.f, 179.f)); + }; + api["getFieldOfView"] = [renderingManager]() { return osg::DegreesToRadians(renderingManager->getFieldOfView()); }; + api["setFieldOfView"] = [renderingManager](float v) { renderingManager->setFieldOfView(osg::RadiansToDegrees(v)); }; + + api["getViewTransform"] = [camera]() { return LuaUtil::TransformM{camera->getViewMatrix()}; }; + + api["viewportToWorldVector"] = [camera, renderingManager](osg::Vec2f pos) -> osg::Vec3f + { + double width = Settings::Manager::getInt("resolution x", "Video"); + double height = Settings::Manager::getInt("resolution y", "Video"); + double aspect = (height == 0.0) ? 1.0 : width / height; + double fovTan = std::tan(osg::DegreesToRadians(renderingManager->getFieldOfView()) / 2); + osg::Matrixf invertedViewMatrix; + invertedViewMatrix.invert(camera->getViewMatrix()); + float x = (pos.x() * 2 - 1) * aspect * fovTan; + float y = (1 - pos.y() * 2) * fovTan; + return invertedViewMatrix.preMult(osg::Vec3f(x, y, -1)) - camera->getPosition(); + }; + return LuaUtil::makeReadOnly(api); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 9f35866f89..b47e1f2dec 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -41,7 +41,7 @@ namespace MWLua { auto* lua = context.mLua; sol::table api(lua->sol(), sol::create); - api["API_REVISION"] = 19; + api["API_REVISION"] = 20; api["quit"] = [lua]() { Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 5ca102bc39..7e83a08493 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -53,6 +53,7 @@ namespace MWRender Camera::Camera (osg::Camera* camera) : mHeightScale(1.f), + mCollisionType(MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor), mCamera(camera), mAnimation(nullptr), mFirstPersonView(true), @@ -127,6 +128,7 @@ namespace MWRender pos = calculateFirstPersonPosition(recalculatedTrackedPosition); } cam->setViewMatrixAsLookAt(pos, pos + forward, up); + mViewMatrix = cam->getViewMatrix(); } void Camera::update(float duration, bool paused) @@ -174,7 +176,6 @@ namespace MWRender constexpr float focalObstacleLimit = 10.f; const auto* rayCasting = MWBase::Environment::get().getWorld()->getRayCasting(); - constexpr int collisionType = (MWPhysics::CollisionType::CollisionType_Default & ~MWPhysics::CollisionType::CollisionType_Actor); // Adjust focal point to prevent clipping. osg::Vec3d focalOffset = getFocalPointOffset(); @@ -184,7 +185,7 @@ namespace MWRender float offsetLen = focalOffset.length(); if (offsetLen > 0) { - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, collisionType); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal - focalOffset, focal, focalObstacleLimit, mCollisionType); if (result.mHit) { double adjustmentCoef = -(result.mHitPos + result.mHitNormal * focalObstacleLimit - focal).length() / offsetLen; @@ -196,7 +197,7 @@ namespace MWRender mCameraDistance = mPreferredCameraDistance; osg::Quat orient = osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1,0,0)) * osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0,0,1)); osg::Vec3d offset = orient * osg::Vec3d(0.f, -mCameraDistance, 0.f); - MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, collisionType); + MWPhysics::RayCastingResult result = rayCasting->castSphere(focal, focal + offset, cameraObstacleLimit, mCollisionType); if (result.mHit) { mCameraDistance = (result.mHitPos + result.mHitNormal * cameraObstacleLimit - focal).length(); @@ -211,7 +212,7 @@ namespace MWRender if (mMode == newMode) return; Mode oldMode = mMode; - if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && !mAnimation->upperBodyReady()) + if (!force && (newMode == Mode::FirstPerson || oldMode == Mode::FirstPerson) && mAnimation && !mAnimation->upperBodyReady()) { // Changing the view will stop all playing animations, so if we are playing // anything important, queue the view change for later diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index 280d309256..ba815a7e60 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -95,11 +96,17 @@ namespace MWRender void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; } osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; } + int getCollisionType() const { return mCollisionType; } + void setCollisionType(int collisionType) { mCollisionType = collisionType; } + + const osg::Matrixf& getViewMatrix() const { return mViewMatrix; } + private: MWWorld::Ptr mTrackingPtr; osg::ref_ptr mTrackingNode; osg::Vec3d mTrackedPosition; float mHeightScale; + int mCollisionType; osg::ref_ptr mCamera; @@ -121,6 +128,7 @@ namespace MWRender float mExtraPitch = 0, mExtraYaw = 0; bool mLockPitch = false, mLockYaw = false; osg::Vec3d mPosition; + osg::Matrixf mViewMatrix; float mCameraDistance, mPreferredCameraDistance; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 4ec44f0141..a504a42faf 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -817,6 +817,11 @@ namespace MWRender updateNavMesh(); updateRecastMesh(); + if (mUpdateProjectionMatrix) + { + mUpdateProjectionMatrix = false; + updateProjectionMatrix(); + } mCamera->update(dt, paused); bool isUnderwater = mWater->isUnderwater(mCamera->getPosition()); @@ -1161,8 +1166,7 @@ namespace MWRender // Since our fog is not radial yet, we should take FOV in account, otherwise terrain near viewing distance may disappear. // Limit FOV here just for sure, otherwise viewing distance can be too high. - fov = std::min(mFieldOfView, 140.f); - float distanceMult = std::cos(osg::DegreesToRadians(fov)/2.f); + float distanceMult = std::cos(osg::DegreesToRadians(std::min(fov, 140.f))/2.f); mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f/distanceMult : 1.f)); } @@ -1300,6 +1304,17 @@ namespace MWRender } } + void RenderingManager::setFieldOfView(float val) + { + mFieldOfView = val; + mUpdateProjectionMatrix = true; + } + + float RenderingManager::getFieldOfView() const + { + return mFieldOfViewOverridden ? mFieldOfViewOverridden : mFieldOfView; + } + osg::Vec3f RenderingManager::getHalfExtents(const MWWorld::ConstPtr& object) const { osg::Vec3f halfExtents(0, 0, 0); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 119c85f82f..b25e675748 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -214,6 +214,8 @@ namespace MWRender /// temporarily override the field of view with given value. void overrideFieldOfView(float val); + void setFieldOfView(float val); + float getFieldOfView() const; /// reset a previous overrideFieldOfView() call, i.e. revert to field of view specified in the settings file. void resetFieldOfView(); @@ -301,6 +303,7 @@ namespace MWRender float mFieldOfViewOverride; float mFieldOfView; float mFirstPersonFieldOfView; + bool mUpdateProjectionMatrix = false; void operator = (const RenderingManager&); RenderingManager(const RenderingManager&); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 6eeda94ef3..dc07a9117b 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -743,6 +743,8 @@ namespace MWWorld std::vector getAll(const std::string& id) override; Misc::Rng::Generator& getPrng() override; + + MWRender::RenderingManager* getRenderingManager() override { return mRendering.get(); } }; } diff --git a/files/builtin_scripts/scripts/omw/camera.lua b/files/builtin_scripts/scripts/omw/camera.lua index 40b25c6d13..04252e1b47 100644 --- a/files/builtin_scripts/scripts/omw/camera.lua +++ b/files/builtin_scripts/scripts/omw/camera.lua @@ -4,6 +4,7 @@ local input = require('openmw.input') local settings = require('openmw.settings') local util = require('openmw.util') local self = require('openmw.self') +local nearby = require('openmw.nearby') local Actor = require('openmw.types').Actor @@ -23,6 +24,8 @@ local noHeadBobbing = 0 local noZoom = 0 local function init() + camera.setCollisionType(util.bitAnd(nearby.COLLISION_TYPE.Default, util.bitNot(nearby.COLLISION_TYPE.Actor))) + camera.setFieldOfView(camera.getBaseFieldOfView()) camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation')) if camera.getMode() == MODE.FirstPerson then primaryMode = MODE.FirstPerson diff --git a/files/builtin_scripts/scripts/omw/third_person.lua b/files/builtin_scripts/scripts/omw/third_person.lua index 95f872b15f..1b833d2322 100644 --- a/files/builtin_scripts/scripts/omw/third_person.lua +++ b/files/builtin_scripts/scripts/omw/third_person.lua @@ -78,6 +78,8 @@ local function updateState() state = STATE.Swimming elseif oldState == STATE.Combat or oldState == STATE.Swimming then state = defaultShoulder + elseif not state then + state = defaultShoulder end if autoSwitchShoulder and (mode == MODE.ThirdPerson or state ~= oldState or noThirdPersonLastFrame) and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then diff --git a/files/lua_api/openmw/camera.lua b/files/lua_api/openmw/camera.lua index 1d09bdf7ad..f744dcbc5c 100644 --- a/files/lua_api/openmw/camera.lua +++ b/files/lua_api/openmw/camera.lua @@ -166,6 +166,36 @@ -- Make instant the current transition of camera focal point and the current deferred rotation (see `allowCharacterDeferredRotation`). -- @function [parent=#camera] instantTransition +--- Get current camera collision type (see @{openmw.nearby#COLLISION_TYPE}). +-- @function [parent=#camera] getCollisionType +-- @return #number + +--- Set camera collision type (see @{openmw.nearby#COLLISION_TYPE}). +-- @function [parent=#camera] setCollisionType +-- @param #number collisionType + +--- Return base field of view vertical angle in radians +-- @function [parent=#camera] getBaseFieldOfView +-- @return #number + +--- Return current field of view vertical angle in radians +-- @function [parent=#camera] getFieldOfView +-- @return #number + +--- Set field of view +-- @function [parent=#camera] setFieldOfView +-- @param #number fov Field of view vertical angle in radians + +--- Get world to local transform for the camera. +-- @function [parent=#camera] getViewTransform +-- @return openmw.util#Transform + +--- Get vector from the camera to the world for the given point in viewport. +-- (0, 0) is the top left corner of the screen. +-- @function [parent=#camera] viewportToWorldVector +-- @param openmw.util#Vector2 normalizedScreenPos +-- @return openmw.util#Vector3 + return nil