src/object/camera.cpp

Go to the documentation of this file.
00001 //  SuperTux
00002 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
00003 //
00004 //  This program is free software: you can redistribute it and/or modify
00005 //  it under the terms of the GNU General Public License as published by
00006 //  the Free Software Foundation, either version 3 of the License, or
00007 //  (at your option) any later version.
00008 //
00009 //  This program is distributed in the hope that it will be useful,
00010 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
00011 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012 //  GNU General Public License for more details.
00013 //
00014 //  You should have received a copy of the GNU General Public License
00015 //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016 
00017 #include "object/camera.hpp"
00018 
00019 #include <math.h>
00020 #include <physfs.h>
00021 
00022 #include "util/reader.hpp"
00023 #include "util/writer.hpp"
00024 #include "lisp/parser.hpp"
00025 #include "object/path_walker.hpp"
00026 #include "object/player.hpp"
00027 #include "scripting/camera.hpp"
00028 #include "scripting/squirrel_util.hpp"
00029 #include "supertux/globals.hpp"
00030 #include "supertux/sector.hpp"
00031 
00032 /* this is the fractional distance toward the peek
00033    position to move each frame; lower is slower,
00034    0 is never get there, 1 is instant */
00035 static const float PEEK_ARRIVE_RATIO = 0.1;
00036 
00037 class CameraConfig
00038 {
00039 public:
00040   // 0 = No, 1 = Fix, 2 = Mario/Yoshi, 3 = Kirby, 4 = Super Metroid-like
00041   int xmode;
00042   // as above
00043   int ymode;
00044   float kirby_rectsize_x;
00045   float kirby_rectsize_y;
00046   // where to fix the player (used for Yoshi and Fix camera)
00047   float target_x;
00048   float target_y;
00049   // maximum scrolling speed in Y direction
00050   float max_speed_x;
00051   float max_speed_y;
00052   // factor to dynamically increase max_speed_x based on player speed
00053   float dynamic_max_speed_x;
00054 
00055   // time the player has to face into the other direction before we assume a
00056   // changed direction
00057   float dirchange_time;
00058   // edge_x
00059   float edge_x;
00060   // when too change from noscroll mode back to lookahead left/right mode
00061   // set to <= 0 to disable noscroll mode
00062   float sensitive_x;
00063 
00064   float clamp_x;
00065   float clamp_y;
00066 
00067   float dynamic_speed_sm;
00068 
00069   CameraConfig() :
00070     xmode(4),
00071     ymode(3),
00072     kirby_rectsize_x(0.2f),
00073     kirby_rectsize_y(0.34f),
00074     target_x(.5f),
00075     target_y(.5f),
00076     max_speed_x(100),
00077     max_speed_y(100),
00078     dynamic_max_speed_x(1.0),
00079     dirchange_time(0.2f),
00080     edge_x(0.4f),
00081     sensitive_x(-1),
00082     clamp_x(0.1666f),
00083     clamp_y(0.3f),
00084     dynamic_speed_sm(0.8f)
00085   {
00086   }
00087 
00088   void load(const std::string& filename)
00089   {
00090     lisp::Parser parser;
00091     const lisp::Lisp* root = parser.parse(filename);
00092     const lisp::Lisp* camconfig = root->get_lisp("camera-config");
00093     if(camconfig == NULL)
00094       throw std::runtime_error("file is not a camera config file.");
00095 
00096     camconfig->get("xmode", xmode);
00097     camconfig->get("ymode", ymode);
00098     camconfig->get("target-x", target_x);
00099     camconfig->get("target-y", target_y);
00100     camconfig->get("max-speed-x", max_speed_x);
00101     camconfig->get("max-speed-y", max_speed_y);
00102     camconfig->get("dynamic-max-speed-x", dynamic_max_speed_x);
00103     camconfig->get("dirchange-time", dirchange_time);
00104     camconfig->get("clamp-x", clamp_x);
00105     camconfig->get("clamp-y", clamp_y);
00106     camconfig->get("kirby-rectsize-x", kirby_rectsize_x);
00107     camconfig->get("kirby-rectsize-y", kirby_rectsize_y);
00108     camconfig->get("edge-x", edge_x);
00109     camconfig->get("sensitive-x", sensitive_x);
00110     camconfig->get("dynamic-speed-sm", dynamic_speed_sm);
00111   }
00112 };
00113 
00114 Camera::Camera(Sector* newsector, std::string name) :
00115   mode(NORMAL),
00116   translation(),
00117   sector(newsector), 
00118   lookahead_mode(LOOKAHEAD_NONE),
00119   changetime(),
00120   lookahead_pos(),
00121   peek_pos(),
00122   cached_translation(),
00123   autoscroll_path(),
00124   autoscroll_walker(),
00125   shaketimer(),
00126   shakespeed(),
00127   shakedepth_x(),
00128   shakedepth_y(),
00129   scroll_from(),
00130   scroll_goal(),
00131   scroll_to_pos(),
00132   scrollspeed(),
00133   config()
00134 {
00135   this->name = name;
00136   config = new CameraConfig();
00137   reload_config();
00138 }
00139 
00140 Camera::~Camera()
00141 {
00142   delete config;
00143 }
00144 
00145 void
00146 Camera::expose(HSQUIRRELVM vm, SQInteger table_idx)
00147 {
00148   if(name.empty()) return;
00149   scripting::Camera* _this = new scripting::Camera(this);
00150   expose_object(vm, table_idx, _this, name, true);
00151 }
00152 
00153 void
00154 Camera::unexpose(HSQUIRRELVM vm, SQInteger table_idx)
00155 {
00156   if(name.empty()) return;
00157   scripting::unexpose_object(vm, table_idx, name);
00158 }
00159 
00160 void
00161 Camera::draw(DrawingContext& )
00162 {
00163 }
00164 
00165 const Vector&
00166 Camera::get_translation() const
00167 {
00168   return translation;
00169 }
00170 
00171 void
00172 Camera::parse(const Reader& reader)
00173 {
00174   std::string modename;
00175 
00176   reader.get("mode", modename);
00177   if(modename == "normal") {
00178     mode = NORMAL;
00179   } else if(modename == "autoscroll") {
00180     mode = AUTOSCROLL;
00181 
00182     const lisp::Lisp* pathLisp = reader.get_lisp("path");
00183     if(pathLisp == NULL)
00184       throw std::runtime_error("No path specified in autoscroll camera.");
00185 
00186     autoscroll_path.reset(new Path());
00187     autoscroll_path->read(*pathLisp);
00188     autoscroll_walker.reset(new PathWalker(autoscroll_path.get()));
00189   } else if(modename == "manual") {
00190     mode = MANUAL;
00191   } else {
00192     std::stringstream str;
00193     str << "invalid camera mode '" << modename << "'found in worldfile.";
00194     throw std::runtime_error(str.str());
00195   }
00196 }
00197 
00198 void
00199 Camera::reset(const Vector& tuxpos)
00200 {
00201   translation.x = tuxpos.x - SCREEN_WIDTH/2;
00202   translation.y = tuxpos.y - SCREEN_HEIGHT/2;
00203 
00204   shakespeed = 0;
00205   shaketimer.stop();
00206   keep_in_bounds(translation);
00207 
00208   cached_translation = translation;
00209 }
00210 
00211 void
00212 Camera::shake(float time, float x, float y)
00213 {
00214   shaketimer.start(time);
00215   shakedepth_x = x;
00216   shakedepth_y = y;
00217   shakespeed = M_PI/2 / time;
00218 }
00219 
00220 void
00221 Camera::scroll_to(const Vector& goal, float scrolltime)
00222 {
00223   scroll_from = translation;
00224   scroll_goal = goal;
00225   keep_in_bounds(scroll_goal);
00226 
00227   scroll_to_pos = 0;
00228   scrollspeed = 1.0 / scrolltime;
00229   mode = SCROLLTO;
00230 }
00231 
00232 static const float CAMERA_EPSILON = .00001f;
00233 static const float MAX_SPEED_Y = 140;
00234 
00235 void
00236 Camera::update(float elapsed_time)
00237 {
00238   switch(mode) {
00239     case NORMAL:
00240       update_scroll_normal(elapsed_time);
00241       break;
00242     case AUTOSCROLL:
00243       update_scroll_autoscroll(elapsed_time);
00244       break;
00245     case SCROLLTO:
00246       update_scroll_to(elapsed_time);
00247       break;
00248     default:
00249       break;
00250   }
00251   shake();
00252 }
00253 
00254 void
00255 Camera::reload_config()
00256 {
00257   if(PHYSFS_exists("camera.cfg")) {
00258     try {
00259       config->load("camera.cfg");
00260       log_info << "Loaded camera.cfg." << std::endl;
00261     } catch(std::exception &e) {
00262       log_debug << "Couldn't load camera.cfg, using defaults ("
00263                 << e.what() << ")" << std::endl;
00264     }
00265   }
00266 }
00267 
00268 float clamp(float val, float min, float max)
00269 {
00270   if(val < min)
00271     return min;
00272   if(val > max)
00273     return max;
00274 
00275   return val;
00276 }
00277 
00278 void
00279 Camera::keep_in_bounds(Vector& translation)
00280 {
00281   float width = sector->get_width();
00282   float height = sector->get_height();
00283 
00284   // don't scroll before the start or after the level's end
00285   translation.x = clamp(translation.x, 0, width - SCREEN_WIDTH);
00286   translation.y = clamp(translation.y, 0, height - SCREEN_HEIGHT);
00287 
00288   if (height < SCREEN_HEIGHT)
00289     translation.y = height/2.0 - SCREEN_HEIGHT/2.0;
00290   if (width < SCREEN_WIDTH)
00291     translation.x = width/2.0 - SCREEN_WIDTH/2.0;
00292 }
00293 
00294 void
00295 Camera::shake()
00296 {
00297   if(shaketimer.started()) {
00298     translation.x -= sin(shaketimer.get_timegone() * shakespeed) * shakedepth_x;
00299     translation.y -= sin(shaketimer.get_timegone() * shakespeed) * shakedepth_y;
00300   }
00301 }
00302 
00303 void
00304 Camera::update_scroll_normal(float elapsed_time)
00305 {
00306   const CameraConfig& config = *(this->config);
00307   Player* player = sector->player;
00308   // TODO: co-op mode needs a good camera
00309   Vector player_pos(player->get_bbox().get_middle().x,
00310                                     player->get_bbox().get_bottom());
00311   static Vector last_player_pos = player_pos;
00312   Vector player_delta = player_pos - last_player_pos;
00313   last_player_pos = player_pos;
00314 
00315   // check that we don't have division by zero later
00316   if(elapsed_time < CAMERA_EPSILON)
00317     return;
00318 
00319   /****** Vertical Scrolling part ******/
00320   int ymode = config.ymode;
00321 
00322   if(player->is_dying() || sector->get_height() == 19*32) {
00323     ymode = 0;
00324   }
00325   if(ymode == 1) {
00326     cached_translation.y = player_pos.y - SCREEN_HEIGHT * config.target_y;
00327   }
00328   if(ymode == 2) {
00329     // target_y is the high we target our scrolling at. This is not always the
00330     // high of the player, but if he is jumping upwards we should use the
00331     // position where he last touched the ground. (this probably needs
00332     // exceptions for trampolines and similar things in the future)
00333     float target_y;
00334     if(player->fall_mode == Player::JUMPING)
00335       target_y = player->last_ground_y + player->get_bbox().get_height();
00336     else
00337       target_y = player->get_bbox().p2.y;
00338     target_y -= SCREEN_HEIGHT * config.target_y;
00339 
00340     // delta_y is the distance we'd have to travel to directly reach target_y
00341     float delta_y = cached_translation.y - target_y;
00342     // speed is the speed the camera would need to reach target_y in this frame
00343     float speed_y = delta_y / elapsed_time;
00344 
00345     // limit the camera speed when jumping upwards
00346     if(player->fall_mode != Player::FALLING
00347        && player->fall_mode != Player::TRAMPOLINE_JUMP) {
00348       speed_y = clamp(speed_y, -config.max_speed_y, config.max_speed_y);
00349     }
00350 
00351     // scroll with calculated speed
00352     cached_translation.y -= speed_y * elapsed_time;
00353   }
00354   if(ymode == 3) {
00355     float halfsize = config.kirby_rectsize_y * 0.5f;
00356     cached_translation.y = clamp(cached_translation.y,
00357                                  player_pos.y - SCREEN_HEIGHT * (0.5f + halfsize),
00358                                  player_pos.y - SCREEN_HEIGHT * (0.5f - halfsize));
00359   }
00360   if(ymode == 4) {
00361     float upperend = SCREEN_HEIGHT * config.edge_x;
00362     float lowerend = SCREEN_HEIGHT * (1 - config.edge_x);
00363 
00364     if (player_delta.y < -CAMERA_EPSILON) {
00365       // walking left
00366       lookahead_pos.y -= player_delta.y * config.dynamic_speed_sm;
00367 
00368       if(lookahead_pos.y > lowerend) {
00369         lookahead_pos.y = lowerend;
00370       }
00371     } else if (player_delta.y > CAMERA_EPSILON) {
00372       // walking right
00373       lookahead_pos.y -= player_delta.y * config.dynamic_speed_sm;
00374       if(lookahead_pos.y < upperend) {
00375         lookahead_pos.y = upperend;
00376       }
00377     }
00378 
00379     // adjust for level ends
00380     if (player_pos.y < upperend) {
00381       lookahead_pos.y = upperend;
00382     }
00383     if (player_pos.y > sector->get_width() - upperend) {
00384       lookahead_pos.y = lowerend;
00385     }
00386 
00387     cached_translation.y = player_pos.y - lookahead_pos.y;
00388   }
00389 
00390   translation.y = cached_translation.y;
00391 
00392   if(ymode != 0) {
00393     float top_edge, bottom_edge;
00394     if(config.clamp_y <= 0) {
00395       top_edge = 0;
00396       bottom_edge = SCREEN_HEIGHT;
00397     } else {
00398       top_edge = SCREEN_HEIGHT*config.clamp_y;
00399       bottom_edge = SCREEN_HEIGHT*(1-config.clamp_y);
00400     }
00401 
00402     float peek_to = 0;
00403     float translation_compensation = player_pos.y - translation.y;
00404 
00405     if(player->peeking_direction_y() == ::UP) {
00406       peek_to = bottom_edge - translation_compensation;
00407     } else if(player->peeking_direction_y() == ::DOWN) {
00408       peek_to = top_edge - translation_compensation;
00409     }
00410 
00411     float peek_move = (peek_to - peek_pos.y) * PEEK_ARRIVE_RATIO;
00412     if(fabs(peek_move) < 1.0) {
00413       peek_move = 0.0;
00414     }
00415 
00416     peek_pos.y += peek_move;
00417 
00418     translation.y -= peek_pos.y;
00419 
00420     if(config.clamp_y > 0) {
00421       translation.y = clamp(translation.y,
00422                             player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
00423                             player_pos.y - SCREEN_HEIGHT * config.clamp_y);
00424       cached_translation.y = clamp(cached_translation.y,
00425                                    player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
00426                                    player_pos.y - SCREEN_HEIGHT * config.clamp_y);
00427     }
00428   }
00429 
00430   /****** Horizontal scrolling part *******/
00431   int xmode = config.xmode;
00432 
00433   if(player->is_dying())
00434     xmode = 0;
00435 
00436   if(xmode == 1) {
00437     cached_translation.x = player_pos.x - SCREEN_WIDTH * config.target_x;
00438   }
00439   if(xmode == 2) {
00440     // our camera is either in leftscrolling, rightscrolling or
00441     // nonscrollingmode.
00442     //
00443     // when suddenly changing directions while scrolling into the other
00444     // direction abort scrolling, since tux might be going left/right at a
00445     // relatively small part of the map (like when jumping upwards)
00446 
00447     // Find out direction in which the player moves
00448     LookaheadMode walkDirection;
00449     if (player_delta.x < -CAMERA_EPSILON) walkDirection = LOOKAHEAD_LEFT;
00450     else if (player_delta.x > CAMERA_EPSILON) walkDirection = LOOKAHEAD_RIGHT;
00451     else if (player->dir == ::LEFT) walkDirection = LOOKAHEAD_LEFT;
00452     else walkDirection = LOOKAHEAD_RIGHT;
00453 
00454     float LEFTEND, RIGHTEND;
00455     if(config.sensitive_x > 0) {
00456       LEFTEND = SCREEN_WIDTH * config.sensitive_x;
00457       RIGHTEND = SCREEN_WIDTH * (1-config.sensitive_x);
00458     } else {
00459       LEFTEND = SCREEN_WIDTH;
00460       RIGHTEND = 0;
00461     }
00462 
00463     if(lookahead_mode == LOOKAHEAD_NONE) {
00464       /* if we're undecided then look if we crossed the left or right
00465        * "sensitive" area */
00466       if(player_pos.x < cached_translation.x + LEFTEND) {
00467         lookahead_mode = LOOKAHEAD_LEFT;
00468       } else if(player_pos.x > cached_translation.x + RIGHTEND) {
00469         lookahead_mode = LOOKAHEAD_RIGHT;
00470       }
00471       /* at the ends of a level it's obvious which way we will go */
00472       if(player_pos.x < SCREEN_WIDTH*0.5) {
00473         lookahead_mode = LOOKAHEAD_RIGHT;
00474       } else if(player_pos.x >= sector->get_width() - SCREEN_WIDTH*0.5) {
00475         lookahead_mode = LOOKAHEAD_LEFT;
00476       }
00477 
00478       changetime = -1;
00479     } else if(lookahead_mode != walkDirection) {
00480       /* player changed direction while camera was scrolling...
00481        * he has to do this for a certain time to add robustness against
00482        * sudden changes */
00483       if(changetime < 0) {
00484         changetime = game_time;
00485       } else if(game_time - changetime > config.dirchange_time) {
00486         if(lookahead_mode == LOOKAHEAD_LEFT &&
00487            player_pos.x > cached_translation.x + RIGHTEND) {
00488           lookahead_mode = LOOKAHEAD_RIGHT;
00489         } else if(lookahead_mode == LOOKAHEAD_RIGHT &&
00490                   player_pos.x < cached_translation.x + LEFTEND) {
00491           lookahead_mode = LOOKAHEAD_LEFT;
00492         } else {
00493           lookahead_mode = LOOKAHEAD_NONE;
00494         }
00495       }
00496     } else {
00497       changetime = -1;
00498     }
00499 
00500     LEFTEND = SCREEN_WIDTH * config.edge_x;
00501     RIGHTEND = SCREEN_WIDTH * (1-config.edge_x);
00502 
00503     // calculate our scroll target depending on scroll mode
00504     float target_x;
00505     if(lookahead_mode == LOOKAHEAD_LEFT)
00506       target_x = player_pos.x - RIGHTEND;
00507     else if(lookahead_mode == LOOKAHEAD_RIGHT)
00508       target_x = player_pos.x - LEFTEND;
00509     else
00510       target_x = cached_translation.x;
00511 
00512     // that's the distance we would have to travel to reach target_x
00513     float delta_x = cached_translation.x - target_x;
00514     // the speed we'd need to travel to reach target_x in this frame
00515     float speed_x = delta_x / elapsed_time;
00516 
00517     // limit our speed
00518     float player_speed_x = player_delta.x / elapsed_time;
00519     float maxv = config.max_speed_x + (fabsf(player_speed_x * config.dynamic_max_speed_x));
00520     speed_x = clamp(speed_x, -maxv, maxv);
00521 
00522     // apply scrolling
00523     cached_translation.x -= speed_x * elapsed_time;
00524   }
00525   if(xmode == 3) {
00526     float halfsize = config.kirby_rectsize_x * 0.5f;
00527     cached_translation.x = clamp(cached_translation.x,
00528                                  player_pos.x - SCREEN_WIDTH * (0.5f + halfsize),
00529                                  player_pos.x - SCREEN_WIDTH * (0.5f - halfsize));
00530   }
00531   if(xmode == 4) {
00532     float LEFTEND = SCREEN_WIDTH * config.edge_x;
00533     float RIGHTEND = SCREEN_WIDTH * (1 - config.edge_x);
00534 
00535     if (player_delta.x < -CAMERA_EPSILON) {
00536       // walking left
00537       lookahead_pos.x -= player_delta.x * config.dynamic_speed_sm;
00538       if(lookahead_pos.x > RIGHTEND) {
00539         lookahead_pos.x = RIGHTEND;
00540       }
00541 
00542     } else if (player_delta.x > CAMERA_EPSILON) {
00543       // walking right
00544       lookahead_pos.x -= player_delta.x * config.dynamic_speed_sm;
00545       if(lookahead_pos.x < LEFTEND) {
00546         lookahead_pos.x = LEFTEND;
00547       }
00548     }
00549 
00550     // adjust for level ends
00551     if (player_pos.x < LEFTEND) {
00552       lookahead_pos.x = LEFTEND;
00553     }
00554     if (player_pos.x > sector->get_width() - LEFTEND) {
00555       lookahead_pos.x = RIGHTEND;
00556     }
00557 
00558     cached_translation.x = player_pos.x - lookahead_pos.x;
00559   }
00560 
00561   translation.x = cached_translation.x;
00562 
00563   if(xmode != 0) {
00564     float left_edge, right_edge;
00565     if(config.clamp_x <= 0) {
00566       left_edge = 0;
00567       right_edge = SCREEN_WIDTH;
00568     } else {
00569       left_edge = SCREEN_WIDTH*config.clamp_x;
00570       right_edge = SCREEN_WIDTH*(1-config.clamp_x);
00571     }
00572 
00573     float peek_to = 0;
00574     float translation_compensation = player_pos.x - translation.x;
00575 
00576     if(player->peeking_direction_x() == ::LEFT) {
00577       peek_to = right_edge - translation_compensation;
00578     } else if(player->peeking_direction_x() == ::RIGHT) {
00579       peek_to = left_edge - translation_compensation;
00580     }
00581 
00582     float peek_move = (peek_to - peek_pos.x) * PEEK_ARRIVE_RATIO;
00583     if(fabs(peek_move) < 1.0) {
00584       peek_move = 0.0;
00585     }
00586 
00587     peek_pos.x += peek_move;
00588 
00589     translation.x -= peek_pos.x;
00590 
00591     if(config.clamp_x > 0) {
00592       translation.x = clamp(translation.x,
00593                             player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
00594                             player_pos.x - SCREEN_WIDTH * config.clamp_x);
00595 
00596       cached_translation.x = clamp(cached_translation.x,
00597                                    player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
00598                                    player_pos.x - SCREEN_WIDTH * config.clamp_x);
00599     }
00600   }
00601 
00602   keep_in_bounds(translation);
00603   keep_in_bounds(cached_translation);
00604 }
00605 
00606 void
00607 Camera::update_scroll_autoscroll(float elapsed_time)
00608 {
00609   Player* player = sector->player;
00610   if(player->is_dying())
00611     return;
00612 
00613   translation = autoscroll_walker->advance(elapsed_time);
00614 
00615   keep_in_bounds(translation);
00616 }
00617 
00618 void
00619 Camera::update_scroll_to(float elapsed_time)
00620 {
00621   scroll_to_pos += elapsed_time * scrollspeed;
00622   if(scroll_to_pos >= 1.0) {
00623     mode = MANUAL;
00624     translation = scroll_goal;
00625     return;
00626   }
00627 
00628   translation = scroll_from + (scroll_goal - scroll_from) * scroll_to_pos;
00629 }
00630 
00631 Vector
00632 Camera::get_center() const {
00633   return translation + Vector(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
00634 }
00635 
00636 /* EOF */

Generated on Mon Jun 9 03:38:19 2014 for SuperTux by  doxygen 1.5.1