Project::OSiRiON - Git repositories
Project::OSiRiON
News . About . Screenshots . Downloads . Forum . Wiki . Tracker . Git
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/model/mapfile.cc')
-rw-r--r--src/model/mapfile.cc1353
1 files changed, 1353 insertions, 0 deletions
diff --git a/src/model/mapfile.cc b/src/model/mapfile.cc
new file mode 100644
index 0000000..21d318b
--- /dev/null
+++ b/src/model/mapfile.cc
@@ -0,0 +1,1353 @@
+/*
+ model/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 "auxiliary/functions.h"
+#include "filesystem/filesystem.h"
+#include "math/mathlib.h"
+#include "model/mapfile.h"
+#include "model/material.h"
+#include "model/model.h"
+#include "model/vertexarray.h"
+#include "sys/sys.h"
+
+#include <sstream>
+#include <string>
+#include <cstring>
+
+namespace model
+{
+
+/*
+ from radiant tools/quake3/map.c
+*/
+
+math::Vector3f texture_baseaxis[18] =
+{
+ math::Vector3f(0,0,1), math::Vector3f(1,0,0), math::Vector3f(0,-1,0), // floor
+ math::Vector3f(0,0,-1), math::Vector3f(1,0,0), math::Vector3f(0,-1,0), // ceiling
+ math::Vector3f(1,0,0), math::Vector3f(0,1,0), math::Vector3f(0,0,-1), // west wall
+ math::Vector3f(-1,0,0), math::Vector3f(0,1,0), math::Vector3f(0,0,-1), // east wall
+ math::Vector3f(0,1,0), math::Vector3f(1,0,0), math::Vector3f(0,0,-1), // south wall
+ math::Vector3f(0,-1,0), math::Vector3f(1,0,0), math::Vector3f(0,0,-1) // north wall
+};
+
+// determines best orthagonal axis to project a texture onto a wall (must be identical in radiant!)
+void texture_axis_from_plane(const Face &face, math::Vector3f &xv, math::Vector3f &yv)
+{
+ size_t best_axis = 0;
+ float dot = 0;
+ float best = 0;
+
+ for (size_t i=0 ; i<6 ; i++)
+ {
+ dot = math::dotproduct(face.normal(), texture_baseaxis[i *3]);
+ if( dot > best + 0.0001f ) /* ydnar: bug 637 fix, suggested by jmonroe */
+ {
+ best = dot;
+ best_axis = i;
+ }
+ }
+
+ xv.assign(texture_baseaxis[best_axis*3+1]);
+ yv.assign(texture_baseaxis[best_axis*3+2]);
+}
+
+// creates world-to-texture mapping vecs for crappy quake plane arrangements
+void face_texture_verts(Face &face, const math::Vector2f &tex_shift, const float tex_rotate, const math::Vector2f & tex_scale)
+{
+ math::Vector3f vecs[2];
+ math::Vector2f scale(tex_scale);
+
+ int sv, tv;
+ float ang, sinv, cosv;
+ float ns, nt;
+ int i, j;
+
+ texture_axis_from_plane(face, vecs[0], vecs[1]);
+
+ if (!scale[0])
+ scale[0] = 1;
+ if (!scale[1])
+ scale[1] = 1;
+
+ // rotate axis
+ if (tex_rotate == 0.0f)
+ { sinv = 0.0f ; cosv = 1.0f; }
+ else if (tex_rotate == 90.0f)
+ { sinv = 1.0f ; cosv = 0.0f; }
+ else if (tex_rotate == 180.0f)
+ { sinv = 0.0f; cosv = -1.0f; }
+ else if (tex_rotate == 270.0f)
+ { sinv = -1.0f ; cosv = 0.0f; }
+ else
+ {
+ ang = tex_rotate / 180.0f * M_PI;
+ sinv = sinf(ang);
+ cosv = cosf(ang);
+ }
+
+ if (vecs[0][0])
+ sv = 0;
+ else if (vecs[0][1])
+ sv = 1;
+ else
+ sv = 2;
+
+ if (vecs[1][0])
+ tv = 0;
+ else if (vecs[1][1])
+ tv = 1;
+ else
+ tv = 2;
+
+ for (i=0 ; i<2 ; i++) {
+ ns = cosv * vecs[i][sv] - sinv * vecs[i][tv];
+ nt = sinv * vecs[i][sv] + cosv * vecs[i][tv];
+ vecs[i][sv] = ns;
+ vecs[i][tv] = nt;
+ }
+
+ for (i=0 ; i<2 ; i++)
+ for (j=0 ; j<3 ; j++)
+ face.tex_vec(i)[j] = vecs[i][j] / scale[i];
+
+ face.tex_shift().assign(tex_shift);
+}
+
+const math::Vector2f map_texture_coords(Face *face, const math::Vector3f &v)
+{
+ math::Vector2f t (
+ (face->tex_shift().x + math::dotproduct(face->tex_vec(0), v)) / face->material()->size().width(),
+ (face->tex_shift().y + math::dotproduct(face->tex_vec(1), v)) / face->material()->size().height()
+ );
+
+ //con_debug << " texture coords for vertex (" << v << ") set to (" << t.x << "," << t.y << ")" << std::endl;
+ return t;
+}
+/*
+
+ // nearest-axial projection
+ dv->st[ 0 ] = s->vecs[ 0 ][ 3 ] + DotProduct( s->vecs[ 0 ], vTranslated );
+ dv->st[ 1 ] = s->vecs[ 1 ][ 3 ] + DotProduct( s->vecs[ 1 ], vTranslated );
+ dv->st[ 0 ] /= si->shaderWidth;
+ dv->st[ 1 ] /= si->shaderHeight;
+
+*/
+
+// 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;
+
+const float MIN_DELTA = 10e-10;
+
+MapFile::MapFile() : map_center(0,0,0)
+{
+ mapfile_name.clear();
+ map_brushes = 0;
+ map_faces = 0;
+ map_faces_detail = 0;
+
+ warning_q2brush = false;
+}
+
+MapFile::~MapFile()
+{
+ clear_materials();
+}
+
+void MapFile::clear_materials()
+{
+ for (Materials::iterator mit = map_materials.begin(); mit != map_materials.end(); mit++) {
+ // delete list of primitives
+ delete(*mit).second;
+ }
+ map_materials.clear();
+}
+
+bool MapFile::open(std::string const & mapname)
+{
+ warning_q2brush = false;
+ 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.append(mapname);
+ mapfile_name.append(".map");
+
+ mapfile_ifs.open(mapfile_name);
+ if (!mapfile_ifs.is_open()) {
+ return false;
+ }
+ return true;
+}
+
+
+bool MapFile::got_classname() const
+{
+ return last_read_was_classname;
+}
+
+bool MapFile::got_classname(const char * classnamelabel) const
+{
+ return (last_read_was_classname && (classname_current.compare(classnamelabel) == 0));
+}
+
+bool MapFile::got_classend(const char * classnamelabel) const
+{
+ return (last_read_was_classend && (classname_current.compare(classnamelabel) == 0));
+}
+
+bool MapFile::getline()
+{
+ using math::Vector3f;
+
+ last_read_was_classname = false;
+ last_read_was_key = false;
+ last_read_was_classend = false;
+
+ key_current = "";
+ value_current = "";
+
+ if (!mapfile_ifs.is_open())
+ return false;
+
+ char data[1024];
+ memset(data, 0, sizeof(data));
+
+ 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) && (planes.size())) {
+ // end-of-brush
+
+ // for every face
+ for (std::vector<Face *>::iterator face = planes.begin(); face != planes.end(); face++) {
+ make_brushface((*face));
+ }
+
+ // clean planes
+ for (std::vector<Face *>::iterator it = planes.begin(); it != planes.end(); it++) {
+ delete(*it);
+ }
+ planes.clear();
+
+ map_brushes++;
+
+ value_current.clear();
+
+ } else if ((parse_level == 1)) {
+ // end-of-class
+ last_read_was_classend = true;
+ }
+
+ 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 == "(") {
+ // brush plane
+
+ 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; // )
+
+ Face *face = new Face(p1, p2, p3);
+
+ // material
+ linestream >> texture;
+ aux::to_lowercase(texture);
+
+ Material *material = Material::find("textures/" + texture);
+ if (!material) {
+ material = new Material("textures/" + texture);
+ Material::add(material);
+ material->set_flags(Material::Texture);
+ material->set_texture(material->name());
+ }
+ face->set_material(material);
+
+ // texture alignment
+ float tx, ty, tr, tsx, tsy;
+ linestream >> tx >> ty; // texture translation
+ linestream >> tr; // texture rotation angle
+ linestream >> tsx >> tsy; // texture scale
+ /*
+ face->tex_translate().assign(tx, ty);
+ face->tex_rotate() = tr;
+ face->tex_scale().assign(tsx, tsy);
+ */
+
+ // from radiant: tools/quake3/q3map2map.c
+ tsx -= (floor( tsx / material->size().width()) * material->size().width());
+ tsy -= (floor( tsy / material->size().height()) * material->size().height());
+
+ // store the texture transformation for this face
+ face_texture_verts((*face), math::Vector2f(tx, ty), tr, math::Vector2f(tsx, tsy));
+
+ // content flags
+ if (!(linestream >> n))
+ n = 0;
+ if (n > 0)
+ face->detail() = true;
+
+ // surface flags
+ if (!(linestream >> n)) {
+ n = 0;
+ warning_q2brush = true;
+ }
+ face->surface_flags() = n;
+
+
+ planes.push_back(face);
+
+ value_current.clear();
+ }
+ }
+ }
+ } else {
+
+ return false;
+ }
+
+ return true;
+}
+
+void MapFile::make_brushface(Face *face)
+{
+ using math::Vector3f;
+
+ // ignore materials with the 'Ignore' flag set
+ if ((face->material()->flags() & Material::Ignore) == Material::Ignore) {
+ 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<math::Vector3f *> 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 > MIN_DELTA) {
+ 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<Vector3f *>::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 > MIN_DELTA) {
+ 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<Vector3f *>::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 > MIN_DELTA) {
+ 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<Vector3f *>::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<Face *>::iterator pit = planes.begin(); pit != planes.end(); pit++) {
+
+ Face *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<Vector3f *>::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) {
+
+ // find the list if primitives for the current material, allocate a new one if necessary
+ Primitives *primitives = 0;
+
+ Materials::iterator mit = map_materials.find(face->material());
+ if (mit == map_materials.end()) {
+ primitives = new Primitives(face->material());
+ map_materials[face->material()] = primitives;
+ } else {
+ primitives = (*mit).second;
+ }
+
+ // scale vertices and calculate the bounding box
+ for (std::vector<Vector3f *>::iterator it = vl.begin(); it != vl.end(); it++) {
+ //*(*it) *= SCALE;
+ for (int i=0; i < 3; i++) {
+ if (class_maxbbox[i] < (*(*it))[i] * SCALE)
+ class_maxbbox[i] = (*(*it))[i] * SCALE;
+
+ if (class_minbbox[i] > (*(*it))[i] * SCALE)
+ class_minbbox[i] = (*(*it))[i] * SCALE;
+ }
+ }
+
+/*
+#ifndef HAVE_BULLET
+
+ // Quads are disable to use model data for bullet physics
+
+ // split polygon into quads
+ while (vl.size() > 3) {
+ std::vector<Vector3f *>::iterator v0 = vl.begin();
+ std::vector<Vector3f *>::reverse_iterator vn = vl.rbegin();
+ std::vector<Vector3f *>::reverse_iterator vn1 = vl.rbegin();
+ ++vn1;
+ std::vector<Vector3f *>::reverse_iterator vn2 = vl.rbegin();
+ ++vn2;
+ ++vn2;
+
+ Vector3f n(face->normal()*-1);
+ n.normalize();
+
+ primitives->add_quad(*(*vn2), *(*vn1), *(*vn), *(*v0), n, face->detail());
+ delete(*vn);
+ delete(*vn1);
+ vl.pop_back();
+ vl.pop_back();
+ }
+#endif
+*/
+ // split polygon into triangles
+ while (vl.size() > 2) {
+ std::vector<Vector3f *>::iterator v0 = vl.begin();
+ std::vector<Vector3f *>::reverse_iterator vn = vl.rbegin();
+ std::vector<Vector3f *>::reverse_iterator vn1 = vl.rbegin();
+ ++vn1;
+
+ Vector3f n(face->normal()*-1);
+ n.normalize();
+
+ Triangle *triangle = primitives->add_triangle(*(*vn1) * SCALE, *(*vn) * SCALE, *(*v0) * SCALE, n, face->detail());
+
+ if (face->material()->flags() & Material::Texture) {
+ triangle->t0().assign(map_texture_coords(face, *(*vn1)));
+ triangle->t1().assign(map_texture_coords(face, *(*vn)));
+ triangle->t2().assign(map_texture_coords(face, *(*v0)));
+ }
+ /*
+ triangle->t0().assign(0, 0);
+ triangle->t1().assign(0, 1);
+ triangle->t2().assign(1, 1);
+ */
+ delete(*vn);
+ vl.pop_back();
+ }
+ } else {
+ con_debug << "Unresolved face!\n";
+ }
+
+ // clean up the vertex list
+ for (std::vector<Vector3f *>::iterator it = vl.begin(); it != vl.end(); it++) {
+ delete(*it);
+ }
+
+ vl.clear();
+}
+
+bool MapFile::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 MapFile::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 MapFile::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 MapFile::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 MapFile::got_key(const char * keylabel)
+{
+ return (last_read_was_key && (key_current.compare(keylabel) == 0));
+}
+
+bool MapFile::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 MapFile::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 MapFile::close()
+{
+ mapfile_ifs.close();
+}
+
+void MapFile::clear_bbox()
+{
+ for (int i=0; i < 3; i++) {
+ class_minbbox[i] = MAX_BOUNDS;
+ class_maxbbox[i] = -MAX_BOUNDS;
+ }
+
+ class_axis.clear();
+ class_speed = 0;
+}
+
+void MapFile::load_worldspawn(Model *model)
+{
+ if (!map_materials.size())
+ return;
+
+ // FIXME center in maps without brushes
+ map_center = (class_minbbox + class_maxbbox) / 2.0f;
+
+ model->model_minbbox = class_minbbox - map_center;
+ model->model_maxbbox = class_maxbbox - map_center;
+
+ model->model_radius = model->model_maxbbox.length();
+
+ load_fragmentgroup(model, FragmentGroup::None);
+}
+
+void MapFile::load_fragmentgroup(Model *model, const FragmentGroup::Type class_type)
+{
+ if (!VertexArray::instance() || VertexArray::instance()->overflow())
+ return;
+
+ if (!map_materials.size())
+ return;
+
+ FragmentGroup *group = new FragmentGroup();
+
+ if (class_type == FragmentGroup::Rotate) {
+ if (class_speed == 0) {
+ // default rotation speed 45 degrees per second
+ class_speed = 45.0f;
+ }
+ }
+
+ if (class_type != FragmentGroup::None) {
+ group->set_transform(true);
+ }
+
+ group->set_type(class_type);
+ group->set_location((class_minbbox + class_maxbbox) / 2.0f - map_center);
+ group->set_axis(class_axis);
+ group->set_speed(class_speed);
+
+ math::Vector3f group_center(map_center);
+ if (group->transform()) {
+ group_center += group->location();
+ }
+
+ 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()-group_center, triangle->normal(), triangle->t0(), false);
+ count += fragment->add_vertex(triangle->v1()-group_center, triangle->normal(), triangle->t1(), false);
+ count += fragment->add_vertex(triangle->v2()-group_center, triangle->normal(), triangle->t2(), 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()-group_center, triangle->normal(), triangle->t0(), true);
+ count += fragment->add_vertex(triangle->v1()-group_center, triangle->normal(), triangle->t1(), true);
+ count += fragment->add_vertex(triangle->v2()-group_center, triangle->normal(), triangle->t2(), true);
+ if (count == 3) {
+ model->model_tris_count++;
+ model->model_tris_detail_count++;
+ }
+ }
+ }
+
+ // add the fragment to the group
+ group->add_fragment(fragment);
+ }
+
+ // store quads
+ if (primitives->quads().size()) {
+ Fragment *fragment = new Fragment(Fragment::Quads, primitives->material());
+
+ // add structural triangles to the fragment
+ for (Primitives::Quads::iterator quad_it = primitives->quads().begin(); quad_it != primitives->quads().end(); quad_it++) {
+ Quad *quad = (*quad_it);
+ if (!quad->detail()) {
+ size_t count = 0;
+ count += fragment->add_vertex(quad->v0()-group_center, quad->normal(), false);
+ count += fragment->add_vertex(quad->v1()-group_center, quad->normal(), false);
+ count += fragment->add_vertex(quad->v2()-group_center, quad->normal(), false);
+ count += fragment->add_vertex(quad->v3()-group_center, quad->normal(), false);
+ if (count == 4)
+ model->model_quad_count++;
+ }
+ }
+
+ // add detail triangles to the fragment
+ for (Primitives::Quads::iterator quad_it = primitives->quads().begin(); quad_it != primitives->quads().end(); quad_it++) {
+ Quad *quad = (*quad_it);
+ if (quad->detail()) {
+ size_t count = 0;
+ count += fragment->add_vertex(quad->v0()-group_center, quad->normal(), false);
+ count += fragment->add_vertex(quad->v1()-group_center, quad->normal(), false);
+ count += fragment->add_vertex(quad->v2()-group_center, quad->normal(), false);
+ count += fragment->add_vertex(quad->v3()-group_center, quad->normal(), false);
+ if (count == 4) {
+ model->model_quad_count++;
+ model->model_quad_detail_count++;
+ }
+ }
+ }
+
+ // add the fragment to the group
+ group->add_fragment(fragment);
+ }
+
+ }
+
+ // add the group to the model
+ model->add_group(group);
+}
+
+void MapFile::unknown_value() const
+{
+ con_warn << name() << " unknown value '" << value() << "' for '" << classname() << ":" << key() << "' at line " << line() << std::endl;
+}
+
+void MapFile::unknown_key() const
+{
+ con_warn << name() << " unknown key '" << classname() << ":" << key() << "' at line " << line() << std::endl;
+}
+
+void MapFile::unknown_class() const
+{
+ con_warn << name() << " unknown class '" << classname() << "' at line " << line() << std::endl;
+}
+
+Model * MapFile::load(std::string const &name)
+{
+ // open the .map file
+ MapFile mapfile;
+
+ if (!mapfile.open(name)) {
+ return 0;
+ }
+
+ Model *model = new Model(name);
+ mapfile.clear_bbox();
+
+ Dock *dock = 0;
+ Particles *particles = 0;
+ Flare *flare = 0;
+ Light *light = 0;
+ SubModel *submodel = 0;
+
+ std::string modelname;
+ math::Vector3f location;
+
+ typedef std::list<SubModel *> SubModelList;
+ SubModelList submodel_list;
+
+ unsigned int u;
+ float angle;
+ float r, s;
+ std::string str;
+
+ while (mapfile.getline()) {
+
+ if (mapfile.got_classname("worldspawn")) {
+ mapfile.clear_bbox();
+
+ } else if (mapfile.got_classend("worldspawn")) {
+ mapfile.load_worldspawn(model);
+ mapfile.clear_materials();
+
+ } else if (mapfile.in_class("worldspawn")) {
+
+ // worldspawn attributes
+ if (mapfile.got_key("name")) {
+ con_debug << " model name '" << name << "'" << std::endl;
+
+ } else if (mapfile.got_key_int("enginesound", u)) {
+ model->model_enginesound = u;
+ continue;
+
+ } else if (mapfile.got_key_int("impulsesound", u)) {
+ model->model_impulsesound = u;
+ continue;
+
+ } else if (mapfile.got_key_color("enginecolor", model->model_enginecolor)) {
+ continue;
+
+ } else if (mapfile.got_key()) {
+ mapfile.unknown_key();
+
+ }
+
+ } else if (mapfile.got_classname("func_door")) {
+ mapfile.clear_bbox();
+
+ } else if (mapfile.got_classend("func_door")) {
+ mapfile.load_fragmentgroup(model, FragmentGroup::Door);
+ mapfile.clear_materials();
+
+ } else if (mapfile.in_class("func_door")) {
+
+ } else if (mapfile.got_classname("func_group")) {
+ mapfile.clear_bbox();
+
+ } else if (mapfile.got_classend("func_group")) {
+ mapfile.load_fragmentgroup(model, FragmentGroup::None);
+ mapfile.clear_materials();
+
+ } else if (mapfile.got_classname("func_rotate")) {
+ mapfile.clear_bbox();
+
+ } else if (mapfile.got_classend("func_rotate")) {
+ mapfile.load_fragmentgroup(model, FragmentGroup::Rotate);
+ mapfile.clear_materials();
+
+ } else if (mapfile.in_class("func_rotate")) {
+
+ if (mapfile.got_key_float("angle", angle)) {
+ if (angle == ANGLEUP) {
+ mapfile.class_axis.change_pitch(90.0f);
+ } else if (angle == ANGLEDOWN) {
+ mapfile.class_axis.change_pitch(-90.0f);
+ } else {
+ mapfile.class_axis.change_direction(angle);
+ }
+ } else if (mapfile.got_key_float("direction", angle)) {
+ mapfile.class_axis.change_direction(angle);
+
+ } else if (mapfile.got_key_float("pitch", angle)) {
+ mapfile.class_axis.change_pitch(angle);
+
+ } else if (mapfile.got_key_float("roll", angle)) {
+ mapfile.class_axis.change_roll(angle);
+
+ } else if (mapfile.got_key_float("speed", mapfile.class_speed)) {
+ continue;
+
+ } else if (mapfile.got_key()) {
+ mapfile.unknown_key();
+ }
+
+ } else if (mapfile.got_classend()) {
+ mapfile.clear_materials();
+
+ } 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 *= LIGHTSCALE;
+
+ } else if (mapfile.got_key_float("radius", light->light_radius)) {
+ light->light_radius *= LIGHTSCALE;
+
+ } 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_key()) {
+ mapfile.unknown_key();
+
+ }
+
+ } else if (mapfile.got_classname("location_dock")) {
+
+ // new docking location
+ dock = new Dock();
+ model->add_dock(dock);
+
+ } else if (mapfile.classname().compare("location_dock") == 0) {
+
+ // dock attributes
+ if (mapfile.got_key_vector3f("origin", dock->dock_location)) {
+ dock->dock_location *= SCALE;
+ continue;
+
+ } else if (mapfile.got_key_float("radius", dock->dock_radius)) {
+ dock->dock_radius *= SCALE;
+ continue;
+
+ } else if (mapfile.got_key("angle")) {
+ continue;
+
+ } else if (mapfile.got_key()) {
+ mapfile.unknown_key();
+
+ }
+
+ } else if (mapfile.got_classname("location_cannon")) {
+ // new cannon
+
+ } else if (mapfile.classname().compare("location_cannon") == 0) {
+
+ } else if (mapfile.got_classname("location_turret")) {
+ // new turret
+
+ } else if (mapfile.classname().compare("location_turret") == 0) {
+
+ } else if (mapfile.got_classname("location_cockpit")) {
+ // cockpit location
+
+ } else if (mapfile.classname().compare("location_cockpit") == 0) {
+
+ } else if (mapfile.got_classname("fx_flare")) {
+
+ // new flare
+ flare = new Flare();
+ model->add_flare(flare);
+
+ } else if (mapfile.classname().compare("fx_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);
+ flare->flare_engine = spawnflag_isset(u, 4);
+
+ } else if (mapfile.got_key_float("radius", flare->light_radius)) {
+ flare->light_radius *= LIGHTSCALE;
+
+ } 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", angle)) {
+ if (angle == ANGLEUP) {
+ flare->flare_axis.change_pitch(90.0f);
+ } else if (angle == ANGLEDOWN) {
+ flare->flare_axis.change_pitch(-90.0f);
+ } else {
+ flare->flare_axis.change_direction(angle);
+ }
+ } else if (mapfile.got_key_float("direction", angle)) {
+ flare->flare_axis.change_direction(angle);
+
+ } else if (mapfile.got_key_float("pitch", angle)) {
+ flare->flare_axis.change_pitch(angle);
+
+ } else if (mapfile.got_key_float("roll", angle)) {
+ flare->flare_axis.change_roll(angle);
+
+ } else if (mapfile.got_key_string("cull", str)) {
+
+ aux::to_lowercase(str);
+ if (str.compare("none") == 0) {
+ flare->flare_cull = CullNone;
+ } else if (str.compare("back") == 0) {
+ flare->flare_cull = CullBack;
+ } else if (str.compare("front") == 0) {
+ flare->flare_cull = CullFront;
+ } else {
+ mapfile.unknown_value();
+ }
+
+ } else if (mapfile.got_key()) {
+ mapfile.unknown_key();
+ }
+
+ } else if (mapfile.got_classname("misc_model")) {
+
+ // new submodel
+ submodel = new SubModel();
+ submodel_list.push_back(submodel);
+
+ } else if (mapfile.classname().compare("misc_model") == 0) {
+
+ // submodel attributes
+ if (mapfile.got_key_vector3f("origin", location)) {
+ submodel->set_location(location * SCALE);
+ continue;
+
+ } else if (mapfile.got_key_string("model", modelname)) {
+
+ // remove extension
+ if (modelname[modelname.size()-4] == '.') {
+ modelname.erase(modelname.size()-4);
+ }
+
+ submodel->set_name(modelname);
+ continue;
+
+ } else if (mapfile.got_key_float("angle", angle)) {
+ if (angle == ANGLEUP) {
+ submodel->axis().change_pitch(90.0f);
+ } else if (angle == ANGLEDOWN) {
+ submodel->axis().change_pitch(-90.0f);
+ } else {
+ submodel->axis().change_direction(angle);
+ }
+
+ } else if (mapfile.got_key_float("modelscale", s)) {
+ if (s) {
+ submodel->set_scale(s);
+ } else {
+ submodel->set_scale(1.0f);
+ }
+ }
+
+ } else if (mapfile.got_classname("fx_particles")) {
+
+ // new particle system
+ particles = new Particles();
+ model->add_particles(particles);
+
+ } else if (mapfile.classname().compare("fx_particles") == 0) {
+
+ // particle system attributes
+ if (mapfile.got_key_vector3f("origin", particles->particles_location)) {
+ particles->particles_location *= SCALE;
+ continue;
+ } else if (mapfile.got_key_string("script", particles->particles_script)) {
+ continue;
+ } else if (mapfile.got_key_float("angle", angle)) {
+ if (angle == ANGLEUP) {
+ particles->particles_axis.change_pitch(90.0f);
+ } else if (angle == ANGLEDOWN) {
+ particles->particles_axis.change_pitch(-90.0f);
+ } else {
+ particles->particles_axis.change_direction(angle);
+ }
+ } else if (mapfile.got_key_float("direction", angle)) {
+ particles->particles_axis.change_direction(angle);
+
+ } else if (mapfile.got_key_float("pitch", angle)) {
+ particles->particles_axis.change_pitch(angle);
+
+ } else if (mapfile.got_key_float("roll", angle)) {
+ particles->particles_axis.change_roll(angle);
+
+ } else if (mapfile.got_key_int("spawnflags", u)) {
+ particles->particles_entity = spawnflag_isset(u, 2);
+ particles->particles_engine = spawnflag_isset(u, 4);
+
+ } else if (mapfile.got_key_float("radius", r)) {
+ particles->set_radius(r * LIGHTSCALE);
+
+ } else if (mapfile.got_key_string("cull", str)) {
+
+ aux::to_lowercase(str);
+ if (str.compare("none") == 0) {
+ particles->particles_cull = CullNone;
+ } else if (str.compare("back") == 0) {
+ particles->particles_cull = CullBack;
+ } else if (str.compare("front") == 0) {
+ particles->particles_cull = CullFront;
+ } else {
+ mapfile.unknown_value();
+ }
+
+ } else if (mapfile.got_key()) {
+ mapfile.unknown_key();
+ }
+ } else if (mapfile.got_classname()) {
+ mapfile.unknown_class();
+ }
+ }
+
+ mapfile.close();
+
+ // reposition docks, lights, flares and particles according to the model center
+ for (Model::Lights::iterator lit = model->lights().begin(); lit != model->lights().end(); lit++) {
+ (*lit)->light_location -= mapfile.map_center;
+ }
+
+ for (Model::Flares::iterator flit = model->flares().begin(); flit != model->flares().end(); flit++) {
+ (*flit)->light_location -= mapfile.map_center;
+ }
+
+ for (Model::ParticleSystems::iterator pit = model->particles().begin(); pit != model->particles().end(); pit++) {
+ (*pit)->particles_location -= mapfile.map_center;
+ }
+
+ for (Model::Docks::iterator dit = model->docks().begin(); dit != model->docks().end(); dit++) {
+ (*dit)->dock_location -= mapfile.map_center;
+ }
+
+ // FIXME this will go wrong if a Rotate group is imported as submodel
+ for (SubModelList::iterator smit = submodel_list.begin(); smit != submodel_list.end(); smit++) {
+ submodel = (*smit);
+ Model *submodel_model = 0;
+
+ if (submodel->name().size()) {
+ submodel_model = Model::load(submodel->name());
+ }
+
+ if (submodel_model) {
+ // copy fragmentgroups
+ for (Model::Groups::iterator git = submodel_model->groups().begin(); git != submodel_model->groups().end(); git++) {
+ FragmentGroup *groupsrc = (*git);
+ FragmentGroup *groupdst = new FragmentGroup();
+
+ groupdst->set_transform(true);
+ groupdst->set_type(groupsrc->type());
+ groupdst->set_scale(groupsrc->scale() * submodel->scale());
+ groupdst->set_speed(groupsrc->speed());
+ groupdst->set_location(groupsrc->location() + submodel->location() - mapfile.map_center);
+ groupdst->set_axis(groupsrc->axis() * submodel->axis());
+
+ // copy fragments
+ for (FragmentGroup::iterator fit = groupsrc->begin(); fit != groupsrc->end(); fit++) {
+ Fragment *fragmentdst = new Fragment(*(*fit));
+ groupdst->add_fragment(fragmentdst);
+ }
+
+ if (groupdst->size()) {
+ model->add_group(groupdst);
+ } else {
+ delete groupdst;
+ }
+ }
+
+ con_debug << " imported submodel '" << submodel->name() << "'" << std::endl;
+ }
+
+ delete submodel;
+
+ }
+
+ con_debug << " " << mapfile.name() << " " << mapfile.map_brushes << " brushes " <<
+ mapfile.map_faces << "/" << mapfile.map_faces_detail << " faces/detail " << std::endl;
+ if (mapfile.warning_q2brush)
+ con_warn << " quake2 style brushes detected" << std::endl;
+
+ return model;
+}
+
+}
+