mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 11:49:56 +00:00
f34b2c73c5
be almost impossible to make a derived class of TerrainMaterialGeneratorA because of the many classes it contains and the inter-relationships between them; just adding the whole source makes it a lot easier to modify if we decide to update this source from OGRE upstream at any point (which seems unlikely), we can take the diff from this commit on to see the changes we did to the material generator
409 lines
17 KiB
C++
409 lines
17 KiB
C++
#include <OgreTerrain.h>
|
|
#include <OgreTerrainGroup.h>
|
|
|
|
#include "terrainmaterial.hpp"
|
|
#include "terrain.hpp"
|
|
|
|
#include "components/esm/loadland.hpp"
|
|
|
|
using namespace Ogre;
|
|
|
|
namespace MWRender
|
|
{
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
|
|
TerrainManager::TerrainManager(Ogre::SceneManager* mgr)
|
|
{
|
|
mTerrainGlobals = OGRE_NEW Ogre::TerrainGlobalOptions();
|
|
|
|
mTerrainGlobals->setMaxPixelError(8);
|
|
mTerrainGlobals->setLayerBlendMapSize(SPLIT_TERRAIN ? 1024 : 256);
|
|
|
|
Ogre::TerrainMaterialGeneratorPtr matGen;
|
|
TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB();
|
|
matGen.bind(matGenP);
|
|
mTerrainGlobals->setDefaultMaterialGenerator(matGen);
|
|
|
|
Ogre::TerrainMaterialGenerator::Profile* const activeProfile =
|
|
mTerrainGlobals->getDefaultMaterialGenerator()
|
|
->getActiveProfile();
|
|
TerrainMaterialGeneratorB::SM2Profile* matProfile =
|
|
static_cast<TerrainMaterialGeneratorB::SM2Profile*>(activeProfile);
|
|
|
|
matProfile->setLightmapEnabled(false);
|
|
matProfile->setReceiveDynamicShadowsEnabled(false);
|
|
matProfile->setLayerNormalMappingEnabled(false);
|
|
//matProfile->setLayerParallaxMappingEnabled(false);
|
|
matProfile->setLayerSpecularMappingEnabled(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,
|
|
mLandSize,
|
|
mRealSize);
|
|
|
|
mTerrainGroup->setOrigin(Ogre::Vector3(mRealSize/2,
|
|
0,
|
|
-mRealSize/2));
|
|
|
|
Ogre::Terrain::ImportData& importSettings =
|
|
mTerrainGroup->getDefaultImportSettings();
|
|
|
|
importSettings.inputBias = 0;
|
|
importSettings.terrainSize = mLandSize;
|
|
importSettings.worldSize = mRealSize;
|
|
importSettings.minBatchSize = 9;
|
|
importSettings.maxBatchSize = mLandSize;
|
|
|
|
importSettings.deleteInputData = true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
|
|
TerrainManager::~TerrainManager()
|
|
{
|
|
OGRE_DELETE mTerrainGroup;
|
|
OGRE_DELETE mTerrainGlobals;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
|
|
void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store)
|
|
{
|
|
const int cellX = store->cell->getGridX();
|
|
const int cellY = store->cell->getGridY();
|
|
|
|
Ogre::Terrain::ImportData terrainData =
|
|
mTerrainGroup->getDefaultImportSettings();
|
|
|
|
if ( SPLIT_TERRAIN )
|
|
{
|
|
//split the cell terrain into four segments
|
|
const int numTextures = ESM::Land::LAND_TEXTURE_SIZE/2;
|
|
|
|
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<uint16_t, int> 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<uint16_t, int> 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);
|
|
}
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
|
|
void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store)
|
|
{
|
|
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,
|
|
std::map<uint16_t, int>& indexes)
|
|
{
|
|
assert(store != NULL && "store must be a valid pointer");
|
|
assert(terrainData != NULL && "Must have valid terrain data");
|
|
assert(fromX >= 0 && fromY >= 0 &&
|
|
"Can't get a terrain texture on terrain outside the current cell");
|
|
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
|
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
|
"Can't get a terrain texture on terrain outside the current cell");
|
|
|
|
//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");
|
|
|
|
for ( int y = fromY - 1; y < fromY + size + 1; y++ )
|
|
{
|
|
for ( int x = fromX - 1; x < fromX + size + 1; x++ )
|
|
{
|
|
const uint16_t ltexIndex = getLtexIndexAt(store, x, y);
|
|
//this is the base texture, so we can ignore this at present
|
|
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 >= 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,
|
|
int fromX, int fromY, int size,
|
|
const std::map<uint16_t, int>& indexes)
|
|
{
|
|
assert(store != NULL && "store must be a valid pointer");
|
|
assert(terrain != NULL && "Must have valid terrain");
|
|
assert(fromX >= 0 && fromY >= 0 &&
|
|
"Can't get a terrain texture on terrain outside the current cell");
|
|
assert(fromX+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
|
fromY+size <= ESM::Land::LAND_TEXTURE_SIZE &&
|
|
"Can't get a terrain texture on terrain outside the current cell");
|
|
|
|
//size must be a power of 2 as we do divisions with a power of 2 number
|
|
//that need to result in an integer for correct splatting
|
|
assert( (size & (size - 1)) == 0 && "Size must be a power of 2");
|
|
|
|
const int blendSize = terrain->getLayerBlendMapSize();
|
|
const int blendDist = TERRAIN_SHADE_DISTANCE * (blendSize / 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
|
|
for ( int texY = fromY - 1; texY < fromY + size + 1; texY++ )
|
|
{
|
|
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)
|
|
->getBlendPointer();
|
|
|
|
const int splatSize = blendSize / size;
|
|
|
|
//setup the bounds for the shading of this texture splat
|
|
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, relY*splatSize - blendDist);
|
|
const int endY = std::min(blendSize, (relY+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 = relX*splatSize - blendX;
|
|
if ( distX < 0 )
|
|
{
|
|
distX = std::max(0, blendX - (relX+1)*splatSize);
|
|
}
|
|
|
|
int distY = relY*splatSize - blendY;
|
|
if ( distY < 0 )
|
|
{
|
|
distY = std::max(0, blendY - (relY+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();
|
|
}
|
|
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------------------
|
|
|
|
int TerrainManager::getLtexIndexAt(MWWorld::Ptr::CellStore* store,
|
|
int x, int y)
|
|
{
|
|
//check texture index falls within the 9 cell bounds
|
|
//as this function can't cope with anything above that
|
|
assert(x >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
|
y >= -ESM::Land::LAND_TEXTURE_SIZE &&
|
|
"Trying to get land textures that are out of bounds");
|
|
|
|
assert(x < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
|
y < 2*ESM::Land::LAND_TEXTURE_SIZE &&
|
|
"Trying to get land textures that are out of bounds");
|
|
|
|
assert(store != NULL && "Store pointer must be valid");
|
|
|
|
//default center cell is indexed at (1,1)
|
|
int cellX = 1;
|
|
int cellY = 1;
|
|
|
|
if ( x < 0 )
|
|
{
|
|
cellX--;
|
|
x += ESM::Land::LAND_TEXTURE_SIZE;
|
|
}
|
|
else if ( x >= ESM::Land::LAND_TEXTURE_SIZE )
|
|
{
|
|
cellX++;
|
|
x -= ESM::Land::LAND_TEXTURE_SIZE;
|
|
}
|
|
|
|
if ( y < 0 )
|
|
{
|
|
cellY--;
|
|
y += ESM::Land::LAND_TEXTURE_SIZE;
|
|
}
|
|
else if ( y >= ESM::Land::LAND_TEXTURE_SIZE )
|
|
{
|
|
cellY++;
|
|
y -= ESM::Land::LAND_TEXTURE_SIZE;
|
|
}
|
|
|
|
return store->land[cellX][cellY]
|
|
->landData
|
|
->textures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
|
|
}
|
|
|
|
}
|