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