mirror of https://github.com/OpenMW/openmw.git
Merge branch 'camera2' into 'master'
Dehardcode camera See merge request OpenMW/openmw!1030fix-static-urls
commit
a9d7598532
@ -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->isFirstPerson())
|
||||
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::Normal)
|
||||
// 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::Normal)
|
||||
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->getFocalPoint() - mCamera->getFocalPointOffset();
|
||||
|
||||
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
|
@ -0,0 +1,6 @@
|
||||
Interface Camera
|
||||
================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/scripts_omw_camera.html
|
||||
|
@ -0,0 +1,5 @@
|
||||
Package openmw.camera
|
||||
=====================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_camera.html
|
@ -0,0 +1 @@
|
||||
PLAYER: scripts/omw/camera.lua
|
@ -0,0 +1,243 @@
|
||||
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
|
||||
|
||||
local 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',
|
||||
--- @module Camera
|
||||
-- @usage require('openmw.interfaces').Camera
|
||||
interface = {
|
||||
--- @field [parent=#Camera] #number version Interface version
|
||||
version = 0,
|
||||
|
||||
--- @function [parent=#Camera] getPrimaryMode Returns primary mode (MODE.FirstPerson or MODE.ThirdPerson).
|
||||
getPrimaryMode = function() return primaryMode end,
|
||||
--- @function [parent=#Camera] getBaseThirdPersonDistance
|
||||
getBaseThirdPersonDistance = function() return third_person.baseDistance end,
|
||||
--- @function [parent=#Camera] setBaseThirdPersonDistance
|
||||
setBaseThirdPersonDistance = function(v) third_person.baseDistance = v end,
|
||||
--- @function [parent=#Camera] getTargetThirdPersonDistance
|
||||
getTargetThirdPersonDistance = function() return third_person.preferredDistance end,
|
||||
|
||||
--- @function [parent=#Camera] isModeControlEnabled
|
||||
isModeControlEnabled = function() return noModeControl == 0 end,
|
||||
--- @function [parent=#Camera] disableModeControl
|
||||
disableModeControl = function() noModeControl = noModeControl + 1 end,
|
||||
--- @function [parent=#Camera] enableModeControl
|
||||
enableModeControl = function() noModeControl = math.max(0, noModeControl - 1) end,
|
||||
|
||||
--- @function [parent=#Camera] isStandingPreviewEnabled
|
||||
isStandingPreviewEnabled = function() return previewIfStandSill and noStandingPreview == 0 end,
|
||||
--- @function [parent=#Camera] disableStandingPreview
|
||||
disableStandingPreview = function() noStandingPreview = noStandingPreview + 1 end,
|
||||
--- @function [parent=#Camera] enableStandingPreview
|
||||
enableStandingPreview = function() noStandingPreview = math.max(0, noStandingPreview - 1) end,
|
||||
|
||||
--- @function [parent=#Camera] isHeadBobbingEnabled
|
||||
isHeadBobbingEnabled = function() return head_bobbing.enabled and noHeadBobbing == 0 end,
|
||||
--- @function [parent=#Camera] disableHeadBobbing
|
||||
disableHeadBobbing = function() noHeadBobbing = noHeadBobbing + 1 end,
|
||||
--- @function [parent=#Camera] enableHeadBobbing
|
||||
enableHeadBobbing = function() noHeadBobbing = math.max(0, noHeadBobbing - 1) end,
|
||||
|
||||
--- @function [parent=#Camera] isZoomEnabled
|
||||
isZoomEnabled = function() return noZoom == 0 end,
|
||||
--- @function [parent=#Camera] disableZoom
|
||||
disableZoom = function() noZoom = noZoom + 1 end,
|
||||
--- @function [parent=#Camera] enableZoom
|
||||
enableZoom = function() noZoom = math.max(0, noZoom - 1) end,
|
||||
|
||||
--- @function [parent=#Camera] isThirdPersonOffsetControlEnabled
|
||||
isThirdPersonOffsetControlEnabled = function() return third_person.noOffsetControl == 0 end,
|
||||
--- @function [parent=#Camera] disableThirdPersonOffsetControl
|
||||
disableThirdPersonOffsetControl = function() third_person.noOffsetControl = third_person.noOffsetControl + 1 end,
|
||||
--- @function [parent=#Camera] enableThirdPersonOffsetControl
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,171 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- `openmw.camera` controls camera.
|
||||
-- Can be used only by player scripts.
|
||||
-- @module camera
|
||||
-- @usage local camera = require('openmw.camera')
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- @type MODE Camera modes.
|
||||
-- @field #number Static Camera doesn't track player; player inputs doesn't affect camera; use `setStaticPosition` to move the camera.
|
||||
-- @field #number FirstPerson First person mode.
|
||||
-- @field #number ThirdPerson Third person mode; player character turns to the view direction.
|
||||
-- @field #number Vanity Similar to Preview; camera slowly moves around the player.
|
||||
-- @field #number Preview Third person mode, but player character doesn't turn to the view direction.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Camera modes.
|
||||
-- @field [parent=#camera] #MODE MODE
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Return the current @{openmw.camera#MODE}.
|
||||
-- @function [parent=#camera] getMode
|
||||
-- @return #MODE
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Return the mode the camera will switch to after the end of the current animation. Can be nil.
|
||||
-- @function [parent=#camera] getQueuedMode
|
||||
-- @return #MODE
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Change @{openmw.camera#MODE}; if the second (optional, true by default) argument is set to false, the switching can be delayed (see `getQueuedMode`).
|
||||
-- @function [parent=#camera] setMode
|
||||
-- @param #MODE mode
|
||||
-- @param #boolean force
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- If set to true then after switching from Preview to ThirdPerson the player character turns to the camera view direction. Otherwise the camera turns to the character view direction.
|
||||
-- @function [parent=#camera] allowCharacterDeferredRotation
|
||||
-- @param #boolean boolValue
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Show/hide crosshair.
|
||||
-- @function [parent=#camera] showCrosshair
|
||||
-- @param #boolean boolValue
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Current position of the tracked object (the characters head if there is no animation).
|
||||
-- @function [parent=#camera] getTrackedPosition
|
||||
-- @return openmw.util#Vector3
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Current position of the camera.
|
||||
-- @function [parent=#camera] getPosition
|
||||
-- @return openmw.util#Vector3
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Camera pitch angle (radians) without taking extraPitch into account.
|
||||
-- Full pitch is `getPitch()+getExtraPitch()`.
|
||||
-- @function [parent=#camera] getPitch
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Force the pitch angle to the given value (radians); player input on this axis is ignored in this frame.
|
||||
-- @function [parent=#camera] setPitch
|
||||
-- @param #number value
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Camera yaw angle (radians) without taking extraYaw into account.
|
||||
-- Full yaw is `getYaw()+getExtraYaw()`.
|
||||
-- @function [parent=#camera] getYaw
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Force the yaw angle to the given value (radians); player input on this axis is ignored in this frame.
|
||||
-- @function [parent=#camera] setYaw
|
||||
-- @param #number value
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Get camera roll angle (radians).
|
||||
-- @function [parent=#camera] getRoll
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Set camera roll angle (radians).
|
||||
-- @function [parent=#camera] setRoll
|
||||
-- @param #number value
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Additional summand for the pitch angle that is not affected by player input.
|
||||
-- Full pitch is `getPitch()+getExtraPitch()`.
|
||||
-- @function [parent=#camera] getExtraPitch
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Additional summand for the pitch angle; useful for camera shaking effects.
|
||||
-- Setting extra pitch doesn't block player input.
|
||||
-- Full pitch is `getPitch()+getExtraPitch()`.
|
||||
-- @function [parent=#camera] setExtraPitch
|
||||
-- @param #number value
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Additional summand for the yaw angle that is not affected by player input.
|
||||
-- Full yaw is `getYaw()+getExtraYaw()`.
|
||||
-- @function [parent=#camera] getExtraYaw
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Additional summand for the yaw angle; useful for camera shaking effects.
|
||||
-- Setting extra pitch doesn't block player input.
|
||||
-- Full yaw is `getYaw()+getExtraYaw()`.
|
||||
-- @function [parent=#camera] setExtraYaw
|
||||
-- @param #number value
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Set camera position; can be used only if camera is in Static mode.
|
||||
-- @function [parent=#camera] setStaticPosition
|
||||
-- @param openmw.util#Vector3 pos
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- The offset between the characters head and the camera in first person mode (3d vector).
|
||||
-- @function [parent=#camera] getFirstPersonOffset
|
||||
-- @return openmw.util#Vector3
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Set the offset between the characters head and the camera in first person mode (3d vector).
|
||||
-- @function [parent=#camera] setFirstPersonOffset
|
||||
-- @param openmw.util#Vector3 offset
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode.
|
||||
-- See `setFocalPreferredOffset`.
|
||||
-- @function [parent=#camera] getFocalPreferredOffset
|
||||
-- @return openmw.util#Vector2
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Set preferred offset between tracked position (see `getTrackedPosition`) and the camera focal point (the center of the screen) in third person mode.
|
||||
-- The offset is a 2d vector (X, Y) where X is horizontal (to the right from the character) and Y component is vertical (upward).
|
||||
-- The real offset can differ from the preferred one during smooth transition of if blocked by an obstacle.
|
||||
-- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition.
|
||||
-- @function [parent=#camera] setFocalPreferredOffset
|
||||
-- @param openmw.util#Vector2 offset
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- The actual distance between the camera and the character in third person mode; can differ from the preferred one if there is an obstacle.
|
||||
-- @function [parent=#camera] getThirdPersonDistance
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Set preferred distance between the camera and the character in third person mode.
|
||||
-- @function [parent=#camera] setPreferredThirdPersonDistance
|
||||
-- @param #number distance
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- The current speed coefficient of focal point (the center of the screen in third person mode) smooth transition.
|
||||
-- @function [parent=#camera] getFocalTransitionSpeed
|
||||
-- @return #number
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Set the speed coefficient of focal point (the center of the screen in third person mode) smooth transition.
|
||||
-- Smooth transition happens by default every time when the preferred offset was changed. Use `instantTransition()` to skip the current transition.
|
||||
-- @function [parent=#camera] setFocalTransitionSpeed
|
||||
-- Set the speed coefficient
|
||||
-- @param #number speed
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Make instant the current transition of camera focal point and the current deferred rotation (see `allowCharacterDeferredRotation`).
|
||||
-- @function [parent=#camera] instantTransition
|
||||
|
||||
|
||||
return nil
|
||||
|
Loading…
Reference in New Issue