diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 90dc917313..70ecdee73d 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "terrain.hpp" @@ -7,64 +8,167 @@ namespace MWRender { + + //---------------------------------------------------------------------------------------------- + TerrainManager::TerrainManager(Ogre::SceneManager* mgr) { mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions(); mTerrainGlobals->setMaxPixelError(8); + mTerrainGlobals->setLayerBlendMapSize(SPLIT_TERRAIN ? 1024 : 256); + + Ogre::TerrainMaterialGenerator::Profile* const activeProfile = + mTerrainGlobals->getDefaultMaterialGenerator() + ->getActiveProfile(); + Ogre::TerrainMaterialGeneratorA::SM2Profile* matProfile = + static_cast(activeProfile); + + matProfile->setLightmapEnabled(false); + matProfile->setReceiveDynamicShadowsEnabled(false); + + mLandSize = ESM::Land::LAND_SIZE; + mRealSize = ESM::Land::REAL_SIZE; + if ( SPLIT_TERRAIN ) + { + mLandSize = (mLandSize - 1)/2 + 1; + mRealSize /= 2; + } mTerrainGroup = OGRE_NEW Ogre::TerrainGroup(mgr, - Ogre::Terrain::ALIGN_X_Z, ESM::Land::LAND_SIZE, - ESM::Land::REAL_SIZE); + Ogre::Terrain::ALIGN_X_Z, + mLandSize, + mRealSize); - mTerrainGroup->setOrigin(Ogre::Vector3(ESM::Land::REAL_SIZE/2, - 0, - -ESM::Land::REAL_SIZE/2)); + mTerrainGroup->setOrigin(Ogre::Vector3(mRealSize/2, + 0, + -mRealSize/2)); - Ogre::Terrain::ImportData importSettings = + Ogre::Terrain::ImportData& importSettings = mTerrainGroup->getDefaultImportSettings(); - importSettings.terrainSize = ESM::Land::LAND_SIZE; - importSettings.worldSize = ESM::Land::REAL_SIZE; + importSettings.inputBias = 0; + importSettings.terrainSize = mLandSize; + importSettings.worldSize = mRealSize; importSettings.minBatchSize = 9; - importSettings.maxBatchSize = 33; + importSettings.maxBatchSize = mLandSize; - importSettings.deleteInputData = false; + importSettings.deleteInputData = true; } + //---------------------------------------------------------------------------------------------- + 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(); + const int cellX = store->cell->getGridX(); + const int cellY = store->cell->getGridY(); - Ogre::Terrain::ImportData terrainData; + Ogre::Terrain::ImportData terrainData = + mTerrainGroup->getDefaultImportSettings(); - terrainData.inputBias = 0; - terrainData.inputFloat = store->land[1][1]->landData->heights; + if ( SPLIT_TERRAIN ) + { + //split the cell terrain into four segments + const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2; - std::map indexes; - initTerrainTextures(&terrainData, store, 0, 0, - ESM::Land::LAND_TEXTURE_SIZE, indexes); - mTerrainGroup->defineTerrain(x, y, &terrainData); + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + const int terrainX = cellX * 2 + x; + const int terrainY = cellY * 2 + y; + + terrainData.inputFloat = OGRE_ALLOC_T(float, + mLandSize*mLandSize, + Ogre::MEMCATEGORY_GEOMETRY); + + //copy the height data row by row + for ( int terrainCopyY = 0; terrainCopyY < mLandSize; terrainCopyY++ ) + { + //the offset of the current segment + const size_t yOffset = y * (mLandSize-1) * ESM::Land::LAND_SIZE + + //offset of the row + terrainCopyY * ESM::Land::LAND_SIZE; + const size_t xOffset = x * (mLandSize-1); + + memcpy(&terrainData.inputFloat[terrainCopyY*mLandSize], + &store->land[1][1]->landData->heights[yOffset + xOffset], + mLandSize*sizeof(float)); + } + + std::map indexes; + initTerrainTextures(&terrainData, store, + x * numTextures, y * numTextures, + numTextures, indexes); + + mTerrainGroup->defineTerrain(terrainX, terrainY, &terrainData); + + mTerrainGroup->loadTerrain(terrainX, terrainY, true); + Ogre::Terrain* terrain = mTerrainGroup->getTerrain(terrainX, terrainY); + initTerrainBlendMaps(terrain, store, + x * numTextures, y * numTextures, + numTextures, indexes); + + } + } + } + else + { + //one cell is one terrain segment + terrainData.inputFloat = OGRE_ALLOC_T(float, + mLandSize*mLandSize, + Ogre::MEMCATEGORY_GEOMETRY); + + memcpy(&terrainData.inputFloat[0], + &store->land[1][1]->landData->heights[0], + mLandSize*mLandSize*sizeof(float)); + + std::map indexes; + initTerrainTextures(&terrainData, store, 0, 0, + ESM::Land::LAND_TEXTURE_SIZE, indexes); + + mTerrainGroup->defineTerrain(cellX, cellY, &terrainData); + + mTerrainGroup->loadTerrain(cellX, cellY, true); + Ogre::Terrain* terrain = mTerrainGroup->getTerrain(cellX, cellY); + initTerrainBlendMaps(terrain, store, 0, 0, + ESM::Land::LAND_TEXTURE_SIZE, indexes); + } - mTerrainGroup->loadTerrain(x, y, true); - Ogre::Terrain* terrain = mTerrainGroup->getTerrain(x,y); - initTerrainBlendMaps(terrain, store, 0, 0, - ESM::Land::LAND_TEXTURE_SIZE, indexes); } + //---------------------------------------------------------------------------------------------- + void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) { - mTerrainGroup->removeTerrain(store->cell->getGridX(), - store->cell->getGridY()); + if ( SPLIT_TERRAIN ) + { + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + mTerrainGroup->removeTerrain(store->cell->getGridX() * 2 + x, + store->cell->getGridY() * 2 + y); + } + } + } + else + { + mTerrainGroup->removeTerrain(store->cell->getGridX(), + store->cell->getGridY()); + } } + //---------------------------------------------------------------------------------------------- + void TerrainManager::initTerrainTextures(Ogre::Terrain::ImportData* terrainData, MWWorld::Ptr::CellStore* store, int fromX, int fromY, int size, @@ -122,9 +226,10 @@ namespace MWRender } } } - } + //---------------------------------------------------------------------------------------------- + void TerrainManager::initTerrainBlendMaps(Ogre::Terrain* terrain, MWWorld::Ptr::CellStore* store, int fromX, int fromY, int size, @@ -157,16 +262,24 @@ namespace MWRender //covert the ltex data into a set of blend maps for ( int texY = fromY - 1; texY < fromY + size + 1; texY++ ) { - for ( int texX = fromY - 1; texX < fromY + size + 1; texX++ ) + for ( int texX = fromX - 1; texX < fromX + size + 1; texX++ ) { const uint16_t ltexIndex = getLtexIndexAt(store, texX, texY); + //whilte texX is the splat index relative to the entire cell, + //relX is relative to the current segment we are splatting + const int relX = texX - fromX; + const int relY = texY - fromY; + //this is the ground texture, which is currently the base texture //so don't alter the splatting map if ( ltexIndex == 0 ){ continue; } + assert (indexes.find(ltexIndex) != indexes.end() && + "Texture layer must exist"); + const int layerIndex = indexes.find(ltexIndex)->second; float* const pBlend = terrain->getLayerBlendMap(layerIndex) @@ -175,11 +288,11 @@ namespace MWRender const int splatSize = blendSize / size; //setup the bounds for the shading of this texture splat - const int startX = std::max(0, texX*splatSize - blendDist); - const int endX = std::min(blendSize, (texX+1)*splatSize + blendDist); + const int startX = std::max(0, relX*splatSize - blendDist); + const int endX = std::min(blendSize, (relX+1)*splatSize + blendDist); - const int startY = std::max(0, texY*splatSize - blendDist); - const int endY = std::min(blendSize, (texY+1)*splatSize + blendDist); + const int startY = std::max(0, relY*splatSize - blendDist); + const int endY = std::min(blendSize, (relY+1)*splatSize + blendDist); for ( int blendX = startX; blendX < endX; blendX++ ) { @@ -193,16 +306,16 @@ namespace MWRender //calculate the distance from the edge of the square // to the point we are shading - int distX = texX*splatSize - blendX; + int distX = relX*splatSize - blendX; if ( distX < 0 ) { - distX = std::max(0, blendX - (texX+1)*splatSize); + distX = std::max(0, blendX - (relX+1)*splatSize); } - int distY = texY*splatSize - blendY; + int distY = relY*splatSize - blendY; if ( distY < 0 ) { - distY = std::max(0, blendY - (texY+1)*splatSize); + distY = std::max(0, blendY - (relY+1)*splatSize); } float blendAmount = blendDist - std::sqrt((float)distX*distX + distY*distY); @@ -235,6 +348,7 @@ namespace MWRender } + //---------------------------------------------------------------------------------------------- int TerrainManager::getLtexIndexAt(MWWorld::Ptr::CellStore* store, int x, int y) diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index c64b741d57..195741b0f7 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -16,6 +16,15 @@ namespace MWRender{ /** * Implements the Morrowind terrain using the Ogre Terrain Component + * + * This currently has two options as to how the terrain is rendered, one + * is that one cell is rendered as one Ogre::Terrain and the other that + * it is rendered as 4 Ogre::Terrain segments + * + * Splitting it up into segments has the following advantages + * * Seems to be faster + * * Terrain can now be culled more aggressivly using view frustram culling + * * We don't hit splat limits as much */ class TerrainManager{ public: @@ -28,6 +37,27 @@ namespace MWRender{ Ogre::TerrainGlobalOptions* mTerrainGlobals; Ogre::TerrainGroup* mTerrainGroup; + /** + * Should each cell be split into a further four Ogre::Terrain objects + * + * This has the advantage that it is possible to cull more terrain and + * we are more likly to be able to be able to fit all the required splats + * in (Ogre's default material generator only works with about 6 textures) + */ + static const bool SPLIT_TERRAIN = true; + + /** + * The length in verticies of a single terrain block. + * This takes into account the SPLIT_TERRAIN option + */ + int mLandSize; + + /** + * The length in game units of a single terrain block. + * This takes into account the SPLIT_TERRAIN option + */ + int mRealSize; + /** * The distance that the current cell should be shaded into the neighbouring * texture. The distance is in terms of the splat size of a texture