Index: ps/trunk/source/graphics/Camera.cpp =================================================================== --- ps/trunk/source/graphics/Camera.cpp (revision 22546) +++ ps/trunk/source/graphics/Camera.cpp (revision 22547) @@ -1,424 +1,420 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #include "precompiled.h" #include "Camera.h" #include "graphics/HFTracer.h" #include "graphics/Terrain.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" #include "ps/Game.h" #include "ps/World.h" #include "renderer/Renderer.h" #include "renderer/WaterManager.h" CCamera::CCamera() { // set viewport to something anything should handle, but should be initialised // to window size before use m_ViewPort.m_X = 0; m_ViewPort.m_Y = 0; m_ViewPort.m_Width = 800; m_ViewPort.m_Height = 600; } CCamera::~CCamera() = default; void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov) { m_NearPlane = nearp; m_FarPlane = farp; m_FOV = fov; - const float aspect = static_cast(m_ViewPort.m_Width) / static_cast(m_ViewPort.m_Height); - m_ProjMat.SetPerspective(m_FOV, aspect, m_NearPlane, m_FarPlane); + m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane); } void CCamera::SetPerspectiveProjectionTile(int tiles, int tile_x, int tile_y) { - const float aspect = static_cast(m_ViewPort.m_Width) / static_cast(m_ViewPort.m_Height); - - m_ProjMat.SetPerspectiveTile(m_FOV, aspect, m_NearPlane, m_FarPlane, tiles, tile_x, tile_y); + m_ProjMat.SetPerspectiveTile(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane, tiles, tile_x, tile_y); } // Updates the frustum planes. Should be called // everytime the view or projection matrices are // altered. void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor) { CMatrix3D MatFinal; CMatrix3D MatView; m_Orientation.GetInverse(MatView); MatFinal = m_ProjMat * MatView; m_ViewFrustum.SetNumPlanes(6); // get the RIGHT plane m_ViewFrustum.m_aPlanes[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11; m_ViewFrustum.m_aPlanes[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12; m_ViewFrustum.m_aPlanes[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13; m_ViewFrustum.m_aPlanes[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14; // get the LEFT plane m_ViewFrustum.m_aPlanes[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11; m_ViewFrustum.m_aPlanes[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12; m_ViewFrustum.m_aPlanes[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13; m_ViewFrustum.m_aPlanes[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14; // get the BOTTOM plane m_ViewFrustum.m_aPlanes[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21; m_ViewFrustum.m_aPlanes[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22; m_ViewFrustum.m_aPlanes[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23; m_ViewFrustum.m_aPlanes[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24; // get the TOP plane m_ViewFrustum.m_aPlanes[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21; m_ViewFrustum.m_aPlanes[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22; m_ViewFrustum.m_aPlanes[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23; m_ViewFrustum.m_aPlanes[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24; // get the FAR plane m_ViewFrustum.m_aPlanes[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31; m_ViewFrustum.m_aPlanes[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32; m_ViewFrustum.m_aPlanes[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33; m_ViewFrustum.m_aPlanes[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34; // get the NEAR plane m_ViewFrustum.m_aPlanes[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31; m_ViewFrustum.m_aPlanes[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32; m_ViewFrustum.m_aPlanes[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33; m_ViewFrustum.m_aPlanes[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34; for (size_t i = 0; i < 6; ++i) m_ViewFrustum.m_aPlanes[i].Normalize(); } void CCamera::ClipFrustum(const CPlane& clipPlane) { CPlane normClipPlane = clipPlane; normClipPlane.Normalize(); m_ViewFrustum.AddPlane(normClipPlane); } void CCamera::SetViewPort(const SViewPort& viewport) { m_ViewPort.m_X = viewport.m_X; m_ViewPort.m_Y = viewport.m_Y; m_ViewPort.m_Width = viewport.m_Width; m_ViewPort.m_Height = viewport.m_Height; } +float CCamera::GetAspectRatio() const +{ + return static_cast(m_ViewPort.m_Width) / static_cast(m_ViewPort.m_Height); +} -/////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// GetCameraPlanePoints: return four points in camera space at given distance from camera -void CCamera::GetCameraPlanePoints(float dist, CVector3D pts[4]) const -{ - float aspect = float(m_ViewPort.m_Width)/float(m_ViewPort.m_Height); - float x = dist*aspect*tanf(m_FOV*0.5f); - float y = dist*tanf(m_FOV*0.5f); - - pts[0].X = -x; - pts[0].Y = -y; - pts[0].Z = dist; - pts[1].X = x; - pts[1].Y = -y; - pts[1].Z = dist; - pts[2].X = x; - pts[2].Y = y; - pts[2].Z = dist; - pts[3].X = -x; - pts[3].Y = y; - pts[3].Z = dist; +void CCamera::GetViewQuad(float dist, Quad& quad) const +{ + const float y = dist * tanf(m_FOV * 0.5f); + const float x = y * GetAspectRatio(); + + quad[0].X = -x; + quad[0].Y = -y; + quad[0].Z = dist; + quad[1].X = x; + quad[1].Y = -y; + quad[1].Z = dist; + quad[2].X = x; + quad[2].Y = y; + quad[2].Z = dist; + quad[3].X = -x; + quad[3].Y = y; + quad[3].Z = dist; } void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const { - CVector3D cPts[4]; - GetCameraPlanePoints(m_FarPlane, cPts); + // Coordinates relative to the camera plane. + const float dx = static_cast(px) / g_Renderer.GetWidth(); + const float dy = 1.0f - static_cast(py) / g_Renderer.GetHeight(); + + Quad points; + GetViewQuad(m_FarPlane, points); + + // Transform from camera space to world space. + for (CVector3D& point : points) + point = m_Orientation.Transform(point); + + // Get world space position of mouse point at the far clipping plane. + CVector3D basisX = points[1] - points[0]; + CVector3D basisY = points[3] - points[0]; + CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy); - // transform to world space - CVector3D wPts[4]; - for (int i = 0; i < 4; i++) - wPts[i] = m_Orientation.Transform(cPts[i]); - - // get world space position of mouse point - float dx = (float)px / (float)g_Renderer.GetWidth(); - float dz = 1 - (float)py / (float)g_Renderer.GetHeight(); - - CVector3D vdx = wPts[1] - wPts[0]; - CVector3D vdz = wPts[3] - wPts[0]; - CVector3D pt = wPts[0] + (vdx * dx) + (vdz * dz); - - // copy origin origin = m_Orientation.GetTranslation(); - // build direction - dir = pt - origin; + + // Build direction for the camera origin to the target point. + dir = targetPoint - origin; dir.Normalize(); } void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const { CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse(); CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f)); x = screenspace.X / screenspace.W; y = screenspace.Y / screenspace.W; x = (x + 1) * 0.5f * g_Renderer.GetWidth(); y = (1 - y) * 0.5f * g_Renderer.GetHeight(); } CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const { CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; BuildCameraRay(px, py, origin, dir); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); if (!aboveWater) { if (gotTerrain) return terrainPoint; // Off the edge of the world? // Work out where it /would/ hit, if the map were extended out to infinity with average height. return GetWorldCoordinates(px, py, 50.0f); } CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint ); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { waterPoint.X = clamp(waterPoint.X, 0.f, (float)((mapSize-1)*TERRAIN_TILE_SIZE)); waterPoint.Z = clamp(waterPoint.Z, 0.f, (float)((mapSize-1)*TERRAIN_TILE_SIZE)); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const { CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h CVector3D origin, dir, delta, currentTarget; BuildCameraRay(px, py, origin, dir); if (plane.FindRayIntersection(origin, dir, ¤tTarget)) return currentTarget; // No intersection with the infinite plane - nothing sensible can be returned, // so just choose an arbitrary point on the plane return CVector3D(0.f, h, 0.f); } CVector3D CCamera::GetFocus() const { // Basically the same as GetWorldCoordinates CHFTracer tracer(g_Game->GetWorld()->GetTerrain()); int x, z; CVector3D origin, dir, delta, terrainPoint, waterPoint; origin = m_Orientation.GetTranslation(); dir = m_Orientation.GetIn(); bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint); CPlane plane; plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint ); // Clamp the water intersection to within the map's bounds, so that // we'll always return a valid position on the map ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide(); if (gotWater) { waterPoint.X = clamp(waterPoint.X, 0.f, (float)((mapSize-1)*TERRAIN_TILE_SIZE)); waterPoint.Z = clamp(waterPoint.Z, 0.f, (float)((mapSize-1)*TERRAIN_TILE_SIZE)); } if (gotTerrain) { if (gotWater) { // Intersecting both heightmap and water plane; choose the closest of those if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared()) return terrainPoint; else return waterPoint; } else { // Intersecting heightmap but parallel to water plane return terrainPoint; } } else { if (gotWater) { // Only intersecting water plane return waterPoint; } else { // Not intersecting terrain or water; just return 0,0,0. return CVector3D(0.f, 0.f, 0.f); } } } void CCamera::LookAt(const CVector3D& camera, const CVector3D& target, const CVector3D& up) { CVector3D delta = target - camera; LookAlong(camera, delta, up); } void CCamera::LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up) { orientation.Normalize(); up.Normalize(); CVector3D s = orientation.Cross(up); m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X; m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y; m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z; m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f; } - -/////////////////////////////////////////////////////////////////////////////////// // Render the camera's frustum void CCamera::Render(int intermediates) const { #if CONFIG2_GLES #warning TODO: implement camera frustum for GLES #else - CVector3D nearPoints[4]; - CVector3D farPoints[4]; + Quad nearPoints; + Quad farPoints; - GetCameraPlanePoints(m_NearPlane, nearPoints); - GetCameraPlanePoints(m_FarPlane, farPoints); + GetViewQuad(m_NearPlane, nearPoints); + GetViewQuad(m_FarPlane, farPoints); for(int i = 0; i < 4; i++) { nearPoints[i] = m_Orientation.Transform(nearPoints[i]); farPoints[i] = m_Orientation.Transform(farPoints[i]); } // near plane glBegin(GL_POLYGON); glVertex3fv(&nearPoints[0].X); glVertex3fv(&nearPoints[1].X); glVertex3fv(&nearPoints[2].X); glVertex3fv(&nearPoints[3].X); glEnd(); // far plane glBegin(GL_POLYGON); glVertex3fv(&farPoints[0].X); glVertex3fv(&farPoints[1].X); glVertex3fv(&farPoints[2].X); glVertex3fv(&farPoints[3].X); glEnd(); // connection lines glBegin(GL_QUAD_STRIP); glVertex3fv(&nearPoints[0].X); glVertex3fv(&farPoints[0].X); glVertex3fv(&nearPoints[1].X); glVertex3fv(&farPoints[1].X); glVertex3fv(&nearPoints[2].X); glVertex3fv(&farPoints[2].X); glVertex3fv(&nearPoints[3].X); glVertex3fv(&farPoints[3].X); glVertex3fv(&nearPoints[0].X); glVertex3fv(&farPoints[0].X); glEnd(); // intermediate planes CVector3D intermediatePoints[4]; for(int i = 0; i < intermediates; ++i) { float t = (i+1.0)/(intermediates+1.0); for(int j = 0; j < 4; ++j) intermediatePoints[j] = nearPoints[j]*t + farPoints[j]*(1.0-t); glBegin(GL_POLYGON); glVertex3fv(&intermediatePoints[0].X); glVertex3fv(&intermediatePoints[1].X); glVertex3fv(&intermediatePoints[2].X); glVertex3fv(&intermediatePoints[3].X); glEnd(); } #endif } Index: ps/trunk/source/graphics/Camera.h =================================================================== --- ps/trunk/source/graphics/Camera.h (revision 22546) +++ ps/trunk/source/graphics/Camera.h (revision 22547) @@ -1,122 +1,128 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * CCamera holds a view and a projection matrix. It also has a frustum * which can be used to cull objects for rendering. */ #ifndef INCLUDED_CAMERA #define INCLUDED_CAMERA #include "graphics/Frustum.h" #include "maths/BoundingBoxAligned.h" #include "maths/Matrix3D.h" +#include + // view port struct SViewPort { int m_X; int m_Y; int m_Width; int m_Height; }; class CCamera { public: + // Represents camera viewport or frustum side in 3D space. + using Quad = std::array; + CCamera(); ~CCamera(); CMatrix3D& GetProjection() { return m_ProjMat; } const CMatrix3D& GetProjection() const { return m_ProjMat; } CMatrix3D GetViewProjection() const { return m_ProjMat * m_Orientation.GetInverse(); } void SetProjection(const CMatrix3D& matrix) { m_ProjMat = matrix; } void SetPerspectiveProjection(float nearp, float farp, float fov); void SetPerspectiveProjectionTile(int tiles, int tile_x, int tile_y); CMatrix3D& GetOrientation() { return m_Orientation; } const CMatrix3D& GetOrientation() const { return m_Orientation; } // Updates the frustum planes. Should be called // everytime the view or projection matrices are // altered. void UpdateFrustum(const CBoundingBoxAligned& scissor = CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, -1.0f), CVector3D(1.0f, 1.0f, 1.0f))); void ClipFrustum(const CPlane& clipPlane); const CFrustum& GetFrustum() const { return m_ViewFrustum; } void SetViewPort(const SViewPort& viewport); const SViewPort& GetViewPort() const { return m_ViewPort; } + float GetAspectRatio() const; float GetNearPlane() const { return m_NearPlane; } float GetFarPlane() const { return m_FarPlane; } float GetFOV() const { return m_FOV; } - // Returns four points in camera space at given distance from camera - void GetCameraPlanePoints(float dist, CVector3D pts[4]) const; + // Returns a quad of view in camera space at given distance from camera. + void GetViewQuad(float dist, Quad& quad) const; // Build a ray passing through the screen coordinate (px, py) and the camera ///////////////////////////////////////////////////////////////////////////////////////// // BuildCameraRay: calculate origin and ray direction of a ray through // the pixel (px,py) on the screen void BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const; // General helpers that seem to fit here // Get the screen-space coordinates corresponding to a given world-space position void GetScreenCoordinates(const CVector3D& world, float& x, float& y) const; // Get the point on the terrain corresponding to pixel (px,py) (or the mouse coordinates) // The aboveWater parameter determines whether we want to stop at the water plane or also get underwater points CVector3D GetWorldCoordinates(int px, int py, bool aboveWater=false) const; // Get the point on the plane at height h corresponding to pixel (px,py) CVector3D GetWorldCoordinates(int px, int py, float h) const; // Get the point on the terrain (or water plane) the camera is pointing towards CVector3D GetFocus() const; // Build an orientation matrix from camera position, camera focus point, and up-vector void LookAt(const CVector3D& camera, const CVector3D& orientation, const CVector3D& up); // Build an orientation matrix from camera position, camera orientation, and up-vector void LookAlong(const CVector3D& camera, CVector3D focus, CVector3D up); /** * Render: Renders the camera's frustum in world space. * The caller should set the color using glColorXy before calling Render. * * @param intermediates determines how many intermediate distance planes should * be hinted at between the near and far planes */ void Render(int intermediates = 0) const; public: // This is the orientation matrix. The inverse of this // is the view matrix CMatrix3D m_Orientation; private: CMatrix3D m_ProjMat; float m_NearPlane; float m_FarPlane; float m_FOV; SViewPort m_ViewPort; CFrustum m_ViewFrustum; }; #endif // INCLUDED_CAMERA Index: ps/trunk/source/graphics/tests/test_Camera.h =================================================================== --- ps/trunk/source/graphics/tests/test_Camera.h (revision 22546) +++ ps/trunk/source/graphics/tests/test_Camera.h (revision 22547) @@ -1,126 +1,163 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "lib/self_test.h" #include "graphics/Camera.h" #include "maths/MathUtil.h" #include "maths/Vector3D.h" #include #include class TestCamera : public CxxTest::TestSuite { public: void test_frustum_perspective() { SViewPort viewPort; viewPort.m_X = 0; viewPort.m_Y = 0; viewPort.m_Width = 512; viewPort.m_Height = 512; CCamera camera; camera.SetViewPort(viewPort); camera.LookAlong( CVector3D(0.0f, 0.0f, 0.0f), CVector3D(0.0f, 0.0f, 1.0f), CVector3D(0.0f, 1.0f, 0.0f) ); camera.SetPerspectiveProjection(1.0f, 101.0f, DEGTORAD(90.0f)); camera.UpdateFrustum(); const float sqrt2 = sqrtf(2.0f) / 2.0f; const std::vector expectedPlanes = { CVector4D(sqrt2, 0.0f, sqrt2, 0.0f), CVector4D(-sqrt2, 0.0f, sqrt2, 0.0f), CVector4D(0.0f, sqrt2, sqrt2, 0.0f), CVector4D(0.0f, -sqrt2, sqrt2, 0.0f), CVector4D(0.0f, 0.0f, -1.0f, 101.0f), CVector4D(0.0f, 0.0f, 1.0f, -1.0f), }; CheckFrustumPlanes(camera.GetFrustum(), expectedPlanes); } void test_frustum_ortho() { SViewPort viewPort; viewPort.m_X = 0; viewPort.m_Y = 0; viewPort.m_Width = 512; viewPort.m_Height = 512; CCamera camera; camera.SetViewPort(viewPort); camera.LookAlong( CVector3D(0.0f, 0.0f, 0.0f), CVector3D(0.0f, 0.0f, 1.0f), CVector3D(0.0f, 1.0f, 0.0f) - ); + ); CMatrix3D projection; projection.SetOrtho(-10.0f, 10.0f, -10.0f, 10.0f, -10.0f, 10.0f); camera.SetProjection(projection); camera.UpdateFrustum(); const std::vector expectedPlanes = { CVector4D(1.0f, 0.0f, 0.0f, 10.0f), CVector4D(-1.0f, 0.0f, 0.0f, 10.0f), CVector4D(0.0f, 1.0f, 0.0f, 10.0f), CVector4D(0.0f, -1.0f, 0.0f, 10.0f), CVector4D(0.0f, 0.0f, 1.0f, 10.0f), CVector4D(0.0f, 0.0f, -1.0f, 10.0f) }; CheckFrustumPlanes(camera.GetFrustum(), expectedPlanes); } // Order of planes is unknown. So use interactive checker. void CheckFrustumPlanes(const CFrustum& frustum, const std::vector& expectedPlanes) { TS_ASSERT_EQUALS(frustum.GetNumPlanes(), expectedPlanes.size()); std::set indices; for (size_t i = 0; i < expectedPlanes.size(); ++i) indices.insert(i); for (size_t i = 0; i < frustum.GetNumPlanes(); ++i) { bool found = false; for (size_t j : indices) { if (EqualPlanes(frustum[i], expectedPlanes[j])) { found = true; indices.erase(j); break; } } if (!found) TS_FAIL(frustum[i]); } } bool EqualPlanes(const CPlane& p1, const CPlane& p2) const { const float EPS = 1e-3f; if (std::fabs(p1.m_Dist - p2.m_Dist) >= EPS) return false; return std::fabs(p1.m_Norm.X - p2.m_Norm.X) < EPS && std::fabs(p1.m_Norm.Y - p2.m_Norm.Y) < EPS && std::fabs(p1.m_Norm.Z - p2.m_Norm.Z) < EPS; } + + void test_persepctive_plane_points() + { + SViewPort viewPort; + viewPort.m_X = 0; + viewPort.m_Y = 0; + viewPort.m_Width = 512; + viewPort.m_Height = 512; + + CCamera camera; + camera.SetViewPort(viewPort); + camera.LookAlong( + CVector3D(0.0f, 0.0f, 0.0f), + CVector3D(0.0f, 0.0f, 1.0f), + CVector3D(0.0f, 1.0f, 0.0f) + ); + camera.m_Orientation.SetTranslation(CVector3D(1.0f, 2.0f, 3.0f)); + camera.SetPerspectiveProjection(1.0f, 101.0f, DEGTORAD(90.0f)); + + CCamera::Quad quad; + + // Zero distance point is the origin of all camera rays, + // so all plane points should be stay there. + camera.GetViewQuad(0.0f, quad); + for (const CVector3D& point : quad) + TS_ASSERT_EQUALS(point, CVector3D(0.0f, 0.0f, 0.0f)); + + // Points lying on the far plane. + CCamera::Quad expectedFarQuad = { + CVector3D(-101.0f, -101.0f, 101.0f), + CVector3D(101.0f, -101.0f, 101.0f), + CVector3D(101.0f, 101.0f, 101.0f), + CVector3D(-101.0f, 101.0f, 101.0f) + }; + camera.GetViewQuad(camera.GetFarPlane(), quad); + TS_ASSERT_EQUALS(quad, expectedFarQuad); + } };