[Gameplay] Reduce loom cost
[0ad.git] / source / graphics / CameraController.cpp
blobb3d1402c94adfa1379cf5cbc6919a62ac3f0ff79
1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "CameraController.h"
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"
46 extern int g_xres, g_yres;
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;
52 CCameraController::CCameraController(CCamera& camera)
53 : ICameraController(camera),
54 m_ConstrainCamera(true),
55 m_FollowEntity(INVALID_ENTITY),
56 m_FollowFirstPerson(false),
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),
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 m_RotateY(0, 0, 0.001f)
90 SViewPort vp;
91 vp.m_X = 0;
92 vp.m_Y = 0;
93 vp.m_Width = g_xres;
94 vp.m_Height = g_yres;
95 m_Camera.SetViewPort(vp);
97 SetCameraProjection();
98 SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
99 m_Camera.UpdateFrustum();
102 CCameraController::~CCameraController() = default;
104 void CCameraController::LoadConfig()
106 CFG_GET_VAL("view.scroll.speed", m_ViewScrollSpeed);
107 CFG_GET_VAL("view.scroll.speed.modifier", m_ViewScrollSpeedModifier);
108 CFG_GET_VAL("view.scroll.mouse.detectdistance", m_ViewScrollMouseDetectDistance);
109 CFG_GET_VAL("view.rotate.x.speed", m_ViewRotateXSpeed);
110 CFG_GET_VAL("view.rotate.x.min", m_ViewRotateXMin);
111 CFG_GET_VAL("view.rotate.x.max", m_ViewRotateXMax);
112 CFG_GET_VAL("view.rotate.x.default", m_ViewRotateXDefault);
113 CFG_GET_VAL("view.rotate.y.speed", m_ViewRotateYSpeed);
114 CFG_GET_VAL("view.rotate.y.speed.wheel", m_ViewRotateYSpeedWheel);
115 CFG_GET_VAL("view.rotate.y.default", m_ViewRotateYDefault);
116 CFG_GET_VAL("view.rotate.speed.modifier", m_ViewRotateSpeedModifier);
117 CFG_GET_VAL("view.drag.speed", m_ViewDragSpeed);
118 CFG_GET_VAL("view.zoom.speed", m_ViewZoomSpeed);
119 CFG_GET_VAL("view.zoom.speed.wheel", m_ViewZoomSpeedWheel);
120 CFG_GET_VAL("view.zoom.min", m_ViewZoomMin);
121 CFG_GET_VAL("view.zoom.max", m_ViewZoomMax);
122 CFG_GET_VAL("view.zoom.default", m_ViewZoomDefault);
123 CFG_GET_VAL("view.zoom.speed.modifier", m_ViewZoomSpeedModifier);
125 CFG_GET_VAL("view.height.smoothness", m_HeightSmoothness);
126 CFG_GET_VAL("view.height.min", m_HeightMin);
128 #define SETUP_SMOOTHNESS(CFG_PREFIX, SMOOTHED_VALUE) \
130 float smoothness = SMOOTHED_VALUE.GetSmoothness(); \
131 CFG_GET_VAL(CFG_PREFIX ".smoothness", smoothness); \
132 SMOOTHED_VALUE.SetSmoothness(smoothness); \
135 SETUP_SMOOTHNESS("view.pos", m_PosX);
136 SETUP_SMOOTHNESS("view.pos", m_PosY);
137 SETUP_SMOOTHNESS("view.pos", m_PosZ);
138 SETUP_SMOOTHNESS("view.zoom", m_Zoom);
139 SETUP_SMOOTHNESS("view.rotate.x", m_RotateX);
140 SETUP_SMOOTHNESS("view.rotate.y", m_RotateY);
141 #undef SETUP_SMOOTHNESS
143 CFG_GET_VAL("view.near", m_ViewNear);
144 CFG_GET_VAL("view.far", m_ViewFar);
145 CFG_GET_VAL("view.fov", m_ViewFOV);
147 // Convert to radians
148 m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
149 m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
150 m_ViewFOV = DEGTORAD(m_ViewFOV);
153 void CCameraController::SetViewport(const SViewPort& vp)
155 m_Camera.SetViewPort(vp);
156 SetCameraProjection();
159 void CCameraController::Update(const float deltaRealTime)
161 // Calculate mouse movement
162 static int mouse_last_x = 0;
163 static int mouse_last_y = 0;
164 int mouse_dx = g_mouse_x - mouse_last_x;
165 int mouse_dy = g_mouse_y - mouse_last_y;
166 mouse_last_x = g_mouse_x;
167 mouse_last_y = g_mouse_y;
169 if (HotkeyIsPressed("camera.rotate.cw"))
170 m_RotateY.AddSmoothly(m_ViewRotateYSpeed * deltaRealTime);
171 if (HotkeyIsPressed("camera.rotate.ccw"))
172 m_RotateY.AddSmoothly(-m_ViewRotateYSpeed * deltaRealTime);
173 if (HotkeyIsPressed("camera.rotate.up"))
174 m_RotateX.AddSmoothly(-m_ViewRotateXSpeed * deltaRealTime);
175 if (HotkeyIsPressed("camera.rotate.down"))
176 m_RotateX.AddSmoothly(m_ViewRotateXSpeed * deltaRealTime);
178 float moveRightward = 0.f;
179 float moveForward = 0.f;
181 if (HotkeyIsPressed("camera.pan"))
183 moveRightward += m_ViewDragSpeed * mouse_dx;
184 moveForward += m_ViewDragSpeed * -mouse_dy;
187 if (g_mouse_active && m_ViewScrollMouseDetectDistance > 0)
189 if (g_mouse_x >= g_xres - m_ViewScrollMouseDetectDistance && g_mouse_x < g_xres)
190 moveRightward += m_ViewScrollSpeed * deltaRealTime;
191 else if (g_mouse_x < m_ViewScrollMouseDetectDistance && g_mouse_x >= 0)
192 moveRightward -= m_ViewScrollSpeed * deltaRealTime;
194 if (g_mouse_y >= g_yres - m_ViewScrollMouseDetectDistance && g_mouse_y < g_yres)
195 moveForward -= m_ViewScrollSpeed * deltaRealTime;
196 else if (g_mouse_y < m_ViewScrollMouseDetectDistance && g_mouse_y >= 0)
197 moveForward += m_ViewScrollSpeed * deltaRealTime;
200 if (HotkeyIsPressed("camera.right"))
201 moveRightward += m_ViewScrollSpeed * deltaRealTime;
202 if (HotkeyIsPressed("camera.left"))
203 moveRightward -= m_ViewScrollSpeed * deltaRealTime;
204 if (HotkeyIsPressed("camera.up"))
205 moveForward += m_ViewScrollSpeed * deltaRealTime;
206 if (HotkeyIsPressed("camera.down"))
207 moveForward -= m_ViewScrollSpeed * deltaRealTime;
209 if (moveRightward || moveForward)
211 // Break out of following mode when the user starts scrolling
212 m_FollowEntity = INVALID_ENTITY;
214 float s = sin(m_RotateY.GetSmoothedValue());
215 float c = cos(m_RotateY.GetSmoothedValue());
216 m_PosX.AddSmoothly(c * moveRightward);
217 m_PosZ.AddSmoothly(-s * moveRightward);
218 m_PosX.AddSmoothly(s * moveForward);
219 m_PosZ.AddSmoothly(c * moveForward);
222 if (m_FollowEntity)
224 CmpPtr<ICmpPosition> cmpPosition(*(g_Game->GetSimulation2()), m_FollowEntity);
225 CmpPtr<ICmpRangeManager> cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
226 if (cmpPosition && cmpPosition->IsInWorld() &&
227 cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == LosVisibility::VISIBLE)
229 // Get the most recent interpolated position
230 float frameOffset = g_Game->GetSimulation2()->GetLastFrameOffset();
231 CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
232 CVector3D pos = transform.GetTranslation();
234 if (m_FollowFirstPerson)
236 float x, z, angle;
237 cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle);
238 float height = 4.f;
239 m_Camera.m_Orientation.SetIdentity();
240 m_Camera.m_Orientation.RotateX(static_cast<float>(M_PI) / 24.f);
241 m_Camera.m_Orientation.RotateY(angle);
242 m_Camera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z);
244 m_Camera.UpdateFrustum();
245 return;
247 else
249 // Move the camera to match the unit
250 CCamera targetCam = m_Camera;
251 SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
253 CVector3D pivot = GetSmoothPivot(targetCam);
254 CVector3D delta = pos - pivot;
255 m_PosX.AddSmoothly(delta.X);
256 m_PosY.AddSmoothly(delta.Y);
257 m_PosZ.AddSmoothly(delta.Z);
260 else
262 // The unit disappeared (died or garrisoned etc), so stop following it
263 m_FollowEntity = INVALID_ENTITY;
267 if (HotkeyIsPressed("camera.zoom.in"))
268 m_Zoom.AddSmoothly(-m_ViewZoomSpeed * deltaRealTime);
269 if (HotkeyIsPressed("camera.zoom.out"))
270 m_Zoom.AddSmoothly(m_ViewZoomSpeed * deltaRealTime);
272 if (m_ConstrainCamera)
273 m_Zoom.ClampSmoothly(m_ViewZoomMin, m_ViewZoomMax);
275 float zoomDelta = -m_Zoom.Update(deltaRealTime);
276 if (zoomDelta)
278 CVector3D forwards = m_Camera.GetOrientation().GetIn();
279 m_PosX.AddSmoothly(forwards.X * zoomDelta);
280 m_PosY.AddSmoothly(forwards.Y * zoomDelta);
281 m_PosZ.AddSmoothly(forwards.Z * zoomDelta);
284 if (m_ConstrainCamera)
285 m_RotateX.ClampSmoothly(DEGTORAD(m_ViewRotateXMin), DEGTORAD(m_ViewRotateXMax));
287 FocusHeight(true);
289 // Ensure the ViewCamera focus is inside the map with the chosen margins
290 // if not so - apply margins to the camera
291 if (m_ConstrainCamera)
293 CCamera targetCam = m_Camera;
294 SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
296 CTerrain* pTerrain = g_Game->GetWorld()->GetTerrain();
298 CVector3D pivot = GetSmoothPivot(targetCam);
299 CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
301 CVector3D desiredPivot = pivot;
303 CmpPtr<ICmpRangeManager> cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
304 if (cmpRangeManager && cmpRangeManager->GetLosCircular())
306 // Clamp to a circular region around the center of the map
307 float r = pTerrain->GetMaxX() / 2;
308 CVector3D center(r, desiredPivot.Y, r);
309 float dist = (desiredPivot - center).Length();
310 if (dist > r - CAMERA_EDGE_MARGIN)
311 desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN);
313 else
315 // Clamp to the square edges of the map
316 desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
317 desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN);
320 // Update the position so that pivot is within the margin
321 m_PosX.SetValueSmoothly(desiredPivot.X + delta.X);
322 m_PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z);
325 m_PosX.Update(deltaRealTime);
326 m_PosY.Update(deltaRealTime);
327 m_PosZ.Update(deltaRealTime);
329 // Handle rotation around the Y (vertical) axis
331 CCamera targetCam = m_Camera;
332 SetupCameraMatrixSmooth(&targetCam.m_Orientation);
334 float rotateYDelta = m_RotateY.Update(deltaRealTime);
335 if (rotateYDelta)
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).
341 CVector3D upwards(0.0f, 1.0f, 0.0f);
343 CVector3D pivot = GetSmoothPivot(targetCam);
344 CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
346 CQuaternion q;
347 q.FromAxisAngle(upwards, rotateYDelta);
348 CVector3D d = q.Rotate(delta) - delta;
350 m_PosX.Add(d.X);
351 m_PosY.Add(d.Y);
352 m_PosZ.Add(d.Z);
356 // Handle rotation around the X (sideways, relative to camera) axis
358 CCamera targetCam = m_Camera;
359 SetupCameraMatrixSmooth(&targetCam.m_Orientation);
361 float rotateXDelta = m_RotateX.Update(deltaRealTime);
362 if (rotateXDelta)
364 CVector3D rightwards = targetCam.GetOrientation().GetLeft() * -1.0f;
366 CVector3D pivot = GetSmoothPivot(targetCam);
367 CVector3D delta = targetCam.GetOrientation().GetTranslation() - pivot;
369 CQuaternion q;
370 q.FromAxisAngle(rightwards, rotateXDelta);
371 CVector3D d = q.Rotate(delta) - delta;
373 m_PosX.Add(d.X);
374 m_PosY.Add(d.Y);
375 m_PosZ.Add(d.Z);
379 /* This is disabled since it doesn't seem necessary:
381 // Ensure the camera's near point is never inside the terrain
382 if (m_ConstrainCamera)
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());
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);
398 m_RotateY.Wrap(-static_cast<float>(M_PI), static_cast<float>(M_PI));
400 // Update the camera matrix
401 SetCameraProjection();
402 SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
403 m_Camera.UpdateFrustum();
406 CVector3D CCameraController::GetSmoothPivot(CCamera& camera) const
408 return camera.GetOrientation().GetTranslation() + camera.GetOrientation().GetIn() * m_Zoom.GetSmoothedValue();
411 CVector3D CCameraController::GetCameraPivot() const
413 return GetSmoothPivot(m_Camera);
416 CVector3D CCameraController::GetCameraPosition() const
418 return CVector3D(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
421 CVector3D CCameraController::GetCameraRotation() const
423 // The angle of rotation around the Z axis is not used.
424 return CVector3D(m_RotateX.GetValue(), m_RotateY.GetValue(), 0.0f);
427 float CCameraController::GetCameraZoom() const
429 return m_Zoom.GetValue();
432 void CCameraController::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
434 m_PosX.SetValue(pos.X);
435 m_PosY.SetValue(pos.Y);
436 m_PosZ.SetValue(pos.Z);
437 m_RotateX.SetValue(rotX);
438 m_RotateY.SetValue(rotY);
439 m_Zoom.SetValue(zoom);
441 FocusHeight(false);
443 SetupCameraMatrixNonSmooth(&m_Camera.m_Orientation);
444 m_Camera.UpdateFrustum();
446 // Break out of following mode so the camera really moves to the target
447 m_FollowEntity = INVALID_ENTITY;
450 void CCameraController::MoveCameraTarget(const CVector3D& target)
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)
457 CCamera targetCam = m_Camera;
458 SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
460 CVector3D pivot = GetSmoothPivot(targetCam);
461 CVector3D delta = target - pivot;
463 m_PosX.SetValueSmoothly(delta.X + m_PosX.GetValue());
464 m_PosZ.SetValueSmoothly(delta.Z + m_PosZ.GetValue());
466 FocusHeight(false);
468 // Break out of following mode so the camera really moves to the target
469 m_FollowEntity = INVALID_ENTITY;
472 void CCameraController::ResetCameraTarget(const CVector3D& target)
474 CMatrix3D orientation;
475 orientation.SetIdentity();
476 orientation.RotateX(DEGTORAD(m_ViewRotateXDefault));
477 orientation.RotateY(DEGTORAD(m_ViewRotateYDefault));
479 CVector3D delta = orientation.GetIn() * m_ViewZoomDefault;
480 m_PosX.SetValue(target.X - delta.X);
481 m_PosY.SetValue(target.Y - delta.Y);
482 m_PosZ.SetValue(target.Z - delta.Z);
483 m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
484 m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
485 m_Zoom.SetValue(m_ViewZoomDefault);
487 FocusHeight(false);
489 SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
490 m_Camera.UpdateFrustum();
492 // Break out of following mode so the camera really moves to the target
493 m_FollowEntity = INVALID_ENTITY;
496 void CCameraController::FollowEntity(entity_id_t entity, bool firstPerson)
498 m_FollowEntity = entity;
499 m_FollowFirstPerson = firstPerson;
502 entity_id_t CCameraController::GetFollowedEntity()
504 return m_FollowEntity;
507 void CCameraController::SetCameraProjection()
509 m_Camera.SetPerspectiveProjection(m_ViewNear, m_ViewFar, m_ViewFOV);
512 void CCameraController::ResetCameraAngleZoom()
514 CCamera targetCam = m_Camera;
515 SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
517 // Compute the zoom adjustment to get us back to the default
518 CVector3D forwards = targetCam.GetOrientation().GetIn();
520 CVector3D pivot = GetSmoothPivot(targetCam);
521 CVector3D delta = pivot - targetCam.GetOrientation().GetTranslation();
522 float dist = delta.Dot(forwards);
523 m_Zoom.AddSmoothly(m_ViewZoomDefault - dist);
525 // Reset orientations to default
526 m_RotateX.SetValueSmoothly(DEGTORAD(m_ViewRotateXDefault));
527 m_RotateY.SetValueSmoothly(DEGTORAD(m_ViewRotateYDefault));
530 void CCameraController::SetupCameraMatrixSmooth(CMatrix3D* orientation)
532 orientation->SetIdentity();
533 orientation->RotateX(m_RotateX.GetSmoothedValue());
534 orientation->RotateY(m_RotateY.GetSmoothedValue());
535 orientation->Translate(m_PosX.GetSmoothedValue(), m_PosY.GetSmoothedValue(), m_PosZ.GetSmoothedValue());
538 void CCameraController::SetupCameraMatrixSmoothRot(CMatrix3D* orientation)
540 orientation->SetIdentity();
541 orientation->RotateX(m_RotateX.GetSmoothedValue());
542 orientation->RotateY(m_RotateY.GetSmoothedValue());
543 orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
546 void CCameraController::SetupCameraMatrixNonSmooth(CMatrix3D* orientation)
548 orientation->SetIdentity();
549 orientation->RotateX(m_RotateX.GetValue());
550 orientation->RotateY(m_RotateY.GetValue());
551 orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
554 void CCameraController::FocusHeight(bool smooth)
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.
565 if (!m_ConstrainCamera)
566 return;
568 CCamera targetCam = m_Camera;
569 SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
571 const CVector3D position = targetCam.GetOrientation().GetTranslation();
572 const CVector3D forwards = targetCam.GetOrientation().GetIn();
574 // horizontal view radius
575 const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m_Zoom.GetSmoothedValue();
576 const float near_radius = radius * m_HeightSmoothness;
577 const float pivot_radius = radius * m_HeightSmoothness;
579 const CVector3D nearPoint = position + forwards * m_ViewNear;
580 const CVector3D pivotPoint = position + forwards * m_Zoom.GetSmoothedValue();
582 const float ground = std::max(
583 g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z),
584 g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight);
586 // filter ground levels for smooth camera movement
587 const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
588 const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
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 g_Renderer.GetSceneRenderer().GetWaterManager().m_WaterHeight);
595 // target camera height above pivot point
596 const float pivot_height = -forwards.Y * (m_Zoom.GetSmoothedValue() - m_ViewNear);
598 // minimum camera height above filtered ground level
599 const float min_height = (m_HeightMin + ground - filtered_ground);
601 const float target_height = std::max(pivot_height, min_height);
602 const float height = (nearPoint.Y - filtered_ground);
603 const float diff = target_height - height;
605 if (fabsf(diff) < 0.0001f)
606 return;
608 if (smooth)
609 m_PosY.AddSmoothly(diff);
610 else
611 m_PosY.Add(diff);
614 InReaction CCameraController::HandleEvent(const SDL_Event_* ev)
616 switch (ev->ev.type)
618 case SDL_HOTKEYPRESS:
620 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
621 if (hotkey == "camera.reset")
623 ResetCameraAngleZoom();
624 return IN_HANDLED;
626 return IN_PASS;
629 case SDL_HOTKEYDOWN:
631 std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
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 if (hotkey == "camera.zoom.wheel.in")
638 m_Zoom.AddSmoothly(-m_ViewZoomSpeedWheel);
639 return IN_HANDLED;
641 else if (hotkey == "camera.zoom.wheel.out")
643 m_Zoom.AddSmoothly(m_ViewZoomSpeedWheel);
644 return IN_HANDLED;
646 else if (hotkey == "camera.rotate.wheel.cw")
648 m_RotateY.AddSmoothly(m_ViewRotateYSpeedWheel);
649 return IN_HANDLED;
651 else if (hotkey == "camera.rotate.wheel.ccw")
653 m_RotateY.AddSmoothly(-m_ViewRotateYSpeedWheel);
654 return IN_HANDLED;
656 else if (hotkey == "camera.scroll.speed.increase")
658 m_ViewScrollSpeed *= m_ViewScrollSpeedModifier;
659 LOGMESSAGERENDER(g_L10n.Translate("Scroll speed increased to %.1f"), m_ViewScrollSpeed);
660 return IN_HANDLED;
662 else if (hotkey == "camera.scroll.speed.decrease")
664 m_ViewScrollSpeed /= m_ViewScrollSpeedModifier;
665 LOGMESSAGERENDER(g_L10n.Translate("Scroll speed decreased to %.1f"), m_ViewScrollSpeed);
666 return IN_HANDLED;
668 else if (hotkey == "camera.rotate.speed.increase")
670 m_ViewRotateXSpeed *= m_ViewRotateSpeedModifier;
671 m_ViewRotateYSpeed *= m_ViewRotateSpeedModifier;
672 LOGMESSAGERENDER(g_L10n.Translate("Rotate speed increased to X=%.3f, Y=%.3f"), m_ViewRotateXSpeed, m_ViewRotateYSpeed);
673 return IN_HANDLED;
675 else if (hotkey == "camera.rotate.speed.decrease")
677 m_ViewRotateXSpeed /= m_ViewRotateSpeedModifier;
678 m_ViewRotateYSpeed /= m_ViewRotateSpeedModifier;
679 LOGMESSAGERENDER(g_L10n.Translate("Rotate speed decreased to X=%.3f, Y=%.3f"), m_ViewRotateXSpeed, m_ViewRotateYSpeed);
680 return IN_HANDLED;
682 else if (hotkey == "camera.zoom.speed.increase")
684 m_ViewZoomSpeed *= m_ViewZoomSpeedModifier;
685 LOGMESSAGERENDER(g_L10n.Translate("Zoom speed increased to %.1f"), m_ViewZoomSpeed);
686 return IN_HANDLED;
688 else if (hotkey == "camera.zoom.speed.decrease")
690 m_ViewZoomSpeed /= m_ViewZoomSpeedModifier;
691 LOGMESSAGERENDER(g_L10n.Translate("Zoom speed decreased to %.1f"), m_ViewZoomSpeed);
692 return IN_HANDLED;
694 return IN_PASS;
698 return IN_PASS;