Line data Source code
1 : /* Copyright (C) 2022 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 : #include "precompiled.h"
19 :
20 : #include "Camera.h"
21 :
22 : #include "graphics/HFTracer.h"
23 : #include "graphics/Terrain.h"
24 : #include "maths/MathUtil.h"
25 : #include "maths/Vector2D.h"
26 : #include "maths/Vector4D.h"
27 : #include "ps/Game.h"
28 : #include "ps/World.h"
29 : #include "renderer/Renderer.h"
30 : #include "renderer/SceneRenderer.h"
31 : #include "renderer/WaterManager.h"
32 :
33 37 : CCamera::CCamera()
34 : {
35 : // Set viewport to something anything should handle, but should be initialised
36 : // to window size before use.
37 37 : m_ViewPort.m_X = 0;
38 37 : m_ViewPort.m_Y = 0;
39 37 : m_ViewPort.m_Width = 800;
40 37 : m_ViewPort.m_Height = 600;
41 37 : }
42 :
43 : CCamera::~CCamera() = default;
44 :
45 2 : void CCamera::SetProjection(const CMatrix3D& matrix)
46 : {
47 2 : m_ProjType = ProjectionType::CUSTOM;
48 2 : m_ProjMat = matrix;
49 2 : }
50 :
51 0 : void CCamera::SetProjectionFromCamera(const CCamera& camera)
52 : {
53 0 : m_ProjType = camera.m_ProjType;
54 0 : m_NearPlane = camera.m_NearPlane;
55 0 : m_FarPlane = camera.m_FarPlane;
56 0 : if (m_ProjType == ProjectionType::PERSPECTIVE)
57 : {
58 0 : m_FOV = camera.m_FOV;
59 : }
60 0 : else if (m_ProjType == ProjectionType::ORTHO)
61 : {
62 0 : m_OrthoScale = camera.m_OrthoScale;
63 : }
64 0 : m_ProjMat = camera.m_ProjMat;
65 0 : }
66 :
67 5 : void CCamera::SetOrthoProjection(float nearp, float farp, float scale)
68 : {
69 5 : m_ProjType = ProjectionType::ORTHO;
70 5 : m_NearPlane = nearp;
71 5 : m_FarPlane = farp;
72 5 : m_OrthoScale = scale;
73 :
74 5 : const float halfHeight = 0.5f * m_OrthoScale;
75 5 : const float halfWidth = halfHeight * GetAspectRatio();
76 5 : m_ProjMat.SetOrtho(-halfWidth, halfWidth, -halfHeight, halfHeight, m_NearPlane, m_FarPlane);
77 5 : }
78 :
79 7 : void CCamera::SetPerspectiveProjection(float nearp, float farp, float fov)
80 : {
81 7 : m_ProjType = ProjectionType::PERSPECTIVE;
82 7 : m_NearPlane = nearp;
83 7 : m_FarPlane = farp;
84 7 : m_FOV = fov;
85 :
86 7 : m_ProjMat.SetPerspective(m_FOV, GetAspectRatio(), m_NearPlane, m_FarPlane);
87 7 : }
88 :
89 : // Updates the frustum planes. Should be called
90 : // everytime the view or projection matrices are
91 : // altered.
92 4 : void CCamera::UpdateFrustum(const CBoundingBoxAligned& scissor)
93 : {
94 4 : CMatrix3D MatFinal;
95 4 : CMatrix3D MatView;
96 :
97 4 : m_Orientation.GetInverse(MatView);
98 :
99 4 : MatFinal = m_ProjMat * MatView;
100 :
101 4 : m_ViewFrustum.SetNumPlanes(6);
102 :
103 : // get the RIGHT plane
104 4 : m_ViewFrustum[0].m_Norm.X = scissor[1].X*MatFinal._41 - MatFinal._11;
105 4 : m_ViewFrustum[0].m_Norm.Y = scissor[1].X*MatFinal._42 - MatFinal._12;
106 4 : m_ViewFrustum[0].m_Norm.Z = scissor[1].X*MatFinal._43 - MatFinal._13;
107 4 : m_ViewFrustum[0].m_Dist = scissor[1].X*MatFinal._44 - MatFinal._14;
108 :
109 : // get the LEFT plane
110 4 : m_ViewFrustum[1].m_Norm.X = -scissor[0].X*MatFinal._41 + MatFinal._11;
111 4 : m_ViewFrustum[1].m_Norm.Y = -scissor[0].X*MatFinal._42 + MatFinal._12;
112 4 : m_ViewFrustum[1].m_Norm.Z = -scissor[0].X*MatFinal._43 + MatFinal._13;
113 4 : m_ViewFrustum[1].m_Dist = -scissor[0].X*MatFinal._44 + MatFinal._14;
114 :
115 : // get the BOTTOM plane
116 4 : m_ViewFrustum[2].m_Norm.X = -scissor[0].Y*MatFinal._41 + MatFinal._21;
117 4 : m_ViewFrustum[2].m_Norm.Y = -scissor[0].Y*MatFinal._42 + MatFinal._22;
118 4 : m_ViewFrustum[2].m_Norm.Z = -scissor[0].Y*MatFinal._43 + MatFinal._23;
119 4 : m_ViewFrustum[2].m_Dist = -scissor[0].Y*MatFinal._44 + MatFinal._24;
120 :
121 : // get the TOP plane
122 4 : m_ViewFrustum[3].m_Norm.X = scissor[1].Y*MatFinal._41 - MatFinal._21;
123 4 : m_ViewFrustum[3].m_Norm.Y = scissor[1].Y*MatFinal._42 - MatFinal._22;
124 4 : m_ViewFrustum[3].m_Norm.Z = scissor[1].Y*MatFinal._43 - MatFinal._23;
125 4 : m_ViewFrustum[3].m_Dist = scissor[1].Y*MatFinal._44 - MatFinal._24;
126 :
127 : // get the FAR plane
128 4 : m_ViewFrustum[4].m_Norm.X = scissor[1].Z*MatFinal._41 - MatFinal._31;
129 4 : m_ViewFrustum[4].m_Norm.Y = scissor[1].Z*MatFinal._42 - MatFinal._32;
130 4 : m_ViewFrustum[4].m_Norm.Z = scissor[1].Z*MatFinal._43 - MatFinal._33;
131 4 : m_ViewFrustum[4].m_Dist = scissor[1].Z*MatFinal._44 - MatFinal._34;
132 :
133 : // get the NEAR plane
134 4 : m_ViewFrustum[5].m_Norm.X = -scissor[0].Z*MatFinal._41 + MatFinal._31;
135 4 : m_ViewFrustum[5].m_Norm.Y = -scissor[0].Z*MatFinal._42 + MatFinal._32;
136 4 : m_ViewFrustum[5].m_Norm.Z = -scissor[0].Z*MatFinal._43 + MatFinal._33;
137 4 : m_ViewFrustum[5].m_Dist = -scissor[0].Z*MatFinal._44 + MatFinal._34;
138 :
139 28 : for (size_t i = 0; i < 6; ++i)
140 24 : m_ViewFrustum[i].Normalize();
141 4 : }
142 :
143 0 : void CCamera::ClipFrustum(const CPlane& clipPlane)
144 : {
145 0 : CPlane normClipPlane = clipPlane;
146 0 : normClipPlane.Normalize();
147 0 : m_ViewFrustum.AddPlane(normClipPlane);
148 0 : }
149 :
150 13 : void CCamera::SetViewPort(const SViewPort& viewport)
151 : {
152 13 : m_ViewPort.m_X = viewport.m_X;
153 13 : m_ViewPort.m_Y = viewport.m_Y;
154 13 : m_ViewPort.m_Width = viewport.m_Width;
155 13 : m_ViewPort.m_Height = viewport.m_Height;
156 13 : }
157 :
158 28 : float CCamera::GetAspectRatio() const
159 : {
160 28 : return static_cast<float>(m_ViewPort.m_Width) / static_cast<float>(m_ViewPort.m_Height);
161 : }
162 :
163 18 : void CCamera::GetViewQuad(float dist, Quad& quad) const
164 : {
165 18 : if (m_ProjType == ProjectionType::CUSTOM)
166 : {
167 3 : const CMatrix3D invProjection = m_ProjMat.GetInverse();
168 : const std::array<CVector2D, 4> ndcCorners = {
169 : CVector2D{-1.0f, -1.0f}, CVector2D{1.0f, -1.0f},
170 3 : CVector2D{1.0f, 1.0f}, CVector2D{-1.0f, 1.0f}};
171 15 : for (size_t idx = 0; idx < 4; ++idx)
172 : {
173 12 : const CVector2D& corner = ndcCorners[idx];
174 : CVector4D nearCorner =
175 12 : invProjection.Transform(CVector4D(corner.X, corner.Y, -1.0f, 1.0f));
176 12 : nearCorner /= nearCorner.W;
177 : CVector4D farCorner =
178 12 : invProjection.Transform(CVector4D(corner.X, corner.Y, 1.0f, 1.0f));
179 12 : farCorner /= farCorner.W;
180 12 : const float t = (dist - nearCorner.Z) / (farCorner.Z - nearCorner.Z);
181 12 : const CVector4D quadCorner = nearCorner * (1.0 - t) + farCorner * t;
182 12 : quad[idx].X = quadCorner.X;
183 12 : quad[idx].Y = quadCorner.Y;
184 12 : quad[idx].Z = quadCorner.Z;
185 : }
186 3 : return;
187 : }
188 :
189 15 : const float y = m_ProjType == ProjectionType::PERSPECTIVE ? dist * tanf(m_FOV * 0.5f) : m_OrthoScale * 0.5f;
190 15 : const float x = y * GetAspectRatio();
191 :
192 15 : quad[0].X = -x;
193 15 : quad[0].Y = -y;
194 15 : quad[0].Z = dist;
195 15 : quad[1].X = x;
196 15 : quad[1].Y = -y;
197 15 : quad[1].Z = dist;
198 15 : quad[2].X = x;
199 15 : quad[2].Y = y;
200 15 : quad[2].Z = dist;
201 15 : quad[3].X = -x;
202 15 : quad[3].Y = y;
203 15 : quad[3].Z = dist;
204 : }
205 :
206 6 : void CCamera::BuildCameraRay(int px, int py, CVector3D& origin, CVector3D& dir) const
207 : {
208 6 : ENSURE(m_ProjType == ProjectionType::PERSPECTIVE || m_ProjType == ProjectionType::ORTHO);
209 :
210 : // Coordinates relative to the camera plane.
211 6 : const float dx = static_cast<float>(px) / m_ViewPort.m_Width;
212 6 : const float dy = 1.0f - static_cast<float>(py) / m_ViewPort.m_Height;
213 :
214 6 : Quad points;
215 6 : GetViewQuad(m_FarPlane, points);
216 :
217 : // Transform from camera space to world space.
218 30 : for (CVector3D& point : points)
219 24 : point = m_Orientation.Transform(point);
220 :
221 : // Get world space position of mouse point at the far clipping plane.
222 6 : const CVector3D basisX = points[1] - points[0];
223 6 : const CVector3D basisY = points[3] - points[0];
224 :
225 6 : if (m_ProjType == ProjectionType::PERSPECTIVE)
226 : {
227 : // Build direction for the camera origin to the target point.
228 3 : origin = m_Orientation.GetTranslation();
229 3 : CVector3D targetPoint = points[0] + (basisX * dx) + (basisY * dy);
230 3 : dir = targetPoint - origin;
231 : }
232 3 : else if (m_ProjType == ProjectionType::ORTHO)
233 : {
234 3 : origin = m_Orientation.GetTranslation() + (basisX * (dx - 0.5f)) + (basisY * (dy - 0.5f));
235 3 : dir = m_Orientation.GetIn();
236 : }
237 6 : dir.Normalize();
238 6 : }
239 :
240 0 : void CCamera::GetScreenCoordinates(const CVector3D& world, float& x, float& y) const
241 : {
242 0 : CMatrix3D transform = m_ProjMat * m_Orientation.GetInverse();
243 :
244 0 : CVector4D screenspace = transform.Transform(CVector4D(world.X, world.Y, world.Z, 1.0f));
245 :
246 0 : x = screenspace.X / screenspace.W;
247 0 : y = screenspace.Y / screenspace.W;
248 0 : x = (x + 1) * 0.5f * m_ViewPort.m_Width;
249 0 : y = (1 - y) * 0.5f * m_ViewPort.m_Height;
250 0 : }
251 :
252 0 : CVector3D CCamera::GetWorldCoordinates(int px, int py, bool aboveWater) const
253 : {
254 0 : CHFTracer tracer(g_Game->GetWorld()->GetTerrain());
255 : int x, z;
256 0 : CVector3D origin, dir, delta, terrainPoint, waterPoint;
257 :
258 0 : BuildCameraRay(px, py, origin, dir);
259 :
260 0 : bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint);
261 :
262 0 : if (!aboveWater)
263 : {
264 0 : if (gotTerrain)
265 0 : return terrainPoint;
266 :
267 : // Off the edge of the world?
268 : // Work out where it /would/ hit, if the map were extended out to infinity with average height.
269 0 : return GetWorldCoordinates(px, py, 50.0f);
270 : }
271 :
272 0 : CPlane plane;
273 0 : plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal
274 0 : CVector3D(0.f, g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight, 0.f)); // passes through water plane
275 :
276 0 : bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint );
277 :
278 : // Clamp the water intersection to within the map's bounds, so that
279 : // we'll always return a valid position on the map
280 0 : ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
281 0 : if (gotWater)
282 : {
283 0 : waterPoint.X = Clamp<float>(waterPoint.X, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
284 0 : waterPoint.Z = Clamp<float>(waterPoint.Z, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
285 : }
286 :
287 0 : if (gotTerrain)
288 : {
289 0 : if (gotWater)
290 : {
291 : // Intersecting both heightmap and water plane; choose the closest of those
292 0 : if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared())
293 0 : return terrainPoint;
294 : else
295 0 : return waterPoint;
296 : }
297 : else
298 : {
299 : // Intersecting heightmap but parallel to water plane
300 0 : return terrainPoint;
301 : }
302 : }
303 : else
304 : {
305 0 : if (gotWater)
306 : {
307 : // Only intersecting water plane
308 0 : return waterPoint;
309 : }
310 : else
311 : {
312 : // Not intersecting terrain or water; just return 0,0,0.
313 0 : return CVector3D(0.f, 0.f, 0.f);
314 : }
315 : }
316 :
317 : }
318 :
319 0 : CVector3D CCamera::GetWorldCoordinates(int px, int py, float h) const
320 : {
321 0 : CPlane plane;
322 0 : plane.Set(CVector3D(0.f, 1.f, 0.f), CVector3D(0.f, h, 0.f)); // upwards normal, passes through h
323 :
324 0 : CVector3D origin, dir, delta, currentTarget;
325 :
326 0 : BuildCameraRay(px, py, origin, dir);
327 :
328 0 : if (plane.FindRayIntersection(origin, dir, ¤tTarget))
329 0 : return currentTarget;
330 :
331 : // No intersection with the infinite plane - nothing sensible can be returned,
332 : // so just choose an arbitrary point on the plane
333 0 : return CVector3D(0.f, h, 0.f);
334 : }
335 :
336 0 : CVector3D CCamera::GetFocus() const
337 : {
338 : // Basically the same as GetWorldCoordinates
339 :
340 0 : CHFTracer tracer(g_Game->GetWorld()->GetTerrain());
341 : int x, z;
342 :
343 0 : CVector3D origin, dir, delta, terrainPoint, waterPoint;
344 :
345 0 : origin = m_Orientation.GetTranslation();
346 0 : dir = m_Orientation.GetIn();
347 :
348 0 : bool gotTerrain = tracer.RayIntersect(origin, dir, x, z, terrainPoint);
349 :
350 0 : CPlane plane;
351 0 : plane.Set(CVector3D(0.f, 1.f, 0.f), // upwards normal
352 0 : CVector3D(0.f, g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight, 0.f)); // passes through water plane
353 :
354 0 : bool gotWater = plane.FindRayIntersection( origin, dir, &waterPoint );
355 :
356 : // Clamp the water intersection to within the map's bounds, so that
357 : // we'll always return a valid position on the map
358 0 : ssize_t mapSize = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
359 0 : if (gotWater)
360 : {
361 0 : waterPoint.X = Clamp<float>(waterPoint.X, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
362 0 : waterPoint.Z = Clamp<float>(waterPoint.Z, 0.f, (mapSize - 1) * TERRAIN_TILE_SIZE);
363 : }
364 :
365 0 : if (gotTerrain)
366 : {
367 0 : if (gotWater)
368 : {
369 : // Intersecting both heightmap and water plane; choose the closest of those
370 0 : if ((origin - terrainPoint).LengthSquared() < (origin - waterPoint).LengthSquared())
371 0 : return terrainPoint;
372 : else
373 0 : return waterPoint;
374 : }
375 : else
376 : {
377 : // Intersecting heightmap but parallel to water plane
378 0 : return terrainPoint;
379 : }
380 : }
381 : else
382 : {
383 0 : if (gotWater)
384 : {
385 : // Only intersecting water plane
386 0 : return waterPoint;
387 : }
388 : else
389 : {
390 : // Not intersecting terrain or water; just return 0,0,0.
391 0 : return CVector3D(0.f, 0.f, 0.f);
392 : }
393 : }
394 : }
395 :
396 11 : CBoundingBoxAligned CCamera::GetBoundsInViewPort(const CBoundingBoxAligned& boundigBox) const
397 : {
398 11 : const CVector3D cameraPosition = GetOrientation().GetTranslation();
399 11 : if (boundigBox.IsPointInside(cameraPosition))
400 1 : return CBoundingBoxAligned(CVector3D(-1.0f, -1.0f, 0.0f), CVector3D(1.0f, 1.0f, 0.0f));
401 :
402 10 : const CMatrix3D viewProjection = GetViewProjection();
403 10 : CBoundingBoxAligned viewPortBounds;
404 : #define ADD_VISIBLE_POINT_TO_VIEWBOUNDS(POSITION) STMT( \
405 : CVector4D v = viewProjection.Transform(CVector4D((POSITION).X, (POSITION).Y, (POSITION).Z, 1.0f)); \
406 : if (v.W != 0.0f) \
407 : viewPortBounds += CVector3D(v.X, v.Y, v.Z) * (1.0f / v.W); )
408 :
409 10 : std::array<CVector3D, 8> worldPositions;
410 : std::array<bool, 8> isBehindNearPlane;
411 10 : const CVector3D lookDirection = GetOrientation().GetIn();
412 : // Check corners.
413 90 : for (size_t idx = 0; idx < 8; ++idx)
414 : {
415 80 : worldPositions[idx] = CVector3D(boundigBox[(idx >> 0) & 0x1].X, boundigBox[(idx >> 1) & 0x1].Y, boundigBox[(idx >> 2) & 0x1].Z);
416 80 : isBehindNearPlane[idx] = lookDirection.Dot(worldPositions[idx]) < lookDirection.Dot(cameraPosition) + GetNearPlane();
417 80 : if (!isBehindNearPlane[idx])
418 68 : ADD_VISIBLE_POINT_TO_VIEWBOUNDS(worldPositions[idx]);
419 : }
420 : // Check edges for intersections with the near plane.
421 90 : for (size_t idxBegin = 0; idxBegin < 8; ++idxBegin)
422 320 : for (size_t nextComponent = 0; nextComponent < 3; ++nextComponent)
423 : {
424 240 : const size_t idxEnd = idxBegin | (1u << nextComponent);
425 240 : if (idxBegin == idxEnd || isBehindNearPlane[idxBegin] == isBehindNearPlane[idxEnd])
426 460 : continue;
427 12 : CVector3D intersection;
428 : // Intersect the segment with the near plane.
429 12 : if (!m_ViewFrustum[5].FindLineSegIntersection(worldPositions[idxBegin], worldPositions[idxEnd], &intersection))
430 4 : continue;
431 8 : ADD_VISIBLE_POINT_TO_VIEWBOUNDS(intersection);
432 : }
433 : #undef ADD_VISIBLE_POINT_TO_VIEWBOUNDS
434 10 : if (viewPortBounds[0].X >= 1.0f || viewPortBounds[1].X <= -1.0f || viewPortBounds[0].Y >= 1.0f || viewPortBounds[1].Y <= -1.0f)
435 4 : return CBoundingBoxAligned{};
436 6 : return viewPortBounds;
437 : }
438 :
439 9 : void CCamera::LookAt(const CVector3D& camera, const CVector3D& focus, const CVector3D& up)
440 : {
441 9 : CVector3D delta = focus - camera;
442 9 : LookAlong(camera, delta, up);
443 9 : }
444 :
445 13 : void CCamera::LookAlong(const CVector3D& camera, CVector3D orientation, CVector3D up)
446 : {
447 13 : orientation.Normalize();
448 13 : up.Normalize();
449 13 : const CVector3D s = orientation.Cross(up);
450 13 : up = s.Cross(orientation);
451 :
452 13 : m_Orientation._11 = -s.X; m_Orientation._12 = up.X; m_Orientation._13 = orientation.X; m_Orientation._14 = camera.X;
453 13 : m_Orientation._21 = -s.Y; m_Orientation._22 = up.Y; m_Orientation._23 = orientation.Y; m_Orientation._24 = camera.Y;
454 13 : m_Orientation._31 = -s.Z; m_Orientation._32 = up.Z; m_Orientation._33 = orientation.Z; m_Orientation._34 = camera.Z;
455 13 : m_Orientation._41 = 0.0f; m_Orientation._42 = 0.0f; m_Orientation._43 = 0.0f; m_Orientation._44 = 1.0f;
456 16 : }
|