model-loader.cpp

Go to the documentation of this file.
00001 /*
00002  * model-loader.cpp
00003  *
00004  * Copyright (C) 2008,2009  Thomas A. Vaughan
00005  * All rights reserved.
00006  *
00007  *
00008  * Redistribution and use in source and binary forms, with or without
00009  * modification, are permitted provided that the following conditions are met:
00010  *     * Redistributions of source code must retain the above copyright
00011  *       notice, this list of conditions and the following disclaimer.
00012  *     * Redistributions in binary form must reproduce the above copyright
00013  *       notice, this list of conditions and the following disclaimer in the
00014  *       documentation and/or other materials provided with the distribution.
00015  *     * Neither the name of the <organization> nor the
00016  *       names of its contributors may be used to endorse or promote products
00017  *       derived from this software without specific prior written permission.
00018  *
00019  * THIS SOFTWARE IS PROVIDED BY THOMAS A. VAUGHAN ''AS IS'' AND ANY
00020  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00021  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
00022  * DISCLAIMED. IN NO EVENT SHALL THOMAS A. VAUGHAN BE LIABLE FOR ANY
00023  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
00024  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
00026  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00027  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
00028  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00029  *
00030  *
00031  * Object that loads + stores glut 3D models.  See model-loader.h.
00032  */
00033 
00034 // includes --------------------------------------------------------------------
00035 #include "model-loader.h"               // always list our own header first!
00036 
00037 #include <fstream>
00038 
00039 #include "common/wave_ex.h"
00040 #include "datahash/datahash_text.h"
00041 #include "geometry/matrix_4.h"
00042 #include "glut-model/glut-model.h"
00043 #include "hfield/heightfield.h"
00044 #include "md3-model/md3-model.h"        // Quake MD3 models
00045 #include "obj-model/obj-model.h"        // OBJ format models
00046 #include "opengl-effects/opengl-effects.h"
00047 #include "perf/perf.h"
00048 #include "skybox/skybox.h"
00049 #include "terrain/terrain.h"
00050 #include "threadsafe/threadsafe_map.h"
00051 #include "util/file.h"
00052 #include "util/parsing.h"
00053 #include "util/token_stream.h"
00054 #include "xform-model/xform-model.h"
00055 
00056 
00057 namespace aesop {
00058 
00060 
00063 static const char * s_name              = "Glut 3D Model Loader";
00064 
00065 
00066 static const float s_radiansPerDegree   = M_PI / 180.0;
00067 
00068 
00070 //
00071 //      static helper methods
00072 //
00074 
00075 
00077 //
00078 //      ModelData -- component data
00079 //
00081 
00082 class ModelData : public ComponentData {
00083 public:
00084         ~ModelData(void) throw() { }
00085 
00086         smart_ptr<glut::Renderable>             m_model;
00087 };
00088 
00089 
00090 
00092 //
00093 //      ModelRegistry - loads + retrieves 3d glut models
00094 //
00096 
00097 class ModelRegistry : public TypeComponentLoader,
00098                         public glut::MaterialRegistry {
00099 public:
00100         // constructor, destructor ---------------------------------------------
00101         ModelRegistry(void) throw() { }
00102         ~ModelRegistry(void) throw() { }
00103 
00104         // public class methods ------------------------------------------------
00105         void initialize(IN smart_ptr<story::Story>& story);
00106 
00107         // aesop::TypeComponentLoader class interface methods -----------------
00108         const char * getComponentName(void) const throw() { return "model"; }
00109         const char * getLoaderName(void) const throw() { return s_name; }
00110         bool isMyFormat(IN const Datahash * data,
00111                                 IN const char * path) const;
00112         smart_ptr<ComponentData> loadTS(IN smart_ptr<Instance>& instance,
00113                                 IN const Datahash * data,
00114                                 IN const char * path);
00115 
00116         // glut::MaterialRegsitry class interface methods ----------------------
00117         smart_ptr<glut::material_t> getMaterial(IN const char * id);
00118 
00119 private:
00120         // private typedefs ----------------------------------------------------
00121 
00122         // map from model ID --> disk file contents
00123         typedef threadsafe_map<std::string, smart_ptr<Datahash> > data_map_t;
00124 
00125         // map from material ID --> material
00126         typedef threadsafe_map<std::string, smart_ptr<glut::material_t> > material_map_t;
00127 
00128         // private helper methods ----------------------------------------------
00129 
00130         // private member data -------------------------------------------------
00131         smart_ptr<story::Story> m_story;
00132         data_map_t              m_modelData;
00133         material_map_t          m_materials;
00134 };
00135 
00136 static smart_ptr<ModelRegistry> s_registry;
00137 
00138 
00139 void
00140 ModelRegistry::initialize
00141 (
00142 IN smart_ptr<story::Story>& story
00143 )
00144 {
00145         ASSERT(story, "null");
00146 
00147         m_story = story;
00148 }
00149 
00150 
00151 
00152 bool
00153 ModelRegistry::isMyFormat
00154 (
00155 IN const Datahash * data,
00156 IN const char * path
00157 )
00158 const
00159 {
00160         ASSERT(data, "null");
00161         ASSERT(path, "null");
00162 
00163         DPRINTF("Checking '%s'", path);
00164 
00165         // get model ID
00166         const char * modelId = getString(data, "id");
00167 
00168         // has to end in .model
00169         if (strcmp("model", GetExtension(modelId))) {
00170                 DPRINTF("Extension does not match!");
00171                 return false;
00172         }
00173 
00174         std::ifstream stream(path);
00175         if (!stream.good()) {
00176                 WAVE_EX(wex);
00177                 wex << "Failed to open file for reading: " << path;
00178         }
00179 
00180         std::string token;
00181         getNextToken(stream, token);
00182         DPRINTF("  token: %s", token.c_str());
00183         if ("modelFormat" != token)
00184                 return false;
00185 
00186         getNextToken(stream, token);
00187         DPRINTF("  token: %s", token.c_str());
00188         if ("model-0.1" != token)
00189                 return false;
00190 
00191         // okay, has the correct extension and format specification!
00192         return true;
00193 }
00194 
00195 
00196 
00197 smart_ptr<ComponentData>
00198 ModelRegistry::loadTS
00199 (
00200 IN smart_ptr<Instance>& instance,
00201 IN const Datahash * typeData,
00202 IN const char * path
00203 )
00204 {
00205         ASSERT(instance, "null");
00206         ASSERT(typeData, "null");
00207         ASSERT(path, "null");
00208 
00209         const char * modelId = getString(typeData, "id");
00210         DPRINTF("Loading model data for model: %s", modelId);
00211         DPRINTF("  path: %s", path);
00212 
00213         std::string parentDir;
00214         GetParentDirectory(path, parentDir);
00215 
00216         // get filesystem manager
00217         smart_ptr<nstream::Manager> mgr =
00218             nstream::getFilesystemManager(parentDir.c_str());
00219         ASSERT(mgr, "null");
00220 
00221         // get the instance data (if any)
00222         smart_ptr<Datahash> instanceData = instance->getInstanceData("model");
00223         // ASSERT(instanceData) -- can be null!
00224 
00225         // see if we need to load the disk data again
00226         smart_ptr<Datahash> modelData;
00227         if (m_modelData.lookup(modelId, modelData) && modelData) {
00228                 DPRINTF("  model data already loaded from disk");
00229         } else {
00230                 DPRINTF("  need to load model data from disk");
00231                 modelData = readHashFromTextFile(path);
00232                 m_modelData.insert(modelId, modelData);
00233         }
00234         ASSERT(modelData, "null");
00235 
00236         smart_ptr<ModelData> md = new ModelData;
00237         ASSERT(md, "out of memory");
00238 
00239         try {
00240                 if (strcmp("model-0.1", getString(modelData, "modelFormat"))) {
00241                         WAVE_EX(wex);
00242                         wex << "Invalid or missing model format";
00243                 }
00244 
00245                 const char * type =
00246                     getKeyWithOverrides("type", instanceData, typeData,
00247                         modelData, eDatahash_Required);
00248                 DPRINTF("  model type: %s", type);
00249 
00250                 if (!strcmp("hfield", type)) {
00251                         const char * hFile = getKeyWithOverrides("hfield",
00252                             instanceData, typeData, modelData,
00253                             eDatahash_Required);
00254 
00255                         // okay, get absolute path
00256 //                      std::string absolute =
00257 //                          getPathRelativeTo(path, hFile);
00258 //                      DPRINTF("Reading heightfield from path: %s",
00259 //                          absolute.c_str());
00260                         DPRINTF("Reading heightfield from path: %s",
00261                             hFile);
00262                         smart_ptr<nstream::Stream> stream =
00263                             nstream::openNamedStream(mgr, hFile);
00264                         ASSERT(stream, "null");
00265                         smart_ptr<hfield::Heightfield> hfield =
00266                             hfield::Heightfield::create(stream);
00267                         ASSERT(hfield, "failed to load heightfield");
00268 
00269                         smart_ptr<glut::Renderable> model =
00270                             glut::createTerrain(hfield);
00271                         ASSERT(model, "failed to create heightfield renderer");
00272                         md->m_model = model;
00273                 } else if (!strcmp("md3", type)) {
00274                         DPRINTF("Loading an MD3 model!");
00275 
00276                         const char * md3Dir = getKeyWithOverrides("md3Dir",
00277                             instanceData, typeData, modelData,
00278                             eDatahash_Required);
00279 
00280                         // get absolute path to model directory
00281                         std::string absolute =
00282                             getPathRelativeTo(path, md3Dir);
00283                         DPRINTF("Reading md3 model from path: %s",
00284                             absolute.c_str());
00285                         md->m_model = md3::loadMd3Player(absolute.c_str());
00286                 } else if (!strcmp("obj-model", type)) {
00287                         DPRINTF("Loading an OBJ model!");
00288 
00289                         const char * objFile = getKeyWithOverrides("file",
00290                             instanceData, typeData, modelData,
00291                             eDatahash_Required);
00292 
00293                         float scale = atof(getKeyWithOverrides("scale",
00294                             instanceData, typeData, modelData,
00295                             eDatahash_Required));
00296                         ASSERT_THROW(scale > 0,
00297                             "Bad obj file scale: " << scale);
00298 
00299                         // create a streamer::Stream
00300                         smart_ptr<nstream::Stream> stream =
00301                             nstream::openNamedStream(mgr, objFile);
00302                         ASSERT_THROW(stream, "Failed to open stream for file: "
00303                             << objFile);
00304                         md->m_model = obj::Model::create(stream, scale);
00305                 } else if (!strcmp("lod-model", type)) {
00306                         bool isSkybox = false;
00307                         const char * flags = getKeyWithOverrides("flags",
00308                             instanceData, typeData, modelData,
00309                             eDatahash_Optional);
00310                         if (flags && !strcmp("skybox", flags)) {
00311                                 isSkybox = true;
00312                         }
00313                         const char * filename = getKeyWithOverrides("file",
00314                             instanceData, typeData, modelData,
00315                             eDatahash_Required);
00316                         std::string absolute = parentDir;
00317                         absolute += "/";
00318                         absolute += filename;
00319                         DPRINTF("Loading glut model from path: %s",
00320                             absolute.c_str());
00321                         smart_ptr<glut::Renderable> model =
00322                             glut::loadModel(absolute.c_str(), this);
00323                         if (isSkybox) {
00324                                 model = glut::createSkybox(model);
00325                         }
00326                         md->m_model = model;
00327                         ASSERT(md->m_model, "failed to load model");
00328                 } else if (!strcmp("sphere", type)) {
00329                         glut::sphere_init_t si;
00330                         si.radius = atof(getKeyWithOverrides("radius",
00331                             instanceData, typeData, modelData,
00332                             eDatahash_Required));
00333                         si.nRings = atoi(getKeyWithOverrides("nRings",
00334                             instanceData, typeData, modelData,
00335                             eDatahash_Required));
00336                         getImgColorFromString(getKeyWithOverrides("color",
00337                             instanceData, typeData, modelData,
00338                             eDatahash_Required), si.color);
00339                         ASSERT_THROW(si.isValid(),
00340                             "Just read sphere data--not valid");
00341 
00342                         md->m_model = glut::createCoolSphere(si);
00343                         ASSERT(md->m_model, "null");
00344 
00345                 } else if (!strcmp("xform", type)) {
00346                         // this model wraps another model!
00347                         // get base model name
00348                         const char * baseModel = getKeyWithOverrides("baseModel",
00349                             instanceData, typeData, modelData,
00350                             eDatahash_Required);
00351                         DPRINTF("xform is wrapping model: '%s'", baseModel);
00352 
00353                         // create fake type
00354                         smart_ptr<Datahash> td = Datahash::create();
00355                         ASSERT(td, "null");
00356                         td->insert("id", baseModel);
00357 
00358                         // get proper path
00359                         std::string basePath = parentDir;
00360                         basePath += "/";
00361                         basePath += baseModel;
00362 
00363                         // load model
00364                         smart_ptr<ComponentData> base =
00365                             this->loadTS(instance, td, basePath.c_str());
00366                         ASSERT(base, "null");
00367                         smart_ptr<ModelData> mdBase = base;
00368                         ASSERT_THROW(mdBase, "component data is bad type?");
00369                         ASSERT(mdBase->m_model, "null");
00370 
00371                         // get scale
00372                         const char * scaleSz = getKeyWithOverrides("scale",
00373                             instanceData, typeData, modelData,
00374                             eDatahash_Required);
00375                         float scale = atof(scaleSz);
00376                         DPRINTF("  scale: %s == %f", scaleSz, scale);
00377 
00378                         // get rotations
00379                         Datahash::iterator_t i;
00380                         modelData->getIterator("rotate", i);
00381                         std::string keystr;
00382                         hash_value_t hv;
00383                         matrix4_t xform;
00384                         xform.setXYZScale(scale);
00385                         while (modelData->getNextElement(i, keystr, hv)) {
00386                                 if (hv.hash)
00387                                         continue;       // skip these
00388 
00389                                 const char * val = hv.text.c_str();
00390                                 DPRINTF("    Parsing rotation: '%s'", val);
00391                                 if (strlen(val) < 3) {
00392                                         continue;       // skip this
00393                                 }
00394 
00395                                 char axis = val[0];
00396                                 float degrees = atof(val + 2);
00397                                 float radians = degrees * s_radiansPerDegree;
00398 
00399                                 matrix4_t R;
00400                                 switch (axis) {
00401                                 case 'x':       R.setXRotation(radians);        break;
00402                                 case 'y':       R.setYRotation(radians);        break;
00403                                 case 'z':       R.setZRotation(radians);        break;
00404                                 default:
00405                                         ASSERT_THROW(false,
00406                                             "Bad rotation axis: 'rotate "
00407                                             << val << "'");
00408                                 }
00409 
00410                                 matrix4_t T = R * xform;
00411                                 xform = T;
00412                         }
00413 
00414                         // create wrapper object
00415                         md->m_model =
00416                             glut::createXformModel(xform, mdBase->m_model);
00417                         ASSERT(md->m_model, "null");
00418 
00419                 } else {
00420                         WAVE_EX(wex);
00421                         wex << "Unknown model type: " << type;
00422                 }
00423         } catch (std::exception& e) {
00424                 DPRINTF("Error loading model '%s' from file '%s'",
00425                     modelId, path);
00426                 DPRINTF("Exception: %s", e.what());
00427 
00428                 // TODO: remove this?  handy to find problem model files...
00429                 ASSERT(false, "Failed to load model!");
00430         }
00431 
00432         // success!
00433         ASSERT(md->m_model, "null");
00434         return md;
00435 }
00436 
00437 
00438 
00439 smart_ptr<glut::material_t>
00440 ModelRegistry::getMaterial
00441 (
00442 IN const char * id
00443 )
00444 {
00445         ASSERT(m_story, "null");
00446         ASSERT(id, "null");
00447 
00448         // already have it?
00449         smart_ptr<glut::material_t> material;
00450         if (m_materials.lookup(id, material))
00451                 return material;
00452 
00453         // don't have it!  need to load...
00454         material = new glut::material_t;
00455         ASSERT(material, "out of memory");
00456 
00457         std::string path = m_story->getObjectPath("model", "foo.model");
00458         std::string dir;
00459         GetParentDirectory(path.c_str(), dir);
00460         dir += "/wgm";
00461 
00462         // TODO: this path stuff is BAD BAD BAD.  Sort this out
00463         path = dir;
00464         path += "/";
00465         path += id;
00466 
00467         DPRINTF("Attempting to load material '%s' from path: %s",
00468             id, path.c_str());
00469 
00470         std::ifstream infile(path.c_str());
00471         if (!infile.good()) {
00472                 WAVE_EX(wex);
00473                 wex << "Failed to open material file: " << path;
00474         }
00475 
00476         try {
00477                 expectToken(infile, "materialVersion");
00478                 expectToken(infile, "0.1");
00479                 expectToken(infile, "material");
00480                 parseMaterial(infile, dir.c_str(), *material);
00481         } catch (std::exception& e) {
00482                 WAVE_EX(wex);
00483                 wex << "Failed to load material '" << id << "' from file: ";
00484                 wex << path << "\n" << e.what();
00485         }
00486 
00487         // add to our (threadsafe) collection
00488         m_materials.insert(id, material);
00489         return material;
00490 }
00491 
00492 
00493 
00495 //
00496 //      public API
00497 //
00499 
00500 smart_ptr<TypeComponentLoader>
00501 getGlutModelLoader
00502 (
00503 void
00504 )
00505 {
00506         if (!s_registry) {
00507                 WAVE_EX(wex);
00508                 wex << "Glut model loader is not yet created";
00509         }
00510 
00511         return s_registry;
00512 }
00513 
00514 
00515 
00516 smart_ptr<TypeComponentLoader>
00517 createModelLoader
00518 (
00519 IN smart_ptr<story::Story>& story
00520 )
00521 {
00522         ASSERT(story, "null");
00523 
00524         s_registry = new ModelRegistry;
00525         ASSERT(s_registry, "null");
00526         s_registry->initialize(story);
00527 
00528         return s_registry;
00529 }
00530 
00531 
00532 smart_ptr<glut::Renderable>
00533 getModel
00534 (
00535 IN smart_ptr<Instance>& instance
00536 )
00537 {
00538         ASSERT(instance, "null");
00539 
00540         smart_ptr<ComponentData> cdata = instance->getComponentData("model");
00541         if (!cdata)
00542                 return NULL;
00543 
00544         smart_ptr<ModelData> md = cdata;
00545         ASSERT(md, "failed to upcast?");
00546 
00547         return md->m_model;
00548 }
00549 
00550 
00551 
00552 };              // aesop namespace
00553