Index: ps/trunk/source/graphics/Camera.cpp
===================================================================
--- ps/trunk/source/graphics/Camera.cpp (revision 23035)
+++ ps/trunk/source/graphics/Camera.cpp (revision 23036)
@@ -1,429 +1,436 @@
/* 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()
- : m_NearPlane(0.0f), m_FarPlane(0.0f), m_FOV(0.0f)
+ : m_NearPlane(0.0f), m_FarPlane(0.0f), m_FOV(0.0f), m_ProjType(CUSTOM)
{
// 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 = 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;
- m_FOV = camera.m_FOV;
+ if (m_ProjType == PERSPECTIVE)
+ {
+ m_FOV = camera.m_FOV;
+ }
m_ProjMat = camera.m_ProjMat;
}
void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov)
{
+ m_ProjType = PERSPECTIVE;
m_NearPlane = nearp;
m_FarPlane = farp;
m_FOV = fov;
m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane);
}
-void CCamera::SetPerspectiveProjectionTile(int tiles, int tile_x, int 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);
}
void CCamera::GetViewQuad(float dist, Quad& quad) const
{
+ ENSURE(m_ProjType == PERSPECTIVE);
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
{
// 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);
origin = m_Orientation.GetTranslation();
// 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, 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& 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
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/Camera.h
===================================================================
--- ps/trunk/source/graphics/Camera.h (revision 23035)
+++ ps/trunk/source/graphics/Camera.h (revision 23036)
@@ -1,129 +1,137 @@
/* 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;
+ enum ProjectionType
+ {
+ CUSTOM,
+ PERSPECTIVE,
+ };
+
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 SetProjection(const CMatrix3D& matrix);
void SetProjectionFromCamera(const CCamera& camera);
void SetPerspectiveProjection(float nearp, float farp, float fov);
- void SetPerspectiveProjectionTile(int tiles, int tile_x, int tile_y);
+ ProjectionType GetProjectionType() const { return m_ProjType; }
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 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;
+ CMatrix3D m_Orientation;
private:
- CMatrix3D m_ProjMat;
+ CMatrix3D m_ProjMat;
+ ProjectionType m_ProjType;
+
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 23035)
+++ ps/trunk/source/graphics/tests/test_Camera.h (revision 23036)
@@ -1,163 +1,165 @@
/* 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));
+ TS_ASSERT_EQUALS(camera.GetProjectionType(), CCamera::PERSPECTIVE);
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);
+ TS_ASSERT_EQUALS(camera.GetProjectionType(), CCamera::CUSTOM);
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);
}
};
Index: ps/trunk/source/ps/Util.cpp
===================================================================
--- ps/trunk/source/ps/Util.cpp (revision 23035)
+++ ps/trunk/source/ps/Util.cpp (revision 23036)
@@ -1,443 +1,452 @@
/* 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 "precompiled.h"
#include "ps/Util.h"
#include "lib/posix/posix_utsname.h"
#include "lib/ogl.h"
#include "lib/snd.h"
#include "lib/timer.h"
#include "lib/bits.h" // round_up
#include "lib/allocators/shared_ptr.h"
#include "lib/sysdep/sysdep.h" // sys_OpenFile
#include "lib/sysdep/gfx.h"
#include "lib/sysdep/cpu.h"
#include "lib/sysdep/os_cpu.h"
#if ARCH_X86_X64
#include "lib/sysdep/arch/x86_x64/topology.h"
#endif
#include "lib/sysdep/smbios.h"
#include "lib/tex/tex.h"
#include "i18n/L10n.h"
#include "lib/utf8.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/GameSetup.h"
#include "ps/Game.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/VideoMode.h"
#include "renderer/Renderer.h"
#include "maths/MathUtil.h"
#include "graphics/GameView.h"
#include
#include
extern CStrW g_CursorName;
static std::string SplitExts(const char *exts)
{
std::string str = exts;
std::string ret = "";
size_t idx = str.find_first_of(" ");
while(idx != std::string::npos)
{
if(idx >= str.length() - 1)
{
ret += str;
break;
}
ret += str.substr(0, idx);
ret += "\n";
str = str.substr(idx + 1);
idx = str.find_first_of(" ");
}
return ret;
}
void WriteSystemInfo()
{
TIMER(L"write_sys_info");
// get_cpu_info and gfx_detect already called during init - see call site
snd_detect();
struct utsname un;
uname(&un);
OsPath pathname = psLogDir()/"system_info.txt";
FILE* f = sys_OpenFile(pathname, "w");
if(!f)
return;
// current timestamp (redundant WRT OS timestamp, but that is not
// visible when people are posting this file's contents online)
{
wchar_t timestampBuf[100] = {'\0'};
time_t seconds;
time(&seconds);
struct tm* t = gmtime(&seconds);
const size_t charsWritten = wcsftime(timestampBuf, ARRAY_SIZE(timestampBuf), L"(generated %Y-%m-%d %H:%M:%S UTC)", t);
ENSURE(charsWritten != 0);
fprintf(f, "%ls\n\n", timestampBuf);
}
// OS
fprintf(f, "OS : %s %s (%s)\n", un.sysname, un.release, un.version);
// CPU
fprintf(f, "CPU : %s, %s", un.machine, cpu_IdentifierString());
#if ARCH_X86_X64
fprintf(f, " (%dx%dx%d)", (int)topology::NumPackages(), (int)topology::CoresPerPackage(), (int)topology::LogicalPerCore());
#endif
double cpuClock = os_cpu_ClockFrequency(); // query OS (may fail)
#if ARCH_X86_X64
if(cpuClock <= 0.0)
cpuClock = x86_x64::ClockFrequency(); // measure (takes a few ms)
#endif
if(cpuClock > 0.0)
{
if(cpuClock < 1e9)
fprintf(f, ", %.2f MHz\n", cpuClock*1e-6);
else
fprintf(f, ", %.2f GHz\n", cpuClock*1e-9);
}
else
fprintf(f, "\n");
// memory
fprintf(f, "Memory : %u MiB; %u MiB free\n", (unsigned)os_cpu_MemorySize(), (unsigned)os_cpu_MemoryAvailable());
// graphics
const std::wstring cardName = gfx::CardName();
const std::wstring driverInfo = gfx::DriverInfo();
fprintf(f, "Graphics Card : %ls\n", cardName.c_str());
fprintf(f, "OpenGL Drivers : %s; %ls\n", glGetString(GL_VERSION), driverInfo.c_str());
fprintf(f, "Video Mode : %dx%d:%d\n", g_VideoMode.GetXRes(), g_VideoMode.GetYRes(), g_VideoMode.GetBPP());
// sound
fprintf(f, "Sound Card : %s\n", snd_card.c_str());
fprintf(f, "Sound Drivers : %s\n", snd_drv_ver.c_str());
// OpenGL extensions (write them last, since it's a lot of text)
const char* exts = ogl_ExtensionString();
if (!exts) exts = "{unknown}";
fprintf(f, "\nOpenGL Extensions: \n%s\n", SplitExts(exts).c_str());
// System Management BIOS (even more text than OpenGL extensions)
std::string smbios = SMBIOS::StringizeStructures(SMBIOS::GetStructures());
fprintf(f, "\nSMBIOS: \n%s\n", smbios.c_str());
fclose(f);
f = 0;
}
// not thread-safe!
static const wchar_t* HardcodedErrorString(int err)
{
static wchar_t description[200];
StatusDescription((Status)err, description, ARRAY_SIZE(description));
return description;
}
// not thread-safe!
const wchar_t* ErrorString(int err)
{
// language file not available (yet)
return HardcodedErrorString(err);
// TODO: load from language file
}
// write the specified texture to disk.
// note: cannot be made const because the image may have to be
// transformed to write it out in the format determined by 's extension.
Status tex_write(Tex* t, const VfsPath& filename)
{
DynArray da;
RETURN_STATUS_IF_ERR(t->encode(filename.Extension(), &da));
// write to disk
Status ret = INFO::OK;
{
shared_ptr file = DummySharedPtr(da.base);
const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos);
if(bytes_written > 0)
ENSURE(bytes_written == (ssize_t)da.pos);
else
ret = (Status)bytes_written;
}
(void)da_free(&da);
return ret;
}
/**
* Return an unused directory, based on date and index (for example 2016-02-09_0001)
*/
OsPath createDateIndexSubdirectory(const OsPath& parentDir)
{
const std::time_t timestamp = std::time(nullptr);
const struct std::tm* now = std::localtime(×tamp);
// Two processes executing this simultaneously might attempt to create the same directory.
int tries = 0;
const int maxTries = 10;
int i = 0;
OsPath path;
char directory[256];
do
{
sprintf(directory, "%04d-%02d-%02d_%04d", now->tm_year+1900, now->tm_mon+1, now->tm_mday, ++i);
path = parentDir / CStr(directory);
if (DirectoryExists(path) || FileExists(path))
continue;
if (CreateDirectories(path, 0700, ++tries > maxTries) == INFO::OK)
break;
} while(tries <= maxTries);
return path;
}
static size_t s_nextScreenshotNumber;
// identifies the file format that is to be written
// (case-insensitive). examples: "bmp", "png", "jpg".
// BMP is good for quick output at the expense of large files.
void WriteScreenshot(const VfsPath& extension)
{
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath basenameFormat(L"screenshots/screenshot%04d");
const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
const size_t w = (size_t)g_xres, h = (size_t)g_yres;
const size_t bpp = 24;
GLenum fmt = GL_RGB;
int flags = TEX_BOTTOM_UP;
// we want writing BMP to be as fast as possible,
// so read data from OpenGL in BMP format to obviate conversion.
if(extension == L".bmp")
{
#if !CONFIG2_GLES // GLES doesn't support BGR
fmt = GL_BGR;
flags |= TEX_BGR;
#endif
}
// Hide log messages and re-render
RenderLogger(false);
Render();
RenderLogger(true);
const size_t img_size = w * h * bpp/8;
const size_t hdr_size = tex_hdr_size(filename);
shared_ptr buf;
AllocateAligned(buf, hdr_size+img_size, maxSectorSize);
GLvoid* img = buf.get() + hdr_size;
Tex t;
if(t.wrap(w, h, bpp, flags, buf, hdr_size) < 0)
return;
glReadPixels(0, 0, (GLsizei)w, (GLsizei)h, fmt, GL_UNSIGNED_BYTE, img);
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
}
// Similar to WriteScreenshot, but generates an image of size 640*tiles x 480*tiles.
void WriteBigScreenshot(const VfsPath& extension, int tiles)
{
// If the game hasn't started yet then use WriteScreenshot to generate the image.
if(g_Game == NULL){ WriteScreenshot(L".bmp"); return; }
// get next available numbered filename
// note: %04d -> always 4 digits, so sorting by filename works correctly.
const VfsPath basenameFormat(L"screenshots/screenshot%04d");
const VfsPath filenameFormat = basenameFormat.ChangeExtension(extension);
VfsPath filename;
vfs::NextNumberedFilename(g_VFS, filenameFormat, s_nextScreenshotNumber, filename);
// Slightly ugly and inflexible: Always draw 640*480 tiles onto the screen, and
// hope the screen is actually large enough for that.
const int tile_w = 640, tile_h = 480;
ENSURE(g_xres >= tile_w && g_yres >= tile_h);
const int img_w = tile_w*tiles, img_h = tile_h*tiles;
const int bpp = 24;
GLenum fmt = GL_RGB;
int flags = TEX_BOTTOM_UP;
// we want writing BMP to be as fast as possible,
// so read data from OpenGL in BMP format to obviate conversion.
if(extension == L".bmp")
{
#if !CONFIG2_GLES // GLES doesn't support BGR
fmt = GL_BGR;
flags |= TEX_BGR;
#endif
}
const size_t img_size = img_w * img_h * bpp/8;
const size_t tile_size = tile_w * tile_h * bpp/8;
const size_t hdr_size = tex_hdr_size(filename);
void* tile_data = malloc(tile_size);
if(!tile_data)
{
WARN_IF_ERR(ERR::NO_MEM);
return;
}
shared_ptr img_buf;
AllocateAligned(img_buf, hdr_size+img_size, maxSectorSize);
Tex t;
GLvoid* img = img_buf.get() + hdr_size;
if(t.wrap(img_w, img_h, bpp, flags, img_buf, hdr_size) < 0)
{
free(tile_data);
return;
}
ogl_WarnIfError();
+ CCamera oldCamera = *g_Game->GetView()->GetCamera();
+
// Resize various things so that the sizes and aspect ratios are correct
{
g_Renderer.Resize(tile_w, tile_h);
SViewPort vp = { 0, 0, tile_w, tile_h };
g_Game->GetView()->SetViewport(vp);
}
#if !CONFIG2_GLES
// Temporarily move everything onto the front buffer, so the user can
// see the exciting progress as it renders (and can tell when it's finished).
// (It doesn't just use SwapBuffers, because it doesn't know whether to
// call the SDL version or the Atlas version.)
GLint oldReadBuffer, oldDrawBuffer;
glGetIntegerv(GL_READ_BUFFER, &oldReadBuffer);
glGetIntegerv(GL_DRAW_BUFFER, &oldDrawBuffer);
glDrawBuffer(GL_FRONT);
glReadBuffer(GL_FRONT);
#endif
// Hide the cursor
CStrW oldCursor = g_CursorName;
g_CursorName = L"";
// Render each tile
+ CMatrix3D projection;
+ projection.SetIdentity();
+ const CCamera& camera = *(g_Game->GetView()->GetCamera());
for (int tile_y = 0; tile_y < tiles; ++tile_y)
{
for (int tile_x = 0; tile_x < tiles; ++tile_x)
{
// Adjust the camera to render the appropriate region
- g_Game->GetView()->GetCamera()->SetPerspectiveProjectionTile(tiles, tile_x, tile_y);
+ if (oldCamera.GetProjectionType() == CCamera::PERSPECTIVE)
+ {
+ projection.SetPerspectiveTile(oldCamera.GetFOV(), oldCamera.GetAspectRatio(), oldCamera.GetNearPlane(), oldCamera.GetFarPlane(), tiles, tile_x, tile_y);
+ }
+ g_Game->GetView()->GetCamera()->SetProjection(projection);
RenderLogger(false);
RenderGui(false);
Render();
RenderGui(true);
RenderLogger(true);
// Copy the tile pixels into the main image
glReadPixels(0, 0, tile_w, tile_h, fmt, GL_UNSIGNED_BYTE, tile_data);
for (int y = 0; y < tile_h; ++y)
{
void* dest = (char*)img + ((tile_y*tile_h + y) * img_w + (tile_x*tile_w)) * bpp/8;
void* src = (char*)tile_data + y * tile_w * bpp/8;
memcpy(dest, src, tile_w * bpp/8);
}
}
}
// Restore the old cursor
g_CursorName = oldCursor;
#if !CONFIG2_GLES
// Restore the buffer settings
glDrawBuffer(oldDrawBuffer);
glReadBuffer(oldReadBuffer);
#endif
// Restore the viewport settings
{
g_Renderer.Resize(g_xres, g_yres);
SViewPort vp = { 0, 0, g_xres, g_yres };
g_Game->GetView()->SetViewport(vp);
- g_Game->GetView()->GetCamera()->SetPerspectiveProjectionTile(1, 0, 0);
+ g_Game->GetView()->GetCamera()->SetProjectionFromCamera(oldCamera);
}
if (tex_write(&t, filename) == INFO::OK)
{
OsPath realPath;
g_VFS->GetRealPath(filename, realPath);
LOGMESSAGERENDER(g_L10n.Translate("Screenshot written to '%s'"), realPath.string8());
debug_printf(
CStr(g_L10n.Translate("Screenshot written to '%s'") + "\n").c_str(),
realPath.string8().c_str());
}
else
LOGERROR("Error writing screenshot to '%s'", filename.string8());
free(tile_data);
}
std::string Hexify(const std::string& s)
{
std::stringstream str;
str << std::hex;
for (const char& c : s)
str << std::setfill('0') << std::setw(2) << static_cast(static_cast(c));
return str.str();
}
std::string Hexify(const u8* s, size_t length)
{
std::stringstream str;
str << std::hex;
for (size_t i = 0; i < length; ++i)
str << std::setfill('0') << std::setw(2) << static_cast(s[i]);
return str.str();
}
Index: ps/trunk/source/renderer/Renderer.cpp
===================================================================
--- ps/trunk/source/renderer/Renderer.cpp (revision 23035)
+++ ps/trunk/source/renderer/Renderer.cpp (revision 23036)
@@ -1,1987 +1,1989 @@
/* 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 .
*/
/*
* higher level interface on top of OpenGL to render basic objects:
* terrain, models, sprites, particles etc.
*/
#include "precompiled.h"
#include