mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-29 22:45:36 +00:00
Dehardcode camera
This commit is contained in:
parent
e56ee2c735
commit
f42badd7be
22 changed files with 638 additions and 514 deletions
|
@ -62,6 +62,7 @@ namespace MWPhysics
|
|||
namespace MWRender
|
||||
{
|
||||
class Animation;
|
||||
class Camera;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
|
@ -433,14 +434,12 @@ namespace MWBase
|
|||
|
||||
virtual osg::Matrixf getActorHeadTransform(const MWWorld::ConstPtr& actor) const = 0;
|
||||
|
||||
virtual MWRender::Camera* getCamera() = 0;
|
||||
virtual void togglePOV(bool force = false) = 0;
|
||||
virtual bool isFirstPerson() const = 0;
|
||||
virtual bool isPreviewModeEnabled() const = 0;
|
||||
virtual void togglePreviewMode(bool enable) = 0;
|
||||
virtual bool toggleVanityMode(bool enable) = 0;
|
||||
virtual void allowVanityMode(bool allow) = 0;
|
||||
virtual bool vanityRotateCamera(float * rot) = 0;
|
||||
virtual void adjustCameraDistance(float dist) = 0;
|
||||
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
|
||||
virtual void disableDeferredPreviewRotation() = 0;
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
namespace MWInput
|
||||
{
|
||||
const float ZOOM_SCALE = 10.f; /// Used for scrolling camera in and out
|
||||
|
||||
ActionManager::ActionManager(BindingsManager* bindingsManager,
|
||||
osgViewer::ScreenCaptureHandler::CaptureOperation* screenCaptureOperation,
|
||||
|
@ -41,7 +40,6 @@ namespace MWInput
|
|||
, mSneaking(false)
|
||||
, mAttemptJump(false)
|
||||
, mOverencumberedMessageDelay(0.f)
|
||||
, mPreviewPOVDelay(0.f)
|
||||
, mTimeIdle(0.f)
|
||||
{
|
||||
}
|
||||
|
@ -109,27 +107,6 @@ namespace MWInput
|
|||
}
|
||||
}
|
||||
|
||||
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch"))
|
||||
{
|
||||
const float switchLimit = 0.25;
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
if (mBindingsManager->actionIsActive(A_TogglePOV))
|
||||
{
|
||||
if (world->isFirstPerson() ? mPreviewPOVDelay > switchLimit : mPreviewPOVDelay == 0)
|
||||
world->togglePreviewMode(true);
|
||||
mPreviewPOVDelay += dt;
|
||||
}
|
||||
else
|
||||
{
|
||||
//disable preview mode
|
||||
if (mPreviewPOVDelay > 0)
|
||||
world->togglePreviewMode(false);
|
||||
if (mPreviewPOVDelay > 0.f && mPreviewPOVDelay <= switchLimit)
|
||||
world->togglePOV();
|
||||
mPreviewPOVDelay = 0.f;
|
||||
}
|
||||
}
|
||||
|
||||
if (triedToMove)
|
||||
MWBase::Environment::get().getInputManager()->resetIdleTime();
|
||||
|
||||
|
@ -162,38 +139,16 @@ namespace MWInput
|
|||
resetIdleTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
updateIdleTime(dt);
|
||||
}
|
||||
mTimeIdle += dt;
|
||||
|
||||
mAttemptJump = false;
|
||||
}
|
||||
|
||||
bool ActionManager::isPreviewModeEnabled()
|
||||
{
|
||||
return MWBase::Environment::get().getWorld()->isPreviewModeEnabled();
|
||||
}
|
||||
|
||||
void ActionManager::resetIdleTime()
|
||||
{
|
||||
if (mTimeIdle < 0)
|
||||
MWBase::Environment::get().getWorld()->toggleVanityMode(false);
|
||||
mTimeIdle = 0.f;
|
||||
}
|
||||
|
||||
void ActionManager::updateIdleTime(float dt)
|
||||
{
|
||||
static const float vanityDelay = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
||||
.find("fVanityDelay")->mValue.getFloat();
|
||||
if (mTimeIdle >= 0.f)
|
||||
mTimeIdle += dt;
|
||||
if (mTimeIdle > vanityDelay)
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->toggleVanityMode(true);
|
||||
mTimeIdle = -1.f;
|
||||
}
|
||||
}
|
||||
|
||||
void ActionManager::executeAction(int action)
|
||||
{
|
||||
MWBase::Environment::get().getLuaManager()->inputEvent({MWBase::LuaManager::InputEvent::Action, action});
|
||||
|
@ -281,14 +236,6 @@ namespace MWInput
|
|||
case A_ToggleDebug:
|
||||
windowManager->toggleDebugWindow();
|
||||
break;
|
||||
case A_ZoomIn:
|
||||
if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode())
|
||||
MWBase::Environment::get().getWorld()->adjustCameraDistance(-ZOOM_SCALE);
|
||||
break;
|
||||
case A_ZoomOut:
|
||||
if (inputManager->getControlSwitch("playerviewswitch") && inputManager->getControlSwitch("playercontrols") && !windowManager->isGuiMode())
|
||||
MWBase::Environment::get().getWorld()->adjustCameraDistance(ZOOM_SCALE);
|
||||
break;
|
||||
case A_QuickSave:
|
||||
quickSave();
|
||||
break;
|
||||
|
|
|
@ -55,13 +55,9 @@ namespace MWInput
|
|||
|
||||
void setAttemptJump(bool enabled) { mAttemptJump = enabled; }
|
||||
|
||||
bool isPreviewModeEnabled();
|
||||
|
||||
private:
|
||||
void handleGuiArrowKey(int action);
|
||||
|
||||
void updateIdleTime(float dt);
|
||||
|
||||
BindingsManager* mBindingsManager;
|
||||
osg::ref_ptr<osgViewer::Viewer> mViewer;
|
||||
osg::ref_ptr<osgViewer::ScreenCaptureHandler> mScreenCaptureHandler;
|
||||
|
@ -72,7 +68,6 @@ namespace MWInput
|
|||
bool mAttemptJump;
|
||||
|
||||
float mOverencumberedMessageDelay;
|
||||
float mPreviewPOVDelay;
|
||||
float mTimeIdle;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -34,12 +34,10 @@ namespace MWInput
|
|||
, mJoystickEnabled (Settings::Manager::getBool("enable controller", "Input"))
|
||||
, mGamepadCursorSpeed(Settings::Manager::getFloat("gamepad cursor speed", "Input"))
|
||||
, mSneakToggleShortcutTimer(0.f)
|
||||
, mGamepadZoom(0)
|
||||
, mGamepadGuiCursorEnabled(true)
|
||||
, mGuiCursorEnabled(true)
|
||||
, mJoystickLastUsed(false)
|
||||
, mSneakGamepadShortcut(false)
|
||||
, mGamepadPreviewMode(false)
|
||||
{
|
||||
if (!controllerBindingsFile.empty())
|
||||
{
|
||||
|
@ -85,8 +83,6 @@ namespace MWInput
|
|||
|
||||
bool ControllerManager::update(float dt)
|
||||
{
|
||||
mGamepadPreviewMode = mActionManager->isPreviewModeEnabled();
|
||||
|
||||
if (mGuiCursorEnabled && !(mJoystickLastUsed && !mGamepadGuiCursorEnabled))
|
||||
{
|
||||
float xAxis = mBindingsManager->getActionValue(A_MoveLeftRight) * 2.0f - 1.0f;
|
||||
|
@ -115,7 +111,6 @@ namespace MWInput
|
|||
if (MWBase::Environment::get().getWindowManager()->isGuiMode()
|
||||
|| MWBase::Environment::get().getStateManager()->getState() != MWBase::StateManager::State_Running)
|
||||
{
|
||||
mGamepadZoom = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -182,15 +177,6 @@ namespace MWInput
|
|||
}
|
||||
}
|
||||
|
||||
if (MWBase::Environment::get().getInputManager()->getControlSwitch("playerviewswitch"))
|
||||
{
|
||||
if (!mBindingsManager->actionIsActive(A_TogglePOV))
|
||||
mGamepadZoom = 0;
|
||||
|
||||
if (mGamepadZoom)
|
||||
MWBase::Environment::get().getWorld()->adjustCameraDistance(-mGamepadZoom);
|
||||
}
|
||||
|
||||
return triedToMove;
|
||||
}
|
||||
|
||||
|
@ -289,21 +275,11 @@ namespace MWInput
|
|||
{
|
||||
gamepadToGuiControl(arg);
|
||||
}
|
||||
else
|
||||
else if (MWBase::Environment::get().getWorld()->isPreviewModeEnabled() &&
|
||||
(arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT || arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT))
|
||||
{
|
||||
if (mGamepadPreviewMode) // Preview Mode Gamepad Zooming
|
||||
{
|
||||
if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERRIGHT)
|
||||
{
|
||||
mGamepadZoom = arg.value * 0.85f / 1000.f / 12.f;
|
||||
return; // Do not propagate event.
|
||||
}
|
||||
else if (arg.axis == SDL_CONTROLLER_AXIS_TRIGGERLEFT)
|
||||
{
|
||||
mGamepadZoom = -arg.value * 0.85f / 1000.f / 12.f;
|
||||
return; // Do not propagate event.
|
||||
}
|
||||
}
|
||||
// Preview Mode Gamepad Zooming; do not propagate to mBindingsManager
|
||||
return;
|
||||
}
|
||||
mBindingsManager->controllerAxisMoved(deviceID, arg);
|
||||
}
|
||||
|
|
|
@ -56,12 +56,10 @@ namespace MWInput
|
|||
bool mJoystickEnabled;
|
||||
float mGamepadCursorSpeed;
|
||||
float mSneakToggleShortcutTimer;
|
||||
float mGamepadZoom;
|
||||
bool mGamepadGuiCursorEnabled;
|
||||
bool mGuiCursorEnabled;
|
||||
bool mJoystickLastUsed;
|
||||
bool mSneakGamepadShortcut;
|
||||
bool mGamepadPreviewMode;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -54,10 +54,6 @@ namespace MWInput
|
|||
/// \fixme maybe crouching at this time
|
||||
player.setUpDown(0);
|
||||
}
|
||||
else if (key == "vanitymode")
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->allowVanityMode(value);
|
||||
}
|
||||
else if (key == "playerlooking" && !value)
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->rotateObject(player.getPlayer(), osg::Vec3f());
|
||||
|
|
|
@ -1,12 +1,82 @@
|
|||
#include "luabindings.hpp"
|
||||
|
||||
#include "../mwrender/camera.hpp"
|
||||
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
using CameraMode = MWRender::Camera::Mode;
|
||||
|
||||
sol::table initCameraPackage(const Context& context)
|
||||
{
|
||||
MWRender::Camera* camera = MWBase::Environment::get().getWorld()->getCamera();
|
||||
|
||||
sol::table api(context.mLua->sol(), sol::create);
|
||||
// TODO
|
||||
api["MODE"] = LuaUtil::makeReadOnly(context.mLua->sol().create_table_with(
|
||||
"Static", CameraMode::Static,
|
||||
"FirstPerson", CameraMode::FirstPerson,
|
||||
"ThirdPerson", CameraMode::ThirdPerson,
|
||||
"Vanity", CameraMode::Vanity,
|
||||
"Preview", CameraMode::Preview
|
||||
));
|
||||
|
||||
api["getMode"] = [camera]() -> int { return static_cast<int>(camera->getMode()); };
|
||||
api["getQueuedMode"] = [camera]() -> sol::optional<int>
|
||||
{
|
||||
std::optional<CameraMode> mode = camera->getQueuedMode();
|
||||
if (mode)
|
||||
return static_cast<int>(*mode);
|
||||
else
|
||||
return sol::nullopt;
|
||||
};
|
||||
api["setMode"] = [camera](int mode, sol::optional<bool> force)
|
||||
{
|
||||
camera->setMode(static_cast<CameraMode>(mode), force ? *force : false);
|
||||
};
|
||||
|
||||
api["allowCharacterDeferredRotation"] = [camera](bool v) { camera->allowCharacterDeferredRotation(v); };
|
||||
api["showCrosshair"] = [camera](bool v) { camera->showCrosshair(v); };
|
||||
|
||||
api["getTrackedPosition"] = [camera]() -> osg::Vec3f { return camera->getTrackedPosition(); };
|
||||
api["getPosition"] = [camera]() -> osg::Vec3f { return camera->getPosition(); };
|
||||
|
||||
// All angles are negated in order to make camera rotation consistent with objects rotation.
|
||||
// TODO: Fix the inconsistency of rotation direction in camera.cpp.
|
||||
api["getPitch"] = [camera]() { return -camera->getPitch(); };
|
||||
api["getYaw"] = [camera]() { return -camera->getYaw(); };
|
||||
api["getRoll"] = [camera]() { return -camera->getRoll(); };
|
||||
|
||||
api["setStaticPosition"] = [camera](const osg::Vec3f& pos) { camera->setStaticPosition(pos); };
|
||||
api["setPitch"] = [camera](float v)
|
||||
{
|
||||
camera->setPitch(-v, true);
|
||||
if (camera->getMode() == CameraMode::ThirdPerson)
|
||||
camera->calculateDeferredRotation();
|
||||
};
|
||||
api["setYaw"] = [camera](float v)
|
||||
{
|
||||
camera->setYaw(-v, true);
|
||||
if (camera->getMode() == CameraMode::ThirdPerson)
|
||||
camera->calculateDeferredRotation();
|
||||
};
|
||||
api["setRoll"] = [camera](float v) { camera->setRoll(-v); };
|
||||
api["setExtraPitch"] = [camera](float v) { camera->setExtraPitch(-v); };
|
||||
api["setExtraYaw"] = [camera](float v) { camera->setExtraYaw(-v); };
|
||||
api["getExtraPitch"] = [camera]() { return -camera->getExtraPitch(); };
|
||||
api["getExtraYaw"] = [camera]() { return -camera->getExtraYaw(); };
|
||||
|
||||
api["getThirdPersonDistance"] = [camera]() { return camera->getCameraDistance(); };
|
||||
api["setPreferredThirdPersonDistance"] = [camera](float v) { camera->setPreferredCameraDistance(v); };
|
||||
|
||||
api["getFirstPersonOffset"] = [camera]() { return camera->getFirstPersonOffset(); };
|
||||
api["setFirstPersonOffset"] = [camera](const osg::Vec3f& v) { camera->setFirstPersonOffset(v); };
|
||||
|
||||
api["getFocalPreferredOffset"] = [camera]() -> osg::Vec2f { return camera->getFocalPointTargetOffset(); };
|
||||
api["setFocalPreferredOffset"] = [camera](const osg::Vec2f& v) { camera->setFocalPointTargetOffset(v); };
|
||||
api["getFocalTransitionSpeed"] = [camera]() { return camera->getFocalPointTransitionSpeed(); };
|
||||
api["setFocalTransitionSpeed"] = [camera](float v) { camera->setFocalPointTransitionSpeed(v); };
|
||||
api["instantTransition"] = [camera]() { camera->instantTransition(); };
|
||||
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace MWLua
|
|||
{
|
||||
auto* lua = context.mLua;
|
||||
sol::table api(lua->sol(), sol::create);
|
||||
api["API_REVISION"] = 9;
|
||||
api["API_REVISION"] = 10;
|
||||
api["quit"] = [lua]()
|
||||
{
|
||||
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
|
||||
|
|
|
@ -58,37 +58,23 @@ namespace MWRender
|
|||
mFirstPersonView(true),
|
||||
mMode(Mode::FirstPerson),
|
||||
mVanityAllowed(true),
|
||||
mStandingPreviewAllowed(Settings::Manager::getBool("preview if stand still", "Camera")),
|
||||
mDeferredRotationAllowed(Settings::Manager::getBool("deferred preview rotation", "Camera")),
|
||||
mNearest(30.f),
|
||||
mFurthest(800.f),
|
||||
mIsNearest(false),
|
||||
mDeferredRotationAllowed(true),
|
||||
mProcessViewChange(false),
|
||||
mHeight(124.f),
|
||||
mBaseCameraDistance(Settings::Manager::getFloat("third person camera distance", "Camera")),
|
||||
mPitch(0.f),
|
||||
mYaw(0.f),
|
||||
mRoll(0.f),
|
||||
mCameraDistance(0.f),
|
||||
mMaxNextCameraDistance(800.f),
|
||||
mPreferredCameraDistance(0.f),
|
||||
mFocalPointCurrentOffset(osg::Vec2d()),
|
||||
mFocalPointTargetOffset(osg::Vec2d()),
|
||||
mFocalPointTransitionSpeedCoef(1.f),
|
||||
mSkipFocalPointTransition(true),
|
||||
mPreviousTransitionInfluence(0.f),
|
||||
mSmoothedSpeed(0.f),
|
||||
mZoomOutWhenMoveCoef(Settings::Manager::getFloat("zoom out when move coef", "Camera")),
|
||||
mDynamicCameraDistanceEnabled(false),
|
||||
mShowCrosshairInThirdPersonMode(false),
|
||||
mHeadBobbingEnabled(Settings::Manager::getBool("head bobbing", "Camera")),
|
||||
mHeadBobbingOffset(0.f),
|
||||
mHeadBobbingWeight(0.f),
|
||||
mTotalMovement(0.f),
|
||||
mShowCrosshair(false),
|
||||
mDeferredRotation(osg::Vec3f()),
|
||||
mDeferredRotationDisabled(false)
|
||||
{
|
||||
mCameraDistance = mBaseCameraDistance;
|
||||
|
||||
mUpdateCallback = new UpdateRenderCameraCallback(this);
|
||||
mCamera->addUpdateCallback(mUpdateCallback);
|
||||
}
|
||||
|
@ -98,7 +84,7 @@ namespace MWRender
|
|||
mCamera->removeUpdateCallback(mUpdateCallback);
|
||||
}
|
||||
|
||||
osg::Vec3d Camera::getTrackingNodePosition() const
|
||||
osg::Vec3d Camera::calculateTrackedPosition() const
|
||||
{
|
||||
if (!mTrackingNode)
|
||||
return osg::Vec3d();
|
||||
|
@ -106,59 +92,46 @@ namespace MWRender
|
|||
if (nodepaths.empty())
|
||||
return osg::Vec3d();
|
||||
osg::Matrix worldMat = osg::computeLocalToWorld(nodepaths[0]);
|
||||
return worldMat.getTrans();
|
||||
}
|
||||
|
||||
osg::Vec3d Camera::getThirdPersonBasePosition() const
|
||||
{
|
||||
osg::Vec3d position = getTrackingNodePosition();
|
||||
position.z() += mHeight * mHeightScale;
|
||||
|
||||
// We subtract 10.f here and add it within focalPointOffset in order to avoid camera clipping through ceiling.
|
||||
// Needed because character's head can be a bit higher than collision area.
|
||||
position.z() -= 10.f;
|
||||
|
||||
return position;
|
||||
osg::Vec3d res = worldMat.getTrans();
|
||||
if (mMode != Mode::FirstPerson)
|
||||
res.z() += mHeight * mHeightScale;
|
||||
return res;
|
||||
}
|
||||
|
||||
osg::Vec3d Camera::getFocalPointOffset() const
|
||||
{
|
||||
osg::Vec3d offset(0, 0, 10.f);
|
||||
offset.x() += mFocalPointCurrentOffset.x() * cos(mYaw);
|
||||
offset.y() += mFocalPointCurrentOffset.x() * sin(mYaw);
|
||||
offset.z() += mFocalPointCurrentOffset.y();
|
||||
osg::Vec3d offset;
|
||||
offset.x() = mFocalPointCurrentOffset.x() * cos(mYaw);
|
||||
offset.y() = mFocalPointCurrentOffset.x() * sin(mYaw);
|
||||
offset.z() = mFocalPointCurrentOffset.y();
|
||||
return offset;
|
||||
}
|
||||
|
||||
void Camera::updateCamera(osg::Camera *cam)
|
||||
{
|
||||
osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) * osg::Quat(mPitch, osg::Vec3d(1, 0, 0)) * osg::Quat(mYaw, osg::Vec3d(0, 0, 1));
|
||||
osg::Quat orient = osg::Quat(mRoll, osg::Vec3d(0, 1, 0)) *
|
||||
osg::Quat(mPitch + mExtraPitch, osg::Vec3d(1, 0, 0)) *
|
||||
osg::Quat(mYaw + mExtraYaw, osg::Vec3d(0, 0, 1));
|
||||
osg::Vec3d forward = orient * osg::Vec3d(0,1,0);
|
||||
osg::Vec3d up = orient * osg::Vec3d(0,0,1);
|
||||
|
||||
cam->setViewMatrixAsLookAt(mPosition, mPosition + forward, up);
|
||||
}
|
||||
|
||||
void Camera::updateHeadBobbing(float duration) {
|
||||
static const float doubleStepLength = Settings::Manager::getFloat("head bobbing step", "Camera") * 2;
|
||||
static const float stepHeight = Settings::Manager::getFloat("head bobbing height", "Camera");
|
||||
static const float maxRoll = osg::DegreesToRadians(Settings::Manager::getFloat("head bobbing roll", "Camera"));
|
||||
|
||||
if (MWBase::Environment::get().getWorld()->isOnGround(mTrackingPtr))
|
||||
mHeadBobbingWeight = std::min(mHeadBobbingWeight + duration * 5, 1.f);
|
||||
else
|
||||
mHeadBobbingWeight = std::max(mHeadBobbingWeight - duration * 5, 0.f);
|
||||
|
||||
float doubleStepState = mTotalMovement / doubleStepLength - std::floor(mTotalMovement / doubleStepLength); // from 0 to 1 during 2 steps
|
||||
float stepState = std::abs(doubleStepState * 4 - 2) - 1; // from -1 to 1 on even steps and from 1 to -1 on odd steps
|
||||
float effect = (1 - std::cos(stepState * osg::DegreesToRadians(30.f))) * 7.5f; // range from 0 to 1
|
||||
float coef = std::min(mSmoothedSpeed / 300.f, 1.f) * mHeadBobbingWeight;
|
||||
mHeadBobbingOffset = (0.5f - effect) * coef * stepHeight; // range from -stepHeight/2 to stepHeight/2
|
||||
mRoll = osg::sign(stepState) * effect * coef * maxRoll; // range from -maxRoll to maxRoll
|
||||
osg::Vec3d pos = mPosition;
|
||||
if (mMode == Mode::FirstPerson)
|
||||
{
|
||||
// It is a hack. Camera position depends on neck animation.
|
||||
// Animations are updated in OSG cull traversal and in order to avoid 1 frame delay we
|
||||
// recalculate the position here. Note that it becomes different from mPosition that
|
||||
// is used in other parts of the code.
|
||||
// TODO: detach camera from OSG animation and get rid of this hack.
|
||||
osg::Vec3d recalculatedTrackedPosition = calculateTrackedPosition();
|
||||
pos = calculateFirstPersonPosition(recalculatedTrackedPosition);
|
||||
}
|
||||
cam->setViewMatrixAsLookAt(pos, pos + forward, up);
|
||||
}
|
||||
|
||||
void Camera::update(float duration, bool paused)
|
||||
{
|
||||
mLockPitch = mLockYaw = false;
|
||||
if (mQueuedMode && mAnimation->upperBodyReady())
|
||||
setMode(*mQueuedMode);
|
||||
if (mProcessViewChange)
|
||||
|
@ -169,38 +142,31 @@ namespace MWRender
|
|||
|
||||
// only show the crosshair in game mode
|
||||
MWBase::WindowManager *wm = MWBase::Environment::get().getWindowManager();
|
||||
wm->showCrosshair(!wm->isGuiMode() && mMode != Mode::Preview && mMode != Mode::Vanity
|
||||
&& (mFirstPersonView || mShowCrosshairInThirdPersonMode));
|
||||
|
||||
if(mMode == Mode::Vanity)
|
||||
setYaw(mYaw + osg::DegreesToRadians(3.f) * duration);
|
||||
|
||||
if (mMode == Mode::FirstPerson && mHeadBobbingEnabled)
|
||||
updateHeadBobbing(duration);
|
||||
else
|
||||
mRoll = mHeadBobbingOffset = 0;
|
||||
wm->showCrosshair(!wm->isGuiMode() && mShowCrosshair);
|
||||
|
||||
updateFocalPointOffset(duration);
|
||||
updatePosition();
|
||||
}
|
||||
|
||||
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
|
||||
mTotalMovement += speed * duration;
|
||||
speed /= (1.f + speed / 500.f);
|
||||
float maxDelta = 300.f * duration;
|
||||
mSmoothedSpeed += std::clamp(speed - mSmoothedSpeed, -maxDelta, maxDelta);
|
||||
|
||||
mMaxNextCameraDistance = mCameraDistance + duration * (100.f + mBaseCameraDistance);
|
||||
updateStandingPreviewMode();
|
||||
osg::Vec3d Camera::calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const
|
||||
{
|
||||
osg::Vec3d res = trackedPosition;
|
||||
osg::Vec2f horizontalOffset = Misc::rotateVec2f(osg::Vec2f(mFirstPersonOffset.x(), mFirstPersonOffset.y()), mYaw);
|
||||
res.x() += horizontalOffset.x();
|
||||
res.y() += horizontalOffset.y();
|
||||
res.z() += mFirstPersonOffset.z();
|
||||
return res;
|
||||
}
|
||||
|
||||
void Camera::updatePosition()
|
||||
{
|
||||
mTrackedPosition = calculateTrackedPosition();
|
||||
if (mMode == Mode::Static)
|
||||
return;
|
||||
if (mMode == Mode::FirstPerson)
|
||||
{
|
||||
mPosition = getTrackingNodePosition();
|
||||
mPosition.z() += mHeadBobbingOffset;
|
||||
mPosition = calculateFirstPersonPosition(mTrackedPosition);
|
||||
mCameraDistance = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -212,7 +178,9 @@ namespace MWRender
|
|||
|
||||
// Adjust focal point to prevent clipping.
|
||||
osg::Vec3d focalOffset = getFocalPointOffset();
|
||||
osg::Vec3d focal = getThirdPersonBasePosition() + focalOffset;
|
||||
osg::Vec3d focal = mTrackedPosition + focalOffset;
|
||||
focalOffset.z() += 10.f; // Needed to avoid camera clipping through the ceiling because
|
||||
// character's head can be a bit higher than the collision area.
|
||||
float offsetLen = focalOffset.length();
|
||||
if (offsetLen > 0)
|
||||
{
|
||||
|
@ -224,12 +192,9 @@ namespace MWRender
|
|||
}
|
||||
}
|
||||
|
||||
// Calculate offset from focal point.
|
||||
mCameraDistance = mBaseCameraDistance + getCameraDistanceCorrection();
|
||||
if (mDynamicCameraDistanceEnabled)
|
||||
mCameraDistance = std::min(mCameraDistance, mMaxNextCameraDistance);
|
||||
|
||||
osg::Quat orient = osg::Quat(getPitch(), osg::Vec3d(1,0,0)) * osg::Quat(getYaw(), osg::Vec3d(0,0,1));
|
||||
// Adjust camera distance.
|
||||
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);
|
||||
if (result.mHit)
|
||||
|
@ -243,8 +208,6 @@ namespace MWRender
|
|||
|
||||
void Camera::setMode(Mode newMode, bool force)
|
||||
{
|
||||
if (newMode == Mode::StandingPreview)
|
||||
newMode = Mode::ThirdPerson;
|
||||
if (mMode == newMode)
|
||||
return;
|
||||
Mode oldMode = mMode;
|
||||
|
@ -267,23 +230,6 @@ namespace MWRender
|
|||
instantTransition();
|
||||
mProcessViewChange = true;
|
||||
}
|
||||
else if (newMode == Mode::ThirdPerson)
|
||||
updateStandingPreviewMode();
|
||||
}
|
||||
|
||||
void Camera::updateStandingPreviewMode()
|
||||
{
|
||||
float speed = mTrackingPtr.getClass().getCurrentSpeed(mTrackingPtr);
|
||||
bool combat = mTrackingPtr.getClass().isActor() &&
|
||||
mTrackingPtr.getClass().getCreatureStats(mTrackingPtr).getDrawState() != MWMechanics::DrawState_Nothing;
|
||||
bool standingStill = speed == 0 && !combat && mStandingPreviewAllowed;
|
||||
if (!standingStill && mMode == Mode::StandingPreview)
|
||||
{
|
||||
mMode = Mode::ThirdPerson;
|
||||
calculateDeferredRotation();
|
||||
}
|
||||
else if (standingStill && mMode == Mode::ThirdPerson)
|
||||
mMode = Mode::StandingPreview;
|
||||
}
|
||||
|
||||
void Camera::setFocalPointTargetOffset(const osg::Vec2d& v)
|
||||
|
@ -339,13 +285,6 @@ namespace MWRender
|
|||
setMode(mFirstPersonView ? Mode::ThirdPerson : Mode::FirstPerson, force);
|
||||
}
|
||||
|
||||
void Camera::allowVanityMode(bool allow)
|
||||
{
|
||||
if (!allow && mMode == Mode::Vanity)
|
||||
toggleVanityMode(false);
|
||||
mVanityAllowed = allow;
|
||||
}
|
||||
|
||||
bool Camera::toggleVanityMode(bool enable)
|
||||
{
|
||||
if (!enable)
|
||||
|
@ -355,73 +294,34 @@ namespace MWRender
|
|||
return (mMode == Mode::Vanity) == enable;
|
||||
}
|
||||
|
||||
void Camera::togglePreviewMode(bool enable)
|
||||
{
|
||||
if (mFirstPersonView && !mAnimation->upperBodyReady())
|
||||
return;
|
||||
if ((mMode == Mode::Preview) == enable)
|
||||
return;
|
||||
if (enable)
|
||||
setMode(Mode::Preview);
|
||||
else
|
||||
setMode(mFirstPersonView ? Mode::FirstPerson : Mode::ThirdPerson);
|
||||
}
|
||||
|
||||
void Camera::setSneakOffset(float offset)
|
||||
{
|
||||
mAnimation->setFirstPersonOffset(osg::Vec3f(0,0,-offset));
|
||||
}
|
||||
|
||||
void Camera::setYaw(float angle)
|
||||
void Camera::setYaw(float angle, bool force)
|
||||
{
|
||||
mYaw = Misc::normalizeAngle(angle);
|
||||
if (!mLockYaw || force)
|
||||
mYaw = Misc::normalizeAngle(angle);
|
||||
if (force)
|
||||
mLockYaw = true;
|
||||
}
|
||||
|
||||
void Camera::setPitch(float angle)
|
||||
void Camera::setPitch(float angle, bool force)
|
||||
{
|
||||
const float epsilon = 0.000001f;
|
||||
float limit = static_cast<float>(osg::PI_2) - epsilon;
|
||||
mPitch = std::clamp(angle, -limit, limit);
|
||||
if (!mLockPitch || force)
|
||||
mPitch = std::clamp(angle, -limit, limit);
|
||||
if (force)
|
||||
mLockPitch = true;
|
||||
}
|
||||
|
||||
float Camera::getCameraDistance() const
|
||||
void Camera::setStaticPosition(const osg::Vec3d& pos)
|
||||
{
|
||||
return mMode == Mode::FirstPerson ? 0.f : mCameraDistance;
|
||||
}
|
||||
|
||||
void Camera::adjustCameraDistance(float delta)
|
||||
{
|
||||
if (mMode == Mode::Static)
|
||||
return;
|
||||
if (mMode != Mode::FirstPerson)
|
||||
{
|
||||
if (mIsNearest && delta < 0.f && mMode != Mode::Preview && mMode != Mode::Vanity)
|
||||
toggleViewMode();
|
||||
else
|
||||
mBaseCameraDistance = std::min(mCameraDistance - getCameraDistanceCorrection(), mBaseCameraDistance) + delta;
|
||||
}
|
||||
else if (delta > 0.f)
|
||||
{
|
||||
toggleViewMode();
|
||||
mBaseCameraDistance = 0;
|
||||
}
|
||||
|
||||
mIsNearest = mBaseCameraDistance <= mNearest;
|
||||
mBaseCameraDistance = std::clamp(mBaseCameraDistance, mNearest, mFurthest);
|
||||
Settings::Manager::setFloat("third person camera distance", "Camera", mBaseCameraDistance);
|
||||
}
|
||||
|
||||
float Camera::getCameraDistanceCorrection() const
|
||||
{
|
||||
if (!mDynamicCameraDistanceEnabled)
|
||||
return 0;
|
||||
|
||||
float pitchCorrection = std::max(-getPitch(), 0.f) * 50.f;
|
||||
|
||||
float smoothedSpeedSqr = mSmoothedSpeed * mSmoothedSpeed;
|
||||
float speedCorrection = smoothedSpeedSqr / (smoothedSpeedSqr + 300.f*300.f) * mZoomOutWhenMoveCoef;
|
||||
|
||||
return pitchCorrection + speedCorrection;
|
||||
if (mMode != Mode::Static)
|
||||
throw std::runtime_error("setStaticPosition can be used only if camera is in Static mode");
|
||||
mPosition = pos;
|
||||
}
|
||||
|
||||
void Camera::setAnimation(NpcAnimation *anim)
|
||||
|
@ -432,6 +332,8 @@ namespace MWRender
|
|||
|
||||
void Camera::processViewChange()
|
||||
{
|
||||
if (mTrackingPtr.isEmpty())
|
||||
return;
|
||||
if (mMode == Mode::FirstPerson)
|
||||
{
|
||||
mAnimation->setViewMode(NpcAnimation::VM_FirstPerson);
|
||||
|
@ -488,7 +390,7 @@ namespace MWRender
|
|||
|
||||
void Camera::rotateCameraToTrackingPtr()
|
||||
{
|
||||
if (mMode == Mode::Static)
|
||||
if (mMode == Mode::Static || mTrackingPtr.isEmpty())
|
||||
return;
|
||||
setPitch(-mTrackingPtr.getRefData().getPosition().rot[0] - mDeferredRotation.x());
|
||||
setYaw(-mTrackingPtr.getRefData().getPosition().rot[2] - mDeferredRotation.z());
|
||||
|
@ -524,9 +426,4 @@ namespace MWRender
|
|||
mDeferredRotationDisabled = true;
|
||||
}
|
||||
|
||||
bool Camera::isVanityOrPreviewModeEnabled() const
|
||||
{
|
||||
return mMode == Mode::Vanity || mMode == Mode::Preview || mMode == Mode::StandingPreview;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,11 +25,80 @@ namespace MWRender
|
|||
class Camera
|
||||
{
|
||||
public:
|
||||
enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4, StandingPreview = 5};
|
||||
enum class Mode : int {Static = 0, FirstPerson = 1, ThirdPerson = 2, Vanity = 3, Preview = 4};
|
||||
|
||||
Camera(osg::Camera* camera);
|
||||
~Camera();
|
||||
|
||||
/// Attach camera to object
|
||||
void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; }
|
||||
MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; }
|
||||
|
||||
void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
|
||||
float getFocalPointTransitionSpeed() const { return mFocalPointTransitionSpeedCoef; }
|
||||
void setFocalPointTargetOffset(const osg::Vec2d& v);
|
||||
osg::Vec2d getFocalPointTargetOffset() const { return mFocalPointTargetOffset; }
|
||||
void instantTransition();
|
||||
void showCrosshair(bool v) { mShowCrosshair = v; }
|
||||
|
||||
/// Update the view matrix of \a cam
|
||||
void updateCamera(osg::Camera* cam);
|
||||
|
||||
/// Reset to defaults
|
||||
void reset() { setMode(Mode::FirstPerson); }
|
||||
|
||||
void rotateCameraToTrackingPtr();
|
||||
|
||||
float getPitch() const { return mPitch; }
|
||||
float getYaw() const { return mYaw; }
|
||||
float getRoll() const { return mRoll; }
|
||||
|
||||
void setPitch(float angle, bool force = false);
|
||||
void setYaw(float angle, bool force = false);
|
||||
void setRoll(float angle) { mRoll = angle; }
|
||||
|
||||
float getExtraPitch() const { return mExtraPitch; }
|
||||
float getExtraYaw() const { return mExtraYaw; }
|
||||
void setExtraPitch(float angle) { mExtraPitch = angle; }
|
||||
void setExtraYaw(float angle) { mExtraYaw = angle; }
|
||||
|
||||
/// @param Force view mode switch, even if currently not allowed by the animation.
|
||||
void toggleViewMode(bool force=false);
|
||||
bool toggleVanityMode(bool enable);
|
||||
|
||||
void applyDeferredPreviewRotationToPlayer(float dt);
|
||||
void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; }
|
||||
|
||||
/// \brief Lowers the camera for sneak.
|
||||
void setSneakOffset(float offset);
|
||||
|
||||
void processViewChange();
|
||||
|
||||
void update(float duration, bool paused=false);
|
||||
|
||||
float getCameraDistance() const { return mCameraDistance; }
|
||||
void setPreferredCameraDistance(float v) { mPreferredCameraDistance = v; }
|
||||
|
||||
void setAnimation(NpcAnimation *anim);
|
||||
|
||||
osg::Vec3d getTrackedPosition() const { return mTrackedPosition; }
|
||||
const osg::Vec3d& getPosition() const { return mPosition; }
|
||||
void setStaticPosition(const osg::Vec3d& pos);
|
||||
|
||||
bool isVanityOrPreviewModeEnabled() const { return mMode == Mode::Vanity || mMode == Mode::Preview; }
|
||||
Mode getMode() const { return mMode; }
|
||||
std::optional<Mode> getQueuedMode() const { return mQueuedMode; }
|
||||
void setMode(Mode mode, bool force = true);
|
||||
|
||||
void allowCharacterDeferredRotation(bool v) { mDeferredRotationAllowed = v; }
|
||||
void calculateDeferredRotation();
|
||||
void setFirstPersonOffset(const osg::Vec3f& v) { mFirstPersonOffset = v; }
|
||||
osg::Vec3f getFirstPersonOffset() const { return mFirstPersonOffset; }
|
||||
|
||||
private:
|
||||
MWWorld::Ptr mTrackingPtr;
|
||||
osg::ref_ptr<const osg::Node> mTrackingNode;
|
||||
osg::Vec3d mTrackedPosition;
|
||||
float mHeightScale;
|
||||
|
||||
osg::ref_ptr<osg::Camera> mCamera;
|
||||
|
@ -43,21 +112,19 @@ namespace MWRender
|
|||
Mode mMode;
|
||||
std::optional<Mode> mQueuedMode;
|
||||
bool mVanityAllowed;
|
||||
bool mStandingPreviewAllowed;
|
||||
bool mDeferredRotationAllowed;
|
||||
|
||||
float mNearest;
|
||||
float mFurthest;
|
||||
bool mIsNearest;
|
||||
|
||||
bool mProcessViewChange;
|
||||
|
||||
float mHeight, mBaseCameraDistance;
|
||||
float mHeight;
|
||||
float mPitch, mYaw, mRoll;
|
||||
float mExtraPitch = 0, mExtraYaw = 0;
|
||||
bool mLockPitch = false, mLockYaw = false;
|
||||
osg::Vec3d mPosition;
|
||||
|
||||
float mCameraDistance;
|
||||
float mMaxNextCameraDistance;
|
||||
float mCameraDistance, mPreferredCameraDistance;
|
||||
|
||||
osg::Vec3f mFirstPersonOffset{0, 0, 0};
|
||||
|
||||
osg::Vec2d mFocalPointCurrentOffset;
|
||||
osg::Vec2d mFocalPointTargetOffset;
|
||||
|
@ -70,91 +137,19 @@ namespace MWRender
|
|||
osg::Vec2d mPreviousTransitionSpeed;
|
||||
osg::Vec2d mPreviousExtraOffset;
|
||||
|
||||
float mSmoothedSpeed;
|
||||
float mZoomOutWhenMoveCoef;
|
||||
bool mDynamicCameraDistanceEnabled;
|
||||
bool mShowCrosshairInThirdPersonMode;
|
||||
bool mShowCrosshair;
|
||||
|
||||
bool mHeadBobbingEnabled;
|
||||
float mHeadBobbingOffset;
|
||||
float mHeadBobbingWeight; // Value from 0 to 1 for smooth enabling/disabling.
|
||||
float mTotalMovement; // Needed for head bobbing.
|
||||
void updateHeadBobbing(float duration);
|
||||
|
||||
osg::Vec3d getTrackingNodePosition() const;
|
||||
osg::Vec3d calculateTrackedPosition() const;
|
||||
osg::Vec3d calculateFirstPersonPosition(const osg::Vec3d& trackedPosition) const;
|
||||
osg::Vec3d getFocalPointOffset() const;
|
||||
void updateFocalPointOffset(float duration);
|
||||
void updatePosition();
|
||||
float getCameraDistanceCorrection() const;
|
||||
|
||||
osg::ref_ptr<osg::Callback> mUpdateCallback;
|
||||
|
||||
// Used to rotate player to the direction of view after exiting preview or vanity mode.
|
||||
osg::Vec3f mDeferredRotation;
|
||||
bool mDeferredRotationDisabled;
|
||||
void calculateDeferredRotation();
|
||||
void updateStandingPreviewMode();
|
||||
|
||||
public:
|
||||
Camera(osg::Camera* camera);
|
||||
~Camera();
|
||||
|
||||
/// Attach camera to object
|
||||
void attachTo(const MWWorld::Ptr &ptr) { mTrackingPtr = ptr; }
|
||||
MWWorld::Ptr getTrackingPtr() const { return mTrackingPtr; }
|
||||
|
||||
void setFocalPointTransitionSpeed(float v) { mFocalPointTransitionSpeedCoef = v; }
|
||||
void setFocalPointTargetOffset(const osg::Vec2d& v);
|
||||
void instantTransition();
|
||||
void enableDynamicCameraDistance(bool v) { mDynamicCameraDistanceEnabled = v; }
|
||||
void enableCrosshairInThirdPersonMode(bool v) { mShowCrosshairInThirdPersonMode = v; }
|
||||
|
||||
/// Update the view matrix of \a cam
|
||||
void updateCamera(osg::Camera* cam);
|
||||
|
||||
/// Reset to defaults
|
||||
void reset() { setMode(Mode::FirstPerson); }
|
||||
|
||||
void rotateCameraToTrackingPtr();
|
||||
|
||||
float getYaw() const { return mYaw; }
|
||||
void setYaw(float angle);
|
||||
|
||||
float getPitch() const { return mPitch; }
|
||||
void setPitch(float angle);
|
||||
|
||||
/// @param Force view mode switch, even if currently not allowed by the animation.
|
||||
void toggleViewMode(bool force=false);
|
||||
|
||||
bool toggleVanityMode(bool enable);
|
||||
void allowVanityMode(bool allow);
|
||||
|
||||
/// @note this may be ignored if an important animation is currently playing
|
||||
void togglePreviewMode(bool enable);
|
||||
|
||||
void applyDeferredPreviewRotationToPlayer(float dt);
|
||||
void disableDeferredPreviewRotation() { mDeferredRotationDisabled = true; }
|
||||
|
||||
/// \brief Lowers the camera for sneak.
|
||||
void setSneakOffset(float offset);
|
||||
|
||||
void processViewChange();
|
||||
|
||||
void update(float duration, bool paused=false);
|
||||
|
||||
/// Adds distDelta to the camera distance. Switches 3rd/1st person view if distance is less than limit.
|
||||
void adjustCameraDistance(float distDelta);
|
||||
|
||||
float getCameraDistance() const;
|
||||
|
||||
void setAnimation(NpcAnimation *anim);
|
||||
|
||||
osg::Vec3d getThirdPersonBasePosition() const;
|
||||
const osg::Vec3d& getPosition() const { return mPosition; }
|
||||
|
||||
bool isVanityOrPreviewModeEnabled() const;
|
||||
Mode getMode() const { return mMode; }
|
||||
void setMode(Mode mode, bool force = true);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,6 @@
|
|||
#include "vismask.hpp"
|
||||
#include "pathgrid.hpp"
|
||||
#include "camera.hpp"
|
||||
#include "viewovershoulder.hpp"
|
||||
#include "water.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
#include "navmesh.hpp"
|
||||
|
@ -472,8 +471,6 @@ namespace MWRender
|
|||
mWater.reset(new Water(sceneRoot->getParent(0), sceneRoot, mResourceSystem, mViewer->getIncrementalCompileOperation(), resourcePath));
|
||||
|
||||
mCamera.reset(new Camera(mViewer->getCamera()));
|
||||
if (Settings::Manager::getBool("view over shoulder", "Camera"))
|
||||
mViewOverShoulderController.reset(new ViewOverShoulderController(mCamera.get()));
|
||||
|
||||
mScreenshotManager.reset(new ScreenshotManager(viewer, mRootNode, sceneRoot, mResourceSystem, mWater.get()));
|
||||
|
||||
|
@ -826,8 +823,6 @@ namespace MWRender
|
|||
updateNavMesh();
|
||||
updateRecastMesh();
|
||||
|
||||
if (mViewOverShoulderController)
|
||||
mViewOverShoulderController->update();
|
||||
mCamera->update(dt, paused);
|
||||
|
||||
bool isUnderwater = mWater->isUnderwater(mCamera->getPosition());
|
||||
|
|
|
@ -79,7 +79,6 @@ namespace MWRender
|
|||
class NpcAnimation;
|
||||
class Pathgrid;
|
||||
class Camera;
|
||||
class ViewOverShoulderController;
|
||||
class Water;
|
||||
class TerrainStorage;
|
||||
class LandManager;
|
||||
|
@ -283,7 +282,6 @@ namespace MWRender
|
|||
osg::ref_ptr<NpcAnimation> mPlayerAnimation;
|
||||
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> mPlayerNode;
|
||||
std::unique_ptr<Camera> mCamera;
|
||||
std::unique_ptr<ViewOverShoulderController> mViewOverShoulderController;
|
||||
|
||||
osg::ref_ptr<StateUpdater> mStateUpdater;
|
||||
osg::ref_ptr<SharedUniformStateUpdater> mSharedUniformStateUpdater;
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
#include "viewovershoulder.hpp"
|
||||
|
||||
#include <osg/Quat>
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "../mwworld/refdata.hpp"
|
||||
|
||||
#include "../mwmechanics/drawstate.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
ViewOverShoulderController::ViewOverShoulderController(Camera* camera) :
|
||||
mCamera(camera), mMode(Mode::RightShoulder),
|
||||
mAutoSwitchShoulder(Settings::Manager::getBool("auto switch shoulder", "Camera")),
|
||||
mOverShoulderHorizontalOffset(30.f), mOverShoulderVerticalOffset(-10.f)
|
||||
{
|
||||
osg::Vec2f offset = Settings::Manager::getVector2("view over shoulder offset", "Camera");
|
||||
mOverShoulderHorizontalOffset = std::abs(offset.x());
|
||||
mOverShoulderVerticalOffset = offset.y();
|
||||
mDefaultShoulderIsRight = offset.x() >= 0;
|
||||
|
||||
mCamera->enableDynamicCameraDistance(true);
|
||||
mCamera->enableCrosshairInThirdPersonMode(true);
|
||||
mCamera->setFocalPointTargetOffset(offset);
|
||||
}
|
||||
|
||||
void ViewOverShoulderController::update()
|
||||
{
|
||||
if (mCamera->getMode() == Camera::Mode::FirstPerson || mCamera->getMode() == Camera::Mode::Static)
|
||||
return;
|
||||
|
||||
Mode oldMode = mMode;
|
||||
auto ptr = mCamera->getTrackingPtr();
|
||||
bool combat = ptr.getClass().isActor() && ptr.getClass().getCreatureStats(ptr).getDrawState() != MWMechanics::DrawState_Nothing;
|
||||
if (combat && !mCamera->isVanityOrPreviewModeEnabled())
|
||||
mMode = Mode::Combat;
|
||||
else if (MWBase::Environment::get().getWorld()->isSwimming(ptr))
|
||||
mMode = Mode::Swimming;
|
||||
else if (oldMode == Mode::Combat || oldMode == Mode::Swimming)
|
||||
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
|
||||
if (mAutoSwitchShoulder && (mMode == Mode::LeftShoulder || mMode == Mode::RightShoulder))
|
||||
trySwitchShoulder();
|
||||
|
||||
if (oldMode == mMode)
|
||||
return;
|
||||
|
||||
if (mCamera->getMode() == Camera::Mode::Vanity)
|
||||
// Player doesn't touch controls for a long time. Transition should be very slow.
|
||||
mCamera->setFocalPointTransitionSpeed(0.2f);
|
||||
else if ((oldMode == Mode::Combat || mMode == Mode::Combat) && mCamera->getMode() == Camera::Mode::ThirdPerson)
|
||||
// Transition to/from combat mode and we are not it preview mode. Should be fast.
|
||||
mCamera->setFocalPointTransitionSpeed(5.f);
|
||||
else
|
||||
mCamera->setFocalPointTransitionSpeed(1.f); // Default transition speed.
|
||||
|
||||
switch (mMode)
|
||||
{
|
||||
case Mode::RightShoulder:
|
||||
mCamera->setFocalPointTargetOffset({mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset});
|
||||
break;
|
||||
case Mode::LeftShoulder:
|
||||
mCamera->setFocalPointTargetOffset({-mOverShoulderHorizontalOffset, mOverShoulderVerticalOffset});
|
||||
break;
|
||||
case Mode::Combat:
|
||||
case Mode::Swimming:
|
||||
default:
|
||||
mCamera->setFocalPointTargetOffset({0, 15});
|
||||
}
|
||||
}
|
||||
|
||||
void ViewOverShoulderController::trySwitchShoulder()
|
||||
{
|
||||
if (mCamera->getMode() != Camera::Mode::ThirdPerson)
|
||||
return;
|
||||
|
||||
const float limitToSwitch = 120; // switch to other shoulder if wall is closer than this limit
|
||||
const float limitToSwitchBack = 300; // switch back to default shoulder if there is no walls at this distance
|
||||
|
||||
auto orient = osg::Quat(mCamera->getYaw(), osg::Vec3d(0,0,1));
|
||||
osg::Vec3d playerPos = mCamera->getThirdPersonBasePosition();
|
||||
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
osg::Vec3d sideOffset = orient * osg::Vec3d(world->getHalfExtents(mCamera->getTrackingPtr()).x() - 1, 0, 0);
|
||||
float rayRight = world->getDistToNearestRayHit(
|
||||
playerPos + sideOffset, orient * osg::Vec3d(1, 0, 0), limitToSwitchBack + 1);
|
||||
float rayLeft = world->getDistToNearestRayHit(
|
||||
playerPos - sideOffset, orient * osg::Vec3d(-1, 0, 0), limitToSwitchBack + 1);
|
||||
float rayRightForward = world->getDistToNearestRayHit(
|
||||
playerPos + sideOffset, orient * osg::Vec3d(1, 3, 0), limitToSwitchBack + 1);
|
||||
float rayLeftForward = world->getDistToNearestRayHit(
|
||||
playerPos - sideOffset, orient * osg::Vec3d(-1, 3, 0), limitToSwitchBack + 1);
|
||||
float distRight = std::min(rayRight, rayRightForward);
|
||||
float distLeft = std::min(rayLeft, rayLeftForward);
|
||||
|
||||
if (distLeft < limitToSwitch && distRight > limitToSwitchBack)
|
||||
mMode = Mode::RightShoulder;
|
||||
else if (distRight < limitToSwitch && distLeft > limitToSwitchBack)
|
||||
mMode = Mode::LeftShoulder;
|
||||
else if (distRight > limitToSwitchBack && distLeft > limitToSwitchBack)
|
||||
mMode = mDefaultShoulderIsRight ? Mode::RightShoulder : Mode::LeftShoulder;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
#ifndef VIEWOVERSHOULDER_H
|
||||
#define VIEWOVERSHOULDER_H
|
||||
|
||||
#include "camera.hpp"
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
class ViewOverShoulderController
|
||||
{
|
||||
public:
|
||||
ViewOverShoulderController(Camera* camera);
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
void trySwitchShoulder();
|
||||
enum class Mode { RightShoulder, LeftShoulder, Combat, Swimming };
|
||||
|
||||
Camera* mCamera;
|
||||
Mode mMode;
|
||||
bool mAutoSwitchShoulder;
|
||||
float mOverShoulderHorizontalOffset;
|
||||
float mOverShoulderVerticalOffset;
|
||||
bool mDefaultShoulderIsRight;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // VIEWOVERSHOULDER_H
|
|
@ -2398,11 +2398,6 @@ namespace MWWorld
|
|||
return mRendering->getCamera()->getMode() == MWRender::Camera::Mode::Preview;
|
||||
}
|
||||
|
||||
void World::togglePreviewMode(bool enable)
|
||||
{
|
||||
mRendering->getCamera()->togglePreviewMode(enable);
|
||||
}
|
||||
|
||||
bool World::toggleVanityMode(bool enable)
|
||||
{
|
||||
return mRendering->getCamera()->toggleVanityMode(enable);
|
||||
|
@ -2418,10 +2413,7 @@ namespace MWWorld
|
|||
mRendering->getCamera()->applyDeferredPreviewRotationToPlayer(dt);
|
||||
}
|
||||
|
||||
void World::allowVanityMode(bool allow)
|
||||
{
|
||||
mRendering->getCamera()->allowVanityMode(allow);
|
||||
}
|
||||
MWRender::Camera* World::getCamera() { return mRendering->getCamera(); }
|
||||
|
||||
bool World::vanityRotateCamera(float * rot)
|
||||
{
|
||||
|
@ -2434,11 +2426,6 @@ namespace MWWorld
|
|||
return true;
|
||||
}
|
||||
|
||||
void World::adjustCameraDistance(float dist)
|
||||
{
|
||||
mRendering->getCamera()->adjustCameraDistance(dist);
|
||||
}
|
||||
|
||||
void World::saveLoaded()
|
||||
{
|
||||
mStore.validateDynamic();
|
||||
|
|
|
@ -529,13 +529,10 @@ namespace MWWorld
|
|||
bool isFirstPerson() const override;
|
||||
bool isPreviewModeEnabled() const override;
|
||||
|
||||
void togglePreviewMode(bool enable) override;
|
||||
|
||||
bool toggleVanityMode(bool enable) override;
|
||||
|
||||
void allowVanityMode(bool allow) override;
|
||||
MWRender::Camera* getCamera() override;
|
||||
bool vanityRotateCamera(float * rot) override;
|
||||
void adjustCameraDistance(float dist) override;
|
||||
|
||||
void applyDeferredPreviewRotationToPlayer(float dt) override;
|
||||
void disableDeferredPreviewRotation() override;
|
||||
|
|
1
files/builtin_scripts/builtin.omwscripts
Normal file
1
files/builtin_scripts/builtin.omwscripts
Normal file
|
@ -0,0 +1 @@
|
|||
PLAYER: scripts/omw/camera.lua
|
221
files/builtin_scripts/scripts/omw/camera.lua
Normal file
221
files/builtin_scripts/scripts/omw/camera.lua
Normal file
|
@ -0,0 +1,221 @@
|
|||
local camera = require('openmw.camera')
|
||||
local input = require('openmw.input')
|
||||
local settings = require('openmw.settings')
|
||||
local util = require('openmw.util')
|
||||
local self = require('openmw.self')
|
||||
|
||||
local head_bobbing = require('scripts.omw.head_bobbing')
|
||||
local third_person = require('scripts.omw.third_person')
|
||||
|
||||
local MODE = camera.MODE
|
||||
|
||||
local previewIfStandSill = settings._getBoolFromSettingsCfg('Camera', 'preview if stand still')
|
||||
local showCrosshairInThirdPerson = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder')
|
||||
|
||||
local primaryMode
|
||||
|
||||
local noModeControl = 0
|
||||
local noStandingPreview = 0
|
||||
local noHeadBobbing = 0
|
||||
local noZoom = 0
|
||||
|
||||
function init()
|
||||
camera.allowCharacterDeferredRotation(settings._getBoolFromSettingsCfg('Camera', 'deferred preview rotation'))
|
||||
if camera.getMode() == MODE.FirstPerson then
|
||||
primaryMode = MODE.FirstPerson
|
||||
else
|
||||
primaryMode = MODE.ThirdPerson
|
||||
camera.setMode(MODE.ThirdPerson)
|
||||
end
|
||||
end
|
||||
|
||||
local smoothedSpeed = 0
|
||||
local previewTimer = 0
|
||||
|
||||
local function updatePOV(dt)
|
||||
local switchLimit = 0.25
|
||||
if input.isActionPressed(input.ACTION.TogglePOV) and input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) then
|
||||
previewTimer = previewTimer + dt
|
||||
if primaryMode == MODE.ThirdPerson or previewTimer >= switchLimit then
|
||||
third_person.standingPreview = false
|
||||
camera.setMode(MODE.Preview)
|
||||
end
|
||||
elseif previewTimer > 0 then
|
||||
if previewTimer <= switchLimit then
|
||||
if primaryMode == MODE.FirstPerson then
|
||||
primaryMode = MODE.ThirdPerson
|
||||
else
|
||||
primaryMode = MODE.FirstPerson
|
||||
end
|
||||
end
|
||||
camera.setMode(primaryMode)
|
||||
previewTimer = 0
|
||||
end
|
||||
end
|
||||
|
||||
local idleTimer = 0
|
||||
local vanityDelay = settings.getGMST('fVanityDelay')
|
||||
|
||||
local function updateVanity(dt)
|
||||
if input.isIdle() then
|
||||
idleTimer = idleTimer + dt
|
||||
else
|
||||
idleTimer = 0
|
||||
end
|
||||
local vanityAllowed = input.getControlSwitch(input.CONTROL_SWITCH.VanityMode)
|
||||
if vanityAllowed and idleTimer > vanityDelay and camera.getMode() ~= MODE.Vanity then
|
||||
camera.setMode(MODE.Vanity)
|
||||
end
|
||||
if camera.getMode() == MODE.Vanity then
|
||||
if not vanityAllowed or idleTimer == 0 then
|
||||
camera.setMode(primaryMode)
|
||||
else
|
||||
camera.setYaw(camera.getYaw() + math.rad(3) * dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function updateSmoothedSpeed(dt)
|
||||
local speed = self:getCurrentSpeed()
|
||||
speed = speed / (1 + speed / 500)
|
||||
local maxDelta = 300 * dt
|
||||
smoothedSpeed = smoothedSpeed + util.clamp(speed - smoothedSpeed, -maxDelta, maxDelta)
|
||||
end
|
||||
|
||||
local minDistance = 30
|
||||
local maxDistance = 800
|
||||
|
||||
local function zoom(delta)
|
||||
if not input.getControlSwitch(input.CONTROL_SWITCH.ViewMode) or
|
||||
not input.getControlSwitch(input.CONTROL_SWITCH.Controls) or
|
||||
camera.getMode() == MODE.Static or noZoom > 0 then
|
||||
return
|
||||
end
|
||||
if camera.getMode() ~= MODE.FirstPerson then
|
||||
local obstacleDelta = third_person.preferredDistance - camera.getThirdPersonDistance()
|
||||
if delta > 0 and third_person.baseDistance == minDistance and
|
||||
(camera.getMode() ~= MODE.Preview or third_person.standingPreview) and noModeControl == 0 then
|
||||
primaryMode = MODE.FirstPerson
|
||||
camera.setMode(primaryMode)
|
||||
elseif delta > 0 or obstacleDelta < -delta then
|
||||
third_person.baseDistance = util.clamp(third_person.baseDistance - delta - obstacleDelta, minDistance, maxDistance)
|
||||
end
|
||||
elseif delta < 0 and noModeControl == 0 then
|
||||
primaryMode = MODE.ThirdPerson
|
||||
camera.setMode(primaryMode)
|
||||
third_person.baseDistance = minDistance
|
||||
end
|
||||
end
|
||||
|
||||
local function applyControllerZoom(dt)
|
||||
if camera.getMode() == MODE.Preview then
|
||||
local triggerLeft = input.getAxisValue(input.CONTROLLER_AXIS.TriggerLeft)
|
||||
local triggerRight = input.getAxisValue(input.CONTROLLER_AXIS.TriggerRight)
|
||||
local controllerZoom = (triggerRight - triggerLeft) * 100 * dt
|
||||
if controllerZoom ~= 0 then
|
||||
zoom(controllerZoom)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function updateStandingPreview()
|
||||
local mode = camera.getMode()
|
||||
if not previewIfStandSill or noStandingPreview > 0
|
||||
or mode == MODE.FirstPerson or mode == MODE.Static or mode == MODE.Vanity then
|
||||
third_person.standingPreview = false
|
||||
return
|
||||
end
|
||||
local standingStill = self:getCurrentSpeed() == 0 and not self:isInWeaponStance() and not self:isInMagicStance()
|
||||
if standingStill and mode == MODE.ThirdPerson then
|
||||
third_person.standingPreview = true
|
||||
camera.setMode(MODE.Preview)
|
||||
elseif not standingStill and third_person.standingPreview then
|
||||
third_person.standingPreview = false
|
||||
camera.setMode(primaryMode)
|
||||
end
|
||||
end
|
||||
|
||||
local function updateCrosshair()
|
||||
camera.showCrosshair(
|
||||
camera.getMode() == MODE.FirstPerson or
|
||||
(showCrosshairInThirdPerson and (camera.getMode() == MODE.ThirdPerson or third_person.standingPreview)))
|
||||
end
|
||||
|
||||
local function onUpdate(dt)
|
||||
camera.setExtraPitch(0)
|
||||
camera.setExtraYaw(0)
|
||||
camera.setRoll(0)
|
||||
camera.setFirstPersonOffset(util.vector3(0, 0, 0))
|
||||
updateSmoothedSpeed(dt)
|
||||
end
|
||||
|
||||
local function onInputUpdate(dt)
|
||||
local mode = camera.getMode()
|
||||
if mode == MODE.FirstPerson or mode == MODE.ThirdPerson then
|
||||
primaryMode = mode
|
||||
end
|
||||
if mode ~= MODE.Static then
|
||||
if not camera.getQueuedMode() or camera.getQueuedMode() == MODE.Preview then
|
||||
if noModeControl == 0 then
|
||||
updatePOV(dt)
|
||||
updateVanity(dt)
|
||||
end
|
||||
updateStandingPreview()
|
||||
end
|
||||
updateCrosshair()
|
||||
end
|
||||
applyControllerZoom(dt)
|
||||
third_person.update(dt, smoothedSpeed)
|
||||
if noHeadBobbing == 0 then head_bobbing.update(dt, smoothedSpeed) end
|
||||
end
|
||||
|
||||
return {
|
||||
interfaceName = 'Camera',
|
||||
interface = {
|
||||
version = 0,
|
||||
|
||||
getPrimaryMode = function() return primaryMode end,
|
||||
getBaseThirdPersonDistance = function() return third_person.baseDistance end,
|
||||
setBaseThirdPersonDistance = function(v) third_person.baseDistance = v end,
|
||||
getTargetThirdPersonDistance = function() return third_person.preferredDistance end,
|
||||
|
||||
isModeControlEnabled = function() return noModeControl == 0 end,
|
||||
disableModeControl = function() noModeControl = noModeControl + 1 end,
|
||||
enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end,
|
||||
|
||||
isStandingPreviewEnabled = function() return previewIfStandSill and noStandingPreview == 0 end,
|
||||
disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end,
|
||||
enableStandingPreview = function() noStandingPreview = math.max(0, noStandingPreview - 1) end,
|
||||
|
||||
isHeadBobbingEnabled = function() return head_bobbing.enabled and noHeadBobbing == 0 end,
|
||||
disableHeadBobbing = function() noHeadBobbing = noHeadBobbing + 1 end,
|
||||
enableHeadBobbing = function() noHeadBobbing = math.max(0, noHeadBobbing - 1) end,
|
||||
|
||||
isZoomEnabled = function() return noZoom == 0 end,
|
||||
disableZoom = function() noZoom = noZoom + 1 end,
|
||||
enableZoom = function() noZoom = math.max(0, noZoom - 1) end,
|
||||
|
||||
isThirdPersonOffsetControlEnabled = function() return third_person.noOffsetControl == 0 end,
|
||||
disableThirdPersonOffsetControl = function() third_person.noOffsetControl = third_person.noOffsetControl + 1 end,
|
||||
enableThirdPersonOffsetControl = function() third_person.noOffsetControl = math.max(0, third_person.noOffsetControl - 1) end,
|
||||
},
|
||||
engineHandlers = {
|
||||
onUpdate = onUpdate,
|
||||
onInputUpdate = onInputUpdate,
|
||||
onInputAction = function(action)
|
||||
if action == input.ACTION.ZoomIn then
|
||||
zoom(10)
|
||||
elseif action == input.ACTION.ZoomOut then
|
||||
zoom(-10)
|
||||
end
|
||||
end,
|
||||
onActive = init,
|
||||
onLoad = function(data)
|
||||
if data and data.distance then third_person.baseDistance = data.distance end
|
||||
end,
|
||||
onSave = function()
|
||||
return {version = 0, distance = third_person.baseDistance}
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
51
files/builtin_scripts/scripts/omw/head_bobbing.lua
Normal file
51
files/builtin_scripts/scripts/omw/head_bobbing.lua
Normal file
|
@ -0,0 +1,51 @@
|
|||
local camera = require('openmw.camera')
|
||||
local self = require('openmw.self')
|
||||
local settings = require('openmw.settings')
|
||||
local util = require('openmw.util')
|
||||
|
||||
local doubleStepLength = settings._getFloatFromSettingsCfg('Camera', 'head bobbing step') * 2
|
||||
local stepHeight = settings._getFloatFromSettingsCfg('Camera', 'head bobbing height')
|
||||
local maxRoll = math.rad(settings._getFloatFromSettingsCfg('Camera', 'head bobbing roll'))
|
||||
|
||||
local effectWeight = 0
|
||||
local totalMovement = 0
|
||||
|
||||
local M = {
|
||||
enabled = settings._getBoolFromSettingsCfg('Camera', 'head bobbing')
|
||||
}
|
||||
|
||||
-- Trajectory of each step is a scaled arc of 60 degrees.
|
||||
local halfArc = math.rad(30)
|
||||
local sampleArc = function(x) return 1 - math.cos(x * halfArc) end
|
||||
local arcHeight = sampleArc(1)
|
||||
|
||||
function M.update(dt, smoothedSpeed)
|
||||
local speed = self:getCurrentSpeed()
|
||||
speed = speed / (1 + speed / 500) -- limit bobbing frequency if the speed is very high
|
||||
totalMovement = totalMovement + speed * dt
|
||||
if not M.enabled or camera.getMode() ~= camera.MODE.FirstPerson then
|
||||
effectWeight = 0
|
||||
return
|
||||
end
|
||||
if self:isOnGround() then
|
||||
effectWeight = math.min(1, effectWeight + dt * 5)
|
||||
else
|
||||
effectWeight = math.max(0, effectWeight - dt * 5)
|
||||
end
|
||||
|
||||
local doubleStepState = totalMovement / doubleStepLength
|
||||
doubleStepState = doubleStepState - math.floor(doubleStepState) -- from 0 to 1 during 2 steps
|
||||
local stepState = math.abs(doubleStepState * 4 - 2) - 1 -- from -1 to 1 on even steps and from 1 to -1 on odd steps
|
||||
local effect = sampleArc(stepState) / arcHeight -- range from 0 to 1
|
||||
|
||||
-- Smoothly reduce the effect to zero when the player stops
|
||||
local coef = math.min(smoothedSpeed / 300, 1) * effectWeight
|
||||
|
||||
local zOffset = (0.5 - effect) * coef * stepHeight -- range from -stepHeight/2 to stepHeight/2
|
||||
local roll = ((stepState > 0 and 1) or -1) * effect * coef * maxRoll -- range from -maxRoll to maxRoll
|
||||
camera.setFirstPersonOffset(camera.getFirstPersonOffset() + util.vector3(0, 0, zOffset))
|
||||
camera.setRoll(camera.getRoll() + roll)
|
||||
end
|
||||
|
||||
return M
|
||||
|
139
files/builtin_scripts/scripts/omw/third_person.lua
Normal file
139
files/builtin_scripts/scripts/omw/third_person.lua
Normal file
|
@ -0,0 +1,139 @@
|
|||
local camera = require('openmw.camera')
|
||||
local settings = require('openmw.settings')
|
||||
local util = require('openmw.util')
|
||||
local self = require('openmw.self')
|
||||
local nearby = require('openmw.nearby')
|
||||
|
||||
local MODE = camera.MODE
|
||||
local STATE = { RightShoulder = 0, LeftShoulder = 1, Combat = 2, Swimming = 3 }
|
||||
|
||||
local M = {
|
||||
baseDistance = settings._getFloatFromSettingsCfg('Camera', 'third person camera distance'),
|
||||
preferredDistance = 0,
|
||||
standingPreview = false,
|
||||
noOffsetControl = 0,
|
||||
}
|
||||
|
||||
local viewOverShoulder = settings._getBoolFromSettingsCfg('Camera', 'view over shoulder')
|
||||
local autoSwitchShoulder = settings._getBoolFromSettingsCfg('Camera', 'auto switch shoulder')
|
||||
local shoulderOffset = settings._getVector2FromSettingsCfg('Camera', 'view over shoulder offset')
|
||||
local zoomOutWhenMoveCoef = settings._getFloatFromSettingsCfg('Camera', 'zoom out when move coef')
|
||||
|
||||
local defaultShoulder = (shoulderOffset.x > 0 and STATE.RightShoulder) or STATE.LeftShoulder
|
||||
local rightShoulderOffset = util.vector2(math.abs(shoulderOffset.x), shoulderOffset.y)
|
||||
local leftShoulderOffset = util.vector2(-math.abs(shoulderOffset.x), shoulderOffset.y)
|
||||
local combatOffset = util.vector2(0, 15)
|
||||
|
||||
local state = defaultShoulder
|
||||
|
||||
local rayOptions = {collisionType = nearby.COLLISION_TYPE.Default - nearby.COLLISION_TYPE.Actor}
|
||||
local function ray(from, angle, limit)
|
||||
local to = from + util.transform.rotateZ(angle) * util.vector3(0, limit, 0)
|
||||
local res = nearby.castRay(from, to, rayOptions)
|
||||
if res.hit then
|
||||
return (res.hitPos - from):length()
|
||||
else
|
||||
return limit
|
||||
end
|
||||
end
|
||||
|
||||
local function trySwitchShoulder()
|
||||
local limitToSwitch = 120 -- switch to other shoulder if wall is closer than this limit
|
||||
local limitToSwitchBack = 300 -- switch back to default shoulder if there is no walls at this distance
|
||||
|
||||
local pos = camera.getTrackedPosition()
|
||||
local rayRight = ray(pos, camera.getYaw() + math.rad(90), limitToSwitchBack + 1)
|
||||
local rayLeft = ray(pos, camera.getYaw() - math.rad(90), limitToSwitchBack + 1)
|
||||
local rayRightForward = ray(pos, camera.getYaw() + math.rad(30), limitToSwitchBack + 1)
|
||||
local rayLeftForward = ray(pos, camera.getYaw() - math.rad(30), limitToSwitchBack + 1)
|
||||
|
||||
local distRight = math.min(rayRight, rayRightForward)
|
||||
local distLeft = math.min(rayLeft, rayLeftForward)
|
||||
|
||||
if distLeft < limitToSwitch and distRight > limitToSwitchBack then
|
||||
state = STATE.RightShoulder
|
||||
elseif distRight < limitToSwitch and distLeft > limitToSwitchBack then
|
||||
state = STATE.LeftShoulder
|
||||
elseif distRight > limitToSwitchBack and distLeft > limitToSwitchBack then
|
||||
state = defaultShoulder
|
||||
end
|
||||
end
|
||||
|
||||
local function calculateDistance(smoothedSpeed)
|
||||
local smoothedSpeedSqr = smoothedSpeed * smoothedSpeed
|
||||
return (M.baseDistance + math.max(camera.getPitch(), 0) * 50
|
||||
+ smoothedSpeedSqr / (smoothedSpeedSqr + 300*300) * zoomOutWhenMoveCoef)
|
||||
end
|
||||
|
||||
local noThirdPersonLastFrame = true
|
||||
|
||||
local function updateState()
|
||||
local mode = camera.getMode()
|
||||
local oldState = state
|
||||
if (self:isInWeaponStance() or self:isInMagicStance()) and mode == MODE.ThirdPerson then
|
||||
state = STATE.Combat
|
||||
elseif self:isSwimming() then
|
||||
state = STATE.Swimming
|
||||
elseif oldState == STATE.Combat or oldState == STATE.Swimming then
|
||||
state = defaultShoulder
|
||||
end
|
||||
if autoSwitchShoulder and (mode == MODE.ThirdPerson or state ~= oldState or noThirdPersonLastFrame)
|
||||
and (state == STATE.LeftShoulder or state == STATE.RightShoulder) then
|
||||
trySwitchShoulder()
|
||||
end
|
||||
if oldState ~= state or noThirdPersonLastFrame then
|
||||
-- State was changed, start focal point transition.
|
||||
if mode == MODE.Vanity then
|
||||
-- Player doesn't touch controls for a long time. Transition should be very slow.
|
||||
camera.setFocalTransitionSpeed(0.2)
|
||||
elseif (oldState == STATE.Combat or state == STATE.Combat) and
|
||||
(mode ~= MODE.Preview or M.standingPreview) then
|
||||
-- Transition to/from combat mode and we are not in preview mode. Should be fast.
|
||||
camera.setFocalTransitionSpeed(5.0)
|
||||
else
|
||||
camera.setFocalTransitionSpeed(1.0) -- Default transition speed.
|
||||
end
|
||||
|
||||
if state == STATE.RightShoulder then
|
||||
camera.setFocalPreferredOffset(rightShoulderOffset)
|
||||
elseif state == STATE.LeftShoulder then
|
||||
camera.setFocalPreferredOffset(leftShoulderOffset)
|
||||
else
|
||||
camera.setFocalPreferredOffset(combatOffset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M.update(dt, smoothedSpeed)
|
||||
local mode = camera.getMode()
|
||||
if mode == MODE.FirstPerson or mode == MODE.Static then
|
||||
noThirdPersonLastFrame = true
|
||||
return
|
||||
end
|
||||
if not viewOverShoulder then
|
||||
M.preferredDistance = M.baseDistance
|
||||
camera.setPreferredThirdPersonDistance(M.baseDistance)
|
||||
noThirdPersonLastFrame = false
|
||||
return
|
||||
end
|
||||
|
||||
if M.noOffsetControl == 0 then
|
||||
updateState()
|
||||
else
|
||||
state = nil
|
||||
end
|
||||
|
||||
M.preferredDistance = calculateDistance(smoothedSpeed)
|
||||
if noThirdPersonLastFrame then -- just switched to third person view
|
||||
camera.setPreferredThirdPersonDistance(M.preferredDistance)
|
||||
camera.instantTransition()
|
||||
noThirdPersonLastFrame = false
|
||||
else
|
||||
local maxIncrease = dt * (100 + M.baseDistance)
|
||||
camera.setPreferredThirdPersonDistance(math.min(
|
||||
M.preferredDistance, camera.getThirdPersonDistance() + maxIncrease))
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
# Modifications should be done on the user openmw.cfg file instead
|
||||
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
|
||||
|
||||
content=builtin.omwscripts
|
||||
data=${MORROWIND_DATA_FILES}
|
||||
data-local="?userdata?data"
|
||||
resources=${OPENMW_RESOURCE_FILES}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# Modifications should be done on the user openmw.cfg file instead
|
||||
# (see: https://openmw.readthedocs.io/en/master/reference/modding/paths.html)
|
||||
|
||||
content=builtin.omwscripts
|
||||
data="?global?data"
|
||||
data=./data
|
||||
data-local="?userdata?data"
|
||||
|
|
Loading…
Reference in a new issue