/* render/draw.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 "core/application.h" #include "core/entity.h" #include "core/entityglobe.h" #include "core/gameinterface.h" #include "core/range.h" #include "math/functions.h" #include "model/fragment.h" #include "model/material.h" #include "model/model.h" #include "render/camera.h" #include "render/debugdrawer.h" #include "render/draw.h" #include "render/dust.h" #include "render/light.h" #include "render/lightenvironment.h" #include "render/render.h" #include "render/textures.h" #include "render/gl.h" namespace render { size_t Stats::tris = 0; size_t Stats::quads = 0; size_t Stats::fragments = 0; void Stats::clear() { tris = 0; quads = 0; fragments = 0; } math::Vector3f v0(1, -1, 1); math::Vector3f v1(1, 1, 1); math::Vector3f v2(-1, 1, 1); math::Vector3f v3(-1, -1, 1); math::Vector3f v4(1, -1, -1); math::Vector3f v5(1, 1, -1); math::Vector3f v6(-1, 1, -1); math::Vector3f v7(-1, -1, -1); core::Zone *zone = 0; Camera _camera_control(Camera::Track); Camera _camera_overview(Camera::Overview); Camera *_camera = &_camera_control; bool draw_particles = true; bool draw_lights = true; typedef std::list DrawlistEntities; typedef std::list DrawlistGlobes; DrawlistEntities drawlist_entities; DrawlistGlobes drawlist_globes; bool compare_entity_distance(const core::Entity * entityfirst, const core::Entity * entitysecond) { const float d1 = ext_render(entityfirst)->distance(); const float d2 = ext_render(entitysecond)->distance(); if (d1 < d2) { return false; } else { return true; } } LightEnvironment lightenv_zone; Camera & camera() { return *_camera; } /* ---- Prepare the renderer state --------------------------------- */ void pass_prepare(float seconds) { // initialize camera const core::Entity *previous_target = _camera->target(); if (core::localplayer()->view()) { _camera = &_camera_overview; _camera->set_target(core::localplayer()->view()); _camera->set_multiplier(2.0f); } else if (core::localcontrol()) { if (core::localcontrol()->state() == core::Entity::Destroyed) { _camera = &_camera_overview; _camera->set_multiplier(1.0f); } else { _camera = &_camera_control; } _camera->set_target(core::localcontrol()); } else { _camera = &_camera_overview; _camera->set_target(0); } if (_camera->target() != previous_target) { _camera->reset(); } _camera->frame(seconds); _camera->draw(); // render settings for this pass_prepare draw_lights = true; if (r_lights && (r_lights->value() <= 0.0f)) { draw_lights = false; } draw_particles = true; if (r_particles && (r_particles->value() <= 0.0f)) { draw_particles = false; } // initialize lights lightenv_zone.clear(); // zone ambient light GLfloat zone_ambient[4]; for (size_t i = 0; i < 3; i++) { zone_ambient[i] = core::localplayer()->zone()->ambient_color()[i]; } zone_ambient[3] = 1.0f; gl::lightmodel(GL_LIGHT_MODEL_AMBIENT, zone_ambient); // initialize draw lists drawlist_entities.clear(); drawlist_globes.clear(); for (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); ++it) { core::Entity *entity = (*it); if (entity->visible() && !entity->serverside()) { // add render extension if required if (!ext_render(entity)) { new RenderExt(entity); } ext_render(entity)->frame(seconds, *_camera); if (entity->type() == core::Entity::Globe) { core::EntityGlobe *globe = static_cast(entity); // add the globe to the draw list if (globe->visible() && !ext_render(globe)->behind()) { drawlist_globes.push_back(globe); } // add zone lights to the light environment if (globe->has_flag(core::Entity::Bright)) { float zone_light_brightness = math::max(globe->color().r, math::max(globe->color().g, globe->color().b)); Light *zone_light = new Light(globe->location(), globe->color()); zone_light->set_attenuation(zone_light_brightness * 1.5f, zone_light_brightness * 0.00001f, zone_light_brightness * 0.000000001f); lightenv_zone.add(zone_light); } } else { // add entity to draw lists if (entity->visible() && !ext_render(entity)->behind()) { drawlist_entities.push_back(entity); } // loads entity models if required if (!entity->model() && entity->modelname().size()) { entity->set_model(model::Model::load(entity->modelname())); } } } } // sort draw lists by distance drawlist_entities.sort(compare_entity_distance); drawlist_globes.sort(compare_entity_distance); // zone lights are draw in world space coordinates lightenv_zone.draw(); lightenv_zone.enable(); } /* ----- Skybox ---------------------------------------------------- */ /* * To use quake3 skyboxes: * switch left and right images * rotate top image 90 degrees clockwise * rotate bottom image 90 degrees counter-clockwise * */ void draw_pass_sky() { if (!(r_sky && r_sky->value())) return; if (!core::localplayer()->zone()) return; if (core::localplayer()->zone()->sky().size()) { Textures::load_cubemap("textures/sky/" + core::localplayer()->zone()->sky()); } else { return; } gl::enable(GL_TEXTURE_CUBE_MAP); gl::push(); gl::translate(_camera->location()); gl::color(1.0f, 1.0f, 1.0f, 1.0f); const float r = core::range::maxdistance * 0.5f; gl::begin((r_wireframe && r_wireframe->value()) ? gl::LineLoop : gl::Quads); // front gl::texcoord(1, 1, 1); gl::vertex(r, r, r); gl::texcoord(1, -1, 1); gl::vertex(r, -r, r); gl::texcoord(1, -1, -1); gl::vertex(r, -r, -r); gl::texcoord(1, 1, -1); gl::vertex(r, r, -r); // right gl::texcoord(1, -1, 1); gl::vertex(r, -r, r); gl::texcoord(-1, -1, 1); gl::vertex(-r, -r, r); gl::texcoord(-1, -1, -1); gl::vertex(-r, -r, -r); gl::texcoord(1, -1, -1); gl::vertex(r, -r, -r); // back gl::texcoord(-1, -1, 1); gl::vertex(-r, -r, r); gl::texcoord(-1, 1, 1); gl::vertex(-r, r, r); gl::texcoord(-1, 1, -1); gl::vertex(-r, r, -r); gl::texcoord(-1, -1, -1); gl::vertex(-r, -r, -r); // left gl::texcoord(-1, 1, 1); gl::vertex(-r, r, r); gl::texcoord(1, 1, 1); gl::vertex(r, r, r); gl::texcoord(1, 1, -1); gl::vertex(r, r, -r); gl::texcoord(-1, 1, -1); gl::vertex(-r, r, -r); // up gl::texcoord(-1, 1, 1); gl::vertex(-r, r, r); gl::texcoord(-1, -1, 1); gl::vertex(-r, -r, r); gl::texcoord(1, -1, 1); gl::vertex(r, -r, r); gl::texcoord(1, 1, 1); gl::vertex(r, r, r); // down gl::texcoord(1, 1, -1); gl::vertex(r, r, -r); gl::texcoord(1, -1, -1); gl::vertex(r, -r, -r); gl::texcoord(-1, -1, -1); gl::vertex(-r, -r, -r); gl::texcoord(-1, 1, -1); gl::vertex(-r, r, -r); gl::end(); gl::pop(); gl::disable(GL_TEXTURE_CUBE_MAP); Stats::quads += 6; } /* ---- Draw a wireframe box --------------------------------------- */ void draw_box(const math::Vector3f & minbox, const math::Vector3f & maxbox) { // 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(); // body 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(); } /* ---- Globes ----------------------------------------------------- */ void draw_sphere(const math::Color & color, float radius) { gl::scale(radius, radius, radius); gl::color(color); size_t index = 0; size_t count = (model::SPHERESEGMENTS) * 2; // draw body for (int j = 0; j < (model::SPHERESEGMENTS - 1) / 2; j++) { glDrawArrays(gl::QuadStrip, index, count); index += count; Stats::quads += count / 2 - 1; } } void draw_globe_corona(const Camera & camera, const math::Vector3f & location, const math::Color & color, const float radius, const size_t corona_id) { // draw the globe's corona if (corona_id) { math::Vector3f v(location - camera.location()); v.normalize(); float a = dotproduct(v, camera.axis().forward()); if (a > 0.1f) { gl::disable(GL_DEPTH_TEST); gl::depthmask(GL_FALSE); // disable depth buffer writes gl::enable(GL_TEXTURE_2D); gl::enable(GL_BLEND); Textures::bind(corona_id); math::Color drawcolor(color); drawcolor.a = a - 0.1f; gl::color(drawcolor); gl::begin(gl::Quads); gl::texcoord(0, 1); gl::vertex((camera.axis().up() - camera.axis().left()) * radius * 4.0f); gl::texcoord(0, 0); gl::vertex((camera.axis().up() + camera.axis().left()) * radius * 4.0f); gl::texcoord(1, 0); gl::vertex((camera.axis().up() * -1.0f + camera.axis().left()) * radius * 4.0f); gl::texcoord(1, 1); gl::vertex((camera.axis().up() * -1.0f - camera.axis().left()) * radius * 4.0f); gl::end(); Stats::quads++; gl::disable(GL_BLEND); gl::disable(GL_TEXTURE_2D); gl::depthmask(GL_TRUE); // enable depth buffer writes gl::enable(GL_DEPTH_TEST); } } } void draw_globe_rings(const math::Color & color, const size_t rings_id) { gl::color(color); gl::enable(GL_BLEND); //gl::disable(GL_CULL_FACE); gl::depthmask(GL_FALSE); // disable depth buffer writes Textures::bind(rings_id); gl::begin(gl::Quads); // top gl::normal(0.0f, 0.0f, 1.0f); gl::texcoord(0, 0); gl::vertex(-2.0f, -2.0f, 0.0f); gl::normal(0.0f, 0.0f, 1.0f); gl::texcoord(1, 0); gl::vertex(2.0f, -2.0f, 0.0f); gl::normal(0.0f, 0.0f, 1.0f); gl::texcoord(1, 1); gl::vertex(2.0f, 2.0f, 0.0f); gl::normal(0.0f, 0.0f, 1.0f); gl::texcoord(0, 1); gl::vertex(-2.0f, 2.0f, 0.0f); //bottom gl::normal(0.0f, 0.0f, -1.0f); gl::texcoord(0, 1); gl::vertex(-2.0f, 2.0f, 0.0f); gl::normal(0.0f, 0.0f, -1.0f); gl::texcoord(1, 1); gl::vertex(2.0f, 2.0f, 0.0f); gl::normal(0.0f, 0.0f, -1.0f); gl::texcoord(1, 0); gl::vertex(2.0f, -2.0f, 0.0f); gl::normal(0.0f, 0.0f, -1.0f); gl::texcoord(0, 0); gl::vertex(-2.0f, -2.0f, 0.0f); gl::end(); gl::depthmask(GL_TRUE); // enable depth buffer writes //gl::enable(GL_CULL_FACE); gl::disable(GL_BLEND); Stats::quads += 2; } void draw_pass_globes() { // FIXME is this ever reset ? GLfloat globe_specular[] = { 0.25f, 0.25f, 0.25f, 1.0f }; gl::material(GL_FRONT, GL_SPECULAR, globe_specular); // Globes have to be rendered distance sorted, closest last. // Globes behind farplane are rescaled and repositioned. for (DrawlistGlobes::const_iterator it = drawlist_globes.begin(); it != drawlist_globes.end(); ++it) { const core::EntityGlobe *globe = (*it); math::Vector3f location(globe->location()); float radius = globe->radius(); if (ext_render(globe)->distance() > (core::range::maxdistance - globe->radius())) { // globe is behind the far plane, make a fake size calculation location = _camera->location() + (location - _camera->location()) * ((core::range::maxdistance - globe->radius()) / ext_render(globe)->distance()); radius *= (core::range::maxdistance - globe->radius()) / (ext_render(globe)->distance()); gl::depthmask(GL_FALSE); lightenv_zone.draw(location - globe->location()); } gl::push(); gl::translate(location); if (globe->has_flag(core::Entity::Bright)) { gl::disable(GL_LIGHTING); if (globe->corona_id()) { // draw globe corona // corona is rendered in camera space draw_globe_corona(*_camera, location, globe->color(), radius, globe->corona_id()); } } if (globe->texture_id()) { // textured globe Textures::bind(globe->texture_id()); gl::enable(GL_TEXTURE_2D); } gl::multmatrix(globe->axis()); if (globe->rotationspeed()) { float angle = math::degrees360f(core::application()->time() * globe->rotationspeed()); gl::rotate(angle, math::Vector3f::Zaxis()); } draw_sphere(globe->color(), radius); if (globe->rings_id()) { if (!globe->texture_id()) { gl::enable(GL_TEXTURE_2D); } draw_globe_rings(globe->color(), globe->rings_id()); } gl::pop(); if (globe->texture_id() || globe->rings_id()) { gl::disable(GL_TEXTURE_2D); } if (globe->has_flag(core::Entity::Bright)) { gl::enable(GL_LIGHTING); } if (ext_render(globe)->distance() > (core::range::maxdistance - globe->radius())) { gl::depthmask(GL_TRUE); lightenv_zone.draw(); } } } /* ---- Default entities ------------------------------------------ */ void draw_entity_sphere(const core::Entity* entity) { draw_sphere(entity->color(), entity->radius()); } void draw_entity_cube(const core::Entity* entity) { const float radius = entity->radius(); gl::scale(radius, radius, radius); gl::color(entity->color()); gl::begin(gl::Quads); // top gl::normal(0, 0, 1); gl::vertex(v0); gl::vertex(v1); gl::vertex(v2); gl::vertex(v3); // bottom gl::normal(0, 0, -1); gl::vertex(v7); gl::vertex(v6); gl::vertex(v5); gl::vertex(v4); // sides gl::normal(1, 0, 0); gl::vertex(v1); gl::vertex(v0); gl::vertex(v4); gl::vertex(v5); gl::normal(-1, 0, 0); gl::vertex(v3); gl::vertex(v2); gl::vertex(v6); gl::vertex(v7); gl::normal(0, 1, 0); gl::vertex(v2); gl::vertex(v1); gl::vertex(v5); gl::vertex(v6); gl::normal(0, -1, 0); gl::vertex(v0); gl::vertex(v3); gl::vertex(v7); gl::vertex(v4); gl::end(); } void draw_entity_diamond(const core::Entity* entity) { const float radius = entity->radius() / 2.0f; /* ---- draw axis lines ---- */ gl::color(entity->color_second()); gl::begin(gl::Lines); gl::vertex(1.25f * radius, 0, 0); gl::vertex(2* radius, 0, 0); gl::vertex(0, 1.25f * radius, 0); gl::vertex(0, 2* radius, 0); gl::vertex(0, 0, 1.25f * radius); gl::vertex(0, 0, 2 * radius); gl::vertex(-1.25f * radius, 0, 0); gl::vertex(-2 * radius, 0, 0); gl::vertex(0, -1.25f * radius, 0); gl::vertex(0, -2 * radius, 0); gl::vertex(0, 0, -1.25f * radius); gl::vertex(0, 0, -2 * radius); gl::end(); /* ---- draw rotating body lines ---- */ float angle = (core::application()->time() + ext_render(entity)->fuzz()) * 45.0f; angle = angle - 360.0f * floorf(angle / 360.0f); gl::rotate(angle, math::Vector3f::Zaxis()); if (r_wireframe->value() == 0.0f) { glPolygonMode(GL_FRONT, GL_LINE); } gl::color(entity->color()); gl::begin(gl::TriangleFan); gl::normal(0, 0 , 1); gl::vertex(0, 0, radius); gl::normal(1, 0 , 0); gl::vertex(radius, 0.0f, 0.0f); gl::normal(0, 1, 0); gl::vertex(0.0f, radius, 0.0f); gl::normal(-1, 0 , 0); gl::vertex(-radius, 0.0f, 0.0f); gl::normal(0, -1, 0); gl::vertex(0.0f, -radius, 0.0f); gl::normal(1, 0 , 0); gl::vertex(radius, 0.0f, 0.0f); gl::end(); gl::begin(gl::TriangleFan); gl::normal(0, 0 , -1); gl::vertex(0, 0, -radius); gl::normal(1, 0, 0); gl::vertex(radius, 0.0f, 0.0f); gl::normal(0, -1, 0); gl::vertex(0.0f, -radius, 0.0f); gl::normal(-1, 0 , 0); gl::vertex(-radius, 0.0f, 0.0f); gl::normal(0, 1, 0); gl::vertex(0.0f, radius, 0.0f); gl::normal(1, 0 , 0); gl::vertex(radius, 0.0f, 0.0f); gl::end(); if (r_wireframe->value() == 0.0f) { glPolygonMode(GL_FRONT, GL_FILL); } } void draw_entity_axis(const core::Entity* entity) { float r = entity->radius(); gl::begin(gl::Lines); gl::color(entity->color_second()); gl::vertex(r, 0.0f, 0.0f); gl::color(entity->color()); gl::vertex(-r, 0.0f, 0.0f); gl::vertex(0.0f, r / 2, 0.0f); gl::vertex(0.0f, -r / 2, 0.0f); gl::vertex(0.0f, 0.0f, r); gl::vertex(0.0f, 0.0f, -r); gl::end(); } void draw_pass_default() { for (DrawlistEntities::const_iterator it = drawlist_entities.begin(); it != drawlist_entities.end(); ++it) { const core::Entity *entity = (*it); if (!entity->model() && ext_render(entity)->visible()) { gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->axis()); if (entity->has_flag(core::Entity::Bright)) { gl::disable(GL_LIGHTING); } switch (entity->shape()) { case core::Entity::Sphere: draw_entity_sphere(entity); break; case core::Entity::Diamond: draw_entity_diamond(entity); break; case core::Entity::Axis: draw_entity_axis(entity); break; case core::Entity::Cube: default: draw_entity_cube(entity); break; } if (entity->has_flag(core::Entity::Bright)) { gl::enable(GL_LIGHTING); } gl::pop(); } } } /* ---- Model Fragments -------------------------------------------- */ void draw_fragment_normals(const model::Fragment *fragment, bool draw_details) { size_t index = fragment->index(); size_t vertex_count = fragment->structural_size(); if (draw_details) vertex_count += fragment->detail_size(); gl::begin(gl::Lines); const float s = 0.05f; for (size_t i = 0; i < vertex_count; i++) { const float *n = &core::game()->vertexarray()->ptr()[(index+i) * 8 + 2]; const float *v = &core::game()->vertexarray()->ptr()[(index+i) * 8 + 5]; //gl::normal(-n[0], -n[1], -n[2]); gl::vertex(v[0], v[1], v[2]); //gl::normal(n[0], n[1], n[2]); gl::vertex(v[0] + n[0] * s, v[1] + n[1] * s, v[2] + n[2] * s); } gl::end(); } void draw_fragment(const model::Fragment *fragment, bool draw_details) { size_t index = fragment->index(); size_t vertex_count = fragment->structural_size(); if (draw_details) vertex_count += fragment->detail_size(); switch (fragment->type()) { case model::Fragment::Triangles: glDrawArrays(gl::Triangles, index, vertex_count); Stats::tris += vertex_count / 3; break; case model::Fragment::Quads: glDrawArrays(gl::Quads, index, vertex_count); Stats::quads += vertex_count / 4; break; } if (r_normals->value()) { gl::begin(gl::Lines); gl::color(1.0f, 0.0f, 0.0f); const float s = 0.01f; for (size_t i = 0; i < vertex_count; i++) { const float *n = &core::game()->vertexarray()->ptr()[(index+i) * 8 + 2]; const float *v = &core::game()->vertexarray()->ptr()[(index+i) * 8 + 5]; gl::normal(-n[0], -n[1], -n[2]); gl::vertex(v[0], v[1], v[2]); gl::normal(n[0], n[1], n[2]); gl::vertex(v[0] + n[0] * s, v[1] + n[1] * s, v[2] + n[2] * s); } gl::end(); } Stats::fragments++; } void draw_model_fragments(model::Model *model, const math::Color & color_primary, const math::Color & color_secondary, const float enginetime, const bool detail, const bool power, const float thrust) { State::set_color(color_primary); State::set_color_second(color_secondary); State::set_color_engine(model->enginecolor() * thrust); State::set_power(power); for (model::Model::Groups::const_iterator git = model->groups().begin(); git != model->groups().end(); git++) { const model::FragmentGroup *group = (*git); gl::push(); gl::translate(group->location()); gl::multmatrix(group->axis()); const float s = group->scale(); if (s) gl::scale(s, s, s); if (group->type() == model::FragmentGroup::Rotate) { const float rotation_angle = math::degrees360f((group->engine() ? enginetime : core::game()->time()) * group->speed()); gl::rotate(-rotation_angle, group->movement()); } else if (group->type() == model::FragmentGroup::Move ) { const float freq = group->speed() / group->distance(); math::Vector3f translation(group->movement() * group->distance()); translation *= sinf((group->engine() ? enginetime : core::game()->time()) * M_PI * freq) * 0.5f + 0.5f; gl::translate(translation); } for (model::FragmentGroup::Fragments::const_iterator fit = group->fragments().begin(); fit != group->fragments().end(); fit++) { const model::Fragment *fragment = (*fit); for (model::Material::Layers::const_iterator lit = fragment->material()->layers().begin(); lit != fragment->material()->layers().end(); ++lit) { State::use_material_layer(fragment->material(), (*lit)); draw_fragment(fragment, detail); State::reset(); } if (r_normals->value()) { // force reset of material settings for the next fragment State::reset(); gl::color(0.75f, 0.0f, 0.0f); draw_fragment_normals(fragment, detail); State::reset(); } } gl::pop(); } } // draw entity axis void draw_model_axis(const core::Entity *entity) { // axis const float r = entity->model()->radius() * 1.5f; gl::begin(gl::Lines); gl::color(1.0f, 1.0f, 1.0f, 1.0f); gl::vertex(-r, 0.0f, 0.0f); gl::vertex(r, 0.0f, 0.0f); gl::vertex(0.0f, -r, 0.0f); gl::vertex(0.0f, r, 0.0f); gl::vertex(0.0f, 0.0f, -r); gl::vertex(0.0f, 0.0f, r); /* const math::Axis worldaxis(entity->axis().transpose()); gl::color(1.0f, 0.0f, 0.0f); gl::vertex(worldaxis.forward() * -1 * r); gl::vertex(worldaxis.forward() * r); gl::vertex(worldaxis.left() * -1 * r); gl::vertex(worldaxis.left() * r); gl::vertex(worldaxis.up() * -1 * r); gl::vertex(worldaxis.up() * r); */ gl::end(); } void draw_pass_model_fragments() { /* * FIXME * fragmentgroups with movement tied to engine activity can not be synchronized with server-side collision * * For moving and rotating fragmentgroups, the enginetime must match * the time used by core:: to calculate the rotation or movement * of the asssociated collision models. * Currently, enginetime is client-side only, and there is no way * to sync core and render movement of fragmentgroups without sacrificing * the functionality of the FragmentGroup::engine() flag. */ for (DrawlistEntities::iterator it = drawlist_entities.begin(); it != drawlist_entities.end(); ++it) { core::Entity *entity = (*it); if (entity->model() && ext_render(entity)->visible()) { gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->axis()); const float modelscale = entity->radius() / entity->model()->radius(); gl::scale(modelscale, modelscale, modelscale); draw_model_fragments( entity->model(), entity->color(), entity->color_second(), ext_render(entity)->enginetime(), ext_render(entity)->detailvisible(), ext_render(entity)->power(), ext_render(entity)->thrust() ); if (r_bbox->value()) { gl::disable(GL_DEPTH_TEST); gl::color(entity->color().r * 0.5f, entity->color().g * 0.5f, entity->color().b * 0.5f, 0.25f); draw_box(entity->model()->box().min(), entity->model()->box().max()); gl::enable(GL_DEPTH_TEST); gl::color(entity->color()); draw_box(entity->model()->box().min(), entity->model()->box().max()); } if (r_axis->value()) { draw_model_axis(entity); } if (ext_render(entity)->detailvisible()) { if ((entity->type() == core::Entity::Controlable) && (r_indicators->value())) { draw_inidicators(static_cast(entity)); } if (entity->slots() && r_slots && r_slots->value()) { gl::pop(); gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->axis()); // not scaled through modelscale draw_slots(entity); } } gl::pop(); } } } /* ---- Model FX --------------------------------------------------- */ void draw_model_lights(model::Model *model, const float scale, const math::Vector3f & entity_location, const math::Axis & entity_axis, const math::Color & entity_color, const math::Color & entity_color_second, const float thrust, const float fuzz) { float t = 0.0f; float a = 0.0f; float light_size = 0.0f; math::Vector3f location; math::Vector3f offset; math::Color color; math::Axis flare_axis; int nbquads = 0; // disable culling by default gl::disable(GL_CULL_FACE); model::Cull current_cull = model::CullNone; size_t current_texture = Textures::bind("textures/fx/flare00"); gl::begin(gl::Quads); // draw model lights for (model::Model::Lights::iterator lit = model->lights().begin(); lit != model->lights().end(); ++lit) { model::Light *light = (*lit); // engine activated lights if (light->engine()) { if (thrust < 0.001f) { continue; // next light } } // strobe frequency if (light->strobe()) { t = (core::application()->time() + fuzz - light->offset()) * light->frequency(); if ((t - floorf(t)) > light->time()) { continue; // next light } } // entity color overrides light color // light color overrides engine color if (light->entity()) { if (light->entity_second()) { // entity tertiary color for (size_t i = 0; i < 3; i++) { color[i] = (entity_color[i] + entity_color_second[i]) / 2; } } else { // entity primary color color.assign(entity_color); } } else if (light->entity_second()) { // entity secondary color color.assign(entity_color_second); } else if (light->has_color()) { // light color color.assign(light->color()); } else if (light->engine()) { // FIXME engine_color or thrust_activated color.assign(model->enginecolor()); } else { color.assign(light->color()); } // default alpha is 0.8, engine flag alters alpha a = 0.8f; if (light->engine()) { a *= thrust; } color.a = a; location.assign(entity_location + (entity_axis * light->location()) * scale); light_size = light->radius() * scale; // track OpenGL state changes if (current_texture != light->texture()) { gl::end(); current_texture = Textures::bind(light->texture()); gl::begin(gl::Quads); } // draw the quad // FIXME add random per-light rotation gl::color(color); gl::texcoord(1, 0); gl::vertex(location + (_camera->axis().up() - _camera->axis().left()) * light_size); gl::texcoord(0, 0); gl::vertex(location + (_camera->axis().up() + _camera->axis().left()) * light_size); gl::texcoord(0, 1); gl::vertex(location + (_camera->axis().up() * -1 + _camera->axis().left()) * light_size); gl::texcoord(1, 1); gl::vertex(location + (_camera->axis().up() * -1 - _camera->axis().left()) * light_size); nbquads++; } // draw flares for (model::Model::Flares::iterator flit = model->flares().begin(); flit != model->flares().end(); ++flit) { model::Flare *flare = (*flit); // engine activated flares if (flare->engine()) { if (thrust < 0.001f) { continue; // next flare } } // strobe frequency if (flare->strobe()) { t = (core::application()->time() + fuzz - flare->offset()) * flare->frequency(); if ((t - floorf(t)) > flare->time()) { continue; // next flare } } // calulcate viewing angle factor flare_axis.assign(entity_axis * flare->axis()); a = math::absf(dotproduct(flare_axis.forward(), _camera->axis().forward())); if (a < 0.001f) { continue; // next flare } // entity color overrides flare color // flare color overrides engine color if (flare->entity()) { if (flare->entity_second()) { // entity tertiary color for (size_t i = 0; i < 3; i++) { color[i] = (entity_color[i] + entity_color_second[i]) / 2; } } else { // entity primary color color.assign(entity_color); } } else if (flare->entity_second()) { // entity secondary color color.assign(entity_color_second); } else if (flare->has_color()) { // flare color color.assign(flare->color()); } else if (flare->engine()) { // FIXME engine_color or thrust_activated color.assign(model->enginecolor()); } else { color.assign(flare->color()); } // default alpha is 0.8, engine flag alters alpha // alpha decreases with viewing angle a *= 0.8f; if (flare->engine()) { a *= thrust; } color.a = a; location.assign(entity_location + (entity_axis * flare->location()) * scale); light_size = flare->radius() * scale; // track OpenGL state changes if ((current_cull != flare->cull()) || (current_texture != flare->texture())) { gl::end(); if (current_texture != flare->texture()) { current_texture = Textures::bind(flare->texture()); } if (current_cull != flare->cull()) { if (flare->cull() == model::CullNone) { gl::disable(GL_CULL_FACE); current_cull = model::CullNone; } else { if (current_cull == model::CullNone) { gl::enable(GL_CULL_FACE); } if (flare->cull() == model::CullBack) { gl::cullface(GL_BACK); current_cull = model::CullBack; } else { gl::cullface(GL_FRONT); current_cull = model::CullFront; } } } gl::begin(gl::Quads); } // draw the quad gl::color(color); gl::texcoord(1, 0); gl::vertex(location + (flare_axis.up() + flare_axis.left()) * light_size); gl::texcoord(0, 0); gl::vertex(location + (flare_axis.up() - flare_axis.left()) * light_size); gl::texcoord(0, 1); gl::vertex(location + (flare_axis.up() * -1 - flare_axis.left()) * light_size); gl::texcoord(1, 1); gl::vertex(location + (flare_axis.up() * -1 + flare_axis.left()) * light_size); nbquads++; } gl::end(); gl::cullface(GL_BACK); gl::enable(GL_CULL_FACE); Stats::quads += nbquads; } void draw_pass_model_fx(float elapsed) { // enable texturing gl::enable(GL_TEXTURE_2D); for (DrawlistEntities::iterator it = drawlist_entities.begin(); it != drawlist_entities.end(); ++it) { core::Entity *entity = (*it); if (entity->model() && ext_render(entity)->detailvisible()) { // draw lights and flares if (ext_render(entity)->power() && draw_lights) { const float modelscale = entity->radius() / entity->model()->radius(); draw_model_lights(entity->model(), modelscale, entity->location(), entity->axis(), entity->color(), entity->color_second(), ext_render(entity)->thrust(), ext_render(entity)->fuzz() ); } // draw particle systems if (draw_particles && ext_render(entity)->particles().size()) { for (RenderExt::ParticleSystems::iterator it = ext_render(entity)->particles().begin(); it != ext_render(entity)->particles().end(); ++it) { (*it)->draw(elapsed, *_camera); } } } } // restore the default depth buffer comparison function gl::depthfunc(GL_LEQUAL); gl::disable(GL_TEXTURE_2D); gl::cullface(GL_BACK); gl::enable(GL_CULL_FACE); } void draw_pass_model_radius() { for (DrawlistEntities::iterator it = drawlist_entities.begin(); it != drawlist_entities.end(); ++it) { core::Entity *entity = (*it); if (entity->model() && ext_render(entity)->visible()) { gl::push(); gl::translate(entity->location()); math::Color color = entity->color(); color.a = 0.25f; draw_sphere(color, entity->radius()); gl::pop(); } } } void draw_pass_spacegrid() { if (!(r_grid && r_grid->value())) return; int gridsize = 32; float s = 1.0f / gridsize; float z = -4.0f; float dx = _camera->target_location().x() - floorf(_camera->target_location().x()); float dy = _camera->target_location().y() - floorf(_camera->target_location().y()); gl::push(); gl::translate(_camera->target_location()); gl::color(0, 0, 1.0f); gl::normal(0, 0, 1.0f); gl::begin(gl::Lines); for (int i = -gridsize; i <= gridsize; i++) { gl::color(0, 0, 0, 0); gl::vertex(i - dx, -gridsize - dy, z); gl::color(0, 0, (gridsize - abs(i))*s, (gridsize - abs(i))*s); gl::vertex(i - dx, -dy, z); gl::vertex(i - dx, -dy , z); gl::color(0, 0, 0, 0); gl::vertex(i - dx, gridsize - dy, z); gl::vertex(-gridsize - dx, i - dy, z); gl::color(0, 0, (gridsize - abs(i))*s, (gridsize - abs(i))*s); gl::vertex(-dx, i - dy, z); gl::vertex(-dx, i - dy, z); gl::color(0, 0, 0, 0); gl::vertex(gridsize - dx, i - dy, z); } gl::end(); gl::pop(); } /* ----- Main draw routine ----------------------------------------- */ void draw(float seconds) { zone = core::localplayer()->zone(); // calculate client state pass_prepare(seconds); gl::disable(GL_DEPTH_TEST); // disable depth testing gl::depthmask(GL_FALSE); // disable depth buffer writing gl::polygonmode(GL_FRONT, GL_FILL); draw_pass_sky(); // draw the skybox gl::depthmask(GL_TRUE); // enable writing to the depth buffer gl::depthfunc(GL_LEQUAL); // default depth buffer comparison function gl::enable(GL_DEPTH_TEST); // enable depth testing gl::enable(GL_CULL_FACE); // enable culling gl::enable(GL_COLOR_MATERIAL); // enable color tracking // enable wireframe mode if requested if (r_wireframe && r_wireframe->value()) { glPolygonMode(GL_FRONT, GL_LINE); } // check if the vertexarray needs re-uploading // set vertex array pointers if (State::has_vbo()) { gl::bindbuffer(GL_ARRAY_BUFFER, State::vbo()); if (core::game()->vertexarray()->dirty()) { gl::bufferdata(GL_ARRAY_BUFFER, sizeof(float) * core::game()->vertexarray()->index(), core::game()->vertexarray()->ptr(), GL_STATIC_DRAW); core::game()->vertexarray()->set_dirty(false); } // Interleaved format is GL_T2F_N3F_V3F // void glTexCoordPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr ) gl::texcoordpointer(2, GL_FLOAT, 8 * sizeof(GLfloat), 0); // void glNormalPointer(GLenum type, GLsizei stride, const GLvoid *ptr) gl::normalpointer(GL_FLOAT, 8 * sizeof(GLfloat), (void*) (2 * sizeof(GLfloat))); // void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) gl::vertexpointer(3, GL_FLOAT, 8 * sizeof(GLfloat), (void*) (5 * sizeof(GLfloat))); } else { gl::interleavedarrays(GL_T2F_N3F_V3F, 0, core::game()->vertexarray()->ptr()); } // enable vertex arrays gl::enableclientstate(GL_TEXTURE_COORD_ARRAY); gl::enableclientstate(GL_NORMAL_ARRAY); gl::enableclientstate(GL_VERTEX_ARRAY); State::set_normalize(true); gl::enable(GL_LIGHTING); // enable lighting draw_pass_globes(); // draw globes draw_pass_default(); // draw entities without model draw_pass_model_fragments(); gl::disable(GL_LIGHTING); // disable lighting State::set_normalize(false); gl::enable(GL_BLEND); gl::depthmask(GL_FALSE); // disable depth buffer writing draw_pass_spacegrid(); // draw the blue spacegrid if (!core::localplayer()->view()) { math::Color dust_color(core::localplayer()->zone()->ambient_color()); float s = math::max(math::max(dust_color[0], dust_color[1]), dust_color[2]); dust_color *= 0.8f / s; Dust::draw(*_camera, dust_color); // draw spacedust } // draw entity lights, flares and particles if (draw_lights || draw_particles) { draw_pass_model_fx(seconds); } // draw entity radius globe if (r_radius && r_radius->value()) { if (r_normalize && r_normalize->value()) { // enable full normalization gl::enable(GL_NORMALIZE); } else { // enable rescaling of normals gl::enable(GL_RESCALE_NORMAL); } gl::enable(GL_LIGHTING); draw_pass_model_radius(); gl::disable(GL_LIGHTING); if (r_normalize && r_normalize->value()) { // disable full normalization gl::disable(GL_NORMALIZE); } else { // disable resaling of normals gl::disable(GL_RESCALE_NORMAL); } } gl::disableclientstate(GL_VERTEX_ARRAY); gl::disableclientstate(GL_NORMAL_ARRAY); gl::disableclientstate(GL_TEXTURE_COORD_ARRAY); // draw physics if (r_physics && r_physics->value()) { if (zone->physics()) { if (!zone->physics()->getDebugDrawer()) zone->physics()->setDebugDrawer(&bullet_debugdrawer); // draw physics bodies in red gl::color(1.0f, 0.0f, 0.0f, 1.0f); gl::begin(gl::Lines); zone->physics()->debugDrawWorld(); gl::end(); } } gl::depthmask(GL_TRUE); // enable depth buffer writing gl::disable(GL_DEPTH_TEST); // disable depth buffer testing gl::disable(GL_CULL_FACE); // disable culling // GL_BLEND and GL_COLOR_MATERIAL must be enabled for the GUI //gl::disable(GL_COLOR_MATERIAL); // disable color tracking // clear current zone light environment lightenv_zone.disable(); lightenv_zone.clear(); } /* ---- Model 3D Direction indicators ------------------------------ */ void draw_inidicators(const core::EntityControlable *entity) { if (entity->thrust() == 0.0f) { return; } math::Color color_reputation(0.0f, 1.0f, 0.0f); if (entity != core::localcontrol()) { // reputation color const float reputation = core::localplayer()->reputation(entity->faction()); if (reputation >= core::range::reputation_friendly) { color_reputation.assign(0.0f, 1.0f, 0.0f); // green } else if (reputation <= core::range::reputation_hostile) { color_reputation.assign(1.0f, 0.0f, 0.0f); // red } else { color_reputation.assign(1.0f, 1.0f, 1.0f); // white } } // these are in model-space coordinates const float r = entity->model()->radius(); // model radius const float l = r * 1.8f * entity->thrust(); // thrust indicator lenght const float b = r * 0.1f; // direction box size math::Vector3f minbox(r * 1.1f, 0.0f, 0.0f); math::Vector3f maxbox(minbox.x() + l, 0.0f, 0.0f); for (int pass = 0; pass < 2; ++pass) { if (pass == 0) { // pass 1 - background with GL_DEPTH_TEST disabled gl::disable(GL_DEPTH_TEST); color_reputation.a = 0.5f; } else { // pass 2 - highlight with GL_DEPTH_TEST enabled gl::enable(GL_DEPTH_TEST); color_reputation.a = 1.0f; } gl::color(color_reputation); gl::begin(gl::Lines); gl::vertex(maxbox); gl::vertex(minbox); gl::end(); } minbox.assign(1.2f * r + l, -b * 0.5f, -b * 0.5f); maxbox.assign(1.2f * r + l + b, b * 0.5f, b * 0.5f); for (int pass = 0; pass < 2; ++pass) { if (pass == 0) { // pass 1 - background with GL_DEPTH_TEST disabled gl::disable(GL_DEPTH_TEST); color_reputation.a = 0.5f; } else { // pass 2 - highlight with GL_DEPTH_TEST enabled gl::enable(GL_DEPTH_TEST); color_reputation.a = 1.0f; } gl::color(color_reputation); draw_box(minbox, maxbox); } //gl::enable(GL_DEPTH_TEST); } /* ---- Model weapon slots and docking ports ----------------------- */ void draw_slots(const core::Entity *entity) { if (entity->slots()) { const float r = 0.025f; // draw slots: pass 1 - background with GL_DEPTH_TEST disabled gl::disable(GL_DEPTH_TEST); for(core::Slots::iterator it = entity->slots()->begin(); it != entity->slots()->end(); ++it) { core::Slot *slot = (*it); switch (slot->type()) { case model::Slot::Cannon: case model::Slot::Turret: // red for weapons gl::color(0.5f, 0.0f, 0.0f, 0.25f); break; case model::Slot::Dock: // cyan for docks gl::color(0.0f, 0.5f, 0.5f, 0.25f); break; case model::Slot::None: // gray for unkown gl::color(0.25f, 0.25f, 0.25f, 0.25f); break; } math::Vector3f maxbox(slot->location()); math::Vector3f minbox(slot->location()); for (size_t i = 0; i < 3; i++) { maxbox[i] += r; minbox[i] -= r; } draw_box(minbox, maxbox); } // draw slots: pass 2 - highlight with GL_DEPTH_TEST enabled gl::enable(GL_DEPTH_TEST); gl::color(1.0f, 0.0f, 0.0f); for(core::Slots::iterator it = entity->slots()->begin(); it != entity->slots()->end(); ++it) { core::Slot *slot = (*it); switch (slot->type()) { case model::Slot::Cannon: case model::Slot::Turret: // red for weapons gl::color(1.0f, 0.0f, 0.0f, 1.0f); break; case model::Slot::Dock: // cyan for docks gl::color(0.0f, 1.0f, 1.0f, 1.0f); break; case model::Slot::None: // gray for unkown gl::color(0.5f, 0.5f, 0.5f, 1.0f); break; } math::Vector3f maxbox(slot->location()); math::Vector3f minbox(slot->location()); for (size_t i = 0; i < 3; i++) { maxbox[i] += r; minbox[i] -= r; } draw_box(minbox, maxbox); } } //gl::enable(GL_DEPTH_TEST); } }