| /* |
| * Copyright © 2008 Ben Smith |
| * Copyright © 2010-2011 Linaro Limited |
| * |
| * This file is part of the glmark2 OpenGL (ES) 2.0 benchmark. |
| * |
| * glmark2 is free software: you can redistribute it and/or modify it under the |
| * terms of the GNU General Public License as published by the Free Software |
| * Foundation, either version 3 of the License, or (at your option) any later |
| * version. |
| * |
| * glmark2 is distributed in the hope that it will be useful, but WITHOUT ANY |
| * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
| * details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * glmark2. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * Authors: |
| * Ben Smith (original glmark benchmark) |
| * Alexandros Frantzis (glmark2) |
| */ |
| #include "mesh.h" |
| #include "model.h" |
| #include "vec.h" |
| #include "log.h" |
| #include "options.h" |
| #include "util.h" |
| #include "float.h" |
| #include "math.h" |
| #include <fstream> |
| #include <sstream> |
| #include <memory> |
| |
| using std::string; |
| using std::vector; |
| using LibMatrix::vec2; |
| using LibMatrix::vec3; |
| using LibMatrix::uvec3; |
| |
| #define read_or_fail(file, dst, size) do { \ |
| file.read(reinterpret_cast<char *>((dst)), (size)); \ |
| if (file.gcount() < (std::streamsize)(size)) { \ |
| Log::error("%s: %d: Failed to read %zd bytes from 3ds file (read %zd)\n", \ |
| __FUNCTION__, __LINE__, \ |
| (size_t)(size), file.gcount()); \ |
| return false; \ |
| } \ |
| } while(0); |
| |
| /** |
| * Computes the bounding box for a Model::Object. |
| * |
| * @param object the Model object |
| */ |
| void |
| Model::compute_bounding_box(const Object& object) |
| { |
| float minX(FLT_MAX); |
| float maxX(FLT_MIN); |
| float minY(FLT_MAX); |
| float maxY(FLT_MIN); |
| float minZ(FLT_MAX); |
| float maxZ(FLT_MIN); |
| for (vector<Vertex>::const_iterator vIt = object.vertices.begin(); vIt != object.vertices.end(); vIt++) |
| { |
| const vec3& curVtx = vIt->v; |
| if (curVtx.x() < minX) |
| { |
| minX = curVtx.x(); |
| } |
| if (curVtx.x() > maxX) |
| { |
| maxX = curVtx.x(); |
| } |
| if (curVtx.y() < minY) |
| { |
| minY = curVtx.y(); |
| } |
| if (curVtx.y() > maxY) |
| { |
| maxY = curVtx.y(); |
| } |
| if (curVtx.z() < minZ) |
| { |
| minZ = curVtx.z(); |
| } |
| if (curVtx.z() > maxZ) |
| { |
| maxZ = curVtx.z(); |
| } |
| } |
| maxVec_ = vec3(maxX, maxY, maxZ); |
| minVec_ = vec3(minX, minY, minZ); |
| } |
| |
| /** |
| * Appends the vertices of a Model::Object to a Mesh. |
| * |
| * @param object the object to append |
| * @param mesh the mesh to append to |
| * @param p_pos the attribute position to use for the 'position' attribute |
| * @param n_pos the attribute position to use for the 'normal' attribute |
| * @param t_pos the attribute position to use for the 'texcoord' attribute |
| */ |
| void |
| Model::append_object_to_mesh(const Object &object, Mesh &mesh, |
| int p_pos, int n_pos, int t_pos, |
| int nt_pos, int nb_pos) |
| { |
| for (vector<Face>::const_iterator faceIt = object.faces.begin(); |
| faceIt != object.faces.end(); |
| faceIt++) |
| { |
| // In some model file formats (OBJ in particular), the face description |
| // may contain separate indices per-attribute. So, we need to allow |
| // for this when adding each vertex attribute to the mesh. |
| const Face &face = *faceIt; |
| const Vertex &v1 = object.vertices[face.v.x()]; |
| const Vertex &v2 = object.vertices[face.v.y()]; |
| const Vertex &v3 = object.vertices[face.v.z()]; |
| |
| bool separate_t(face.which & Face::OBJ_FACE_T); |
| |
| const Vertex &t1 = object.vertices[separate_t ? face.t.x() : face.v.x()]; |
| const Vertex &t2 = object.vertices[separate_t ? face.t.y() : face.v.y()]; |
| const Vertex &t3 = object.vertices[separate_t ? face.t.z() : face.v.z()]; |
| |
| bool separate_n(face.which & Face::OBJ_FACE_N); |
| |
| const Vertex &n1 = object.vertices[separate_n ? face.n.x() : face.v.x()]; |
| const Vertex &n2 = object.vertices[separate_n ? face.n.y() : face.v.y()]; |
| const Vertex &n3 = object.vertices[separate_n ? face.n.z() : face.v.z()]; |
| |
| mesh.next_vertex(); |
| if (p_pos >= 0) |
| mesh.set_attrib(p_pos, v1.v); |
| if (n_pos >= 0) |
| mesh.set_attrib(n_pos, n1.n); |
| if (t_pos >= 0) |
| mesh.set_attrib(t_pos, t1.t); |
| if (nt_pos >= 0) |
| mesh.set_attrib(nt_pos, v1.nt); |
| if (nb_pos >= 0) |
| mesh.set_attrib(nb_pos, v1.nb); |
| |
| mesh.next_vertex(); |
| if (p_pos >= 0) |
| mesh.set_attrib(p_pos, v2.v); |
| if (n_pos >= 0) |
| mesh.set_attrib(n_pos, n2.n); |
| if (t_pos >= 0) |
| mesh.set_attrib(t_pos, t2.t); |
| if (nt_pos >= 0) |
| mesh.set_attrib(nt_pos, v2.nt); |
| if (nb_pos >= 0) |
| mesh.set_attrib(nb_pos, v2.nb); |
| |
| mesh.next_vertex(); |
| if (p_pos >= 0) |
| mesh.set_attrib(p_pos, v3.v); |
| if (n_pos >= 0) |
| mesh.set_attrib(n_pos, n3.n); |
| if (t_pos >= 0) |
| mesh.set_attrib(t_pos, t3.t); |
| if (nt_pos >= 0) |
| mesh.set_attrib(nt_pos, v3.nt); |
| if (nb_pos >= 0) |
| mesh.set_attrib(nb_pos, v3.nb); |
| } |
| } |
| |
| /** |
| * Converts a model to a mesh using the default attributes bindings. |
| * |
| * The default attributes and their order is: Position, Normal, Texcoord |
| * |
| * @param mesh the mesh to populate |
| */ |
| void |
| Model::convert_to_mesh(Mesh &mesh) |
| { |
| std::vector<std::pair<AttribType, int> > attribs; |
| |
| attribs.push_back(std::pair<AttribType, int>(AttribTypePosition, 3)); |
| attribs.push_back(std::pair<AttribType, int>(AttribTypeNormal, 3)); |
| attribs.push_back(std::pair<AttribType, int>(AttribTypeTexcoord, 2)); |
| |
| convert_to_mesh(mesh, attribs); |
| } |
| |
| /** |
| * Converts a model to a mesh using custom attribute bindings. |
| * |
| * The attribute bindings are pairs of <AttribType, dimensionality>. |
| * |
| * @param mesh the mesh to populate |
| * @param attribs the attribute bindings to use |
| */ |
| void |
| Model::convert_to_mesh(Mesh &mesh, |
| const std::vector<std::pair<AttribType, int> > &attribs) |
| { |
| std::vector<int> format; |
| int p_pos = -1; |
| int n_pos = -1; |
| int t_pos = -1; |
| int nt_pos = -1; |
| int nb_pos = -1; |
| |
| mesh.reset(); |
| |
| for (std::vector<std::pair<AttribType, int> >::const_iterator ai = attribs.begin(); |
| ai != attribs.end(); |
| ai++) |
| { |
| format.push_back(ai->second); |
| if (ai->first == AttribTypePosition) |
| p_pos = ai - attribs.begin(); |
| else if (ai->first == AttribTypeNormal) |
| n_pos = ai - attribs.begin(); |
| else if (ai->first == AttribTypeTexcoord) |
| t_pos = ai - attribs.begin(); |
| else if (ai->first == AttribTypeTangent) |
| nt_pos = ai - attribs.begin(); |
| else if (ai->first == AttribTypeBitangent) |
| nb_pos = ai - attribs.begin(); |
| } |
| |
| mesh.set_vertex_format(format); |
| |
| for (std::vector<Object>::const_iterator iter = objects_.begin(); |
| iter != objects_.end(); |
| iter++) |
| { |
| append_object_to_mesh(*iter, mesh, p_pos, n_pos, t_pos, nt_pos, nb_pos); |
| } |
| } |
| |
| void |
| Model::calculate_texcoords() |
| { |
| if (gotTexcoords_) |
| return; |
| |
| // Since the model didn't come with texcoords, and we don't actually know |
| // if it came with normals, either, we'll use positional spherical mapping |
| // to generate texcoords for the model. See: |
| // http://www.mvps.org/directx/articles/spheremap.htm for more details. |
| vec3 centerVec = maxVec_ + minVec_; |
| centerVec *= 0.5; |
| |
| for (std::vector<Object>::iterator iter = objects_.begin(); |
| iter != objects_.end(); |
| iter++) |
| { |
| Object &object = *iter; |
| for (vector<Vertex>::iterator vertexIt = object.vertices.begin(); |
| vertexIt != object.vertices.end(); |
| vertexIt++) |
| { |
| Vertex& curVertex = *vertexIt; |
| vec3 vnorm(curVertex.v - centerVec); |
| vnorm.normalize(); |
| curVertex.t.x(asinf(vnorm.x()) / M_PI + 0.5); |
| curVertex.t.y(asinf(vnorm.y()) / M_PI + 0.5); |
| } |
| } |
| |
| gotTexcoords_ = true; |
| } |
| |
| /** |
| * Calculates the normal vectors of the model vertices. |
| */ |
| void |
| Model::calculate_normals() |
| { |
| if (gotNormals_) |
| return; |
| |
| LibMatrix::vec3 n; |
| |
| for (std::vector<Object>::iterator iter = objects_.begin(); |
| iter != objects_.end(); |
| iter++) |
| { |
| Object &object = *iter; |
| |
| for (vector<Face>::const_iterator f_iter = object.faces.begin(); |
| f_iter != object.faces.end(); |
| f_iter++) |
| { |
| const Face &face = *f_iter; |
| Vertex &a = object.vertices[face.v.x()]; |
| Vertex &b = object.vertices[face.v.y()]; |
| Vertex &c = object.vertices[face.v.z()]; |
| |
| /* Calculate normal */ |
| n = LibMatrix::vec3::cross(b.v - a.v, c.v - a.v); |
| n.normalize(); |
| a.n += n; |
| b.n += n; |
| c.n += n; |
| |
| LibMatrix::vec3 q1(b.v - a.v); |
| LibMatrix::vec3 q2(c.v - a.v); |
| LibMatrix::vec2 u1(b.t - a.t); |
| LibMatrix::vec2 u2(c.t - a.t); |
| float det = (u1.x() * u2.y() - u2.x() * u1.y()); |
| |
| /* Calculate tangent */ |
| LibMatrix::vec3 nt; |
| nt.x(det * (u2.y() * q1.x() - u1.y() * q2.x())); |
| nt.y(det * (u2.y() * q1.y() - u1.y() * q2.y())); |
| nt.z(det * (u2.y() * q1.z() - u1.y() * q2.z())); |
| nt.normalize(); |
| a.nt += nt; |
| b.nt += nt; |
| c.nt += nt; |
| |
| /* Calculate bitangent */ |
| LibMatrix::vec3 nb; |
| nb.x(det * (u1.x() * q2.x() - u2.x() * q1.x())); |
| nb.y(det * (u1.x() * q2.y() - u2.x() * q1.y())); |
| nb.z(det * (u1.x() * q2.z() - u2.x() * q1.z())); |
| nb.normalize(); |
| a.nb += nb; |
| b.nb += nb; |
| c.nb += nb; |
| } |
| |
| for (vector<Vertex>::iterator v_iter = object.vertices.begin(); |
| v_iter != object.vertices.end(); |
| v_iter++) |
| { |
| Vertex &v = *v_iter; |
| /* Orthogonalize */ |
| v.nt = (v.nt - v.n * LibMatrix::vec3::dot(v.nt, v.n)); |
| v.n.normalize(); |
| v.nt.normalize(); |
| v.nb.normalize(); |
| } |
| } |
| |
| gotNormals_ = true; |
| } |
| |
| /** |
| * Load a model from a 3DS file. |
| * |
| * @param filename the name of the file |
| * |
| * @return whether loading succeeded |
| */ |
| bool |
| Model::load_3ds(const std::string &filename) |
| { |
| Object *object(0); |
| |
| Log::debug("Loading model from 3ds file '%s'\n", filename.c_str()); |
| |
| const std::unique_ptr<std::istream> input_file_ptr(Util::get_resource(filename)); |
| std::istream& input_file(*input_file_ptr); |
| |
| if (!input_file) { |
| Log::error("Could not open 3ds file '%s'\n", filename.c_str()); |
| return false; |
| } |
| |
| // Loop to scan the whole file |
| while (!input_file.eof()) { |
| uint16_t chunk_id; |
| uint32_t chunk_length; |
| |
| // Read the chunk header |
| input_file.read(reinterpret_cast<char *>(&chunk_id), 2); |
| if (input_file.gcount() == 0) { |
| continue; |
| } |
| else if (input_file.gcount() < 2) { |
| Log::error("%s: %d: Failed to read %zd bytes from 3ds file (read %zd)\n", |
| __FUNCTION__, __LINE__, 2, input_file.gcount()); |
| return false; |
| } |
| |
| //Read the length of the chunk |
| read_or_fail(input_file, &chunk_length, 4); |
| |
| switch (chunk_id) |
| { |
| //----------------- MAIN3DS ----------------- |
| // Description: Main chunk, contains all the other chunks |
| // Chunk ID: 4d4d |
| // Chunk Length: 0 + sub chunks |
| //------------------------------------------- |
| case 0x4d4d: |
| break; |
| |
| //----------------- EDIT3DS ----------------- |
| // Description: 3D Editor chunk, objects layout info |
| // Chunk ID: 3d3d (hex) |
| // Chunk Length: 0 + sub chunks |
| //------------------------------------------- |
| case 0x3d3d: |
| break; |
| |
| //--------------- EDIT_OBJECT --------------- |
| // Description: Object block, info for each object |
| // Chunk ID: 4000 (hex) |
| // Chunk Length: len(object name) + sub chunks |
| //------------------------------------------- |
| case 0x4000: |
| { |
| std::stringstream ss; |
| unsigned char c = 1; |
| |
| for (int i = 0; i < 20 && c != '\0'; i++) { |
| read_or_fail(input_file, &c, 1); |
| ss << c; |
| } |
| |
| objects_.push_back(Object(ss.str())); |
| object = &objects_.back(); |
| } |
| break; |
| |
| //--------------- OBJ_TRIMESH --------------- |
| // Description: Triangular mesh, contains chunks for 3d mesh info |
| // Chunk ID: 4100 (hex) |
| // Chunk Length: 0 + sub chunks |
| //------------------------------------------- |
| case 0x4100: |
| break; |
| |
| //--------------- TRI_VERTEXL --------------- |
| // Description: Vertices list |
| // Chunk ID: 4110 (hex) |
| // Chunk Length: 1 x unsigned short (number of vertices) |
| // + 3 x float (vertex coordinates) x (number of vertices) |
| // + sub chunks |
| //------------------------------------------- |
| case 0x4110: |
| { |
| uint16_t qty; |
| read_or_fail(input_file, &qty, sizeof(uint16_t)); |
| object->vertices.resize(qty); |
| |
| for (uint16_t i = 0; i < qty; i++) { |
| float f[3]; |
| read_or_fail(input_file, f, sizeof(float) * 3); |
| vec3& vertex = object->vertices[i].v; |
| vertex.x(f[0]); |
| vertex.y(f[1]); |
| vertex.z(f[2]); |
| } |
| } |
| break; |
| |
| //--------------- TRI_FACEL1 ---------------- |
| // Description: Polygons (faces) list |
| // Chunk ID: 4120 (hex) |
| // Chunk Length: 1 x unsigned short (number of polygons) |
| // + 3 x unsigned short (polygon points) x (number of polygons) |
| // + sub chunks |
| //------------------------------------------- |
| case 0x4120: |
| { |
| uint16_t qty; |
| read_or_fail(input_file, &qty, sizeof(uint16_t)); |
| object->faces.resize(qty); |
| for (uint16_t i = 0; i < qty; i++) { |
| uint16_t f[4]; |
| read_or_fail(input_file, f, sizeof(uint16_t) * 4); |
| uvec3& face = object->faces[i].v; |
| face.x(f[0]); |
| face.y(f[1]); |
| face.z(f[2]); |
| } |
| } |
| break; |
| |
| //------------- TRI_MAPPINGCOORS ------------ |
| // Description: Vertices list |
| // Chunk ID: 4140 (hex) |
| // Chunk Length: 1 x unsigned short (number of mapping points) |
| // + 2 x float (mapping coordinates) x (number of mapping points) |
| // + sub chunks |
| //------------------------------------------- |
| case 0x4140: |
| { |
| uint16_t qty; |
| read_or_fail(input_file, &qty, sizeof(uint16_t)); |
| for (uint16_t i = 0; i < qty; i++) { |
| float f[2]; |
| read_or_fail(input_file, f, sizeof(float) * 2); |
| vec2& texcoord = object->vertices[i].t; |
| texcoord.x(f[0]); |
| texcoord.y(f[1]); |
| } |
| } |
| gotTexcoords_ = true; |
| break; |
| |
| //----------- Skip unknow chunks ------------ |
| //We need to skip all the chunks that currently we don't use |
| //We use the chunk length information to set the file pointer |
| //to the same level next chunk |
| //------------------------------------------- |
| default: |
| input_file.seekg(chunk_length - 6, std::ios::cur); |
| } |
| } |
| |
| // Compute bounding box for perspective projection |
| compute_bounding_box(*object); |
| |
| if (Options::show_debug) { |
| for (std::vector<Object>::const_iterator iter = objects_.begin(); |
| iter != objects_.end(); |
| iter++) |
| { |
| Log::debug(" Object name: %s Vertex count: %d Face count: %d\n", |
| iter->name.c_str(), iter->vertices.size(), iter->faces.size()); |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| const unsigned int Model::Face::OBJ_FACE_V = 0x1; |
| const unsigned int Model::Face::OBJ_FACE_T = 0x2; |
| const unsigned int Model::Face::OBJ_FACE_N = 0x4; |
| |
| /** |
| * Parse 2-element vertex attribute from an OBJ file. |
| * |
| * @param source the source line to parse |
| * @param v the vec2 to populate |
| */ |
| void |
| Model::obj_get_attrib(const string& source, vec2& v) |
| { |
| // Our attribs are whitespace separated, so use a fuzzy split. |
| vector<string> elements; |
| Util::split(source, ' ', elements, Util::SplitModeFuzzy); |
| |
| // Find the first value... |
| float x = Util::fromString<float>(elements[0]); |
| // And the second value (there might be a third, but we don't care)... |
| float y = Util::fromString<float>(elements[1]); |
| v.x(x); |
| v.y(y); |
| } |
| |
| /** |
| * Parse 3-element vertex attribute from an OBJ file. |
| * |
| * @param source the source line to parse |
| * @param v the vec3 to populate |
| */ |
| void |
| Model::obj_get_attrib(const string& source, vec3& v) |
| { |
| // Our attribs are whitespace separated, so use a fuzzy split. |
| vector<string> elements; |
| Util::split(source, ' ', elements, Util::SplitModeFuzzy); |
| |
| // Find the first value... |
| float x = Util::fromString<float>(elements[0]); |
| // Then the second value... |
| float y = Util::fromString<float>(elements[1]); |
| // And the third value (there might be a fourth, but we don't care)... |
| float z = Util::fromString<float>(elements[2]); |
| v.x(x); |
| v.y(y); |
| v.z(z); |
| } |
| |
| |
| void |
| Model::obj_face_get_index(const string& tuple, unsigned int& which, |
| unsigned int& v, unsigned int& t, unsigned int& n) |
| { |
| // We can use a normal split here as syntax requires no spaces around |
| // the '/' delimiter for a face description. |
| vector<string> elements; |
| Util::split(tuple, '/', elements, Util::SplitModeNormal); |
| |
| if (elements.empty()) |
| { |
| which = 0; |
| return; |
| } |
| |
| which = Face::OBJ_FACE_V; |
| v = Util::fromString<unsigned int>(elements[0]); |
| |
| unsigned int num_elements = elements.size(); |
| |
| if (num_elements > 1 && !elements[1].empty()) |
| { |
| which |= Face::OBJ_FACE_T; |
| t = Util::fromString<unsigned int>(elements[1]); |
| } |
| |
| if (num_elements > 2 && !elements[2].empty()) |
| { |
| which |= Face::OBJ_FACE_N; |
| n = Util::fromString<unsigned int>(elements[2]); |
| } |
| |
| return; |
| } |
| |
| /** |
| * Parse a face description from an OBJ file. |
| * Faces always specify position, but optionally can also contain separate |
| * indices for texcoords and normals. |
| * |
| * @param source the source line to parse |
| * @param v the uvec3 to populate |
| */ |
| void |
| Model::obj_get_face(const string& source, Face& f) |
| { |
| // Our indices are whitespace separated, so use a fuzzy split. |
| vector<string> elements; |
| Util::split(source, ' ', elements, Util::SplitModeFuzzy); |
| |
| // Find the first value... |
| unsigned int which(0); |
| unsigned int vx(0); |
| unsigned int tx(0); |
| unsigned int nx(0); |
| obj_face_get_index(elements[0], which, vx, tx, nx); |
| |
| // Then the second value... |
| unsigned int vy(0); |
| unsigned int ty(0); |
| unsigned int ny(0); |
| obj_face_get_index(elements[1], which, vy, ty, ny); |
| |
| // And the third value (there might be a fourth, but we don't care)... |
| unsigned int vz(0); |
| unsigned int tz(0); |
| unsigned int nz(0); |
| obj_face_get_index(elements[2], which, vz, tz, nz); |
| |
| // OBJ models start absoluted indices at '1', so subtract to re-base to |
| // '0'. We do not handle relative indexing (negative indices). |
| f.which = which; |
| f.v.x(vx - 1); |
| f.v.y(vy - 1); |
| f.v.z(vz - 1); |
| if (which & Face::OBJ_FACE_T) |
| { |
| f.t.x(tx - 1); |
| f.t.y(ty - 1); |
| f.t.z(tz - 1); |
| } |
| if (which & Face::OBJ_FACE_N) |
| { |
| f.n.x(nx - 1); |
| f.n.y(ny - 1); |
| f.n.z(nz - 1); |
| } |
| } |
| |
| /** |
| * Load a model from an OBJ file. |
| * |
| * @param filename the name of the file |
| * |
| * @return whether loading succeeded |
| */ |
| bool |
| Model::load_obj(const std::string &filename) |
| { |
| Log::debug("Loading model from obj file '%s'\n", filename.c_str()); |
| |
| const std::unique_ptr<std::istream> input_file_ptr(Util::get_resource(filename)); |
| std::istream& inputFile(*input_file_ptr); |
| if (!inputFile) |
| { |
| Log::error("Failed to open '%s'\n", filename.c_str()); |
| return false; |
| } |
| |
| vector<string> sourceVec; |
| string curLine; |
| while (getline(inputFile, curLine)) |
| { |
| sourceVec.push_back(curLine); |
| } |
| |
| // Give ourselves an object to populate. |
| objects_.push_back(Object(string())); |
| Object& object(objects_.back()); |
| |
| static const string object_definition("o"); |
| static const string vertex_definition("v"); |
| static const string normal_definition("vn"); |
| static const string texcoord_definition("vt"); |
| static const string face_definition("f"); |
| vector<vec3> positions; |
| vector<vec3> normals; |
| vector<vec2> texcoords; |
| for (vector<string>::const_iterator lineIt = sourceVec.begin(); |
| lineIt != sourceVec.end(); |
| lineIt++) |
| { |
| const string& curSrc = *lineIt; |
| // Is it a vertex attribute, a face description, comment or other? |
| // We only care about the first two, we ignore comments, group names, |
| // smoothing groups, etc. |
| string::size_type startPos(0); |
| string::size_type spacePos = curSrc.find(" ", startPos); |
| string::size_type num_chars(string::npos); |
| string definition; |
| if (spacePos != string::npos) |
| { |
| // Could be arbitrary whitespace between description type and |
| // the data |
| string::size_type defPos = curSrc.find_first_not_of(' ', spacePos); |
| definition = string(curSrc, defPos); |
| num_chars = spacePos - startPos; |
| } |
| string definitionType(curSrc, startPos, num_chars); |
| |
| if (definitionType == vertex_definition) |
| { |
| vec3 p; |
| obj_get_attrib(definition, p); |
| positions.push_back(p); |
| } |
| else if (definitionType == normal_definition) |
| { |
| vec3 n; |
| obj_get_attrib(definition, n); |
| normals.push_back(n); |
| } |
| else if (definitionType == texcoord_definition) |
| { |
| vec2 t; |
| obj_get_attrib(definition, t); |
| texcoords.push_back(t); |
| } |
| else if (definitionType == face_definition) |
| { |
| Face f; |
| obj_get_face(definition, f); |
| object.faces.push_back(f); |
| } |
| else if (definitionType == object_definition) |
| { |
| object.name = definition; |
| } |
| } |
| |
| if (!texcoords.empty()) |
| { |
| gotTexcoords_ = true; |
| } |
| if (!normals.empty()) |
| { |
| gotNormals_ = true; |
| } |
| unsigned int numVertices = positions.size(); |
| object.vertices.resize(numVertices); |
| for (unsigned int i = 0; i < numVertices; i++) |
| { |
| Vertex& curVertex = object.vertices[i]; |
| curVertex.v = positions[i]; |
| if (gotTexcoords_) |
| { |
| curVertex.t = texcoords[i]; |
| } |
| if (gotNormals_) |
| { |
| curVertex.n = normals[i]; |
| } |
| } |
| |
| // Compute bounding box for perspective projection |
| compute_bounding_box(object); |
| |
| Log::debug("Object name: %s Vertex count: %u Face count: %u\n", |
| object.name.empty() ? "(none)" : object.name.c_str(), object.vertices.size(), object.faces.size()); |
| return true; |
| } |
| |
| namespace ModelPrivate |
| { |
| ModelMap modelMap; |
| } |
| |
| /** |
| * Locate all available models. |
| * |
| * This method scans the built-in data paths and build a database of usable |
| * models available to scenes. Map is available on a read-only basis to scenes |
| * that might find it useful for listing models, etc. |
| * |
| * @return a map containing information about the located models |
| */ |
| const ModelMap& |
| Model::find_models() |
| { |
| if (!ModelPrivate::modelMap.empty()) |
| { |
| return ModelPrivate::modelMap; |
| } |
| vector<string> pathVec; |
| string dataDir(Options::data_path + "/models"); |
| Util::list_files(dataDir, pathVec); |
| #ifdef GLMARK_EXTRAS_PATH |
| string extrasDir(GLMARK_EXTRAS_PATH"/models"); |
| Util::list_files(extrasDir, pathVec); |
| #endif |
| |
| // Now that we have a list of all of the model files available to us, |
| // let's go through and pull out the names and what format they're in |
| // so the scene can decide which ones to use. |
| for(vector<string>::const_iterator pathIt = pathVec.begin(); |
| pathIt != pathVec.end(); |
| pathIt++) |
| { |
| const string& curPath = *pathIt; |
| string::size_type namePos(0); |
| string::size_type slashPos = curPath.rfind("/"); |
| if (slashPos != string::npos) |
| { |
| // Advance to the first character after the last slash |
| namePos = slashPos + 1; |
| } |
| |
| ModelFormat format(MODEL_INVALID); |
| string::size_type extPos = curPath.rfind(".3ds"); |
| if (extPos == string::npos) |
| { |
| // It's not a 3ds model |
| extPos = curPath.rfind(".obj"); |
| if (extPos == string::npos) |
| { |
| // It's not an obj model either, so skip it. |
| continue; |
| } |
| format = MODEL_OBJ; |
| } |
| else |
| { |
| // It's a 3ds model |
| format = MODEL_3DS; |
| } |
| |
| string name(curPath, namePos, extPos - namePos); |
| ModelDescriptor* desc = new ModelDescriptor(name, format, curPath); |
| ModelPrivate::modelMap.insert(std::make_pair(name, desc)); |
| } |
| |
| return ModelPrivate::modelMap; |
| } |
| |
| /** |
| * Load a model by name. |
| * |
| * You must initialize the available model collection using |
| * Model::find_models() before using this method. |
| * |
| * @param modelName the model name |
| * |
| * @return whether the operation succeeded |
| */ |
| bool |
| Model::load(const string& modelName) |
| { |
| bool retVal(false); |
| ModelMap::const_iterator modelIt = ModelPrivate::modelMap.find(modelName); |
| if (modelIt == ModelPrivate::modelMap.end()) |
| { |
| return retVal; |
| } |
| |
| ModelDescriptor* desc = modelIt->second; |
| switch (desc->format()) |
| { |
| case MODEL_INVALID: |
| break; |
| case MODEL_3DS: |
| retVal = load_3ds(desc->pathname()); |
| break; |
| case MODEL_OBJ: |
| retVal = load_obj(desc->pathname()); |
| break; |
| } |
| |
| return retVal; |
| } |