LCOV - code coverage report
Current view: top level - source/graphics - CameraController.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 348 0.3 %
Date: 2023-01-19 00:18:29 Functions: 2 23 8.7 %

          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 : }

Generated by: LCOV version 1.13