1 /******************************************************************************
2  *  Warmux is a convivial mass murder game.
3  *  Copyright (C) 2001-2011 Warmux Team.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18  ******************************************************************************
19  * Camera : follow an object, center on it or follow mouse interaction.
20  *****************************************************************************/
21 
22 #include <WARMUX_debug.h>
23 #include <WARMUX_random.h>
24 
25 #include "character/character.h"
26 #include "game/config.h"
27 #include "game/game.h"
28 #include "game/game_time.h"
29 #include "graphic/video.h"
30 #include "include/app.h"
31 #include "interface/cursor.h"
32 #include "interface/interface.h"
33 #include "interface/mouse.h"
34 #include "map/camera.h"
35 #include "map/map.h"
36 #include "object/physical_obj.h"
37 #include "team/teams_list.h"
38 #include "tool/math_tools.h"
39 #include "tool/string_tools.h"
40 
41 static const Point2f MAX_CAMERA_SPEED(5000, 5000);
42 static const Point2f MAX_CAMERA_ACCELERATION(1.5,1.5);
43 
44 #define REACTIVITY             0.6f
45 #define SPEED_REACTIVITY       0.05f
46 #define REALTIME_FOLLOW_FACTOR 0.15f
47 
48 #define ANTICIPATION               18
49 #define ADVANCE_ANTICIPATION       20
50 #define SPEED_REACTIVITY_CEIL       4
51 #define SCROLL_KEYBOARD            20 // pixel
52 #define REALTIME_FOLLOW_LIMIT      25
53 #define MAX_REFRESHES_PER_SECOND  100
54 
Camera()55 Camera::Camera()
56   : m_started_shaking(0)
57   , m_shake_duration(0)
58   , m_shake_amplitude(0, 0)
59   , m_shake_centerpoint(0, 0)
60   , m_shake(0, 0)
61   , m_last_time_shake_calculated(0)
62   , m_speed(0, 0)
63   , m_stop(false)
64   , m_control_mode(NO_CAMERA_CONTROL)
65   , m_begin_controlled_move_time(0)
66   , m_mouse_counter(0)
67   , m_scroll_start_pos(0, 0)
68   , m_last_mouse_pos(0, 0)
69   , m_scroll_vector(0.0f, 0.0f)
70   , auto_crop(true)
71   , followed_object(NULL)
72 {
73   pointer_used_before_scroll = Mouse::POINTER_SELECT;
74 }
75 
Reset()76 void Camera::Reset()
77 {
78   m_stop = false;
79   auto_crop = true;
80   followed_object = NULL;
81   m_begin_controlled_move_time = 0;
82   m_control_mode = NO_CAMERA_CONTROL;
83   SetXYabs(GetWorld().GetSize() / 2);
84 }
85 
HasFixedX() const86 bool Camera::HasFixedX() const
87 {
88   return GetWorld().GetWidth() <= GetSizeX();
89 }
90 
HasFixedY() const91 bool Camera::HasFixedY() const
92 {
93   return GetWorld().GetHeight() <= GetSizeY();
94 }
95 
SetXYabs(int x,int y)96 void Camera::SetXYabs(int x, int y)
97 {
98   Surface &window = GetMainWindow();
99 
100   if (!HasFixedX())
101     position.x = InRange_Long(x, 0, GetWorld().GetWidth() - GetSizeX());
102   else
103     position.x = (GetWorld().GetWidth() - window.GetWidth())/2;
104 
105   if (!HasFixedY())
106     position.y = InRange_Long(y, 0, GetWorld().GetHeight() - GetSizeY());
107   else
108     position.y = (GetWorld().GetHeight() - window.GetHeight())/2;
109 }
110 
SetXY(Point2i pos)111 void Camera::SetXY(Point2i pos)
112 {
113   pos = pos*FreeDegrees();
114   if (pos.IsNull())
115     return;
116 
117   SetXYabs(position + pos);
118 }
119 
AutoCrop()120 void Camera::AutoCrop()
121 {
122   /* Stuff is put static in order to be able to reach the last position
123    * of the object the camera was following, in case it disappears. This
124    * typically happen when something explodes or a character dies. */
125   static Point2i obj_pos(0, 0);
126 
127   Point2i target(0,0);
128   bool stop = false;
129 
130   //Stop kinetic scrolling from interfering camera movement
131   m_scroll_vector = Point2f();
132 
133   if (followed_object && !followed_object->IsGhost()) {
134 
135     /* compute the ideal position!
136      * it takes the physical object direction into account
137      */
138     obj_pos = followed_object->GetCenter();
139 
140     if (obj_pos > GetPosition() + (GetSize()>>3) &&
141         obj_pos < GetPosition() + ((7 * GetSize())>>3)) {
142       if (m_stop)
143         stop = true;
144 
145     } else {
146       m_stop = false;
147     }
148 
149     target = obj_pos;
150 
151     if (followed_object->IsMoving()) {
152       float fps = Game::GetInstance()->GetLastFrameRate();
153       if (fps < 0.1f)
154         fps = 0.1f;
155       Double time_delta = 1000 / (Double)fps;
156       Point2d anticipation = followed_object->GetSpeed() * time_delta;
157 
158       //limit anticipation to screen size/3
159       Point2d anticipation_limit = GetSize()/3;
160       target += anticipation.clamp(-anticipation_limit, anticipation_limit);
161     }
162 
163     target -= GetSize()>>1;
164 
165   } else {
166     target = GetPosition();
167     m_stop = true;
168   }
169 
170   //Compute new speed to reach target
171   Point2f acceleration = (target - m_speed*ANTICIPATION - position)*REACTIVITY;
172   // Limit acceleration
173   acceleration = acceleration.clamp(-MAX_CAMERA_ACCELERATION, MAX_CAMERA_ACCELERATION);
174 
175   // std::cout<<"acceleration before : "<<acceleration.x<<" "<<acceleration.y<<std::endl;
176   if ((int)abs(m_speed.x) > SPEED_REACTIVITY_CEIL) {
177     acceleration.x *= (1 + SPEED_REACTIVITY * ((int)abs(m_speed.x) - SPEED_REACTIVITY_CEIL));
178   }
179 
180   if ((int)abs(m_speed.y) > SPEED_REACTIVITY_CEIL) {
181     acceleration.y *= (1 + SPEED_REACTIVITY * ((int)abs(m_speed.y) - SPEED_REACTIVITY_CEIL));
182   }
183 
184   //printf("speed=(%.2f,%.2f)  target=(%i,%i)\n", m_speed.x, m_speed.y, target.x, target.y);
185   if (stop) {
186     m_speed = m_speed/2;
187 
188   } else {
189 
190     //Apply acceleration
191     m_speed += acceleration;
192 
193     // Realtime follow is enabled if object is too fast to be correctly followed
194     // stop can only be true if followed_obj!=NULL
195     // speed is set to 0 if not moving
196     if (abs((int)followed_object->GetSpeed().x) > REALTIME_FOLLOW_LIMIT) {
197       m_speed.x = (target.x - position.x) * REALTIME_FOLLOW_FACTOR;
198     }
199 
200     if (abs((int)followed_object->GetSpeed().y) > REALTIME_FOLLOW_LIMIT) {
201       m_speed.y = (target.y - position.y) * REALTIME_FOLLOW_FACTOR;
202     }
203 
204     //Limit
205     m_speed = m_speed.clamp(-MAX_CAMERA_SPEED, MAX_CAMERA_SPEED);
206   }
207 
208   //Update position
209   Point2i next_position((int)m_speed.x, (int)m_speed.y);
210   //printf("pos=(%i,%i) speed=(%.2f,%.2f)\n", next_position.x, next_position.y, m_speed.x, m_speed.y);
211   SetXY(next_position);
212 
213   if (!m_stop && next_position.IsNull() && followed_object->GetSpeed().IsNull()) {
214     m_stop = true;
215   }
216 }
217 
SaveMouseCursor()218 void Camera::SaveMouseCursor()
219 {
220   Mouse::pointer_t current_pointer = Mouse::GetInstance()->GetPointer();
221   if (current_pointer != Mouse::POINTER_MOVE &&
222       current_pointer != Mouse::POINTER_ARROW_UP &&
223       current_pointer != Mouse::POINTER_ARROW_DOWN &&
224       current_pointer != Mouse::POINTER_ARROW_LEFT &&
225       current_pointer != Mouse::POINTER_ARROW_RIGHT &&
226       current_pointer != Mouse::POINTER_ARROW_DOWN_RIGHT &&
227       current_pointer != Mouse::POINTER_ARROW_UP_RIGHT &&
228       current_pointer != Mouse::POINTER_ARROW_UP_LEFT &&
229       current_pointer != Mouse::POINTER_ARROW_DOWN_LEFT) {
230     pointer_used_before_scroll = current_pointer;
231   }
232 }
233 
RestoreMouseCursor()234 void Camera::RestoreMouseCursor()
235 {
236   Mouse::GetInstance()->SetPointer(pointer_used_before_scroll);
237 }
238 
ScrollCamera()239 void Camera::ScrollCamera()
240 {
241   if (!Mouse::GetInstance()->HasFocus()) // The application has not the focus, don't move the camera!!
242     return;
243 
244   Point2i mousePos = Mouse::GetInstance()->GetPosition();
245   bool over_interface = Interface::GetInstance()->Intersect(mousePos);
246 
247   if (!Config::GetInstance()->GetScrollOnBorder()) {
248     /* Kinetic scrolling */
249     if (!SDL_GetMouseState(NULL, NULL) || over_interface) {
250       m_scroll_start_pos = Point2i();
251       m_last_mouse_pos   = Point2i();
252       m_mouse_counter    = 0;
253 
254       if (!m_scroll_vector.IsNull()) {
255         Point2f brk = 0.2f * m_scroll_vector.GetfloatNormal();
256 
257         MSG_DEBUG("camera",
258                   "scroll_vector=(%.3f,%.3f)  scroll_vector_break=(%.3f,%.3f)\n",
259                   m_scroll_vector.GetX(), m_scroll_vector.GetY(),
260                   brk.GetX(), brk.GetY());
261 
262         m_scroll_vector -= brk;
263         SetXY(-m_scroll_vector);
264         if (m_scroll_vector.Norm() < 1)
265           m_scroll_vector = Point2f();
266       }
267       return;
268     }
269 
270     if (over_interface)
271       return;
272 
273     m_mouse_counter++;
274 
275     if (m_scroll_start_pos.IsNull())
276       m_scroll_start_pos = mousePos;
277     if (m_last_mouse_pos.IsNull())
278       m_last_mouse_pos = mousePos;
279 
280     m_scroll_vector = mousePos - m_scroll_start_pos;
281     m_scroll_vector = m_scroll_vector / m_mouse_counter;
282     MSG_DEBUG("camera",
283               "scroll_vector=(%.3f,%.3f) mousePos=(%i,%i) lastMousePos=(%i,%i) scrollStartPos=(%i,%i)",
284               m_scroll_vector.GetX(), m_scroll_vector.GetY(),
285               mousePos.GetX(), mousePos.GetY(),
286               m_last_mouse_pos.GetX(), m_last_mouse_pos.GetY(),
287               m_scroll_start_pos.GetX(), m_scroll_start_pos.GetY());
288 
289     if (mousePos != m_last_mouse_pos) {
290       SetXY(-(mousePos-m_last_mouse_pos));
291       m_last_mouse_pos = mousePos;
292       SetAutoCrop(false);
293     }
294   } else {
295 
296     uint zone_size = Config::GetInstance()->GetScrollBorderSize();
297     Point2i sensitZone(zone_size, zone_size);
298 
299     /* tstVector represents the vector of how deep the cursor is in a sensit
300      * zone; negative value means that the camera has to reduce its coordinates,
301      * a positive value means that it should increase. Actually reduce means
302      * LEFT/UP (for x/y) and increase RIGHT/DOWN directions.
303      * The bigger tstVector is, the faster the camera will scroll. */
304     Point2i tstVector;
305     tstVector = GetSize().inf(mousePos + sensitZone) * (mousePos + sensitZone - GetSize()) ;
306     tstVector -= mousePos.inf(sensitZone) * (sensitZone - mousePos);
307 
308     if (!tstVector.IsNull()) {
309       SetXY(tstVector);
310       SetAutoCrop(false);
311     }
312 
313     /* mouse pointer ***********************************************************/
314     SaveMouseCursor();
315 
316     if (tstVector.IsNull())
317       RestoreMouseCursor();
318     else if (tstVector.IsXNull() && tstVector.y < 0)
319       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_UP);
320     else if (tstVector.IsXNull() && tstVector.y > 0)
321       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_DOWN);
322     else if (tstVector.IsYNull() && tstVector.x < 0)
323       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_LEFT);
324     else if (tstVector.IsYNull() && tstVector.x > 0)
325       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_RIGHT);
326     else if (tstVector.y > 0 && tstVector.x > 0)
327       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_DOWN_RIGHT);
328     else if (tstVector.y < 0 && tstVector.x > 0)
329       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_UP_RIGHT);
330     else if (tstVector.y < 0 && tstVector.x < 0)
331       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_UP_LEFT);
332     else if (tstVector.y > 0 && tstVector.x < 0)
333       Mouse::GetInstance()->SetPointer(Mouse::POINTER_ARROW_DOWN_LEFT);
334     /***************************************************************************/
335   }
336 }
337 
HandleMouseMovement()338 void Camera::HandleMouseMovement()
339 {
340   static Point2i first_mouse_pos(-1, -1);
341   static Point2i last_mouse_pos(0, 0);
342   Point2i curr_pos = Mouse::GetInstance()->GetPosition();
343 
344   int x,y;
345   //Move camera with mouse holding Ctrl key down or with middle button of mouse
346   if (SDL_GetMouseState(&x, &y) & SDL_BUTTON(SDL_BUTTON_MIDDLE)
347       || SDL_GetModState() & KMOD_CTRL) {
348 
349     // Begin to move the camera...
350     if (Mouse::GetInstance()->GetPointer() != Mouse::POINTER_MOVE) {
351       first_mouse_pos = Point2i(x, y);
352       SaveMouseCursor();
353       Mouse::GetInstance()->SetPointer(Mouse::POINTER_MOVE);
354     }
355 
356     SetAutoCrop(false);
357     SetXY(last_mouse_pos - curr_pos);
358     last_mouse_pos = curr_pos;
359 
360     if (m_begin_controlled_move_time == 0) {
361       m_begin_controlled_move_time = GameTime::GetInstance()->Read();
362     }
363 
364     if (SDL_GetModState() & KMOD_CTRL) {
365       m_control_mode = KEYBOARD_CAMERA_CONTROL;
366     } else {
367       m_control_mode = MOUSE_CAMERA_CONTROL;
368     }
369     return;
370 
371   } else if (m_control_mode == MOUSE_CAMERA_CONTROL) {
372 
373     // if the mouse has not moved at all since the user pressed the middle button, we center the camera!
374     if (abs((int)first_mouse_pos.x - curr_pos.x) < 5 &&
375         abs((int)first_mouse_pos.y - curr_pos.y) < 5 &&
376         GameTime::GetInstance()->Read() - m_begin_controlled_move_time < 500) {
377       CenterOnActiveCharacter();
378     }
379 
380     first_mouse_pos = Point2i(-1, -1);
381     RestoreMouseCursor();
382     m_control_mode = NO_CAMERA_CONTROL;
383     m_begin_controlled_move_time = 0;
384 
385   } else if (m_control_mode == KEYBOARD_CAMERA_CONTROL) {
386     first_mouse_pos = Point2i(-1, -1);
387     RestoreMouseCursor();
388     m_control_mode = NO_CAMERA_CONTROL;
389     m_begin_controlled_move_time = 0;
390   }
391 
392   ScrollCamera();
393   last_mouse_pos = curr_pos;
394 }
395 
HandleMoveIntentions()396 void Camera::HandleMoveIntentions()
397 {
398   const UDMoveIntention * ud_move_intention = GetLastUDMoveIntention();
399   if (ud_move_intention) {
400     if (ud_move_intention->GetDirection() == DIRECTION_UP)
401       SetXY(Point2i(0, -SCROLL_KEYBOARD));
402     else
403       SetXY(Point2i(0, SCROLL_KEYBOARD));
404   }
405   const LRMoveIntention * lr_move_intention = GetLastLRMoveIntention();
406   if (lr_move_intention) {
407     if (lr_move_intention->GetDirection() == DIRECTION_RIGHT)
408       SetXY(Point2i(SCROLL_KEYBOARD, 0));
409     else
410       SetXY(Point2i(-SCROLL_KEYBOARD, 0));
411   }
412   if (lr_move_intention || ud_move_intention)
413     SetAutoCrop(false);
414 }
415 
Refresh(bool ignore_user)416 void Camera::Refresh(bool ignore_user)
417 {
418   // Refresh gets called very often when the game is paused.
419   // This "if" ensures that the camera doesn't move too fast.
420   if (refresh_stopwatch.GetValue() >= 1000 / MAX_REFRESHES_PER_SECOND) {
421     // Check if player wants the camera to move
422     if (!ignore_user) {
423       HandleMouseMovement();
424       HandleMoveIntentions();
425     }
426 
427     if (auto_crop && followed_object)
428       AutoCrop();
429     refresh_stopwatch.Reset();
430   }
431 }
432 
FollowObject(const PhysicalObj * obj,bool follow_closely)433 void Camera::FollowObject(const PhysicalObj *obj, bool follow_closely)
434 {
435   MSG_DEBUG("camera.tracking", "Following object %s (%d)", obj->GetName().c_str(), follow_closely);
436 
437   Mouse::GetInstance()->Hide();
438 
439   auto_crop = true;
440 
441   m_stop = !follow_closely;
442   followed_object = obj;
443 }
444 
StopFollowingObj(const PhysicalObj * obj)445 void Camera::StopFollowingObj(const PhysicalObj* obj)
446 {
447   if (followed_object == obj) {
448     followed_object = NULL;
449     m_stop = true;
450     m_speed = Point2f(0,0);
451   }
452 }
453 
IsVisible(const PhysicalObj & obj) const454 bool Camera::IsVisible(const PhysicalObj &obj) const
455 {
456   return Intersect(obj.GetRect());
457 }
458 
CenterOnActiveCharacter()459 void Camera::CenterOnActiveCharacter()
460 {
461   CharacterCursor::GetInstance()->FollowActiveCharacter();
462   FollowObject(&ActiveCharacter(),true);
463 }
464 
ComputeShake() const465 Point2i Camera::ComputeShake() const
466 {
467   uint time = GameTime::GetInstance()->Read();
468   ASSERT(time >= m_started_shaking);
469 
470   if (time > m_started_shaking + m_shake_duration || m_shake_duration == 0) {
471     return Point2i(0, 0); // not shaking now
472   }
473 
474   if (time == m_last_time_shake_calculated)
475     return m_shake;
476 
477   // FIXME: we can underflow to 0 if time and m_started_shaking are large enough
478   float t = (float)(time - m_started_shaking) / (float)m_shake_duration;
479 
480   float func_val = 1.0f;
481   if (t >= 0.001f) {
482     float k_scale_angle = float(10 * M_PI);
483     float arg = k_scale_angle * t;
484     // denormalized sinc
485     func_val = (1 - t) * sin(arg) / arg;
486   }
487 
488   float x_ampl = RandomLocal().Getfloat(-m_shake_amplitude.x, m_shake_amplitude.x);
489   float y_ampl = RandomLocal().Getfloat(-m_shake_amplitude.y, m_shake_amplitude.y);
490 
491   m_shake.x = x_ampl * func_val + m_shake_centerpoint.x;
492   m_shake.y = y_ampl * func_val + m_shake_centerpoint.y;
493 
494   static uint t_last_time_logged = 0;
495   if (time - t_last_time_logged > 10) {
496     MSG_DEBUG("camera.shake", "Shaking: time = %d, t = %.3f, func_val = %.3f, shake: %d, %d",
497               time, t, func_val, m_shake.x, m_shake.y);
498     t_last_time_logged = time;
499   }
500 
501   m_last_time_shake_calculated = time;
502   return m_shake;
503 }
504 
Shake(uint how_long_msec,const Point2i & amplitude,const Point2i & centerpoint)505 void Camera::Shake(uint how_long_msec, const Point2i & amplitude, const Point2i & centerpoint)
506 {
507   MSG_DEBUG("camera.shake", "Shake added!");
508 
509   uint time = GameTime::GetInstance()->Read();
510 
511   ASSERT(time >= m_started_shaking);
512 
513   if (m_started_shaking + m_shake_duration > time) {
514     // still shaking, so add amplitude/centerpoint to allow shakes to combine
515     m_shake_amplitude = max(m_shake_amplitude, amplitude);
516     m_shake_centerpoint = centerpoint;
517 
518     // increase shake duration so it lasts how_long_msec from this time
519     m_shake_duration = how_long_msec + (time - m_started_shaking);
520   } else {
521     // reinit the shake
522     m_started_shaking = time;
523     m_shake_duration = how_long_msec;
524     m_shake_amplitude = amplitude;
525     m_shake_centerpoint = centerpoint;
526   }
527 }
528 
ResetShake()529 void Camera::ResetShake()
530 {
531   m_started_shaking = 0;
532   m_shake_duration = 0;
533   m_last_time_shake_calculated = 0;
534   m_shake = Point2i(0, 0);
535 }
536