1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include <engine/shared/config.h>
5
6#include <base/log.h>
7#include <base/math.h>
8#include <base/vmath.h>
9#include <game/client/gameclient.h>
10#include <game/collision.h>
11#include <game/mapitems.h>
12
13#include "camera.h"
14#include "controls.h"
15
16#include <limits>
17
18CCamera::CCamera()
19{
20 m_CamType = CAMTYPE_UNDEFINED;
21 m_ZoomSet = false;
22 m_Zoom = 1.0f;
23 m_Zooming = false;
24 m_ForceFreeview = false;
25 m_GotoSwitchOffset = 0;
26 m_GotoTeleOffset = 0;
27 m_GotoSwitchLastPos = ivec2(-1, -1);
28 m_GotoTeleLastPos = ivec2(-1, -1);
29
30 mem_zero(block: m_aLastPos, size: sizeof(m_aLastPos));
31 m_PrevCenter = vec2(0, 0);
32 m_Center = vec2(0, 0);
33}
34
35float CCamera::ZoomProgress(float CurrentTime) const
36{
37 return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart);
38}
39
40void CCamera::ScaleZoom(float Factor)
41{
42 float CurrentTarget = m_Zooming ? m_ZoomSmoothingTarget : m_Zoom;
43 ChangeZoom(Target: CurrentTarget * Factor, Smoothness: m_pClient->m_Snap.m_SpecInfo.m_Active && GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime);
44}
45
46float CCamera::MaxZoomLevel()
47{
48 return (g_Config.m_ClLimitMaxZoomLevel) ? ((Graphics()->IsTileBufferingEnabled() ? 240 : 30)) : std::numeric_limits<float>::max();
49}
50
51float CCamera::MinZoomLevel()
52{
53 return 0.01f;
54}
55
56void CCamera::ChangeZoom(float Target, int Smoothness)
57{
58 if(Target > MaxZoomLevel() || Target < MinZoomLevel())
59 {
60 return;
61 }
62
63 float Now = Client()->LocalTime();
64 float Current = m_Zoom;
65 float Derivative = 0.0f;
66 if(m_Zooming)
67 {
68 float Progress = ZoomProgress(CurrentTime: Now);
69 Current = m_ZoomSmoothing.Evaluate(t: Progress);
70 Derivative = m_ZoomSmoothing.Derivative(t: Progress);
71 }
72
73 m_ZoomSmoothingTarget = Target;
74 m_ZoomSmoothing = CCubicBezier::With(Start: Current, StartDerivative: Derivative, EndDerivative: 0, End: m_ZoomSmoothingTarget);
75 m_ZoomSmoothingStart = Now;
76 m_ZoomSmoothingEnd = Now + (float)Smoothness / 1000;
77
78 m_Zooming = true;
79}
80
81void CCamera::OnRender()
82{
83 if(m_Zooming)
84 {
85 float Time = Client()->LocalTime();
86 if(Time >= m_ZoomSmoothingEnd)
87 {
88 m_Zoom = m_ZoomSmoothingTarget;
89 m_Zooming = false;
90 }
91 else
92 {
93 const float OldLevel = m_Zoom;
94 m_Zoom = m_ZoomSmoothing.Evaluate(t: ZoomProgress(CurrentTime: Time));
95 if((OldLevel < m_ZoomSmoothingTarget && m_Zoom > m_ZoomSmoothingTarget) || (OldLevel > m_ZoomSmoothingTarget && m_Zoom < m_ZoomSmoothingTarget))
96 {
97 m_Zoom = m_ZoomSmoothingTarget;
98 m_Zooming = false;
99 }
100 }
101 m_Zoom = clamp(val: m_Zoom, lo: MinZoomLevel(), hi: MaxZoomLevel());
102 }
103
104 if(!(m_pClient->m_Snap.m_SpecInfo.m_Active || GameClient()->m_GameInfo.m_AllowZoom || Client()->State() == IClient::STATE_DEMOPLAYBACK))
105 {
106 m_ZoomSet = false;
107 m_Zoom = 1.0f;
108 m_Zooming = false;
109 }
110 else if(!m_ZoomSet && g_Config.m_ClDefaultZoom != 10)
111 {
112 m_ZoomSet = true;
113 OnReset();
114 }
115
116 // update camera center
117 if(m_pClient->m_Snap.m_SpecInfo.m_Active && !m_pClient->m_Snap.m_SpecInfo.m_UsePosition)
118 {
119 if(m_CamType != CAMTYPE_SPEC)
120 {
121 m_aLastPos[g_Config.m_ClDummy] = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy];
122 m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] = m_PrevCenter;
123 m_pClient->m_Controls.ClampMousePos();
124 m_CamType = CAMTYPE_SPEC;
125 }
126 m_Center = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy];
127 }
128 else
129 {
130 if(m_CamType != CAMTYPE_PLAYER)
131 {
132 m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] = m_aLastPos[g_Config.m_ClDummy];
133 m_pClient->m_Controls.ClampMousePos();
134 m_CamType = CAMTYPE_PLAYER;
135 }
136
137 float DeltaTime = Client()->RenderFrameTime();
138 static vec2 s_LastMousePos(0, 0);
139 static vec2 s_aCurrentCameraOffset[NUM_DUMMIES] = {vec2(0, 0), vec2(0, 0)};
140 static float s_SpeedBias = 0.5f;
141
142 if(g_Config.m_ClDyncamSmoothness > 0)
143 {
144 float CameraSpeed = (1.0f - (g_Config.m_ClDyncamSmoothness / 100.0f)) * 9.5f + 0.5f;
145 float CameraStabilizingFactor = 1 + g_Config.m_ClDyncamStabilizing / 100.0f;
146
147 s_SpeedBias += CameraSpeed * DeltaTime;
148 if(g_Config.m_ClDyncam)
149 {
150 s_SpeedBias -= length(a: m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] - s_LastMousePos) * std::log10(x: CameraStabilizingFactor) * 0.02f;
151 s_SpeedBias = clamp(val: s_SpeedBias, lo: 0.5f, hi: CameraSpeed);
152 }
153 else
154 {
155 s_SpeedBias = maximum(a: 5.0f, b: CameraSpeed); // make sure toggle back is fast
156 }
157 }
158
159 vec2 TargetCameraOffset(0, 0);
160 s_LastMousePos = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy];
161 float l = length(a: s_LastMousePos);
162 if(l > 0.0001f) // make sure that this isn't 0
163 {
164 float DeadZone = g_Config.m_ClDyncam ? g_Config.m_ClDyncamDeadzone : g_Config.m_ClMouseDeadzone;
165 float FollowFactor = (g_Config.m_ClDyncam ? g_Config.m_ClDyncamFollowFactor : g_Config.m_ClMouseFollowfactor) / 100.0f;
166 float OffsetAmount = maximum(a: l - DeadZone, b: 0.0f) * FollowFactor;
167
168 TargetCameraOffset = normalize(v: m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy]) * OffsetAmount;
169 }
170
171 if(g_Config.m_ClDyncamSmoothness > 0)
172 s_aCurrentCameraOffset[g_Config.m_ClDummy] += (TargetCameraOffset - s_aCurrentCameraOffset[g_Config.m_ClDummy]) * minimum(a: DeltaTime * s_SpeedBias, b: 1.0f);
173 else
174 s_aCurrentCameraOffset[g_Config.m_ClDummy] = TargetCameraOffset;
175
176 if(m_pClient->m_Snap.m_SpecInfo.m_Active)
177 m_Center = m_pClient->m_Snap.m_SpecInfo.m_Position + s_aCurrentCameraOffset[g_Config.m_ClDummy];
178 else
179 m_Center = m_pClient->m_LocalCharacterPos + s_aCurrentCameraOffset[g_Config.m_ClDummy];
180 }
181
182 if(m_ForceFreeview && m_CamType == CAMTYPE_SPEC)
183 {
184 m_Center = m_pClient->m_Controls.m_aMousePos[g_Config.m_ClDummy] = m_ForceFreeviewPos;
185 m_ForceFreeview = false;
186 }
187 else
188 m_ForceFreeviewPos = m_Center;
189
190 m_PrevCenter = m_Center;
191}
192
193void CCamera::OnConsoleInit()
194{
195 Console()->Register(pName: "zoom+", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConZoomPlus, pUser: this, pHelp: "Zoom increase");
196 Console()->Register(pName: "zoom-", pParams: "", Flags: CFGFLAG_CLIENT, pfnFunc: ConZoomMinus, pUser: this, pHelp: "Zoom decrease");
197 Console()->Register(pName: "zoom", pParams: "?i", Flags: CFGFLAG_CLIENT, pfnFunc: ConZoom, pUser: this, pHelp: "Change zoom");
198 Console()->Register(pName: "set_view", pParams: "i[x]i[y]", Flags: CFGFLAG_CLIENT, pfnFunc: ConSetView, pUser: this, pHelp: "Set camera position to x and y in the map");
199 Console()->Register(pName: "set_view_relative", pParams: "i[x]i[y]", Flags: CFGFLAG_CLIENT, pfnFunc: ConSetViewRelative, pUser: this, pHelp: "Set camera position relative to current view in the map");
200 Console()->Register(pName: "goto_switch", pParams: "i[number]?i[offset]", Flags: CFGFLAG_CLIENT, pfnFunc: ConGotoSwitch, pUser: this, pHelp: "View switch found (at offset) with given number");
201 Console()->Register(pName: "goto_tele", pParams: "i[number]?i[offset]", Flags: CFGFLAG_CLIENT, pfnFunc: ConGotoTele, pUser: this, pHelp: "View tele found (at offset) with given number");
202}
203
204void CCamera::OnReset()
205{
206 m_Zoom = std::pow(x: CCamera::ZOOM_STEP, y: g_Config.m_ClDefaultZoom - 10);
207 m_Zooming = false;
208}
209
210void CCamera::ConZoomPlus(IConsole::IResult *pResult, void *pUserData)
211{
212 CCamera *pSelf = (CCamera *)pUserData;
213 if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK)
214 {
215 pSelf->ScaleZoom(Factor: CCamera::ZOOM_STEP);
216
217 if(pSelf->GameClient()->m_MultiViewActivated)
218 pSelf->GameClient()->m_MultiViewPersonalZoom++;
219 }
220}
221void CCamera::ConZoomMinus(IConsole::IResult *pResult, void *pUserData)
222{
223 CCamera *pSelf = (CCamera *)pUserData;
224 if(pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active || pSelf->GameClient()->m_GameInfo.m_AllowZoom || pSelf->Client()->State() == IClient::STATE_DEMOPLAYBACK)
225 {
226 pSelf->ScaleZoom(Factor: 1 / CCamera::ZOOM_STEP);
227
228 if(pSelf->GameClient()->m_MultiViewActivated)
229 pSelf->GameClient()->m_MultiViewPersonalZoom--;
230 }
231}
232void CCamera::ConZoom(IConsole::IResult *pResult, void *pUserData)
233{
234 CCamera *pSelf = (CCamera *)pUserData;
235 float TargetLevel = pResult->NumArguments() ? pResult->GetFloat(Index: 0) : g_Config.m_ClDefaultZoom;
236 pSelf->ChangeZoom(Target: std::pow(x: CCamera::ZOOM_STEP, y: TargetLevel - 10), Smoothness: pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active && pSelf->GameClient()->m_MultiViewActivated ? g_Config.m_ClMultiViewZoomSmoothness : g_Config.m_ClSmoothZoomTime);
237
238 if(pSelf->GameClient()->m_MultiViewActivated && pSelf->m_pClient->m_Snap.m_SpecInfo.m_Active)
239 pSelf->GameClient()->m_MultiViewPersonalZoom = 0;
240}
241void CCamera::ConSetView(IConsole::IResult *pResult, void *pUserData)
242{
243 CCamera *pSelf = (CCamera *)pUserData;
244 // wait until free view camera type to update the position
245 pSelf->SetView(Pos: ivec2(pResult->GetInteger(Index: 0), pResult->GetInteger(Index: 1)));
246}
247void CCamera::ConSetViewRelative(IConsole::IResult *pResult, void *pUserData)
248{
249 CCamera *pSelf = (CCamera *)pUserData;
250 // wait until free view camera type to update the position
251 pSelf->SetView(Pos: ivec2(pResult->GetInteger(Index: 0), pResult->GetInteger(Index: 1)), Relative: true);
252}
253void CCamera::ConGotoSwitch(IConsole::IResult *pResult, void *pUserData)
254{
255 CCamera *pSelf = (CCamera *)pUserData;
256 pSelf->GotoSwitch(Number: pResult->GetInteger(Index: 0), Offset: pResult->NumArguments() > 1 ? pResult->GetInteger(Index: 1) : -1);
257}
258void CCamera::ConGotoTele(IConsole::IResult *pResult, void *pUserData)
259{
260 CCamera *pSelf = (CCamera *)pUserData;
261 pSelf->GotoTele(Number: pResult->GetInteger(Index: 0), Offset: pResult->NumArguments() > 1 ? pResult->GetInteger(Index: 1) : -1);
262}
263
264void CCamera::SetView(ivec2 Pos, bool Relative)
265{
266 vec2 RealPos = vec2(Pos.x * 32.0, Pos.y * 32.0);
267 vec2 UntestedViewPos = Relative ? m_ForceFreeviewPos + RealPos : RealPos;
268
269 m_ForceFreeview = true;
270
271 m_ForceFreeviewPos = vec2(
272 clamp(val: UntestedViewPos.x, lo: 200.0f, hi: Collision()->GetWidth() * 32 - 200.0f),
273 clamp(val: UntestedViewPos.y, lo: 200.0f, hi: Collision()->GetWidth() * 32 - 200.0f));
274}
275
276void CCamera::GotoSwitch(int Number, int Offset)
277{
278 if(Collision()->SwitchLayer() == nullptr)
279 return;
280
281 int Match = -1;
282 ivec2 MatchPos = ivec2(-1, -1);
283
284 auto FindTile = [this, &Match, &MatchPos, &Number, &Offset]() {
285 for(int x = 0; x < Collision()->GetWidth(); x++)
286 {
287 for(int y = 0; y < Collision()->GetHeight(); y++)
288 {
289 int i = y * Collision()->GetWidth() + x;
290 if(Number == Collision()->GetSwitchNumber(Index: i))
291 {
292 Match++;
293 if(Offset != -1)
294 {
295 if(Match == Offset)
296 {
297 MatchPos = ivec2(x, y);
298 m_GotoSwitchOffset = Match;
299 return;
300 }
301 continue;
302 }
303 MatchPos = ivec2(x, y);
304 if(Match == m_GotoSwitchOffset)
305 return;
306 }
307 }
308 }
309 };
310 FindTile();
311
312 if(MatchPos == ivec2(-1, -1))
313 return;
314 if(Match < m_GotoSwitchOffset)
315 m_GotoSwitchOffset = -1;
316 SetView(Pos: MatchPos);
317 m_GotoSwitchOffset++;
318}
319
320void CCamera::GotoTele(int Number, int Offset)
321{
322 if(Collision()->TeleLayer() == nullptr)
323 return;
324 Number--;
325
326 if(m_GotoTeleLastNumber != Number)
327 m_GotoTeleLastPos = ivec2(-1, -1);
328
329 ivec2 MatchPos = ivec2(-1, -1);
330 const size_t NumTeles = Collision()->TeleAllSize(Number);
331 if(!NumTeles)
332 {
333 log_error("camera", "No teleporter with number %d found.", Number + 1);
334 return;
335 }
336
337 if(Offset != -1 || m_GotoTeleLastPos == ivec2(-1, -1))
338 {
339 if((size_t)Offset >= NumTeles || Offset < 0)
340 Offset = 0;
341 vec2 Tele = Collision()->TeleAllGet(Number, Offset);
342 MatchPos = ivec2(Tele.x / 32, Tele.y / 32);
343 m_GotoTeleOffset = Offset;
344 }
345 else
346 {
347 bool FullRound = false;
348 do
349 {
350 vec2 Tele = Collision()->TeleAllGet(Number, Offset: m_GotoTeleOffset);
351 MatchPos = ivec2(Tele.x / 32, Tele.y / 32);
352 m_GotoTeleOffset++;
353 if((size_t)m_GotoTeleOffset >= NumTeles)
354 {
355 m_GotoTeleOffset = 0;
356 if(FullRound)
357 {
358 MatchPos = m_GotoTeleLastPos;
359 break;
360 }
361 else
362 {
363 FullRound = true;
364 }
365 }
366 } while(distance(a: m_GotoTeleLastPos, b: MatchPos) < 10.0f);
367 }
368
369 if(MatchPos == ivec2(-1, -1))
370 return;
371 m_GotoTeleLastPos = MatchPos;
372 m_GotoTeleLastNumber = Number;
373 SetView(Pos: MatchPos);
374}
375
376void CCamera::SetZoom(float Target, int Smoothness)
377{
378 ChangeZoom(Target, Smoothness);
379}
380