Index: ps/trunk/source/graphics/Camera.cpp =================================================================== --- ps/trunk/source/graphics/Camera.cpp (revision 25157) +++ ps/trunk/source/graphics/Camera.cpp (revision 25158) @@ -1,460 +1,460 @@ /* Copyright (C) 2021 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::SetProjection(const CMatrix3D& matrix) { m_ProjType = ProjectionType::CUSTOM; m_ProjMat = matrix; } void CCamera::SetProjectionFromCamera(const CCamera& camera) { m_ProjType = camera.m_ProjType; m_NearPlane = camera.m_NearPlane; m_FarPlane = camera.m_FarPlane; if (m_ProjType == ProjectionType::PERSPECTIVE) { m_FOV = camera.m_FOV; } else if (m_ProjType == ProjectionType::ORTHO) { m_OrthoScale = camera.m_OrthoScale; } m_ProjMat = camera.m_ProjMat; } void CCamera::SetOrthoProjection(float nearp, float farp, float scale) { m_ProjType = ProjectionType::ORTHO; m_NearPlane = nearp; m_FarPlane = farp; m_OrthoScale = scale; const float halfHeight = 0.5f * m_OrthoScale; const float halfWidth = halfHeight * GetAspectRatio(); m_ProjMat.SetOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, m_NearPlane, m_FarPlane); } void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov) { m_ProjType = ProjectionType::PERSPECTIVE; m_NearPlane = nearp; m_FarPlane = farp; m_FOV = fov; m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane); } // 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; + m_ViewFrustum[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11; + m_ViewFrustum[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12; + m_ViewFrustum[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13; + m_ViewFrustum[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; + m_ViewFrustum[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11; + m_ViewFrustum[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12; + m_ViewFrustum[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13; + m_ViewFrustum[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; + m_ViewFrustum[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21; + m_ViewFrustum[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22; + m_ViewFrustum[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23; + m_ViewFrustum[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; + m_ViewFrustum[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21; + m_ViewFrustum[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22; + m_ViewFrustum[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23; + m_ViewFrustum[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; + m_ViewFrustum[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31; + m_ViewFrustum[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32; + m_ViewFrustum[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33; + m_ViewFrustum[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; + m_ViewFrustum[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31; + m_ViewFrustum[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32; + m_ViewFrustum[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33; + m_ViewFrustum[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34; for (size_t i = 0; i < 6; ++i) - m_ViewFrustum.m_aPlanes[i].Normalize(); + m_ViewFrustum[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); } void CCamera::GetViewQuad(float dist, Quad& quad) const { ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO); const float y = m_ProjType == ProjectionType::PERSPECTIVE ? dist * tanf(m_FOV * 0.5f) : m_OrthoScale * 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 { ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO); // Coordinates relative to the camera plane. const float dx = static_cast(px) / m_ViewPort.m_Width; const float dy = 1.0f - static_cast(py) / m_ViewPort.m_Height; 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. const CVector3D basisX = points[1] - points[0]; const CVector3D basisY = points[3] - points[0]; if (m_ProjType == ProjectionType::PERSPECTIVE) { // Build direction for the camera origin to the target point. origin = m_Orientation.GetTranslation(); CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy); dir = targetPoint - origin; } else if (m_ProjType == ProjectionType::ORTHO) { origin = m_Orientation.GetTranslation() + (basisX * (dx - 0.5f)) + (basisY * (dy - 0.5f)); dir = m_Orientation.GetIn(); } 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 * m_ViewPort.m_Width; y = (1 - y) * 0.5f * m_ViewPort.m_Height; } 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, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); waterPoint.Z = Clamp(waterPoint.Z, 0.f, static_cast((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, static_cast((mapSize - 1) * TERRAIN_TILE_SIZE)); waterPoint.Z = Clamp(waterPoint.Z, 0.f, static_cast((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& focus, const CVector3D& up) { CVector3D delta = focus - 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 Quad nearPoints; Quad 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/Frustum.cpp =================================================================== --- ps/trunk/source/graphics/Frustum.cpp (revision 25157) +++ ps/trunk/source/graphics/Frustum.cpp (revision 25158) @@ -1,160 +1,156 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2021 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 . */ /* - * CFrustum is a collection of planes which define a viewing space. - */ - -/* Usually associated with the camera, there are 6 planes which define the view pyramid. But we allow more planes per frustum which may be used for portal rendering, where a portal may have 3 or more edges. */ #include "precompiled.h" #include "Frustum.h" + #include "maths/BoundingBoxAligned.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" -CFrustum::CFrustum () +CFrustum::CFrustum() { m_NumPlanes = 0; } -CFrustum::~CFrustum () +CFrustum::~CFrustum() { } -void CFrustum::SetNumPlanes (size_t num) +void CFrustum::SetNumPlanes(size_t num) { m_NumPlanes = num; - //clip it if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) { debug_warn(L"CFrustum::SetNumPlanes: Too many planes"); - m_NumPlanes = MAX_NUM_FRUSTUM_PLANES-1; + m_NumPlanes = MAX_NUM_FRUSTUM_PLANES - 1; } } void CFrustum::AddPlane(const CPlane& plane) { if (m_NumPlanes >= MAX_NUM_FRUSTUM_PLANES) { debug_warn(L"CFrustum::AddPlane: Too many planes"); return; } - m_aPlanes[m_NumPlanes++] = plane; + m_Planes[m_NumPlanes++] = plane; } -void CFrustum::Transform(CMatrix3D& m) +void CFrustum::Transform(const CMatrix3D& m) { for (size_t i = 0; i < m_NumPlanes; ++i) { - CVector3D n = m.Rotate(m_aPlanes[i].m_Norm); - CVector3D p = m.Transform(m_aPlanes[i].m_Norm * -m_aPlanes[i].m_Dist); - m_aPlanes[i].Set(n, p); - m_aPlanes[i].Normalize(); + CVector3D n = m.Rotate(m_Planes[i].m_Norm); + CVector3D p = m.Transform(m_Planes[i].m_Norm * -m_Planes[i].m_Dist); + m_Planes[i].Set(n, p); + m_Planes[i].Normalize(); } } bool CFrustum::IsPointVisible(const CVector3D& point) const { - for (size_t i=0; i radius) + // it is outside the frustum. + if (-m_Planes[i].DistanceToPlane(center) > radius) return false; } return true; } bool CFrustum::IsBoxVisible(const CVector3D& position, const CBoundingBoxAligned& bounds) const { - //basically for every plane we calculate the furthest point - //in the box to that plane. If that point is beyond the plane - //then the box is not visible - CVector3D FarPoint; - CVector3D Min = position+bounds[0]; - CVector3D Max = position+bounds[1]; - - for (size_t i=0; i 0.0f ? Max.X : Min.X; - FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? Max.Y : Min.Y; - FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? Max.Z : Min.Z; + // Basically for every plane we calculate the furthest point + // in the box to that plane. If that point is beyond the plane + // then the box is not visible. + CVector3D farPoint; + CVector3D minPoint = position + bounds[0]; + CVector3D maxPoint = position + bounds[1]; - if (m_aPlanes[i].IsPointOnBackSide(FarPoint)) + for (size_t i = 0; i < m_NumPlanes; ++i) + { + farPoint.X = m_Planes[i].m_Norm.X > 0.0f ? maxPoint.X : minPoint.X; + farPoint.Y = m_Planes[i].m_Norm.Y > 0.0f ? maxPoint.Y : minPoint.Y; + farPoint.Z = m_Planes[i].m_Norm.Z > 0.0f ? maxPoint.Z : minPoint.Z; + + if (m_Planes[i].IsPointOnBackSide(farPoint)) return false; } return true; } bool CFrustum::IsBoxVisible(const CBoundingBoxAligned& bounds) const { - //Same as the previous one, but with the position = (0, 0, 0) - CVector3D FarPoint; + // Same as the previous one, but with the position = (0, 0, 0). + CVector3D farPoint; - for (size_t i=0; i 0.0f ? bounds[1].X : bounds[0].X; - FarPoint.Y = m_aPlanes[i].m_Norm.Y > 0.0f ? bounds[1].Y : bounds[0].Y; - FarPoint.Z = m_aPlanes[i].m_Norm.Z > 0.0f ? bounds[1].Z : bounds[0].Z; + farPoint.X = m_Planes[i].m_Norm.X > 0.0f ? bounds[1].X : bounds[0].X; + farPoint.Y = m_Planes[i].m_Norm.Y > 0.0f ? bounds[1].Y : bounds[0].Y; + farPoint.Z = m_Planes[i].m_Norm.Z > 0.0f ? bounds[1].Z : bounds[0].Z; - if (m_aPlanes[i].IsPointOnBackSide(FarPoint)) + if (m_Planes[i].IsPointOnBackSide(farPoint)) return false; } return true; } Index: ps/trunk/source/graphics/Frustum.h =================================================================== --- ps/trunk/source/graphics/Frustum.h (revision 25157) +++ ps/trunk/source/graphics/Frustum.h (revision 25158) @@ -1,75 +1,70 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2021 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 . */ /* * CFrustum is a collection of planes which define a viewing space. */ /* Usually associated with the camera, there are 6 planes which define the view pyramid. But we allow more planes per frustum which may be used for portal rendering, where a portal may have 3 or more edges. */ #ifndef INCLUDED_FRUSTUM #define INCLUDED_FRUSTUM #include "maths/Plane.h" -//10 planes should be enough -#define MAX_NUM_FRUSTUM_PLANES (10) - class CBoundingBoxAligned; class CMatrix3D; class CFrustum { public: - CFrustum (); - ~CFrustum (); + CFrustum(); + ~CFrustum(); - //Set the number of planes to use for - //calculations. This is clipped to - //[0,MAX_NUM_FRUSTUM_PLANES] - void SetNumPlanes (size_t num); + // Set the number of planes to use for calculations. This is clamped to + // [0, MAX_NUM_FRUSTUM_PLANES]. + void SetNumPlanes(size_t num); size_t GetNumPlanes() const { return m_NumPlanes; } - void AddPlane (const CPlane& plane); + void AddPlane(const CPlane& plane); - void Transform(CMatrix3D& m); + void Transform(const CMatrix3D& m); - //The following methods return true if the shape is - //partially or completely in front of the frustum planes + // The following methods return true if the shape is + // partially or completely in front of the frustum planes. bool IsPointVisible(const CVector3D& point) const; bool DoesSegmentIntersect(const CVector3D& start, const CVector3D& end) const; bool IsSphereVisible(const CVector3D& center, float radius) const; bool IsBoxVisible(const CVector3D& position, const CBoundingBoxAligned& bounds) const; bool IsBoxVisible(const CBoundingBoxAligned& bounds) const; - CPlane& operator[](size_t idx) { return m_aPlanes[idx]; } - const CPlane& operator[](size_t idx) const { return m_aPlanes[idx]; } - -public: - //make the planes public for ease of use - CPlane m_aPlanes[MAX_NUM_FRUSTUM_PLANES]; + CPlane& operator[](size_t idx) { return m_Planes[idx]; } + const CPlane& operator[](size_t idx) const { return m_Planes[idx]; } private: + static const size_t MAX_NUM_FRUSTUM_PLANES = 10; + + CPlane m_Planes[MAX_NUM_FRUSTUM_PLANES]; size_t m_NumPlanes; }; -#endif +#endif // INCLUDED_FRUSTUM Index: ps/trunk/source/maths/BoundingBoxAligned.cpp =================================================================== --- ps/trunk/source/maths/BoundingBoxAligned.cpp (revision 25157) +++ ps/trunk/source/maths/BoundingBoxAligned.cpp (revision 25158) @@ -1,335 +1,335 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 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 . */ /* * Axis-aligned bounding box */ #include "precompiled.h" #include "BoundingBoxAligned.h" #include "graphics/Frustum.h" #include "graphics/ShaderProgram.h" #include "lib/ogl.h" #include "maths/BoundingBoxOriented.h" #include "maths/Brush.h" #include "maths/Matrix3D.h" #include const CBoundingBoxAligned CBoundingBoxAligned::EMPTY = CBoundingBoxAligned(); // initializes to an empty bound /////////////////////////////////////////////////////////////////////////////// // RayIntersect: intersect ray with this bound; return true // if ray hits (and store entry and exit times), or false // otherwise // note: incoming ray direction must be normalised bool CBoundingBoxAligned::RayIntersect( const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const { float t1, t2; float tnear, tfar; if (dir[0] == 0) { if (origin[0] < m_Data[0][0] || origin[0] > m_Data[1][0]) return false; else { tnear = -std::numeric_limits::max(); tfar = std::numeric_limits::max(); } } else { t1 = (m_Data[0][0] - origin[0]) / dir[0]; t2 = (m_Data[1][0] - origin[0]) / dir[0]; if (dir[0] < 0) { tnear = t2; tfar = t1; } else { tnear = t1; tfar = t2; } if (tfar < 0) return false; } if (dir[1] == 0 && (origin[1] < m_Data[0][1] || origin[1] > m_Data[1][1])) return false; else { t1 = (m_Data[0][1] - origin[1]) / dir[1]; t2 = (m_Data[1][1] - origin[1]) / dir[1]; if (dir[1] < 0) { if (t2 > tnear) tnear = t2; if (t1 < tfar) tfar = t1; } else { if (t1 > tnear) tnear = t1; if (t2 < tfar) tfar = t2; } if (tnear > tfar || tfar < 0) return false; } if (dir[2] == 0 && (origin[2] < m_Data[0][2] || origin[2] > m_Data[1][2])) return false; else { t1 = (m_Data[0][2] - origin[2]) / dir[2]; t2 = (m_Data[1][2] - origin[2]) / dir[2]; if (dir[2] < 0) { if (t2 > tnear) tnear = t2; if (t1 < tfar) tfar = t1; } else { if (t1 > tnear) tnear = t1; if (t2 < tfar) tfar = t2; } if (tnear > tfar || tfar < 0) return false; } tmin = tnear; tmax = tfar; return true; } /////////////////////////////////////////////////////////////////////////////// // SetEmpty: initialise this bound as empty void CBoundingBoxAligned::SetEmpty() { m_Data[0] = CVector3D::Max(); m_Data[1] = CVector3D::Min(); } /////////////////////////////////////////////////////////////////////////////// // IsEmpty: tests whether this bound is empty bool CBoundingBoxAligned::IsEmpty() const { return m_Data[0] == CVector3D::Max() && m_Data[1] == CVector3D::Min(); } /////////////////////////////////////////////////////////////////////////////// // Transform: transform this bound by given matrix; return transformed bound // in 'result' parameter - slightly modified version of code in Graphic Gems // (can't remember which one it was, though) void CBoundingBoxAligned::Transform(const CMatrix3D& m, CBoundingBoxAligned& result) const { ENSURE(this != &result); for (int i = 0; i < 3; ++i) { // handle translation result[0][i] = result[1][i] = m(i, 3); // Now find the extreme points by considering the product of the // min and max with each component of matrix for (int j = 0; j < 3; ++j) { float a = m(i, j) * m_Data[0][j]; float b = m(i, j) * m_Data[1][j]; if (a >= b) std::swap(a, b); result[0][i] += a; result[1][i] += b; } } } void CBoundingBoxAligned::Transform(const CMatrix3D& transform, CBoundingBoxOriented& result) const { const CVector3D& pMin = m_Data[0]; const CVector3D& pMax = m_Data[1]; // the basis vectors of the OBB are the normalized versions of the transformed AABB basis vectors, which // are the columns of the identity matrix, so the unnormalized OBB basis vectors are the transformation // matrix columns: CVector3D u(transform._11, transform._21, transform._31); CVector3D v(transform._12, transform._22, transform._32); CVector3D w(transform._13, transform._23, transform._33); // the half-sizes are scaled by whatever factor the AABB unit vectors end up scaled by result.m_HalfSizes = CVector3D( (pMax.X - pMin.X) / 2.f * u.Length(), (pMax.Y - pMin.Y) / 2.f * v.Length(), (pMax.Z - pMin.Z) / 2.f * w.Length() ); u.Normalize(); v.Normalize(); w.Normalize(); result.m_Basis[0] = u; result.m_Basis[1] = v; result.m_Basis[2] = w; result.m_Center = transform.Transform((pMax + pMin) * 0.5f); } /////////////////////////////////////////////////////////////////////////////// // Intersect with the given frustum in a conservative manner void CBoundingBoxAligned::IntersectFrustumConservative(const CFrustum& frustum) { // if this bound is empty, then the result must be empty (we should not attempt to intersect with // a brush, may cause crashes due to the numeric representation of empty bounds -- see // http://trac.wildfiregames.com/ticket/1027) if (IsEmpty()) return; CBrush brush(*this); CBrush buf; brush.Intersect(frustum, buf); buf.Bounds(*this); } /////////////////////////////////////////////////////////////////////////////// CFrustum CBoundingBoxAligned::ToFrustum() const { CFrustum frustum; frustum.SetNumPlanes(6); // get the LEFT plane - frustum.m_aPlanes[0].m_Norm = CVector3D(1, 0, 0); - frustum.m_aPlanes[0].m_Dist = -m_Data[0].X; + frustum[0].m_Norm = CVector3D(1, 0, 0); + frustum[0].m_Dist = -m_Data[0].X; // get the RIGHT plane - frustum.m_aPlanes[1].m_Norm = CVector3D(-1, 0, 0); - frustum.m_aPlanes[1].m_Dist = m_Data[1].X; + frustum[1].m_Norm = CVector3D(-1, 0, 0); + frustum[1].m_Dist = m_Data[1].X; // get the BOTTOM plane - frustum.m_aPlanes[2].m_Norm = CVector3D(0, 1, 0); - frustum.m_aPlanes[2].m_Dist = -m_Data[0].Y; + frustum[2].m_Norm = CVector3D(0, 1, 0); + frustum[2].m_Dist = -m_Data[0].Y; // get the TOP plane - frustum.m_aPlanes[3].m_Norm = CVector3D(0, -1, 0); - frustum.m_aPlanes[3].m_Dist = m_Data[1].Y; + frustum[3].m_Norm = CVector3D(0, -1, 0); + frustum[3].m_Dist = m_Data[1].Y; // get the NEAR plane - frustum.m_aPlanes[4].m_Norm = CVector3D(0, 0, 1); - frustum.m_aPlanes[4].m_Dist = -m_Data[0].Z; + frustum[4].m_Norm = CVector3D(0, 0, 1); + frustum[4].m_Dist = -m_Data[0].Z; // get the FAR plane - frustum.m_aPlanes[5].m_Norm = CVector3D(0, 0, -1); - frustum.m_aPlanes[5].m_Dist = m_Data[1].Z; + frustum[5].m_Norm = CVector3D(0, 0, -1); + frustum[5].m_Dist = m_Data[1].Z; return frustum; } /////////////////////////////////////////////////////////////////////////////// void CBoundingBoxAligned::Expand(float amount) { m_Data[0] -= CVector3D(amount, amount, amount); m_Data[1] += CVector3D(amount, amount, amount); } /////////////////////////////////////////////////////////////////////////////// // Render the bounding box void CBoundingBoxAligned::Render(CShaderProgramPtr& shader) const { std::vector data; #define ADD_FACE(x, y, z) \ ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \ ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z); #define ADD_PT(u_, v_, x, y, z) \ STMT(int u = u_; int v = v_; \ data.push_back(u); \ data.push_back(v); \ data.push_back(m_Data[x].X); \ data.push_back(m_Data[y].Y); \ data.push_back(m_Data[z].Z); \ ) ADD_FACE(u, v, 0); ADD_FACE(0, u, v); ADD_FACE(u, 0, 1-v); ADD_FACE(u, 1-v, 1); ADD_FACE(1, u, 1-v); ADD_FACE(u, 1, v); #undef ADD_FACE shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]); shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]); shader->AssertPointersBound(); glDrawArrays(GL_TRIANGLES, 0, 6*6); } void CBoundingBoxAligned::RenderOutline(CShaderProgramPtr& shader) const { std::vector data; #define ADD_FACE(x, y, z) \ ADD_PT(0, 0, x, y, z); ADD_PT(1, 0, x, y, z); \ ADD_PT(1, 0, x, y, z); ADD_PT(1, 1, x, y, z); \ ADD_PT(1, 1, x, y, z); ADD_PT(0, 1, x, y, z); \ ADD_PT(0, 1, x, y, z); ADD_PT(0, 0, x, y, z); #define ADD_PT(u_, v_, x, y, z) \ STMT(int u = u_; int v = v_; \ data.push_back(u); \ data.push_back(v); \ data.push_back(m_Data[x].X); \ data.push_back(m_Data[y].Y); \ data.push_back(m_Data[z].Z); \ ) ADD_FACE(u, v, 0); ADD_FACE(0, u, v); ADD_FACE(u, 0, 1-v); ADD_FACE(u, 1-v, 1); ADD_FACE(1, u, 1-v); ADD_FACE(u, 1, v); #undef ADD_FACE shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]); shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]); shader->AssertPointersBound(); glDrawArrays(GL_LINES, 0, 6*8); }