/* view.cc This file is part of the Osirion project and is distributed under the terms and conditions of the GNU General Public License version 2 */ #include #include #include #include #include #include "auxiliary/functions.h" #include "client/client.h" #include "client/chat.h" #include "client/console.h" #include "client/input.h" #include "client/targets.h" #include "client/video.h" #include "client/view.h" #include "render/render.h" #include "core/core.h" #include "math/mathlib.h" #include "sys/sys.h" #include "ui/paint.h" #include "ui/ui.h" #include "ui/widget.h" namespace client { core::Cvar *draw_ui = 0; core::Cvar *draw_stats = 0; core::Cvar *draw_devinfo = 0; core::Cvar *draw_keypress = 0; core::Cvar *ui_pointercolor = 0; core::Cvar *ui_pointerhovercolor = 0; const float pointer_size = 48.0f; void time_to_stream(std::stringstream &str, float time) { int minutes = (int) floorf(time / 60.0f); int seconds = (int) floorf( time - (float) minutes* 60.0f); str << std::setfill(' ') << std::setw(4) << minutes << ":" << std::setfill('0') << std::setw(2) << seconds; } /* -- DevInfo------------------------------------------------------- */ DevInfo::DevInfo(ui::Widget *parent) : ui::Widget(parent) { set_label("devinfo"); set_border(true); } void DevInfo::draw() { draw_border(); std::stringstream textstream; core::Entity *target = targets::current(); float d = 0; textstream << "^Ncore ^B"; time_to_stream(textstream, core::application()->time()); textstream << '\n'; if (core::game()) { textstream << "^Ntime ^B"; time_to_stream(textstream, core::game()->time()); } textstream << '\n'; if (core::localcontrol()) { textstream << std::fixed << std::setprecision(2) << "^Nx ^B" << core::localcontrol()->location().x << " " << "^Ny ^B" << core::localcontrol()->location().y << " " << "^Nz ^B" << core::localcontrol()->location().z << '\n'; textstream << "^Nthurst ^B" << core::localcontrol()->thrust() << " " << "^Nspeed ^B" << core::localcontrol()->speed() << '\n'; if (target) { d = math::distance(core::localcontrol()->location(), target->state()->location()) - target->radius() - core::localcontrol()->radius(); textstream << "^Ndist ^B" << d << '\n'; } } ui::paint::color(palette()->foreground()); ui::paint::text(global_location(), font(), textstream); } /* -- Stats -------------------------------------------------------- */ Stats::Stats(ui::Widget *parent) : ui::Widget(parent) { set_label("stats"); set_border(true); // clear counters for (size_t i =0; i < fps_counter_size; i++) fps_counter_time[i] = 0.0f; for (size_t i = 0; i < net_counter_size; i++) net_counter_traffic[i] = 0; } void Stats::draw() { draw_border(); // average fps fps_counter_time[fps_counter_index] = core::application()->time(); fps_counter_index = (fps_counter_index + 1 ) % fps_counter_size; float min_time = core::application()->time(); for (size_t i=0; i < fps_counter_size; i++) if (fps_counter_time[i] < min_time) min_time = fps_counter_time[i]; float fps = 0.0f; float t = (core::application()->time() - min_time); if (t > 0) { fps = roundf(((float) fps_counter_size - 1.0f) / t); } std::stringstream textstream; if (core::game()) { textstream << "^Ntime ^B"; time_to_stream(textstream, core::game()->clientframetime()); } textstream << std::setfill(' ') << "\n"; textstream << "^Nfps ^B" << std::setw(6) << fps << "\n"; if (core::application()->connected()) { textstream << "^Ntris ^B" << std::setw(5) << render::Stats::tris << "\n"; textstream << "^Nquads ^B" << std::setw(5) << render::Stats::quads << "\n"; if (core::Stats::network_bytes_sent + core::Stats::network_bytes_received) { net_counter_traffic[net_counter_index] = core::Stats::network_bytes_sent + core::Stats::network_bytes_received; net_counter_time[net_counter_index] = core::application()->time(); size_t index_max = net_counter_index; net_counter_index = (net_counter_index + 1) % net_counter_size; size_t index_min = net_counter_index; float d = net_counter_time[index_max] - net_counter_time[index_min]; if (d > 0) { float traffic = net_counter_traffic[index_max] - net_counter_traffic[index_min]; textstream << "^Nnet ^B" << std::setw(6) << roundf( (float) traffic / d ) << "\n"; } } } ui::paint::color(palette()->foreground()); ui::paint::text(global_location(), font(), textstream); } /* -- KeyPress ----------------------------------------------------- */ KeyPress::KeyPress(ui::Widget *parent) : Widget(parent) { set_label("keypress"); set_border(true); } void KeyPress::draw() { draw_border(); if(input::last_key_pressed()) { ui::paint::color(palette()->highlight()); ui::paint::text(global_location(), size(), font(), input::last_key_pressed()->name(), ui::AlignCenter); } } /* -- View --------------------------------------------------------- */ View::View(ui::Widget *parent) : ui::Widget(parent) { set_label("view"); set_border(false); // initialize client variables draw_devinfo = core::Cvar::get("draw_devinfo", "0", core::Cvar::Archive); draw_devinfo->set_info("[bool] draw developer information"); draw_stats = core::Cvar::get("draw_stats", "0", core::Cvar::Archive); draw_stats->set_info("[bool] draw network and render statistics"); draw_keypress = core::Cvar::get("draw_keypress", "0", core::Cvar::Archive); draw_keypress->set_info("[bool] draw keypress key names"); // add child widgets view_center = new ui::Bitmap(this, "pointers/center"); view_devinfo = new DevInfo(this); view_stats = new Stats(this); view_keypress = new KeyPress(this); view_chat = new Chat(this); // make sure the view is at the bottom of the draw stack lower(); } void View::resize() { set_size(parent()->size()); // reposition chat widget view_chat->set_size(font()->width()*64, font()->height()*5); view_chat->set_location(font()->width() * 0.5f, height() *0.5f); // reposition devinfo widget view_devinfo->set_size(font()->width()*32, font()->height()*5); view_devinfo->set_location(font()->width() * 0.5f, font()->height() * 0.5f); // reposition stats widget view_stats->set_size(font()->width()*12, font()->height()*5); view_stats->set_location(width() - view_stats->width() - font()->width() * 0.5, font()->height() * 0.5f); // reposition keypress widget view_keypress->set_size(font()->width()*12, font()->height()*1); view_keypress->set_location(width() - view_keypress->width() - font()->width() * 0.5, height() - view_keypress->height() - font()->height() * 0.5f); // reposition center view_center->set_size(pointer_size, pointer_size); view_center->set_location((size() - view_center->size()) * 0.5f); view_center->set_color(palette()->pointer()); } void View::draw() { view_devinfo->set_visible(draw_devinfo->value() ? true : false); view_stats->set_visible(draw_stats->value() ? true : false); view_keypress->set_visible(draw_keypress->value() ? true : false); if (core::localcontrol() && (input::mouse_control || input::joystick_control) && (render::Camera::mode() == render::Camera::Cockpit || render::Camera::mode() == render::Camera::Track)) { view_center->set_visible(true); } else { view_center->set_visible(false); } } /* -- namespace view ----------------------------------------------- */ namespace view { void init() { // FIXME integrate with libui draw_ui = core::Cvar::get("draw_ui", "1", core::Cvar::Archive); draw_ui->set_info("[bool] draw the user interface"); ui_pointercolor = core::Cvar::get("ui_pointercolor", "0 .5 0", core::Cvar::Archive); ui_pointercolor->set_info("[r g b] mouse pointer color"); ui_pointerhovercolor = core::Cvar::get("ui_pointerhovercolor", "0 1 0", core::Cvar::Archive); ui_pointerhovercolor->set_info("[r g b] mouse pointer hover color"); targets::init(); } void shutdown() { targets::shutdown(); } /* FIXME should be merged with the render passes and in the bbox pass */ void draw_entity_world_target(core::Entity *entity) { using namespace render; model::Model *model = entity->model(); if (!model) return; if (!model->docks().size()) return; float d = math::distance(core::localcontrol()->location(), entity->state()->location()) - entity->radius() - core::localcontrol()->radius(); if (d > 100.0f) return; gl::enable(GL_DEPTH_TEST); gl::push(); gl::translate(entity->state()->location()); gl::multmatrix(entity->state()->axis()); gl::color(0, 1.0f, 1.0f, 1.0f); for (model::Model::Docks::iterator dit = model->docks().begin(); dit != model->docks().end(); dit++) { model::Dock *dock = (*dit); math::Vector3f maxbox(dock->location()); math::Vector3f minbox(dock->location()); for (size_t i=0; i < 3; i++) { maxbox[i] += 0.025; minbox[i] -= 0.025; } // top gl::begin(gl::LineLoop); gl::vertex(maxbox.x, maxbox.y, maxbox.z); gl::vertex(minbox.x, maxbox.y, maxbox.z); gl::vertex(minbox.x, minbox.y, maxbox.z); gl::vertex(maxbox.x, minbox.y, maxbox.z); gl::end(); // bottom gl::begin(gl::LineLoop); gl::vertex(maxbox.x, maxbox.y, minbox.z); gl::vertex(minbox.x, maxbox.y, minbox.z); gl::vertex(minbox.x, minbox.y, minbox.z); gl::vertex(maxbox.x, minbox.y, minbox.z); gl::end(); gl::begin(gl::Lines); gl::vertex(maxbox.x, maxbox.y, maxbox.z); gl::vertex(maxbox.x, maxbox.y, minbox.z); gl::vertex(minbox.x, maxbox.y, maxbox.z); gl::vertex(minbox.x, maxbox.y, minbox.z); gl::vertex(minbox.x, minbox.y, maxbox.z); gl::vertex(minbox.x, minbox.y, minbox.z); gl::vertex(maxbox.x, minbox.y, maxbox.z); gl::vertex(maxbox.x, minbox.y, minbox.z); gl::end(); } gl::pop(); gl::disable(GL_DEPTH_TEST); } void draw_entity_offscreen_target(core::Entity *entity, bool is_active_target) { math::Vector3f target(entity->state()->location() - render::Camera::eye()); target = render::Camera::axis().transpose() * target; float cx = 0; float cy = 0; if ( target.y*target.y + target.z*target.z < 0.0001 ) { // X - bound, behind (front is visible) cx = 0.0f; cy = -0.5f; } else if (fabs(target.y) > fabs(target.z)) { // Y-bound cx = math::sgnf(target.y) * 0.5f; cy = 0.5f * target.z / fabs(target.y); } else { // Z-bound cx = 0.5f * target.y / fabs(target.z); cy = math::sgnf(target.z) * 0.5f; } const float r = 16; const float margin = 24; cx = (0.5f - cx) * ((float) video::width - margin*2); cx += margin; cy = (0.5f - cy) * ((float)video::height - margin*2); cy += margin; render::gl::disable(GL_TEXTURE_2D); render::gl::color(0, 0, 0, 1); render::gl::begin(render::gl::LineLoop); glVertex3f(cx+r, cy+2, 0); glVertex3f(cx, cy+r+2, 0); glVertex3f(cx-r, cy+2, 0); glVertex3f(cx, cy-r+2, 0); render::gl::end(); if (entity == core::localplayer()->mission_target()) { render::gl::color(1, 0.5f, 1, 1); // FIXME mission color } else if (entity->type() == core::Entity::Controlable) { render::gl::color(0, 1, 0, 1); // FIXME allegiance color } else { render::gl::color(1, 1, 1, 1); // FIXME neutral color } render::gl::begin(render::gl::LineLoop); glVertex3f(cx+r, cy, 0); glVertex3f(cx, cy+r, 0); glVertex3f(cx-r, cy, 0); glVertex3f(cx, cy-r, 0); render::gl::end(); render::gl::enable(GL_TEXTURE_2D); } void draw_entity_target(core::Entity *entity, bool is_active_target) { using math::Vector3f; // don't draw target if we're very close to it if (entity->state()->distance() < 0.001f) return; // don't draw target if it is outside the visible cone Vector3f target(entity->state()->location() - render::Camera::eye()); if (math::dotproduct(render::Camera::axis().forward(), Vector3f::normalized(target)) < 0.75 ) { draw_entity_offscreen_target(entity, is_active_target); return; } // transform the target into the camera coordinate system target = render::Camera::axis().transpose() * target; // calculate the intersection between the line (0,0,0)-target and the frustum front float t = (render::Camera::frustum_front() + 0.001f) / target.x; Vector3f center(target *t); float cx = video::width * (0.5 - center.y); float cy = video::height * (0.5 - center.z * render::Camera::aspect()); if ((cx < 0 ) || (cy < 0) || (cx > video::width) || (cy > video::height)) { draw_entity_offscreen_target(entity, is_active_target); return; } const float pointer_size = 48.0f; float r = pointer_size; if (!is_active_target) r *= 0.5; render::gl::disable(GL_TEXTURE_2D); // outer square shadow render::gl::color(0, 0, 0, 1); render::gl::begin(render::gl::LineLoop); glVertex3f(cx+r, cy+2, 0); glVertex3f(cx, cy+r+2, 0); glVertex3f(cx-r, cy+2, 0); glVertex3f(cx, cy-r+2, 0); render::gl::end(); if (entity == core::localplayer()->mission_target()) { render::gl::color(1, 0.5f, 1, 1); // FIXME mission color } else if (entity->type() == core::Entity::Controlable) { render::gl::color(0, 1, 0, 1); // FIXME allegiance color } else { render::gl::color(1, 1, 1, 1); // FIXME neutral color } // outer square0 render::gl::begin(render::gl::LineLoop); glVertex3f(cx+r, cy, 0); glVertex3f(cx, cy+r, 0); glVertex3f(cx-r, cy, 0); glVertex3f(cx, cy-r, 0); render::gl::end(); render::gl::enable(GL_TEXTURE_2D); if (is_active_target) { // entity name and distance std::stringstream strdistance; float d = math::distance(core::localcontrol()->location(), entity->state()->location()) - entity->radius() - core::localcontrol()->radius(); if (d > 0 ) { if (d > 100.0f) { strdistance << roundf(d * 0.1f) << "km"; } else { strdistance << roundf(d * 100.0f) << "m"; } } else { strdistance << "--"; } if (entity->type() == core::Entity::Controlable) { render::Text::setcolor('B'); } else { render::Text::setcolor('N'); } render::Text::draw(cx-aux::text_length(entity->name()) * render::Text::fontwidth()*0.5f, cy-r-4-render::Text::fontheight(), entity->name()); render::Text::draw(cx - aux::text_length(strdistance.str()) * render::Text::fontwidth() * 0.5f, cy+r+4, strdistance); } } void draw_hud() { using namespace render; std::stringstream status; // draw a basic HUD if (core::localcontrol() && core::localcontrol()->zone()) { core::Zone *zone = core::localcontrol()->zone(); // draw targets for (core::Zone::Content::iterator it=zone->content().begin(); it != zone->content().end(); it++) { core::Entity *entity = (*it); if (targets::is_legal_target(entity)) { if (entity == core::localplayer()->mission_target()) { draw_entity_target(entity, true); } else if (entity == targets::current()) { draw_entity_target(entity, true); } else if (entity->type() == core::Entity::Controlable) { draw_entity_target(entity, false); } } } unsigned int state = core::localcontrol()->eventstate(); if (state) { std::stringstream statestr; statestr.clear(); if (state == core::Entity::ImpulseInitiate) { statestr << "^FInitializing kinetic impulse drive " << core::localcontrol()->timer(); } else if (state == core::Entity::Impulse) { //statestr << "^FKinetic impulse"; } else if (state == core::Entity::JumpInitiate) { statestr << "^FInitializing hyperspace jump drive "<< core::localcontrol()->timer(); } else if (state == core::Entity::Jump) { statestr << "^FJumping..."; } Text::draw(4, video::height - Text::fontheight()*3-4, statestr); } core::Entity *target = targets::current(); std::stringstream strdistance; float d = 0; float y = 1.0f; if (target) { std::stringstream strtarget; strtarget << "^B" << target->name() << "\n^B"; d = math::distance(core::localcontrol()->location(), target->state()->location()) - target->radius() - core::localcontrol()->radius(); if (d > 0 ) { strtarget << "^Ndist:^B "; if (d > 100.0f) { strtarget << roundf(d * 0.1f) << "km"; } else { strtarget << roundf(d * 100.0f) << "m"; } } else { strtarget << " --"; } strtarget << '\n'; Text::draw(video::width - 4-Text::fontwidth()*32, video::height - Text::fontheight()*2 -4, strtarget); y = 3.0f; } Text::setcolor('N'); //set normal color Text::draw(video::width-4-Text::fontwidth()*32, video::height-Text::fontheight()*y-4, core::localcontrol()->zone()->name()); Textures::bind("bitmaps/hud/thruster_base"); // 316 x 32 bitmap gl::color(1, 1, 1, 1); gl::begin(render::gl::Quads); glTexCoord2f(0, 0); gl::vertex(4, video::height - 4 - 32, 0); glTexCoord2f(1, 0); gl::vertex(4 + 316, video::height - 4 - 32, 0); glTexCoord2f(1, 1); gl::vertex(4 + 316, video::height - 4 , 0); glTexCoord2f(0, 1); gl::vertex(4, video::height - 4 , 0); gl::end(); float u = core::localcontrol()->thrust(); if (core::localcontrol()->eventstate() == core::Entity::Impulse) { u = 1.0; } if (( u > 0) || (core::localcontrol()->eventstate() == core::Entity::Impulse)) { if (core::localcontrol()->eventstate() == core::Entity::Impulse) { gl::color(0, .8, 0); } else { float d = math::absf(input::local_thrust - u); if (d > 0.1) { d = 0.1f; } gl::color(1, 1, .5f + d * 5.0f); } Textures::bind("bitmaps/hud/thruster_indicator"); // 316 x 32 bitmap gl::begin(render::gl::Quads); glTexCoord2f(0, 0); gl::vertex(4, video::height - 4 - 32, 0); glTexCoord2f(u, 0); gl::vertex(4.0f + u * 316.0f, video::height - 4 - 32, 0); glTexCoord2f(u, 1); gl::vertex(4.0f + u * 316.0f, video::height - 4 , 0); glTexCoord2f(0, 1); gl::vertex(4, video::height - 4 , 0); gl::end(); } Text::setfont("gui", 14, 24); Text::setcolor('B'); //set normal color std::stringstream speedstr; speedstr << "^B" << roundf(core::localcontrol()->speed() * 100.0f); Text::draw( 316+4+10, video::height - 6 -16 - render::Text::fontwidth() /2, speedstr); Text::setfont("gui", 12, 18); Text::setcolor('N'); //set normal color } } void draw_cursor() { if (client()->console()->visible()) return; float angle = 0; float x = (float) input::mouse_position_x() - (pointer_size / 2.0f); float y = (float) input::mouse_position_y() - (pointer_size / 2.0f); bool cursor_animated = false; math::Color color(1.0, 0.5); if(ui::root()->active()) { render::Textures::bind("bitmaps/pointers/pointer"); } else if (core::localcontrol()) { if (render::Camera::mode() == render::Camera::Overview) { render::Textures::bind("bitmaps/pointers/aim"); } else { if (targets::hover()) { if (ui_pointerhovercolor) { std::stringstream colorstr(ui_pointerhovercolor->str()); colorstr >> color; } render::Textures::bind("bitmaps/pointers/target"); cursor_animated = true; if (input::joystick_lastmoved_time() > input::mouse_lastmoved_time()) { x = (video::width - pointer_size) /2; y = (video::height - pointer_size) /2; } } else if (input::mouse_control) { if (ui_pointercolor) { std::stringstream colorstr(ui_pointercolor->str()); colorstr >> color; } render::Textures::bind("bitmaps/pointers/control"); if (!input::mouse_deadzone) { x = input::mouse_position_x() - (pointer_size /2); y = input::mouse_position_y() - (pointer_size /2); } else { x = (video::width - pointer_size) /2; y = (video::height - pointer_size) /2; } } else { if ((input::joystick_lastmoved_time() > input::mouse_lastmoved_time()) && (render::Camera::mode() == render::Camera::Cockpit || render::Camera::mode() == render::Camera::Track)) { color.assign(1.0, 0.0); } else { color.assign(1.0, 0.5); } render::Textures::bind("bitmaps/pointers/aim"); } } } else { return; } if (cursor_animated) { render::gl::push(); render::gl::translate(x+pointer_size/2, y+pointer_size/2, 0.0f); angle = core::application()->time()* 0.75f - floorf(core::application()->time() * 0.75f); angle *= 360.0f; render::gl::rotate(angle, math::Vector3f(0, 0, 1.0f)); render::gl::translate(-x-pointer_size/2, -y-pointer_size/2, 0.0f); } render::gl::color(color); render::gl::begin(render::gl::Quads); glTexCoord2f(0,0 ); render::gl::vertex(x,y,0.0f); glTexCoord2f(1, 0); render::gl::vertex(x+pointer_size, y, 0.0f); glTexCoord2f(1, 1); render::gl::vertex(x+pointer_size, y+pointer_size, 0.0f); glTexCoord2f(0, 1); render::gl::vertex(x, y+pointer_size, 0.0f); render::gl::end(); if (cursor_animated) { render::gl::pop(); } } void frame(float elapsed) { using namespace render; // Clear the color and depth buffers. gl::clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); render::Stats::clear(); if (core::application()->connected() && core::game()->serverframetime() && core::localplayer()->zone()) { render::Camera::frame(elapsed); render::Camera::frustum(); render::draw(elapsed); // draw the world targets::draw(); // validate current target, render sound if (targets::current()) // draw target docks etc draw_entity_world_target(targets::current()); render::Camera::ortho(); } else { render::Camera::ortho(); } // draw the user interface gl::color(1.0f, 1.0f, 1.0f, 1.0f); gl::disable(GL_TEXTURE_2D); gl::enable(GL_BLEND); ui::root()->frame(); // draw the hud - TODO move as much as possible into ui:: gl::enable(GL_TEXTURE_2D); gl::color(1.0f, 1.0f, 1.0f, 1.0f); // draw text elements if (draw_ui->value()) { Text::setfont("gui", 12, 18); // draw the hud draw_hud(); // draw the mouse cursor draw_cursor(); } gl::disable(GL_TEXTURE_2D); gl::disable(GL_BLEND); } } //namespace view } // namespace client