1
0
forked from 0ad/0ad

Fixes a bounding box projection to an incorrect camera causes the red water bug.

Tested By: Langbart
Differential Revision: https://code.wildfiregames.com/D3905
This was SVN commit r25440.
This commit is contained in:
Vladislav Belov 2021-05-15 13:33:36 +00:00
parent 6b0802836a
commit 44f8d2c6f5
6 changed files with 171 additions and 40 deletions

View File

@ -377,38 +377,42 @@ CVector3D CCamera::GetFocus() const
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<CVector3D, 8> worldPositions;
std::array<bool, 8> 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;

View File

@ -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 <cmath>
#include <vector>
@ -341,4 +343,82 @@ public:
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);
}
}
};

View File

@ -263,3 +263,11 @@ void CBoundingBoxAligned::Expand(float amount)
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;
}

View File

@ -107,6 +107,8 @@ public:
*/
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
{

View File

@ -208,4 +208,34 @@ public:
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);
}
}
}
};

View File

@ -989,6 +989,8 @@ void CRenderer::RenderReflections(const CShaderDefines& context, const CBounding
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 @@ void CRenderer::RenderReflections(const CShaderDefines& context, const CBounding
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 @@ void CRenderer::RenderRefractions(const CShaderDefines& context, const CBounding
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 @@ void CRenderer::RenderRefractions(const CShaderDefines& context, const CBounding
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 @@ void CRenderer::RenderRefractions(const CShaderDefines& context, const CBounding
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();