/* 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/gameinterface.h" #include "core/range.h" #include "model/fragment.h" #include "model/material.h" #include "model/model.h" #include "render/render.h" #include "render/textures.h" #include "render/debugdrawer.h" #include "render/draw.h" #include "render/dust.h" #include "render/gl.h" #include "math/functions.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; math::Vector3f zone_light_location; // location of the zone light math::Color zone_light_color; // color of the zone light bool has_zone_light = false; bool draw_particles = true; bool draw_lights = true; size_t max_lights = 8; float ambient_light_intensity = 0.1f; float diffuse_light_intensity = 0.75f; float specular_light_intensity = 0.75f; GLenum zone_gllight = GL_LIGHT0; typedef std::map Globes; Globes globes_list; /* ---- Prepare the renderer state --------------------------------- */ void pass_reset_lights() { // reset light state has_zone_light = false; zone_light_color.assign(1.0); for (size_t i = 0; i < max_lights; i++) gl::disable(GL_LIGHT0 + i); } // setup an OpenGL light int add_light(const math::Vector3f & location, float attenuation, const math::Color & color) { int gllight; // check if a light is available for (size_t i = 0; i < max_lights; i++) { if (!glIsEnabled(GL_LIGHT0 + i)) { gllight = GL_LIGHT0 + i; break; } else { gllight = GL_LIGHT0; return gllight; } } // set up color and location GLfloat gllight_location[4]; GLfloat diffuse_light[4]; GLfloat ambient_light[4]; GLfloat specular_light[4]; for (size_t i = 0; i < 3; i++) { gllight_location[i] = location[i]; ambient_light[i] = color[i] * ambient_light_intensity; diffuse_light[i] = color[i] * diffuse_light_intensity; specular_light[i] = color[i] * specular_light_intensity; } gllight_location[3] = 1.0f; ambient_light[3] = 1.0f; diffuse_light[3] = 1.0f; specular_light[3] = 1.0f; // set up the light // we use a simple averaging of the color times the specified attenuation value(such as 0.00025f). // FIXME: use a more standardized and realistic way of doing this. glLightf(gllight, GL_LINEAR_ATTENUATION, ((color[0] + color[1] + color[2]) / 3.0f) * attenuation); glLightfv(gllight, GL_POSITION, gllight_location); glLightfv(gllight, GL_AMBIENT, ambient_light); glLightfv(gllight, GL_DIFFUSE, diffuse_light); glLightfv(gllight, GL_SPECULAR, specular_light); return gllight; } void pass_prepare(float seconds) { using namespace model; // 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 pass_reset_lights(); // clear current list of globes globes_list.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; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, zone_ambient); for (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); it++) { core::Entity *entity = (*it); if (!ext_render(entity)) { new RenderExt(entity); } entity->extension((size_t) core::Extension::Render)->frame(seconds); // globes if (entity->type() == core::Entity::Globe) { core::EntityGlobe *globe = static_cast(entity); // add the globe to the globes list if (globe->visible() && !ext_render(globe)->behind()) { globes_list[ext_render(globe)->distance()] = globe; } // add zone lights if (globe->flag_is_set(core::Entity::Bright)) { for (size_t i = 0; i < 3; i++) { zone_light_location[i] = globe->location()[i]; zone_light_color[i] = globe->color()[i]; } GLenum zone_gllight = add_light(zone_light_location, 0.00025f, globe->color()); gl::enable(zone_gllight); has_zone_light = true; } } else { // this reloads entity models if required by r_restart if (!entity->model() && entity->modelname().size()) { entity->set_model(model::Model::load(entity->modelname())); } } } } /* ----- 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 { Textures::load_cubemap("textures/sky/default"); } gl::enable(GL_TEXTURE_CUBE_MAP); gl::push(); gl::translate(Camera::eye()); gl::color(1.0f, 1.0f, 1.0f, 1.0f); const float r = 128.0f; 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::disable(GL_TEXTURE_CUBE_MAP); gl::pop(); Stats::quads += 6; } /* ---- 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 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::eye(); v.normalize(); float a = dotproduct(v, Camera::axis().forward()); if (a > 0.1f) { gl::enable(GL_BLEND); gl::disable(GL_DEPTH_TEST); gl::enable(GL_TEXTURE_2D); Textures::bind(corona_id); math::Color drawcolor(color); drawcolor.a = a - 0.1f; gl::color(drawcolor); gl::begin(gl::Quads); glTexCoord2f(0, 1); gl::vertex((Camera::axis().up() - Camera::axis().left()) * radius * 4.0f); glTexCoord2f(0, 0); gl::vertex((Camera::axis().up() + Camera::axis().left()) * radius * 4.0f); glTexCoord2f(1, 0); gl::vertex((Camera::axis().up() * -1 + Camera::axis().left()) * radius * 4.0f); glTexCoord2f(1, 1); gl::vertex((Camera::axis().up() * -1 - Camera::axis().left()) * radius * 4.0f); gl::end(); Stats::quads++; gl::disable(GL_TEXTURE_2D); gl::enable(GL_DEPTH_TEST); gl::disable(GL_BLEND); } } } void draw_pass_globes() { // FIXME is this ever reset ? GLfloat globe_specular[] = { 0.25f, 0.25f, 0.25f, 1.0f }; glMaterialfv(GL_FRONT, GL_SPECULAR, globe_specular); // Globes have to be rendered distance sorted, closest last. // Globes behind farplane are rescaled and repositioned. for (Globes::reverse_iterator rit = globes_list.rbegin(); rit != globes_list.rend(); rit++) { const core::EntityGlobe *globe = (*rit).second; math::Vector3f location(globe->location()); float radius = globe->radius(); if (ext_render(globe)->distance() > (FARPLANE - globe->radius())) { // globe is behind the far plane, make a fake size calculation location = Camera::eye() + (location - Camera::eye()) * ((FARPLANE - globe->radius()) / ext_render(globe)->distance()); radius *= (FARPLANE - globe->radius()) / (ext_render(globe)->distance()); gl::depthmask(GL_FALSE); if (has_zone_light) { // nudge zone light // FIXME doesn't work correctly with multiple zone lights float fake_light_location[4]; for (size_t i = 0; i < 3; i++) { fake_light_location[i] = zone_light_location[i] + location[i] - globe->location()[i]; } fake_light_location[3] = 1.0f; glLightfv(zone_gllight, GL_POSITION, fake_light_location); } } gl::push(); gl::translate(location); if (globe->flag_is_set(core::Entity::Bright)) { gl::disable(GL_LIGHTING); if (globe->corona_id()) { // draw globe corona // corona is rendered in camera space draw_globe_corona(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); gl::pop(); if (globe->texture_id()) { gl::disable(GL_TEXTURE_2D); } if (globe->flag_is_set(core::Entity::Bright)) { gl::enable(GL_LIGHTING); } if (ext_render(globe)->distance() > (FARPLANE - globe->radius())) { gl::depthmask(GL_TRUE); if (has_zone_light) { // restore zone light glLightfv(zone_gllight, GL_POSITION, zone_light_location.ptr()); } } } } /* ---- 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 (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); ++it) { core::Entity *entity = (*it); if (!entity->model() && (entity->type() != core::Entity::Globe) && !entity->serverside() && !ext_render(entity)->behind()) { gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->axis()); if (entity->flag_is_set(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->flag_is_set(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); State::use_material(fragment->material()); draw_fragment(fragment, detail); 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); } } gl::pop(); } State::reset(); } // draw bounding box void draw_model_bbox(model::Model *model) { // top gl::begin(gl::LineLoop); gl::vertex(model->box().max().x(), model->box().max().y(), model->box().max().z()); gl::vertex(model->box().min().x(), model->box().max().y(), model->box().max().z()); gl::vertex(model->box().min().x(), model->box().min().y(), model->box().max().z()); gl::vertex(model->box().max().x(), model->box().min().y(), model->box().max().z()); gl::end(); // bottom gl::begin(gl::LineLoop); gl::vertex(model->box().max().x(), model->box().max().y(), model->box().min().z()); gl::vertex(model->box().min().x(), model->box().max().y(), model->box().min().z()); gl::vertex(model->box().min().x(), model->box().min().y(), model->box().min().z()); gl::vertex(model->box().max().x(), model->box().min().y(), model->box().min().z()); gl::end(); // body gl::begin(gl::Lines); gl::vertex(model->box().max().x(), model->box().max().y(), model->box().max().z()); gl::vertex(model->box().max().x(), model->box().max().y(), model->box().min().z()); gl::vertex(model->box().min().x(), model->box().max().y(), model->box().max().z()); gl::vertex(model->box().min().x(), model->box().max().y(), model->box().min().z()); gl::vertex(model->box().min().x(), model->box().min().y(), model->box().max().z()); gl::vertex(model->box().min().x(), model->box().min().y(), model->box().min().z()); gl::vertex(model->box().max().x(), model->box().min().y(), model->box().max().z()); gl::vertex(model->box().max().x(), model->box().min().y(), model->box().min().z()); gl::end(); } // 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(entity->color()); 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 * 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 (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); it++) { core::Entity *entity = (*it); if (entity->model() && ext_render(entity)->visible() && !ext_render(entity)->behind()) { 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::color(entity->color()); draw_model_bbox(entity->model()); } if (r_axis->value()) { draw_model_axis(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 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; // 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()) { color.assign(entity_color); } else if (light->has_color()) { color.assign(light->color()); } else if (light->engine()) { 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 gl::color(color); glTexCoord2f(1, 0); gl::vertex(location + (Camera::axis().up() - Camera::axis().left()) * light_size); glTexCoord2f(0, 0); gl::vertex(location + (Camera::axis().up() + Camera::axis().left()) * light_size); glTexCoord2f(0, 1); gl::vertex(location + (Camera::axis().up() * -1 + Camera::axis().left()) * light_size); glTexCoord2f(1, 1); gl::vertex(location + (Camera::axis().up() * -1 - Camera::axis().left()) * light_size); Stats::quads++; } // 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 light color // light color overrides engine color if (flare->entity()) { color.assign(entity_color); } else if (flare->has_color()) { color.assign(flare->color()); } else if (flare->engine()) { 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); glTexCoord2f(1, 0); gl::vertex(location + (flare_axis.up() + flare_axis.left()) * light_size); glTexCoord2f(0, 0); gl::vertex(location + (flare_axis.up() - flare_axis.left()) * light_size); glTexCoord2f(0, 1); gl::vertex(location + (flare_axis.up() * -1 - flare_axis.left()) * light_size); glTexCoord2f(1, 1); gl::vertex(location + (flare_axis.up() * -1 + flare_axis.left()) * light_size); Stats::quads++; } gl::end(); gl::cullface(GL_BACK); gl::enable(GL_CULL_FACE); } void draw_pass_model_fx(float elapsed) { // enable texturing gl::enable(GL_TEXTURE_2D); for (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); it++) { core::Entity *entity = (*it); if (entity->model() && ext_render(entity)->detailvisible() && ext_render(entity)->power()) { // draw lights and flares if (draw_lights) { const float modelscale = entity->radius() / entity->model()->radius(); draw_model_lights(entity->model(), modelscale, entity->location(), entity->axis(), entity->color(), ext_render(entity)->thrust(), ext_render(entity)->fuzz() ); } // draw particle systems if (draw_particles && ext_render(entity)->particles().size()) { gl::disable(GL_CULL_FACE); model::Cull current_cull = model::CullNone; for (RenderExt::ParticleSystems::iterator it = ext_render(entity)->particles().begin(); it != ext_render(entity)->particles().end(); it++) { ParticleSystem *particlesystem = (*it); if (current_cull != particlesystem->cull()) { if (particlesystem->cull() == model::CullNone) { gl::disable(GL_CULL_FACE); current_cull = model::CullNone; } else { if (current_cull == model::CullNone) { gl::enable(GL_CULL_FACE); } if (particlesystem->cull() == model::CullBack) { gl::cullface(GL_BACK); current_cull = model::CullBack; } else { gl::cullface(GL_FRONT); current_cull = model::CullFront; } } } particlesystem->draw(elapsed); } } } } // restore the default depth buffer comparison function gl::depthfunc(GL_LESS); gl::disable(GL_TEXTURE_2D); gl::cullface(GL_BACK); gl::enable(GL_CULL_FACE); } void draw_pass_model_radius() { for (core::Zone::Content::iterator it = zone->content().begin(); it != zone->content().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().x() - floorf(Camera::target().x()); float dy = Camera::target().y() - floorf(Camera::target().y()); gl::push(); gl::translate(Camera::target()); 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 glPolygonMode(GL_FRONT, GL_FILL); draw_pass_sky(); // draw the skybox gl::depthmask(GL_TRUE); // enable writing to the depth buffer gl::depthfunc(GL_LESS); // 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 ) glTexCoordPointer(2, GL_FLOAT, 8 * sizeof(GLfloat), 0); // void glNormalPointer(GLenum type, GLsizei stride, const GLvoid *ptr) glNormalPointer(GL_FLOAT, 8 * sizeof(GLfloat), (void*) (2 * sizeof(GLfloat))); // void glVertexPointer(GLint size, GLenum type, GLsizei stride, const GLvoid *ptr) glVertexPointer(3, GL_FLOAT, 8 * sizeof(GLfloat), (void*) (5 * sizeof(GLfloat))); } else { glInterleavedArrays(GL_T2F_N3F_V3F, 0, core::game()->vertexarray()->ptr()); } // enable vertex arrays glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(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()) { Dust::draw(zone_light_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); } } glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(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 // reset light state pass_reset_lights(); } // draw HUD target world space geometry, like dock indicators void draw_target(const core::Entity *entity) { model::Model *model = entity->model(); if (!model) return; if (!model->docks().size()) return; float d = math::distance(core::localcontrol()->location(), entity->location()) - entity->radius() - core::localcontrol()->radius(); if (d > core::range::fxdistance) return; const float modelscale = entity->radius() / entity->model()->radius(); gl::enable(GL_DEPTH_TEST); gl::push(); gl::translate(entity->location()); gl::multmatrix(entity->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() * modelscale); math::Vector3f minbox(dock->location() * modelscale); 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); } }