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