2 // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
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.
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.
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/>.
17 #include "object/camera.hpp"
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/globals.hpp"
30 #include "supertux/sector.hpp"
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;
40 // 0 = No, 1 = Fix, 2 = Mario/Yoshi, 3 = Kirby, 4 = Super Metroid-like
44 float kirby_rectsize_x;
45 float kirby_rectsize_y;
46 // where to fix the player (used for Yoshi and Fix camera)
49 // maximum scrolling speed in Y direction
52 // factor to dynamically increase max_speed_x based on player speed
53 float dynamic_max_speed_x;
55 // time the player has to face into the other direction before we assume a
60 // when too change from noscroll mode back to lookahead left/right mode
61 // set to <= 0 to disable noscroll mode
67 float dynamic_speed_sm;
72 kirby_rectsize_x(0.2f),
73 kirby_rectsize_y(0.34f),
78 dynamic_max_speed_x(1.0),
84 dynamic_speed_sm(0.8f)
88 void load(const std::string& filename)
91 const lisp::Lisp* root = parser.parse(filename);
92 const lisp::Lisp* camconfig = root->get_lisp("camera-config");
94 throw std::runtime_error("file is not a camera config file.");
96 camconfig->get("xmode", xmode);
97 camconfig->get("ymode", ymode);
98 camconfig->get("target-x", target_x);
99 camconfig->get("target-y", target_y);
100 camconfig->get("max-speed-x", max_speed_x);
101 camconfig->get("max-speed-y", max_speed_y);
102 camconfig->get("dynamic-max-speed-x", dynamic_max_speed_x);
103 camconfig->get("dirchange-time", dirchange_time);
104 camconfig->get("clamp-x", clamp_x);
105 camconfig->get("clamp-y", clamp_y);
106 camconfig->get("kirby-rectsize-x", kirby_rectsize_x);
107 camconfig->get("kirby-rectsize-y", kirby_rectsize_y);
108 camconfig->get("edge-x", edge_x);
109 camconfig->get("sensitive-x", sensitive_x);
110 camconfig->get("dynamic-speed-sm", dynamic_speed_sm);
114 Camera::Camera(Sector* newsector, std::string name) :
118 lookahead_mode(LOOKAHEAD_NONE),
122 cached_translation(),
136 config = new CameraConfig();
146 Camera::expose(HSQUIRRELVM vm, SQInteger table_idx)
148 if(name.empty()) return;
149 scripting::Camera* _this = new scripting::Camera(this);
150 expose_object(vm, table_idx, _this, name, true);
154 Camera::unexpose(HSQUIRRELVM vm, SQInteger table_idx)
156 if(name.empty()) return;
157 scripting::unexpose_object(vm, table_idx, name);
161 Camera::draw(DrawingContext& )
166 Camera::get_translation() const
172 Camera::parse(const Reader& reader)
174 std::string modename;
176 reader.get("mode", modename);
177 if(modename == "normal") {
179 } else if(modename == "autoscroll") {
182 const lisp::Lisp* pathLisp = reader.get_lisp("path");
184 throw std::runtime_error("No path specified in autoscroll camera.");
186 autoscroll_path.reset(new Path());
187 autoscroll_path->read(*pathLisp);
188 autoscroll_walker.reset(new PathWalker(autoscroll_path.get()));
189 } else if(modename == "manual") {
192 std::stringstream str;
193 str << "invalid camera mode '" << modename << "'found in worldfile.";
194 throw std::runtime_error(str.str());
199 Camera::reset(const Vector& tuxpos)
201 translation.x = tuxpos.x - SCREEN_WIDTH/2;
202 translation.y = tuxpos.y - SCREEN_HEIGHT/2;
206 keep_in_bounds(translation);
208 cached_translation = translation;
212 Camera::shake(float time, float x, float y)
214 shaketimer.start(time);
217 shakespeed = M_PI/2 / time;
221 Camera::scroll_to(const Vector& goal, float scrolltime)
223 scroll_from = translation;
225 keep_in_bounds(scroll_goal);
228 scrollspeed = 1.0 / scrolltime;
232 static const float CAMERA_EPSILON = .00001f;
233 static const float MAX_SPEED_Y = 140;
236 Camera::update(float elapsed_time)
240 update_scroll_normal(elapsed_time);
243 update_scroll_autoscroll(elapsed_time);
246 update_scroll_to(elapsed_time);
255 Camera::reload_config()
257 if(PHYSFS_exists("camera.cfg")) {
259 config->load("camera.cfg");
260 log_info << "Loaded camera.cfg." << std::endl;
261 } catch(std::exception &e) {
262 log_debug << "Couldn't load camera.cfg, using defaults ("
263 << e.what() << ")" << std::endl;
268 float clamp(float val, float min, float max)
279 Camera::keep_in_bounds(Vector& translation)
281 float width = sector->get_width();
282 float height = sector->get_height();
284 // don't scroll before the start or after the level's end
285 translation.x = clamp(translation.x, 0, width - SCREEN_WIDTH);
286 translation.y = clamp(translation.y, 0, height - SCREEN_HEIGHT);
288 if (height < SCREEN_HEIGHT)
289 translation.y = height/2.0 - SCREEN_HEIGHT/2.0;
290 if (width < SCREEN_WIDTH)
291 translation.x = width/2.0 - SCREEN_WIDTH/2.0;
297 if(shaketimer.started()) {
298 translation.x -= sin(shaketimer.get_timegone() * shakespeed) * shakedepth_x;
299 translation.y -= sin(shaketimer.get_timegone() * shakespeed) * shakedepth_y;
304 Camera::update_scroll_normal(float elapsed_time)
306 const CameraConfig& config = *(this->config);
307 Player* player = sector->player;
308 const Vector& player_pos = Vector(player->get_bbox().get_middle().x,
309 player->get_bbox().get_bottom());
310 static Vector last_player_pos = player_pos;
311 Vector player_delta = player_pos - last_player_pos;
312 last_player_pos = player_pos;
314 // check that we don't have division by zero later
315 if(elapsed_time < CAMERA_EPSILON)
318 /****** Vertical Scrolling part ******/
319 int ymode = config.ymode;
321 if(player->is_dying() || sector->get_height() == 19*32) {
325 cached_translation.y = player_pos.y - SCREEN_HEIGHT * config.target_y;
328 // target_y is the high we target our scrolling at. This is not always the
329 // high of the player, but if he is jumping upwards we should use the
330 // position where he last touched the ground. (this probably needs
331 // exceptions for trampolines and similar things in the future)
333 if(player->fall_mode == Player::JUMPING)
334 target_y = player->last_ground_y + player->get_bbox().get_height();
336 target_y = player->get_bbox().p2.y;
337 target_y -= SCREEN_HEIGHT * config.target_y;
339 // delta_y is the distance we'd have to travel to directly reach target_y
340 float delta_y = cached_translation.y - target_y;
341 // speed is the speed the camera would need to reach target_y in this frame
342 float speed_y = delta_y / elapsed_time;
344 // limit the camera speed when jumping upwards
345 if(player->fall_mode != Player::FALLING
346 && player->fall_mode != Player::TRAMPOLINE_JUMP) {
347 speed_y = clamp(speed_y, -config.max_speed_y, config.max_speed_y);
350 // scroll with calculated speed
351 cached_translation.y -= speed_y * elapsed_time;
354 float halfsize = config.kirby_rectsize_y * 0.5f;
355 cached_translation.y = clamp(cached_translation.y,
356 player_pos.y - SCREEN_HEIGHT * (0.5f + halfsize),
357 player_pos.y - SCREEN_HEIGHT * (0.5f - halfsize));
360 float upperend = SCREEN_HEIGHT * config.edge_x;
361 float lowerend = SCREEN_HEIGHT * (1 - config.edge_x);
363 if (player_delta.y < -CAMERA_EPSILON) {
365 lookahead_pos.y -= player_delta.y * config.dynamic_speed_sm;
367 if(lookahead_pos.y > lowerend) {
368 lookahead_pos.y = lowerend;
370 } else if (player_delta.y > CAMERA_EPSILON) {
372 lookahead_pos.y -= player_delta.y * config.dynamic_speed_sm;
373 if(lookahead_pos.y < upperend) {
374 lookahead_pos.y = upperend;
378 // adjust for level ends
379 if (player_pos.y < upperend) {
380 lookahead_pos.y = upperend;
382 if (player_pos.y > sector->get_width() - upperend) {
383 lookahead_pos.y = lowerend;
386 cached_translation.y = player_pos.y - lookahead_pos.y;
389 translation.y = cached_translation.y;
392 float top_edge, bottom_edge;
393 if(config.clamp_y <= 0) {
395 bottom_edge = SCREEN_HEIGHT;
397 top_edge = SCREEN_HEIGHT*config.clamp_y;
398 bottom_edge = SCREEN_HEIGHT*(1-config.clamp_y);
402 float translation_compensation = player_pos.y - translation.y;
404 if(player->peeking_direction_y() == ::UP) {
405 peek_to = bottom_edge - translation_compensation;
406 } else if(player->peeking_direction_y() == ::DOWN) {
407 peek_to = top_edge - translation_compensation;
410 float peek_move = (peek_to - peek_pos.y) * PEEK_ARRIVE_RATIO;
411 if(fabs(peek_move) < 1.0) {
415 peek_pos.y += peek_move;
417 translation.y -= peek_pos.y;
419 if(config.clamp_y > 0) {
420 translation.y = clamp(translation.y,
421 player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
422 player_pos.y - SCREEN_HEIGHT * config.clamp_y);
423 cached_translation.y = clamp(cached_translation.y,
424 player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
425 player_pos.y - SCREEN_HEIGHT * config.clamp_y);
429 /****** Horizontal scrolling part *******/
430 int xmode = config.xmode;
432 if(player->is_dying())
436 cached_translation.x = player_pos.x - SCREEN_WIDTH * config.target_x;
439 // our camera is either in leftscrolling, rightscrolling or
442 // when suddenly changing directions while scrolling into the other
443 // direction abort scrolling, since tux might be going left/right at a
444 // relatively small part of the map (like when jumping upwards)
446 // Find out direction in which the player moves
447 LookaheadMode walkDirection;
448 if (player_delta.x < -CAMERA_EPSILON) walkDirection = LOOKAHEAD_LEFT;
449 else if (player_delta.x > CAMERA_EPSILON) walkDirection = LOOKAHEAD_RIGHT;
450 else if (player->dir == ::LEFT) walkDirection = LOOKAHEAD_LEFT;
451 else walkDirection = LOOKAHEAD_RIGHT;
453 float LEFTEND, RIGHTEND;
454 if(config.sensitive_x > 0) {
455 LEFTEND = SCREEN_WIDTH * config.sensitive_x;
456 RIGHTEND = SCREEN_WIDTH * (1-config.sensitive_x);
458 LEFTEND = SCREEN_WIDTH;
462 if(lookahead_mode == LOOKAHEAD_NONE) {
463 /* if we're undecided then look if we crossed the left or right
464 * "sensitive" area */
465 if(player_pos.x < cached_translation.x + LEFTEND) {
466 lookahead_mode = LOOKAHEAD_LEFT;
467 } else if(player_pos.x > cached_translation.x + RIGHTEND) {
468 lookahead_mode = LOOKAHEAD_RIGHT;
470 /* at the ends of a level it's obvious which way we will go */
471 if(player_pos.x < SCREEN_WIDTH*0.5) {
472 lookahead_mode = LOOKAHEAD_RIGHT;
473 } else if(player_pos.x >= sector->get_width() - SCREEN_WIDTH*0.5) {
474 lookahead_mode = LOOKAHEAD_LEFT;
478 } else if(lookahead_mode != walkDirection) {
479 /* player changed direction while camera was scrolling...
480 * he has to do this for a certain time to add robustness against
483 changetime = game_time;
484 } else if(game_time - changetime > config.dirchange_time) {
485 if(lookahead_mode == LOOKAHEAD_LEFT &&
486 player_pos.x > cached_translation.x + RIGHTEND) {
487 lookahead_mode = LOOKAHEAD_RIGHT;
488 } else if(lookahead_mode == LOOKAHEAD_RIGHT &&
489 player_pos.x < cached_translation.x + LEFTEND) {
490 lookahead_mode = LOOKAHEAD_LEFT;
492 lookahead_mode = LOOKAHEAD_NONE;
499 LEFTEND = SCREEN_WIDTH * config.edge_x;
500 RIGHTEND = SCREEN_WIDTH * (1-config.edge_x);
502 // calculate our scroll target depending on scroll mode
504 if(lookahead_mode == LOOKAHEAD_LEFT)
505 target_x = player_pos.x - RIGHTEND;
506 else if(lookahead_mode == LOOKAHEAD_RIGHT)
507 target_x = player_pos.x - LEFTEND;
509 target_x = cached_translation.x;
511 // that's the distance we would have to travel to reach target_x
512 float delta_x = cached_translation.x - target_x;
513 // the speed we'd need to travel to reach target_x in this frame
514 float speed_x = delta_x / elapsed_time;
517 float player_speed_x = player_delta.x / elapsed_time;
518 float maxv = config.max_speed_x + (fabsf(player_speed_x * config.dynamic_max_speed_x));
519 speed_x = clamp(speed_x, -maxv, maxv);
522 cached_translation.x -= speed_x * elapsed_time;
525 float halfsize = config.kirby_rectsize_x * 0.5f;
526 cached_translation.x = clamp(cached_translation.x,
527 player_pos.x - SCREEN_WIDTH * (0.5f + halfsize),
528 player_pos.x - SCREEN_WIDTH * (0.5f - halfsize));
531 float LEFTEND = SCREEN_WIDTH * config.edge_x;
532 float RIGHTEND = SCREEN_WIDTH * (1 - config.edge_x);
534 if (player_delta.x < -CAMERA_EPSILON) {
536 lookahead_pos.x -= player_delta.x * config.dynamic_speed_sm;
537 if(lookahead_pos.x > RIGHTEND) {
538 lookahead_pos.x = RIGHTEND;
541 } else if (player_delta.x > CAMERA_EPSILON) {
543 lookahead_pos.x -= player_delta.x * config.dynamic_speed_sm;
544 if(lookahead_pos.x < LEFTEND) {
545 lookahead_pos.x = LEFTEND;
549 // adjust for level ends
550 if (player_pos.x < LEFTEND) {
551 lookahead_pos.x = LEFTEND;
553 if (player_pos.x > sector->get_width() - LEFTEND) {
554 lookahead_pos.x = RIGHTEND;
557 cached_translation.x = player_pos.x - lookahead_pos.x;
560 translation.x = cached_translation.x;
563 float left_edge, right_edge;
564 if(config.clamp_x <= 0) {
566 right_edge = SCREEN_WIDTH;
568 left_edge = SCREEN_WIDTH*config.clamp_x;
569 right_edge = SCREEN_WIDTH*(1-config.clamp_x);
573 float translation_compensation = player_pos.x - translation.x;
575 if(player->peeking_direction_x() == ::LEFT) {
576 peek_to = right_edge - translation_compensation;
577 } else if(player->peeking_direction_x() == ::RIGHT) {
578 peek_to = left_edge - translation_compensation;
581 float peek_move = (peek_to - peek_pos.x) * PEEK_ARRIVE_RATIO;
582 if(fabs(peek_move) < 1.0) {
586 peek_pos.x += peek_move;
588 translation.x -= peek_pos.x;
590 if(config.clamp_x > 0) {
591 translation.x = clamp(translation.x,
592 player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
593 player_pos.x - SCREEN_WIDTH * config.clamp_x);
595 cached_translation.x = clamp(cached_translation.x,
596 player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
597 player_pos.x - SCREEN_WIDTH * config.clamp_x);
601 keep_in_bounds(translation);
602 keep_in_bounds(cached_translation);
606 Camera::update_scroll_autoscroll(float elapsed_time)
608 Player* player = sector->player;
609 if(player->is_dying())
612 translation = autoscroll_walker->advance(elapsed_time);
614 keep_in_bounds(translation);
618 Camera::update_scroll_to(float elapsed_time)
620 scroll_to_pos += elapsed_time * scrollspeed;
621 if(scroll_to_pos >= 1.0) {
623 translation = scroll_goal;
627 translation = scroll_from + (scroll_goal - scroll_from) * scroll_to_pos;
631 Camera::get_center() const {
632 return translation + Vector(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);