/* filesystem/map.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include "filesystem/filesystem.h" #include "math/mathlib.h" #include "model/engine.h" #include "model/light.h" #include "model/map.h" #include "model/material.h" #include "model/model.h" #include "model/vertexarray.h" #include "sys/sys.h" #include #include namespace model { // function to test spawnflags inline bool spawnflag_isset(unsigned int spawnflags, unsigned int flag) { return ((spawnflags & flag) == flag); } // max geometry bounds const float MAX_BOUNDS = 16384; // scaling factor when loading .map geometry const float SCALE = 1.0f / 1024.0f; const float MIN_DELTA = 10e-10; Map::Map() { mapfile_name.clear(); } Map::~Map() { clear_materials(); } void Map::clear_materials() { for (Materials::iterator mit = map_materials.begin(); mit != map_materials.end(); mit++) { // delete list of primitives delete(*mit).second; } map_materials.clear(); map_brushes = 0; map_faces = 0; map_faces_detail = 0; } bool Map::open(std::string const & name) { last_read_was_classname = false; last_read_was_key = false; key_current = ""; value_current = ""; classname_current = ""; line_number = 0; parse_level = 0; clear_materials(); mapfile_name.assign("maps/"); mapfile_name.append(name); mapfile_name.append(".map"); filesystem::File *f = filesystem::open(mapfile_name.c_str()); if (!f) { con_warn << "Could not open " << mapfile_name << std::endl; return false; } std::string fn = f->path(); fn.append(f->name()); filesystem::close(f); mapfile_ifs.open(fn.c_str()); if (!mapfile_ifs.is_open()) { con_warn << "Could not stream " << fn << "!\n"; return false; } return true; } bool Map::got_classname() const { return last_read_was_classname; } bool Map::got_classname(const char * classnamelabel) const { return (last_read_was_classname && (classname_current.compare(classnamelabel) == 0)); } bool Map::getline() { using math::Vector3f; char data[1024]; last_read_was_classname = false; last_read_was_key = false; key_current = ""; value_current = ""; if (!mapfile_ifs.is_open()) return false; if (mapfile_ifs.getline(data, 1023)) { line_number++; std::istringstream linestream(data); std::string firstword; if (linestream >> firstword) { if (!firstword.size()) { return true; } else if (firstword == "//") { return true; } else if (firstword == "{") { parse_level++; } else if (firstword == "}") { if ((parse_level == 2) && (classname_current == "worldspawn")) { // brush if (VertexArray::instance()) { // for every face for (std::vector::iterator face = planes.begin(); face != planes.end(); face++) { make_brushface((*face)); } // clean planes for (std::vector::iterator it = planes.begin(); it != planes.end(); it++) { delete(*it); } planes.clear(); map_brushes++; } value_current.clear(); } parse_level--; } else if (parse_level == 1) { if (firstword == "\"classname\"") { classname_current.clear(); if (linestream >> classname_current) { if (classname_current.size() > 2) { classname_current.erase(0,1); classname_current.erase(classname_current.size()-1, 1); last_read_was_classname = true; } else { classname_current.clear(); } } } else if ((firstword.size() > 2) && (firstword[0] == '\"') && (firstword[firstword.size()-1] == '\"')) { key_current.assign(firstword); key_current.erase(0,1); key_current.erase(key_current.size()-1, 1); value_current.clear(); char c; while ((linestream.get(c)) && (c != '"')); while ((linestream.get(c)) && (c != '"')) value_current += c; last_read_was_key = true; } } else if (parse_level == 2) { if ((firstword == "(") && (classname_current == "worldspawn")) { // brush plane if (VertexArray::instance()) { Vector3f p1, p2, p3; std::string tmp; std::string texture; int n = 0; linestream >> p1; linestream >> tmp; // ) linestream >> tmp; // ( linestream >> p2; linestream >> tmp; // ) linestream >> tmp; // ( linestream >> p3; linestream >> tmp; // ) linestream >> texture; // 5 numbers (texture alignment?) for (int i=0; i < 5; i++) linestream >> tmp; // content flags ? if (!(linestream >> n)) n = 0; Plane *plane = new Plane(p1, p2, p3); plane->texture() = texture; if (n > 0) plane->detail() = true; // surface flags if (!(linestream >> n)) n = 0; plane->surface_flags() = n; planes.push_back(plane); } value_current.clear(); } } } } else { return false; } return true; } void Map::make_brushface(Plane *face) { using math::Vector3f; // ignore caulk if (face->texture() == "common/caulk") { return; } // FIXME clip should be parsed as collision blocks if (face->texture() == "common/clip") { return; } // statistics map_faces++; if (face->detail()) { map_faces_detail++; } // using suggestions from // http://www.flipcode.com/archives/Level_Editing.shtml // vertex list std::vector vl; // calculate initial vertices on the bounding box // check if the face is x-axis oriented if ((fabsf(face->normal().x) >= fabsf(face->normal().y)) && (fabsf(face->normal().x) >= fabsf(face->normal().z))) { if (face->normal().x >= 0) { vl.push_back(new math::Vector3f(0, -MAX_BOUNDS, -MAX_BOUNDS)); vl.push_back(new math::Vector3f(0, -MAX_BOUNDS, MAX_BOUNDS)); vl.push_back(new math::Vector3f(0, MAX_BOUNDS, MAX_BOUNDS)); vl.push_back(new math::Vector3f(0, MAX_BOUNDS, -MAX_BOUNDS)); } else { vl.push_back(new math::Vector3f(0, MAX_BOUNDS, -MAX_BOUNDS)); vl.push_back(new math::Vector3f(0, MAX_BOUNDS, MAX_BOUNDS)); vl.push_back(new math::Vector3f(0, -MAX_BOUNDS, MAX_BOUNDS)); vl.push_back(new math::Vector3f(0, -MAX_BOUNDS, -MAX_BOUNDS)); } // calculate the x coordinate of each face vertex for (std::vector::iterator it = vl.begin(); it != vl.end(); it++) { (*it)->x = (-face->d() - face->normal().z * (*it)->z - face->normal().y * (*it)->y) / face->normal().x; } } // check if the face is y-axis oriented else if ((fabsf(face->normal().y) >= fabsf(face->normal().x)) && (fabsf(face->normal().y) >= fabsf(face->normal().z))) { if (face->normal().y >= 0) { vl.push_back(new Vector3f(MAX_BOUNDS, 0, -MAX_BOUNDS)); vl.push_back(new Vector3f(MAX_BOUNDS, 0, MAX_BOUNDS)); vl.push_back(new Vector3f(-MAX_BOUNDS, 0, MAX_BOUNDS)); vl.push_back(new Vector3f(-MAX_BOUNDS, 0, -MAX_BOUNDS)); } else { vl.push_back(new Vector3f(-MAX_BOUNDS, 0, -MAX_BOUNDS)); vl.push_back(new Vector3f(-MAX_BOUNDS, 0, MAX_BOUNDS)); vl.push_back(new Vector3f(MAX_BOUNDS, 0, MAX_BOUNDS)); vl.push_back(new Vector3f(MAX_BOUNDS, 0, -MAX_BOUNDS)); } // calculate the x coordinate of each face vertex for (std::vector::iterator it = vl.begin(); it != vl.end(); it++) { (*it)->y = (-face->d() - face->normal().z * (*it)->z - face->normal().x * (*it)->x) / face->normal().y; } } // face must be z-axis oriented else { if (face->normal().z >= 0) { vl.push_back(new Vector3f(-MAX_BOUNDS, -MAX_BOUNDS, 0)); vl.push_back(new Vector3f(-MAX_BOUNDS, MAX_BOUNDS, 0)); vl.push_back(new Vector3f(MAX_BOUNDS, MAX_BOUNDS, 0)); vl.push_back(new Vector3f(MAX_BOUNDS, -MAX_BOUNDS, 0)); } else { vl.push_back(new Vector3f(MAX_BOUNDS, -MAX_BOUNDS, 0)); vl.push_back(new Vector3f(MAX_BOUNDS, MAX_BOUNDS, 0)); vl.push_back(new Vector3f(-MAX_BOUNDS, MAX_BOUNDS, 0)); vl.push_back(new Vector3f(-MAX_BOUNDS, -MAX_BOUNDS, 0)); } // calculate the x coordinate of each face vertex for (std::vector::iterator it = vl.begin(); it != vl.end(); it++) { (*it)->z = (-face->d() - face->normal().x * (*it)->x - face->normal().y * (*it)->y) / face->normal().z; } } // intersect the face with every plane for (std::vector::iterator pit = planes.begin(); pit != planes.end(); pit++) { Plane *plane = (*pit); if (plane == face) { continue; } Vector3f fn = crossproduct(face->point(1)-face->point(0), face->point(2)-face->point(0)); Vector3f pn = crossproduct(plane->point(1)-plane->point(0), plane->point(2)-plane->point(0)); Vector3f t = crossproduct(fn, pn); if ((t.x == 0) && (t.y == 0) && (t.z == 0)) { continue; } // intersect face with plane for (int i=0; vl.size() - i > 0; i++) { Vector3f v(*vl.at(i)); Vector3f next; if (vl.size() - i > 1) { next = *vl.at(i+1); } else { next = *vl.front(); } Vector3f prev; if (i > 0) { prev = *vl.at(i-1); } else { prev = *vl.back(); } if ((v.x*plane->normal().x + v.y*plane->normal().y + v.z*plane->normal().z +plane->d()) < MIN_DELTA) { // find current std::vector::iterator vit = vl.begin(); while ((*vit) != vl.at(i)) { vit++; } // check if prev - v intersects with plane if ((prev.x*plane->normal().x + prev.y*plane->normal().y + prev.z*plane->normal().z + plane->d()) > -MIN_DELTA) { // calculate intersection float t1 = -plane->normal().x * prev.x - plane->normal().y * prev.y - plane->normal().z * prev.z -plane->d(); float t2 = (plane->normal().x * v.x - plane->normal().x * prev.x + plane->normal().y * v.y - plane->normal().y * prev.y + plane->normal().z * v.z - plane->normal().z * prev.z); Vector3f *s = new Vector3f; if (t2 == 0) { *s = v; } else { for (int j = 0; j < 3; j++) (*s)[j] = prev [j] + t1 * (v[j] - prev[j]) / t2; } vit = vl.insert(vit,s); vit++; i++; } // check if next - v intersects with plane if ((next.x*plane->normal().x + next.y*plane->normal().y + next.z*plane->normal().z + plane->d()) > -MIN_DELTA) { // calculate intersection float t1 = -plane->normal().x * v.x - plane->normal().y * v.y - plane->normal().z * v.z -plane->d(); float t2 = (plane->normal().x * next.x - plane->normal().x * v.x + plane->normal().y * next.y - plane->normal().y * v.y + plane->normal().z * next.z - plane->normal().z * v.z); //cout << "next t2 " << t2 << std::endl; Vector3f *s = new Vector3f; if (t2 == 0) { *s = v; } else { for (int j = 0; j < 3; j++) (*s)[j] = v [j] + t1 * (next[j] - v[j]) / t2; } vit = vl.insert(vit,s); vit++; i++; } // erase delete *vit; vl.erase(vit); i--; } } } if (vl.size() > 2) { // default material is none unsigned int material = 0; // default color makrs unknown textures hot pink math::Color color(1.0f, 0.0, 1.0f, 1.0f); // translate texture names to color and material if (face->texture().compare("colors/white") == 0) { color.assign(1.0f); } else if (face->texture().compare("colors/grey90") == 0) { color.assign(0.9f); } else if (face->texture().compare("colors/grey75") == 0) { color.assign(0.75f); } else if (face->texture().compare("colors/grey50") == 0) { color.assign(0.5f); } else if (face->texture().compare("colors/grey25") == 0) { color.assign(0.25f); } else if (face->texture().compare("colors/black") == 0) { color.assign(0.0f); } else if (face->texture().compare("colors/red") == 0) { color.assign(1, 0, 0); } else if (face->texture().compare("colors/green") == 0) { color.assign(0, 1, 0); } else if (face->texture().compare("colors/blue") == 0) { color.assign(0, 0, 1); } else if ((face->texture().compare("common/entity") == 0) || (face->texture().compare("common/primary") == 0)) { material |= Material::Primary; } else if (face->texture().compare("common/primary_dark") == 0) { material |= Material::Primary; material |= Material::Dark; } else if (face->texture().compare("common/secundary") == 0) { material |= Material::Secondary; } else if (face->texture().compare("common/secundary_dark") == 0) { material |= Material::Secondary; material |= Material::Dark; } else if (face->texture().compare("common/tertiary") == 0) { material |= Material::Tertiary; } else if (face->texture().compare("common/tertiary_dark") == 0) { material |= Material::Tertiary; material |= Material::Dark; } // translate surface flags to materials // surface flag 1 light if ((face->surface_flags() & 1) == 1) { material |= Material::Light; } // find the list if primitives for the current material, allocate a new one if necessary Primitives *primitives = 0; Materials::iterator mit = map_materials.find(material); if (mit == map_materials.end()) { primitives = new Primitives(material); map_materials[material] = primitives; } else { primitives = (*mit).second; } // calculate bounding box for (std::vector::iterator it = vl.begin(); it != vl.end(); it++) { *(*it) *= SCALE; for (int i=0; i < 3; i++) { if (class_maxbbox[i] < (*(*it))[i]) class_maxbbox[i] = (*(*it))[i]; if (class_minbbox[i] > (*(*it))[i]) class_minbbox[i] = (*(*it))[i]; } } // split face into triangles // FIXME split off quads while (vl.size() >2) { std::vector::iterator v0 = vl.begin(); std::vector::reverse_iterator vn = vl.rbegin(); std::vector::reverse_iterator vn1 = vl.rbegin(); ++vn1; Vector3f n(face->normal()*-1); n.normalize(); primitives->add_triangle(*(*vn1), *(*vn), *(*v0), n, color, face->detail()); delete(*vn); vl.pop_back(); } } else { con_debug << "Unresolved face!\n"; } // clean up the vertex list for (std::vector::iterator it = vl.begin(); it != vl.end(); it++) { delete(*it); } vl.clear(); } bool Map::got_key_string(const char * keylabel, std::string & valuestring) { if (last_read_was_key && (key_current.compare(keylabel) == 0)) { valuestring.assign(value_current); return true; } else { return false; } } bool Map::got_key_vector3f(const char * keylabel, math::Vector3f & v) { if (last_read_was_key && (key_current.compare(keylabel) == 0)) { std::istringstream is(value_current); float x, y, z; if ((is >> x) && (is >> y) && (is >> z)) { v = math::Vector3f(x,y,z); } else { v= math::Vector3f(); } return true; } else { return false; } } bool Map::got_key_float(const char * keylabel, float & f) { if (last_read_was_key && (key_current.compare(keylabel) == 0)) { std::istringstream is(value_current); if (!(is >> f)) { f = 0; } return true; } else { return false; } } bool Map::got_key_int(const char * keylabel, unsigned int & u) { if (last_read_was_key && (key_current.compare(keylabel) == 0)) { std::istringstream is(value_current); if (!(is >> u)) { u = 0; } return true; } else { return false; } } bool Map::got_key(const char * keylabel) { return (last_read_was_key && (key_current.compare(keylabel) == 0)); } bool Map::got_key_angle(const char * keylabel, float & f) { if (last_read_was_key && (key_current.compare(keylabel) == 0)) { std::istringstream is(value_current); if ((is >> f)) { f = math::degrees360f(f); } else { f = 0; } return true; } else { return false; } } bool Map::got_key_color(const char * keylabel, math::Color & color) { if (last_read_was_key && (key_current.compare(keylabel) == 0)) { std::istringstream is(value_current); float r, g, b; if ((is >> r) && (is >> g) && (is >> b)) { if ((r > 1) || (g > 1) || (b > 1)) { r /= 255; g /= 255; b /= 255; } color = math::Color(r, g, b); } else { color = math::Color(); } return true; } else { return false; } } void Map::close() { mapfile_ifs.close(); } void Map::load_fragments(Model *model) { if (!VertexArray::instance() || VertexArray::instance()->overflow()) return; if (!map_materials.size()) return; // FIXME center in maps without brushes math::Vector3f center = (class_minbbox + class_maxbbox) / 2; model->model_minbbox = class_minbbox - center; model->model_maxbbox = class_maxbbox - center; model->model_radius = model->model_maxbbox.length(); // reposition lights, flares and engines for (Model::Lights::iterator lit = model->lights().begin(); lit != model->lights().end(); lit++) { (*lit)->light_location -= center; } for (Model::Flares::iterator flit = model->flares().begin(); flit != model->flares().end(); flit++) { (*flit)->light_location -= center; } for (Model::Engines::iterator eit = model->engines().begin(); eit != model->engines().end(); eit++) { (*eit)->engine_location -= center; } for (Materials::iterator mit = map_materials.begin(); mit != map_materials.end(); mit++) { // split the Primitives with this material into fragments Primitives *primitives = (*mit).second; // store triangles if (primitives->triangles().size()) { Fragment *fragment = new Fragment(Fragment::Triangles, primitives->material()); // add structural triangles to the fragment for (Primitives::Triangles::iterator tris_it = primitives->triangles().begin(); tris_it != primitives->triangles().end(); tris_it++) { Triangle *triangle = (*tris_it); if (!triangle->detail()) { size_t count = 0; count += fragment->add_vertex(triangle->v0()-center, triangle->normal(), triangle->color(), false); count += fragment->add_vertex(triangle->v1()-center, triangle->normal(), triangle->color(), false); count += fragment->add_vertex(triangle->v2()-center, triangle->normal(), triangle->color(), false); if (count == 3) model->model_tris_count++; } } // add detail triangles to the fragment for (Primitives::Triangles::iterator tris_it = primitives->triangles().begin(); tris_it != primitives->triangles().end(); tris_it++) { Triangle *triangle = (*tris_it); if (triangle->detail()) { size_t count = 0; count += fragment->add_vertex(triangle->v0()-center, triangle->normal(), triangle->color(), true); count += fragment->add_vertex(triangle->v1()-center, triangle->normal(), triangle->color(), true); count += fragment->add_vertex(triangle->v2()-center, triangle->normal(), triangle->color(), true); if (count == 3) { model->model_tris_count++; model->model_tris_detail_count++; } } } // add the fragment to the model model->fragments().push_back(fragment); } } if (model->model_tris_count + model->model_quad_count > 0) { } } Model * Map::load(std::string const &name) { // open the .map file Map mapfile; if (!mapfile.open(name)) { return 0; } Model *model = new Model(name); Light *light = 0; Flare *flare = 0; Engine *engine = 0; unsigned int u; while (mapfile.getline()) { if (mapfile.got_classname("worldspawn")) { // new wordspawn } else if (mapfile.classname().compare("worldspawn") == 0) { // worldspawn attributes if (mapfile.got_key_int("enginesound", u)) { model->model_enginesound = u; continue; } } else if (mapfile.got_classname("light")) { // new light light = new Light(); model->add_light(light); } else if (mapfile.classname().compare("light") == 0) { // light attributes if (mapfile.got_key_vector3f("origin", light->light_location)) { light->light_location *= SCALE; continue; } else if (mapfile.got_key_color("_color", light->light_color)) { continue; } else if (mapfile.got_key_int("spawnflags", u)) { light->light_strobe = spawnflag_isset(u, 1); light->light_entity = spawnflag_isset(u, 2); } else if (mapfile.got_key_float("light", light->light_radius)) { light->light_radius /= 100.0f; } else if (mapfile.got_key_float("frequency", light->light_frequency)) { continue; } else if (mapfile.got_key_float("offset", light->light_offset)) { continue; } else if (mapfile.got_key_float("time", light->light_time)) { continue; } else if (mapfile.got_key_int("flare", light->light_flare)) { continue; } } else if (mapfile.got_classname("target_flare")) { // new flare flare = new Flare(); model->add_flare(flare); } else if (mapfile.classname().compare("target_flare") == 0) { // flare attributes if (mapfile.got_key_vector3f("origin", flare->light_location)) { flare->light_location *= SCALE; continue; } else if (mapfile.got_key_color("_color", flare->light_color)) { continue; } else if (mapfile.got_key_int("spawnflags", u)) { flare->light_strobe = spawnflag_isset(u, 1); flare->light_entity = spawnflag_isset(u, 2); } else if (mapfile.got_key_float("radius", flare->light_radius)) { flare->light_radius /= 100.0f; } else if (mapfile.got_key_float("frequency", flare->light_frequency)) { continue; } else if (mapfile.got_key_float("offset", flare->light_offset)) { continue; } else if (mapfile.got_key_float("time", flare->light_time)) { continue; } else if (mapfile.got_key_int("flare", flare->light_flare)) { continue; } else if (mapfile.got_key_float("angle", flare->flare_angle)) { flare->flare_angle = math::degrees360f(flare->flare_angle); continue; } } else if (mapfile.got_classname("target_engine")) { // new engine engine = new Engine(); model->add_engine(engine); } else if (mapfile.classname().compare("target_engine") == 0) { // engine attributes if (mapfile.got_key_vector3f("origin", engine->engine_location)) { engine->engine_location *= SCALE; continue; } else if (mapfile.got_key_color("_color", engine->engine_color)) { continue; } else if (mapfile.got_key_float("radius", engine->engine_radius)) { engine->engine_radius /= 100.0f; continue; } else if (mapfile.got_key_int("flare", engine->engine_flare)) { continue; } else if (mapfile.got_key_color("_color", engine->engine_color)) { continue; } } } mapfile.close(); /* con_print << " " << mapfile.name() << " " << mapfile.map_materials.size() << " mat" << mapfile.map_brushes << " brushes " << mapfile.map_faces << "/" << mapfile.map_faces_detail << " faces/detail " << std::endl; */ mapfile.load_fragments(model); con_print << " " << mapfile.name() << " " << model->model_tris_count << "/" << model->model_tris_detail_count << " tris/detail " << model->model_quad_count << "/" << model->model_quad_detail_count << " quads/detail " << model->fragments().size() << " frags " <