diff --git a/CMakeLists.txt b/CMakeLists.txt index 723d10b34..015ffd7ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,6 +188,7 @@ find_package(OpenAL REQUIRED) find_package(Bullet REQUIRED) include_directories("." ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE + ${OGRE_Terrain_INCLUDE_DIR} ${OIS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} ${CMAKE_HOME_DIRECTORY}/extern/caelum/include @@ -259,6 +260,16 @@ if (APPLE) configure_file(${OGRE_PLUGIN_DIR}/Plugin_ParticleFX.dylib "${APP_BUNDLE_DIR}/Contents/Plugins/Plugin_ParticleFX.dylib" COPYONLY) + # prepare components + configure_file(${OGRE_LIB_DIR}/libOgrePaging.dylib + "${APP_BUNDLE_DIR}/Contents/Components/libOgrePaging.dylib" COPYONLY) + + configure_file(${OGRE_LIB_DIR}/libOgreTerrain.dylib + "${APP_BUNDLE_DIR}/Contents/Components/libOgreTerrain.dylib" COPYONLY) + + configure_file(${OpenMW_SOURCE_DIR}/files/openmw.cfg + "${APP_BUNDLE_DIR}/Contents/MacOS/openmw.cfg") + endif (APPLE) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index e17a2cb25..663a10787 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -16,7 +16,7 @@ set(GAME_HEADER source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender - renderingmanager debugging sky player npcs creatures objects renderinginterface + renderingmanager debugging sky terrain player npcs creatures objects renderinginterface ) add_openmw_dir (mwinput @@ -75,6 +75,7 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} + ${OGRE_Terrain_LIBRARY} ${OIS_LIBRARIES} ${Boost_LIBRARIES} ${OPENAL_LIBRARY} diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 06339cdd4..b0b7c88c4 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -26,6 +26,7 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const { rend.createScene("PlayerCam", 55, 5); mSkyManager = MWRender::SkyManager::create(rend.getWindow(), rend.getCamera(), resDir); + mTerrainManager = new TerrainManager(rend.getScene()); // Set default mipmap level (NB some APIs ignore this) TextureManager::getSingleton().setDefaultNumMipmaps(5); @@ -59,6 +60,7 @@ RenderingManager::~RenderingManager () { delete mPlayer; delete mSkyManager; + delete mTerrainManager; } MWRender::Npcs& RenderingManager::getNPCs(){ @@ -76,11 +78,13 @@ MWRender::Player& RenderingManager::getPlayer(){ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store){ objects.removeCell(store); + mTerrainManager->cellRemoved(store); } void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { objects.buildStaticGeometry (*store); + mTerrainManager->cellAdded(store); } void RenderingManager::addObject (const MWWorld::Ptr& ptr){ diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 8d8c98232..5afbd9b78 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -3,6 +3,7 @@ #include "sky.hpp" +#include "terrain.hpp" #include "debugging.hpp" #include "../mwworld/class.hpp" @@ -97,6 +98,7 @@ class RenderingManager: private RenderingInterface { void setAmbientMode(); SkyManager* mSkyManager; + TerrainManager* mTerrainManager; OEngine::Render::OgreRenderer &rend; Ogre::Camera* camera; MWRender::Npcs npcs; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp new file mode 100644 index 000000000..44c499130 --- /dev/null +++ b/apps/openmw/mwrender/terrain.cpp @@ -0,0 +1,62 @@ +#include +#include + +#include "terrain.hpp" + +#include "components/esm/loadland.hpp" + +namespace MWRender +{ + TerrainManager::TerrainManager(Ogre::SceneManager* mgr) + { + mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions(); + + mTerrainGlobals->setMaxPixelError(8); + + mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mgr, + Ogre::Terrain::ALIGN_X_Z, ESM::Land::LAND_SIZE, + ESM::Land::REAL_SIZE); + + mTerrainGroup->setOrigin(Ogre::Vector3(ESM::Land::REAL_SIZE/2, + 0, + -ESM::Land::REAL_SIZE/2)); + + Ogre::Terrain::ImportData importSettings = + mTerrainGroup->getDefaultImportSettings(); + + importSettings.terrainSize = ESM::Land::LAND_SIZE; + importSettings.worldSize = ESM::Land::REAL_SIZE; + importSettings.minBatchSize = 9; + importSettings.maxBatchSize = 33; + + importSettings.deleteInputData = false; + } + + TerrainManager::~TerrainManager() + { + OGRE_DELETE mTerrainGroup; + OGRE_DELETE mTerrainGlobals; + } + + void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store) + { + int x = store->cell->getGridX(); + int y = store->cell->getGridY(); + + Ogre::Terrain::ImportData terrainData; + + terrainData.inputBias = 0; + terrainData.inputFloat = store->land->landData->heights; + + mTerrainGroup->defineTerrain(x, y, &terrainData); + + mTerrainGroup->loadTerrain(x, y, true); + } + + void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) + { + mTerrainGroup->removeTerrain(store->cell->getGridX(), + store->cell->getGridY()); + } + +} diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp new file mode 100644 index 000000000..0a8b1ace0 --- /dev/null +++ b/apps/openmw/mwrender/terrain.hpp @@ -0,0 +1,31 @@ +#ifndef _GAME_RENDER_TERRAIN_H +#define _GAME_RENDER_TERRAIN_H + +#include "../mwworld/ptr.hpp" + +namespace Ogre{ + class SceneManager; + class TerrainGroup; + class TerrainGlobalOptions; +} + +namespace MWRender{ + + /** + * Implements the Morrowind terrain using the Ogre Terrain Component + */ + class TerrainManager{ + public: + TerrainManager(Ogre::SceneManager*); + virtual ~TerrainManager(); + + void cellAdded(MWWorld::Ptr::CellStore* store); + void cellRemoved(MWWorld::Ptr::CellStore* store); + private: + Ogre::TerrainGlobalOptions* mTerrainGlobals; + Ogre::TerrainGroup* mTerrainGroup; + }; + +} + +#endif // _GAME_RENDER_TERRAIN_H diff --git a/cmake/FindOGRE.cmake b/cmake/FindOGRE.cmake index ce3993805..c3b471bb2 100644 --- a/cmake/FindOGRE.cmake +++ b/cmake/FindOGRE.cmake @@ -96,10 +96,25 @@ IF (OGRE_INCLUDE_DIR AND OGRE_LIBRARIES) ENDIF (OGRE_INCLUDE_DIR AND OGRE_LIBRARIES) IF (OGRE_FOUND) + # find terrain component + find_path(OGRE_Terrain_INCLUDE_DIR NAMES OgreTerrain.h HINTS ${OGRE_INCLUDE_DIR} PATH_SUFFIXES Terrain Components/Terrain/include) + set(OGRE_Terrain_LIBRARY_NAMES "OgreTerrain") + find_library(OGRE_Terrain_LIBRARY NAMES ${OGRE_Terrain_LIBRARY_NAMES} HINTS ${OGRE_LIB_DIR} PATH_SUFFIXES "" "release" "relwithdebinfo" "minsizerel") + if(OGRE_Terrain_INCLUDE_DIR AND OGRE_Terrain_LIBRARY) + SET(OGRE_Terrain_FOUND TRUE) + endif(OGRE_Terrain_INCLUDE_DIR AND OGRE_Terrain_LIBRARY) + IF (NOT OGRE_FIND_QUIETLY) MESSAGE(STATUS " libraries : ${OGRE_LIBRARIES} from ${OGRE_LIB_DIR}") MESSAGE(STATUS " includes : ${OGRE_INCLUDE_DIR}") MESSAGE(STATUS " plugins : ${OGRE_PLUGIN_DIR}") + IF (OGRE_Terrain_FOUND) + MESSAGE(STATUS "Ogre Terrain component found:") + MESSAGE(STATUS " include : ${OGRE_Terrain_INCLUDE_DIR}") + MESSAGE(STATUS " library : ${OGRE_Terrain_LIBRARY}") + ELSE (OGRE_Terrain_FOUND) + MESSAGE(FATAL_ERROR "Required Ogre terrain component not found") + ENDIF (OGRE_Terrain_FOUND) ENDIF (NOT OGRE_FIND_QUIETLY) ELSE (OGRE_FOUND) IF (OGRE_FIND_REQUIRED) diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 671f702ca..a5d61f930 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -119,6 +119,21 @@ struct Cell void load(ESMReader &esm); + bool isExterior() const + { + return !(data.flags & Interior); + } + + int getGridX() const + { + return data.gridX; + } + + int getGridY() const + { + return data.gridY; + } + // Restore the given reader to the stored position. Will try to open // the file matching the stored file name. If you want to read from // somewhere other than the file system, you need to pre-open the diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 740d15a40..b8de98f0a 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -19,14 +19,93 @@ void Land::load(ESMReader &esm) int cnt = 0; // Skip these here. Load the actual data when the cell is loaded. - if(esm.isNextSub("VNML")) {esm.skipHSubSize(12675);cnt++;} - if(esm.isNextSub("VHGT")) {esm.skipHSubSize(4232);cnt++;} - if(esm.isNextSub("WNAM")) esm.skipHSubSize(81); - if(esm.isNextSub("VCLR")) esm.skipHSubSize(12675); - if(esm.isNextSub("VTEX")) {esm.skipHSubSize(512);cnt++;} + if (esm.isNextSub("VNML")) + { + esm.skipHSubSize(12675); + cnt++; + } + if (esm.isNextSub("VHGT")) + { + esm.skipHSubSize(4232); + cnt++; + } + if (esm.isNextSub("WNAM")) + { + esm.skipHSubSize(81); + } + if (esm.isNextSub("VCLR")) + { + esm.skipHSubSize(12675); + } + if (esm.isNextSub("VTEX")) + { + esm.skipHSubSize(512); + cnt++; + } // We need all three of VNML, VHGT and VTEX in order to use the // landscape. hasData = (cnt == 3); + + dataLoaded = false; + landData = NULL; } + +void Land::loadData(ESMReader &esm) +{ + if (dataLoaded) + { + return; + } + + landData = new LandData; + + if (hasData) + { + esm.restoreContext(context); + + //esm.getHNExact(landData->normals, sizeof(VNML), "VNML"); + if (esm.isNextSub("VNML")) + { + esm.skipHSubSize(12675); + } + + VHGT rawHeights; + + esm.getHNExact(&rawHeights, sizeof(VHGT), "VHGT"); + int currentHeightOffset = rawHeights.heightOffset; + for (int y = 0; y < LAND_SIZE; y++) + { + currentHeightOffset += rawHeights.heightData[y * LAND_SIZE]; + landData->heights[y * LAND_SIZE] = currentHeightOffset * HEIGHT_SCALE; + + int tempOffset = currentHeightOffset; + for (int x = 1; x < LAND_SIZE; x++) + { + tempOffset += rawHeights.heightData[y * LAND_SIZE + x]; + landData->heights[x + y * LAND_SIZE] = tempOffset * HEIGHT_SCALE; + } + } + } + else + { + for (int i = 0; i < LAND_NUM_VERTS; i++) + { + landData->heights[i] = -256.0f * HEIGHT_SCALE; + } + } + + dataLoaded = true; +} + +void Land::unloadData() +{ + if (dataLoaded) + { + delete landData; + landData = NULL; + dataLoaded = false; + } +} + } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index af91850ac..898e7f529 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -21,7 +21,51 @@ struct Land bool hasData; + bool dataLoaded; + + // number of vertices per side + static const int LAND_SIZE = 65; + + // cell terrain size in world coords + static const int REAL_SIZE = 8192; + + // total number of vertices + static const int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE; + + static const int HEIGHT_SCALE = 8; + +#pragma pack(push,1) + struct VHGT + { + float heightOffset; + int8_t heightData[LAND_NUM_VERTS]; + short unknown1; + char unknown2; + }; +#pragma pack(pop) + + typedef uint8_t VNML[LAND_NUM_VERTS * 3]; + + struct LandData + { + float heightOffset; + float heights[LAND_NUM_VERTS]; + //float normals[LAND_NUM_VERTS * 3]; + }; + + LandData *landData; + void load(ESMReader &esm); + + /** + * Actually loads data + */ + void loadData(ESMReader &esm); + + /** + * Frees memory allocated for land data + */ + void unloadData(); }; } #endif diff --git a/components/esm_store/cell_store.hpp b/components/esm_store/cell_store.hpp index d064312f1..7c2ee48fb 100644 --- a/components/esm_store/cell_store.hpp +++ b/components/esm_store/cell_store.hpp @@ -96,7 +96,8 @@ namespace ESMS State_Unloaded, State_Preloaded, State_Loaded }; - CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded) {} + CellStore (const ESM::Cell *cell_) : cell (cell_), mState (State_Unloaded), + land(NULL) {} const ESM::Cell *cell; State mState; @@ -124,6 +125,8 @@ namespace ESMS CellRefList statics; CellRefList weapons; + const Land* land; + void load (const ESMStore &store, ESMReader &esm) { if (mState!=State_Loaded) @@ -135,6 +138,11 @@ namespace ESMS loadRefs (store, esm); + if ( ! (cell->data.flags & ESM::Cell::Interior) ) + { + loadTerrain(cell->data.gridX, cell->data.gridY, store, esm); + } + mState = State_Loaded; } } @@ -180,6 +188,29 @@ namespace ESMS private: + void loadTerrain(int X, int Y, const ESMStore &store, ESMReader &esm) + { + // load terrain + Land *land = store.lands.search(X, Y); + if (land != NULL) + { + land->loadData(esm); + } + + this->land = land; + } + + void unloadTerrain(int X, int Y, const ESMStore &store) { + Land *land = store.lands.search(X,Y); + // unload terrain + if (land != NULL) + { + land->unloadData(); + } + + this->land = NULL; + } + template bool forEachImp (Functor& functor, List& list) { diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 20a2e8ff9..e150f1085 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -235,7 +235,7 @@ namespace ESMS virtual void listIdentifier (std::vector& identifier) const {} // Find land for the given coordinates. Return null if no data. - const Land *search(int x, int y) const + Land *search(int x, int y) const { Lands::const_iterator it = lands.find(x); if(it==lands.end()) diff --git a/components/esm_store/store.hpp b/components/esm_store/store.hpp index e3bbf9e82..83cd5ab16 100644 --- a/components/esm_store/store.hpp +++ b/components/esm_store/store.hpp @@ -115,7 +115,7 @@ namespace ESMS recLists[REC_GLOB] = &globals; recLists[REC_GMST] = &gameSettings; recLists[REC_INGR] = &ingreds; - //recLists[REC_LAND] = &lands; + recLists[REC_LAND] = &lands; recLists[REC_LEVC] = &creatureLists; recLists[REC_LEVI] = &itemLists; recLists[REC_LIGH] = &lights; diff --git a/files/plugins.cfg.linux b/files/plugins.cfg.linux index b6e104351..2921153b4 100644 --- a/files/plugins.cfg.linux +++ b/files/plugins.cfg.linux @@ -7,6 +7,6 @@ PluginFolder=${OGRE_PLUGIN_DIR} Plugin=RenderSystem_GL Plugin=Plugin_ParticleFX Plugin=Plugin_OctreeSceneManager -# Plugin=Plugin_CgProgramManager +Plugin=Plugin_CgProgramManager diff --git a/files/plugins.cfg.mac b/files/plugins.cfg.mac index baaca4479..0c16bddaf 100644 --- a/files/plugins.cfg.mac +++ b/files/plugins.cfg.mac @@ -7,6 +7,6 @@ PluginFolder= Plugin=RenderSystem_GL.dylib Plugin=Plugin_ParticleFX.dylib Plugin=Plugin_OctreeSceneManager.dylib -# Plugin=Plugin_CgProgramManager +Plugin=Plugin_CgProgramManager