From 637302fc87a85fb3c67574861baef406734a7076 Mon Sep 17 00:00:00 2001 From: Jacob Essex Date: Sat, 21 Jan 2012 17:59:12 +0000 Subject: [PATCH] Added blended textures to the terrain --- apps/openmw/mwrender/terrain.cpp | 150 ++++++++++++++++++++++++++++ apps/openmw/mwrender/terrain.hpp | 28 ++++++ components/esm/loadland.cpp | 19 ++++ components/esm/loadland.hpp | 8 ++ components/esm_store/cell_store.hpp | 2 + 5 files changed, 207 insertions(+) diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp index 44c499130..4d13b6889 100644 --- a/apps/openmw/mwrender/terrain.cpp +++ b/apps/openmw/mwrender/terrain.cpp @@ -48,9 +48,13 @@ namespace MWRender terrainData.inputBias = 0; terrainData.inputFloat = store->land->landData->heights; + std::map indexes; + initTerrainTextures(&terrainData, store, indexes); mTerrainGroup->defineTerrain(x, y, &terrainData); mTerrainGroup->loadTerrain(x, y, true); + Ogre::Terrain* terrain = mTerrainGroup->getTerrain(x,y); + initTerrainBlendMaps(terrain, store, indexes); } void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) @@ -59,4 +63,150 @@ namespace MWRender store->cell->getGridY()); } + void TerrainManager::initTerrainTextures(Ogre::Terrain::ImportData* terrainData, + MWWorld::Ptr::CellStore* store, + std::map& indexes) + { + //have a base texture for now, but this is probably not needed on most cells + terrainData->layerList.resize(1); + terrainData->layerList[0].worldSize = 256; + terrainData->layerList[0].textureNames.push_back("textures\\_land_default.dds"); + terrainData->layerList[0].textureNames.push_back("textures\\_land_default.dds"); + + const uint16_t* const textures = store->land->landData->textures; + for ( int y = 0; y < ESM::Land::LAND_TEXTURE_SIZE; y++ ) + { + for ( int x = 0; x < ESM::Land::LAND_TEXTURE_SIZE; x++ ) + { + const uint16_t ltexIndex = textures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if ( ltexIndex == 0 ) + { + continue; + } + + const std::map::const_iterator it = indexes.find(ltexIndex); + + if ( it == indexes.end() ) + { + //NB: All vtex ids are +1 compared to the ltex ids + assert((int)ltexIndex - 1 > 0 && + store->landTextures->ltex.size() > (size_t)ltexIndex - 1 && + "LAND.VTEX must be within the bounds of the LTEX array"); + + std::string texture = store->landTextures->ltex[ltexIndex-1].texture; + //TODO this is needed due to MWs messed up texture handling + texture = texture.substr(0, texture.rfind(".")) + ".dds"; + + const size_t position = terrainData->layerList.size(); + terrainData->layerList.push_back(Ogre::Terrain::LayerInstance()); + + terrainData->layerList[position].worldSize = 256; + terrainData->layerList[position].textureNames.push_back("textures\\" + texture); + + //Normal map. This should be removed but it would require alterations to + //the material generator. Another option would be to use a 1x1 blank texture + terrainData->layerList[position].textureNames.push_back("textures\\" + texture); + + indexes[ltexIndex] = position; + } + } + } + + } + + void TerrainManager::initTerrainBlendMaps(Ogre::Terrain* terrain, + MWWorld::Ptr::CellStore* store, + const std::map& indexes) + { + const int blendSize = terrain->getLayerBlendMapSize(); + const int blendDist = TERRAIN_SHADE_DISTANCE * + (blendSize / ESM::Land::LAND_TEXTURE_SIZE); + + //zero out every map + std::map::const_iterator iter; + for ( iter = indexes.begin(); iter != indexes.end(); ++iter ) + { + float* pBlend = terrain->getLayerBlendMap(iter->second) + ->getBlendPointer(); + memset(pBlend, 0, sizeof(float) * blendSize * blendSize); + } + + //covert the ltex data into a set of blend maps + const uint16_t* const textures = store->land->landData->textures; + for ( int texY = 0; texY < ESM::Land::LAND_TEXTURE_SIZE; texY++ ) + { + for ( int texX = 0; texX < ESM::Land::LAND_TEXTURE_SIZE; texX++ ) + { + const uint16_t ltexIndex = textures[texY * ESM::Land::LAND_TEXTURE_SIZE + texX]; + if ( ltexIndex == 0 ){ + continue; + } + const int layerIndex = indexes.find(ltexIndex)->second; + + float* const pBlend = terrain->getLayerBlendMap(layerIndex) + ->getBlendPointer(); + + const int splatSize = blendSize / ESM::Land::LAND_TEXTURE_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 startY = std::max(0, texY*splatSize - blendDist); + const int endY = std::min(blendSize, (texY+1)*splatSize + blendDist); + + for ( int blendX = startX; blendX < endX; blendX++ ) + { + for ( int blendY = startY; blendY < endY; blendY++ ) + { + assert(blendX >= 0 && blendX < blendSize && + "index must be within texture bounds"); + + assert(blendY >= 0 && blendY < blendSize && + "index must be within texture bounds"); + + //calculate the distance from the edge of the square + // to the point we are shading + int distX = texX*splatSize - blendX; + if ( distX < 0 ) + { + distX = std::max(0, blendX - (texX+1)*splatSize); + } + + int distY = texY*splatSize - blendY; + if ( distY < 0 ) + { + distY = std::max(0, blendY - (texY+1)*splatSize); + } + + float blendAmount = blendDist - std::sqrt((float)distX*distX + distY*distY); + blendAmount /= blendDist; + + //this is required as blendDist < sqrt(blendDist*blendDist + blendDist*blendDist) + //this means that the corners are slightly the "wrong" shape but totaly smooth + //shading for the edges + blendAmount = std::max( (float) 0.0, blendAmount); + + assert(blendAmount >= 0 && "Blend should never be negative"); + + //flips the y + const int index = blendSize*(blendSize - 1 - blendY) + blendX; + pBlend[index] += blendAmount; + pBlend[index] = std::min((float)1, pBlend[index]); + } + } + + } + } + + //update the maps + for ( iter = indexes.begin(); iter != indexes.end(); ++iter ) + { + Ogre::TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(iter->second); + blend->dirty(); + blend->update(); + } + + } + } diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp index 0a8b1ace0..127c3f5f2 100644 --- a/apps/openmw/mwrender/terrain.hpp +++ b/apps/openmw/mwrender/terrain.hpp @@ -1,12 +1,15 @@ #ifndef _GAME_RENDER_TERRAIN_H #define _GAME_RENDER_TERRAIN_H +#include + #include "../mwworld/ptr.hpp" namespace Ogre{ class SceneManager; class TerrainGroup; class TerrainGlobalOptions; + class Terrain; } namespace MWRender{ @@ -24,6 +27,31 @@ namespace MWRender{ private: Ogre::TerrainGlobalOptions* mTerrainGlobals; Ogre::TerrainGroup* mTerrainGroup; + + /** + * 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 + */ + static const float TERRAIN_SHADE_DISTANCE = 0.5; + + /** + * Setups up the list of textures for the cell + * @param terrainData the terrain data to setup the textures for + * @param indexes a mapping of ltex index to the terrain texture layer that + * can be used by initTerrainBlendMaps + */ + void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, + MWWorld::Ptr::CellStore* store, + std::map& indexes); + + /** + * Creates the blend (splatting maps) for the given terrain from the ltex data. + * @param terrain the terrain object for the current cell + * @param indexes the mapping of ltex to blend map produced by initTerrainTextures + */ + void initTerrainBlendMaps(Ogre::Terrain* terrain, + MWWorld::Ptr::CellStore* store, + const std::map& indexes); }; } diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index b8de98f0a..1d670036e 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -86,6 +86,25 @@ void Land::loadData(ESMReader &esm) landData->heights[x + y * LAND_SIZE] = tempOffset * HEIGHT_SCALE; } } + + if (esm.isNextSub("WNAM")) + { + esm.skipHSubSize(81); + } + if (esm.isNextSub("VCLR")) + { + esm.skipHSubSize(12675); + } + //TODO fix magic numbers + uint16_t vtex[512]; + esm.getHNExact(&vtex, 512, "VTEX"); + + int readPos = 0; //bit ugly, but it works + for ( int y1 = 0; y1 < 4; y1++ ) + for ( int x1 = 0; x1 < 4; x1++ ) + for ( int y2 = 0; y2 < 4; y2++) + for ( int x2 = 0; x2 < 4; x2++ ) + landData->textures[(y1*4+y2)*16+(x1*4+x2)] = vtex[readPos++]; } else { diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 898e7f529..4219f3eb6 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -34,6 +34,12 @@ struct Land static const int HEIGHT_SCALE = 8; + //number of textures per side of land + static const int LAND_TEXTURE_SIZE = 16; + + //total number of textures per land + static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE; + #pragma pack(push,1) struct VHGT { @@ -51,6 +57,8 @@ struct Land float heightOffset; float heights[LAND_NUM_VERTS]; //float normals[LAND_NUM_VERTS * 3]; + uint16_t textures[LAND_NUM_TEXTURES]; + char colours[3 * LAND_NUM_VERTS]; }; LandData *landData; diff --git a/components/esm_store/cell_store.hpp b/components/esm_store/cell_store.hpp index 7c2ee48fb..5310237fe 100644 --- a/components/esm_store/cell_store.hpp +++ b/components/esm_store/cell_store.hpp @@ -126,6 +126,7 @@ namespace ESMS CellRefList weapons; const Land* land; + const LTexList* landTextures; void load (const ESMStore &store, ESMReader &esm) { @@ -141,6 +142,7 @@ namespace ESMS if ( ! (cell->data.flags & ESM::Cell::Interior) ) { loadTerrain(cell->data.gridX, cell->data.gridY, store, esm); + landTextures = &store.landTexts; } mState = State_Loaded;