Index: ps/trunk/source/graphics/Camera.cpp =================================================================== --- ps/trunk/source/graphics/Camera.cpp +++ ps/trunk/source/graphics/Camera.cpp @@ -377,38 +377,42 @@ CBoundingBoxAligned CCamera::GetBoundsInViewPort(const CBoundingBoxAligned& boundigBox) const { - CVector4D v1 = GetViewProjection().Transform(CVector4D(boundigBox[0].X, boundigBox[1].Y, boundigBox[0].Z, 1.0f)); - CVector4D v2 = GetViewProjection().Transform(CVector4D(boundigBox[1].X, boundigBox[1].Y, boundigBox[0].Z, 1.0f)); - CVector4D v3 = GetViewProjection().Transform(CVector4D(boundigBox[0].X, boundigBox[1].Y, boundigBox[1].Z, 1.0f)); - CVector4D v4 = GetViewProjection().Transform(CVector4D(boundigBox[1].X, boundigBox[1].Y, boundigBox[1].Z, 1.0f)); + const CVector3D cameraPosition = GetOrientation().GetTranslation(); + if (boundigBox.IsPointInside(cameraPosition)) + return CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, 0.0f), CVector3D(1.0f, 1.0f, 0.0f)); + + const CMatrix3D viewProjection = GetViewProjection(); CBoundingBoxAligned viewPortBounds; - #define ADDBOUND(v1, v2, v3, v4) \ - if (v1.Z >= -v1.W) \ - viewPortBounds += CVector3D(v1.X, v1.Y, v1.Z) * (1.0f / v1.W); \ - else \ - { \ - float t = v1.Z + v1.W; \ - if (v2.Z > -v2.W) \ - { \ - CVector4D c2 = v1 + (v2 - v1) * (t / (t - (v2.Z + v2.W))); \ - viewPortBounds += CVector3D(c2.X, c2.Y, c2.Z) * (1.0f / c2.W); \ - } \ - if (v3.Z > -v3.W) \ - { \ - CVector4D c3 = v1 + (v3 - v1) * (t / (t - (v3.Z + v3.W))); \ - viewPortBounds += CVector3D(c3.X, c3.Y, c3.Z) * (1.0f / c3.W); \ - } \ - if (v4.Z > -v4.W) \ - { \ - CVector4D c4 = v1 + (v4 - v1) * (t / (t - (v4.Z + v4.W))); \ - viewPortBounds += CVector3D(c4.X, c4.Y, c4.Z) * (1.0f / c4.W); \ - } \ +#define ADD_VISIBLE_POINT_TO_VIEWBOUNDS(POSITION) STMT( \ + CVector4D v = viewProjection.Transform(CVector4D((POSITION).X, (POSITION).Y, (POSITION).Z, 1.0f)); \ + if (v.W != 0.0f) \ + viewPortBounds += CVector3D(v.X, v.Y, v.Z) * (1.0f / v.W); ) + + std::array worldPositions; + std::array isBehindNearPlane; + const CVector3D lookDirection = GetOrientation().GetIn(); + // Check corners. + for (size_t idx = 0; idx < 8; ++idx) + { + worldPositions[idx] = CVector3D(boundigBox[(idx >> 0) & 0x1].X, boundigBox[(idx >> 1) & 0x1].Y, boundigBox[(idx >> 2) & 0x1].Z); + isBehindNearPlane[idx] = lookDirection.Dot(worldPositions[idx]) < lookDirection.Dot(cameraPosition) + GetNearPlane(); + if (!isBehindNearPlane[idx]) + ADD_VISIBLE_POINT_TO_VIEWBOUNDS(worldPositions[idx]); + } + // Check edges for intersections with the near plane. + for (size_t idxBegin = 0; idxBegin < 8; ++idxBegin) + for (size_t nextComponent = 0; nextComponent < 3; ++nextComponent) + { + const size_t idxEnd = idxBegin | (1u << nextComponent); + if (idxBegin == idxEnd || isBehindNearPlane[idxBegin] == isBehindNearPlane[idxEnd]) + continue; + CVector3D intersection; + // Intersect the segment with the near plane. + if (!m_ViewFrustum[5].FindLineSegIntersection(worldPositions[idxBegin], worldPositions[idxEnd], &intersection)) + continue; + ADD_VISIBLE_POINT_TO_VIEWBOUNDS(intersection); } - ADDBOUND(v1, v2, v3, v4); - ADDBOUND(v2, v1, v3, v4); - ADDBOUND(v3, v1, v2, v4); - ADDBOUND(v4, v1, v2, v3); - #undef ADDBOUND +#undef ADD_VISIBLE_POINT_TO_VIEWBOUNDS if (viewPortBounds[0].X >= 1.0f || viewPortBounds[1].X <= -1.0f || viewPortBounds[0].Y >= 1.0f || viewPortBounds[1].Y <= -1.0f) return CBoundingBoxAligned{}; return viewPortBounds; Index: ps/trunk/source/graphics/tests/test_Camera.h =================================================================== --- ps/trunk/source/graphics/tests/test_Camera.h +++ ps/trunk/source/graphics/tests/test_Camera.h @@ -19,7 +19,9 @@ #include "graphics/Camera.h" #include "maths/MathUtil.h" +#include "maths/Vector2D.h" #include "maths/Vector3D.h" +#include "maths/Vector4D.h" #include #include @@ -341,4 +343,82 @@ CompareVectors(dir, expectedDir, EPS); } } + + void CompareBoundingBoxes(const CBoundingBoxAligned& bb1, const CBoundingBoxAligned& bb2) + { + constexpr float EPS = 1e-3f; + CompareVectors(bb1[0], bb2[0], EPS); + CompareVectors(bb1[1], bb2[1], EPS); + } + + void test_viewport_bounds_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(); + + struct TestCase + { + CBoundingBoxAligned worldSpaceBoundingBox; + CBoundingBoxAligned expectedViewPortBoundingBox; + }; + const TestCase testCases[] = { + // Box is in front of the camera. + { + {{-1.0f, 0.0f, 5.0f}, {1.0f, 0.0f, 7.0f}}, + {{-0.2f, 0.0f, 0.616f}, {0.2f, 0.0f, 0.731429f}} + }, + // Box is out of the camera view. + { + {{-10.0f, -1.0f, 5.0f}, {-8.0f, 1.0f, 7.0f}}, + {} + }, + { + {{-1.0f, -10.0f, 5.0f}, {1.0f, -8.0f, 7.0f}}, + {} + }, + // Box is in the bottom part of the camera view. + { + {{-1.0f, -3.0f, 5.0f}, {1.0f, -3.0f, 7.0f}}, + {{-0.2f, -0.6f, 0.616f}, {0.2f, -0.428571f, 0.731429f}} + }, + { + {{-1.0f, -3.0f, 0.0f}, {1.0f, -3.0f, 7.0f}}, + {{-1.0f, -3.0f, -1.0f}, {1.0f, -0.428571f, 0.731429f}} + }, + { + {{-1.0f, -3.0f, -7.0f}, {1.0f, -3.0f, 7.0f}}, + {{-1.0f, -3.0f, -1.0f}, {1.0f, -0.428571f, 0.731429f}} + }, + }; + + for (const TestCase& testCase : testCases) + { + TS_ASSERT(testCase.worldSpaceBoundingBox[0].X <= testCase.worldSpaceBoundingBox[1].X); + TS_ASSERT(testCase.worldSpaceBoundingBox[0].Y <= testCase.worldSpaceBoundingBox[1].Y); + TS_ASSERT(testCase.worldSpaceBoundingBox[0].Z <= testCase.worldSpaceBoundingBox[1].Z); + + const CBoundingBoxAligned result = + camera.GetBoundsInViewPort(testCase.worldSpaceBoundingBox); + if (testCase.expectedViewPortBoundingBox.IsEmpty()) + { + TS_ASSERT(result.IsEmpty()); + } + else + CompareBoundingBoxes(result, testCase.expectedViewPortBoundingBox); + } + } + }; Index: ps/trunk/source/maths/BoundingBoxAligned.h =================================================================== --- ps/trunk/source/maths/BoundingBoxAligned.h +++ ps/trunk/source/maths/BoundingBoxAligned.h @@ -107,6 +107,8 @@ */ bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const; + bool IsPointInside(const CVector3D& point) const; + // return the volume of this bounding box float GetVolume() const { Index: ps/trunk/source/maths/BoundingBoxAligned.cpp =================================================================== --- ps/trunk/source/maths/BoundingBoxAligned.cpp +++ ps/trunk/source/maths/BoundingBoxAligned.cpp @@ -263,3 +263,11 @@ m_Data[0] -= CVector3D(amount, amount, amount); m_Data[1] += CVector3D(amount, amount, amount); } + +bool CBoundingBoxAligned::IsPointInside(const CVector3D& point) const +{ + return + m_Data[0].X <= point.X && point.X <= m_Data[1].X && + m_Data[0].Y <= point.Y && point.Y <= m_Data[1].Y && + m_Data[0].Z <= point.Z && point.Z <= m_Data[1].Z; +} Index: ps/trunk/source/maths/tests/test_Bound.h =================================================================== --- ps/trunk/source/maths/tests/test_Bound.h +++ ps/trunk/source/maths/tests/test_Bound.h @@ -208,4 +208,34 @@ TS_ASSERT(!isnan(result.m_Basis[2].X) && !isnan(result.m_Basis[2].Y) && !isnan(result.m_Basis[2].Z)); } + void test_point_visibility() + { + const CBoundingBoxAligned bb(CVector3D(1.0f, -10.0f, 3.0f), CVector3D(3.0f, -8.0f, 5.0f)); + + TS_ASSERT(!bb.IsPointInside(CVector3D(0.0f, 0.0f, 0.0f))); + TS_ASSERT(bb.IsPointInside(bb[0])); + TS_ASSERT(bb.IsPointInside(bb[1])); + + CVector3D center; + bb.GetCenter(center); + TS_ASSERT(bb.IsPointInside(center)); + + for (int offsetX = -1; offsetX <= 1; ++offsetX) + for (int offsetY = -1; offsetY <= 1; ++offsetY) + for (int offsetZ = -1; offsetZ <= 1; ++offsetZ) + { + TS_ASSERT(bb.IsPointInside( + center + CVector3D(offsetX, offsetY, offsetZ) * 0.9f)); + const bool isInside = bb.IsPointInside( + center + CVector3D(offsetX, offsetY, offsetZ) * 1.1f); + if (offsetX == 0 && offsetY == 0 && offsetZ == 0) + { + TS_ASSERT(isInside); + } + else + { + TS_ASSERT(!isInside); + } + } + } }; Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp +++ ps/trunk/source/renderer/Renderer.cpp @@ -989,6 +989,8 @@ CCamera normalCamera = m_ViewCamera; ComputeReflectionCamera(m_ViewCamera, scissor); + const CBoundingBoxAligned reflectionScissor = + m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera); SetViewport(m_ViewCamera.GetViewPort()); @@ -999,10 +1001,10 @@ float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; - screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); - screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); - screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); - screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); + screenScissor.x1 = (GLint)floor((reflectionScissor[0].X*0.5f+0.5f)*vpWidth); + screenScissor.y1 = (GLint)floor((reflectionScissor[0].Y*0.5f+0.5f)*vpHeight); + screenScissor.x2 = (GLint)ceil((reflectionScissor[1].X*0.5f+0.5f)*vpWidth); + screenScissor.y2 = (GLint)ceil((reflectionScissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); @@ -1062,6 +1064,8 @@ CCamera normalCamera = m_ViewCamera; ComputeRefractionCamera(m_ViewCamera, scissor); + const CBoundingBoxAligned refractionScissor = + m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f); SetObliqueFrustumClipping(m_ViewCamera, camPlane); @@ -1077,13 +1081,13 @@ float vpWidth = wm.m_RefTextureSize; SScreenRect screenScissor; - screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); - screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); - screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); - screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); + screenScissor.x1 = (GLint)floor((refractionScissor[0].X*0.5f+0.5f)*vpWidth); + screenScissor.y1 = (GLint)floor((refractionScissor[0].Y*0.5f+0.5f)*vpHeight); + screenScissor.x2 = (GLint)ceil((refractionScissor[1].X*0.5f+0.5f)*vpWidth); + screenScissor.y2 = (GLint)ceil((refractionScissor[1].Y*0.5f+0.5f)*vpHeight); - glEnable(GL_SCISSOR_TEST); - glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); + //glEnable(GL_SCISSOR_TEST); + //glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo); @@ -1091,6 +1095,9 @@ glClearColor(1.0f, 0.0f, 0.0f, 0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); + // Render terrain and models RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError();