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/Legacy/CryCommon/Cry_Camera.h

449 lines
17 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Common Camera class implementation
#pragma once
//DOC-IGNORE-BEGIN
#include <Cry_Math.h>
#include <Cry_Geo.h>
//DOC-IGNORE-END
//////////////////////////////////////////////////////////////////////
#define CAMERA_MIN_NEAR 0.001f
#define DEFAULT_NEAR 0.2f
#define DEFAULT_FAR 1024.0f
#define DEFAULT_FOV (75.0f * gf_PI / 180.0f)
#define MIN_FOV 0.0000001f
//////////////////////////////////////////////////////////////////////
enum
{
FR_PLANE_NEAR,
FR_PLANE_FAR,
FR_PLANE_RIGHT,
FR_PLANE_LEFT,
FR_PLANE_TOP,
FR_PLANE_BOTTOM,
FRUSTUM_PLANES
};
//////////////////////////////////////////////////////////////////////
enum cull
{
CULL_EXCLUSION, // The whole object is outside of frustum.
CULL_OVERLAP, // The object & frustum overlap.
CULL_INCLUSION // The whole object is inside frustum.
};
///////////////////////////////////////////////////////////////////////////////
// Implements essential operations like calculation of a view-matrix and
// frustum-culling with simple geometric primitives (Point, Sphere, AABB, OBB).
// All calculation are based on the CryENGINE coordinate-system
//
// We are using a "right-handed" coordinate systems, where the positive X-Axis points
// to the right, the positive Y-Axis points away from the viewer and the positive
// Z-Axis points up. The following illustration shows our coordinate system.
//
// <PRE>
// z-axis
// ^
// |
// | y-axis
// | /
// | /
// |/
// +----------------> x-axis
// </PRE>
//
// This same system is also used in 3D-Studio-MAX. It is not unusual for 3D-APIs like D3D9 or
// OpenGL to use a different coordinate system. Currently in D3D9 we use a coordinate system
// in which the X-Axis points to the right, the Y-Axis points down and the Z-Axis points away
// from the viewer. To convert from the CryEngine system into D3D9 we are just doing a clockwise
// rotation of pi/2 about the X-Axis. This conversion happens in the renderer.
//
// The 6 DOFs (degrees-of-freedom) are stored in one single 3x4 matrix ("m_Matrix"). The 3
// orientation-DOFs are stored in the 3x3 part and the 3 position-DOFs are stored in the translation-
// vector. You can use the member-functions "GetMatrix()" or "SetMatrix(Matrix34(orientation,positon))"
// to change or access the 6 DOFs.
//
// There are helper-function in Cry_Math.h to create the orientation:
//
// This function builds a 3x3 orientation matrix using a view-direction and a radiant to rotate about Y-axis.
// Matrix33 orientation=Matrix33::CreateOrientation( Vec3(0,1,0), 0 );
//
// This function builds a 3x3 orientation matrix using Yaw-Pitch-Roll angles.
// Matrix33 orientation=CCamera::CreateOrientationYPR( Ang3(1.234f,0.342f,0) );
//
///////////////////////////////////////////////////////////////////////////////
class CCamera
{
public:
ILINE static Matrix33 CreateOrientationYPR(const Ang3& ypr);
ILINE static Ang3 CreateAnglesYPR(const Matrix33& m);
ILINE static Ang3 CreateAnglesYPR(const Vec3& vdir, f32 r = 0);
ILINE void SetMatrix(const Matrix34& mat) { assert(mat.IsOrthonormal()); m_Matrix = mat; UpdateFrustum(); };
ILINE const Matrix34& GetMatrix() const { return m_Matrix; };
ILINE Vec3 GetViewdir() const { return m_Matrix.GetColumn1(); };
ILINE Vec3 GetPosition() const { return m_Matrix.GetTranslation(); }
ILINE void SetPosition(const Vec3& p) { m_Matrix.SetTranslation(p); UpdateFrustum(); }
//------------------------------------------------------------
void SetFrustum(int nWidth, int nHeight, f32 FOV = DEFAULT_FOV, f32 nearplane = DEFAULT_NEAR, f32 farplane = DEFAULT_FAR, f32 fPixelAspectRatio = 1.0f);
ILINE int GetViewSurfaceZ() const { return m_Height; }
ILINE f32 GetFov() const { return m_fov; }
ILINE f32 GetPixelAspectRatio() const { return m_PixelAspectRatio; }
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------------
//-------- Frustum-Culling ----------------------------
//-----------------------------------------------------------------------------------
// AABB-frustum test
// Fast
bool IsAABBVisible_F(const ::AABB& aabb) const;
//## constructor/destructor
CCamera()
{
m_Matrix.SetIdentity();
m_asymRight = 0;
m_asymLeft = 0;
m_asymBottom = 0;
m_asymTop = 0;
SetFrustum(640, 480);
m_nPosX = m_nPosY = m_nSizeX = m_nSizeY = 0;
m_entityPos = Vec3(0, 0, 0);
}
~CCamera() {}
void SetJustActivated([[maybe_unused]] const bool justActivated) {}
void UpdateFrustum();
private:
Matrix34 m_Matrix; // world space-matrix
f32 m_fov; // vertical fov in radiants [0..1*PI[
int m_Width; // surface width-resolution
int m_Height; // surface height-resolution
f32 m_PixelAspectRatio; // accounts for aspect ratio and non-square pixels
Vec3 m_entityPos; //The position of this camera's entity (does not include HMD position or stereo offsets)
Vec3 m_edge_nlt; // this is the left/upper vertex of the near-plane
Vec3 m_edge_plt; // this is the left/upper vertex of the projection-plane
Vec3 m_edge_flt; // this is the left/upper vertex of the far-clip-plane
f32 m_asymLeft, m_asymRight, m_asymBottom, m_asymTop; // Shift to create asymmetric frustum (only used for GPU culling of tessellated objects)
f32 m_asymLeftProj, m_asymRightProj, m_asymBottomProj, m_asymTopProj;
f32 m_asymLeftFar, m_asymRightFar, m_asymBottomFar, m_asymTopFar;
//usually we update these values every frame (they depend on m_Matrix)
Vec3 m_cltp, m_crtp, m_clbp, m_crbp; //this are the 4 vertices of the projection-plane in cam-space
Vec3 m_cltn, m_crtn, m_clbn, m_crbn; //this are the 4 vertices of the near-plane in cam-space
Vec3 m_cltf, m_crtf, m_clbf, m_crbf; //this are the 4 vertices of the farclip-plane in cam-space
Plane_tpl<f32> m_fp [FRUSTUM_PLANES]; //
uint32 m_idx1[FRUSTUM_PLANES], m_idy1[FRUSTUM_PLANES], m_idz1[FRUSTUM_PLANES]; //
uint32 m_idx2[FRUSTUM_PLANES], m_idy2[FRUSTUM_PLANES], m_idz2[FRUSTUM_PLANES]; //
int m_nPosX, m_nPosY, m_nSizeX, m_nSizeY;
};
// Description
// This function builds a 3x3 orientation matrix using YPR-angles
// Rotation order for the orientation-matrix is Z-X-Y. (Zaxis=YAW / Xaxis=PITCH / Yaxis=ROLL)
//
// <PRE>
// COORDINATE-SYSTEM
//
// z-axis
// ^
// |
// | y-axis
// | /
// | /
// |/
// +---------------> x-axis
// </PRE>
//
// Example:
// Matrix33 orientation=CCamera::CreateOrientationYPR( Ang3(1,2,3) );
inline Matrix33 CCamera::CreateOrientationYPR(const Ang3& ypr)
{
f32 sz, cz;
sincos_tpl(ypr.x, &sz, &cz); //Zaxis = YAW
f32 sx, cx;
sincos_tpl(ypr.y, &sx, &cx); //Xaxis = PITCH
f32 sy, cy;
sincos_tpl(ypr.z, &sy, &cy); //Yaxis = ROLL
Matrix33 c;
c.m00 = cy * cz - sy * sz * sx;
c.m01 = -sz * cx;
c.m02 = sy * cz + cy * sz * sx;
c.m10 = cy * sz + sy * sx * cz;
c.m11 = cz * cx;
c.m12 = sy * sz - cy * sx * cz;
c.m20 = -sy * cx;
c.m21 = sx;
c.m22 = cy * cx;
return c;
}
// Description
// <PRE>
// x-YAW
// y-PITCH (negative=looking down / positive=looking up)
// z-ROLL
// </PRE>
// Note: If we are looking along the z-axis, its not possible to specify the x and z-angle
inline Ang3 CCamera::CreateAnglesYPR(const Matrix33& m)
{
assert(m.IsOrthonormal());
float l = Vec3(m.m01, m.m11, 0.0f).GetLength();
if (l > 0.0001)
{
return Ang3(atan2f(-m.m01 / l, m.m11 / l), atan2f(m.m21, l), atan2f(-m.m20 / l, m.m22 / l));
}
else
{
return Ang3(0, atan2f(m.m21, l), 0);
}
}
// Description
// <PRE>
//x-YAW
//y-PITCH (negative=looking down / positive=looking up)
//z-ROLL (its not possile to extract a "roll" from a view-vector)
// </PRE>
// Note: if we are looking along the z-axis, its not possible to specify the rotation about the z-axis
ILINE Ang3 CCamera::CreateAnglesYPR(const Vec3& vdir, f32 r)
{
assert((fabs_tpl(1 - (vdir | vdir))) < 0.001); //check if unit-vector
f32 l = Vec3(vdir.x, vdir.y, 0.0f).GetLength(); //check if not zero
if (l > 0.0001)
{
return Ang3(atan2f(-vdir.x / l, vdir.y / l), atan2f(vdir.z, l), r);
}
else
{
return Ang3(0, atan2f(vdir.z, l), r);
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
inline void CCamera::SetFrustum(int nWidth, int nHeight, f32 FOV, f32 nearplane, f32 farplane, f32 fPixelAspectRatio)
{
assert (nearplane >= CAMERA_MIN_NEAR); //check if near-plane is valid
assert (farplane >= 0.1f); //check if far-plane is valid
assert (farplane >= nearplane); //check if far-plane bigger then near-plane
assert (FOV >= MIN_FOV && FOV < gf_PI); //check if specified FOV is valid
m_fov = FOV;
m_Width = nWidth; //surface x-resolution
m_Height = nHeight; //surface z-resolution
f32 fWidth = (((f32)nWidth) / fPixelAspectRatio);
f32 fHeight = (f32) nHeight;
m_PixelAspectRatio = fPixelAspectRatio;
//-------------------------------------------------------------------------
//--- calculate the Left/Top edge of the Projection-Plane in EYE-SPACE ---
//-------------------------------------------------------------------------
f32 projLeftTopX = -fWidth * 0.5f;
f32 projLeftTopY = static_cast<f32>((1.0f / tan_tpl(m_fov * 0.5f)) * (fHeight * 0.5f));
f32 projLeftTopZ = fHeight * 0.5f;
m_edge_plt.x = projLeftTopX;
m_edge_plt.y = projLeftTopY;
m_edge_plt.z = projLeftTopZ;
float invProjLeftTopY = 1.0f / projLeftTopY;
//Apply asym shift to the camera frustum - Necessary for properly culling tessellated objects in VR
//These are applied in UpdateFrustum to the camera space frustum planes
//Can't apply asym shift to frustum edges here. That would only apply to the top left corner
//rather than the whole frustum. It would also interfere with shadow map application
//m_asym is at the near plane, we want it at the projection plane too
m_asymLeftProj = (m_asymLeft / nearplane) * projLeftTopY;
m_asymTopProj = (m_asymTop / nearplane) * projLeftTopY;
m_asymRightProj = (m_asymRight / nearplane) * projLeftTopY;
m_asymBottomProj = (m_asymBottom / nearplane) * projLeftTopY;
//Also want m_asym at the far plane
m_asymLeftFar = m_asymLeftProj * (farplane * invProjLeftTopY);
m_asymTopFar = m_asymTopProj * (farplane * invProjLeftTopY);
m_asymRightFar = m_asymRightProj * (farplane * invProjLeftTopY);
m_asymBottomFar = m_asymBottomProj * (farplane * invProjLeftTopY);
m_edge_nlt.x = nearplane * projLeftTopX * invProjLeftTopY;
m_edge_nlt.y = nearplane;
m_edge_nlt.z = nearplane * projLeftTopZ * invProjLeftTopY;
//calculate the left/upper edge of the far-plane (=not rotated)
m_edge_flt.x = projLeftTopX * (farplane * invProjLeftTopY);
m_edge_flt.y = farplane;
m_edge_flt.z = projLeftTopZ * (farplane * invProjLeftTopY);
UpdateFrustum();
}
/*!
*
* Updates all parameters required by the render-engine:
*
* 3d-view-frustum and all matrices
*
*/
inline void CCamera::UpdateFrustum()
{
//-------------------------------------------------------------------
//--- calculate frustum-edges of projection-plane in CAMERA-SPACE ---
//-------------------------------------------------------------------
Matrix33 m33 = Matrix33(m_Matrix);
m_cltp = m33 * Vec3(+m_edge_plt.x + m_asymLeftProj, +m_edge_plt.y, +m_edge_plt.z + m_asymTopProj);
m_crtp = m33 * Vec3(-m_edge_plt.x + m_asymRightProj, +m_edge_plt.y, +m_edge_plt.z + m_asymTopProj);
m_clbp = m33 * Vec3(+m_edge_plt.x + m_asymLeftProj, +m_edge_plt.y, -m_edge_plt.z + m_asymBottomProj);
m_crbp = m33 * Vec3(-m_edge_plt.x + m_asymRightProj, +m_edge_plt.y, -m_edge_plt.z + m_asymBottomProj);
m_cltn = m33 * Vec3(+m_edge_nlt.x + m_asymLeft, +m_edge_nlt.y, +m_edge_nlt.z + m_asymTop);
m_crtn = m33 * Vec3(-m_edge_nlt.x + m_asymRight, +m_edge_nlt.y, +m_edge_nlt.z + m_asymTop);
m_clbn = m33 * Vec3(+m_edge_nlt.x + m_asymLeft, +m_edge_nlt.y, -m_edge_nlt.z + m_asymBottom);
m_crbn = m33 * Vec3(-m_edge_nlt.x + m_asymRight, +m_edge_nlt.y, -m_edge_nlt.z + m_asymBottom);
m_cltf = m33 * Vec3(+m_edge_flt.x + m_asymLeftFar, +m_edge_flt.y, +m_edge_flt.z + m_asymTopFar);
m_crtf = m33 * Vec3(-m_edge_flt.x + m_asymRightFar, +m_edge_flt.y, +m_edge_flt.z + m_asymTopFar);
m_clbf = m33 * Vec3(+m_edge_flt.x + m_asymLeftFar, +m_edge_flt.y, -m_edge_flt.z + m_asymBottomFar);
m_crbf = m33 * Vec3(-m_edge_flt.x + m_asymRightFar, +m_edge_flt.y, -m_edge_flt.z + m_asymBottomFar);
//-------------------------------------------------------------------------------
//--- calculate the six frustum-planes using the frustum edges in world-space ---
//-------------------------------------------------------------------------------
m_fp[FR_PLANE_NEAR ] = Plane_tpl<f32>::CreatePlane(m_crtn + GetPosition(), m_cltn + GetPosition(), m_crbn + GetPosition());
m_fp[FR_PLANE_RIGHT ] = Plane_tpl<f32>::CreatePlane(m_crbf + GetPosition(), m_crtf + GetPosition(), GetPosition());
m_fp[FR_PLANE_LEFT ] = Plane_tpl<f32>::CreatePlane(m_cltf + GetPosition(), m_clbf + GetPosition(), GetPosition());
m_fp[FR_PLANE_TOP ] = Plane_tpl<f32>::CreatePlane(m_crtf + GetPosition(), m_cltf + GetPosition(), GetPosition());
m_fp[FR_PLANE_BOTTOM] = Plane_tpl<f32>::CreatePlane(m_clbf + GetPosition(), m_crbf + GetPosition(), GetPosition());
m_fp[FR_PLANE_FAR ] = Plane_tpl<f32>::CreatePlane(m_crtf + GetPosition(), m_crbf + GetPosition(), m_cltf + GetPosition()); //clip-plane
uint32 rh = m_Matrix.IsOrthonormalRH();
if (rh == 0)
{
m_fp[FR_PLANE_NEAR ] = -m_fp[FR_PLANE_NEAR ];
m_fp[FR_PLANE_RIGHT ] = -m_fp[FR_PLANE_RIGHT ];
m_fp[FR_PLANE_LEFT ] = -m_fp[FR_PLANE_LEFT ];
m_fp[FR_PLANE_TOP ] = -m_fp[FR_PLANE_TOP ];
m_fp[FR_PLANE_BOTTOM] = -m_fp[FR_PLANE_BOTTOM];
m_fp[FR_PLANE_FAR ] = -m_fp[FR_PLANE_FAR ]; //clip-plane
}
union f32_u
{
float floatVal;
uint32 uintVal;
};
for (int i = 0; i < FRUSTUM_PLANES; i++)
{
f32_u ux;
ux.floatVal = m_fp[i].n.x;
f32_u uy;
uy.floatVal = m_fp[i].n.y;
f32_u uz;
uz.floatVal = m_fp[i].n.z;
uint32 bitX = ux.uintVal >> 31;
uint32 bitY = uy.uintVal >> 31;
uint32 bitZ = uz.uintVal >> 31;
m_idx1[i] = bitX * 3 + 0;
m_idx2[i] = (1 - bitX) * 3 + 0;
m_idy1[i] = bitY * 3 + 1;
m_idy2[i] = (1 - bitY) * 3 + 1;
m_idz1[i] = bitZ * 3 + 2;
m_idz2[i] = (1 - bitZ) * 3 + 2;
}
}
// Description
// Simple approach to check if an AABB and the camera-frustum overlap. The AABB
// is assumed to be in world-space. This is a very fast method, just one single
// dot-product is necessary to check an AABB against a plane. Actually there
// is no significant speed-different between culling a sphere or an AABB.
//
// Example
// bool InOut=camera.IsAABBVisible_F(aabb);
//
// return values
// CULL_EXCLUSION = AABB outside of frustum (very fast rejection-test)
// CULL_OVERLAP = AABB either intersects the borders of the frustum or is totally inside
inline bool CCamera::IsAABBVisible_F(const AABB& aabb) const
{
const f32* p = &aabb.min.x;
uint32 x, y, z;
x = m_idx1[0];
y = m_idy1[0];
z = m_idz1[0];
if ((m_fp[0] | Vec3(p[x], p[y], p[z])) > 0)
{
return CULL_EXCLUSION;
}
x = m_idx1[1];
y = m_idy1[1];
z = m_idz1[1];
if ((m_fp[1] | Vec3(p[x], p[y], p[z])) > 0)
{
return CULL_EXCLUSION;
}
x = m_idx1[2];
y = m_idy1[2];
z = m_idz1[2];
if ((m_fp[2] | Vec3(p[x], p[y], p[z])) > 0)
{
return CULL_EXCLUSION;
}
x = m_idx1[3];
y = m_idy1[3];
z = m_idz1[3];
if ((m_fp[3] | Vec3(p[x], p[y], p[z])) > 0)
{
return CULL_EXCLUSION;
}
x = m_idx1[4];
y = m_idy1[4];
z = m_idz1[4];
if ((m_fp[4] | Vec3(p[x], p[y], p[z])) > 0)
{
return CULL_EXCLUSION;
}
x = m_idx1[5];
y = m_idy1[5];
z = m_idz1[5];
if ((m_fp[5] | Vec3(p[x], p[y], p[z])) > 0)
{
return CULL_EXCLUSION;
}
return CULL_OVERLAP;
}