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 | |
18 | CCamera::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 | |
35 | float CCamera::ZoomProgress(float CurrentTime) const |
36 | { |
37 | return (CurrentTime - m_ZoomSmoothingStart) / (m_ZoomSmoothingEnd - m_ZoomSmoothingStart); |
38 | } |
39 | |
40 | void 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 | |
46 | float CCamera::MaxZoomLevel() |
47 | { |
48 | return (g_Config.m_ClLimitMaxZoomLevel) ? ((Graphics()->IsTileBufferingEnabled() ? 240 : 30)) : std::numeric_limits<float>::max(); |
49 | } |
50 | |
51 | float CCamera::MinZoomLevel() |
52 | { |
53 | return 0.01f; |
54 | } |
55 | |
56 | void 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 | |
81 | void 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 | |
193 | void 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 | |
204 | void CCamera::OnReset() |
205 | { |
206 | m_Zoom = std::pow(x: CCamera::ZOOM_STEP, y: g_Config.m_ClDefaultZoom - 10); |
207 | m_Zooming = false; |
208 | } |
209 | |
210 | void 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 | } |
221 | void 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 | } |
232 | void 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 | } |
241 | void 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 | } |
247 | void 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 | } |
253 | void 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 | } |
258 | void 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 | |
264 | void 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 | |
276 | void 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 | |
320 | void 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 | |
376 | void CCamera::SetZoom(float Target, int Smoothness) |
377 | { |
378 | ChangeZoom(Target, Smoothness); |
379 | } |
380 | |