mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-19 19:53:53 +00:00
Added blended textures to the terrain
This commit is contained in:
parent
18108c02a7
commit
637302fc87
5 changed files with 207 additions and 0 deletions
|
@ -48,9 +48,13 @@ namespace MWRender
|
|||
terrainData.inputBias = 0;
|
||||
terrainData.inputFloat = store->land->landData->heights;
|
||||
|
||||
std::map<uint16_t, int> 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<uint16_t, int>& 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<uint16_t, int>::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<uint16_t, int>& indexes)
|
||||
{
|
||||
const int blendSize = terrain->getLayerBlendMapSize();
|
||||
const int blendDist = TERRAIN_SHADE_DISTANCE *
|
||||
(blendSize / ESM::Land::LAND_TEXTURE_SIZE);
|
||||
|
||||
//zero out every map
|
||||
std::map<uint16_t, int>::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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
#ifndef _GAME_RENDER_TERRAIN_H
|
||||
#define _GAME_RENDER_TERRAIN_H
|
||||
|
||||
#include <OgreTerrain.h>
|
||||
|
||||
#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<uint16_t, int>& 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<uint16_t, int>& indexes);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -126,6 +126,7 @@ namespace ESMS
|
|||
CellRefList<Weapon, D> 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;
|
||||
|
|
Loading…
Reference in a new issue