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
72 kirby_rectsize_x(0.2f),
73 kirby_rectsize_y(0.34f),
78 dynamic_max_speed_x(1.0),
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("dead-zone-x", dead_zone_x);
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 // Magic Konstant (include config multiplier?)
361 float K = SCREEN_HEIGHT / (16.0f * 2 + SCREEN_HEIGHT);
363 lookahead_pos.y = player_pos.y + K*(lookahead_pos.y - player_pos.y);
365 cached_translation.y = 2 * player_pos.y - lookahead_pos.y - (SCREEN_WIDTH / 2);
368 translation.y = cached_translation.y;
371 float top_edge, bottom_edge;
372 if(config.clamp_y <= 0) {
374 bottom_edge = SCREEN_HEIGHT;
376 top_edge = SCREEN_HEIGHT*config.clamp_y;
377 bottom_edge = SCREEN_HEIGHT*(1-config.clamp_y);
381 float translation_compensation = player_pos.y - translation.y;
383 if(player->peeking_direction_y() == ::UP) {
384 peek_to = bottom_edge - translation_compensation;
385 } else if(player->peeking_direction_y() == ::DOWN) {
386 peek_to = top_edge - translation_compensation;
389 float peek_move = (peek_to - peek_pos.y) * PEEK_ARRIVE_RATIO;
390 if(fabs(peek_move) < 1.0) {
394 peek_pos.y += peek_move;
396 translation.y -= peek_pos.y;
398 if(config.clamp_y > 0) {
399 translation.y = clamp(translation.y,
400 player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
401 player_pos.y - SCREEN_HEIGHT * config.clamp_y);
402 cached_translation.y = clamp(cached_translation.y,
403 player_pos.y - SCREEN_HEIGHT * (1-config.clamp_y),
404 player_pos.y - SCREEN_HEIGHT * config.clamp_y);
408 /****** Horizontal scrolling part *******/
409 int xmode = config.xmode;
411 if(player->is_dying())
415 cached_translation.x = player_pos.x - SCREEN_WIDTH * config.target_x;
418 // our camera is either in leftscrolling, rightscrolling or
421 // when suddenly changing directions while scrolling into the other
422 // direction abort scrolling, since tux might be going left/right at a
423 // relatively small part of the map (like when jumping upwards)
425 // Find out direction in which the player moves
426 LookaheadMode walkDirection;
427 if (player_delta.x < -CAMERA_EPSILON) walkDirection = LOOKAHEAD_LEFT;
428 else if (player_delta.x > CAMERA_EPSILON) walkDirection = LOOKAHEAD_RIGHT;
429 else if (player->dir == ::LEFT) walkDirection = LOOKAHEAD_LEFT;
430 else walkDirection = LOOKAHEAD_RIGHT;
432 float LEFTEND, RIGHTEND;
433 if(config.sensitive_x > 0) {
434 LEFTEND = SCREEN_WIDTH * config.sensitive_x;
435 RIGHTEND = SCREEN_WIDTH * (1-config.sensitive_x);
437 LEFTEND = SCREEN_WIDTH;
441 if(lookahead_mode == LOOKAHEAD_NONE) {
442 /* if we're undecided then look if we crossed the left or right
443 * "sensitive" area */
444 if(player_pos.x < cached_translation.x + LEFTEND) {
445 lookahead_mode = LOOKAHEAD_LEFT;
446 } else if(player_pos.x > cached_translation.x + RIGHTEND) {
447 lookahead_mode = LOOKAHEAD_RIGHT;
449 /* at the ends of a level it's obvious which way we will go */
450 if(player_pos.x < SCREEN_WIDTH*0.5) {
451 lookahead_mode = LOOKAHEAD_RIGHT;
452 } else if(player_pos.x >= sector->get_width() - SCREEN_WIDTH*0.5) {
453 lookahead_mode = LOOKAHEAD_LEFT;
457 } else if(lookahead_mode != walkDirection) {
458 /* player changed direction while camera was scrolling...
459 * he has to do this for a certain time to add robustness against
462 changetime = game_time;
463 } else if(game_time - changetime > config.dirchange_time) {
464 if(lookahead_mode == LOOKAHEAD_LEFT &&
465 player_pos.x > cached_translation.x + RIGHTEND) {
466 lookahead_mode = LOOKAHEAD_RIGHT;
467 } else if(lookahead_mode == LOOKAHEAD_RIGHT &&
468 player_pos.x < cached_translation.x + LEFTEND) {
469 lookahead_mode = LOOKAHEAD_LEFT;
471 lookahead_mode = LOOKAHEAD_NONE;
478 LEFTEND = SCREEN_WIDTH * config.edge_x;
479 RIGHTEND = SCREEN_WIDTH * (1-config.edge_x);
481 // calculate our scroll target depending on scroll mode
483 if(lookahead_mode == LOOKAHEAD_LEFT)
484 target_x = player_pos.x - RIGHTEND;
485 else if(lookahead_mode == LOOKAHEAD_RIGHT)
486 target_x = player_pos.x - LEFTEND;
488 target_x = cached_translation.x;
490 // that's the distance we would have to travel to reach target_x
491 float delta_x = cached_translation.x - target_x;
492 // the speed we'd need to travel to reach target_x in this frame
493 float speed_x = delta_x / elapsed_time;
496 float player_speed_x = player_delta.x / elapsed_time;
497 float maxv = config.max_speed_x + (fabsf(player_speed_x * config.dynamic_max_speed_x));
498 speed_x = clamp(speed_x, -maxv, maxv);
501 cached_translation.x -= speed_x * elapsed_time;
504 float halfsize = config.kirby_rectsize_x * 0.5f;
505 cached_translation.x = clamp(cached_translation.x,
506 player_pos.x - SCREEN_WIDTH * (0.5f + halfsize),
507 player_pos.x - SCREEN_WIDTH * (0.5f - halfsize));
511 float K = SCREEN_WIDTH / (320.0f /* MAX_RUN_XM */ /64.0f * 2 + SCREEN_WIDTH);
513 // Only update in dead zone
514 if( fabsf(player_pos.x - lookahead_pos.x) > config.dead_zone_x )
515 lookahead_pos.x = player_pos.x + K*(lookahead_pos.x - player_pos.x);
517 cached_translation.x = 2 * player_pos.x - lookahead_pos.x - (SCREEN_WIDTH / 2);
520 translation.x = cached_translation.x;
523 float left_edge, right_edge;
524 if(config.clamp_x <= 0) {
526 right_edge = SCREEN_WIDTH;
528 left_edge = SCREEN_WIDTH*config.clamp_x;
529 right_edge = SCREEN_WIDTH*(1-config.clamp_x);
533 float translation_compensation = player_pos.x - translation.x;
535 if(player->peeking_direction_x() == ::LEFT) {
536 peek_to = right_edge - translation_compensation;
537 } else if(player->peeking_direction_x() == ::RIGHT) {
538 peek_to = left_edge - translation_compensation;
541 float peek_move = (peek_to - peek_pos.x) * PEEK_ARRIVE_RATIO;
542 if(fabs(peek_move) < 1.0) {
546 peek_pos.x += peek_move;
548 translation.x -= peek_pos.x;
550 if(config.clamp_x > 0) {
551 translation.x = clamp(translation.x,
552 player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
553 player_pos.x - SCREEN_WIDTH * config.clamp_x);
555 cached_translation.x = clamp(cached_translation.x,
556 player_pos.x - SCREEN_WIDTH * (1-config.clamp_x),
557 player_pos.x - SCREEN_WIDTH * config.clamp_x);
561 keep_in_bounds(translation);
562 keep_in_bounds(cached_translation);
566 Camera::update_scroll_autoscroll(float elapsed_time)
568 Player* player = sector->player;
569 if(player->is_dying())
572 translation = autoscroll_walker->advance(elapsed_time);
574 keep_in_bounds(translation);
578 Camera::update_scroll_to(float elapsed_time)
580 scroll_to_pos += elapsed_time * scrollspeed;
581 if(scroll_to_pos >= 1.0) {
583 translation = scroll_goal;
587 translation = scroll_from + (scroll_goal - scroll_from) * scroll_to_pos;
591 Camera::get_center() const {
592 return translation + Vector(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);