00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035 #include "obj-model.h"
00036
00037 #include "obj-material.h"
00038
00039 #include "common/wave_ex.h"
00040 #include "perf/perf.h"
00041 #include "util/parsing.h"
00042 #include "util/file.h"
00043 #include "wave-glut/material.h"
00044
00045
00046 namespace obj {
00047
00048
00049
00050 Model::~Model(void) throw() { }
00051
00052
00053
00054 static const eParseBehavior s_parseFlags = eParse_Strip;
00055
00056
00057
00058 struct uv_t {
00059 uv_t(void) throw() { }
00060 uv_t(IN float iu, IN float iv) : u(iu), v(iv) { }
00061 void clear(void) throw() {
00062 u = v = 0.0;
00063 }
00064
00065
00066 float u;
00067 float v;
00068 };
00069
00070
00071
00072
00073 struct face_idx_t {
00074 face_idx_t(void) throw() { }
00075 face_idx_t(IN long iv, IN long ivt, IN long ivn) throw() :
00076 v(iv), vt(ivt), vn(ivn) { }
00077
00078
00079 long v;
00080 long vt;
00081 long vn;
00082 };
00083
00084
00085
00086
00087 struct face_vtx_t {
00088
00089 point3d_t v;
00090 point3d_t n;
00091 uv_t uv;
00092 };
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102 char *
00103 findChar
00104 (
00105 IN char * string,
00106 IN char find
00107 )
00108 throw()
00109 {
00110 ASSERT(string, "null");
00111
00112 char * p = string;
00113 for (; *p && *p != find; ++p) { }
00114 return p;
00115 }
00116
00117
00118
00119 static point3d_t
00120 readPoint3d
00121 (
00122 IN const char * p
00123 )
00124 {
00125 point3d_t v(0, 0, 0);
00126 readFloatsFromString(p, 3, &v.x);
00127
00128 return v;
00129 }
00130
00131
00132
00133 static uv_t
00134 readUV
00135 (
00136 IN const char * p
00137 )
00138 {
00139 uv_t uv(0, 0);
00140 readFloatsFromString(p, 3, &uv.u);
00141 return uv;
00142 }
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153 class OBJModel : public obj::Model {
00154 public:
00155
00156 OBJModel(void) throw();
00157 ~OBJModel(void) throw() { }
00158
00159
00160 void initialize(IN nstream::Stream * stream,
00161 IN float scale);
00162
00163
00164 rect3d_t getBoundingBox(void) const throw() { return m_bounds; }
00165 void render(IN const glut::render_context_t& rc,
00166 IN glut::RenderQueue * rq);
00167
00168
00169
00170 private:
00171
00172 typedef std::vector<point3d_t> vec_point_t;
00173 typedef std::vector<uv_t> vec_uv_t;
00174 typedef std::vector<face_vtx_t> face_t;
00175
00176 struct mesh_t {
00177 std::string material;
00178 face_t vertices;
00179 };
00180
00181 typedef std::vector<smart_ptr<mesh_t> > vec_mesh_t;
00182
00183
00184 void readFace(IN mesh_t * mesh,
00185 IN const char * p);
00186 void readMaterialMaps(IN nstream::Stream * stream,
00187 IN const char * p);
00188
00189
00190 rect3d_t m_bounds;
00191 std::string m_timerName;
00192 vec_point_t m_vertices;
00193 vec_point_t m_vertexNormals;
00194 vec_uv_t m_vertexTexCoords;
00195 vec_mesh_t m_meshes;
00196 map_mtl_t m_materials;
00197 float m_scale;
00198 long m_faceCount;
00199 long m_triangleCount;
00200 };
00201
00202
00203
00204 OBJModel::OBJModel
00205 (
00206 void
00207 )
00208 throw()
00209 {
00210 m_faceCount = 0;
00211 m_triangleCount = 0;
00212 m_scale = 1.0;
00213 }
00214
00215
00216
00217 void
00218 OBJModel::initialize
00219 (
00220 IN nstream::Stream * stream,
00221 IN float scale
00222 )
00223 {
00224 perf::Timer timer("OBJ format parse");
00225 ASSERT(stream, "null");
00226 ASSERT(scale > 0, "Bad scale");
00227
00228
00229 m_scale = scale;
00230
00231
00232 m_timerName = "render:";
00233 smart_ptr<nstream::File> file = stream->getFile();
00234 if (file) {
00235 m_timerName += GetFilename(file->getName());
00236 }
00237
00238
00239 eParseBehavior flags = s_parseFlags;
00240 std::string line, keyword, token;
00241 smart_ptr<mesh_t> currMesh;
00242 while (!stream->eof()) {
00243 line = getNextLineFromStream(*stream, flags);
00244
00245
00246 const char * p = line.c_str();
00247
00248
00249 p = getNextTokenFromString(p, keyword, flags);
00250
00251
00252 if ("" == keyword)
00253 continue;
00254
00255
00256
00257 if ("v" == keyword) {
00258 point3d_t v = readPoint3d(p);
00259 if (!m_vertices.size()) {
00260
00261 m_bounds.setToPoint(v);
00262 } else {
00263 m_bounds.includePoint(v);
00264 }
00265 m_vertices.push_back(v);
00266 } else if ("vn" == keyword) {
00267 m_vertexNormals.push_back(readPoint3d(p));
00268 } else if ("vt" == keyword) {
00269 m_vertexTexCoords.push_back(readUV(p));
00270 } else if ("f" == keyword) {
00271 ASSERT_THROW(currMesh,
00272 "faces specified without material?");
00273 this->readFace(currMesh, p);
00274 } else if ("usemtl" == keyword) {
00275 currMesh = new mesh_t;
00276 ASSERT(currMesh, "out of memory");
00277 getNextTokenFromString(p, currMesh->material,
00278 s_parseFlags);
00279 ASSERT_THROW(
00280 m_materials.end() != m_materials.find(currMesh->material),
00281 "Unknown material: " << p);
00282 DPRINTF("Reading new mesh with material: '%s'",
00283 currMesh->material.c_str());
00284 m_meshes.push_back(currMesh);
00285 } else if ("mtllib" == keyword) {
00286 this->readMaterialMaps(stream, p);
00287 } else {
00288 DPRINTF("Skipping unrecognized OBJ keyword: %s",
00289 line.c_str());
00290 }
00291 }
00292
00293
00294 DPRINTF("Read %d vertices", (int) m_vertices.size());
00295 DPRINTF("Read %d vertex normals", (int) m_vertexNormals.size());
00296 DPRINTF("Read %d vertex texture coordinates", (int) m_vertexTexCoords.size());
00297 DPRINTF("Read %d meshes", (int) m_meshes.size());
00298 DPRINTF("Read %d materials", (int) m_materials.size());
00299 DPRINTF("Converted %ld faces into %ld triangles", m_faceCount,
00300 m_triangleCount);
00301 m_bounds.dump("Bounding box");
00302
00303
00304 m_vertices.clear();
00305 m_vertexNormals.clear();
00306 m_vertexTexCoords.clear();
00307 }
00308
00309
00310
00311 void
00312 OBJModel::render
00313 (
00314 IN const glut::render_context_t& rc,
00315 IN glut::RenderQueue * rq
00316 )
00317 {
00318 perf::Timer timer(m_timerName.c_str());
00319 ASSERT(rq, "null");
00320
00321
00322 glMatrixMode(GL_MODELVIEW);
00323 glPushMatrix();
00324 glScalef(m_scale, m_scale, m_scale);
00325
00326
00327 glEnableClientState(GL_VERTEX_ARRAY);
00328 glEnableClientState(GL_NORMAL_ARRAY);
00329
00330
00331 for (vec_mesh_t::iterator i = m_meshes.begin(); i != m_meshes.end();
00332 ++i) {
00333 mesh_t * mesh = *i;
00334 ASSERT(mesh, "null");
00335
00336
00337 map_mtl_t::iterator mtl = m_materials.find(mesh->material);
00338 if (m_materials.end() == mtl) {
00339 DPRINTF("WARNING: cannot find material: %s",
00340 mesh->material.c_str());
00341 continue;
00342 }
00343 smart_ptr<glut::Material>& material = mtl->second;
00344 ASSERT(material, "null");
00345
00346
00347 glut::MaterialContext mc(material);
00348
00349
00350
00351 int nVertices = (int) mesh->vertices.size();
00352
00353 const int coordinatesPerVertex = 3;
00354 const int type = GL_FLOAT;
00355 const int stride = sizeof(face_vtx_t);
00356
00357
00358 face_vtx_t& fv0 = mesh->vertices[0];
00359
00360
00361 glVertexPointer(coordinatesPerVertex, type, stride, &fv0.v.x);
00362 glNormalPointer(type, stride, &fv0.n.x);
00363
00364
00365 const int mode = GL_TRIANGLES;
00366 const int startIndex = 0;
00367 glDrawArrays(mode, startIndex, nVertices);
00368 }
00369
00370
00371 glDisableClientState(GL_VERTEX_ARRAY);
00372 glDisableClientState(GL_NORMAL_ARRAY);
00373 glPopMatrix();
00374 }
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384 void
00385 OBJModel::readFace
00386 (
00387 IN mesh_t * mesh,
00388 IN const char * p
00389 )
00390 {
00391 ASSERT(mesh, "null");
00392 ASSERT(p, "null");
00393
00394
00395 smart_ptr<face_t> face = new face_t;
00396 ASSERT(face, "out of memory");
00397
00398
00399 const int s_maxSize = 31;
00400 const int s_bufsize = s_maxSize + 1;
00401 char buffer[s_bufsize];
00402
00403
00404 const int s_maxFaceVertices = 12;
00405 face_idx_t face_indices[s_maxFaceVertices];
00406 int nVertices = 0;
00407 while (*p) {
00408 int nChars;
00409 p = getNextTokenFromString(p, buffer, s_bufsize, eParse_None,
00410 nChars);
00411 ASSERT_THROW(nChars < s_bufsize,
00412 "entry in face vertex list is too large!");
00413
00414
00415
00416 char * firstSlash = findChar(buffer, '/');
00417 char * secondSlash = findChar(firstSlash + 1, '/');
00418
00419 ASSERT_THROW(*firstSlash && *secondSlash,
00420 "Badly formatted face string: " << buffer);
00421
00422
00423 *firstSlash = 0;
00424 *secondSlash = 0;
00425 char * secondInt = firstSlash + 1;
00426 char * thirdInt = secondSlash + 1;
00427
00428
00429 ASSERT_THROW(nVertices < s_maxFaceVertices,
00430 "OBJ file contains face with " << nVertices
00431 << " or more vertices!");
00432
00433
00434 face_idx_t& f = face_indices[nVertices];
00435 nVertices++;
00436
00437
00438 f.v = atol(buffer);
00439 f.vt = atol(secondInt);
00440 f.vn = atol(thirdInt);
00441
00442
00443 f.v--;
00444 f.vt--;
00445 f.vn--;
00446
00447
00448 ASSERT_THROW(f.v >= 0 && f.v < (long) m_vertices.size(),
00449 "Invalid face vertex index: " << f.v);
00450 ASSERT_THROW(f.vt >= -1 && f.vt < (long) m_vertexTexCoords.size(),
00451 "Invalid face vertex texture coordinate index: " << f.vt);
00452 ASSERT_THROW(f.vn >= -1 && f.vn < (long) m_vertexNormals.size(),
00453 "Invalid face vertex normal index: " << f.vn);
00454 }
00455
00456 m_faceCount++;
00457
00458
00459 int nTriangles = nVertices - 2;
00460
00461 for (int i = 0; i < nTriangles; ++i) {
00462
00463
00464 face_idx_t v[3];
00465 v[0] = face_indices[0];
00466 v[1] = face_indices[i + 1];
00467 v[2] = face_indices[i + 2];
00468
00469
00470 for (int j = 0; j < 3; ++j) {
00471 face_vtx_t fv;
00472 fv.v = m_vertices[v[j].v];
00473 long idx = v[j].vn;
00474 if (idx > -1) {
00475 fv.n = m_vertexNormals[idx];
00476 } else {
00477 fv.n.clear();
00478 }
00479 idx = v[j].vt;
00480 if (idx > -1) {
00481 fv.uv = m_vertexTexCoords[idx];
00482 } else {
00483 fv.uv.clear();
00484 }
00485
00486
00487 mesh->vertices.push_back(fv);
00488 }
00489 }
00490 m_triangleCount += nTriangles;
00491 }
00492
00493
00494
00495 void
00496 OBJModel::readMaterialMaps
00497 (
00498 IN nstream::Stream * stream,
00499 IN const char * p
00500 )
00501 {
00502 ASSERT(stream, "null");
00503 ASSERT(p, "null");
00504
00505
00506 smart_ptr<nstream::File> file = stream->getFile();
00507 ASSERT_THROW(file, "stream does not have valid file?");
00508 const char * filename = file->getName();
00509
00510 smart_ptr<nstream::Manager> mgr = file->getManager();
00511 ASSERT_THROW(mgr, "file does not have valid stream Manager? " <<
00512 filename);
00513
00514
00515 std::string relPath;
00516 while (*p) {
00517 p = getNextTokenFromString(p, relPath, s_parseFlags);
00518
00519 std::string path = getPathRelativeTo(filename, relPath.c_str());
00520
00521
00522
00523 smart_ptr<nstream::File> file = mgr->getEntry(path.c_str());
00524 if (!file) {
00525 DPRINTF("WARNING--could not load material file: %s",
00526 path.c_str());
00527 DPRINTF("This is often due to bad .obj files with "
00528 "absolute instead of relative paths to .mtl files");
00529 DPRINTF("(absolute paths are nonportable--your OBJ "
00530 "files will break if copied to another path, even "
00531 "if the MTL files go with them)");
00532 DPRINTF("I'll try to load the mtl file as a peer to the"
00533 " obj file...");
00534
00535 path = getPathRelativeTo(filename,
00536 GetFilename(relPath.c_str()));
00537 DPRINTF("Trying path: %s", path.c_str());
00538 file = mgr->getEntry(path.c_str());
00539
00540 if (!file) {
00541 DPRINTF("That didn't work either!");
00542 DPRINTF("As a last-ditch attempt I'll try the "
00543 "absolute path.");
00544 DPRINTF("This is completely nonportable "
00545 "(definitely won't work if people copy "
00546 "files around) but it's the last thing I "
00547 "can try...");
00548 DPRINTF("Trying path: %s", relPath.c_str());
00549 file = mgr->getEntry(relPath.c_str());
00550
00551 if (!file) {
00552 DPRINTF("ERROR--failed to load material"
00553 "file: %s", relPath.c_str());
00554 DPRINTF("Usually this is an error in "
00555 "the application writing the .OBJ "
00556 "file to begin with.");
00557 continue;
00558 }
00559 }
00560 }
00561
00562 smart_ptr<nstream::Stream> stream = file->openStream();
00563 ASSERT_THROW(stream, "could not open stream: " << path);
00564
00565
00566 map_mtl_t map;
00567 try {
00568 map = parseOBJMaterials(stream);
00569 } catch (std::exception& e) {
00570 WAVE_EX(wex);
00571 wex << "Failed to parse material file: " << path;
00572 wex << "\n" << e.what();
00573 }
00574 DPRINTF("Found %d materials in file", (int) map.size());
00575 for (map_mtl_t::iterator i = map.begin(); i != map.end(); ++i) {
00576 const char * mtlName = i->first.c_str();
00577 smart_ptr<glut::Material> mtl = i->second;
00578 ASSERT(mtl, "null");
00579
00580
00581
00582 map_mtl_t::iterator j = m_materials.find(mtlName);
00583 if (m_materials.end() != j) {
00584 DPRINTF("WARNING--material already exists: %s",
00585 mtlName);
00586 DPRINTF(" Ignoring second specification");
00587 continue;
00588 }
00589
00590
00591 m_materials[mtlName] = mtl;
00592 }
00593 }
00594 }
00595
00596
00597
00598
00599
00600
00601
00602
00603
00604 smart_ptr<Model>
00605 Model::create
00606 (
00607 IN nstream::Stream * stream,
00608 IN float scale
00609 )
00610 {
00611 ASSERT(stream, "null");
00612 ASSERT(scale > 0, "Bad scale: %f", scale);
00613
00614 smart_ptr<OBJModel> local = new OBJModel;
00615 ASSERT(local, "out of memory");
00616
00617 try {
00618 local->initialize(stream, scale);
00619 } catch (std::exception& e) {
00620 WAVE_EX(wex);
00621 wex << "Failed to parse OBJ file: ";
00622 stream->writeDiagnostics(wex);
00623 wex << "\n" << e.what();
00624 }
00625
00626 return local;
00627 }
00628
00629
00630
00631 };
00632