You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CrySystem/ViewSystem/View.cpp

674 lines
22 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
#include "CrySystem_precompiled.h"
#include <Cry_Camera.h>
#include <HMDBus.h>
#include "View.h"
#include <AzCore/Math/MathUtils.h>
#include <IStereoRenderer.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/Entity.h>
#include <Random.h>
#include <MathConversion.h>
namespace LegacyViewSystem
{
static ICVar* pCamShakeMult = 0;
static ICVar* pHmdReferencePoint = 0;
//------------------------------------------------------------------------
CView::CView(ISystem* pSystem)
: m_pSystem(pSystem)
, m_linkedTo(0)
, m_frameAdditiveAngles(0.0f, 0.0f, 0.0f)
, m_scale(1.0f)
, m_zoomedScale(1.0f)
{
if (!pCamShakeMult)
{
pCamShakeMult = gEnv->pConsole->GetCVar("c_shakeMult");
}
if (!pHmdReferencePoint)
{
pHmdReferencePoint = gEnv->pConsole->GetCVar("hmd_reference_point");
}
}
//------------------------------------------------------------------------
CView::~CView()
{
}
//-----------------------------------------------------------------------
void CView::Release()
{
delete this;
}
//------------------------------------------------------------------------
void CView::Update(float frameTime, bool isActive)
{
//FIXME:some cameras may need to be updated always
if (!isActive)
{
return;
}
if (m_azEntity)
{
m_viewParams.SaveLast();
CCamera* pSysCam = &m_pSystem->GetViewCamera();
//process screen shaking
ProcessShaking(frameTime);
//FIXME:to let the updateView implementation use the correct shakeVector
m_viewParams.currentShakeShift = m_viewParams.rotation * m_viewParams.currentShakeShift;
m_viewParams.frameTime = frameTime;
//update view position/rotation
if (m_azEntity != nullptr)
{
auto entityTransform = m_azEntity->GetTransform();
if (entityTransform != nullptr)
{
AZ::Transform transform = entityTransform->GetWorldTM();
m_viewParams.position = AZVec3ToLYVec3(transform.GetTranslation());
m_viewParams.rotation = AZQuaternionToLYQuaternion(transform.GetRotation());
}
}
ApplyFrameAdditiveAngles(m_viewParams.rotation);
const float fNearZ = gEnv->pSystem->GetIViewSystem()->GetDefaultZNear();
//see if the view have to use a custom near clipping plane
const float nearPlane = (m_viewParams.nearplane >= CAMERA_MIN_NEAR) ? (m_viewParams.nearplane) : fNearZ;
const float farPlane = (m_viewParams.farplane > 0.f) ? m_viewParams.farplane : gEnv->p3DEngine->GetMaxViewDistance();
float fov = (m_viewParams.fov < 0.001f) ? DEFAULT_FOV : m_viewParams.fov;
// [VR] specific
// Modify FOV based on the HMD device configuration
bool isRenderingToHMD = gEnv->pRenderer ? gEnv->pRenderer->GetIStereoRenderer()->IsRenderingToHMD() : false;
if (isRenderingToHMD)
{
const AZ::VR::HMDDeviceInfo* deviceInfo = nullptr;
EBUS_EVENT_RESULT(deviceInfo, AZ::VR::HMDDeviceRequestBus, GetDeviceInfo);
if (deviceInfo)
{
//Add 12 degrees to the FOV here used for culling.
//It won't be used for rendering, just to make sure we don't cull
//anything out incorrectly.
//This value was decided based on experimentation with the HTC Vive
//and works perfectly fine for the Oculus Rift
const float fovCorrection = 12.0f;
fov = deviceInfo->fovV + DEG2RAD(fovCorrection);
}
}
m_camera.SetFrustum(pSysCam->GetViewSurfaceX(), pSysCam->GetViewSurfaceZ(), fov, nearPlane, farPlane, pSysCam->GetPixelAspectRatio());
//apply shake & set the view matrix
m_viewParams.rotation *= m_viewParams.currentShakeQuat;
m_viewParams.rotation.NormalizeSafe();
m_viewParams.position += m_viewParams.currentShakeShift;
// Blending between cameras needs to happen after Camera space rendering calculations have been applied
// so that the m_viewParams.position is in World Space again
m_viewParams.UpdateBlending(frameTime);
// [VR] specific
// Add HMD's pose tracking on top of current camera pose
// Each game-title can decide whether to keep this functionality here or (most likely)
// move it somewhere else.
Quat q = m_viewParams.rotation;
Vec3 pos = m_viewParams.position;
Vec3 p = Vec3(ZERO);
if (isRenderingToHMD)
{
//This HMD tracking state is used JUST for use in the visibility system.
//RT_SetStereoCamera in D3DRendPipeline will override this info before rendering with the absolute
//latest tracking info.
const AZ::VR::TrackingState* trackingState = nullptr;
EBUS_EVENT_RESULT(trackingState, AZ::VR::HMDDeviceRequestBus, GetTrackingState);
if (trackingState && trackingState->CheckStatusFlags(AZ::VR::HMDStatus_IsUsable))
{
p = q * AZVec3ToLYVec3(trackingState->pose.position);
q = q * AZQuaternionToLYQuaternion(trackingState->pose.orientation);
}
}
Matrix34 viewMtx(q);
viewMtx.SetTranslation(pos + p);
m_camera.SetMatrix(viewMtx);
m_camera.SetEntityRotation(m_viewParams.rotation);
m_camera.SetEntityPos(pos);
}
else
{
m_linkedTo = AZ::EntityId(0);
}
}
//-----------------------------------------------------------------------
void CView::ApplyFrameAdditiveAngles(Quat& cameraOrientation)
{
if ((m_frameAdditiveAngles.x != 0.f) || (m_frameAdditiveAngles.y != 0.f) || (m_frameAdditiveAngles.z != 0.f))
{
Ang3 cameraAngles(cameraOrientation);
cameraAngles += m_frameAdditiveAngles;
cameraOrientation.SetRotationXYZ(cameraAngles);
m_frameAdditiveAngles.Set(0.0f, 0.0f, 0.0f);
}
}
//------------------------------------------------------------------------
void CView::SetViewShake(Ang3 shakeAngle, Vec3 shakeShift, float duration, float frequency, float randomness, int shakeID, bool bFlipVec, bool bUpdateOnly, bool bGroundOnly)
{
SShakeParams params;
params.shakeAngle = shakeAngle;
params.shakeShift = shakeShift;
params.frequency = frequency;
params.randomness = randomness;
params.shakeID = shakeID;
params.bFlipVec = bFlipVec;
params.bUpdateOnly = bUpdateOnly;
params.bGroundOnly = bGroundOnly;
params.fadeInDuration = 0; //
params.fadeOutDuration = duration; // originally it was faded out from start. that is why the values are set this way here, to preserve compatibility.
params.sustainDuration = 0; //
SetViewShakeEx(params);
}
//------------------------------------------------------------------------
void CView::SetViewShakeEx(const SShakeParams& params)
{
float shakeMult = GetScale();
if (shakeMult < 0.001f)
{
return;
}
int shakes(m_shakes.size());
SShake* pSetShake(NULL);
for (int i = 0; i < shakes; ++i)
{
SShake* pShake = &m_shakes[i];
if (pShake->ID == params.shakeID)
{
pSetShake = pShake;
break;
}
}
if (!pSetShake)
{
m_shakes.push_back(SShake(params.shakeID));
pSetShake = &m_shakes.back();
}
if (pSetShake)
{
// this can be set dynamically
pSetShake->frequency = max(0.00001f, params.frequency);
// the following are set on a 'new' shake as well
if (params.bUpdateOnly == false)
{
pSetShake->amount = params.shakeAngle * shakeMult;
pSetShake->amountVector = params.shakeShift * shakeMult;
pSetShake->randomness = params.randomness;
pSetShake->doFlip = params.bFlipVec;
pSetShake->groundOnly = params.bGroundOnly;
pSetShake->isSmooth = params.isSmooth;
pSetShake->permanent = params.bPermanent;
pSetShake->fadeInDuration = params.fadeInDuration;
pSetShake->sustainDuration = params.sustainDuration;
pSetShake->fadeOutDuration = params.fadeOutDuration;
pSetShake->timeDone = 0;
pSetShake->updating = true;
pSetShake->interrupted = false;
pSetShake->goalShake = Quat(ZERO);
pSetShake->goalShakeSpeed = Quat(ZERO);
pSetShake->goalShakeVector = Vec3(ZERO);
pSetShake->goalShakeVectorSpeed = Vec3(ZERO);
pSetShake->nextShake = 0.0f;
}
}
}
//------------------------------------------------------------------------
void CView::SetScale(const float scale)
{
CRY_ASSERT_MESSAGE(scale == 1.0f || m_scale == 1.0f, "Attempting to CView::SetScale but has already been set!");
m_scale = scale;
}
void CView::SetZoomedScale(const float scale)
{
CRY_ASSERT_MESSAGE(scale == 1.0f || m_zoomedScale == 1.0f, "Attempting to CView::SetZoomedScale but has already been set!");
m_zoomedScale = scale;
}
//------------------------------------------------------------------------
const float CView::GetScale()
{
float shakeMult(pCamShakeMult->GetFVal());
return m_scale * shakeMult * m_zoomedScale;
}
//------------------------------------------------------------------------
void CView::ProcessShaking(float frameTime)
{
m_viewParams.currentShakeQuat.SetIdentity();
m_viewParams.currentShakeShift.zero();
m_viewParams.shakingRatio = 0;
m_viewParams.groundOnly = false;
int shakes(m_shakes.size());
for (int i = 0; i < shakes; ++i)
{
ProcessShake(&m_shakes[i], frameTime);
}
}
//------------------------------------------------------------------------
void CView::ProcessShake(SShake* pShake, float frameTime)
{
if (!pShake->updating)
{
return;
}
pShake->timeDone += frameTime;
if (pShake->isSmooth)
{
ProcessShakeSmooth(pShake, frameTime);
}
else
{
ProcessShakeNormal(pShake, frameTime);
}
}
//------------------------------------------------------------------------
void CView::ProcessShakeNormal(SShake* pShake, float frameTime)
{
float endSustain = pShake->fadeInDuration + pShake->sustainDuration;
float totalDuration = endSustain + pShake->fadeOutDuration;
bool finalDamping = (!pShake->permanent && pShake->timeDone > totalDuration) || (pShake->interrupted && pShake->ratio < 0.05f);
if (finalDamping)
{
ProcessShakeNormal_FinalDamping(pShake, frameTime);
}
else
{
ProcessShakeNormal_CalcRatio(pShake, frameTime, endSustain);
ProcessShakeNormal_DoShaking(pShake, frameTime);
//for the global shaking ratio keep the biggest
if (pShake->groundOnly)
{
m_viewParams.groundOnly = true;
}
m_viewParams.shakingRatio = max(m_viewParams.shakingRatio, pShake->ratio);
m_viewParams.currentShakeQuat *= pShake->shakeQuat;
m_viewParams.currentShakeShift += pShake->shakeVector;
}
}
//////////////////////////////////////////////////////////////////////////
void CView::ProcessShakeSmooth(SShake* pShake, float frameTime)
{
assert(pShake->timeDone >= 0);
float endTimeFadeIn = pShake->fadeInDuration;
float endTimeSustain = pShake->sustainDuration + endTimeFadeIn;
float totalTime = endTimeSustain + pShake->fadeOutDuration;
if (pShake->interrupted && endTimeFadeIn <= pShake->timeDone && pShake->timeDone < endTimeSustain)
{
pShake->timeDone = endTimeSustain;
}
float damping = 1.f;
if (pShake->timeDone < endTimeFadeIn)
{
damping = pShake->timeDone / endTimeFadeIn;
}
else if (endTimeSustain < pShake->timeDone && pShake->timeDone < totalTime)
{
damping = (totalTime - pShake->timeDone) / (totalTime - endTimeSustain);
}
else if (totalTime <= pShake->timeDone)
{
pShake->shakeQuat.SetIdentity();
pShake->shakeVector.zero();
pShake->ratio = 0.0f;
pShake->nextShake = 0.0f;
pShake->flip = false;
pShake->updating = false;
return;
}
ProcessShakeSmooth_DoShaking(pShake, frameTime);
if (pShake->groundOnly)
{
m_viewParams.groundOnly = true;
}
pShake->ratio = (3.f - 2.f * damping) * damping * damping; // smooth ration change
m_viewParams.shakingRatio = max(m_viewParams.shakingRatio, pShake->ratio);
m_viewParams.currentShakeQuat *= Quat::CreateSlerp(IDENTITY, pShake->shakeQuat, pShake->ratio);
m_viewParams.currentShakeShift += Vec3::CreateLerp(ZERO, pShake->shakeVector, pShake->ratio);
}
//////////////////////////////////////////////////////////////////////////
void CView::GetRandomQuat(Quat& quat, SShake* pShake)
{
quat.SetRotationXYZ(pShake->amount);
float randomAmt(pShake->randomness);
float len(fabs(pShake->amount.x) + fabs(pShake->amount.y) + fabs(pShake->amount.z));
len /= 3.f;
float r = len * randomAmt;
quat *= Quat::CreateRotationXYZ(Ang3(cry_random(-r, r), cry_random(-r, r), cry_random(-r, r)));
}
//////////////////////////////////////////////////////////////////////////
void CView::GetRandomVector(Vec3& vec, SShake* pShake)
{
vec = pShake->amountVector;
float randomAmt(pShake->randomness);
float len = fabs(pShake->amountVector.x) + fabs(pShake->amountVector.y) + fabs(pShake->amountVector.z);
len /= 3.f;
float r = len * randomAmt;
vec += Vec3(cry_random(-r, r), cry_random(-r, r), cry_random(-r, r));
}
//////////////////////////////////////////////////////////////////////////
void CView::CubeInterpolateQuat(float t, SShake* pShake)
{
Quat p0 = pShake->startShake;
Quat p1 = pShake->goalShake;
Quat v0 = pShake->startShakeSpeed * 0.5f;
Quat v1 = pShake->goalShakeSpeed * 0.5f;
pShake->shakeQuat = (((p0 * 2.f + p1 * -2.f + v0 + v1) * t
+ (p0 * -3.f + p1 * 3.f + v0 * -2.f - v1)) * t
+ (v0)) * t
+ p0;
pShake->shakeQuat.Normalize();
}
//////////////////////////////////////////////////////////////////////////
void CView::CubeInterpolateVector(float t, SShake* pShake)
{
Vec3 p0 = pShake->startShakeVector;
Vec3 p1 = pShake->goalShakeVector;
Vec3 v0 = pShake->startShakeVectorSpeed * 0.8f;
Vec3 v1 = pShake->goalShakeVectorSpeed * 0.8f;
pShake->shakeVector = (((p0 * 2.f + p1 * -2.f + v0 + v1) * t
+ (p0 * -3.f + p1 * 3.f + v0 * -2.f - v1)) * t
+ (v0)) * t
+ p0;
}
//////////////////////////////////////////////////////////////////////////
void CView::ProcessShakeSmooth_DoShaking(SShake* pShake, float frameTime)
{
if (pShake->nextShake <= 0.0f)
{
pShake->nextShake = pShake->frequency;
pShake->startShake = pShake->goalShake;
pShake->startShakeSpeed = pShake->goalShakeSpeed;
pShake->startShakeVector = pShake->goalShakeVector;
pShake->startShakeVectorSpeed = pShake->goalShakeVectorSpeed;
GetRandomQuat(pShake->goalShake, pShake);
GetRandomQuat(pShake->goalShakeSpeed, pShake);
GetRandomVector(pShake->goalShakeVector, pShake);
GetRandomVector(pShake->goalShakeVectorSpeed, pShake);
if (pShake->flip)
{
pShake->goalShake.Invert();
pShake->goalShakeSpeed.Invert();
pShake->goalShakeVector = -pShake->goalShakeVector;
pShake->goalShakeVectorSpeed = -pShake->goalShakeVectorSpeed;
}
if (pShake->doFlip)
{
pShake->flip = !pShake->flip;
}
}
pShake->nextShake -= frameTime;
float t = (pShake->frequency - pShake->nextShake) / pShake->frequency;
CubeInterpolateQuat(t, pShake);
CubeInterpolateVector(t, pShake);
}
//////////////////////////////////////////////////////////////////////////
void CView::ProcessShakeNormal_FinalDamping(SShake* pShake, float frameTime)
{
pShake->shakeQuat = Quat::CreateSlerp(pShake->shakeQuat, IDENTITY, frameTime * 5.0f);
m_viewParams.currentShakeQuat *= pShake->shakeQuat;
pShake->shakeVector = Vec3::CreateLerp(pShake->shakeVector, ZERO, frameTime * 5.0f);
m_viewParams.currentShakeShift += pShake->shakeVector;
float svlen2(pShake->shakeVector.len2());
bool quatIsIdentity(Quat::IsEquivalent(IDENTITY, pShake->shakeQuat, 0.0001f));
if (quatIsIdentity && svlen2 < 0.01f)
{
pShake->shakeQuat.SetIdentity();
pShake->shakeVector.zero();
pShake->ratio = 0.0f;
pShake->nextShake = 0.0f;
pShake->flip = false;
pShake->updating = false;
}
}
// "ratio" is the amplitude of the shaking
void CView::ProcessShakeNormal_CalcRatio(SShake* pShake, float frameTime, float endSustain)
{
const float FADEOUT_TIME_WHEN_INTERRUPTED = 0.5f;
if (pShake->interrupted)
{
pShake->ratio = max(0.f, pShake->ratio - (frameTime / FADEOUT_TIME_WHEN_INTERRUPTED)); // fadeout after interrupted
}
else
if (pShake->timeDone >= endSustain && pShake->fadeOutDuration > 0)
{
float timeFading = pShake->timeDone - endSustain;
pShake->ratio = clamp_tpl(1.f - timeFading / pShake->fadeOutDuration, 0.f, 1.f); // fadeOut
}
else
if (pShake->timeDone >= pShake->fadeInDuration)
{
pShake->ratio = 1.f; // sustain
}
else
{
pShake->ratio = min(1.f, pShake->timeDone / pShake->fadeInDuration); // fadeIn
}
if (pShake->permanent && pShake->timeDone >= pShake->fadeInDuration && !pShake->interrupted)
{
pShake->ratio = 1.f; // permanent standing
}
}
//////////////////////////////////////////////////////////////////////////
void CView::ProcessShakeNormal_DoShaking(SShake* pShake, float frameTime)
{
float t;
if (pShake->nextShake <= 0.0f)
{
//angular
pShake->goalShake.SetRotationXYZ(pShake->amount);
if (pShake->flip)
{
pShake->goalShake.Invert();
}
//translational
pShake->goalShakeVector = pShake->amountVector;
if (pShake->flip)
{
pShake->goalShakeVector = -pShake->goalShakeVector;
}
if (pShake->doFlip)
{
pShake->flip = !pShake->flip;
}
//randomize it a little
float randomAmt(pShake->randomness);
float len(fabs(pShake->amount.x) + fabs(pShake->amount.y) + fabs(pShake->amount.z));
len /= 3.0f;
float r = len * randomAmt;
pShake->goalShake *= Quat::CreateRotationXYZ(Ang3(cry_random(-r, r), cry_random(-r, r), cry_random(-r, r)));
//translational randomization
len = fabs(pShake->amountVector.x) + fabs(pShake->amountVector.y) + fabs(pShake->amountVector.z);
len /= 3.0f;
r = len * randomAmt;
pShake->goalShakeVector += Vec3(cry_random(-r, r), cry_random(-r, r), cry_random(-r, r));
//damp & bounce it in a non linear fashion
t = 1.0f - (pShake->ratio * pShake->ratio);
pShake->goalShake = Quat::CreateSlerp(pShake->goalShake, IDENTITY, t);
pShake->goalShakeVector = Vec3::CreateLerp(pShake->goalShakeVector, ZERO, t);
pShake->nextShake = pShake->frequency;
}
pShake->nextShake = max(0.0f, pShake->nextShake - frameTime);
t = min(1.0f, frameTime * (1.0f / pShake->frequency));
pShake->shakeQuat = Quat::CreateSlerp(pShake->shakeQuat, pShake->goalShake, t);
pShake->shakeQuat.Normalize();
pShake->shakeVector = Vec3::CreateLerp(pShake->shakeVector, pShake->goalShakeVector, t);
}
//------------------------------------------------------------------------
void CView::StopShake(int shakeID)
{
uint32 num = m_shakes.size();
for (uint32 i = 0; i < num; ++i)
{
if (m_shakes[i].ID == shakeID && m_shakes[i].updating)
{
m_shakes[i].interrupted = true;
}
}
}
//------------------------------------------------------------------------
void CView::ResetShaking()
{
// disable shakes
std::vector<SShake>::iterator iter = m_shakes.begin();
std::vector<SShake>::iterator iterEnd = m_shakes.end();
while (iter != iterEnd)
{
SShake& shake = *iter;
shake.updating = false;
shake.timeDone = 0;
++iter;
}
}
//------------------------------------------------------------------------
void CView::LinkTo(AZ::Entity* follow)
{
CRY_ASSERT(follow);
m_azEntity = follow;
m_linkedTo = follow->GetId();
m_viewParams.targetPos = Vec3();// This should be quickly overwritten by the camera's acutal position from its matrix
}
//------------------------------------------------------------------------
void CView::Unlink()
{
m_azEntity = nullptr;
m_linkedTo.SetInvalid();
m_viewParams.targetPos = Vec3();
}
//------------------------------------------------------------------------
void CView::SetFrameAdditiveCameraAngles(const Ang3& addFrameAngles)
{
m_frameAdditiveAngles = addFrameAngles;
}
void CView::GetMemoryUsage(ICrySizer* s) const
{
s->AddObject(this, sizeof(*this));
s->AddObject(m_shakes);
}
void CView::Serialize(TSerialize ser)
{
if (ser.IsReading())
{
ResetShaking();
}
}
void CView::PostSerialize()
{
}
//////////////////////////////////////////////////////////////////////////
void CView::UpdateAudioListener([[maybe_unused]] Matrix34 const& rMatrix)
{
}
//////////////////////////////////////////////////////////////////////////
void CView::SetActive([[maybe_unused]] bool const bActive)
{
}
} // namespace LegacyViewSystem