diff --git a/source/graphics/CameraController.cpp b/source/graphics/CameraController.cpp
new file mode 100644
index 0000000000..e37f38dab6
--- /dev/null
+++ b/source/graphics/CameraController.cpp
@@ -0,0 +1,707 @@
+/* Copyright (C) 2019 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+
+#include "precompiled.h"
+
+#include "CameraController.h"
+
+#include "graphics/HFTracer.h"
+#include "graphics/Terrain.h"
+#include "graphics/scripting/JSInterface_GameView.h"
+#include "lib/input.h"
+#include "lib/timer.h"
+#include "maths/MathUtil.h"
+#include "maths/Matrix3D.h"
+#include "maths/Quaternion.h"
+#include "ps/ConfigDB.h"
+#include "ps/Game.h"
+#include "ps/Globals.h"
+#include "ps/Hotkey.h"
+#include "ps/Joystick.h"
+#include "ps/Pyrogenesis.h"
+#include "ps/TouchInput.h"
+#include "ps/World.h"
+#include "renderer/Renderer.h"
+#include "renderer/WaterManager.h"
+#include "simulation2/Simulation2.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+
+extern int g_xres, g_yres;
+
+// Maximum distance outside the edge of the map that the camera's
+// focus point can be moved
+static const float CAMERA_EDGE_MARGIN = 2.0f * TERRAIN_TILE_SIZE;
+
+CCameraController::CCameraController(CCamera& camera)
+ : m_Camera(camera),
+ m_ConstrainCamera(true),
+ m_FollowEntity(INVALID_ENTITY),
+ m_FollowFirstPerson(false),
+
+ // Dummy values (these will be filled in by the config file)
+ m_ViewScrollSpeed(0),
+ m_ViewScrollSpeedModifier(1),
+ m_ViewRotateXSpeed(0),
+ m_ViewRotateXMin(0),
+ m_ViewRotateXMax(0),
+ m_ViewRotateXDefault(0),
+ m_ViewRotateYSpeed(0),
+ m_ViewRotateYSpeedWheel(0),
+ m_ViewRotateYDefault(0),
+ m_ViewRotateSpeedModifier(1),
+ m_ViewDragSpeed(0),
+ m_ViewZoomSpeed(0),
+ m_ViewZoomSpeedWheel(0),
+ m_ViewZoomMin(0),
+ m_ViewZoomMax(0),
+ m_ViewZoomDefault(0),
+ m_ViewZoomSpeedModifier(1),
+ m_ViewFOV(DEGTORAD(45.f)),
+ m_ViewNear(2.f),
+ m_ViewFar(4096.f),
+ m_JoystickPanX(-1),
+ m_JoystickPanY(-1),
+ m_JoystickRotateX(-1),
+ m_JoystickRotateY(-1),
+ m_JoystickZoomIn(-1),
+ m_JoystickZoomOut(-1),
+ m_HeightSmoothness(0.5f),
+ m_HeightMin(16.f),
+
+ m_PosX(0, 0, 0.01f),
+ m_PosY(0, 0, 0.01f),
+ m_PosZ(0, 0, 0.01f),
+ m_Zoom(0, 0, 0.1f),
+ m_RotateX(0, 0, 0.001f),
+ m_RotateY(0, 0, 0.001f)
+{
+ SViewPort vp;
+ vp.m_X = 0;
+ vp.m_Y = 0;
+ vp.m_Width = g_xres;
+ vp.m_Height = g_yres;
+ m_Camera.SetViewPort(vp);
+
+ SetCameraProjection();
+ SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
+ m_Camera.UpdateFrustum();
+}
+
+void CCameraController::Initialize()
+{
+ CFG_GET_VAL("view.scroll.speed", m_ViewScrollSpeed);
+ CFG_GET_VAL("view.scroll.speed.modifier", m_ViewScrollSpeedModifier);
+ CFG_GET_VAL("view.rotate.x.speed", m_ViewRotateXSpeed);
+ CFG_GET_VAL("view.rotate.x.min", m_ViewRotateXMin);
+ CFG_GET_VAL("view.rotate.x.max", m_ViewRotateXMax);
+ CFG_GET_VAL("view.rotate.x.default", m_ViewRotateXDefault);
+ CFG_GET_VAL("view.rotate.y.speed", m_ViewRotateYSpeed);
+ CFG_GET_VAL("view.rotate.y.speed.wheel", m_ViewRotateYSpeedWheel);
+ CFG_GET_VAL("view.rotate.y.default", m_ViewRotateYDefault);
+ CFG_GET_VAL("view.rotate.speed.modifier", m_ViewRotateSpeedModifier);
+ CFG_GET_VAL("view.drag.speed", m_ViewDragSpeed);
+ CFG_GET_VAL("view.zoom.speed", m_ViewZoomSpeed);
+ CFG_GET_VAL("view.zoom.speed.wheel", m_ViewZoomSpeedWheel);
+ CFG_GET_VAL("view.zoom.min", m_ViewZoomMin);
+ CFG_GET_VAL("view.zoom.max", m_ViewZoomMax);
+ CFG_GET_VAL("view.zoom.default", m_ViewZoomDefault);
+ CFG_GET_VAL("view.zoom.speed.modifier", m_ViewZoomSpeedModifier);
+
+ CFG_GET_VAL("joystick.camera.pan.x", m_JoystickPanX);
+ CFG_GET_VAL("joystick.camera.pan.y", m_JoystickPanY);
+ CFG_GET_VAL("joystick.camera.rotate.x", m_JoystickRotateX);
+ CFG_GET_VAL("joystick.camera.rotate.y", m_JoystickRotateY);
+ CFG_GET_VAL("joystick.camera.zoom.in", m_JoystickZoomIn);
+ CFG_GET_VAL("joystick.camera.zoom.out", m_JoystickZoomOut);
+
+ CFG_GET_VAL("view.height.smoothness", m_HeightSmoothness);
+ CFG_GET_VAL("view.height.min", m_HeightMin);
+
+#define SETUP_SMOOTHNESS(CFG_PREFIX, SMOOTHED_VALUE) \
+ { \
+ float smoothness = SMOOTHED_VALUE.GetSmoothness(); \
+ CFG_GET_VAL(CFG_PREFIX ".smoothness", smoothness); \
+ SMOOTHED_VALUE.SetSmoothness(smoothness); \
+ }
+
+ SETUP_SMOOTHNESS("view.pos", m_PosX);
+ SETUP_SMOOTHNESS("view.pos", m_PosY);
+ SETUP_SMOOTHNESS("view.pos", m_PosZ);
+ SETUP_SMOOTHNESS("view.zoom", m_Zoom);
+ SETUP_SMOOTHNESS("view.rotate.x", m_RotateX);
+ SETUP_SMOOTHNESS("view.rotate.y", m_RotateY);
+#undef SETUP_SMOOTHNESS
+
+ CFG_GET_VAL("view.near", m_ViewNear);
+ CFG_GET_VAL("view.far", m_ViewFar);
+ CFG_GET_VAL("view.fov", m_ViewFOV);
+
+ // Convert to radians
+ m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
+ m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
+ m_ViewFOV = DEGTORAD(m_ViewFOV);
+}
+
+void CCameraController::SetViewport(const SViewPort& vp)
+{
+ m_Camera.SetViewPort(vp);
+ SetCameraProjection();
+}
+
+void CCameraController::Update(const float deltaRealTime)
+{
+ // Calculate mouse movement
+ static int mouse_last_x = 0;
+ static int mouse_last_y = 0;
+ int mouse_dx = g_mouse_x - mouse_last_x;
+ int mouse_dy = g_mouse_y - mouse_last_y;
+ mouse_last_x = g_mouse_x;
+ mouse_last_y = g_mouse_y;
+
+ if (HotkeyIsPressed("camera.rotate.cw"))
+ m_RotateY.AddSmoothly(m_ViewRotateYSpeed * deltaRealTime);
+ if (HotkeyIsPressed("camera.rotate.ccw"))
+ m_RotateY.AddSmoothly(-m_ViewRotateYSpeed * deltaRealTime);
+ if (HotkeyIsPressed("camera.rotate.up"))
+ m_RotateX.AddSmoothly(-m_ViewRotateXSpeed * deltaRealTime);
+ if (HotkeyIsPressed("camera.rotate.down"))
+ m_RotateX.AddSmoothly(m_ViewRotateXSpeed * deltaRealTime);
+
+ float moveRightward = 0.f;
+ float moveForward = 0.f;
+
+ if (HotkeyIsPressed("camera.pan"))
+ {
+ moveRightward += m_ViewDragSpeed * mouse_dx;
+ moveForward += m_ViewDragSpeed * -mouse_dy;
+ }
+
+ if (g_mouse_active)
+ {
+ if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres)
+ moveRightward += m_ViewScrollSpeed * deltaRealTime;
+ else if (g_mouse_x <= 3 && g_mouse_x >= 0)
+ moveRightward -= m_ViewScrollSpeed * deltaRealTime;
+
+ if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres)
+ moveForward -= m_ViewScrollSpeed * deltaRealTime;
+ else if (g_mouse_y <= 3 && g_mouse_y >= 0)
+ moveForward += m_ViewScrollSpeed * deltaRealTime;
+ }
+
+ if (HotkeyIsPressed("camera.right"))
+ moveRightward += m_ViewScrollSpeed * deltaRealTime;
+ if (HotkeyIsPressed("camera.left"))
+ moveRightward -= m_ViewScrollSpeed * deltaRealTime;
+ if (HotkeyIsPressed("camera.up"))
+ moveForward += m_ViewScrollSpeed * deltaRealTime;
+ if (HotkeyIsPressed("camera.down"))
+ moveForward -= m_ViewScrollSpeed * deltaRealTime;
+
+ if (g_Joystick.IsEnabled())
+ {
+ // This could all be improved with extra speed and sensitivity settings
+ // (maybe use pow to allow finer control?), and inversion settings
+
+ moveRightward += g_Joystick.GetAxisValue(m_JoystickPanX) * m_ViewScrollSpeed * deltaRealTime;
+ moveForward -= g_Joystick.GetAxisValue(m_JoystickPanY) * m_ViewScrollSpeed * deltaRealTime;
+
+ m_RotateX.AddSmoothly(g_Joystick.GetAxisValue(m_JoystickRotateX) * m_ViewRotateXSpeed * deltaRealTime);
+ m_RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m_JoystickRotateY) * m_ViewRotateYSpeed * deltaRealTime);
+
+ // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1
+ m_Zoom.AddSmoothly((g_Joystick.GetAxisValue(m_JoystickZoomIn) + 1.0f) / 2.0f * m_ViewZoomSpeed * deltaRealTime);
+ m_Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m_JoystickZoomOut) + 1.0f) / 2.0f * m_ViewZoomSpeed * deltaRealTime);
+ }
+
+ if (moveRightward || moveForward)
+ {
+ // Break out of following mode when the user starts scrolling
+ m_FollowEntity = INVALID_ENTITY;
+
+ float s = sin(m_RotateY.GetSmoothedValue());
+ float c = cos(m_RotateY.GetSmoothedValue());
+ m_PosX.AddSmoothly(c * moveRightward);
+ m_PosZ.AddSmoothly(-s * moveRightward);
+ m_PosX.AddSmoothly(s * moveForward);
+ m_PosZ.AddSmoothly(c * moveForward);
+ }
+
+ if (m_FollowEntity)
+ {
+ CmpPtr cmpPosition(*(g_Game->GetSimulation2()), m_FollowEntity);
+ CmpPtr cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
+ if (cmpPosition && cmpPosition->IsInWorld() &&
+ cmpRangeManager && cmpRangeManager->GetLosVisibility(m_FollowEntity, g_Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE)
+ {
+ // Get the most recent interpolated position
+ float frameOffset = g_Game->GetSimulation2()->GetLastFrameOffset();
+ CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
+ CVector3D pos = transform.GetTranslation();
+
+ if (m_FollowFirstPerson)
+ {
+ float x, z, angle;
+ cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle);
+ float height = 4.f;
+ m_Camera.m_Orientation.SetIdentity();
+ m_Camera.m_Orientation.RotateX(static_cast(M_PI) / 24.f);
+ m_Camera.m_Orientation.RotateY(angle);
+ m_Camera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z);
+
+ m_Camera.UpdateFrustum();
+ return;
+ }
+ else
+ {
+ // Move the camera to match the unit
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = pos - pivot;
+ m_PosX.AddSmoothly(delta.X);
+ m_PosY.AddSmoothly(delta.Y);
+ m_PosZ.AddSmoothly(delta.Z);
+ }
+ }
+ else
+ {
+ // The unit disappeared (died or garrisoned etc), so stop following it
+ m_FollowEntity = INVALID_ENTITY;
+ }
+ }
+
+ if (HotkeyIsPressed("camera.zoom.in"))
+ m_Zoom.AddSmoothly(-m_ViewZoomSpeed * deltaRealTime);
+ if (HotkeyIsPressed("camera.zoom.out"))
+ m_Zoom.AddSmoothly(m_ViewZoomSpeed * deltaRealTime);
+
+ if (m_ConstrainCamera)
+ m_Zoom.ClampSmoothly(m_ViewZoomMin, m_ViewZoomMax);
+
+ float zoomDelta = -m_Zoom.Update(deltaRealTime);
+ if (zoomDelta)
+ {
+ CVector3D forwards = m_Camera.m_Orientation.GetIn();
+ m_PosX.AddSmoothly(forwards.X * zoomDelta);
+ m_PosY.AddSmoothly(forwards.Y * zoomDelta);
+ m_PosZ.AddSmoothly(forwards.Z * zoomDelta);
+ }
+
+ if (m_ConstrainCamera)
+ m_RotateX.ClampSmoothly(DEGTORAD(m_ViewRotateXMin), DEGTORAD(m_ViewRotateXMax));
+
+ FocusHeight(true);
+
+ // Ensure the ViewCamera focus is inside the map with the chosen margins
+ // if not so - apply margins to the camera
+ if (m_ConstrainCamera)
+ {
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
+
+ CTerrain* pTerrain = g_Game->GetWorld()->GetTerrain();
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
+
+ CVector3D desiredPivot = pivot;
+
+ CmpPtr cmpRangeManager(*(g_Game->GetSimulation2()), SYSTEM_ENTITY);
+ if (cmpRangeManager && cmpRangeManager->GetLosCircular())
+ {
+ // Clamp to a circular region around the center of the map
+ float r = pTerrain->GetMaxX() / 2;
+ CVector3D center(r, desiredPivot.Y, r);
+ float dist = (desiredPivot - center).Length();
+ if (dist > r - CAMERA_EDGE_MARGIN)
+ desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN);
+ }
+ else
+ {
+ // Clamp to the square edges of the map
+ desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
+ desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN);
+ }
+
+ // Update the position so that pivot is within the margin
+ m_PosX.SetValueSmoothly(desiredPivot.X + delta.X);
+ m_PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z);
+ }
+
+ m_PosX.Update(deltaRealTime);
+ m_PosY.Update(deltaRealTime);
+ m_PosZ.Update(deltaRealTime);
+
+ // Handle rotation around the Y (vertical) axis
+ {
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixSmooth(&targetCam.m_Orientation);
+
+ float rotateYDelta = m_RotateY.Update(deltaRealTime);
+ if (rotateYDelta)
+ {
+ // We've updated RotateY, and need to adjust Pos so that it's still
+ // facing towards the original focus point (the terrain in the center
+ // of the screen).
+
+ CVector3D upwards(0.0f, 1.0f, 0.0f);
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
+
+ CQuaternion q;
+ q.FromAxisAngle(upwards, rotateYDelta);
+ CVector3D d = q.Rotate(delta) - delta;
+
+ m_PosX.Add(d.X);
+ m_PosY.Add(d.Y);
+ m_PosZ.Add(d.Z);
+ }
+ }
+
+ // Handle rotation around the X (sideways, relative to camera) axis
+ {
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixSmooth(&targetCam.m_Orientation);
+
+ float rotateXDelta = m_RotateX.Update(deltaRealTime);
+ if (rotateXDelta)
+ {
+ CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f;
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
+
+ CQuaternion q;
+ q.FromAxisAngle(rightwards, rotateXDelta);
+ CVector3D d = q.Rotate(delta) - delta;
+
+ m_PosX.Add(d.X);
+ m_PosY.Add(d.Y);
+ m_PosZ.Add(d.Z);
+ }
+ }
+
+ /* This is disabled since it doesn't seem necessary:
+
+ // Ensure the camera's near point is never inside the terrain
+ if (m_ConstrainCamera)
+ {
+ CMatrix3D target;
+ target.SetIdentity();
+ target.RotateX(m_RotateX.GetValue());
+ target.RotateY(m_RotateY.GetValue());
+ target.Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
+
+ CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear;
+ float ground = g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
+ float limit = ground + 16.f;
+ if (nearPoint.Y < limit)
+ m_PosY.AddSmoothly(limit - nearPoint.Y);
+ }
+ */
+
+ m_RotateY.Wrap(-static_cast(M_PI), static_cast(M_PI));
+
+ // Update the camera matrix
+ SetCameraProjection();
+ SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
+ m_Camera.UpdateFrustum();
+}
+
+CVector3D CCameraController::GetSmoothPivot(CCamera& camera) const
+{
+ return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m_Zoom.GetSmoothedValue();
+}
+
+CVector3D CCameraController::GetCameraPivot() const
+{
+ return GetSmoothPivot(m_Camera);
+}
+
+CVector3D CCameraController::GetCameraPosition() const
+{
+ return CVector3D(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
+}
+
+CVector3D CCameraController::GetCameraRotation() const
+{
+ // The angle of rotation around the Z axis is not used.
+ return CVector3D(m_RotateX.GetValue(), m_RotateY.GetValue(), 0.0f);
+}
+
+float CCameraController::GetCameraZoom() const
+{
+ return m_Zoom.GetValue();
+}
+
+void CCameraController::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
+{
+ m_PosX.SetValue(pos.X);
+ m_PosY.SetValue(pos.Y);
+ m_PosZ.SetValue(pos.Z);
+ m_RotateX.SetValue(rotX);
+ m_RotateY.SetValue(rotY);
+ m_Zoom.SetValue(zoom);
+
+ FocusHeight(false);
+
+ SetupCameraMatrixNonSmooth(&m_Camera.m_Orientation);
+ m_Camera.UpdateFrustum();
+
+ // Break out of following mode so the camera really moves to the target
+ m_FollowEntity = INVALID_ENTITY;
+}
+
+void CCameraController::MoveCameraTarget(const CVector3D& target)
+{
+ // Maintain the same orientation and level of zoom, if we can
+ // (do this by working out the point the camera is looking at, saving
+ // the difference between that position and the camera point, and restoring
+ // that difference to our new target)
+
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = target - pivot;
+
+ m_PosX.SetValueSmoothly(delta.X + m_PosX.GetValue());
+ m_PosZ.SetValueSmoothly(delta.Z + m_PosZ.GetValue());
+
+ FocusHeight(false);
+
+ // Break out of following mode so the camera really moves to the target
+ m_FollowEntity = INVALID_ENTITY;
+}
+
+void CCameraController::ResetCameraTarget(const CVector3D& target)
+{
+ CMatrix3D orientation;
+ orientation.SetIdentity();
+ orientation.RotateX(DEGTORAD(m_ViewRotateXDefault));
+ orientation.RotateY(DEGTORAD(m_ViewRotateYDefault));
+
+ CVector3D delta = orientation.GetIn() * m_ViewZoomDefault;
+ m_PosX.SetValue(target.X - delta.X);
+ m_PosY.SetValue(target.Y - delta.Y);
+ m_PosZ.SetValue(target.Z - delta.Z);
+ m_RotateX.SetValue(DEGTORAD(m_ViewRotateXDefault));
+ m_RotateY.SetValue(DEGTORAD(m_ViewRotateYDefault));
+ m_Zoom.SetValue(m_ViewZoomDefault);
+
+ FocusHeight(false);
+
+ SetupCameraMatrixSmooth(&m_Camera.m_Orientation);
+ m_Camera.UpdateFrustum();
+
+ // Break out of following mode so the camera really moves to the target
+ m_FollowEntity = INVALID_ENTITY;
+}
+
+void CCameraController::CameraFollow(entity_id_t entity, bool firstPerson)
+{
+ m_FollowEntity = entity;
+ m_FollowFirstPerson = firstPerson;
+}
+
+entity_id_t CCameraController::GetFollowedEntity()
+{
+ return m_FollowEntity;
+}
+
+void CCameraController::SetCameraProjection()
+{
+ m_Camera.SetPerspectiveProjection(m_ViewNear, m_ViewFar, m_ViewFOV);
+}
+
+void CCameraController::ResetCameraAngleZoom()
+{
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixNonSmooth(&targetCam.m_Orientation);
+
+ // Compute the zoom adjustment to get us back to the default
+ CVector3D forwards = targetCam.m_Orientation.GetIn();
+
+ CVector3D pivot = GetSmoothPivot(targetCam);
+ CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation();
+ float dist = delta.Dot(forwards);
+ m_Zoom.AddSmoothly(m_ViewZoomDefault - dist);
+
+ // Reset orientations to default
+ m_RotateX.SetValueSmoothly(DEGTORAD(m_ViewRotateXDefault));
+ m_RotateY.SetValueSmoothly(DEGTORAD(m_ViewRotateYDefault));
+}
+
+void CCameraController::SetupCameraMatrixSmooth(CMatrix3D* orientation)
+{
+ orientation->SetIdentity();
+ orientation->RotateX(m_RotateX.GetSmoothedValue());
+ orientation->RotateY(m_RotateY.GetSmoothedValue());
+ orientation->Translate(m_PosX.GetSmoothedValue(), m_PosY.GetSmoothedValue(), m_PosZ.GetSmoothedValue());
+}
+
+void CCameraController::SetupCameraMatrixSmoothRot(CMatrix3D* orientation)
+{
+ orientation->SetIdentity();
+ orientation->RotateX(m_RotateX.GetSmoothedValue());
+ orientation->RotateY(m_RotateY.GetSmoothedValue());
+ orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
+}
+
+void CCameraController::SetupCameraMatrixNonSmooth(CMatrix3D* orientation)
+{
+ orientation->SetIdentity();
+ orientation->RotateX(m_RotateX.GetValue());
+ orientation->RotateY(m_RotateY.GetValue());
+ orientation->Translate(m_PosX.GetValue(), m_PosY.GetValue(), m_PosZ.GetValue());
+}
+
+void CCameraController::FocusHeight(bool smooth)
+{
+ /*
+ The camera pivot height is moved towards ground level.
+ To prevent excessive zoom when looking over a cliff,
+ the target ground level is the maximum of the ground level at the camera's near and pivot points.
+ The ground levels are filtered to achieve smooth camera movement.
+ The filter radius is proportional to the zoom level.
+ The camera height is clamped to prevent map penetration.
+ */
+
+ if (!m_ConstrainCamera)
+ return;
+
+ CCamera targetCam = m_Camera;
+ SetupCameraMatrixSmoothRot(&targetCam.m_Orientation);
+
+ const CVector3D position = targetCam.m_Orientation.GetTranslation();
+ const CVector3D forwards = targetCam.m_Orientation.GetIn();
+
+ // horizontal view radius
+ const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m_Zoom.GetSmoothedValue();
+ const float near_radius = radius * m_HeightSmoothness;
+ const float pivot_radius = radius * m_HeightSmoothness;
+
+ const CVector3D nearPoint = position + forwards * m_ViewNear;
+ const CVector3D pivotPoint = position + forwards * m_Zoom.GetSmoothedValue();
+
+ const float ground = std::max(
+ g_Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z),
+ g_Renderer.GetWaterManager()->m_WaterHeight);
+
+ // filter ground levels for smooth camera movement
+ const float filtered_near_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
+ const float filtered_pivot_ground = g_Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
+
+ // filtered maximum visible ground level in view
+ const float filtered_ground = std::max(
+ std::max(filtered_near_ground, filtered_pivot_ground),
+ g_Renderer.GetWaterManager()->m_WaterHeight);
+
+ // target camera height above pivot point
+ const float pivot_height = -forwards.Y * (m_Zoom.GetSmoothedValue() - m_ViewNear);
+
+ // minimum camera height above filtered ground level
+ const float min_height = (m_HeightMin + ground - filtered_ground);
+
+ const float target_height = std::max(pivot_height, min_height);
+ const float height = (nearPoint.Y - filtered_ground);
+ const float diff = target_height - height;
+
+ if (fabsf(diff) < 0.0001f)
+ return;
+
+ if (smooth)
+ m_PosY.AddSmoothly(diff);
+ else
+ m_PosY.Add(diff);
+}
+
+InReaction CCameraController::HandleEvent(const SDL_Event_* ev)
+{
+ switch (ev->ev.type)
+ {
+ case SDL_HOTKEYDOWN:
+ std::string hotkey = static_cast(ev->ev.user.data1);
+
+ // Mouse wheel must be treated using events instead of polling,
+ // because SDL auto-generates a sequence of mousedown/mouseup events
+ // and we never get to see the "down" state inside Update().
+ if (hotkey == "camera.zoom.wheel.in")
+ {
+ m_Zoom.AddSmoothly(-m_ViewZoomSpeedWheel);
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.zoom.wheel.out")
+ {
+ m_Zoom.AddSmoothly(m_ViewZoomSpeedWheel);
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.rotate.wheel.cw")
+ {
+ m_RotateY.AddSmoothly(m_ViewRotateYSpeedWheel);
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.rotate.wheel.ccw")
+ {
+ m_RotateY.AddSmoothly(-m_ViewRotateYSpeedWheel);
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.scroll.speed.increase")
+ {
+ m_ViewScrollSpeed *= m_ViewScrollSpeedModifier;
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.scroll.speed.decrease")
+ {
+ m_ViewScrollSpeed /= m_ViewScrollSpeedModifier;
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.rotate.speed.increase")
+ {
+ m_ViewRotateXSpeed *= m_ViewRotateSpeedModifier;
+ m_ViewRotateYSpeed *= m_ViewRotateSpeedModifier;
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.rotate.speed.decrease")
+ {
+ m_ViewRotateXSpeed /= m_ViewRotateSpeedModifier;
+ m_ViewRotateYSpeed /= m_ViewRotateSpeedModifier;
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.zoom.speed.increase")
+ {
+ m_ViewZoomSpeed *= m_ViewZoomSpeedModifier;
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.zoom.speed.decrease")
+ {
+ m_ViewZoomSpeed /= m_ViewZoomSpeedModifier;
+ return IN_HANDLED;
+ }
+ else if (hotkey == "camera.reset")
+ {
+ ResetCameraAngleZoom();
+ return IN_HANDLED;
+ }
+ }
+
+ return IN_PASS;
+}
diff --git a/source/graphics/CameraController.h b/source/graphics/CameraController.h
new file mode 100644
index 0000000000..567b4b8520
--- /dev/null
+++ b/source/graphics/CameraController.h
@@ -0,0 +1,129 @@
+/* Copyright (C) 2019 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+
+#ifndef INCLUDED_CAMERACONTROLLER
+#define INCLUDED_CAMERACONTROLLER
+
+#include "graphics/Camera.h"
+#include "graphics/SmoothedValue.h"
+#include "simulation2/system/Entity.h"
+
+#include "lib/input.h" // InReaction - can't forward-declare enum
+
+class CCameraController
+{
+ NONCOPYABLE(CCameraController);
+public:
+ CCameraController(CCamera& camera);
+
+ void Initialize();
+
+ InReaction HandleEvent(const SDL_Event_* ev);
+
+ CVector3D GetCameraPivot() const;
+ CVector3D GetCameraPosition() const;
+ CVector3D GetCameraRotation() const;
+ float GetCameraZoom() const;
+
+ void SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom);
+ void MoveCameraTarget(const CVector3D& target);
+ void ResetCameraTarget(const CVector3D& target);
+ void CameraFollow(entity_id_t entity, bool firstPerson);
+ entity_id_t GetFollowedEntity();
+
+ // Set projection of current camera using near, far, and FOV values
+ void SetCameraProjection();
+
+ void Update(const float deltaRealTime);
+ void SetViewport(const SViewPort& vp);
+
+ bool GetConstrainCamera() const
+ {
+ return m_ConstrainCamera;
+ }
+
+ void SetConstrainCamera(bool constrain)
+ {
+ m_ConstrainCamera = constrain;
+ }
+
+private:
+ CVector3D GetSmoothPivot(CCamera &camera) const;
+ void ResetCameraAngleZoom();
+ void SetupCameraMatrixSmooth(CMatrix3D* orientation);
+ void SetupCameraMatrixSmoothRot(CMatrix3D* orientation);
+ void SetupCameraMatrixNonSmooth(CMatrix3D* orientation);
+ void FocusHeight(bool smooth);
+
+ CCamera& m_Camera;
+
+ /**
+ * Whether the camera movement should be constrained by min/max limits
+ * and terrain avoidance.
+ */
+ bool m_ConstrainCamera;
+
+ /**
+ * Entity for the camera to follow, or INVALID_ENTITY if none.
+ */
+ entity_id_t m_FollowEntity;
+
+ /**
+ * Whether to follow FollowEntity in first-person mode.
+ */
+ bool m_FollowFirstPerson;
+
+ // Settings
+ float m_ViewScrollSpeed;
+ float m_ViewScrollSpeedModifier;
+ float m_ViewRotateXSpeed;
+ float m_ViewRotateXMin;
+ float m_ViewRotateXMax;
+ float m_ViewRotateXDefault;
+ float m_ViewRotateYSpeed;
+ float m_ViewRotateYSpeedWheel;
+ float m_ViewRotateYDefault;
+ float m_ViewRotateSpeedModifier;
+ float m_ViewDragSpeed;
+ float m_ViewZoomSpeed;
+ float m_ViewZoomSpeedWheel;
+ float m_ViewZoomMin;
+ float m_ViewZoomMax;
+ float m_ViewZoomDefault;
+ float m_ViewZoomSpeedModifier;
+ float m_ViewFOV;
+ float m_ViewNear;
+ float m_ViewFar;
+ int m_JoystickPanX;
+ int m_JoystickPanY;
+ int m_JoystickRotateX;
+ int m_JoystickRotateY;
+ int m_JoystickZoomIn;
+ int m_JoystickZoomOut;
+ float m_HeightSmoothness;
+ float m_HeightMin;
+
+ // Camera Controls State
+ CSmoothedValue m_PosX;
+ CSmoothedValue m_PosY;
+ CSmoothedValue m_PosZ;
+ CSmoothedValue m_Zoom;
+ CSmoothedValue m_RotateX; // inclination around x axis (relative to camera)
+ CSmoothedValue m_RotateY; // rotation around y (vertical) axis
+};
+
+#endif // INCLUDED_CAMERACONTROLLER
diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp
index a5d28d31d3..f2f2d35d9e 100644
--- a/source/graphics/GameView.cpp
+++ b/source/graphics/GameView.cpp
@@ -19,7 +19,7 @@
#include "GameView.h"
-#include "graphics/Camera.h"
+#include "graphics/CameraController.h"
#include "graphics/CinemaManager.h"
#include "graphics/ColladaManager.h"
#include "graphics/HFTracer.h"
@@ -61,8 +61,6 @@
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpRangeManager.h"
-extern int g_xres, g_yres;
-
// Maximum distance outside the edge of the map that the camera's
// focus point can be moved
static const float CAMERA_EDGE_MARGIN = 2.0f * TERRAIN_TILE_SIZE;
@@ -80,47 +78,8 @@ public:
ViewCamera(),
CullCamera(),
LockCullCamera(false),
- ConstrainCamera(true),
Culling(true),
- FollowEntity(INVALID_ENTITY),
- FollowFirstPerson(false),
-
- // Dummy values (these will be filled in by the config file)
- ViewScrollSpeed(0),
- ViewScrollSpeedModifier(1),
- ViewRotateXSpeed(0),
- ViewRotateXMin(0),
- ViewRotateXMax(0),
- ViewRotateXDefault(0),
- ViewRotateYSpeed(0),
- ViewRotateYSpeedWheel(0),
- ViewRotateYDefault(0),
- ViewRotateSpeedModifier(1),
- ViewDragSpeed(0),
- ViewZoomSpeed(0),
- ViewZoomSpeedWheel(0),
- ViewZoomMin(0),
- ViewZoomMax(0),
- ViewZoomDefault(0),
- ViewZoomSpeedModifier(1),
- ViewFOV(DEGTORAD(45.f)),
- ViewNear(2.f),
- ViewFar(4096.f),
- JoystickPanX(-1),
- JoystickPanY(-1),
- JoystickRotateX(-1),
- JoystickRotateY(-1),
- JoystickZoomIn(-1),
- JoystickZoomOut(-1),
- HeightSmoothness(0.5f),
- HeightMin(16.f),
-
- PosX(0, 0, 0.01f),
- PosY(0, 0, 0.01f),
- PosZ(0, 0, 0.01f),
- Zoom(0, 0, 0.1f),
- RotateX(0, 0, 0.001f),
- RotateY(0, 0, 0.001f)
+ CameraController(ViewCamera)
{
}
@@ -164,12 +123,6 @@ public:
*/
bool Culling;
- /**
- * Whether the camera movement should be constrained by min/max limits
- * and terrain avoidance.
- */
- bool ConstrainCamera;
-
/**
* Cache global lighting environment. This is used to check whether the
* environment has changed during the last frame, so that vertex data can be updated etc.
@@ -178,55 +131,7 @@ public:
CCinemaManager CinemaManager;
- /**
- * Entity for the camera to follow, or INVALID_ENTITY if none.
- */
- entity_id_t FollowEntity;
-
- /**
- * Whether to follow FollowEntity in first-person mode.
- */
- bool FollowFirstPerson;
-
- ////////////////////////////////////////
- // Settings
- float ViewScrollSpeed;
- float ViewScrollSpeedModifier;
- float ViewRotateXSpeed;
- float ViewRotateXMin;
- float ViewRotateXMax;
- float ViewRotateXDefault;
- float ViewRotateYSpeed;
- float ViewRotateYSpeedWheel;
- float ViewRotateYDefault;
- float ViewRotateSpeedModifier;
- float ViewDragSpeed;
- float ViewZoomSpeed;
- float ViewZoomSpeedWheel;
- float ViewZoomMin;
- float ViewZoomMax;
- float ViewZoomDefault;
- float ViewZoomSpeedModifier;
- float ViewFOV;
- float ViewNear;
- float ViewFar;
- int JoystickPanX;
- int JoystickPanY;
- int JoystickRotateX;
- int JoystickRotateY;
- int JoystickZoomIn;
- int JoystickZoomOut;
- float HeightSmoothness;
- float HeightMin;
-
- ////////////////////////////////////////
- // Camera Controls State
- CSmoothedValue PosX;
- CSmoothedValue PosY;
- CSmoothedValue PosZ;
- CSmoothedValue Zoom;
- CSmoothedValue RotateX; // inclination around x axis (relative to camera)
- CSmoothedValue RotateY; // rotation around y (vertical) axis
+ CCameraController CameraController;
};
#define IMPLEMENT_BOOLEAN_SETTING(NAME) \
@@ -242,48 +147,22 @@ void CGameView::Set##NAME##Enabled(bool Enabled) \
IMPLEMENT_BOOLEAN_SETTING(Culling);
IMPLEMENT_BOOLEAN_SETTING(LockCullCamera);
-IMPLEMENT_BOOLEAN_SETTING(ConstrainCamera);
+
+bool CGameView::GetConstrainCameraEnabled() const
+{
+ return m->CameraController.GetConstrainCamera();
+}
+
+void CGameView::SetConstrainCameraEnabled(bool enabled)
+{
+ m->CameraController.SetConstrainCamera(enabled);
+}
#undef IMPLEMENT_BOOLEAN_SETTING
-static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation)
-{
- orientation->SetIdentity();
- orientation->RotateX(m->RotateX.GetSmoothedValue());
- orientation->RotateY(m->RotateY.GetSmoothedValue());
- orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue());
-}
-
-static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation)
-{
- orientation->SetIdentity();
- orientation->RotateX(m->RotateX.GetSmoothedValue());
- orientation->RotateY(m->RotateY.GetSmoothedValue());
- orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
-}
-
-static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation)
-{
- orientation->SetIdentity();
- orientation->RotateX(m->RotateX.GetValue());
- orientation->RotateY(m->RotateY.GetValue());
- orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
-}
-
CGameView::CGameView(CGame *pGame):
m(new CGameViewImpl(pGame))
{
- SViewPort vp;
- vp.m_X = 0;
- vp.m_Y = 0;
- vp.m_Width = g_xres;
- vp.m_Height = g_yres;
- m->ViewCamera.SetViewPort(vp);
-
- SetCameraProjection();
- SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
- m->ViewCamera.UpdateFrustum();
-
m->CullCamera = m->ViewCamera;
g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera);
}
@@ -297,8 +176,7 @@ CGameView::~CGameView()
void CGameView::SetViewport(const SViewPort& vp)
{
- m->ViewCamera.SetViewPort(vp);
- SetCameraProjection();
+ m->CameraController.SetViewport(vp);
}
CObjectManager& CGameView::GetObjectManager()
@@ -328,63 +206,10 @@ CTerritoryTexture& CGameView::GetTerritoryTexture()
int CGameView::Initialize()
{
- CFG_GET_VAL("view.scroll.speed", m->ViewScrollSpeed);
- CFG_GET_VAL("view.scroll.speed.modifier", m->ViewScrollSpeedModifier);
- CFG_GET_VAL("view.rotate.x.speed", m->ViewRotateXSpeed);
- CFG_GET_VAL("view.rotate.x.min", m->ViewRotateXMin);
- CFG_GET_VAL("view.rotate.x.max", m->ViewRotateXMax);
- CFG_GET_VAL("view.rotate.x.default", m->ViewRotateXDefault);
- CFG_GET_VAL("view.rotate.y.speed", m->ViewRotateYSpeed);
- CFG_GET_VAL("view.rotate.y.speed.wheel", m->ViewRotateYSpeedWheel);
- CFG_GET_VAL("view.rotate.y.default", m->ViewRotateYDefault);
- CFG_GET_VAL("view.rotate.speed.modifier", m->ViewRotateSpeedModifier);
- CFG_GET_VAL("view.drag.speed", m->ViewDragSpeed);
- CFG_GET_VAL("view.zoom.speed", m->ViewZoomSpeed);
- CFG_GET_VAL("view.zoom.speed.wheel", m->ViewZoomSpeedWheel);
- CFG_GET_VAL("view.zoom.min", m->ViewZoomMin);
- CFG_GET_VAL("view.zoom.max", m->ViewZoomMax);
- CFG_GET_VAL("view.zoom.default", m->ViewZoomDefault);
- CFG_GET_VAL("view.zoom.speed.modifier", m->ViewZoomSpeedModifier);
-
- CFG_GET_VAL("joystick.camera.pan.x", m->JoystickPanX);
- CFG_GET_VAL("joystick.camera.pan.y", m->JoystickPanY);
- CFG_GET_VAL("joystick.camera.rotate.x", m->JoystickRotateX);
- CFG_GET_VAL("joystick.camera.rotate.y", m->JoystickRotateY);
- CFG_GET_VAL("joystick.camera.zoom.in", m->JoystickZoomIn);
- CFG_GET_VAL("joystick.camera.zoom.out", m->JoystickZoomOut);
-
- CFG_GET_VAL("view.height.smoothness", m->HeightSmoothness);
- CFG_GET_VAL("view.height.min", m->HeightMin);
-
-#define SETUP_SMOOTHNESS(CFG_PREFIX, SMOOTHED_VALUE) \
- { \
- float smoothness = SMOOTHED_VALUE.GetSmoothness(); \
- CFG_GET_VAL(CFG_PREFIX ".smoothness", smoothness); \
- SMOOTHED_VALUE.SetSmoothness(smoothness); \
- }
-
- SETUP_SMOOTHNESS("view.pos", m->PosX);
- SETUP_SMOOTHNESS("view.pos", m->PosY);
- SETUP_SMOOTHNESS("view.pos", m->PosZ);
- SETUP_SMOOTHNESS("view.zoom", m->Zoom);
- SETUP_SMOOTHNESS("view.rotate.x", m->RotateX);
- SETUP_SMOOTHNESS("view.rotate.y", m->RotateY);
-#undef SETUP_SMOOTHNESS
-
- CFG_GET_VAL("view.near", m->ViewNear);
- CFG_GET_VAL("view.far", m->ViewFar);
- CFG_GET_VAL("view.fov", m->ViewFOV);
-
- // Convert to radians
- m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault));
- m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault));
- m->ViewFOV = DEGTORAD(m->ViewFOV);
-
+ m->CameraController.Initialize();
return 0;
}
-
-
void CGameView::RegisterInit()
{
// CGameView init
@@ -394,7 +219,6 @@ void CGameView::RegisterInit()
RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5);
}
-
void CGameView::BeginFrame()
{
if (m->LockCullCamera == false)
@@ -475,71 +299,6 @@ void CGameView::UnloadResources()
g_Renderer.GetWaterManager()->UnloadWaterTextures();
}
-static void FocusHeight(CGameViewImpl* m, bool smooth)
-{
- /*
- The camera pivot height is moved towards ground level.
- To prevent excessive zoom when looking over a cliff,
- the target ground level is the maximum of the ground level at the camera's near and pivot points.
- The ground levels are filtered to achieve smooth camera movement.
- The filter radius is proportional to the zoom level.
- The camera height is clamped to prevent map penetration.
- */
-
- if (!m->ConstrainCamera)
- return;
-
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
-
- const CVector3D position = targetCam.m_Orientation.GetTranslation();
- const CVector3D forwards = targetCam.m_Orientation.GetIn();
-
- // horizontal view radius
- const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue();
- const float near_radius = radius * m->HeightSmoothness;
- const float pivot_radius = radius * m->HeightSmoothness;
-
- const CVector3D nearPoint = position + forwards * m->ViewNear;
- const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue();
-
- const float ground = std::max(
- m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z),
- g_Renderer.GetWaterManager()->m_WaterHeight);
-
- // filter ground levels for smooth camera movement
- const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
- const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
-
- // filtered maximum visible ground level in view
- const float filtered_ground = std::max(
- std::max(filtered_near_ground, filtered_pivot_ground),
- g_Renderer.GetWaterManager()->m_WaterHeight);
-
- // target camera height above pivot point
- const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear);
-
- // minimum camera height above filtered ground level
- const float min_height = (m->HeightMin + ground - filtered_ground);
-
- const float target_height = std::max(pivot_height, min_height);
- const float height = (nearPoint.Y - filtered_ground);
- const float diff = target_height - height;
-
- if (fabsf(diff) < 0.0001f)
- return;
-
- if (smooth)
- m->PosY.AddSmoothly(diff);
- else
- m->PosY.Add(diff);
-}
-
-CVector3D CGameView::GetSmoothPivot(CCamera& camera) const
-{
- return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue();
-}
-
void CGameView::Update(const float deltaRealTime)
{
// If camera movement is being handled by the touch-input system,
@@ -554,384 +313,57 @@ void CGameView::Update(const float deltaRealTime)
if (m->CinemaManager.IsEnabled())
return;
- // Calculate mouse movement
- static int mouse_last_x = 0;
- static int mouse_last_y = 0;
- int mouse_dx = g_mouse_x - mouse_last_x;
- int mouse_dy = g_mouse_y - mouse_last_y;
- mouse_last_x = g_mouse_x;
- mouse_last_y = g_mouse_y;
-
- if (HotkeyIsPressed("camera.rotate.cw"))
- m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime);
- if (HotkeyIsPressed("camera.rotate.ccw"))
- m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime);
- if (HotkeyIsPressed("camera.rotate.up"))
- m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime);
- if (HotkeyIsPressed("camera.rotate.down"))
- m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime);
-
- float moveRightward = 0.f;
- float moveForward = 0.f;
-
- if (HotkeyIsPressed("camera.pan"))
- {
- moveRightward += m->ViewDragSpeed * mouse_dx;
- moveForward += m->ViewDragSpeed * -mouse_dy;
- }
-
- if (g_mouse_active)
- {
- if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres)
- moveRightward += m->ViewScrollSpeed * deltaRealTime;
- else if (g_mouse_x <= 3 && g_mouse_x >= 0)
- moveRightward -= m->ViewScrollSpeed * deltaRealTime;
-
- if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres)
- moveForward -= m->ViewScrollSpeed * deltaRealTime;
- else if (g_mouse_y <= 3 && g_mouse_y >= 0)
- moveForward += m->ViewScrollSpeed * deltaRealTime;
- }
-
- if (HotkeyIsPressed("camera.right"))
- moveRightward += m->ViewScrollSpeed * deltaRealTime;
- if (HotkeyIsPressed("camera.left"))
- moveRightward -= m->ViewScrollSpeed * deltaRealTime;
- if (HotkeyIsPressed("camera.up"))
- moveForward += m->ViewScrollSpeed * deltaRealTime;
- if (HotkeyIsPressed("camera.down"))
- moveForward -= m->ViewScrollSpeed * deltaRealTime;
-
- if (g_Joystick.IsEnabled())
- {
- // This could all be improved with extra speed and sensitivity settings
- // (maybe use pow to allow finer control?), and inversion settings
-
- moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime;
- moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime;
-
- m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime);
- m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime);
-
- // Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1
- m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime);
- m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime);
- }
-
- if (moveRightward || moveForward)
- {
- // Break out of following mode when the user starts scrolling
- m->FollowEntity = INVALID_ENTITY;
-
- float s = sin(m->RotateY.GetSmoothedValue());
- float c = cos(m->RotateY.GetSmoothedValue());
- m->PosX.AddSmoothly(c * moveRightward);
- m->PosZ.AddSmoothly(-s * moveRightward);
- m->PosX.AddSmoothly(s * moveForward);
- m->PosZ.AddSmoothly(c * moveForward);
- }
-
- if (m->FollowEntity)
- {
- CmpPtr cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity);
- CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY);
- if (cmpPosition && cmpPosition->IsInWorld() &&
- cmpRangeManager && cmpRangeManager->GetLosVisibility(m->FollowEntity, m->Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE)
- {
- // Get the most recent interpolated position
- float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset();
- CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
- CVector3D pos = transform.GetTranslation();
-
- if (m->FollowFirstPerson)
- {
- float x, z, angle;
- cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle);
- float height = 4.f;
- m->ViewCamera.m_Orientation.SetIdentity();
- m->ViewCamera.m_Orientation.RotateX(static_cast(M_PI) / 24.f);
- m->ViewCamera.m_Orientation.RotateY(angle);
- m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z);
-
- m->ViewCamera.UpdateFrustum();
- return;
- }
- else
- {
- // Move the camera to match the unit
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
-
- CVector3D pivot = GetSmoothPivot(targetCam);
- CVector3D delta = pos - pivot;
- m->PosX.AddSmoothly(delta.X);
- m->PosY.AddSmoothly(delta.Y);
- m->PosZ.AddSmoothly(delta.Z);
- }
- }
- else
- {
- // The unit disappeared (died or garrisoned etc), so stop following it
- m->FollowEntity = INVALID_ENTITY;
- }
- }
-
- if (HotkeyIsPressed("camera.zoom.in"))
- m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime);
- if (HotkeyIsPressed("camera.zoom.out"))
- m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime);
-
- if (m->ConstrainCamera)
- m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax);
-
- float zoomDelta = -m->Zoom.Update(deltaRealTime);
- if (zoomDelta)
- {
- CVector3D forwards = m->ViewCamera.m_Orientation.GetIn();
- m->PosX.AddSmoothly(forwards.X * zoomDelta);
- m->PosY.AddSmoothly(forwards.Y * zoomDelta);
- m->PosZ.AddSmoothly(forwards.Z * zoomDelta);
- }
-
- if (m->ConstrainCamera)
- m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax));
-
- FocusHeight(m, true);
-
- // Ensure the ViewCamera focus is inside the map with the chosen margins
- // if not so - apply margins to the camera
- if (m->ConstrainCamera)
- {
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
-
- CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
-
- CVector3D pivot = GetSmoothPivot(targetCam);
- CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
-
- CVector3D desiredPivot = pivot;
-
- CmpPtr cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY);
- if (cmpRangeManager && cmpRangeManager->GetLosCircular())
- {
- // Clamp to a circular region around the center of the map
- float r = pTerrain->GetMaxX() / 2;
- CVector3D center(r, desiredPivot.Y, r);
- float dist = (desiredPivot - center).Length();
- if (dist > r - CAMERA_EDGE_MARGIN)
- desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN);
- }
- else
- {
- // Clamp to the square edges of the map
- desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
- desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN);
- }
-
- // Update the position so that pivot is within the margin
- m->PosX.SetValueSmoothly(desiredPivot.X + delta.X);
- m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z);
- }
-
- m->PosX.Update(deltaRealTime);
- m->PosY.Update(deltaRealTime);
- m->PosZ.Update(deltaRealTime);
-
- // Handle rotation around the Y (vertical) axis
- {
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixSmooth(m, &targetCam.m_Orientation);
-
- float rotateYDelta = m->RotateY.Update(deltaRealTime);
- if (rotateYDelta)
- {
- // We've updated RotateY, and need to adjust Pos so that it's still
- // facing towards the original focus point (the terrain in the center
- // of the screen).
-
- CVector3D upwards(0.0f, 1.0f, 0.0f);
-
- CVector3D pivot = GetSmoothPivot(targetCam);
- CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
-
- CQuaternion q;
- q.FromAxisAngle(upwards, rotateYDelta);
- CVector3D d = q.Rotate(delta) - delta;
-
- m->PosX.Add(d.X);
- m->PosY.Add(d.Y);
- m->PosZ.Add(d.Z);
- }
- }
-
- // Handle rotation around the X (sideways, relative to camera) axis
- {
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixSmooth(m, &targetCam.m_Orientation);
-
- float rotateXDelta = m->RotateX.Update(deltaRealTime);
- if (rotateXDelta)
- {
- CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f;
-
- CVector3D pivot = GetSmoothPivot(targetCam);
- CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
-
- CQuaternion q;
- q.FromAxisAngle(rightwards, rotateXDelta);
- CVector3D d = q.Rotate(delta) - delta;
-
- m->PosX.Add(d.X);
- m->PosY.Add(d.Y);
- m->PosZ.Add(d.Z);
- }
- }
-
- /* This is disabled since it doesn't seem necessary:
-
- // Ensure the camera's near point is never inside the terrain
- if (m->ConstrainCamera)
- {
- CMatrix3D target;
- target.SetIdentity();
- target.RotateX(m->RotateX.GetValue());
- target.RotateY(m->RotateY.GetValue());
- target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
-
- CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear;
- float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
- float limit = ground + 16.f;
- if (nearPoint.Y < limit)
- m->PosY.AddSmoothly(limit - nearPoint.Y);
- }
- */
-
- m->RotateY.Wrap(-static_cast(M_PI), static_cast(M_PI));
-
- // Update the camera matrix
- SetCameraProjection();
- SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
- m->ViewCamera.UpdateFrustum();
+ m->CameraController.Update(deltaRealTime);
}
CVector3D CGameView::GetCameraPivot() const
{
- return GetSmoothPivot(m->ViewCamera);
+ return m->CameraController.GetCameraPivot();
}
CVector3D CGameView::GetCameraPosition() const
{
- return CVector3D(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
+ return m->CameraController.GetCameraPosition();
}
CVector3D CGameView::GetCameraRotation() const
{
- // The angle of rotation around the Z axis is not used.
- return CVector3D(m->RotateX.GetValue(), m->RotateY.GetValue(), 0.0f);
+ return m->CameraController.GetCameraRotation();
}
float CGameView::GetCameraZoom() const
{
- return m->Zoom.GetValue();
+ return m->CameraController.GetCameraZoom();
}
void CGameView::SetCamera(const CVector3D& pos, float rotX, float rotY, float zoom)
{
- m->PosX.SetValue(pos.X);
- m->PosY.SetValue(pos.Y);
- m->PosZ.SetValue(pos.Z);
- m->RotateX.SetValue(rotX);
- m->RotateY.SetValue(rotY);
- m->Zoom.SetValue(zoom);
-
- FocusHeight(m, false);
-
- SetupCameraMatrixNonSmooth(m, &m->ViewCamera.m_Orientation);
- m->ViewCamera.UpdateFrustum();
-
- // Break out of following mode so the camera really moves to the target
- m->FollowEntity = INVALID_ENTITY;
+ m->CameraController.SetCamera(pos, rotX, rotY, zoom);
}
void CGameView::MoveCameraTarget(const CVector3D& target)
{
- // Maintain the same orientation and level of zoom, if we can
- // (do this by working out the point the camera is looking at, saving
- // the difference between that position and the camera point, and restoring
- // that difference to our new target)
-
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation);
-
- CVector3D pivot = GetSmoothPivot(targetCam);
- CVector3D delta = target - pivot;
-
- m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue());
- m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());
-
- FocusHeight(m, false);
-
- // Break out of following mode so the camera really moves to the target
- m->FollowEntity = INVALID_ENTITY;
+ m->CameraController.MoveCameraTarget(target);
}
void CGameView::ResetCameraTarget(const CVector3D& target)
{
- CMatrix3D orientation;
- orientation.SetIdentity();
- orientation.RotateX(DEGTORAD(m->ViewRotateXDefault));
- orientation.RotateY(DEGTORAD(m->ViewRotateYDefault));
-
- CVector3D delta = orientation.GetIn() * m->ViewZoomDefault;
- m->PosX.SetValue(target.X - delta.X);
- m->PosY.SetValue(target.Y - delta.Y);
- m->PosZ.SetValue(target.Z - delta.Z);
- m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault));
- m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault));
- m->Zoom.SetValue(m->ViewZoomDefault);
-
- FocusHeight(m, false);
-
- SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
- m->ViewCamera.UpdateFrustum();
-
- // Break out of following mode so the camera really moves to the target
- m->FollowEntity = INVALID_ENTITY;
-}
-
-void CGameView::ResetCameraAngleZoom()
-{
- CCamera targetCam = m->ViewCamera;
- SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation);
-
- // Compute the zoom adjustment to get us back to the default
- CVector3D forwards = targetCam.m_Orientation.GetIn();
-
- CVector3D pivot = GetSmoothPivot(targetCam);
- CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation();
- float dist = delta.Dot(forwards);
- m->Zoom.AddSmoothly(m->ViewZoomDefault - dist);
-
- // Reset orientations to default
- m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault));
- m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault));
+ m->CameraController.ResetCameraTarget(target);
}
void CGameView::CameraFollow(entity_id_t entity, bool firstPerson)
{
- m->FollowEntity = entity;
- m->FollowFirstPerson = firstPerson;
+ m->CameraController.CameraFollow(entity, firstPerson);
}
entity_id_t CGameView::GetFollowedEntity()
{
- return m->FollowEntity;
+ return m->CameraController.GetFollowedEntity();
}
void CGameView::SetCameraProjection()
{
- m->ViewCamera.SetPerspectiveProjection(m->ViewNear, m->ViewFar, m->ViewFOV);
+ m->CameraController.SetCameraProjection();
}
InReaction game_view_handler(const SDL_Event_* ev)
@@ -977,67 +409,7 @@ InReaction CGameView::HandleEvent(const SDL_Event_* ev)
}
return IN_HANDLED;
}
- // Mouse wheel must be treated using events instead of polling,
- // because SDL auto-generates a sequence of mousedown/mouseup events
- // and we never get to see the "down" state inside Update().
- else if (hotkey == "camera.zoom.wheel.in")
- {
- m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel);
- return IN_HANDLED;
- }
- else if (hotkey == "camera.zoom.wheel.out")
- {
- m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel);
- return IN_HANDLED;
- }
- else if (hotkey == "camera.rotate.wheel.cw")
- {
- m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel);
- return IN_HANDLED;
- }
- else if (hotkey == "camera.rotate.wheel.ccw")
- {
- m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel);
- return IN_HANDLED;
- }
- else if (hotkey == "camera.scroll.speed.increase")
- {
- m->ViewScrollSpeed *= m->ViewScrollSpeedModifier;
- return IN_HANDLED;
- }
- else if (hotkey == "camera.scroll.speed.decrease")
- {
- m->ViewScrollSpeed /= m->ViewScrollSpeedModifier;
- return IN_HANDLED;
- }
- else if (hotkey == "camera.rotate.speed.increase")
- {
- m->ViewRotateXSpeed *= m->ViewRotateSpeedModifier;
- m->ViewRotateYSpeed *= m->ViewRotateSpeedModifier;
- return IN_HANDLED;
- }
- else if (hotkey == "camera.rotate.speed.decrease")
- {
- m->ViewRotateXSpeed /= m->ViewRotateSpeedModifier;
- m->ViewRotateYSpeed /= m->ViewRotateSpeedModifier;
- return IN_HANDLED;
- }
- else if (hotkey == "camera.zoom.speed.increase")
- {
- m->ViewZoomSpeed *= m->ViewZoomSpeedModifier;
- return IN_HANDLED;
- }
- else if (hotkey == "camera.zoom.speed.decrease")
- {
- m->ViewZoomSpeed /= m->ViewZoomSpeedModifier;
- return IN_HANDLED;
- }
- else if (hotkey == "camera.reset")
- {
- ResetCameraAngleZoom();
- return IN_HANDLED;
- }
}
- return IN_PASS;
+ return m->CameraController.HandleEvent(ev);
}
diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h
index 8232b0ed0b..5c27187e9a 100644
--- a/source/graphics/GameView.h
+++ b/source/graphics/GameView.h
@@ -97,9 +97,6 @@ private:
// Checks whether lighting environment has changed and update vertex data if necessary.
void CheckLightEnv();
- CVector3D GetSmoothPivot(CCamera &camera) const;
- void ResetCameraAngleZoom();
-
CGameViewImpl* m;
};