terrain.cpp

Go to the documentation of this file.
00001 /*
00002  * terrain.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  * Object that uses libmini to render heightfields
00031  */
00032 
00033 // includes --------------------------------------------------------------------
00034 #include "terrain.h"            // always include our own header first
00035 
00036 #include "common/wave_ex.h"
00037 #include "nstream/nstream.h"
00038 #include "perf/perf.h"
00039 #include "util/file.h"          // pathname manipulations
00040 #include "wave-glut/glut-state.h"
00041 #include "wave-glut/wave-glut.h"
00042 
00043 // include this last!
00044 #include "mini.h"
00045 
00046 
00047 namespace glut {
00048 
00049 
00050 
00051 
00052 ////////////////////////////////////////////////////////////////////////////////
00053 //
00054 //      static helper methods
00055 //
00056 ////////////////////////////////////////////////////////////////////////////////
00057 
00058 class Terrain : public Renderable {
00059 public:
00060         // constructor, destructor ---------------------------------------------
00061         Terrain(void) throw() { }
00062         ~Terrain(void) throw() { }
00063 
00064         // public class methods ------------------------------------------------
00065         void initialize(IN hfield::Heightfield * hfield);
00066 
00067         // glut::Renderable class interface methods ----------------------------
00068         void render(IN const render_context_t& rc,
00069                                 IN RenderQueue * rq);
00070         rect3d_t getBoundingBox(void) const throw() { return m_boundingBox; }
00071 
00072 private:
00073         // private member data -------------------------------------------------
00074         smart_ptr<minitile>             m_tileset;
00075         smart_ptr<minicache>            m_cache;
00076         point3d_t                       m_offset;
00077         rect3d_t                        m_boundingBox;
00078         float                           m_yOffset;
00079 };
00080 
00081 
00082 
00083 void
00084 Terrain::initialize
00085 (
00086 IN hfield::Heightfield * hfield
00087 )
00088 {
00089         perf::Timer timer("Terrain::initialize");
00090         ASSERT(hfield, "null");
00091 
00092         hfield->dump("Creating terrain rendering object");
00093 
00094         // reset a bunch of opengl stuff
00095         glPushMatrix();
00096         glMatrixMode(GL_PROJECTION);
00097         glLoadIdentity();
00098         glMatrixMode(GL_MODELVIEW);
00099         glLoadIdentity();
00100 
00101         // reset normal to default
00102         glNormal3f(0, 0, 1.0);
00103 
00104         // clear any texture binding
00105         glBindTexture(GL_TEXTURE_2D, 0);
00106 
00107         int iOldMode;
00108         glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &iOldMode);
00109         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
00110 
00111         int iOldMinFilter;
00112         glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, &iOldMinFilter);
00113         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
00114 
00115         // dump openGL state
00116 //      smart_ptr<glut::State> state = glut::State::snapshotFromOpenGL();
00117 //      ASSERT(state, "null");
00118         //state->dump("state");
00119 
00120         // here we construct all the necessary libmini objects
00121         // NOTE: someday we may support multiple tiles in a model!  See
00122         // the libmini minitile documentation.  But for now we only support
00123         // 1x1 tilesets.
00124         int nRows = 1;
00125         int nCols = 1;
00126 
00127         const char * pgmFile = hfield->getHeightfieldPath();
00128         ASSERT(pgmFile, "null heightfield file?");
00129 
00130         const char * ppmFile = hfield->getTexturePath();
00131         ASSERT(ppmFile, "null texture file?");
00132 
00133         // get absolute paths
00134         smart_ptr<nstream::Manager> mgr = hfield->getStreamManager();
00135         ASSERT_THROW(mgr, "null");
00136         std::string pgmAbs = mgr->getFullName(pgmFile);
00137         std::string ppmAbs = mgr->getFullName(ppmFile);
00138 
00139         DPRINTF("height file:  %s", pgmAbs.c_str());
00140         DPRINTF("texture file: %s", ppmAbs.c_str());
00141 
00142         const char * hfield_files[] = { pgmAbs.c_str() };
00143         const char * texture_files[] = { ppmAbs.c_str() };
00144 
00145         int width = hfield->getWidth();         // grid points in x-direction
00146         int length = hfield->getLength();       // grid points in z-direction
00147         ASSERT(width > 0 && length > 0,
00148             "Bad width or length?  w=%d, h=%d", width, length);
00149 
00150         ASSERT(width == length, "libmini requires width = length!");
00151 
00152         float xzScale = hfield->getXZScale();
00153         ASSERT(xzScale > 0, "Bad xz-scale: %f", xzScale);
00154 
00155         float xSize = (width - 1) * xzScale;
00156         float zSize = (length - 1) * xzScale;
00157 
00158         float yScale = hfield->getYScale();
00159         ASSERT(yScale > 0, "Bad y-scale: %f", yScale);
00160 
00161         // create tileset (1x1, see above) based on heightfield data
00162         ASSERT(!m_tileset, "Already have a tileset?");
00163         m_tileset = new minitile((const unsigned char **) hfield_files,
00164                                  (const unsigned char **) texture_files,
00165                                  nRows, nCols, xSize, zSize, yScale);
00166         ASSERT(m_tileset, "out of memory");
00167 
00168         // create a minicache for faster rendering
00169         ASSERT(!m_cache, "already have a cache?");
00170         m_cache = new minicache();
00171         ASSERT(m_cache, "out of memory");
00172 
00173         // initialize cache
00174         m_cache->attach(m_tileset);
00175         m_cache->usevtxshader(0);
00176 
00177         // set up bounding box
00178         m_boundingBox.clear();
00179         m_boundingBox.x1 = xSize;
00180         m_boundingBox.z1 = zSize;
00181         m_boundingBox.y0 = hfield->getMinHeight() * yScale;
00182         m_boundingBox.y1 = hfield->getMaxHeight() * yScale;
00183         m_boundingBox.dump("Raw bounding box (from native heightfield)");
00184 
00185         // calculate offset
00186         // There is an annoyance in how terrain coordinates are determined!
00187         // Bullet ignores terrain coordinates, and automatically centers
00188         //   heightfields in their bounding box.  This is probably appropriate
00189         //   for physics engines, but is a huge headache for rendering engines.
00190         // We keep the bullet convention that all heightfields are automatically
00191         //   recentered, so their local bounding box is symmetric about the
00192         //   local origin.
00193         // As a result, we need to calculate the offset.  This is used to
00194         //   center the heightfield, and is used to account for that centering
00195         //   when rendering.
00196 
00197         m_offset.x = 0.5 * (m_boundingBox.x0 + m_boundingBox.x1);
00198         m_offset.y = 0.5 * (m_boundingBox.y0 + m_boundingBox.y1);
00199         m_offset.z = 0.5 * (m_boundingBox.z0 + m_boundingBox.z1);
00200         m_offset.dump("Calculated offset");
00201 
00202         m_boundingBox.translate(-m_offset);
00203         m_boundingBox.dump("Offset bounding box");
00204 
00205         // also, ask heightfield height in middle...
00206         DPRINTF("Height at (0,0): %lf", hfield->getHeight(0, 0));
00207         DPRINTF("Height at (%f, %f): %f", -m_offset.x, -m_offset.z, hfield->getHeight(-m_offset.x, -m_offset.z));
00208 
00209         float yMax = hfield->getMaxHeight() * yScale;
00210         float yMin = hfield->getMinHeight() * yScale;
00211         float yAvg = 0.5 * (yMax + yMin);
00212         DPRINTF("y-value at center: %f", yAvg);
00213         m_yOffset = -yAvg;
00214 
00215         // all done!
00216         glPopMatrix();
00217         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, iOldMode);
00218         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, iOldMinFilter);
00219 
00220         // any OpenGL state changes?
00221 //      smart_ptr<glut::State> state2 = glut::State::snapshotFromOpenGL();
00222 //      state->diff(state2);
00223 }
00224 
00225 
00226 
00227 void
00228 Terrain::render
00229 (
00230 IN const render_context_t& rc,
00231 IN RenderQueue * rq
00232 )
00233 {
00234         perf::Timer timer("Terrain::render");
00235         ASSERT(rq, "null");
00236         ASSERT(m_tileset, "null");
00237         ASSERT(m_cache, "null");
00238 
00239         // TODO: handle rotations!
00240         // Although I'm not sure how important that is.  Most people don't
00241         //   rotate heightfields.
00242 
00243         // get orientation
00244         point3d_t view = rc.viewer.getFacing();
00245         point3d_t eye = rc.viewer.getPosition();
00246         point3d_t up = rc.viewer.getUp();
00247 
00248         // libmini assumes you are rendering at the origin.  We need to
00249         //   compensate for where the heighfield is actually centered,
00250         //   and our local centering offset.
00251         glPushMatrix();
00252 
00253         // adjust for our bounding box offset and actual position
00254 //      eye = eye + m_offset;
00255         eye = eye - rc.placement.position;
00256         eye.y = eye.y - m_yOffset;
00257 
00258         static int s_counter = 0;
00259         if (!(s_counter % 1000)) {
00260                 DPRINTF("Rendering heightfield!");
00261                 rc.viewer.getPosition().dump("  viewer");
00262                 rc.placement.position.dump("  placement");
00263                 eye.dump("  eye");
00264                 m_offset.dump("  offset");
00265                 DPRINTF("  y-offset: %f", m_yOffset);
00266         }
00267         ++s_counter;
00268 
00269         Viewer local = rc.viewer;
00270         local.setPosition(eye);
00271         setOpenGLViewer(local);
00272 
00273         // set normal
00274         // TODO: libmini should handle normals as part of terrain!
00275 //      glNormal3f(0.0, 1.0, 0.0);
00276 //      glBindTexture(GL_TEXTURE_2D, 0);
00277 
00278         // cache parameters
00279         float resolution = 400.0;       // high numbers=more resolution--and CPU
00280         int updates = 5;                // draws per cache update
00281 
00282         // save the current texture environment mode
00283         // (libmini sometimes changes it!)
00284         int iTexMode;
00285         glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &iTexMode);
00286         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
00287 
00288         // enable color materials
00289         int oldColorMaterial;
00290         glGetIntegerv(GL_COLOR_MATERIAL, &oldColorMaterial);
00291         glEnable(GL_COLOR_MATERIAL);
00292 
00293         // save texture minfilter, since libmini messes with this
00294         int oldTexMinFilter;
00295         glGetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
00296             &oldTexMinFilter);
00297 
00298         // save ambient and diffuse materials, since libmini messes with these
00299         float oldAmbient[4];
00300         float oldDiffuse[4];
00301         glGetMaterialfv(GL_FRONT, GL_AMBIENT, oldAmbient);
00302         glGetMaterialfv(GL_FRONT, GL_DIFFUSE, oldDiffuse);
00303 
00304         // dump openGL state
00305 //      smart_ptr<glut::State> state = glut::State::snapshotFromOpenGL();
00306 //      ASSERT(state, "null");
00307 //      state->dump("state");
00308 
00309         // let cache know we are using it
00310         m_cache->makecurrent();
00311 
00312         // set up cache
00313         {
00314                 perf::Timer timer("minitile::draw");
00315                 m_tileset->draw(resolution,
00316                                 eye.x, eye.y, eye.z,
00317                                 view.x, view.y, view.z,
00318                                 up.x, up.y, up.z,
00319                                 rc.camera.getFovy(),
00320                                 rc.camera.getAspect(),
00321                                 rc.camera.getZNear(),
00322                                 rc.camera.getZFar(),
00323                                 updates);
00324         }
00325 
00326         // actually draw
00327         {
00328                 perf::Timer timer("minicache::draw");
00329                 m_cache->rendercache();
00330         }
00331 
00332 //      smart_ptr<glut::State> state2 = glut::State::snapshotFromOpenGL();
00333 //      state->diff(state2);
00334 
00335         // restore texture environment mode
00336         glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, iTexMode);
00337         if (!oldColorMaterial) {
00338                 glDisable(GL_COLOR_MATERIAL);
00339         }
00340 
00341         // restore texture min filter
00342         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, oldTexMinFilter);
00343 
00344         // restore color materials
00345         glMaterialfv(GL_FRONT, GL_AMBIENT, oldAmbient);
00346         glMaterialfv(GL_FRONT, GL_DIFFUSE, oldDiffuse);
00347 
00348         // put matrices back
00349         glPopMatrix();
00350 }
00351 
00352 
00353 
00354 class LoadTerrain : public Task {
00355 public:
00356         LoadTerrain(IN Terrain * terrain, IN hfield::Heightfield * hfield) {
00357                         ASSERT(terrain, "null");
00358                         ASSERT(hfield, "null");
00359                         m_terrain = terrain;
00360                         m_hfield = hfield;
00361                 }
00362         void doTask(void) {
00363                         ASSERT(m_terrain, "null");
00364                         ASSERT(m_hfield, "null");
00365                         m_terrain->initialize(m_hfield);
00366                 }
00367 
00368 private:
00369         Terrain *               m_terrain;
00370         hfield::Heightfield *   m_hfield;
00371 };
00372 
00373 
00374 ////////////////////////////////////////////////////////////////////////////////
00375 //
00376 //      public API
00377 //
00378 ////////////////////////////////////////////////////////////////////////////////
00379 
00380 smart_ptr<Renderable>
00381 createTerrain
00382 (
00383 IN hfield::Heightfield * hfield
00384 )
00385 {
00386         ASSERT(hfield, "null");
00387 
00388         smart_ptr<Terrain> local = new Terrain;
00389         ASSERT(local, "out of memory");
00390 
00391         // we have to delegate to GLUT for the actual load, since the
00392         // libmini library makes a bunch of glut calls
00393         LoadTerrain task(local, hfield);
00394         requestTask(&task);
00395         return local;
00396 }
00397 
00398 
00399 
00400 };      // glut namespace
00401