/* client/targets.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include #include #include #include #include "audio/audio.h" #include "auxiliary/functions.h" #include "audio/sources.h" #include "client/input.h" #include "client/view.h" #include "client/video.h" #include "core/application.h" #include "core/entity.h" #include "core/func.h" #include "core/gameinterface.h" #include "core/range.h" #include "math/axis.h" #include "math/vector3f.h" #include "render/gl.h" #include "render/render.h" #include "render/text.h" namespace client { namespace targets { const float TARGETBOXRADIUS = 0.025f; unsigned int current_target_id = 0; unsigned int current_hover = 0; core::Entity *current_target = 0; core::Cvar *snd_engines = 0; bool is_legal_target(core::Entity *entity) { if (entity->id() == core::localcontrol()->id()) { return false; } else if (entity->state()->distance() < 0.001f) { return false; } else { if (entity->type() != core::Entity::Controlable) { return entity->state()->visible(); } else if (entity->state()->distance() < core::range::max) { return true; } } return false; } core::Entity* current() { return current_target; } unsigned int current_id() { return current_target_id; } unsigned int hover() { return current_hover; } void select_target(core::Entity *entity) { current_target = entity; current_target_id = current_target->id(); core::application()->notify_sound("ui/target"); } void select_target(unsigned int id) { if (!core::localcontrol()) return; core::Zone *zone = core::localcontrol()->zone(); if (!zone) return; core::Entity *entity = zone->find_entity(id); if (entity) select_target(entity); } void func_target_next(std::string const &args) { if (!core::localcontrol()) return; core::Zone *zone = core::localcontrol()->zone(); if (!zone) return; if (!zone->content().size()) { current_target = 0; current_target_id = 0; return; } core::Zone::Content::iterator it = zone->content().begin(); if (!current_target_id) { // first entity it = zone->content().begin(); while (!is_legal_target((*it)) && it != zone->content().end()) it++; } else { // current entity while (it != zone->content().end() && ((*it)->id() != current_target_id)) ++it; // next legal entity if (it != zone->content().end()) it++; if (it == zone->content().end()) { it = zone->content().begin(); } while (!is_legal_target((*it))) { it++; if (it == zone->content().end()) it = zone->content().begin(); if ((*it)->id() == current_target_id) { current_target = (*it); return; } } } if (it != zone->content().end()) { select_target((*it)); } else { current_target = 0; current_target_id = 0; } } void func_target_prev(std::string const &args) { if (!core::localcontrol()) return; core::Zone *zone = core::localcontrol()->zone(); if (!zone) return; if (!zone->content().size()) { current_target = 0; current_target_id = 0; return; } core::Zone::Content::reverse_iterator rit = zone->content().rbegin(); if (!current_target_id) { // last entity rit = zone->content().rbegin(); while (!is_legal_target((*rit)) && rit != zone->content().rend()) rit++; } else { // current entity while (rit != zone->content().rend() && ((*rit)->id() != current_target_id)) ++rit; // previous legal entity if (rit != zone->content().rend()) ++rit; if (rit == zone->content().rend()) { rit = zone->content().rbegin(); } while (!is_legal_target((*rit))) { ++rit; if (rit == zone->content().rend()) rit = zone->content().rbegin(); if ((*rit)->id() == current_target_id) { current_target = (*rit); return; } } } if (rit != zone->content().rend()) { select_target((*rit)); } else { current_target = 0; current_target_id = 0; } } void func_target_none(std::string const &args) { current_target = 0; current_target_id = 0; } void func_target_center(std::string const &args) { if (!core::localcontrol()) return; // this is essentialy the hover algorithm with the cursor in the center core::Entity *new_target = 0; math::Vector3f center = render::Camera::eye() + render::Camera::axis().forward() * (render::Camera::frustum_front() + 0.001); float smallest_d = -1; for (core::Zone::Content::iterator it=core::localcontrol()->zone()->content().begin(); it != core::localcontrol()->zone()->content().end(); it++) { core::Entity *entity = (*it); math::Vector3f v(entity->state()->location() - render::Camera::eye()); v.normalize(); if (is_legal_target(entity) && math::dotproduct(render::Camera::axis().forward(), v) > 0.5 ) { // calculate the distance from entity location to the line [eye - cursor] float d = math::Vector3f::length(math::crossproduct( (center - render::Camera::eye()) , (render::Camera::eye() - entity->location()))) / math::Vector3f::length(center - render::Camera::eye()); // the entity closer to the center beam if (smallest_d < 0 || d < smallest_d) { new_target = entity; smallest_d = d; } } } if (new_target) select_target(new_target); } void reset() { current_target = 0; current_target_id = 0; current_hover = 0; } void init() { snd_engines = core::Cvar::get("snd_engines", "1", core::Cvar::Archive); snd_engines->set_info("[bool] enable or disable engine sounds"); core::Func *func = 0; func = core::Func::add("target_next", func_target_next); func->set_info("select next target"); core::Func::add("target_prev", func_target_prev); func->set_info("select previous target"); core::Func::add("target_none", func_target_none); func->set_info("deselect current target"); core::Func::add("target_center", func_target_center); func->set_info("select target near center"); reset(); } void shutdown() { reset(); core::Func::remove("target_next"); core::Func::remove("target_prev"); core::Func::remove("target_none"); core::Func::remove("target_center"); } void render_listener_sound() { if (!(snd_engines && snd_engines->value())) return; math::Vector3f velocity(0, 0 ,0); if (core::localcontrol()) { velocity.assign(core::localcontrol()->state()->axis().forward() * core::localcontrol()->speed()); } audio::update_listener(render::Camera::eye(), render::Camera::axis(), velocity); } void render_entity_sound(core::Entity *entity) { if (!(entity->type() == core::Entity::Controlable)) return; if (!(snd_engines && snd_engines->value())) { entity->state()->clearsound(); return; } core::EntityControlable *entitycontrolable = (core::EntityControlable *) entity; core::ClientState *state = entity->state(); if (entity->model() && state->detailvisible() && entitycontrolable->thrust() > 0 ) { float speed = entitycontrolable->speed(); float pitch = 0.2f + entitycontrolable->thrust() * 0.8f; if (!state->state_enginesound) { if ((state->state_enginesound = audio::Sources::get()) > 0 ) { size_t enginesound = 0; if (entity->model()) enginesound = entity->model()->enginesound(); std::stringstream soundname; soundname << "engines/loop" << std::setfill('0') << std::setw(2) << enginesound; audio::loop(state->state_enginesound, soundname.str().c_str(), pitch, 0); } } if (state->state_enginesound) { audio::update_source(state->state_enginesound, state->location() - state->axis().forward() * entity->model()->maxbbox().y , state->axis().forward() * speed, pitch); } } else { entity->state()->clearsound(); } } //core::Entity *entity = current_target; 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 ) return; // transform the target into the normalized 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()); const float pointer_size = 48.0f; float r = pointer_size; if (!is_active_target) r *= 0.5; 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(); render::gl::color(1, 1, 1, 1); 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) { 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 - render::Text::fontheight() * 0.5f, strdistance); } } // render targets and sounds void draw() { core::Zone *zone = core::localplayer()->zone(); if (!zone) return; /* Notes http://en.wikipedia.org/wiki/Line-plane_intersection http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html */ using math::Vector3f; render_listener_sound(); current_target = 0; current_hover = 0; float z = -1; // mouse cursor location in 3d world space float x = 0; float y = 0; if ((input::joystick_lastmoved_time() > input::mouse_lastmoved_time()) && (render::Camera::mode() == render::Camera::Cockpit || render::Camera::mode() == render::Camera::Track)) { x = 0; y = 0; } else { x = (float)(input::mouse_position_x() - video::width /2) / (float)video::width; y = (float)(input::mouse_position_y() - video::height /2) / (float)video::height / render::Camera::aspect(); } Vector3f cursor = render::Camera::eye() + render::Camera::axis().forward() * (render::Camera::frustum_front() + 0.001); cursor -= render::Camera::axis().left() * x; cursor -= render::Camera::axis().up() * y; math::Vector3f center = render::Camera::eye() + (render::Camera::axis().forward() * (render::Camera::frustum_front() +0.001f)); for (core::Zone::Content::iterator it=zone->content().begin(); it != zone->content().end(); it++) { core::Entity *entity = (*it); // render entity sound render_entity_sound(entity); // find the current target if (core::localcontrol() && is_legal_target(entity)) { if (entity->id() == current_target_id) { current_target = entity; draw_entity_target(current_target, true); } else if (entity->type() == core::Entity::Controlable) { draw_entity_target(entity, false); } // check if the mouse is hovering the entity Vector3f v(entity->state()->location() - render::Camera::eye()); v.normalize(); if (math::dotproduct(render::Camera::axis().forward(), v) > 0.75 ) { // calculate the distance from entity location to the line [eye - cursor] float d = math::Vector3f::length(math::crossproduct( (cursor - render::Camera::eye()) , (render::Camera::eye() - entity->location()))) / math::Vector3f::length(cursor - render::Camera::eye()); float r = entity->radius() * 0.5f; if (entity->state()->distance() > 512) math::clamp(r, 8.0f,r); else if (entity->state()->distance() > 256) math::clamp(r, 4.0f,r); else if (entity->state()->distance() > 128.0f) math::clamp(r, 2.0f,r); // if the cursor-beam hits the entity sphere if (d < r) { float myz = math::distance(cursor, entity->location()); if (z < 0 || myz < z) { current_hover = entity->id(); z = myz; } } } } } if (!current_target) { current_target_id = 0; } else { current_target_id = current_target->id(); } } } }