Merged changes from branches/supertux-milestone2-grumbel/ to trunk/supertux/
[supertux.git] / src / object / camera.cpp
1 //  SuperTux
2 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
3 //
4 //  This program 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 3 of the License, or
7 //  (at your option) any later version.
8 //
9 //  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
16
17 #include "object/camera.hpp"
18
19 #include <cmath>
20 #include <physfs.h>
21
22 #include "util/reader.hpp"
23 #include "util/writer.hpp"
24 #include "lisp/parser.hpp"
25 #include "object/path_walker.hpp"
26 #include "object/player.hpp"
27 #include "scripting/camera.hpp"
28 #include "scripting/squirrel_util.hpp"
29 #include "supertux/main.hpp"
30 #include "supertux/sector.hpp"
31
32 /* this is the fractional distance toward the peek
33    position to move each frame; lower is slower,
34    0 is never get there, 1 is instant */
35 static const float PEEK_ARRIVE_RATIO = 0.1;
36
37 class CameraConfig
38 {
39 public:
40   // 0 = No, 1 = Fix, 2 = Mario/Yoshi, 3 = Kirby, 4 = Super Metroid-like
41   int ymode;
42   // as above
43   int xmode;
44   float kirby_rectsize_x;
45   float kirby_rectsize_y;
46   // where to fix the player (used for Yoshi and Fix camera)
47   float target_y;
48   float target_x;
49   // maximum scrolling speed in Y direction
50   float max_speed_y;
51   float max_speed_x;
52   // factor to dynamically increase max_speed_x based on player speed
53   float dynamic_max_speed_x;
54
55   // time the player has to face into the other direction before we assume a
56   // changed direction
57   float dirchange_time;
58   // edge_x
59   float edge_x;
60   // when too change from noscroll mode back to lookahead left/right mode
61   // set to <= 0 to disable noscroll mode
62   float sensitive_x;
63
64   float clamp_y;
65   float clamp_x;
66
67   float dynamic_speed_sm;
68
69   CameraConfig() {
70     xmode = 4;
71     ymode = 3;
72     target_x = .5f;
73     target_y = .5f;
74     max_speed_y = 100;
75     max_speed_x = 100;
76     clamp_x = 0.1666f;
77     clamp_y = 0.3f;
78     kirby_rectsize_x = 0.2f;
79     kirby_rectsize_y = 0.34f;
80     edge_x = 0.4f;
81     sensitive_x = -1;
82     dynamic_max_speed_x = 1.0;
83     dirchange_time = 0.2f;
84     dynamic_speed_sm = 0.8f;
85   }
86
87   void load(const std::string& filename)
88   {
89     lisp::Parser parser;
90     const lisp::Lisp* root = parser.parse(filename);
91     const lisp::Lisp* camconfig = root->get_lisp("camera-config");
92     if(camconfig == NULL)
93       throw std::runtime_error("file is not a camera config file.");
94
95     camconfig->get("xmode", xmode);
96     camconfig->get("ymode", ymode);
97     camconfig->get("target-x", target_x);
98     camconfig->get("target-y", target_y);
99     camconfig->get("max-speed-x", max_speed_x);
100     camconfig->get("max-speed-y", max_speed_y);
101     camconfig->get("dynamic-max-speed-x", dynamic_max_speed_x);
102     camconfig->get("dirchange-time", dirchange_time);
103     camconfig->get("clamp-x", clamp_x);
104     camconfig->get("clamp-y", clamp_y);
105     camconfig->get("kirby-rectsize-x", kirby_rectsize_x);
106     camconfig->get("kirby-rectsize-y", kirby_rectsize_y);
107     camconfig->get("edge-x", edge_x);
108     camconfig->get("sensitive-x", sensitive_x);
109     camconfig->get("dynamic-speed-sm", dynamic_speed_sm);
110   }
111 };
112
113 Camera::Camera(Sector* newsector, std::string name)
114   : mode(NORMAL), sector(newsector), lookahead_mode(LOOKAHEAD_NONE)
115 {
116   this->name = name;
117   config = new CameraConfig();
118   reload_config();
119 }
120
121 Camera::~Camera()
122 {
123   delete config;
124 }
125
126 void
127 Camera::expose(HSQUIRRELVM vm, SQInteger table_idx)
128 {
129   if(name.empty()) return;
130   Scripting::Camera* interface = new Scripting::Camera(this);
131   expose_object(vm, table_idx, interface, name, true);
132 }
133
134 void
135 Camera::unexpose(HSQUIRRELVM vm, SQInteger table_idx)
136 {
137   if(name.empty()) return;
138   Scripting::unexpose_object(vm, table_idx, name);
139 }
140
141 void
142 Camera::draw(DrawingContext& )
143 {
144 }
145
146 const Vector&
147 Camera::get_translation() const
148 {
149   return translation;
150 }
151
152 void
153 Camera::parse(const Reader& reader)
154 {
155   std::string modename;
156
157   reader.get("mode", modename);
158   if(modename == "normal") {
159     mode = NORMAL;
160   } else if(modename == "autoscroll") {
161     mode = AUTOSCROLL;
162
163     const lisp::Lisp* pathLisp = reader.get_lisp("path");
164     if(pathLisp == NULL)
165       throw std::runtime_error("No path specified in autoscroll camera.");
166
167     autoscroll_path.reset(new Path());
168     autoscroll_path->read(*pathLisp);
169     autoscroll_walker.reset(new PathWalker(autoscroll_path.get()));
170   } else if(modename == "manual") {
171     mode = MANUAL;
172   } else {
173     std::stringstream str;
174     str << "invalid camera mode '" << modename << "'found in worldfile.";
175     throw std::runtime_error(str.str());
176   }
177 }
178
179 void
180 Camera::reset(const Vector& tuxpos)
181 {
182   translation.x = tuxpos.x - SCREEN_WIDTH/2;
183   translation.y = tuxpos.y - SCREEN_HEIGHT/2;
184
185   shakespeed = 0;
186   shaketimer.stop();
187   keep_in_bounds(translation);
188
189   cached_translation = translation;
190 }
191
192 void
193 Camera::shake(float time, float x, float y)
194 {
195   shaketimer.start(time);
196   shakedepth_x = x;
197   shakedepth_y = y;
198   shakespeed = M_PI/2 / time;
199 }
200
201 void
202 Camera::scroll_to(const Vector& goal, float scrolltime)
203 {
204   scroll_from = translation;
205   scroll_goal = goal;
206   keep_in_bounds(scroll_goal);
207
208   scroll_to_pos = 0;
209   scrollspeed = 1.0 / scrolltime;
210   mode = SCROLLTO;
211 }
212
213 static const float EPSILON = .00001f;
214 static const float MAX_SPEED_Y = 140;
215
216 void
217 Camera::update(float elapsed_time)
218 {
219   switch(mode) {
220     case NORMAL:
221       update_scroll_normal(elapsed_time);
222       break;
223     case AUTOSCROLL:
224       update_scroll_autoscroll(elapsed_time);
225       break;
226     case SCROLLTO:
227       update_scroll_to(elapsed_time);
228       break;
229     default:
230       break;
231   }
232   shake();
233 }
234
235 void
236 Camera::reload_config()
237 {
238   if(PHYSFS_exists("camera.cfg")) {
239     try {
240       config->load("camera.cfg");
241       log_info << "Loaded camera.cfg." << std::endl;
242     } catch(std::exception &e) {
243       log_debug << "Couldn't load camera.cfg, using defaults ("
244                 << e.what() << ")" << std::endl;
245     }
246   }
247 }
248
249 float clamp(float val, float min, float max)
250 {
251   if(val < min)
252     return min;
253   if(val > max)
254     return max;
255
256   return val;
257 }
258
259 void
260 Camera::keep_in_bounds(Vector& translation)
261 {
262   float width = sector->get_width();
263   float height = sector->get_height();
264
265   // don't scroll before the start or after the level's end
266   translation.x = clamp(translation.x, 0, width - SCREEN_WIDTH);
267   translation.y = clamp(translation.y, 0, height - SCREEN_HEIGHT);
268
269   if (height < SCREEN_HEIGHT)
270     translation.y = height/2.0 - SCREEN_HEIGHT/2.0;
271   if (width < SCREEN_WIDTH)
272     translation.x = width/2.0 - SCREEN_WIDTH/2.0;
273 }
274
275 void
276 Camera::shake()
277 {
278   if(shaketimer.started()) {
279     translation.x -= sin(shaketimer.get_timegone() * shakespeed) * shakedepth_x;
280     translation.y -= sin(shaketimer.get_timegone() * shakespeed) * shakedepth_y;
281   }
282 }
283
284 void
285 Camera::update_scroll_normal(float elapsed_time)
286 {
287   const CameraConfig& config = *(this->config);
288   Player* player = sector->player;
289   const Vector& player_pos = Vector(player->get_bbox().get_middle().x,
290                                     player->get_bbox().get_bottom());
291   static Vector last_player_pos = player_pos;
292   Vector player_delta = player_pos - last_player_pos;
293   last_player_pos = player_pos;
294
295   // check that we don't have division by zero later
296   if(elapsed_time < EPSILON)
297     return;
298
299   /****** Vertical Scrolling part ******/
300   int ymode = config.ymode;
301
302   if(player->is_dying() || sector->get_height() == 19*32) {
303     ymode = 0;
304   }
305   if(ymode == 1) {
306     cached_translation.y = player_pos.y - SCREEN_HEIGHT * config.target_y;
307   }
308   if(ymode == 2) {
309     // target_y is the high we target our scrolling at. This is not always the
310     // high of the player, but if he is jumping upwards we should use the
311     // position where he last touched the ground. (this probably needs
312     // exceptions for trampolines and similar things in the future)
313     float target_y;
314     if(player->fall_mode == Player::JUMPING)
315       target_y = player->last_ground_y + player->get_bbox().get_height();
316     else
317       target_y = player->get_bbox().p2.y;
318     target_y -= SCREEN_HEIGHT * config.target_y;
319
320     // delta_y is the distance we'd have to travel to directly reach target_y
321     float delta_y = cached_translation.y - target_y;
322     // speed is the speed the camera would need to reach target_y in this frame
323     float speed_y = delta_y / elapsed_time;
324
325     // limit the camera speed when jumping upwards
326     if(player->fall_mode != Player::FALLING
327        && player->fall_mode != Player::TRAMPOLINE_JUMP) {
328       speed_y = clamp(speed_y, -config.max_speed_y, config.max_speed_y);
329     }
330
331     // scroll with calculated speed
332     cached_translation.y -= speed_y * elapsed_time;
333   }
334   if(ymode == 3) {
335     float halfsize = config.kirby_rectsize_y * 0.5f;
336     cached_translation.y = clamp(cached_translation.y,
337                                  player_pos.y - SCREEN_HEIGHT * (0.5f + halfsize),
338                                  player_pos.y - SCREEN_HEIGHT * (0.5f - halfsize));
339   }
340   if(ymode == 4) {
341     float upperend = SCREEN_HEIGHT * config.edge_x;
342     float lowerend = SCREEN_HEIGHT * (1 - config.edge_x);
343
344     if (player_delta.y < -EPSILON) {
345       // walking left
346       lookahead_pos.y -= player_delta.y * config.dynamic_speed_sm;
347
348       if(lookahead_pos.y > lowerend) {
349         lookahead_pos.y = lowerend;
350       }
351     } else if (player_delta.y > EPSILON) {
352       // walking right
353       lookahead_pos.y -= player_delta.y * config.dynamic_speed_sm;
354       if(lookahead_pos.y < upperend) {
355         lookahead_pos.y = upperend;
356       }
357     }
358
359     // adjust for level ends
360     if (player_pos.y < upperend) {
361       lookahead_pos.y = upperend;
362     }
363     if (player_pos.y > sector->get_width() - upperend) {
364       lookahead_pos.y = lowerend;
365     }
366
367     cached_translation.y = player_pos.y - lookahead_pos.y;
368   }
369
370   translation.y = cached_translation.y;
371
372   if(ymode != 0) {
373     float top_edge, bottom_edge;
374     if(config.clamp_y <= 0) {
375       top_edge = 0;
376       bottom_edge = SCREEN_HEIGHT;
377     } else {
378       top_edge = SCREEN_HEIGHT*config.clamp_y;
379       bottom_edge = SCREEN_HEIGHT*(1-config.clamp_y);
380     }
381
382     float peek_to = 0;
383     float translation_compensation = player_pos.y - translation.y;
384
385     if(player->peeking_direction_y() == ::UP) {
386       peek_to = bottom_edge - translation_compensation;
387     } else if(player->peeking_direction_y() == ::DOWN) {
388       peek_to = top_edge - translation_compensation;
389     }
390
391     float peek_move = (peek_to - peek_pos.y) * PEEK_ARRIVE_RATIO;
392     if(fabs(peek_move) < 1.0) {
393       peek_move = 0.0;
394     }
395
396     peek_pos.y += peek_move;
397
398     translation.y -= peek_pos.y;
399
400     if(config.clamp_y > 0) {
401       translation.y = clamp(translation.y,
402                             player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
403                             player_pos.y - SCREEN_HEIGHT * config.clamp_y);
404       cached_translation.y = clamp(cached_translation.y,
405                                    player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
406                                    player_pos.y - SCREEN_HEIGHT * config.clamp_y);
407     }
408   }
409
410   /****** Horizontal scrolling part *******/
411   int xmode = config.xmode;
412
413   if(player->is_dying())
414     xmode = 0;
415
416   if(xmode == 1) {
417     cached_translation.x = player_pos.x - SCREEN_WIDTH * config.target_x;
418   }
419   if(xmode == 2) {
420     // our camera is either in leftscrolling, rightscrolling or
421     // nonscrollingmode.
422     //
423     // when suddenly changing directions while scrolling into the other
424     // direction abort scrolling, since tux might be going left/right at a
425     // relatively small part of the map (like when jumping upwards)
426
427     // Find out direction in which the player moves
428     LookaheadMode walkDirection;
429     if (player_delta.x < -EPSILON) walkDirection = LOOKAHEAD_LEFT;
430     else if (player_delta.x > EPSILON) walkDirection = LOOKAHEAD_RIGHT;
431     else if (player->dir == ::LEFT) walkDirection = LOOKAHEAD_LEFT;
432     else walkDirection = LOOKAHEAD_RIGHT;
433
434     float LEFTEND, RIGHTEND;
435     if(config.sensitive_x > 0) {
436       LEFTEND = SCREEN_WIDTH * config.sensitive_x;
437       RIGHTEND = SCREEN_WIDTH * (1-config.sensitive_x);
438     } else {
439       LEFTEND = SCREEN_WIDTH;
440       RIGHTEND = 0;
441     }
442
443     if(lookahead_mode == LOOKAHEAD_NONE) {
444       /* if we're undecided then look if we crossed the left or right
445        * "sensitive" area */
446       if(player_pos.x < cached_translation.x + LEFTEND) {
447         lookahead_mode = LOOKAHEAD_LEFT;
448       } else if(player_pos.x > cached_translation.x + RIGHTEND) {
449         lookahead_mode = LOOKAHEAD_RIGHT;
450       }
451       /* at the ends of a level it's obvious which way we will go */
452       if(player_pos.x < SCREEN_WIDTH*0.5) {
453         lookahead_mode = LOOKAHEAD_RIGHT;
454       } else if(player_pos.x >= sector->get_width() - SCREEN_WIDTH*0.5) {
455         lookahead_mode = LOOKAHEAD_LEFT;
456       }
457
458       changetime = -1;
459     } else if(lookahead_mode != walkDirection) {
460       /* player changed direction while camera was scrolling...
461        * he has to do this for a certain time to add robustness against
462        * sudden changes */
463       if(changetime < 0) {
464         changetime = game_time;
465       } else if(game_time - changetime > config.dirchange_time) {
466         if(lookahead_mode == LOOKAHEAD_LEFT &&
467            player_pos.x > cached_translation.x + RIGHTEND) {
468           lookahead_mode = LOOKAHEAD_RIGHT;
469         } else if(lookahead_mode == LOOKAHEAD_RIGHT &&
470                   player_pos.x < cached_translation.x + LEFTEND) {
471           lookahead_mode = LOOKAHEAD_LEFT;
472         } else {
473           lookahead_mode = LOOKAHEAD_NONE;
474         }
475       }
476     } else {
477       changetime = -1;
478     }
479
480     LEFTEND = SCREEN_WIDTH * config.edge_x;
481     RIGHTEND = SCREEN_WIDTH * (1-config.edge_x);
482
483     // calculate our scroll target depending on scroll mode
484     float target_x;
485     if(lookahead_mode == LOOKAHEAD_LEFT)
486       target_x = player_pos.x - RIGHTEND;
487     else if(lookahead_mode == LOOKAHEAD_RIGHT)
488       target_x = player_pos.x - LEFTEND;
489     else
490       target_x = cached_translation.x;
491
492     // that's the distance we would have to travel to reach target_x
493     float delta_x = cached_translation.x - target_x;
494     // the speed we'd need to travel to reach target_x in this frame
495     float speed_x = delta_x / elapsed_time;
496
497     // limit our speed
498     float player_speed_x = player_delta.x / elapsed_time;
499     float maxv = config.max_speed_x + (fabsf(player_speed_x * config.dynamic_max_speed_x));
500     speed_x = clamp(speed_x, -maxv, maxv);
501
502     // apply scrolling
503     cached_translation.x -= speed_x * elapsed_time;
504   }
505   if(xmode == 3) {
506     float halfsize = config.kirby_rectsize_x * 0.5f;
507     cached_translation.x = clamp(cached_translation.x,
508                                  player_pos.x - SCREEN_WIDTH * (0.5f + halfsize),
509                                  player_pos.x - SCREEN_WIDTH * (0.5f - halfsize));
510   }
511   if(xmode == 4) {
512     float LEFTEND = SCREEN_WIDTH * config.edge_x;
513     float RIGHTEND = SCREEN_WIDTH * (1 - config.edge_x);
514
515     if (player_delta.x < -EPSILON) {
516       // walking left
517       lookahead_pos.x -= player_delta.x * config.dynamic_speed_sm;
518       if(lookahead_pos.x > RIGHTEND) {
519         lookahead_pos.x = RIGHTEND;
520       }
521
522     } else if (player_delta.x > EPSILON) {
523       // walking right
524       lookahead_pos.x -= player_delta.x * config.dynamic_speed_sm;
525       if(lookahead_pos.x < LEFTEND) {
526         lookahead_pos.x = LEFTEND;
527       }
528     }
529
530     // adjust for level ends
531     if (player_pos.x < LEFTEND) {
532       lookahead_pos.x = LEFTEND;
533     }
534     if (player_pos.x > sector->get_width() - LEFTEND) {
535       lookahead_pos.x = RIGHTEND;
536     }
537
538     cached_translation.x = player_pos.x - lookahead_pos.x;
539   }
540
541   translation.x = cached_translation.x;
542
543   if(xmode != 0) {
544     float left_edge, right_edge;
545     if(config.clamp_x <= 0) {
546       left_edge = 0;
547       right_edge = SCREEN_WIDTH;
548     } else {
549       left_edge = SCREEN_WIDTH*config.clamp_x;
550       right_edge = SCREEN_WIDTH*(1-config.clamp_x);
551     }
552
553     float peek_to = 0;
554     float translation_compensation = player_pos.x - translation.x;
555
556     if(player->peeking_direction_x() == ::LEFT) {
557       peek_to = right_edge - translation_compensation;
558     } else if(player->peeking_direction_x() == ::RIGHT) {
559       peek_to = left_edge - translation_compensation;
560     }
561
562     float peek_move = (peek_to - peek_pos.x) * PEEK_ARRIVE_RATIO;
563     if(fabs(peek_move) < 1.0) {
564       peek_move = 0.0;
565     }
566
567     peek_pos.x += peek_move;
568
569     translation.x -= peek_pos.x;
570
571     if(config.clamp_x > 0) {
572       translation.x = clamp(translation.x,
573                             player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
574                             player_pos.x - SCREEN_WIDTH * config.clamp_x);
575
576       cached_translation.x = clamp(cached_translation.x,
577                                    player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
578                                    player_pos.x - SCREEN_WIDTH * config.clamp_x);
579     }
580   }
581
582   keep_in_bounds(translation);
583   keep_in_bounds(cached_translation);
584 }
585
586 void
587 Camera::update_scroll_autoscroll(float elapsed_time)
588 {
589   Player* player = sector->player;
590   if(player->is_dying())
591     return;
592
593   translation = autoscroll_walker->advance(elapsed_time);
594
595   keep_in_bounds(translation);
596 }
597
598 void
599 Camera::update_scroll_to(float elapsed_time)
600 {
601   scroll_to_pos += elapsed_time * scrollspeed;
602   if(scroll_to_pos >= 1.0) {
603     mode = MANUAL;
604     translation = scroll_goal;
605     return;
606   }
607
608   translation = scroll_from + (scroll_goal - scroll_from) * scroll_to_pos;
609 }
610
611 Vector
612 Camera::get_center() const {
613   return translation + Vector(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
614 }
615
616 /* EOF */