Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

570 lines (461 sloc) 18.839 kB
#include "graphics.hpp"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include "asserts.hpp"
#include "controls.hpp"
#include "debug_console.hpp"
#include "draw_number.hpp"
#include "draw_scene.hpp"
#include "editor.hpp"
#include "font.hpp"
#include "foreach.hpp"
#include "globals.h"
#include "graphical_font.hpp"
#include "gui_section.hpp"
#include "i18n.hpp"
#include "level.hpp"
#include "message_dialog.hpp"
#include "player_info.hpp"
#include "preferences.hpp"
#include "raster.hpp"
#include "speech_dialog.hpp"
#include "texture.hpp"
#include "texture_frame_buffer.hpp"
namespace {
std::vector<rect> current_debug_rects;
int current_debug_rects_valid_cycle = -1;
std::string& scene_title() {
static std::string title;
return title;
}
achievement_ptr current_achievement;
int current_achievement_duration = 0;
struct screen_flash {
graphics::color_transform color, delta;
int duration;
};
std::vector<screen_flash>& flashes() {
static std::vector<screen_flash> obj;
return obj;
}
int scene_title_duration_;
screen_position last_position;
}
bool is_achievement_displayed() {
return current_achievement && current_achievement_duration > 0;
}
screen_position& last_draw_position()
{
return last_position;
}
namespace {
int g_flash_disable = 0;
}
disable_flashes_scope::disable_flashes_scope()
{
++g_flash_disable;
}
disable_flashes_scope::~disable_flashes_scope()
{
--g_flash_disable;
}
void screen_color_flash(const graphics::color_transform& color, const graphics::color_transform& color_delta, int duration)
{
if(!g_flash_disable) {
screen_flash f = { color, color_delta, duration };
flashes().push_back(f);
}
}
void set_scene_title(const std::string& msg, int duration) {
//explicitly translate all level titles
scene_title() = (msg.size() > 0) ? _(msg) : msg;
scene_title_duration_ = duration;
}
void set_displayed_achievement(achievement_ptr a)
{
current_achievement = a;
current_achievement_duration = 250;
}
GLfloat hp_ratio = -1.0;
void draw_scene(const level& lvl, screen_position& pos, const entity* focus, bool do_draw) {
const bool draw_ready = update_camera_position(lvl, pos, focus, do_draw);
if(draw_ready) {
render_scene(lvl, pos, focus, do_draw);
}
}
bool update_camera_position(const level& lvl, screen_position& pos, const entity* focus, bool do_draw) {
if(focus == NULL && lvl.player()) {
focus = &lvl.player()->get_entity();
}
//flag which gets set to false if we abort drawing, due to the
//screen position being initialized now.
const bool draw_level = do_draw && pos.init;
#ifndef NO_EDITOR
const int sidebar_width = editor::sidebar_width();
const int codebar_height = editor::codebar_height();
#else
const int sidebar_width = 0;
const int codebar_height = 0;
#endif
const int screen_width = graphics::screen_width() - (lvl.in_editor() ? sidebar_width : 0);
const int screen_height = graphics::screen_height() - (lvl.in_editor() ? codebar_height : 0);
if(focus) {
// If the camera is automatically moved along by the level (e.g. a
// hurtling through the sky level) do that here.
pos.x += lvl.auto_move_camera_x()*100;
pos.y += lvl.auto_move_camera_y()*100;
//find how much padding will have to be on the edge of the screen due
//to the level being wider than the screen. This value will be 0
//if the level is larger than the screen (i.e. most cases)
const int x_screen_pad = std::max<int>(0, screen_width - lvl.boundaries().w());
const int y_screen_pad = std::max<int>(0, screen_height - lvl.boundaries().h());
//find the boundary values for the camera position based on the size
//of the level. These boundaries keep the camera from ever going out
//of the bounds of the level.
const int min_x = lvl.boundaries().x() + screen_width/2 - x_screen_pad/2;
const int max_x = lvl.boundaries().x2() - screen_width/2 + x_screen_pad/2;
const int min_y = lvl.boundaries().y() + screen_height/2 - y_screen_pad/2;
const int max_y = lvl.boundaries().y2() - screen_height/2 + y_screen_pad/2;
//std::cerr << "BOUNDARIES: " << lvl.boundaries().x() << ", " << lvl.boundaries().x2() << " WIDTH: " << screen_width << " PAD: " << x_screen_pad << "\n";
//we look a certain number of frames ahead -- assuming the focus
//keeps moving at the current velocity, we converge toward the point
//they will be at in x frames.
const int PredictiveFramesHorz = 20;
const int PredictiveFramesVert = 5;
int displacement_x = 0, displacement_y = 0;
if(pos.focus_x || pos.focus_y) {
displacement_x = focus->feet_x() - pos.focus_x;
displacement_y = focus->feet_y() - pos.focus_y;
}
pos.focus_x = focus->feet_x();
pos.focus_y = focus->feet_y();
//find the point we want the camera to converge toward. It will be the
//feet of the player, but inside the boundaries we calculated above.
int x = std::min(std::max(focus->feet_x() + displacement_x*PredictiveFramesHorz, min_x), max_x);
//calculate the adjustment to the camera's target position based on
//our vertical look. This is calculated as the square root of the
//vertical look, to make the movement slowly converge.
const int vertical_look = focus->vertical_look();
//find the y point for the camera to converge toward
int y = std::min(std::max(focus->feet_y() - (screen_height/(5*lvl.zoom_level())).as_int() + displacement_y*PredictiveFramesVert + vertical_look, min_y), max_y);
//std::cerr << "POSITION: " << x << "," << y << " IN " << min_x << "," << min_y << "," << max_x << "," << max_y << "\n";
if(lvl.focus_override().empty() == false) {
std::vector<entity_ptr> v = lvl.focus_override();
int left = 0, right = 0, top = 0, bottom = 0;
while(v.empty() == false) {
left = v[0]->feet_x();
right = v[0]->feet_x();
top = v[0]->feet_y();
bottom = v[0]->feet_y();
foreach(const entity_ptr& e, v) {
left = std::min<int>(e->feet_x(), left);
right = std::max<int>(e->feet_x(), right);
top = std::min<int>(e->feet_y(), top);
bottom = std::min<int>(e->feet_y(), bottom);
}
const int BorderSize = 20;
if(v.size() == 1 || right - left < screen_width/lvl.zoom_level() - BorderSize && bottom - top < screen_height/lvl.zoom_level() - BorderSize) {
break;
}
break;
v.pop_back();
}
x = std::min(std::max((left + right)/2, min_x), max_x);
y = std::min(std::max(((top + bottom)/2 - screen_height/(5*lvl.zoom_level())).as_int(), min_y), max_y);
}
//std::cerr << "POSITION2: " << x << "," << y << " IN " << min_x << "," << min_y << "," << max_x << "," << max_y << "\n";
if(lvl.lock_screen()) {
x = lvl.lock_screen()->x;
y = lvl.lock_screen()->y;
}
//std::cerr << "POSITION3: " << x << "," << y << " IN " << min_x << "," << min_y << "," << max_x << "," << max_y << "\n";
//for small screens the speech dialog arrows cover the entities they are
//pointing to. adjust to that by looking up a little bit.
if (lvl.current_speech_dialog() && preferences::virtual_screen_height() < 600)
y = std::min((y + (600 - screen_height)/(2*lvl.zoom_level())).as_int(), max_y);
//find the target x,y position of the camera in centi-pixels. Note that
//(x,y) represents the position the camera should center on, while
//now we're calculating the top-left point.
//
//the actual camera position will converge toward this point
const int target_xpos = 100*(x - screen_width/2);
const int target_ypos = 100*(y - screen_height/2);
if(pos.init == false) {
pos.x = target_xpos;
pos.y = target_ypos;
pos.init = true;
} else {
//Make (pos.x, pos.y) converge toward (target_xpos,target_ypos).
//We do this by moving asymptotically toward the target, which
//makes the camera have a nice acceleration/decceleration effect
//as the target position moves.
const int horizontal_move_speed = (30/lvl.zoom_level()).as_int();
const int vertical_move_speed = (10/lvl.zoom_level()).as_int();
int xdiff = (target_xpos - pos.x)/horizontal_move_speed;
int ydiff = (target_ypos - pos.y)/vertical_move_speed;
pos.x += xdiff;
pos.y += ydiff;
}
//shake decay is handled automatically; just by giving it an offset and velocity,
//it will automatically return to equilibrium
//shake speed
pos.x += (pos.shake_x_offset);
pos.y += (pos.shake_y_offset);
//shake velocity
pos.shake_x_offset += pos.shake_x_vel;
pos.shake_y_offset += pos.shake_y_vel;
//shake acceleration
if ((std::abs(pos.shake_x_vel) < 50) && (std::abs(pos.shake_x_offset) < 50)){
//prematurely end the oscillation if it's in the asymptote
pos.shake_x_offset = 0;
pos.shake_x_vel = 0;
}else{
//extraneous signs kept for consistency with conventional spring physics, also
//the value that "offset" is divided by, is (the inverse of) 'k', aka "spring stiffness"
//the value that "velocity" is divided by, is (the inverse of) 'b', aka "oscillation damping",
//which causes the spring to come to rest.
//These values are very sensitive, and tweaking them wrongly will cause the spring to 'explode',
//and increase its motion out of game-bounds.
if (pos.shake_x_offset > 0){
pos.shake_x_vel -= (1 * pos.shake_x_offset/3 + pos.shake_x_vel/15);
}else if(pos.shake_x_offset < 0) {
pos.shake_x_vel += (-1 * pos.shake_x_offset/3 - pos.shake_x_vel/15);
}
}
if ((std::abs(pos.shake_y_vel) < 50) && (std::abs(pos.shake_y_offset) < 50)){
//prematurely end the oscillation if it's in the asymptote
pos.shake_y_offset = 0;
pos.shake_y_vel = 0;
}else{
if (pos.shake_y_offset > 0){
pos.shake_y_vel -= (1 * pos.shake_y_offset/3 + pos.shake_y_vel/15);
}else if(pos.shake_y_offset < 0) {
pos.shake_y_vel += (-1 * pos.shake_y_offset/3 - pos.shake_y_vel/15);
}
}
const float target_zoom = lvl.zoom_level().as_float();
const float ZoomSpeed = 0.03;
if(std::abs(target_zoom - pos.zoom) < ZoomSpeed) {
pos.zoom = target_zoom;
} else if(pos.zoom > target_zoom) {
pos.zoom -= ZoomSpeed;
} else {
pos.zoom += ZoomSpeed;
}
}
last_position = pos;
return draw_level;
}
void render_scene(const level& lvl, screen_position& pos, const entity* focus, bool do_draw) {
#ifndef NO_EDITOR
const int sidebar_width = editor::sidebar_width();
#else
const int sidebar_width = 0;
#endif
const int screen_width = graphics::screen_width() - (lvl.in_editor() ? sidebar_width : 0);
graphics::prepare_raster();
glPushMatrix();
const int camera_rotation = lvl.camera_rotation();
if(camera_rotation) {
GLfloat rotate = GLfloat(camera_rotation)/1000.0;
glRotatef(rotate, 0.0, 0.0, 1.0);
}
if(pos.flip_rotate) {
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
const SDL_Surface* fb = SDL_GetVideoSurface();
const double angle = sin(0.5*3.141592653589*GLfloat(pos.flip_rotate)/1000.0);
const int pixels = (preferences::actual_screen_width()/2)*angle;
//then squish all future drawing inwards
glViewport(pixels, 0, preferences::actual_screen_width() - pixels*2, preferences::actual_screen_height());
}
int xscroll = (pos.x/100)&preferences::xypos_draw_mask;
int yscroll = (pos.y/100)&preferences::xypos_draw_mask;
// if(pos.zoom > 1.0) {
glScalef(pos.zoom, pos.zoom, 0);
xscroll += (screen_width/2)*(-1.0/pos.zoom + 1.0);
yscroll += (graphics::screen_height()/2)*(-1.0/pos.zoom + 1.0);
// }
glTranslatef(-xscroll, -yscroll, 0);
lvl.draw_background(xscroll, yscroll, camera_rotation);
int draw_width = screen_width;
int draw_height = graphics::screen_height();
if(pos.zoom < 1.0) {
draw_width /= pos.zoom;
draw_height /= pos.zoom;
}
lvl.draw(xscroll, yscroll, draw_width, draw_height);
foreach(const rect& r, current_debug_rects) {
graphics::draw_rect(r, graphics::color(0, 0, 255, 175));
}
if(current_debug_rects_valid_cycle != lvl.cycle()) {
current_debug_rects.clear();
}
current_debug_rects_valid_cycle = lvl.cycle();
graphics::clear_raster_distortion();
glPopMatrix();
for(std::vector<screen_flash>::iterator i = flashes().begin();
i != flashes().end(); ) {
const graphics::color& tint = i->color.to_color();
if(tint.a() > 0) {
graphics::draw_rect(rect(0, 0, graphics::screen_width(), graphics::screen_height()), tint);
}
i->color = graphics::color_transform(i->color.r() + i->delta.r(),
i->color.g() + i->delta.g(),
i->color.b() + i->delta.b(),
i->color.a() + i->delta.a());
if(--i->duration <= 0) {
i = flashes().erase(i);
} else {
++i;
}
}
#ifndef NO_EDITOR
debug_console::draw();
#endif
debug_console::draw_graph();
if (!pause_stack) lvl.draw_status();
if(scene_title_duration_ > 0) {
--scene_title_duration_;
const const_graphical_font_ptr f = graphical_font::get("default");
ASSERT_LOG(f.get() != NULL, "COULD NOT LOAD DEFAULT FONT");
const rect r = f->dimensions(scene_title());
const GLfloat alpha = scene_title_duration_ > 10 ? 1.0 : scene_title_duration_/10.0;
{
glColor4ub(0, 0, 0, 128*alpha);
f->draw(graphics::screen_width()/2 - r.w()/2 + 2, graphics::screen_height()/2 - r.h()/2 + 2, scene_title());
glColor4ub(255, 255, 255, 255*alpha);
}
{
f->draw(graphics::screen_width()/2 - r.w()/2, graphics::screen_height()/2 - r.h()/2, scene_title());
glColor4ub(255, 255, 255, 255);
}
}
if(current_achievement && current_achievement_duration > 0) {
--current_achievement_duration;
const_gui_section_ptr left = gui_section::get("achievements_left");
const_gui_section_ptr right = gui_section::get("achievements_right");
const_gui_section_ptr main = gui_section::get("achievements_main");
const const_graphical_font_ptr title_font = graphical_font::get("white_outline");
const const_graphical_font_ptr main_font = graphical_font::get("door_label");
const std::string title_text = _("Achievement Unlocked!");
const std::string name = current_achievement->name();
const std::string description = "(" + current_achievement->description() + ")";
const int width = std::max<int>(std::max<int>(
title_font->dimensions(title_text).w(),
main_font->dimensions(name).w()),
main_font->dimensions(description).w()
) + 8;
const int xpos = graphics::screen_width() - 16 - left->width() - right->width() - width;
const int ypos = 16;
const GLfloat alpha = current_achievement_duration > 10 ? 1.0 : current_achievement_duration/10.0;
glColor4f(1.0, 1.0, 1.0, alpha);
left->blit(xpos, ypos);
main->blit(xpos + left->width(), ypos, width, main->height());
right->blit(xpos + left->width() + width, ypos);
title_font->draw(xpos + left->width(), ypos - 10, title_text);
main_font->draw(xpos + left->width(), ypos + 24, name);
glColor4f(0.0, 1.0, 0.0, alpha);
main_font->draw(xpos + left->width(), ypos + 48, description);
glColor4f(1.0, 1.0, 1.0, 1.0);
}
if(pos.flip_rotate) {
const SDL_Surface* fb = SDL_GetVideoSurface();
const double angle = sin(0.5*3.141592653589*GLfloat(pos.flip_rotate)/1000.0);
//const int pixels = (fb->w/2)*angle;
const int pixels = (preferences::actual_screen_width()/2)*angle;
//first draw black over the sections of the screen which aren't to be drawn to
//GLshort varray1[8] = {0,0, pixels,0, pixels,fb->h, 0,fb->h};
//GLshort varray2[8] = {fb->w - pixels,0, fb->w,0, fb->w,fb->h, fb->w - pixels,fb->h};
GLshort varray1[8] = {0,0, pixels,0, pixels,preferences::actual_screen_height(), 0,preferences::actual_screen_height()};
GLshort varray2[8] = {preferences::actual_screen_width() - pixels,0, preferences::actual_screen_width(),0, preferences::actual_screen_width(),preferences::actual_screen_height(), preferences::actual_screen_width() - pixels,preferences::actual_screen_height()};
glColor4ub(0, 0, 0, 255);
#if defined(USE_GLES2)
gles2::manager gles2_manager(gles2::get_simple_shader());
glViewport(0, 0, preferences::actual_screen_width(), preferences::actual_screen_height());
gles2::active_shader()->shader()->vertex_array(2, GL_SHORT, 0, 0,varray1);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
gles2::active_shader()->shader()->vertex_array(2, GL_SHORT, 0, 0, varray2);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
#else
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
//glViewport(0, 0, fb->w, fb->h);
glViewport(0, 0, preferences::actual_screen_width(), preferences::actual_screen_height());
glVertexPointer(2, GL_SHORT, 0, &varray1);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glVertexPointer(2, GL_SHORT, 0, &varray2);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
#endif
glColor4ub(255, 255, 255, 255);
}
}
namespace {
boost::intrusive_ptr<performance_data> current_perf_data;
}
variant performance_data::get_value(const std::string& key) const
{
#define PERF_ATTR(m) if(key == #m) return variant(m);
PERF_ATTR(fps);
PERF_ATTR(cycles_per_second);
PERF_ATTR(delay);
PERF_ATTR(draw);
PERF_ATTR(process);
PERF_ATTR(flip);
PERF_ATTR(cycle);
PERF_ATTR(nevents);
#undef PERF_ATTR
return variant();
}
void performance_data::get_inputs(std::vector<game_logic::formula_input>* inputs) const
{
#define PERF_ATTR(m) inputs->push_back(std::string(#m))
PERF_ATTR(fps);
PERF_ATTR(cycles_per_second);
PERF_ATTR(delay);
PERF_ATTR(draw);
PERF_ATTR(process);
PERF_ATTR(flip);
PERF_ATTR(cycle);
PERF_ATTR(nevents);
#undef PERF_ATTR
}
void performance_data::set_current(const performance_data& d)
{
current_perf_data.reset(new performance_data(d));
}
performance_data* performance_data::current()
{
return current_perf_data.get();
}
void draw_fps(const level& lvl, const performance_data& data)
{
if(!preferences::debug()) {
return;
}
const_graphical_font_ptr font(graphical_font::get("door_label"));
if(!font) {
return;
}
std::ostringstream s;
s << data.fps << "/" << data.cycles_per_second << "fps; " << (data.draw/10) << "% draw; " << (data.flip/10) << "% flip; " << (data.process/10) << "% process; " << (data.delay/10) << "% idle; " << lvl.num_active_chars() << " objects; " << data.nevents << " events";
rect area = font->draw(10, 60, s.str());
if(controls::num_players() > 1) {
//draw networking stats
std::ostringstream s;
s << controls::packets_received() << " packets received; " << controls::num_errors() << " errors; " << controls::cycles_behind() << " behind; " << controls::their_highest_confirmed() << " remote cycles " << controls::last_packet_size() << " packet";
area = font->draw(10, area.y2() + 5, s.str());
}
if(!data.profiling_info.empty()) {
font->draw(10, area.y2() + 5, data.profiling_info);
}
}
void add_debug_rect(const rect& r)
{
current_debug_rects.push_back(r);
}
Jump to Line
Something went wrong with that request. Please try again.