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 "CameraController.h"
21 :
22 : #include "graphics/HFTracer.h"
23 : #include "graphics/Terrain.h"
24 : #include "i18n/L10n.h"
25 : #include "lib/input.h"
26 : #include "lib/timer.h"
27 : #include "maths/MathUtil.h"
28 : #include "maths/Matrix3D.h"
29 : #include "maths/Quaternion.h"
30 : #include "ps/CLogger.h"
31 : #include "ps/ConfigDB.h"
32 : #include "ps/Game.h"
33 : #include "ps/Globals.h"
34 : #include "ps/Hotkey.h"
35 : #include "ps/Pyrogenesis.h"
36 : #include "ps/TouchInput.h"
37 : #include "ps/World.h"
38 : #include "renderer/Renderer.h"
39 : #include "renderer/SceneRenderer.h"
40 : #include "renderer/WaterManager.h"
41 : #include "simulation2/Simulation2.h"
42 : #include "simulation2/components/ICmpPosition.h"
43 : #include "simulation2/components/ICmpRangeManager.h"
44 : #include "simulation2/helpers/Los.h"
45 :
46 : extern int g_xres, g_yres;
47 :
48 : // Maximum distance outside the edge of the map that the camera's
49 : // focus point can be moved
50 : static const float CAMERA_EDGE_MARGIN = 2.0f * TERRAIN_TILE_SIZE;
51 :
52 0 : CCameraController::CCameraController(CCamera& camera)
53 : : ICameraController(camera),
54 : m_ConstrainCamera(true),
55 : m_FollowEntity(INVALID_ENTITY),
56 : m_FollowFirstPerson(false),
57 :
58 : // Dummy values (these will be filled in by the config file)
59 : m_ViewScrollSpeed(0),
60 : m_ViewScrollSpeedModifier(1),
61 : m_ViewScrollMouseDetectDistance(3),
62 : m_ViewRotateXSpeed(0),
63 : m_ViewRotateXMin(0),
64 : m_ViewRotateXMax(0),
65 : m_ViewRotateXDefault(0),
66 : m_ViewRotateYSpeed(0),
67 : m_ViewRotateYSpeedWheel(0),
68 : m_ViewRotateYDefault(0),
69 : m_ViewRotateSpeedModifier(1),
70 : m_ViewDragSpeed(0),
71 : m_ViewZoomSpeed(0),
72 : m_ViewZoomSpeedWheel(0),
73 : m_ViewZoomMin(0),
74 : m_ViewZoomMax(0),
75 : m_ViewZoomDefault(0),
76 : m_ViewZoomSpeedModifier(1),
77 : m_ViewFOV(DEGTORAD(45.f)),
78 : m_ViewNear(2.f),
79 : m_ViewFar(4096.f),
80 : m_HeightSmoothness(0.5f),
81 : m_HeightMin(16.f),
82 :
83 : m_PosX(0, 0, 0.01f),
84 : m_PosY(0, 0, 0.01f),
85 : m_PosZ(0, 0, 0.01f),
86 : m_Zoom(0, 0, 0.1f),
87 : m_RotateX(0, 0, 0.001f),
88 0 : m_RotateY(0, 0, 0.001f)
89 : {
90 : SViewPort vp;
91 0 : vp.m_X = 0;
92 0 : vp.m_Y = 0;
93 0 : vp.m_Width = g_xres;
94 0 : vp.m_Height = g_yres;
95 0 : m_Camera.SetViewPort(vp);
96 :
97 0 : SetCameraProjection();
98 0 : SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
99 0 : m_Camera.UpdateFrustum();
100 0 : }
101 :
102 : CCameraController::~CCameraController() = default;
103 :
104 0 : void CCameraController::LoadConfig()
105 : {
106 0 : CFG_GET_VAL("view.scroll.speed", m_ViewScrollSpeed);
107 0 : CFG_GET_VAL("view.scroll.speed.modifier", m_ViewScrollSpeedModifier);
108 0 : CFG_GET_VAL("view.scroll.mouse.detectdistance", m_ViewScrollMouseDetectDistance);
109 0 : CFG_GET_VAL("view.rotate.x.speed", m_ViewRotateXSpeed);
110 0 : CFG_GET_VAL("view.rotate.x.min", m_ViewRotateXMin);
111 0 : CFG_GET_VAL("view.rotate.x.max", m_ViewRotateXMax);
112 0 : CFG_GET_VAL("view.rotate.x.default", m_ViewRotateXDefault);
113 0 : CFG_GET_VAL("view.rotate.y.speed", m_ViewRotateYSpeed);
114 0 : CFG_GET_VAL("view.rotate.y.speed.wheel", m_ViewRotateYSpeedWheel);
115 0 : CFG_GET_VAL("view.rotate.y.default", m_ViewRotateYDefault);
116 0 : CFG_GET_VAL("view.rotate.speed.modifier", m_ViewRotateSpeedModifier);
117 0 : CFG_GET_VAL("view.drag.speed", m_ViewDragSpeed);
118 0 : CFG_GET_VAL("view.zoom.speed", m_ViewZoomSpeed);
119 0 : CFG_GET_VAL("view.zoom.speed.wheel", m_ViewZoomSpeedWheel);
120 0 : CFG_GET_VAL("view.zoom.min", m_ViewZoomMin);
121 0 : CFG_GET_VAL("view.zoom.max", m_ViewZoomMax);
122 0 : CFG_GET_VAL("view.zoom.default", m_ViewZoomDefault);
123 0 : CFG_GET_VAL("view.zoom.speed.modifier", m_ViewZoomSpeedModifier);
124 :
125 0 : CFG_GET_VAL("view.height.smoothness", m_HeightSmoothness);
126 0 : CFG_GET_VAL("view.height.min", m_HeightMin);
127 :
128 : #define SETUP_SMOOTHNESS(CFG_PREFIX, SMOOTHED_VALUE) \
129 : { \
130 : float smoothness = SMOOTHED_VALUE.GetSmoothness(); \
131 : CFG_GET_VAL(CFG_PREFIX ".smoothness", smoothness); \
132 : SMOOTHED_VALUE.SetSmoothness(smoothness); \
133 : }
134 :
135 0 : SETUP_SMOOTHNESS("view.pos", m_PosX);
136 0 : SETUP_SMOOTHNESS("view.pos", m_PosY);
137 0 : SETUP_SMOOTHNESS("view.pos", m_PosZ);
138 0 : SETUP_SMOOTHNESS("view.zoom", m_Zoom);
139 0 : SETUP_SMOOTHNESS("view.rotate.x", m_RotateX);
140 0 : SETUP_SMOOTHNESS("view.rotate.y", m_RotateY);
141 : #undef SETUP_SMOOTHNESS
142 :
143 0 : CFG_GET_VAL("view.near", m_ViewNear);
144 0 : CFG_GET_VAL("view.far", m_ViewFar);
145 0 : CFG_GET_VAL("view.fov", m_ViewFOV);
146 :
147 : // Convert to radians
148 0 : m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
149 0 : m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
150 0 : m_ViewFOV = DEGTORAD(m_ViewFOV);
151 0 : }
152 :
153 0 : void CCameraController::SetViewport(const SViewPort& vp)
154 : {
155 0 : m_Camera.SetViewPort(vp);
156 0 : SetCameraProjection();
157 0 : }
158 :
159 0 : void CCameraController::Update(const float deltaRealTime)
160 : {
161 : // Calculate mouse movement
162 : static int mouse_last_x = 0;
163 : static int mouse_last_y = 0;
164 0 : int mouse_dx = g_mouse_x - mouse_last_x;
165 0 : int mouse_dy = g_mouse_y - mouse_last_y;
166 0 : mouse_last_x = g_mouse_x;
167 0 : mouse_last_y = g_mouse_y;
168 :
169 0 : if (HotkeyIsPressed("camera.rotate.cw"))
170 0 : m_RotateY.AddSmoothly(m_ViewRotateYSpeed * deltaRealTime);
171 0 : if (HotkeyIsPressed("camera.rotate.ccw"))
172 0 : m_RotateY.AddSmoothly(-m_ViewRotateYSpeed * deltaRealTime);
173 0 : if (HotkeyIsPressed("camera.rotate.up"))
174 0 : m_RotateX.AddSmoothly(-m_ViewRotateXSpeed * deltaRealTime);
175 0 : if (HotkeyIsPressed("camera.rotate.down"))
176 0 : m_RotateX.AddSmoothly(m_ViewRotateXSpeed * deltaRealTime);
177 :
178 0 : float moveRightward = 0.f;
179 0 : float moveForward = 0.f;
180 :
181 0 : if (HotkeyIsPressed("camera.pan"))
182 : {
183 0 : moveRightward += m_ViewDragSpeed * mouse_dx;
184 0 : moveForward += m_ViewDragSpeed * -mouse_dy;
185 : }
186 :
187 0 : if (g_mouse_active && m_ViewScrollMouseDetectDistance > 0)
188 : {
189 0 : if (g_mouse_x >= g_xres - m_ViewScrollMouseDetectDistance && g_mouse_x < g_xres)
190 0 : moveRightward += m_ViewScrollSpeed * deltaRealTime;
191 0 : else if (g_mouse_x < m_ViewScrollMouseDetectDistance && g_mouse_x >= 0)
192 0 : moveRightward -= m_ViewScrollSpeed * deltaRealTime;
193 :
194 0 : if (g_mouse_y >= g_yres - m_ViewScrollMouseDetectDistance && g_mouse_y < g_yres)
195 0 : moveForward -= m_ViewScrollSpeed * deltaRealTime;
196 0 : else if (g_mouse_y < m_ViewScrollMouseDetectDistance && g_mouse_y >= 0)
197 0 : moveForward += m_ViewScrollSpeed * deltaRealTime;
198 : }
199 :
200 0 : if (HotkeyIsPressed("camera.right"))
201 0 : moveRightward += m_ViewScrollSpeed * deltaRealTime;
202 0 : if (HotkeyIsPressed("camera.left"))
203 0 : moveRightward -= m_ViewScrollSpeed * deltaRealTime;
204 0 : if (HotkeyIsPressed("camera.up"))
205 0 : moveForward += m_ViewScrollSpeed * deltaRealTime;
206 0 : if (HotkeyIsPressed("camera.down"))
207 0 : moveForward -= m_ViewScrollSpeed * deltaRealTime;
208 :
209 0 : if (moveRightward || moveForward)
210 : {
211 : // Break out of following mode when the user starts scrolling
212 0 : m_FollowEntity = INVALID_ENTITY;
213 :
214 0 : float s = sin(m_RotateY.GetSmoothedValue());
215 0 : float c = cos(m_RotateY.GetSmoothedValue());
216 0 : m_PosX.AddSmoothly(c * moveRightward);
217 0 : m_PosZ.AddSmoothly(-s * moveRightward);
218 0 : m_PosX.AddSmoothly(s * moveForward);
219 0 : m_PosZ.AddSmoothly(c * moveForward);
220 : }
221 :
222 0 : if (m_FollowEntity)
223 : {
224 0 : CmpPtr<ICmpPosition> cmpPosition(*(g_Game->GetSimulation2()), m_FollowEntity);
225 0 : CmpPtr<ICmpRangeManager> cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
226 0 : if (cmpPosition && cmpPosition->IsInWorld() &&
227 0 : cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == LosVisibility::VISIBLE)
228 : {
229 : // Get the most recent interpolated position
230 0 : float frameOffset = g_Game->GetSimulation2()->GetLastFrameOffset();
231 0 : CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
232 0 : CVector3D pos = transform.GetTranslation();
233 :
234 0 : if (m_FollowFirstPerson)
235 : {
236 : float x, z, angle;
237 0 : cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle);
238 0 : float height = 4.f;
239 0 : m_Camera.m_Orientation.SetIdentity();
240 0 : m_Camera.m_Orientation.RotateX(static_cast<float>(M_PI) / 24.f);
241 0 : m_Camera.m_Orientation.RotateY(angle);
242 0 : m_Camera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z);
243 :
244 0 : m_Camera.UpdateFrustum();
245 0 : return;
246 : }
247 : else
248 : {
249 : // Move the camera to match the unit
250 0 : CCamera targetCam = m_Camera;
251 0 : SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
252 :
253 0 : CVector3D pivot = GetSmoothPivot(targetCam);
254 0 : CVector3D delta = pos - pivot;
255 0 : m_PosX.AddSmoothly(delta.X);
256 0 : m_PosY.AddSmoothly(delta.Y);
257 0 : m_PosZ.AddSmoothly(delta.Z);
258 : }
259 : }
260 : else
261 : {
262 : // The unit disappeared (died or garrisoned etc), so stop following it
263 0 : m_FollowEntity = INVALID_ENTITY;
264 : }
265 : }
266 :
267 0 : if (HotkeyIsPressed("camera.zoom.in"))
268 0 : m_Zoom.AddSmoothly(-m_ViewZoomSpeed * deltaRealTime);
269 0 : if (HotkeyIsPressed("camera.zoom.out"))
270 0 : m_Zoom.AddSmoothly(m_ViewZoomSpeed * deltaRealTime);
271 :
272 0 : if (m_ConstrainCamera)
273 0 : m_Zoom.ClampSmoothly(m_ViewZoomMin, m_ViewZoomMax);
274 :
275 0 : float zoomDelta = -m_Zoom.Update(deltaRealTime);
276 0 : if (zoomDelta)
277 : {
278 0 : CVector3D forwards = m_Camera.GetOrientation().GetIn();
279 0 : m_PosX.AddSmoothly(forwards.X * zoomDelta);
280 0 : m_PosY.AddSmoothly(forwards.Y * zoomDelta);
281 0 : m_PosZ.AddSmoothly(forwards.Z * zoomDelta);
282 : }
283 :
284 0 : if (m_ConstrainCamera)
285 0 : m_RotateX.ClampSmoothly(DEGTORAD(m_ViewRotateXMin), DEGTORAD(m_ViewRotateXMax));
286 :
287 0 : FocusHeight(true);
288 :
289 : // Ensure the ViewCamera focus is inside the map with the chosen margins
290 : // if not so - apply margins to the camera
291 0 : if (m_ConstrainCamera)
292 : {
293 0 : CCamera targetCam = m_Camera;
294 0 : SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
295 :
296 0 : CTerrain* pTerrain = g_Game->GetWorld()->GetTerrain();
297 :
298 0 : CVector3D pivot = GetSmoothPivot(targetCam);
299 0 : CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
300 :
301 0 : CVector3D desiredPivot = pivot;
302 :
303 0 : CmpPtr<ICmpRangeManager> cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
304 0 : if (cmpRangeManager && cmpRangeManager->GetLosCircular())
305 : {
306 : // Clamp to a circular region around the center of the map
307 0 : float r = pTerrain->GetMaxX() / 2;
308 0 : CVector3D center(r, desiredPivot.Y, r);
309 0 : float dist = (desiredPivot - center).Length();
310 0 : if (dist > r - CAMERA_EDGE_MARGIN)
311 0 : desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN);
312 : }
313 : else
314 : {
315 : // Clamp to the square edges of the map
316 0 : desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
317 0 : desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN);
318 : }
319 :
320 : // Update the position so that pivot is within the margin
321 0 : m_PosX.SetValueSmoothly(desiredPivot.X + delta.X);
322 0 : m_PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z);
323 : }
324 :
325 0 : m_PosX.Update(deltaRealTime);
326 0 : m_PosY.Update(deltaRealTime);
327 0 : m_PosZ.Update(deltaRealTime);
328 :
329 : // Handle rotation around the Y (vertical) axis
330 : {
331 0 : CCamera targetCam = m_Camera;
332 0 : SetupCameraMatrixSmooth(&targetCam.m_Orientation);
333 :
334 0 : float rotateYDelta = m_RotateY.Update(deltaRealTime);
335 0 : if (rotateYDelta)
336 : {
337 : // We've updated RotateY, and need to adjust Pos so that it's still
338 : // facing towards the original focus point (the terrain in the center
339 : // of the screen).
340 :
341 0 : CVector3D upwards(0.0f, 1.0f, 0.0f);
342 :
343 0 : CVector3D pivot = GetSmoothPivot(targetCam);
344 0 : CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
345 :
346 0 : CQuaternion q;
347 0 : q.FromAxisAngle(upwards, rotateYDelta);
348 0 : CVector3D d = q.Rotate(delta) - delta;
349 :
350 0 : m_PosX.Add(d.X);
351 0 : m_PosY.Add(d.Y);
352 0 : m_PosZ.Add(d.Z);
353 : }
354 : }
355 :
356 : // Handle rotation around the X (sideways, relative to camera) axis
357 : {
358 0 : CCamera targetCam = m_Camera;
359 0 : SetupCameraMatrixSmooth(&targetCam.m_Orientation);
360 :
361 0 : float rotateXDelta = m_RotateX.Update(deltaRealTime);
362 0 : if (rotateXDelta)
363 : {
364 0 : CVector3D rightwards = targetCam.GetOrientation().GetLeft() * -1.0f;
365 :
366 0 : CVector3D pivot = GetSmoothPivot(targetCam);
367 0 : CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
368 :
369 0 : CQuaternion q;
370 0 : q.FromAxisAngle(rightwards, rotateXDelta);
371 0 : CVector3D d = q.Rotate(delta) - delta;
372 :
373 0 : m_PosX.Add(d.X);
374 0 : m_PosY.Add(d.Y);
375 0 : m_PosZ.Add(d.Z);
376 : }
377 : }
378 :
379 : /* This is disabled since it doesn't seem necessary:
380 :
381 : // Ensure the camera's near point is never inside the terrain
382 : if (m_ConstrainCamera)
383 : {
384 : CMatrix3D target;
385 : target.SetIdentity();
386 : target.RotateX(m_RotateX.GetValue());
387 : target.RotateY(m_RotateY.GetValue());
388 : target.Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
389 :
390 : CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear;
391 : float ground = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
392 : float limit = ground + 16.f;
393 : if (nearPoint.Y < limit)
394 : m_PosY.AddSmoothly(limit - nearPoint.Y);
395 : }
396 : */
397 :
398 0 : m_RotateY.Wrap(-static_cast<float>(M_PI), static_cast<float>(M_PI));
399 :
400 : // Update the camera matrix
401 0 : SetCameraProjection();
402 0 : SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
403 0 : m_Camera.UpdateFrustum();
404 : }
405 :
406 0 : CVector3D CCameraController::GetSmoothPivot(CCamera& camera) const
407 : {
408 0 : return camera.GetOrientation().GetTranslation() + camera.GetOrientation().GetIn() * m_Zoom.GetSmoothedValue();
409 : }
410 :
411 0 : CVector3D CCameraController::GetCameraPivot() const
412 : {
413 0 : return GetSmoothPivot(m_Camera);
414 : }
415 :
416 0 : CVector3D CCameraController::GetCameraPosition() const
417 : {
418 0 : return CVector3D(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
419 : }
420 :
421 0 : CVector3D CCameraController::GetCameraRotation() const
422 : {
423 : // The angle of rotation around the Z axis is not used.
424 0 : return CVector3D(m_RotateX.GetValue(), m_RotateY.GetValue(), 0.0f);
425 : }
426 :
427 0 : float CCameraController::GetCameraZoom() const
428 : {
429 0 : return m_Zoom.GetValue();
430 : }
431 :
432 0 : void CCameraController::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
433 : {
434 0 : m_PosX.SetValue(pos.X);
435 0 : m_PosY.SetValue(pos.Y);
436 0 : m_PosZ.SetValue(pos.Z);
437 0 : m_RotateX.SetValue(rotX);
438 0 : m_RotateY.SetValue(rotY);
439 0 : m_Zoom.SetValue(zoom);
440 :
441 0 : FocusHeight(false);
442 :
443 0 : SetupCameraMatrixNonSmooth(&m_Camera.m_Orientation);
444 0 : m_Camera.UpdateFrustum();
445 :
446 : // Break out of following mode so the camera really moves to the target
447 0 : m_FollowEntity = INVALID_ENTITY;
448 0 : }
449 :
450 0 : void CCameraController::MoveCameraTarget(const CVector3D& target)
451 : {
452 : // Maintain the same orientation and level of zoom, if we can
453 : // (do this by working out the point the camera is looking at, saving
454 : // the difference between that position and the camera point, and restoring
455 : // that difference to our new target)
456 :
457 0 : CCamera targetCam = m_Camera;
458 0 : SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
459 :
460 0 : CVector3D pivot = GetSmoothPivot(targetCam);
461 0 : CVector3D delta = target - pivot;
462 :
463 0 : m_PosX.SetValueSmoothly(delta.X + m_PosX.GetValue());
464 0 : m_PosZ.SetValueSmoothly(delta.Z + m_PosZ.GetValue());
465 :
466 0 : FocusHeight(false);
467 :
468 : // Break out of following mode so the camera really moves to the target
469 0 : m_FollowEntity = INVALID_ENTITY;
470 0 : }
471 :
472 0 : void CCameraController::ResetCameraTarget(const CVector3D& target)
473 : {
474 0 : CMatrix3D orientation;
475 0 : orientation.SetIdentity();
476 0 : orientation.RotateX(DEGTORAD(m_ViewRotateXDefault));
477 0 : orientation.RotateY(DEGTORAD(m_ViewRotateYDefault));
478 :
479 0 : CVector3D delta = orientation.GetIn() * m_ViewZoomDefault;
480 0 : m_PosX.SetValue(target.X - delta.X);
481 0 : m_PosY.SetValue(target.Y - delta.Y);
482 0 : m_PosZ.SetValue(target.Z - delta.Z);
483 0 : m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
484 0 : m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
485 0 : m_Zoom.SetValue(m_ViewZoomDefault);
486 :
487 0 : FocusHeight(false);
488 :
489 0 : SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
490 0 : m_Camera.UpdateFrustum();
491 :
492 : // Break out of following mode so the camera really moves to the target
493 0 : m_FollowEntity = INVALID_ENTITY;
494 0 : }
495 :
496 0 : void CCameraController::FollowEntity(entity_id_t entity, bool firstPerson)
497 : {
498 0 : m_FollowEntity = entity;
499 0 : m_FollowFirstPerson = firstPerson;
500 0 : }
501 :
502 0 : entity_id_t CCameraController::GetFollowedEntity()
503 : {
504 0 : return m_FollowEntity;
505 : }
506 :
507 0 : void CCameraController::SetCameraProjection()
508 : {
509 0 : m_Camera.SetPerspectiveProjection(m_ViewNear, m_ViewFar, m_ViewFOV);
510 0 : }
511 :
512 0 : void CCameraController::ResetCameraAngleZoom()
513 : {
514 0 : CCamera targetCam = m_Camera;
515 0 : SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
516 :
517 : // Compute the zoom adjustment to get us back to the default
518 0 : CVector3D forwards = targetCam.GetOrientation().GetIn();
519 :
520 0 : CVector3D pivot = GetSmoothPivot(targetCam);
521 0 : CVector3D delta = pivot - targetCam.GetOrientation().GetTranslation();
522 0 : float dist = delta.Dot(forwards);
523 0 : m_Zoom.AddSmoothly(m_ViewZoomDefault - dist);
524 :
525 : // Reset orientations to default
526 0 : m_RotateX.SetValueSmoothly(DEGTORAD(m_ViewRotateXDefault));
527 0 : m_RotateY.SetValueSmoothly(DEGTORAD(m_ViewRotateYDefault));
528 0 : }
529 :
530 0 : void CCameraController::SetupCameraMatrixSmooth(CMatrix3D* orientation)
531 : {
532 0 : orientation->SetIdentity();
533 0 : orientation->RotateX(m_RotateX.GetSmoothedValue());
534 0 : orientation->RotateY(m_RotateY.GetSmoothedValue());
535 0 : orientation->Translate(m_PosX.GetSmoothedValue(), m_PosY.GetSmoothedValue(), m_PosZ.GetSmoothedValue());
536 0 : }
537 :
538 0 : void CCameraController::SetupCameraMatrixSmoothRot(CMatrix3D* orientation)
539 : {
540 0 : orientation->SetIdentity();
541 0 : orientation->RotateX(m_RotateX.GetSmoothedValue());
542 0 : orientation->RotateY(m_RotateY.GetSmoothedValue());
543 0 : orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
544 0 : }
545 :
546 0 : void CCameraController::SetupCameraMatrixNonSmooth(CMatrix3D* orientation)
547 : {
548 0 : orientation->SetIdentity();
549 0 : orientation->RotateX(m_RotateX.GetValue());
550 0 : orientation->RotateY(m_RotateY.GetValue());
551 0 : orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
552 0 : }
553 :
554 0 : void CCameraController::FocusHeight(bool smooth)
555 : {
556 : /*
557 : The camera pivot height is moved towards ground level.
558 : To prevent excessive zoom when looking over a cliff,
559 : the target ground level is the maximum of the ground level at the camera's near and pivot points.
560 : The ground levels are filtered to achieve smooth camera movement.
561 : The filter radius is proportional to the zoom level.
562 : The camera height is clamped to prevent map penetration.
563 : */
564 :
565 0 : if (!m_ConstrainCamera)
566 0 : return;
567 :
568 0 : CCamera targetCam = m_Camera;
569 0 : SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
570 :
571 0 : const CVector3D position = targetCam.GetOrientation().GetTranslation();
572 0 : const CVector3D forwards = targetCam.GetOrientation().GetIn();
573 :
574 : // horizontal view radius
575 0 : const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m_Zoom.GetSmoothedValue();
576 0 : const float near_radius = radius * m_HeightSmoothness;
577 0 : const float pivot_radius = radius * m_HeightSmoothness;
578 :
579 0 : const CVector3D nearPoint = position + forwards * m_ViewNear;
580 0 : const CVector3D pivotPoint = position + forwards * m_Zoom.GetSmoothedValue();
581 :
582 : const float ground = std::max(
583 0 : g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z),
584 0 : g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight);
585 :
586 : // filter ground levels for smooth camera movement
587 0 : const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
588 0 : const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
589 :
590 : // filtered maximum visible ground level in view
591 : const float filtered_ground = std::max(
592 : std::max(filtered_near_ground, filtered_pivot_ground),
593 0 : g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight);
594 :
595 : // target camera height above pivot point
596 0 : const float pivot_height = -forwards.Y * (m_Zoom.GetSmoothedValue() - m_ViewNear);
597 :
598 : // minimum camera height above filtered ground level
599 0 : const float min_height = (m_HeightMin + ground - filtered_ground);
600 :
601 0 : const float target_height = std::max(pivot_height, min_height);
602 0 : const float height = (nearPoint.Y - filtered_ground);
603 0 : const float diff = target_height - height;
604 :
605 0 : if (fabsf(diff) < 0.0001f)
606 0 : return;
607 :
608 0 : if (smooth)
609 0 : m_PosY.AddSmoothly(diff);
610 : else
611 0 : m_PosY.Add(diff);
612 : }
613 :
614 0 : InReaction CCameraController::HandleEvent(const SDL_Event_* ev)
615 : {
616 0 : switch (ev->ev.type)
617 : {
618 0 : case SDL_HOTKEYPRESS:
619 : {
620 0 : std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
621 0 : if (hotkey == "camera.reset")
622 : {
623 0 : ResetCameraAngleZoom();
624 0 : return IN_HANDLED;
625 : }
626 0 : return IN_PASS;
627 : }
628 :
629 0 : case SDL_HOTKEYDOWN:
630 : {
631 0 : std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
632 :
633 : // Mouse wheel must be treated using events instead of polling,
634 : // because SDL auto-generates a sequence of mousedown/mouseup events
635 : // and we never get to see the "down" state inside Update().
636 0 : if (hotkey == "camera.zoom.wheel.in")
637 : {
638 0 : m_Zoom.AddSmoothly(-m_ViewZoomSpeedWheel);
639 0 : return IN_HANDLED;
640 : }
641 0 : else if (hotkey == "camera.zoom.wheel.out")
642 : {
643 0 : m_Zoom.AddSmoothly(m_ViewZoomSpeedWheel);
644 0 : return IN_HANDLED;
645 : }
646 0 : else if (hotkey == "camera.rotate.wheel.cw")
647 : {
648 0 : m_RotateY.AddSmoothly(m_ViewRotateYSpeedWheel);
649 0 : return IN_HANDLED;
650 : }
651 0 : else if (hotkey == "camera.rotate.wheel.ccw")
652 : {
653 0 : m_RotateY.AddSmoothly(-m_ViewRotateYSpeedWheel);
654 0 : return IN_HANDLED;
655 : }
656 0 : else if (hotkey == "camera.scroll.speed.increase")
657 : {
658 0 : m_ViewScrollSpeed *= m_ViewScrollSpeedModifier;
659 0 : LOGMESSAGERENDER(g_L10n.Translate("Scroll speed increased to %.1f"), m_ViewScrollSpeed);
660 0 : return IN_HANDLED;
661 : }
662 0 : else if (hotkey == "camera.scroll.speed.decrease")
663 : {
664 0 : m_ViewScrollSpeed /= m_ViewScrollSpeedModifier;
665 0 : LOGMESSAGERENDER(g_L10n.Translate("Scroll speed decreased to %.1f"), m_ViewScrollSpeed);
666 0 : return IN_HANDLED;
667 : }
668 0 : else if (hotkey == "camera.rotate.speed.increase")
669 : {
670 0 : m_ViewRotateXSpeed *= m_ViewRotateSpeedModifier;
671 0 : m_ViewRotateYSpeed *= m_ViewRotateSpeedModifier;
672 0 : LOGMESSAGERENDER(g_L10n.Translate("Rotate speed increased to X=%.3f, Y=%.3f"), m_ViewRotateXSpeed, m_ViewRotateYSpeed);
673 0 : return IN_HANDLED;
674 : }
675 0 : else if (hotkey == "camera.rotate.speed.decrease")
676 : {
677 0 : m_ViewRotateXSpeed /= m_ViewRotateSpeedModifier;
678 0 : m_ViewRotateYSpeed /= m_ViewRotateSpeedModifier;
679 0 : LOGMESSAGERENDER(g_L10n.Translate("Rotate speed decreased to X=%.3f, Y=%.3f"), m_ViewRotateXSpeed, m_ViewRotateYSpeed);
680 0 : return IN_HANDLED;
681 : }
682 0 : else if (hotkey == "camera.zoom.speed.increase")
683 : {
684 0 : m_ViewZoomSpeed *= m_ViewZoomSpeedModifier;
685 0 : LOGMESSAGERENDER(g_L10n.Translate("Zoom speed increased to %.1f"), m_ViewZoomSpeed);
686 0 : return IN_HANDLED;
687 : }
688 0 : else if (hotkey == "camera.zoom.speed.decrease")
689 : {
690 0 : m_ViewZoomSpeed /= m_ViewZoomSpeedModifier;
691 0 : LOGMESSAGERENDER(g_L10n.Translate("Zoom speed decreased to %.1f"), m_ViewZoomSpeed);
692 0 : return IN_HANDLED;
693 : }
694 0 : return IN_PASS;
695 : }
696 : }
697 :
698 0 : return IN_PASS;
699 3 : }
|