1 /* Copyright (C) 2017 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 "GameView.h"
21 
22 #include "graphics/Camera.h"
23 #include "graphics/CinemaManager.h"
24 #include "graphics/ColladaManager.h"
25 #include "graphics/HFTracer.h"
26 #include "graphics/LOSTexture.h"
27 #include "graphics/LightEnv.h"
28 #include "graphics/Model.h"
29 #include "graphics/ObjectManager.h"
30 #include "graphics/Patch.h"
31 #include "graphics/SkeletonAnimManager.h"
32 #include "graphics/Terrain.h"
33 #include "graphics/TerrainTextureManager.h"
34 #include "graphics/TerritoryTexture.h"
35 #include "graphics/Unit.h"
36 #include "graphics/UnitManager.h"
37 #include "graphics/scripting/JSInterface_GameView.h"
38 #include "lib/input.h"
39 #include "lib/timer.h"
40 #include "lobby/IXmppClient.h"
41 #include "maths/BoundingBoxAligned.h"
42 #include "maths/MathUtil.h"
43 #include "maths/Matrix3D.h"
44 #include "maths/Quaternion.h"
45 #include "ps/ConfigDB.h"
46 #include "ps/Filesystem.h"
47 #include "ps/Game.h"
48 #include "ps/Globals.h"
49 #include "ps/Hotkey.h"
50 #include "ps/Joystick.h"
51 #include "ps/Loader.h"
52 #include "ps/LoaderThunks.h"
53 #include "ps/Profile.h"
54 #include "ps/Pyrogenesis.h"
55 #include "ps/TouchInput.h"
56 #include "ps/World.h"
57 #include "renderer/Renderer.h"
58 #include "renderer/WaterManager.h"
59 #include "simulation2/Simulation2.h"
60 #include "simulation2/components/ICmpPosition.h"
61 #include "simulation2/components/ICmpRangeManager.h"
62 
63 extern int g_xres, g_yres;
64 
65 // Maximum distance outside the edge of the map that the camera's
66 // focus point can be moved
67 static const float CAMERA_EDGE_MARGIN = 2.0f*TERRAIN_TILE_SIZE;
68 
69 /**
70  * A value with exponential decay towards the target value.
71  */
72 class CSmoothedValue
73 {
74 public:
CSmoothedValue(float value,float smoothness,float minDelta)75 	CSmoothedValue(float value, float smoothness, float minDelta)
76 		: m_Target(value), m_Current(value), m_Smoothness(smoothness), m_MinDelta(minDelta)
77 	{
78 	}
79 
GetSmoothedValue()80 	float GetSmoothedValue()
81 	{
82 		return m_Current;
83 	}
84 
SetValueSmoothly(float value)85 	void SetValueSmoothly(float value)
86 	{
87 		m_Target = value;
88 	}
89 
AddSmoothly(float value)90 	void AddSmoothly(float value)
91 	{
92 		m_Target += value;
93 	}
94 
Add(float value)95 	void Add(float value)
96 	{
97 		m_Target += value;
98 		m_Current += value;
99 	}
100 
GetValue()101 	float GetValue()
102 	{
103 		return m_Target;
104 	}
105 
SetValue(float value)106 	void SetValue(float value)
107 	{
108 		m_Target = value;
109 		m_Current = value;
110 	}
111 
Update(float time)112 	float Update(float time)
113 	{
114 		if (fabs(m_Target - m_Current) < m_MinDelta)
115 			return 0.0f;
116 
117 		double p = pow((double)m_Smoothness, 10.0 * (double)time);
118 		// (add the factor of 10 so that smoothnesses don't have to be tiny numbers)
119 
120 		double delta = (m_Target - m_Current) * (1.0 - p);
121 		m_Current += delta;
122 		return (float)delta;
123 	}
124 
ClampSmoothly(float min,float max)125 	void ClampSmoothly(float min, float max)
126 	{
127 		m_Target = Clamp(m_Target, (double)min, (double)max);
128 	}
129 
130 	// Wrap so 'target' is in the range [min, max]
Wrap(float min,float max)131 	void Wrap(float min, float max)
132 	{
133 		double t = fmod(m_Target - min, (double)(max - min));
134 		if (t < 0)
135 			t += max - min;
136 		t += min;
137 
138 		m_Current += t - m_Target;
139 		m_Target = t;
140 	}
141 
142 private:
143 	double m_Target; // the value which m_Current is tending towards
144 	double m_Current;
145 	// (We use double because the extra precision is worthwhile here)
146 
147 	float m_MinDelta; // cutoff where we stop moving (to avoid ugly shimmering effects)
148 public:
149 	float m_Smoothness;
150 };
151 
152 class CGameViewImpl
153 {
154 	NONCOPYABLE(CGameViewImpl);
155 public:
CGameViewImpl(CGame * game)156 	CGameViewImpl(CGame* game)
157 		: Game(game),
158 		ColladaManager(g_VFS), MeshManager(ColladaManager), SkeletonAnimManager(ColladaManager),
159 		ObjectManager(MeshManager, SkeletonAnimManager, *game->GetSimulation2()),
160 		LOSTexture(*game->GetSimulation2()),
161 		TerritoryTexture(*game->GetSimulation2()),
162 		ViewCamera(),
163 		CullCamera(),
164 		LockCullCamera(false),
165 		ConstrainCamera(true),
166 		Culling(true),
167 		FollowEntity(INVALID_ENTITY),
168 		FollowFirstPerson(false),
169 
170 		// Dummy values (these will be filled in by the config file)
171 		ViewScrollSpeed(0),
172 		ViewScrollSpeedModifier(1),
173 		ViewRotateXSpeed(0),
174 		ViewRotateXMin(0),
175 		ViewRotateXMax(0),
176 		ViewRotateXDefault(0),
177 		ViewRotateYSpeed(0),
178 		ViewRotateYSpeedWheel(0),
179 		ViewRotateYDefault(0),
180 		ViewRotateSpeedModifier(1),
181 		ViewDragSpeed(0),
182 		ViewZoomSpeed(0),
183 		ViewZoomSpeedWheel(0),
184 		ViewZoomMin(0),
185 		ViewZoomMax(0),
186 		ViewZoomDefault(0),
187 		ViewZoomSpeedModifier(1),
188 		ViewFOV(DEGTORAD(45.f)),
189 		ViewNear(2.f),
190 		ViewFar(4096.f),
191 		JoystickPanX(-1),
192 		JoystickPanY(-1),
193 		JoystickRotateX(-1),
194 		JoystickRotateY(-1),
195 		JoystickZoomIn(-1),
196 		JoystickZoomOut(-1),
197 		HeightSmoothness(0.5f),
198 		HeightMin(16.f),
199 
200 		PosX(0, 0, 0.01f),
201 		PosY(0, 0, 0.01f),
202 		PosZ(0, 0, 0.01f),
203 		Zoom(0, 0, 0.1f),
204 		RotateX(0, 0, 0.001f),
205 		RotateY(0, 0, 0.001f)
206 	{
207 	}
208 
209 	CGame* Game;
210 	CColladaManager ColladaManager;
211 	CMeshManager MeshManager;
212 	CSkeletonAnimManager SkeletonAnimManager;
213 	CObjectManager ObjectManager;
214 	CLOSTexture LOSTexture;
215 	CTerritoryTexture TerritoryTexture;
216 
217 	/**
218 	 * this camera controls the eye position when rendering
219 	 */
220 	CCamera ViewCamera;
221 
222 	/**
223 	 * this camera controls the frustum that is used for culling
224 	 * and shadow calculations
225 	 *
226 	 * Note that all code that works with camera movements should only change
227 	 * m_ViewCamera. The render functions automatically sync the cull camera to
228 	 * the view camera depending on the value of m_LockCullCamera.
229 	 */
230 	CCamera CullCamera;
231 
232 	/**
233 	 * When @c true, the cull camera is locked in place.
234 	 * When @c false, the cull camera follows the view camera.
235 	 *
236 	 * Exposed to JS as gameView.lockCullCamera
237 	 */
238 	bool LockCullCamera;
239 
240 	/**
241 	 * When @c true, culling is enabled so that only models that have a chance of
242 	 * being visible are sent to the renderer.
243 	 * Otherwise, the entire world is sent to the renderer.
244 	 *
245 	 * Exposed to JS as gameView.culling
246 	 */
247 	bool Culling;
248 
249 	/**
250 	 * Whether the camera movement should be constrained by min/max limits
251 	 * and terrain avoidance.
252 	 */
253 	bool ConstrainCamera;
254 
255 	/**
256 	 * Cache global lighting environment. This is used  to check whether the
257 	 * environment has changed during the last frame, so that vertex data can be updated etc.
258 	 */
259 	CLightEnv CachedLightEnv;
260 
261 	CCinemaManager CinemaManager;
262 
263 	/**
264 	 * Entity for the camera to follow, or INVALID_ENTITY if none.
265 	 */
266 	entity_id_t FollowEntity;
267 
268 	/**
269 	 * Whether to follow FollowEntity in first-person mode.
270 	 */
271 	bool FollowFirstPerson;
272 
273 	////////////////////////////////////////
274 	// Settings
275 	float ViewScrollSpeed;
276 	float ViewScrollSpeedModifier;
277 	float ViewRotateXSpeed;
278 	float ViewRotateXMin;
279 	float ViewRotateXMax;
280 	float ViewRotateXDefault;
281 	float ViewRotateYSpeed;
282 	float ViewRotateYSpeedWheel;
283 	float ViewRotateYDefault;
284 	float ViewRotateSpeedModifier;
285 	float ViewDragSpeed;
286 	float ViewZoomSpeed;
287 	float ViewZoomSpeedWheel;
288 	float ViewZoomMin;
289 	float ViewZoomMax;
290 	float ViewZoomDefault;
291 	float ViewZoomSpeedModifier;
292 	float ViewFOV;
293 	float ViewNear;
294 	float ViewFar;
295 	int JoystickPanX;
296 	int JoystickPanY;
297 	int JoystickRotateX;
298 	int JoystickRotateY;
299 	int JoystickZoomIn;
300 	int JoystickZoomOut;
301 	float HeightSmoothness;
302 	float HeightMin;
303 
304 	////////////////////////////////////////
305 	// Camera Controls State
306 	CSmoothedValue PosX;
307 	CSmoothedValue PosY;
308 	CSmoothedValue PosZ;
309 	CSmoothedValue Zoom;
310 	CSmoothedValue RotateX; // inclination around x axis (relative to camera)
311 	CSmoothedValue RotateY; // rotation around y (vertical) axis
312 };
313 
314 #define IMPLEMENT_BOOLEAN_SETTING(NAME) \
315 bool CGameView::Get##NAME##Enabled() \
316 { \
317 	return m->NAME; \
318 } \
319 \
320 void CGameView::Set##NAME##Enabled(bool Enabled) \
321 { \
322 	m->NAME = Enabled; \
323 }
324 
325 IMPLEMENT_BOOLEAN_SETTING(Culling);
326 IMPLEMENT_BOOLEAN_SETTING(LockCullCamera);
327 IMPLEMENT_BOOLEAN_SETTING(ConstrainCamera);
328 
329 #undef IMPLEMENT_BOOLEAN_SETTING
330 
SetupCameraMatrixSmooth(CGameViewImpl * m,CMatrix3D * orientation)331 static void SetupCameraMatrixSmooth(CGameViewImpl* m, CMatrix3D* orientation)
332 {
333 	orientation->SetIdentity();
334 	orientation->RotateX(m->RotateX.GetSmoothedValue());
335 	orientation->RotateY(m->RotateY.GetSmoothedValue());
336 	orientation->Translate(m->PosX.GetSmoothedValue(), m->PosY.GetSmoothedValue(), m->PosZ.GetSmoothedValue());
337 }
338 
SetupCameraMatrixSmoothRot(CGameViewImpl * m,CMatrix3D * orientation)339 static void SetupCameraMatrixSmoothRot(CGameViewImpl* m, CMatrix3D* orientation)
340 {
341 	orientation->SetIdentity();
342 	orientation->RotateX(m->RotateX.GetSmoothedValue());
343 	orientation->RotateY(m->RotateY.GetSmoothedValue());
344 	orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
345 }
346 
SetupCameraMatrixNonSmooth(CGameViewImpl * m,CMatrix3D * orientation)347 static void SetupCameraMatrixNonSmooth(CGameViewImpl* m, CMatrix3D* orientation)
348 {
349 	orientation->SetIdentity();
350 	orientation->RotateX(m->RotateX.GetValue());
351 	orientation->RotateY(m->RotateY.GetValue());
352 	orientation->Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
353 }
354 
CGameView(CGame * pGame)355 CGameView::CGameView(CGame *pGame):
356 	m(new CGameViewImpl(pGame))
357 {
358 	SViewPort vp;
359 	vp.m_X=0;
360 	vp.m_Y=0;
361 	vp.m_Width=g_xres;
362 	vp.m_Height=g_yres;
363 	m->ViewCamera.SetViewPort(vp);
364 
365 	m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV);
366 	SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
367 	m->ViewCamera.UpdateFrustum();
368 
369 	m->CullCamera = m->ViewCamera;
370 	g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera);
371 }
372 
~CGameView()373 CGameView::~CGameView()
374 {
375 	UnloadResources();
376 
377 	delete m;
378 }
379 
SetViewport(const SViewPort & vp)380 void CGameView::SetViewport(const SViewPort& vp)
381 {
382 	m->ViewCamera.SetViewPort(vp);
383 	m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV);
384 }
385 
GetObjectManager() const386 CObjectManager& CGameView::GetObjectManager() const
387 {
388 	return m->ObjectManager;
389 }
390 
GetCamera()391 CCamera* CGameView::GetCamera()
392 {
393 	return &m->ViewCamera;
394 }
395 
GetCinema()396 CCinemaManager* CGameView::GetCinema()
397 {
398 	return &m->CinemaManager;
399 };
400 
GetLOSTexture()401 CLOSTexture& CGameView::GetLOSTexture()
402 {
403 	return m->LOSTexture;
404 }
405 
GetTerritoryTexture()406 CTerritoryTexture& CGameView::GetTerritoryTexture()
407 {
408 	return m->TerritoryTexture;
409 }
410 
Initialize()411 int CGameView::Initialize()
412 {
413 	CFG_GET_VAL("view.scroll.speed", m->ViewScrollSpeed);
414 	CFG_GET_VAL("view.scroll.speed.modifier", m->ViewScrollSpeedModifier);
415 	CFG_GET_VAL("view.rotate.x.speed", m->ViewRotateXSpeed);
416 	CFG_GET_VAL("view.rotate.x.min", m->ViewRotateXMin);
417 	CFG_GET_VAL("view.rotate.x.max", m->ViewRotateXMax);
418 	CFG_GET_VAL("view.rotate.x.default", m->ViewRotateXDefault);
419 	CFG_GET_VAL("view.rotate.y.speed", m->ViewRotateYSpeed);
420 	CFG_GET_VAL("view.rotate.y.speed.wheel", m->ViewRotateYSpeedWheel);
421 	CFG_GET_VAL("view.rotate.y.default", m->ViewRotateYDefault);
422 	CFG_GET_VAL("view.rotate.speed.modifier", m->ViewRotateSpeedModifier);
423 	CFG_GET_VAL("view.drag.speed", m->ViewDragSpeed);
424 	CFG_GET_VAL("view.zoom.speed", m->ViewZoomSpeed);
425 	CFG_GET_VAL("view.zoom.speed.wheel", m->ViewZoomSpeedWheel);
426 	CFG_GET_VAL("view.zoom.min", m->ViewZoomMin);
427 	CFG_GET_VAL("view.zoom.max", m->ViewZoomMax);
428 	CFG_GET_VAL("view.zoom.default", m->ViewZoomDefault);
429 	CFG_GET_VAL("view.zoom.speed.modifier", m->ViewZoomSpeedModifier);
430 
431 	CFG_GET_VAL("joystick.camera.pan.x", m->JoystickPanX);
432 	CFG_GET_VAL("joystick.camera.pan.y", m->JoystickPanY);
433 	CFG_GET_VAL("joystick.camera.rotate.x", m->JoystickRotateX);
434 	CFG_GET_VAL("joystick.camera.rotate.y", m->JoystickRotateY);
435 	CFG_GET_VAL("joystick.camera.zoom.in", m->JoystickZoomIn);
436 	CFG_GET_VAL("joystick.camera.zoom.out", m->JoystickZoomOut);
437 
438 	CFG_GET_VAL("view.height.smoothness", m->HeightSmoothness);
439 	CFG_GET_VAL("view.height.min", m->HeightMin);
440 
441 	CFG_GET_VAL("view.pos.smoothness", m->PosX.m_Smoothness);
442 	CFG_GET_VAL("view.pos.smoothness", m->PosY.m_Smoothness);
443 	CFG_GET_VAL("view.pos.smoothness", m->PosZ.m_Smoothness);
444 	CFG_GET_VAL("view.zoom.smoothness", m->Zoom.m_Smoothness);
445 	CFG_GET_VAL("view.rotate.x.smoothness", m->RotateX.m_Smoothness);
446 	CFG_GET_VAL("view.rotate.y.smoothness", m->RotateY.m_Smoothness);
447 
448 	CFG_GET_VAL("view.near", m->ViewNear);
449 	CFG_GET_VAL("view.far", m->ViewFar);
450 	CFG_GET_VAL("view.fov", m->ViewFOV);
451 
452 	// Convert to radians
453 	m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault));
454 	m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault));
455 	m->ViewFOV = DEGTORAD(m->ViewFOV);
456 
457 	return 0;
458 }
459 
460 
461 
RegisterInit()462 void CGameView::RegisterInit()
463 {
464 	// CGameView init
465 	RegMemFun(this, &CGameView::Initialize, L"CGameView init", 1);
466 
467 	// previously done by CGameView::InitResources
468 	RegMemFun(g_TexMan.GetSingletonPtr(), &CTerrainTextureManager::LoadTerrainTextures, L"LoadTerrainTextures", 60);
469 	RegMemFun(g_Renderer.GetSingletonPtr(), &CRenderer::LoadAlphaMaps, L"LoadAlphaMaps", 5);
470 }
471 
472 
BeginFrame()473 void CGameView::BeginFrame()
474 {
475 	if (m->LockCullCamera == false)
476 	{
477 		// Set up cull camera
478 		m->CullCamera = m->ViewCamera;
479 	}
480 	g_Renderer.SetSceneCamera(m->ViewCamera, m->CullCamera);
481 
482 	CheckLightEnv();
483 
484 	m->Game->CachePlayerColors();
485 }
486 
Render()487 void CGameView::Render()
488 {
489 	g_Renderer.RenderScene(*this);
490 }
491 
492 ///////////////////////////////////////////////////////////
493 // This callback is part of the Scene interface
494 // Submit all objects visible in the given frustum
EnumerateObjects(const CFrustum & frustum,SceneCollector * c)495 void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
496 {
497 	{
498 	PROFILE3("submit terrain");
499 
500 	CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
501 	float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f;
502 	const ssize_t patchesPerSide = pTerrain->GetPatchesPerSide();
503 
504 	// find out which patches will be drawn
505 	for (ssize_t j=0; j<patchesPerSide; ++j)
506 	{
507 		for (ssize_t i=0; i<patchesPerSide; ++i)
508 		{
509 			CPatch* patch=pTerrain->GetPatch(i,j);	// can't fail
510 
511 			// If the patch is underwater, calculate a bounding box that also contains the water plane
512 			CBoundingBoxAligned bounds = patch->GetWorldBounds();
513 			if(bounds[1].Y < waterHeight)
514 				bounds[1].Y = waterHeight;
515 
516 			if (!m->Culling || frustum.IsBoxVisible(bounds))
517 				c->Submit(patch);
518 		}
519 	}
520 	}
521 
522 	m->Game->GetSimulation2()->RenderSubmit(*c, frustum, m->Culling);
523 }
524 
525 
CheckLightEnv()526 void CGameView::CheckLightEnv()
527 {
528 	if (m->CachedLightEnv == g_LightEnv)
529 		return;
530 
531 	if (m->CachedLightEnv.GetLightingModel() != g_LightEnv.GetLightingModel())
532 		g_Renderer.MakeShadersDirty();
533 
534 	m->CachedLightEnv = g_LightEnv;
535 	CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
536 
537 	if (!pTerrain)
538 		return;
539 
540 	PROFILE("update light env");
541 	pTerrain->MakeDirty(RENDERDATA_UPDATE_COLOR);
542 
543 	const std::vector<CUnit*>& units = m->Game->GetWorld()->GetUnitManager().GetUnits();
544 	for (size_t i = 0; i < units.size(); ++i)
545 		units[i]->GetModel().SetDirtyRec(RENDERDATA_UPDATE_COLOR);
546 }
547 
548 
UnloadResources()549 void CGameView::UnloadResources()
550 {
551 	g_TexMan.UnloadTerrainTextures();
552 	g_Renderer.UnloadAlphaMaps();
553 	g_Renderer.GetWaterManager()->UnloadWaterTextures();
554 }
555 
FocusHeight(CGameViewImpl * m,bool smooth)556 static void FocusHeight(CGameViewImpl* m, bool smooth)
557 {
558 	/*
559 		The camera pivot height is moved towards ground level.
560 		To prevent excessive zoom when looking over a cliff,
561 		the target ground level is the maximum of the ground level at the camera's near and pivot points.
562 		The ground levels are filtered to achieve smooth camera movement.
563 		The filter radius is proportional to the zoom level.
564 		The camera height is clamped to prevent map penetration.
565 	*/
566 
567 	if (!m->ConstrainCamera)
568 		return;
569 
570 	CCamera targetCam = m->ViewCamera;
571 	SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
572 
573 	const CVector3D position = targetCam.m_Orientation.GetTranslation();
574 	const CVector3D forwards = targetCam.m_Orientation.GetIn();
575 
576 	// horizontal view radius
577 	const float radius = sqrtf(forwards.X * forwards.X + forwards.Z * forwards.Z) * m->Zoom.GetSmoothedValue();
578 	const float near_radius = radius * m->HeightSmoothness;
579 	const float pivot_radius = radius * m->HeightSmoothness;
580 
581 	const CVector3D nearPoint = position + forwards * m->ViewNear;
582 	const CVector3D pivotPoint = position + forwards * m->Zoom.GetSmoothedValue();
583 
584 	const float ground = std::max(
585 		m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z),
586 		g_Renderer.GetWaterManager()->m_WaterHeight);
587 
588 	// filter ground levels for smooth camera movement
589 	const float filtered_near_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(nearPoint.X, nearPoint.Z, near_radius);
590 	const float filtered_pivot_ground = m->Game->GetWorld()->GetTerrain()->GetFilteredGroundLevel(pivotPoint.X, pivotPoint.Z, pivot_radius);
591 
592 	// filtered maximum visible ground level in view
593 	const float filtered_ground = std::max(
594 		std::max(filtered_near_ground, filtered_pivot_ground),
595 		g_Renderer.GetWaterManager()->m_WaterHeight);
596 
597 	// target camera height above pivot point
598 	const float pivot_height = -forwards.Y * (m->Zoom.GetSmoothedValue() - m->ViewNear);
599 
600 	// minimum camera height above filtered ground level
601 	const float min_height = (m->HeightMin + ground - filtered_ground);
602 
603 	const float target_height = std::max(pivot_height, min_height);
604 	const float height = (nearPoint.Y - filtered_ground);
605 	const float diff = target_height - height;
606 
607 	if (fabsf(diff) < 0.0001f)
608 		return;
609 
610 	if (smooth)
611 		m->PosY.AddSmoothly(diff);
612 	else
613 		m->PosY.Add(diff);
614 }
615 
GetSmoothPivot(CCamera & camera) const616 CVector3D CGameView::GetSmoothPivot(CCamera& camera) const
617 {
618 	return camera.m_Orientation.GetTranslation() + camera.m_Orientation.GetIn() * m->Zoom.GetSmoothedValue();
619 }
620 
Update(const float deltaRealTime)621 void CGameView::Update(const float deltaRealTime)
622 {
623 	// If camera movement is being handled by the touch-input system,
624 	// then we should stop to avoid conflicting with it
625 	if (g_TouchInput.IsEnabled())
626 		return;
627 
628 	if (!g_app_has_focus)
629 		return;
630 
631 	m->CinemaManager.Update(deltaRealTime);
632 	if (m->CinemaManager.IsEnabled())
633 		return;
634 
635 	// Calculate mouse movement
636 	static int mouse_last_x = 0;
637 	static int mouse_last_y = 0;
638 	int mouse_dx = g_mouse_x - mouse_last_x;
639 	int mouse_dy = g_mouse_y - mouse_last_y;
640 	mouse_last_x = g_mouse_x;
641 	mouse_last_y = g_mouse_y;
642 
643 	if (HotkeyIsPressed("camera.rotate.cw"))
644 		m->RotateY.AddSmoothly(m->ViewRotateYSpeed * deltaRealTime);
645 	if (HotkeyIsPressed("camera.rotate.ccw"))
646 		m->RotateY.AddSmoothly(-m->ViewRotateYSpeed * deltaRealTime);
647 	if (HotkeyIsPressed("camera.rotate.up"))
648 		m->RotateX.AddSmoothly(-m->ViewRotateXSpeed * deltaRealTime);
649 	if (HotkeyIsPressed("camera.rotate.down"))
650 		m->RotateX.AddSmoothly(m->ViewRotateXSpeed * deltaRealTime);
651 
652 	float moveRightward = 0.f;
653 	float moveForward = 0.f;
654 
655 	if (HotkeyIsPressed("camera.pan"))
656 	{
657 		moveRightward += m->ViewDragSpeed * mouse_dx;
658 		moveForward += m->ViewDragSpeed * -mouse_dy;
659 	}
660 
661 	if (g_mouse_active)
662 	{
663 		if (g_mouse_x >= g_xres - 2 && g_mouse_x < g_xres)
664 			moveRightward += m->ViewScrollSpeed * deltaRealTime;
665 		else if (g_mouse_x <= 3 && g_mouse_x >= 0)
666 			moveRightward -= m->ViewScrollSpeed * deltaRealTime;
667 
668 		if (g_mouse_y >= g_yres - 2 && g_mouse_y < g_yres)
669 			moveForward -= m->ViewScrollSpeed * deltaRealTime;
670 		else if (g_mouse_y <= 3 && g_mouse_y >= 0)
671 			moveForward += m->ViewScrollSpeed * deltaRealTime;
672 	}
673 
674 	if (HotkeyIsPressed("camera.right"))
675 		moveRightward += m->ViewScrollSpeed * deltaRealTime;
676 	if (HotkeyIsPressed("camera.left"))
677 		moveRightward -= m->ViewScrollSpeed * deltaRealTime;
678 	if (HotkeyIsPressed("camera.up"))
679 		moveForward += m->ViewScrollSpeed * deltaRealTime;
680 	if (HotkeyIsPressed("camera.down"))
681 		moveForward -= m->ViewScrollSpeed * deltaRealTime;
682 
683 	if (g_Joystick.IsEnabled())
684 	{
685 		// This could all be improved with extra speed and sensitivity settings
686 		// (maybe use pow to allow finer control?), and inversion settings
687 
688 		moveRightward += g_Joystick.GetAxisValue(m->JoystickPanX) * m->ViewScrollSpeed * deltaRealTime;
689 		moveForward -= g_Joystick.GetAxisValue(m->JoystickPanY) * m->ViewScrollSpeed * deltaRealTime;
690 
691 		m->RotateX.AddSmoothly(g_Joystick.GetAxisValue(m->JoystickRotateX) * m->ViewRotateXSpeed * deltaRealTime);
692 		m->RotateY.AddSmoothly(-g_Joystick.GetAxisValue(m->JoystickRotateY) * m->ViewRotateYSpeed * deltaRealTime);
693 
694 		// Use a +1 bias for zoom because I want this to work with trigger buttons that default to -1
695 		m->Zoom.AddSmoothly((g_Joystick.GetAxisValue(m->JoystickZoomIn) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime);
696 		m->Zoom.AddSmoothly(-(g_Joystick.GetAxisValue(m->JoystickZoomOut) + 1.0f) / 2.0f * m->ViewZoomSpeed * deltaRealTime);
697 	}
698 
699 	if (moveRightward || moveForward)
700 	{
701 		// Break out of following mode when the user starts scrolling
702 		m->FollowEntity = INVALID_ENTITY;
703 
704 		float s = sin(m->RotateY.GetSmoothedValue());
705 		float c = cos(m->RotateY.GetSmoothedValue());
706 		m->PosX.AddSmoothly(c * moveRightward);
707 		m->PosZ.AddSmoothly(-s * moveRightward);
708 		m->PosX.AddSmoothly(s * moveForward);
709 		m->PosZ.AddSmoothly(c * moveForward);
710 	}
711 
712 	if (m->FollowEntity)
713 	{
714 		CmpPtr<ICmpPosition> cmpPosition(*(m->Game->GetSimulation2()), m->FollowEntity);
715 		CmpPtr<ICmpRangeManager> cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY);
716 		if (cmpPosition && cmpPosition->IsInWorld() &&
717 		    cmpRangeManager && cmpRangeManager->GetLosVisibility(m->FollowEntity, m->Game->GetViewedPlayerID()) == ICmpRangeManager::VIS_VISIBLE)
718 		{
719 			// Get the most recent interpolated position
720 			float frameOffset = m->Game->GetSimulation2()->GetLastFrameOffset();
721 			CMatrix3D transform = cmpPosition->GetInterpolatedTransform(frameOffset);
722 			CVector3D pos = transform.GetTranslation();
723 
724 			if (m->FollowFirstPerson)
725 			{
726 				float x, z, angle;
727 				cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, angle);
728 				float height = 4.f;
729 				m->ViewCamera.m_Orientation.SetIdentity();
730 				m->ViewCamera.m_Orientation.RotateX((float)M_PI/24.f);
731 				m->ViewCamera.m_Orientation.RotateY(angle);
732 				m->ViewCamera.m_Orientation.Translate(pos.X, pos.Y + height, pos.Z);
733 
734 				m->ViewCamera.UpdateFrustum();
735 				return;
736 			}
737 			else
738 			{
739 				// Move the camera to match the unit
740 				CCamera targetCam = m->ViewCamera;
741 				SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
742 
743 				CVector3D pivot = GetSmoothPivot(targetCam);
744 				CVector3D delta = pos - pivot;
745 				m->PosX.AddSmoothly(delta.X);
746 				m->PosY.AddSmoothly(delta.Y);
747 				m->PosZ.AddSmoothly(delta.Z);
748 			}
749 		}
750 		else
751 		{
752 			// The unit disappeared (died or garrisoned etc), so stop following it
753 			m->FollowEntity = INVALID_ENTITY;
754 		}
755 	}
756 
757 	if (HotkeyIsPressed("camera.zoom.in"))
758 		m->Zoom.AddSmoothly(-m->ViewZoomSpeed * deltaRealTime);
759 	if (HotkeyIsPressed("camera.zoom.out"))
760 		m->Zoom.AddSmoothly(m->ViewZoomSpeed * deltaRealTime);
761 
762 	if (m->ConstrainCamera)
763 		m->Zoom.ClampSmoothly(m->ViewZoomMin, m->ViewZoomMax);
764 
765 	float zoomDelta = -m->Zoom.Update(deltaRealTime);
766 	if (zoomDelta)
767 	{
768 		CVector3D forwards = m->ViewCamera.m_Orientation.GetIn();
769 		m->PosX.AddSmoothly(forwards.X * zoomDelta);
770 		m->PosY.AddSmoothly(forwards.Y * zoomDelta);
771 		m->PosZ.AddSmoothly(forwards.Z * zoomDelta);
772 	}
773 
774 	if (m->ConstrainCamera)
775 		m->RotateX.ClampSmoothly(DEGTORAD(m->ViewRotateXMin), DEGTORAD(m->ViewRotateXMax));
776 
777 	FocusHeight(m, true);
778 
779 	// Ensure the ViewCamera focus is inside the map with the chosen margins
780 	// if not so - apply margins to the camera
781 	if (m->ConstrainCamera)
782 	{
783 		CCamera targetCam = m->ViewCamera;
784 		SetupCameraMatrixSmoothRot(m, &targetCam.m_Orientation);
785 
786 		CTerrain* pTerrain = m->Game->GetWorld()->GetTerrain();
787 
788 		CVector3D pivot = GetSmoothPivot(targetCam);
789 		CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
790 
791 		CVector3D desiredPivot = pivot;
792 
793 		CmpPtr<ICmpRangeManager> cmpRangeManager(*(m->Game->GetSimulation2()), SYSTEM_ENTITY);
794 		if (cmpRangeManager && cmpRangeManager->GetLosCircular())
795 		{
796 			// Clamp to a circular region around the center of the map
797 			float r = pTerrain->GetMaxX() / 2;
798 			CVector3D center(r, desiredPivot.Y, r);
799 			float dist = (desiredPivot - center).Length();
800 			if (dist > r - CAMERA_EDGE_MARGIN)
801 				desiredPivot = center + (desiredPivot - center).Normalized() * (r - CAMERA_EDGE_MARGIN);
802 		}
803 		else
804 		{
805 			// Clamp to the square edges of the map
806 			desiredPivot.X = Clamp(desiredPivot.X, pTerrain->GetMinX() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxX() - CAMERA_EDGE_MARGIN);
807 			desiredPivot.Z = Clamp(desiredPivot.Z, pTerrain->GetMinZ() + CAMERA_EDGE_MARGIN, pTerrain->GetMaxZ() - CAMERA_EDGE_MARGIN);
808 		}
809 
810 		// Update the position so that pivot is within the margin
811 		m->PosX.SetValueSmoothly(desiredPivot.X + delta.X);
812 		m->PosZ.SetValueSmoothly(desiredPivot.Z + delta.Z);
813 	}
814 
815 	m->PosX.Update(deltaRealTime);
816 	m->PosY.Update(deltaRealTime);
817 	m->PosZ.Update(deltaRealTime);
818 
819 	// Handle rotation around the Y (vertical) axis
820 	{
821 		CCamera targetCam = m->ViewCamera;
822 		SetupCameraMatrixSmooth(m, &targetCam.m_Orientation);
823 
824 		float rotateYDelta = m->RotateY.Update(deltaRealTime);
825 		if (rotateYDelta)
826 		{
827 			// We've updated RotateY, and need to adjust Pos so that it's still
828 			// facing towards the original focus point (the terrain in the center
829 			// of the screen).
830 
831 			CVector3D upwards(0.0f, 1.0f, 0.0f);
832 
833 			CVector3D pivot = GetSmoothPivot(targetCam);
834 			CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
835 
836 			CQuaternion q;
837 			q.FromAxisAngle(upwards, rotateYDelta);
838 			CVector3D d = q.Rotate(delta) - delta;
839 
840 			m->PosX.Add(d.X);
841 			m->PosY.Add(d.Y);
842 			m->PosZ.Add(d.Z);
843 		}
844 	}
845 
846 	// Handle rotation around the X (sideways, relative to camera) axis
847 	{
848 		CCamera targetCam = m->ViewCamera;
849 		SetupCameraMatrixSmooth(m, &targetCam.m_Orientation);
850 
851 		float rotateXDelta = m->RotateX.Update(deltaRealTime);
852 		if (rotateXDelta)
853 		{
854 			CVector3D rightwards = targetCam.m_Orientation.GetLeft() * -1.0f;
855 
856 			CVector3D pivot = GetSmoothPivot(targetCam);
857 			CVector3D delta = targetCam.m_Orientation.GetTranslation() - pivot;
858 
859 			CQuaternion q;
860 			q.FromAxisAngle(rightwards, rotateXDelta);
861 			CVector3D d = q.Rotate(delta) - delta;
862 
863 			m->PosX.Add(d.X);
864 			m->PosY.Add(d.Y);
865 			m->PosZ.Add(d.Z);
866 		}
867 	}
868 
869 	/* This is disabled since it doesn't seem necessary:
870 
871 	// Ensure the camera's near point is never inside the terrain
872 	if (m->ConstrainCamera)
873 	{
874 		CMatrix3D target;
875 		target.SetIdentity();
876 		target.RotateX(m->RotateX.GetValue());
877 		target.RotateY(m->RotateY.GetValue());
878 		target.Translate(m->PosX.GetValue(), m->PosY.GetValue(), m->PosZ.GetValue());
879 
880 		CVector3D nearPoint = target.GetTranslation() + target.GetIn() * defaultNear;
881 		float ground = m->Game->GetWorld()->GetTerrain()->GetExactGroundLevel(nearPoint.X, nearPoint.Z);
882 		float limit = ground + 16.f;
883 		if (nearPoint.Y < limit)
884 			m->PosY.AddSmoothly(limit - nearPoint.Y);
885 	}
886 	*/
887 
888 	m->RotateY.Wrap(-(float)M_PI, (float)M_PI);
889 
890 	// Update the camera matrix
891 	m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV);
892 	SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
893 	m->ViewCamera.UpdateFrustum();
894 }
895 
GetCameraX()896 float CGameView::GetCameraX()
897 {
898 	CCamera targetCam = m->ViewCamera;
899 	CVector3D pivot = GetSmoothPivot(targetCam);
900 	return pivot.X;
901 }
902 
GetCameraZ()903 float CGameView::GetCameraZ()
904 {
905 	CCamera targetCam = m->ViewCamera;
906 	CVector3D pivot = GetSmoothPivot(targetCam);
907 	return pivot.Z;
908 }
909 
GetCameraPosX()910 float CGameView::GetCameraPosX()
911 {
912 	return m->PosX.GetValue();
913 }
914 
GetCameraPosY()915 float CGameView::GetCameraPosY()
916 {
917 	return m->PosY.GetValue();
918 }
919 
GetCameraPosZ()920 float CGameView::GetCameraPosZ()
921 {
922 	return m->PosZ.GetValue();
923 }
924 
GetCameraRotX()925 float CGameView::GetCameraRotX()
926 {
927 	return m->RotateX.GetValue();
928 }
929 
GetCameraRotY()930 float CGameView::GetCameraRotY()
931 {
932 	return m->RotateY.GetValue();
933 }
934 
GetCameraZoom()935 float CGameView::GetCameraZoom()
936 {
937 	return m->Zoom.GetValue();
938 }
939 
SetCamera(CVector3D Pos,float RotX,float RotY,float zoom)940 void CGameView::SetCamera(CVector3D Pos, float RotX, float RotY, float zoom)
941 {
942 	m->PosX.SetValue(Pos.X);
943 	m->PosY.SetValue(Pos.Y);
944 	m->PosZ.SetValue(Pos.Z);
945 	m->RotateX.SetValue(RotX);
946 	m->RotateY.SetValue(RotY);
947 	m->Zoom.SetValue(zoom);
948 
949 	FocusHeight(m, false);
950 
951 	SetupCameraMatrixNonSmooth(m, &m->ViewCamera.m_Orientation);
952 	m->ViewCamera.UpdateFrustum();
953 
954 	// Break out of following mode so the camera really moves to the target
955 	m->FollowEntity = INVALID_ENTITY;
956 }
957 
MoveCameraTarget(const CVector3D & target)958 void CGameView::MoveCameraTarget(const CVector3D& target)
959 {
960 	// Maintain the same orientation and level of zoom, if we can
961 	// (do this by working out the point the camera is looking at, saving
962 	//  the difference between that position and the camera point, and restoring
963 	//  that difference to our new target)
964 
965 	CCamera targetCam = m->ViewCamera;
966 	SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation);
967 
968 	CVector3D pivot = GetSmoothPivot(targetCam);
969 	CVector3D delta = target - pivot;
970 
971 	m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue());
972 	m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());
973 
974 	FocusHeight(m, false);
975 
976 	// Break out of following mode so the camera really moves to the target
977 	m->FollowEntity = INVALID_ENTITY;
978 }
979 
ResetCameraTarget(const CVector3D & target)980 void CGameView::ResetCameraTarget(const CVector3D& target)
981 {
982 	CMatrix3D orientation;
983 	orientation.SetIdentity();
984 	orientation.RotateX(DEGTORAD(m->ViewRotateXDefault));
985 	orientation.RotateY(DEGTORAD(m->ViewRotateYDefault));
986 
987 	CVector3D delta = orientation.GetIn() * m->ViewZoomDefault;
988 	m->PosX.SetValue(target.X - delta.X);
989 	m->PosY.SetValue(target.Y - delta.Y);
990 	m->PosZ.SetValue(target.Z - delta.Z);
991 	m->RotateX.SetValue(DEGTORAD(m->ViewRotateXDefault));
992 	m->RotateY.SetValue(DEGTORAD(m->ViewRotateYDefault));
993 	m->Zoom.SetValue(m->ViewZoomDefault);
994 
995 	FocusHeight(m, false);
996 
997 	SetupCameraMatrixSmooth(m, &m->ViewCamera.m_Orientation);
998 	m->ViewCamera.UpdateFrustum();
999 
1000 	// Break out of following mode so the camera really moves to the target
1001 	m->FollowEntity = INVALID_ENTITY;
1002 }
1003 
ResetCameraAngleZoom()1004 void CGameView::ResetCameraAngleZoom()
1005 {
1006 	CCamera targetCam = m->ViewCamera;
1007 	SetupCameraMatrixNonSmooth(m, &targetCam.m_Orientation);
1008 
1009 	// Compute the zoom adjustment to get us back to the default
1010 	CVector3D forwards = targetCam.m_Orientation.GetIn();
1011 
1012 	CVector3D pivot = GetSmoothPivot(targetCam);
1013 	CVector3D delta = pivot - targetCam.m_Orientation.GetTranslation();
1014 	float dist = delta.Dot(forwards);
1015 	m->Zoom.AddSmoothly(m->ViewZoomDefault - dist);
1016 
1017 	// Reset orientations to default
1018 	m->RotateX.SetValueSmoothly(DEGTORAD(m->ViewRotateXDefault));
1019 	m->RotateY.SetValueSmoothly(DEGTORAD(m->ViewRotateYDefault));
1020 }
1021 
CameraFollow(entity_id_t entity,bool firstPerson)1022 void CGameView::CameraFollow(entity_id_t entity, bool firstPerson)
1023 {
1024 	m->FollowEntity = entity;
1025 	m->FollowFirstPerson = firstPerson;
1026 }
1027 
GetFollowedEntity()1028 entity_id_t CGameView::GetFollowedEntity()
1029 {
1030 	return m->FollowEntity;
1031 }
1032 
GetNear() const1033 float CGameView::GetNear() const
1034 {
1035 	return m->ViewNear;
1036 }
1037 
GetFar() const1038 float CGameView::GetFar() const
1039 {
1040 	return m->ViewFar;
1041 }
1042 
GetFOV() const1043 float CGameView::GetFOV() const
1044 {
1045 	return m->ViewFOV;
1046 }
1047 
SetCameraProjection()1048 void CGameView::SetCameraProjection()
1049 {
1050 	m->ViewCamera.SetProjection(m->ViewNear, m->ViewFar, m->ViewFOV);
1051 }
1052 
game_view_handler(const SDL_Event_ * ev)1053 InReaction game_view_handler(const SDL_Event_* ev)
1054 {
1055 	// put any events that must be processed even if inactive here
1056 	if (!g_app_has_focus || !g_Game || !g_Game->IsGameStarted() || g_Game->GetView()->GetCinema()->IsEnabled())
1057 		return IN_PASS;
1058 
1059 	CGameView *pView=g_Game->GetView();
1060 
1061 	return pView->HandleEvent(ev);
1062 }
1063 
HandleEvent(const SDL_Event_ * ev)1064 InReaction CGameView::HandleEvent(const SDL_Event_* ev)
1065 {
1066 	switch(ev->ev.type)
1067 	{
1068 
1069 	case SDL_HOTKEYDOWN:
1070 		std::string hotkey = static_cast<const char*>(ev->ev.user.data1);
1071 
1072 		if (hotkey == "wireframe")
1073 		{
1074 			if (g_XmppClient && g_rankedGame == true)
1075 				break;
1076 			else if (g_Renderer.GetModelRenderMode() == SOLID)
1077 			{
1078 				g_Renderer.SetTerrainRenderMode(EDGED_FACES);
1079 				g_Renderer.SetWaterRenderMode(EDGED_FACES);
1080 				g_Renderer.SetModelRenderMode(EDGED_FACES);
1081 			}
1082 			else if (g_Renderer.GetModelRenderMode() == EDGED_FACES)
1083 			{
1084 				g_Renderer.SetTerrainRenderMode(WIREFRAME);
1085 				g_Renderer.SetWaterRenderMode(WIREFRAME);
1086 				g_Renderer.SetModelRenderMode(WIREFRAME);
1087 			}
1088 			else
1089 			{
1090 				g_Renderer.SetTerrainRenderMode(SOLID);
1091 				g_Renderer.SetWaterRenderMode(SOLID);
1092 				g_Renderer.SetModelRenderMode(SOLID);
1093 			}
1094 			return IN_HANDLED;
1095 		}
1096 		// Mouse wheel must be treated using events instead of polling,
1097 		// because SDL auto-generates a sequence of mousedown/mouseup events
1098 		// and we never get to see the "down" state inside Update().
1099 		else if (hotkey == "camera.zoom.wheel.in")
1100 		{
1101 			m->Zoom.AddSmoothly(-m->ViewZoomSpeedWheel);
1102 			return IN_HANDLED;
1103 		}
1104 		else if (hotkey == "camera.zoom.wheel.out")
1105 		{
1106 			m->Zoom.AddSmoothly(m->ViewZoomSpeedWheel);
1107 			return IN_HANDLED;
1108 		}
1109 		else if (hotkey == "camera.rotate.wheel.cw")
1110 		{
1111 			m->RotateY.AddSmoothly(m->ViewRotateYSpeedWheel);
1112 			return IN_HANDLED;
1113 		}
1114 		else if (hotkey == "camera.rotate.wheel.ccw")
1115 		{
1116 			m->RotateY.AddSmoothly(-m->ViewRotateYSpeedWheel);
1117 			return IN_HANDLED;
1118 		}
1119 		else if (hotkey == "camera.scroll.speed.increase")
1120 		{
1121 			m->ViewScrollSpeed *= m->ViewScrollSpeedModifier;
1122 			return IN_HANDLED;
1123 		}
1124 		else if (hotkey == "camera.scroll.speed.decrease")
1125 		{
1126 			m->ViewScrollSpeed /= m->ViewScrollSpeedModifier;
1127 			return IN_HANDLED;
1128 		}
1129 		else if (hotkey == "camera.rotate.speed.increase")
1130 		{
1131 			m->ViewRotateXSpeed *= m->ViewRotateSpeedModifier;
1132 			m->ViewRotateYSpeed *= m->ViewRotateSpeedModifier;
1133 			return IN_HANDLED;
1134 		}
1135 		else if (hotkey == "camera.rotate.speed.decrease")
1136 		{
1137 			m->ViewRotateXSpeed /= m->ViewRotateSpeedModifier;
1138 			m->ViewRotateYSpeed /= m->ViewRotateSpeedModifier;
1139 			return IN_HANDLED;
1140 		}
1141 		else if (hotkey == "camera.zoom.speed.increase")
1142 		{
1143 			m->ViewZoomSpeed *= m->ViewZoomSpeedModifier;
1144 			return IN_HANDLED;
1145 		}
1146 		else if (hotkey == "camera.zoom.speed.decrease")
1147 		{
1148 			m->ViewZoomSpeed /= m->ViewZoomSpeedModifier;
1149 			return IN_HANDLED;
1150 		}
1151 		else if (hotkey == "camera.reset")
1152 		{
1153 			ResetCameraAngleZoom();
1154 			return IN_HANDLED;
1155 		}
1156 	}
1157 
1158 	return IN_PASS;
1159 }
1160