source: ps/trunk/source/graphics/Camera.cpp@ 25278

Last change on this file since 25278 was 25278, checked in by wraitii, 3 years ago

Rework rP25266

Following comments by @vladislavbelov.

Differential Revision: https://code.wildfiregames.com/D3860

  • Property svn:eol-style set to native
File size: 11.7 KB
Line 
1/* Copyright (C) 2021 Wildfire Games.
2 * This file is part of 0 A.D.
3 *
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * CCamera holds a view and a projection matrix. It also has a frustum
20 * which can be used to cull objects for rendering.
21 */
22
23#include "precompiled.h"
24
25#include "Camera.h"
26
27#include "graphics/HFTracer.h"
28#include "graphics/Terrain.h"
29#include "lib/ogl.h"
30#include "maths/MathUtil.h"
31#include "maths/Vector4D.h"
32#include "ps/Game.h"
33#include "ps/World.h"
34#include "renderer/Renderer.h"
35#include "renderer/WaterManager.h"
36
37CCamera::CCamera()
38{
39 // Set viewport to something anything should handle, but should be initialised
40 // to window size before use.
41 m_ViewPort.m_X = 0;
42 m_ViewPort.m_Y = 0;
43 m_ViewPort.m_Width = 800;
44 m_ViewPort.m_Height = 600;
45}
46
47CCamera::~CCamera() = default;
48
49void CCamera::SetProjection(const CMatrix3D& matrix)
50{
51 m_ProjType = ProjectionType::CUSTOM;
52 m_ProjMat = matrix;
53}
54
55void CCamera::SetProjectionFromCamera(const CCamera& camera)
56{
57 m_ProjType = camera.m_ProjType;
58 m_NearPlane = camera.m_NearPlane;
59 m_FarPlane = camera.m_FarPlane;
60 if (m_ProjType == ProjectionType::PERSPECTIVE)
61 {
62 m_FOV = camera.m_FOV;
63 }
64 else if (m_ProjType == ProjectionType::ORTHO)
65 {
66 m_OrthoScale = camera.m_OrthoScale;
67 }
68 m_ProjMat = camera.m_ProjMat;
69}
70
71void CCamera::SetOrthoProjection(float nearp, float farp, float scale)
72{
73 m_ProjType = ProjectionType::ORTHO;
74 m_NearPlane = nearp;
75 m_FarPlane = farp;
76 m_OrthoScale = scale;
77
78 const float halfHeight = 0.5f * m_OrthoScale;
79 const float halfWidth = halfHeight * GetAspectRatio();
80 m_ProjMat.SetOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, m_NearPlane, m_FarPlane);
81}
82
83void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov)
84{
85 m_ProjType = ProjectionType::PERSPECTIVE;
86 m_NearPlane = nearp;
87 m_FarPlane = farp;
88 m_FOV = fov;
89
90 m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane);
91}
92
93// Updates the frustum planes. Should be called
94// everytime the view or projection matrices are
95// altered.
96void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor)
97{
98 CMatrix3D MatFinal;
99 CMatrix3D MatView;
100
101 m_Orientation.GetInverse(MatView);
102
103 MatFinal = m_ProjMat * MatView;
104
105 m_ViewFrustum.SetNumPlanes(6);
106
107 // get the RIGHT plane
108 m_ViewFrustum[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11;
109 m_ViewFrustum[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12;
110 m_ViewFrustum[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13;
111 m_ViewFrustum[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14;
112
113 // get the LEFT plane
114 m_ViewFrustum[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11;
115 m_ViewFrustum[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12;
116 m_ViewFrustum[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13;
117 m_ViewFrustum[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14;
118
119 // get the BOTTOM plane
120 m_ViewFrustum[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21;
121 m_ViewFrustum[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22;
122 m_ViewFrustum[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23;
123 m_ViewFrustum[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24;
124
125 // get the TOP plane
126 m_ViewFrustum[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21;
127 m_ViewFrustum[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22;
128 m_ViewFrustum[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23;
129 m_ViewFrustum[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24;
130
131 // get the FAR plane
132 m_ViewFrustum[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31;
133 m_ViewFrustum[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32;
134 m_ViewFrustum[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33;
135 m_ViewFrustum[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34;
136
137 // get the NEAR plane
138 m_ViewFrustum[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31;
139 m_ViewFrustum[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32;
140 m_ViewFrustum[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33;
141 m_ViewFrustum[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34;
142
143 for (size_t i = 0; i < 6; ++i)
144 m_ViewFrustum[i].Normalize();
145}
146
147void CCamera::ClipFrustum(const CPlane& clipPlane)
148{
149 CPlane normClipPlane = clipPlane;
150 normClipPlane.Normalize();
151 m_ViewFrustum.AddPlane(normClipPlane);
152}
153
154void CCamera::SetViewPort(const SViewPort& viewport)
155{
156 m_ViewPort.m_X = viewport.m_X;
157 m_ViewPort.m_Y = viewport.m_Y;
158 m_ViewPort.m_Width = viewport.m_Width;
159 m_ViewPort.m_Height = viewport.m_Height;
160}
161
162float CCamera::GetAspectRatio() const
163{
164 return static_cast<float>(m_ViewPort.m_Width) / static_cast<float>(m_ViewPort.m_Height);
165}
166
167void CCamera::GetViewQuad(float dist, Quad& quad) const
168{
169 ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO);
170
171 const float y = m_ProjType == ProjectionType::PERSPECTIVE ? dist * tanf(m_FOV * 0.5f) : m_OrthoScale * 0.5f;
172 const float x = y * GetAspectRatio();
173
174 quad[0].X = -x;
175 quad[0].Y = -y;
176 quad[0].Z = dist;
177 quad[1].X = x;
178 quad[1].Y = -y;
179 quad[1].Z = dist;
180 quad[2].X = x;
181 quad[2].Y = y;
182 quad[2].Z = dist;
183 quad[3].X = -x;
184 quad[3].Y = y;
185 quad[3].Z = dist;
186}
187
188void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const
189{
190 ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO);
191
192 // Coordinates relative to the camera plane.
193 const float dx = static_cast<float>(px) / m_ViewPort.m_Width;
194 const float dy = 1.0f - static_cast<float>(py) / m_ViewPort.m_Height;
195
196 Quad points;
197 GetViewQuad(m_FarPlane, points);
198
199 // Transform from camera space to world space.
200 for (CVector3D& point : points)
201 point = m_Orientation.Transform(point);
202
203 // Get world space position of mouse point at the far clipping plane.
204 const CVector3D basisX = points[1] - points[0];
205 const CVector3D basisY = points[3] - points[0];
206
207 if (m_ProjType == ProjectionType::PERSPECTIVE)
208 {
209 // Build direction for the camera origin to the target point.
210 origin = m_Orientation.GetTranslation();
211 CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy);
212 dir = targetPoint - origin;
213 }
214 else if (m_ProjType == ProjectionType::ORTHO)
215 {
216 origin = m_Orientation.GetTranslation() + (basisX * (dx - 0.5f)) + (basisY * (dy - 0.5f));
217 dir = m_Orientation.GetIn();
218 }
219 dir.Normalize();
220}
221
222void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const
223{
224 CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse();
225
226 CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f));
227
228 x = screenspace.X / screenspace.W;
229 y = screenspace.Y / screenspace.W;
230 x = (x + 1) * 0.5f * m_ViewPort.m_Width;
231 y = (1 - y) * 0.5f * m_ViewPort.m_Height;
232}
233
234CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const
235{
236 CHFTracer tracer(g_Game->GetWorld()->GetTerrain());
237 int x, z;
238 CVector3D origin, dir, delta, terrainPoint, waterPoint;
239
240 BuildCameraRay(px, py, origin, dir);
241
242 bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint);
243
244 if (!aboveWater)
245 {
246 if (gotTerrain)
247 return terrainPoint;
248
249 // Off the edge of the world?
250 // Work out where it /would/ hit, if the map were extended out to infinity with average height.
251 return GetWorldCoordinates(px, py, 50.0f);
252 }
253
254 CPlane plane;
255 plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal
256 CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane
257
258 bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint );
259
260 // Clamp the water intersection to within the map's bounds, so that
261 // we'll always return a valid position on the map
262 ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
263 if (gotWater)
264 {
265 waterPoint.X = Clamp<float>(waterPoint.X, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
266 waterPoint.Z = Clamp<float>(waterPoint.Z, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
267 }
268
269 if (gotTerrain)
270 {
271 if (gotWater)
272 {
273 // Intersecting both heightmap and water plane; choose the closest of those
274 if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared())
275 return terrainPoint;
276 else
277 return waterPoint;
278 }
279 else
280 {
281 // Intersecting heightmap but parallel to water plane
282 return terrainPoint;
283 }
284 }
285 else
286 {
287 if (gotWater)
288 {
289 // Only intersecting water plane
290 return waterPoint;
291 }
292 else
293 {
294 // Not intersecting terrain or water; just return 0,0,0.
295 return CVector3D(0.f, 0.f, 0.f);
296 }
297 }
298
299}
300
301CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const
302{
303 CPlane plane;
304 plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h
305
306 CVector3D origin, dir, delta, currentTarget;
307
308 BuildCameraRay(px, py, origin, dir);
309
310 if (plane.FindRayIntersection(origin, dir, &currentTarget))
311 return currentTarget;
312
313 // No intersection with the infinite plane - nothing sensible can be returned,
314 // so just choose an arbitrary point on the plane
315 return CVector3D(0.f, h, 0.f);
316}
317
318CVector3D CCamera::GetFocus() const
319{
320 // Basically the same as GetWorldCoordinates
321
322 CHFTracer tracer(g_Game->GetWorld()->GetTerrain());
323 int x, z;
324
325 CVector3D origin, dir, delta, terrainPoint, waterPoint;
326
327 origin = m_Orientation.GetTranslation();
328 dir = m_Orientation.GetIn();
329
330 bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint);
331
332 CPlane plane;
333 plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal
334 CVector3D(0.f, g_Renderer.GetWaterManager()->m_WaterHeight, 0.f)); // passes through water plane
335
336 bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint );
337
338 // Clamp the water intersection to within the map's bounds, so that
339 // we'll always return a valid position on the map
340 ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
341 if (gotWater)
342 {
343 waterPoint.X = Clamp<float>(waterPoint.X, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
344 waterPoint.Z = Clamp<float>(waterPoint.Z, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
345 }
346
347 if (gotTerrain)
348 {
349 if (gotWater)
350 {
351 // Intersecting both heightmap and water plane; choose the closest of those
352 if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared())
353 return terrainPoint;
354 else
355 return waterPoint;
356 }
357 else
358 {
359 // Intersecting heightmap but parallel to water plane
360 return terrainPoint;
361 }
362 }
363 else
364 {
365 if (gotWater)
366 {
367 // Only intersecting water plane
368 return waterPoint;
369 }
370 else
371 {
372 // Not intersecting terrain or water; just return 0,0,0.
373 return CVector3D(0.f, 0.f, 0.f);
374 }
375 }
376}
377
378void CCamera::LookAt(const CVector3D& camera, const CVector3D& focus, const CVector3D& up)
379{
380 CVector3D delta = focus - camera;
381 LookAlong(camera, delta, up);
382}
383
384void CCamera::LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up)
385{
386 orientation.Normalize();
387 up.Normalize();
388 CVector3D s = orientation.Cross(up);
389
390 m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X;
391 m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y;
392 m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z;
393 m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f;
394}
Note: See TracBrowser for help on using the repository browser.