00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
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
00033
00034
00035 static const float PEEK_ARRIVE_RATIO = 0.1;
00036
00037 class CameraConfig
00038 {
00039 public:
00040
00041 int xmode;
00042
00043 int ymode;
00044 float kirby_rectsize_x;
00045 float kirby_rectsize_y;
00046
00047 float target_x;
00048 float target_y;
00049
00050 float max_speed_x;
00051 float max_speed_y;
00052
00053 float dynamic_max_speed_x;
00054
00055
00056
00057 float dirchange_time;
00058
00059 float edge_x;
00060
00061
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
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
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
00316 if(elapsed_time < CAMERA_EPSILON)
00317 return;
00318
00319
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
00330
00331
00332
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
00341 float delta_y = cached_translation.y - target_y;
00342
00343 float speed_y = delta_y / elapsed_time;
00344
00345
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
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
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
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
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
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
00441
00442
00443
00444
00445
00446
00447
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
00465
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
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
00481
00482
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
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
00513 float delta_x = cached_translation.x - target_x;
00514
00515 float speed_x = delta_x / elapsed_time;
00516
00517
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
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
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
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
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