// Demeter Terrain Visualization Library by Clay Fowler // Copyright (C) 2001 Clay Fowler /* This project demonstrates how to use Demeter with the Open Scene Graph Library. This project also demonstrates the use of texture "splats" on the terrain surface. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include #ifdef _WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Demeter/DemeterDrawable.h" #include "HoverCamera.h" using namespace Demeter; using namespace osg; using namespace osgUtil; using namespace std; #ifdef _WIN32 #define WRITEMESSAGE(x) cout << __LINE__ << endl; MessageBox(NULL,x,"Demeter",MB_OK) #else #define WRITEMESSAGE(x) cout << x << endl #endif Group *CreateTrees(Terrain * pTerrain); osg::Geometry * CreateTree(float width, float height, StateSet * dstate); Node *CreateTerrainNode(Terrain * pTerrain); const float CAMERA_SPEED = 100.0f; // Meters per second const float CAMERA_ROT_SPEED = 72.0f * (M_PI / 180.0f); // Radians per second const float MAX_VIEW_DISTANCE = 5000.0f; const float SKY_RED = 0.5f; const float SKY_GREEN = 0.75f; const float SKY_BLUE = 1.0f; const float SKY_ALPHA = 1.0f; extern bool GetSampleDataPath(char *szPath); bool bFullscreen = true; #ifdef _WIN32 int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) #else int main(int argc, char *argv[]) #endif { #ifdef _DEBUG bFullscreen = false; #ifdef _WIN32 MessageBox(NULL,"You are running a DEBUG build so performance will be very bad.\nRun a release build for better performance.","Warning",MB_OK); #endif #endif char szMediaPath[1024]; if (!GetSampleDataPath(szMediaPath)) exit(9); int ScreenWidth = 800; int ScreenHeight = 600; float detailThreshold = 9.0f; if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) { printf("MAIN: Unable to initialize SDL: %s\n", SDL_GetError()); exit(1); } atexit(SDL_Quit); try { SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_EnableUNICODE(1); Uint32 screenFlags = SDL_OPENGL; if (bFullscreen) screenFlags |= SDL_FULLSCREEN; SDL_Surface *pScreen = SDL_SetVideoMode(ScreenWidth, ScreenHeight, 32, screenFlags); if (pScreen == NULL) { WRITEMESSAGE("MAIN: Unable to set screen resolution"); exit(2); } cout << "MAIN: Screen is " << (int)(pScreen->format->BytesPerPixel) << " bytes per pixel" << endl; SDL_WM_SetCaption("Demeter", "testgl"); SceneView *pView = new SceneView(); pView->setDefaults(); pView->setViewport(0, 0, ScreenWidth, ScreenHeight); Vec4 bkgColor; bkgColor[0] = SKY_RED; bkgColor[1] = SKY_GREEN; bkgColor[2] = SKY_BLUE; bkgColor[3] = SKY_ALPHA; pView->setBackgroundColor(bkgColor); pView->setComputeNearFarMode(CullVisitor::DO_NOT_COMPUTE_NEAR_FAR); Settings::GetInstance()->SetMediaPath(szMediaPath); osgDB::Registry::instance()->setDataFilePathList(szMediaPath); Group *pRootNode = new Group; Settings::GetInstance()->SetVerbose(true); Settings::GetInstance()->SetScreenWidth(800); Settings::GetInstance()->SetScreenHeight(600); const int MAX_NUM_VISIBLE_TRIANGLES = 50000; // Chosen based on the expected number of triangles that will be visible on-screen at any one time (the terrain mesh will typically have far more triangles than are seen at one time, especially with dynamic tessellation) // Load a terrain that was generated in the Demeter Texture Editor Terrain *pTerrain = new Terrain(MAX_NUM_VISIBLE_TRIANGLES,0.0f,0.0f); try { #ifdef _DEBUG Demeter::Loader::GetInstance()->LoadElevations("DemeterElevationLoaderDebug", "Llano.terrain", pTerrain); Demeter::Loader::GetInstance()->LoadTerrainTexture("DemeterTextureLoaderDebug", "Llano.terrain", pTerrain); #else Demeter::Loader::GetInstance()->LoadElevations("DemeterElevationLoader", "Llano.terrain", pTerrain); Demeter::Loader::GetInstance()->LoadTerrainTexture("DemeterTextureLoader", "Llano.terrain", pTerrain); #endif } catch(DemeterException * pEx) { WRITEMESSAGE(pEx->GetErrorMessage()); exit(0); } Node *pTerrainNode = CreateTerrainNode(pTerrain); pRootNode->addChild(pTerrainNode); StateSet *pState = new StateSet; pState->setMode(GL_LIGHTING, StateAttribute::OFF); Fog *fog = new Fog; fog->setMode(Fog::LINEAR); fog->setDensity(0.5f); fog->setStart(100.0f); fog->setEnd(MAX_VIEW_DISTANCE); fog->setColor(bkgColor); pState->setAttributeAndModes(fog, StateAttribute::ON); pRootNode->setStateSet(pState); Group *pTreesGroup = CreateTrees(pTerrain); pRootNode->addChild(pTreesGroup); pView->setSceneData(pRootNode); HoverCamera camera(pTerrain, pView, MAX_VIEW_DISTANCE, (float)Settings::GetInstance()->GetScreenWidth() / (float)Settings::GetInstance()->GetScreenHeight()); camera.SetHoverElevation(25.0f); camera.SetPosition(pTerrain->GetWidth() / 2.0f + 450.0f, pTerrain->GetHeight() / 2.0f + 50.0f); pTerrain->SetDetailThreshold(detailThreshold); Uint32 startPeriodTime = SDL_GetTicks(); float fps, timePerFrame = 0.01f; int cycles = 0; int showFPSCyclePeriod = 0; // We could call SDL_WM_GrabInput() here to trap the mouse cursor and keyboard input for a game-style application in a window //SDL_WM_GrabInput(SDL_GRAB_ON); bool continueRunning = true; while (continueRunning) { // If we had any app behavior in the scene graph, we could activate it here... //pView->app(); camera.Recalc(); // We have to call this each cycle because OSG resets near/far to infinity (even though we have turned off calc near/far) //pView->getCamera()->setNearFar(1.0f,MAX_VIEW_DISTANCE); // Draw the scene pView->cull(); pView->draw(); SDL_GL_SwapBuffers(); // Adjust simulation velocities based on the user's computer speed, so we have real velocities in units/second (independent of rendering frame rate.) float distance = CAMERA_SPEED * timePerFrame; float angle = CAMERA_ROT_SPEED * timePerFrame; int mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); if (mouseX == 0) camera.AddYaw(-angle); if (mouseX == ScreenWidth - 1) camera.AddYaw(angle); if (mouseY == 0) camera.TranslateForward(distance); if (mouseY == ScreenHeight - 1) camera.TranslateBackward(distance); SDL_PumpEvents(); SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) continueRunning = false; } int numKeys; Uint8 *pKeys = SDL_GetKeyState(&numKeys); if (pKeys[SDLK_w]) camera.TranslateForward(distance); if (pKeys[SDLK_x]) camera.TranslateBackward(distance); if (pKeys[SDLK_a]) camera.TranslateLeft(distance); if (pKeys[SDLK_d]) camera.TranslateRight(distance); if (pKeys[SDLK_q]) camera.SetHoverElevation(camera.GetHoverElevation() + distance / 10.0f); if (pKeys[SDLK_z]) camera.SetHoverElevation(camera.GetHoverElevation() - distance / 10.0f); if (pKeys[SDLK_ESCAPE]) continueRunning = false; if (pKeys[SDLK_n]) { detailThreshold += 0.1f; pTerrain->SetDetailThreshold(detailThreshold); cout << detailThreshold << endl; } if (pKeys[SDLK_m]) { if (0.1f < detailThreshold) { detailThreshold -= 0.1f; pTerrain->SetDetailThreshold(detailThreshold); cout << detailThreshold << endl; } } cycles++; Uint32 now = SDL_GetTicks(); Uint32 elapsedTime = now - startPeriodTime; if (300 < elapsedTime) { fps = (float)cycles / ((float)elapsedTime / 1000.0f); if (Settings::GetInstance()->IsVerbose() && ++showFPSCyclePeriod == 10) { cout << "MAIN: " << fps << " frames per second" << endl; showFPSCyclePeriod = 0; } timePerFrame = 1.0f / fps; startPeriodTime = now; cycles = 0; } } // SDL_WM_GrabInput(SDL_GRAB_OFF); SDL_Quit(); return 1; } catch(DemeterException * pEx) { cout << "poo1" << endl; WRITEMESSAGE(pEx->GetErrorMessage()); SDL_Quit(); exit(0); } #ifndef _DEBUG catch(...) { cout << "poo2" << endl; WRITEMESSAGE("MAINOSG: Unexpected exception"); SDL_Quit(); exit(0); } #endif } Node *CreateTerrainNode(Terrain * pTerrain) { Geode *pGeode = NULL; try { DemeterDrawable *pDrawable = new DemeterDrawable; pDrawable->SetTerrain(pTerrain); pGeode = new Geode; pGeode->addDrawable(pDrawable); } catch(...) { } return pGeode; } Group *CreateTrees(Terrain * pTerrain) { const float GROUP_DIAMETER = 500.0f; const float GROUP_FREQUENCY = 0.15f; const int MAX_NUM_TREES_PER_GROUP = 30; const int MIN_NUM_TREES_PER_GROUP = 10; osg::Geometry * pTree = NULL; Group *pAllTreesGroup = new Group; osg::Texture2D * tex = new osg::Texture2D; osg::Image * pImage = osgDB::readImageFile("tree0.rgba"); tex->setImage(pImage); StateSet *dstate = new StateSet; dstate->setTextureAttributeAndModes(0, tex, StateAttribute::ON); dstate->setTextureAttribute(0, new TexEnv); dstate->setAttributeAndModes(new BlendFunc, StateAttribute::ON); AlphaFunc *alphaFunc = new AlphaFunc; alphaFunc->setFunction(AlphaFunc::GEQUAL, 0.05f); dstate->setAttributeAndModes(alphaFunc, StateAttribute::ON); dstate->setMode(GL_LIGHTING, StateAttribute::OFF); dstate->setRenderingHint(StateSet::TRANSPARENT_BIN); // const int mudDetailTextureId = 2; // The mud texture was added to the terrain in the Demeter Texture Editor #ifdef _DEBUG Demeter::Texture * pDetailTexture = Demeter::Loader::GetInstance()->LoadTexture("SDLTextureLoaderDebug", "mud.png,false,false,true"); #else Demeter::Texture * pDetailTexture = Demeter::Loader::GetInstance()->LoadTexture("SDLTextureLoader", "mud.png,false,false,true"); #endif int detailTextureId = pTerrain->GetTextureSet()->AddTexture(pDetailTexture); #ifdef _WIN32 srand(2); #else srand(18); #endif for (float groupX = GROUP_DIAMETER / 2.0f; groupX < pTerrain->GetWidth() - (GROUP_DIAMETER / 2.0f); groupX += GROUP_DIAMETER) { for (float groupY = GROUP_DIAMETER / 2.0f; groupY < pTerrain->GetHeight() - (GROUP_DIAMETER / 2.0f); groupY += GROUP_DIAMETER) { if (((float)rand() / (float)RAND_MAX) <= GROUP_FREQUENCY) { // There is a group of trees centered at groupX,groupY. Group *pGroup = new Group; int numTrees = (int)(((float)rand() / (float)RAND_MAX) * MAX_NUM_TREES_PER_GROUP); if (numTrees < MIN_NUM_TREES_PER_GROUP) numTrees = MIN_NUM_TREES_PER_GROUP; for (int i = 0; i < numTrees; i++) { float angle = ((float)rand() / (float)RAND_MAX) * 360.0f; Matrix matrix; matrix.makeRotate(osg::inDegrees(angle), 0.0f, 0.0f, 1.0f); Vec3 dir; dir.x() = 0.0f; dir.y() = 1.0f; dir.z() = 0.0f; dir = dir * matrix; float distance = ((float)rand() / (float)RAND_MAX) * GROUP_DIAMETER * 0.5f; Vec3 position; position.x() = groupX + dir.x() * distance; position.y() = groupY + dir.y() * distance; position.z() = pTerrain->GetElevation(position.x(), position.y()); pTree = CreateTree(40.0f, 20.0f, dstate); Billboard *pBillboard = new Billboard; pBillboard->addDrawable(pTree, position); // Paint a texture "splat" at the base of the tree. pTerrain->Paint(detailTextureId, 20, 1.0f, 1.0f, false, position.x(), position.y()); pGroup->addChild(pBillboard); } pAllTreesGroup->addChild(pGroup); } } } return pAllTreesGroup; } osg::Geometry * CreateTree(float width, float height, StateSet * dstate) { // This tree code is taken directly from OSG's hang glider sample application. float vv[][3] = { {-width / 2.0, 0.0, 0.0}, {width / 2.0, 0.0, 0.0}, {width / 2.0, 0.0, 2 * height}, {-width / 2.0, 0.0, 2 * height}, }; Vec3Array & v = *(new Vec3Array(4)); Vec2Array & t = *(new Vec2Array(4)); Vec4Array & l = *(new Vec4Array(1)); int i; l[0][0] = l[0][1] = l[0][2] = l[0][3] = 1; for (i = 0; i < 4; i++) { v[i][0] = vv[i][0]; v[i][1] = vv[i][1]; v[i][2] = vv[i][2]; } t[0][0] = 0.0; t[0][1] = 0.0; t[1][0] = 1.0; t[1][1] = 0.0; t[2][0] = 1.0; t[2][1] = 1.0; t[3][0] = 0.0; t[3][1] = 1.0; osg::Geometry * geom = new osg::Geometry(); geom->setVertexArray(&v); geom->setTexCoordArray(0, &t); geom->setColorArray(&l); geom->setColorBinding(osg::Geometry::BIND_OVERALL); geom->addPrimitiveSet(new DrawArrays(PrimitiveSet::QUADS, 0, 4)); geom->setStateSet(dstate); return geom; }