diff --git a/CMakeLists.txt b/CMakeLists.txt index a95539bef..343ec46ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -181,6 +181,7 @@ ENDIF(WIN32) ENDIF(OGRE_STATIC) include_directories("." ${OGRE_INCLUDE_DIR} ${OGRE_INCLUDE_DIR}/Ogre ${OGRE_INCLUDE_DIR}/OGRE ${OGRE_PLUGIN_INCLUDE_DIRS} + ${OGRE_Terrain_INCLUDE_DIR} ${OIS_INCLUDE_DIRS} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} ${MYGUI_INCLUDE_DIRS} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 75bc57b0f..2d49a414c 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,7 +15,7 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky player animation npcanimation creatureanimation actors objects - renderinginterface localmap water + renderinginterface localmap water terrain terrainmaterial ) add_openmw_dir (mwinput @@ -82,6 +82,7 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} + ${OGRE_Terrain_LIBRARY} ${OGRE_STATIC_PLUGINS} ${OIS_LIBRARIES} ${Boost_LIBRARIES} diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index d6c4ce4e5..ac6681e27 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -43,9 +43,6 @@ DialogueWindow::DialogueWindow(WindowManager& parWindowManager,MWWorld::Environm // Centre dialog center(); - //WindowManager *wm = environment.mWindowManager; - setText("NpcName", "Name of character"); - //History view getWidget(history, "History"); history->setOverflowToTheLeft(true); @@ -116,7 +113,8 @@ void DialogueWindow::onSelectTopic(MyGUI::ListBox* _sender, size_t _index) void DialogueWindow::startDialogue(std::string npcName) { - setText("NpcName", npcName); + static_cast(mMainWidget)->setCaption(npcName); + adjustWindowCaption(); } void DialogueWindow::setKeywords(std::list keyWords) diff --git a/apps/openmw/mwgui/window_manager.cpp b/apps/openmw/mwgui/window_manager.cpp index a04e2dcb8..465467469 100644 --- a/apps/openmw/mwgui/window_manager.cpp +++ b/apps/openmw/mwgui/window_manager.cpp @@ -371,7 +371,6 @@ void WindowManager::updateSkillArea() void WindowManager::removeDialog(OEngine::GUI::Layout*dialog) { - std::cout << "dialogue a la poubelle"; assert(dialog); if (!dialog) return; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 5355306c8..64e83d632 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -23,6 +23,12 @@ RenderingManager::RenderingManager (OEngine::Render::OgreRenderer& _rend, const :mRendering(_rend), mObjects(mRendering), mActors(mRendering, environment), mAmbientMode(0) { mRendering.createScene("PlayerCam", 55, 5); + mTerrainManager = new TerrainManager(mRendering.getScene(), + environment); + + //The fog type must be set before any terrain objects are created as if the + //fog type is set to FOG_NONE then the initially created terrain won't have any fog + configureFog(1, ColourValue(1,1,1)); // Set default mipmap level (NB some APIs ignore this) TextureManager::getSingleton().setDefaultNumMipmaps(5); @@ -70,6 +76,7 @@ RenderingManager::~RenderingManager () delete mPlayer; delete mSkyManager; delete mDebugging; + delete mTerrainManager; delete mLocalMap; } @@ -99,6 +106,8 @@ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) mObjects.removeCell(store); mActors.removeCell(store); mDebugging->cellRemoved(store); + if (store->cell->isExterior()) + mTerrainManager->cellRemoved(store); } void RenderingManager::removeWater () @@ -119,6 +128,8 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { mObjects.buildStaticGeometry (*store); mDebugging->cellAdded(store); + if (store->cell->isExterior()) + mTerrainManager->cellAdded(store); } void RenderingManager::addObject (const MWWorld::Ptr& ptr){ @@ -280,17 +291,17 @@ void RenderingManager::setAmbientMode() { case 0: - mRendering.getScene()->setAmbientLight(mAmbientColor); + setAmbientColour(mAmbientColor); break; case 1: - mRendering.getScene()->setAmbientLight(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); + setAmbientColour(0.7f*mAmbientColor + 0.3f*ColourValue(1,1,1)); break; case 2: - mRendering.getScene()->setAmbientLight(ColourValue(1,1,1)); + setAmbientColour(ColourValue(1,1,1)); break; } } @@ -350,11 +361,13 @@ void RenderingManager::skipAnimation (const MWWorld::Ptr& ptr) void RenderingManager::setSunColour(const Ogre::ColourValue& colour) { mSun->setDiffuseColour(colour); + mTerrainManager->setDiffuse(colour); } void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) { mRendering.getScene()->setAmbientLight(colour); + mTerrainManager->setAmbient(colour); } void RenderingManager::sunEnable() diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 08f760f97..2ba52588d 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" @@ -81,7 +82,7 @@ class RenderingManager: private RenderingInterface { void waterAdded(MWWorld::Ptr::CellStore *store); void removeWater(); - + void preCellChange (MWWorld::Ptr::CellStore* store); ///< this event is fired immediately before changing cell @@ -146,6 +147,8 @@ class RenderingManager: private RenderingInterface { MWRender::Water *mWater; + TerrainManager* mTerrainManager; + OEngine::Render::OgreRenderer &mRendering; MWRender::Objects mObjects; diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp new file mode 100644 index 000000000..887721565 --- /dev/null +++ b/apps/openmw/mwrender/terrain.cpp @@ -0,0 +1,510 @@ +#include +#include +#include + +#include "../mwworld/world.hpp" + +#include "terrainmaterial.hpp" +#include "terrain.hpp" + + +using namespace Ogre; + +namespace MWRender +{ + + //---------------------------------------------------------------------------------------------- + + TerrainManager::TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& evn) : + mEnvironment(evn), mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Z, mLandSize, mWorldSize)) + { + + TerrainMaterialGeneratorPtr matGen; + TerrainMaterialGeneratorB* matGenP = new TerrainMaterialGeneratorB(); + matGen.bind(matGenP); + mTerrainGlobals.setDefaultMaterialGenerator(matGen); + + TerrainMaterialGenerator::Profile* const activeProfile = + mTerrainGlobals.getDefaultMaterialGenerator() + ->getActiveProfile(); + mActiveProfile = static_cast(activeProfile); + + //The pixel error should be as high as possible without it being noticed + //as it governs how fast mesh quality decreases. + mTerrainGlobals.setMaxPixelError(8); + + mTerrainGlobals.setLayerBlendMapSize(32); + mTerrainGlobals.setDefaultGlobalColourMapSize(65); + + //10 (default) didn't seem to be quite enough + mTerrainGlobals.setSkirtSize(128); + + //due to the sudden flick between composite and non composite textures, + //this seemed the distance where it wasn't too noticeable + mTerrainGlobals.setCompositeMapDistance(mWorldSize*2); + + mActiveProfile->setLightmapEnabled(false); + mActiveProfile->setLayerSpecularMappingEnabled(false); + mActiveProfile->setLayerNormalMappingEnabled(false); + mActiveProfile->setLayerParallaxMappingEnabled(false); + mActiveProfile->setReceiveDynamicShadowsEnabled(false); + + //composite maps lead to a drastic reduction in loading time so are + //disabled + mActiveProfile->setCompositeMapEnabled(false); + + mTerrainGroup.setOrigin(Vector3(mWorldSize/2, + 0, + -mWorldSize/2)); + + Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings(); + + importSettings.inputBias = 0; + importSettings.terrainSize = mLandSize; + importSettings.worldSize = mWorldSize; + importSettings.minBatchSize = 9; + importSettings.maxBatchSize = mLandSize; + + importSettings.deleteInputData = true; + } + + //---------------------------------------------------------------------------------------------- + + TerrainManager::~TerrainManager() + { + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::setDiffuse(const ColourValue& diffuse) + { + mTerrainGlobals.setCompositeMapDiffuse(diffuse); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::setAmbient(const ColourValue& ambient) + { + mTerrainGlobals.setCompositeMapAmbient(ambient); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::cellAdded(MWWorld::Ptr::CellStore *store) + { + const int cellX = store->cell->getGridX(); + const int cellY = store->cell->getGridY(); + + ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY); + if ( land != NULL ) + { + land->loadData(); + } + + //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++ ) + { + Terrain::ImportData terrainData = + mTerrainGroup.getDefaultImportSettings(); + + const int terrainX = cellX * 2 + x; + const int terrainY = cellY * 2 + y; + + //it makes far more sense to reallocate the memory here, + //and let Ogre deal with it due to the issues with deleting + //it at the wrong time if using threads (Which Terrain does) + terrainData.inputFloat = OGRE_ALLOC_T(float, + mLandSize*mLandSize, + MEMCATEGORY_GEOMETRY); + + if ( land != NULL ) + { + //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], + &land->landData->heights[yOffset + xOffset], + mLandSize*sizeof(float)); + } + } + else + { + memset(terrainData.inputFloat, 0, mLandSize*mLandSize*sizeof(float)); + } + + std::map indexes; + initTerrainTextures(&terrainData, cellX, cellY, + x * numTextures, y * numTextures, + numTextures, indexes); + + if (mTerrainGroup.getTerrain(terrainX, terrainY) == NULL) + { + mTerrainGroup.defineTerrain(terrainX, terrainY, &terrainData); + + mTerrainGroup.loadTerrain(terrainX, terrainY, true); + + Terrain* terrain = mTerrainGroup.getTerrain(terrainX, terrainY); + initTerrainBlendMaps(terrain, + cellX, cellY, + x * numTextures, y * numTextures, + numTextures, + indexes); + + if ( land && land->landData->usingColours ) + { + // disable or enable global colour map (depends on available vertex colours) + mActiveProfile->setGlobalColourMapEnabled(true); + TexturePtr vertex = getVertexColours(land, + cellX, cellY, + x*(mLandSize-1), + y*(mLandSize-1), + mLandSize); + + //this is a hack to get around the fact that Ogre seems to + //corrupt the global colour map leading to rendering errors + MaterialPtr mat = terrain->getMaterial(); + mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + //mat = terrain->_getCompositeMapMaterial(); + //mat->getTechnique(0)->getPass(0)->getTextureUnitState(1)->setTextureName( vertex->getName() ); + } + else + { + mActiveProfile->setGlobalColourMapEnabled(false); + } + } + } + } + + mTerrainGroup.freeTemporaryResources(); + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) + { + for ( int x = 0; x < 2; x++ ) + { + for ( int y = 0; y < 2; y++ ) + { + mTerrainGroup.unloadTerrain(store->cell->getGridX() * 2 + x, + store->cell->getGridY() * 2 + y); + } + } + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, + int cellX, int cellY, + int fromX, int fromY, int size, + std::map& indexes) + { + 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"); + + //this ensures that the ltex indexes are sorted (or retrived as sorted + //which simplifies shading between cells). + // + //If we don't sort the ltex indexes, the splatting order may differ between + //cells which may lead to inconsistent results when shading between cells + std::set ltexIndexes; + for ( int y = fromY - 1; y < fromY + size + 1; y++ ) + { + for ( int x = fromX - 1; x < fromX + size + 1; x++ ) + { + ltexIndexes.insert(getLtexIndexAt(cellX, cellY, x, y)); + } + } + + //there is one texture that we want to use as a base (i.e. it won't have + //a blend map). This holds the ltex index of that base texture so that + //we know not to include it in the output map + int baseTexture = -1; + for ( std::set::iterator iter = ltexIndexes.begin(); + iter != ltexIndexes.end(); + ++iter ) + { + const uint16_t ltexIndex = *iter; + //this is the base texture, so we can ignore this at present + if ( ltexIndex == baseTexture ) + { + 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)mEnvironment.mWorld->getStore().landTexts.getSize() >= (int)ltexIndex - 1 && + "LAND.VTEX must be within the bounds of the LTEX array"); + + std::string texture; + if ( ltexIndex == 0 ) + { + texture = "_land_default.dds"; + } + else + { + texture = mEnvironment.mWorld->getStore().landTexts.search(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(Terrain::LayerInstance()); + + terrainData->layerList[position].worldSize = 256; + terrainData->layerList[position].textureNames.push_back("textures\\" + texture); + + if ( baseTexture == -1 ) + { + baseTexture = ltexIndex; + } + else + { + indexes[ltexIndex] = position; + } + } + } + } + + //---------------------------------------------------------------------------------------------- + + void TerrainManager::initTerrainBlendMaps(Terrain* terrain, + int cellX, int cellY, + int fromX, int fromY, int size, + const std::map& indexes) + { + 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 blendMapSize = terrain->getLayerBlendMapSize(); + const int splatSize = blendMapSize / 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) * blendMapSize * blendMapSize); + } + + //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(cellX, cellY, texX, texY); + + //check if it is the base texture (which isn't in the map) and + //if it is don't bother altering the blend map for it + if ( indexes.find(ltexIndex) == indexes.end() ) + { + continue; + } + + //while 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; + + const int layerIndex = indexes.find(ltexIndex)->second; + + float* const pBlend = terrain->getLayerBlendMap(layerIndex) + ->getBlendPointer(); + + for ( int y = -1; y < splatSize + 1; y++ ) + { + for ( int x = -1; x < splatSize + 1; x++ ) + { + + //Note: Y is reversed + const int splatY = blendMapSize - 1 - relY * splatSize - y; + const int splatX = relX * splatSize + x; + + if ( splatX >= 0 && splatX < blendMapSize && + splatY >= 0 && splatY < blendMapSize ) + { + const int index = (splatY)*blendMapSize + splatX; + + if ( y >= 0 && y < splatSize && + x >= 0 && x < splatSize ) + { + pBlend[index] = 1; + } + else + { + //this provides a transition shading but also + //rounds off the corners slightly + pBlend[index] = std::min(1.0f, pBlend[index] + 0.5f); + } + } + + } + } + } + } + + for ( int i = 1; i < terrain->getLayerCount(); i++ ) + { + TerrainLayerBlendMap* blend = terrain->getLayerBlendMap(i); + blend->dirty(); + blend->update(); + } + + } + + //---------------------------------------------------------------------------------------------- + + int TerrainManager::getLtexIndexAt(int cellX, int cellY, + 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"); + + 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; + } + + + ESM::Land* land = mEnvironment.mWorld->getStore().lands.search(cellX, cellY); + if ( land != NULL ) + { + land->loadData(); + return land->landData + ->textures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + } + else + { + return 0; + } + } + + //---------------------------------------------------------------------------------------------- + + TexturePtr TerrainManager::getVertexColours(ESM::Land* land, + int cellX, int cellY, + int fromX, int fromY, int size) + { + TextureManager* const texMgr = TextureManager::getSingletonPtr(); + + const std::string colourTextureName = "VtexColours_" + + boost::lexical_cast(cellX) + + "_" + + boost::lexical_cast(cellY) + + "_" + + boost::lexical_cast(fromX) + + "_" + + boost::lexical_cast(fromY); + + TexturePtr tex = texMgr->getByName(colourTextureName); + if ( !tex.isNull() ) + { + return tex; + } + + tex = texMgr->createManual(colourTextureName, + ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + TEX_TYPE_2D, size, size, 0, PF_BYTE_BGR); + + HardwarePixelBufferSharedPtr pixelBuffer = tex->getBuffer(); + + pixelBuffer->lock(HardwareBuffer::HBL_DISCARD); + const PixelBox& pixelBox = pixelBuffer->getCurrentLock(); + + uint8* pDest = static_cast(pixelBox.data); + + if ( land != NULL ) + { + const char* const colours = land->landData->colours; + for ( int y = 0; y < size; y++ ) + { + for ( int x = 0; x < size; x++ ) + { + const size_t colourOffset = (y+fromY)*3*65 + (x+fromX)*3; + + assert( colourOffset < 65*65*3 && + "Colour offset is out of the expected bounds of record" ); + + const unsigned char r = colours[colourOffset + 0]; + const unsigned char g = colours[colourOffset + 1]; + const unsigned char b = colours[colourOffset + 2]; + + //as is the case elsewhere we need to flip the y + const size_t imageOffset = (size - 1 - y)*size*4 + x*4; + pDest[imageOffset + 0] = b; + pDest[imageOffset + 1] = g; + pDest[imageOffset + 2] = r; + } + } + } + else + { + for ( int y = 0; y < size; y++ ) + { + for ( int x = 0; x < size; x++ ) + { + for ( int k = 0; k < 3; k++ ) + { + *pDest++ = 0; + } + } + } + } + + pixelBuffer->unlock(); + + return tex; + } + +} diff --git a/apps/openmw/mwrender/terrain.hpp b/apps/openmw/mwrender/terrain.hpp new file mode 100644 index 000000000..29a4ba36b --- /dev/null +++ b/apps/openmw/mwrender/terrain.hpp @@ -0,0 +1,117 @@ +#ifndef _GAME_RENDER_TERRAIN_H +#define _GAME_RENDER_TERRAIN_H + +#include +#include +#include "terrainmaterial.hpp" + +#include "../mwworld/ptr.hpp" + +namespace Ogre{ + class SceneManager; + class TerrainGroup; + class TerrainGlobalOptions; + class Terrain; +} + +namespace MWRender{ + + /** + * Implements the Morrowind terrain using the Ogre Terrain Component + * + * Each terrain cell is split into four blocks as this leads to an increase + * in performance and means we don't hit splat limits quite as much + */ + class TerrainManager{ + public: + TerrainManager(Ogre::SceneManager* mgr, const MWWorld::Environment& env); + virtual ~TerrainManager(); + + void setDiffuse(const Ogre::ColourValue& diffuse); + void setAmbient(const Ogre::ColourValue& ambient); + + void cellAdded(MWWorld::Ptr::CellStore* store); + void cellRemoved(MWWorld::Ptr::CellStore* store); + private: + Ogre::TerrainGlobalOptions mTerrainGlobals; + Ogre::TerrainGroup mTerrainGroup; + + const MWWorld::Environment& mEnvironment; + + Ogre::TerrainMaterialGeneratorB::SM2Profile* mActiveProfile; + + /** + * The length in verticies of a single terrain block. + */ + static const int mLandSize = (ESM::Land::LAND_SIZE - 1)/2 + 1; + + /** + * The length in game units of a single terrain block. + */ + static const int mWorldSize = ESM::Land::REAL_SIZE/2; + + /** + * Setups up the list of textures for part of a cell, using indexes as + * an output to create a mapping of MW LtexIndex to the relevant terrain + * layer + * + * @param terrainData the terrain data to setup the textures for + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param fromX the ltex index in the current cell to start making the texture from + * @param fromY the ltex index in the current cell to start making the texture from + * @param size the size (number of splats) to get + * @param indexes a mapping of ltex index to the terrain texture layer that + * can be used by initTerrainBlendMaps + */ + void initTerrainTextures(Ogre::Terrain::ImportData* terrainData, + int cellX, int cellY, + int fromX, int fromY, int size, + 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 cellX the coord of the cell + * @param cellY the coord of the cell + * @param fromX the ltex index in the current cell to start making the texture from + * @param fromY the ltex index in the current cell to start making the texture from + * @param size the size (number of splats) to get + * @param indexes the mapping of ltex to blend map produced by initTerrainTextures + */ + void initTerrainBlendMaps(Ogre::Terrain* terrain, + int cellX, int cellY, + int fromX, int fromY, int size, + const std::map& indexes); + + /** + * Gets a LTEX index at the given point, assuming the current cell + * starts at (0,0). This supports getting values from the surrounding + * cells so negative x, y is acceptable + * + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param x, y the splat position of the ltex index to get relative to the + * first splat of the current cell + */ + int getLtexIndexAt(int cellX, int cellY, int x, int y); + + /** + * Due to the fact that Ogre terrain doesn't support vertex colours + * we have to generate them manually + * + * @param cellX the coord of the cell + * @param cellY the coord of the cell + * @param fromX the *vertex* index in the current cell to start making texture from + * @param fromY the *vertex* index in the current cell to start making the texture from + * @param size the size (number of vertexes) to get + */ + Ogre::TexturePtr getVertexColours(ESM::Land* land, + int cellX, int cellY, + int fromX, int fromY, int size); + }; + +} + +#endif // _GAME_RENDER_TERRAIN_H diff --git a/apps/openmw/mwrender/terrainmaterial.cpp b/apps/openmw/mwrender/terrainmaterial.cpp new file mode 100644 index 000000000..67ebf45af --- /dev/null +++ b/apps/openmw/mwrender/terrainmaterial.cpp @@ -0,0 +1,1741 @@ +/* +----------------------------------------------------------------------------- +This source file is part of OGRE +(Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org/ + +Copyright (c) 2000-2011 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ +#include "terrainmaterial.hpp" +#include "OgreTerrain.h" +#include "OgreMaterialManager.h" +#include "OgreTechnique.h" +#include "OgrePass.h" +#include "OgreTextureUnitState.h" +#include "OgreGpuProgramManager.h" +#include "OgreHighLevelGpuProgramManager.h" +#include "OgreHardwarePixelBuffer.h" +#include "OgreShadowCameraSetupPSSM.h" + +#define POINTLIGHTS + +namespace Ogre +{ + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::TerrainMaterialGeneratorB() + { + // define the layers + // We expect terrain textures to have no alpha, so we use the alpha channel + // in the albedo texture to store specular reflection + // similarly we double-up the normal and height (for parallax) + mLayerDecl.samplers.push_back(TerrainLayerSampler("albedo_specular", PF_BYTE_RGBA)); + //mLayerDecl.samplers.push_back(TerrainLayerSampler("normal_height", PF_BYTE_RGBA)); + + mLayerDecl.elements.push_back( + TerrainLayerSamplerElement(0, TLSS_ALBEDO, 0, 3)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(0, TLSS_SPECULAR, 3, 1)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(1, TLSS_NORMAL, 0, 3)); + //mLayerDecl.elements.push_back( + // TerrainLayerSamplerElement(1, TLSS_HEIGHT, 3, 1)); + + + mProfiles.push_back(OGRE_NEW SM2Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); + // TODO - check hardware capabilities & use fallbacks if required (more profiles needed) + setActiveProfile("SM2"); + + } + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::~TerrainMaterialGeneratorB() + { + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::SM2Profile::SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc) + : Profile(parent, name, desc) + , mShaderGen(0) + , mLayerNormalMappingEnabled(true) + , mLayerParallaxMappingEnabled(true) + , mLayerSpecularMappingEnabled(true) + , mGlobalColourMapEnabled(true) + , mLightmapEnabled(true) + , mCompositeMapEnabled(true) + , mReceiveDynamicShadows(true) + , mPSSM(0) + , mDepthShadows(false) + , mLowLodShadows(false) + { + + } + //--------------------------------------------------------------------- + TerrainMaterialGeneratorB::SM2Profile::~SM2Profile() + { + OGRE_DELETE mShaderGen; + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::requestOptions(Terrain* terrain) + { + terrain->_setMorphRequired(true); + terrain->_setNormalMapRequired(true); + terrain->_setLightMapRequired(mLightmapEnabled, true); + terrain->_setCompositeMapRequired(mCompositeMapEnabled); + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerNormalMappingEnabled(bool enabled) + { + if (enabled != mLayerNormalMappingEnabled) + { + mLayerNormalMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerParallaxMappingEnabled(bool enabled) + { + if (enabled != mLayerParallaxMappingEnabled) + { + mLayerParallaxMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLayerSpecularMappingEnabled(bool enabled) + { + if (enabled != mLayerSpecularMappingEnabled) + { + mLayerSpecularMappingEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setGlobalColourMapEnabled(bool enabled) + { + if (enabled != mGlobalColourMapEnabled) + { + mGlobalColourMapEnabled = enabled; + //mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setLightmapEnabled(bool enabled) + { + if (enabled != mLightmapEnabled) + { + mLightmapEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setCompositeMapEnabled(bool enabled) + { + if (enabled != mCompositeMapEnabled) + { + mCompositeMapEnabled = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsEnabled(bool enabled) + { + if (enabled != mReceiveDynamicShadows) + { + mReceiveDynamicShadows = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings) + { + if (pssmSettings != mPSSM) + { + mPSSM = pssmSettings; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsDepth(bool enabled) + { + if (enabled != mDepthShadows) + { + mDepthShadows = enabled; + mParent->_markChanged(); + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::setReceiveDynamicShadowsLowLod(bool enabled) + { + if (enabled != mLowLodShadows) + { + mLowLodShadows = enabled; + mParent->_markChanged(); + } + } + //--------------------------------------------------------------------- + uint8 TerrainMaterialGeneratorB::SM2Profile::getMaxLayers(const Terrain* terrain) const + { + // count the texture units free + uint8 freeTextureUnits = 16; + // lightmap + if (mLightmapEnabled) + --freeTextureUnits; + // normalmap + --freeTextureUnits; + // colourmap + //if (terrain->getGlobalColourMapEnabled()) + --freeTextureUnits; + if (isShadowingEnabled(HIGH_LOD, terrain)) + { + uint numShadowTextures = 1; + if (getReceiveDynamicShadowsPSSM()) + { + numShadowTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + freeTextureUnits -= numShadowTextures; + } + + // each layer needs 2.25 units (1xdiffusespec, (1xnormalheight), 0.25xblend) + return static_cast(freeTextureUnits / (1.25f + (mLayerNormalMappingEnabled||mLayerParallaxMappingEnabled))); + + + } + int TerrainMaterialGeneratorB::SM2Profile::getNumberOfLightsSupported() const + { + #ifndef POINTLIGHTS + return 1; + #else + // number of supported lights depends on the number of available constant registers, + // which in turn depends on the shader profile used + if (GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0") + || GpuProgramManager::getSingleton().isSyntaxSupported("ps_4_0") + || GpuProgramManager::getSingleton().isSyntaxSupported("fp40") + ) + return 32; + else + return 8; + #endif + } + //--------------------------------------------------------------------- + MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generate(const Terrain* terrain) + { + // re-use old material if exists + MaterialPtr mat = terrain->_getMaterial(); + if (mat.isNull()) + { + MaterialManager& matMgr = MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const String& matName = terrain->getMaterialName(); + mat = matMgr.getByName(matName); + if (mat.isNull()) + { + mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + // clear everything + mat->removeAllTechniques(); + + // Automatically disable normal & parallax mapping if card cannot handle it + // We do this rather than having a specific technique for it since it's simpler + GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); + if (!gmgr.isSyntaxSupported("ps_3_0") && !gmgr.isSyntaxSupported("ps_2_x") + && !gmgr.isSyntaxSupported("fp40") && !gmgr.isSyntaxSupported("arbfp1")) + { + setLayerNormalMappingEnabled(false); + setLayerParallaxMappingEnabled(false); + } + + addTechnique(mat, terrain, HIGH_LOD); + + // LOD + if(mCompositeMapEnabled) + { + addTechnique(mat, terrain, LOW_LOD); + Material::LodValueList lodValues; + lodValues.push_back(TerrainGlobalOptions::getSingleton().getCompositeMapDistance()); + mat->setLodLevels(lodValues); + Technique* lowLodTechnique = mat->getTechnique(1); + lowLodTechnique->setLodIndex(1); + } + + updateParams(mat, terrain); + + return mat; + + } + //--------------------------------------------------------------------- + MaterialPtr TerrainMaterialGeneratorB::SM2Profile::generateForCompositeMap(const Terrain* terrain) + { + // re-use old material if exists + MaterialPtr mat = terrain->_getCompositeMapMaterial(); + if (mat.isNull()) + { + MaterialManager& matMgr = MaterialManager::getSingleton(); + + // it's important that the names are deterministic for a given terrain, so + // use the terrain pointer as an ID + const String& matName = terrain->getMaterialName() + "/comp"; + mat = matMgr.getByName(matName); + if (mat.isNull()) + { + mat = matMgr.create(matName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + } + } + // clear everything + mat->removeAllTechniques(); + + addTechnique(mat, terrain, RENDER_COMPOSITE_MAP); + + updateParamsForCompositeMap(mat, terrain); + + return mat; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::addTechnique( + const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt) + { + + Technique* tech = mat->createTechnique(); + + // Only supporting one pass + Pass* pass = tech->createPass(); + + GpuProgramManager& gmgr = GpuProgramManager::getSingleton(); + HighLevelGpuProgramManager& hmgr = HighLevelGpuProgramManager::getSingleton(); + if (!mShaderGen) + { + bool check2x = mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled; + if (hmgr.isLanguageSupported("cg")) + mShaderGen = OGRE_NEW ShaderHelperCg(); + else if (hmgr.isLanguageSupported("hlsl") && + ((check2x && gmgr.isSyntaxSupported("ps_2_x")) || + (!check2x && gmgr.isSyntaxSupported("ps_2_0")))) + mShaderGen = OGRE_NEW ShaderHelperHLSL(); + else if (hmgr.isLanguageSupported("glsl")) + mShaderGen = OGRE_NEW ShaderHelperGLSL(); + else + { + // todo + } + + // check SM3 features + mSM3Available = GpuProgramManager::getSingleton().isSyntaxSupported("ps_3_0"); + + } + HighLevelGpuProgramPtr vprog = mShaderGen->generateVertexProgram(this, terrain, tt); + HighLevelGpuProgramPtr fprog = mShaderGen->generateFragmentProgram(this, terrain, tt); + + pass->setVertexProgram(vprog->getName()); + pass->setFragmentProgram(fprog->getName()); + + if (tt == HIGH_LOD || tt == RENDER_COMPOSITE_MAP) + { + // global normal map + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(terrain->getTerrainNormalMap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + // global colour map + if (isGlobalColourMapEnabled()) + { + tu = pass->createTextureUnitState(""); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // light map + if (isLightmapEnabled()) + { + tu = pass->createTextureUnitState(terrain->getLightmap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // blend maps + uint maxLayers = getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + for (uint i = 0; i < numBlendTextures; ++i) + { + tu = pass->createTextureUnitState(terrain->getBlendTextureName(i)); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + } + + // layer textures + for (uint i = 0; i < numLayers; ++i) + { + // diffuse / specular + tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 0)); + + // normal / height + if (mLayerNormalMappingEnabled || mLayerParallaxMappingEnabled) + tu = pass->createTextureUnitState(terrain->getLayerTextureName(i, 1)); + } + + } + else + { + // LOW_LOD textures + // composite map + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setTextureName(terrain->getCompositeMap()->getName()); + tu->setTextureAddressingMode(TextureUnitState::TAM_CLAMP); + + // That's it! + + } + + // Add shadow textures (always at the end) + if (isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (getReceiveDynamicShadowsPSSM()) + { + numTextures = getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + TextureUnitState* tu = pass->createTextureUnitState(); + tu->setContentType(TextureUnitState::CONTENT_SHADOW); + tu->setTextureAddressingMode(TextureUnitState::TAM_BORDER); + tu->setTextureBorderColour(ColourValue::White); + } + } + + } + //--------------------------------------------------------------------- + bool TerrainMaterialGeneratorB::SM2Profile::isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const + { + return getReceiveDynamicShadowsEnabled() && tt != RENDER_COMPOSITE_MAP && + (tt != LOW_LOD || mLowLodShadows) && + terrain->getSceneManager()->isShadowTechniqueTextureBased(); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::updateParams(const MaterialPtr& mat, const Terrain* terrain) + { + mShaderGen->updateParams(this, mat, terrain, false); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain) + { + mShaderGen->updateParams(this, mat, terrain, true); + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramPtr ret = createVertexProgram(prof, terrain, tt); + + StringUtil::StrStreamType sourceStr; + generateVertexProgramSource(prof, terrain, tt, sourceStr); + ret->setSource(sourceStr.str()); + ret->load(); + defaultVpParams(prof, terrain, tt, ret); +#if OGRE_DEBUG_MODE + LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Vertex Program: " + << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; +#endif + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramPtr ret = createFragmentProgram(prof, terrain, tt); + + StringUtil::StrStreamType sourceStr; + generateFragmentProgramSource(prof, terrain, tt, sourceStr); + ret->setSource(sourceStr.str()); + ret->load(); + defaultFpParams(prof, terrain, tt, ret); + +#if OGRE_DEBUG_MODE + LogManager::getSingleton().stream(LML_TRIVIAL) << "*** Terrain Fragment Program: " + << ret->getName() << " ***\n" << ret->getSource() << "\n*** ***"; +#endif + + return ret; + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateVertexProgramSource( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + generateVpHeader(prof, terrain, tt, outStream); + + if (tt != LOW_LOD) + { + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + for (uint i = 0; i < numLayers; ++i) + generateVpLayer(prof, terrain, tt, i, outStream); + } + + generateVpFooter(prof, terrain, tt, outStream); + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::generateFragmentProgramSource( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + generateFpHeader(prof, terrain, tt, outStream); + + if (tt != LOW_LOD) + { + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + + for (uint i = 0; i < numLayers; ++i) + generateFpLayer(prof, terrain, tt, i, outStream); + } + + generateFpFooter(prof, terrain, tt, outStream); + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultVpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) + { + GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + params->setNamedAutoConstant("worldMatrix", GpuProgramParameters::ACT_WORLD_MATRIX); + params->setNamedAutoConstant("viewProjMatrix", GpuProgramParameters::ACT_VIEWPROJ_MATRIX); + params->setNamedAutoConstant("lodMorph", GpuProgramParameters::ACT_CUSTOM, + Terrain::LOD_MORPH_CUSTOM_PARAM); + params->setNamedAutoConstant("fogParams", GpuProgramParameters::ACT_FOG_PARAMS); + + if (prof->isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("texViewProjMatrix" + StringConverter::toString(i), + GpuProgramParameters::ACT_TEXTURE_VIEWPROJ_MATRIX, i); + if (prof->getReceiveDynamicShadowsDepth()) + { + params->setNamedAutoConstant("depthRange" + StringConverter::toString(i), + GpuProgramParameters::ACT_SHADOW_SCENE_DEPTH_RANGE, i); + } + } + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::defaultFpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog) + { + GpuProgramParametersSharedPtr params = prog->getDefaultParameters(); + params->setIgnoreMissingParams(true); + + params->setNamedAutoConstant("ambient", GpuProgramParameters::ACT_AMBIENT_LIGHT_COLOUR); + + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + params->setNamedAutoConstant("lightPosObjSpace"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_POSITION_OBJECT_SPACE, i); + params->setNamedAutoConstant("lightDiffuseColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_DIFFUSE_COLOUR, i); + params->setNamedAutoConstant("lightAttenuation"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_ATTENUATION, i); + //params->setNamedAutoConstant("lightSpecularColour"+StringConverter::toString(i), GpuProgramParameters::ACT_LIGHT_SPECULAR_COLOUR, i); + } + + params->setNamedAutoConstant("eyePosObjSpace", GpuProgramParameters::ACT_CAMERA_POSITION_OBJECT_SPACE); + params->setNamedAutoConstant("fogColour", GpuProgramParameters::ACT_FOG_COLOUR); + + if (prof->isShadowingEnabled(tt, terrain)) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + PSSMShadowCameraSetup* pssm = prof->getReceiveDynamicShadowsPSSM(); + numTextures = pssm->getSplitCount(); + Vector4 splitPoints; + const PSSMShadowCameraSetup::SplitPointList& splitPointList = pssm->getSplitPoints(); + // Populate from split point 1, not 0, since split 0 isn't useful (usually 0) + for (uint i = 1; i < numTextures; ++i) + { + splitPoints[i-1] = splitPointList[i]; + } + params->setNamedConstant("pssmSplitPoints", splitPoints); + } + + if (prof->getReceiveDynamicShadowsDepth()) + { + size_t samplerOffset = (tt == HIGH_LOD) ? mShadowSamplerStartHi : mShadowSamplerStartLo; + for (uint i = 0; i < numTextures; ++i) + { + params->setNamedAutoConstant("inverseShadowmapSize" + StringConverter::toString(i), + GpuProgramParameters::ACT_INVERSE_TEXTURE_SIZE, i + samplerOffset); + } + } + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateParams( + const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap) + { + Pass* p = mat->getTechnique(0)->getPass(0); + if (compositeMap) + { + updateVpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, RENDER_COMPOSITE_MAP, p->getFragmentProgramParameters()); + } + else + { + // high lod + updateVpParams(prof, terrain, HIGH_LOD, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, HIGH_LOD, p->getFragmentProgramParameters()); + + if(prof->isCompositeMapEnabled()) + { + // low lod + p = mat->getTechnique(1)->getPass(0); + updateVpParams(prof, terrain, LOW_LOD, p->getVertexProgramParameters()); + updateFpParams(prof, terrain, LOW_LOD, p->getFragmentProgramParameters()); + } + } + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateVpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) + { + params->setIgnoreMissingParams(true); + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVMul = numLayers / 4; + if (numLayers % 4) + ++numUVMul; + for (uint i = 0; i < numUVMul; ++i) + { + Vector4 uvMul( + terrain->getLayerUVMultiplier(i * 4), + terrain->getLayerUVMultiplier(i * 4 + 1), + terrain->getLayerUVMultiplier(i * 4 + 2), + terrain->getLayerUVMultiplier(i * 4 + 3) + ); + params->setNamedConstant("uvMul" + StringConverter::toString(i), uvMul); + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::updateFpParams( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params) + { + params->setIgnoreMissingParams(true); + // TODO - parameterise this? + Vector4 scaleBiasSpecular(0.03, -0.04, 32, 1); + params->setNamedConstant("scaleBiasSpecular", scaleBiasSpecular); + + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getChannel(uint idx) + { + uint rem = idx % 4; + switch(rem) + { + case 0: + default: + return "r"; + case 1: + return "g"; + case 2: + return "b"; + case 3: + return "a"; + }; + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getVertexProgramName( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + String progName = terrain->getMaterialName() + "/sm2/vp"; + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + return progName; + + } + //--------------------------------------------------------------------- + String TerrainMaterialGeneratorB::SM2Profile::ShaderHelper::getFragmentProgramName( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + + String progName = terrain->getMaterialName() + "/sm2/fp"; + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + return progName; + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + ret->setParameter("profiles", "vs_3_0 vs_2_0 arbvp1"); + ret->setParameter("entry_point", "main_vp"); + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "cg", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + if(prof->isLayerNormalMappingEnabled() || prof->isLayerParallaxMappingEnabled()) + ret->setParameter("profiles", "ps_3_0 ps_2_x fp40 arbfp1"); + //else + //ret->setParameter("profiles", "ps_3_0 ps_2_0 fp30 arbfp1"); + else // fp30 doesn't work (black terrain) + ret->setParameter("profiles", "ps_3_0 ps_2_x fp40 arbfp1"); + ret->setParameter("entry_point", "main_fp"); + + return ret; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpHeader( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + outStream << + "void main_vp(\n" + "float4 pos : POSITION,\n" + "float2 uv : TEXCOORD0,\n"; + if (tt != RENDER_COMPOSITE_MAP) + outStream << "float2 delta : TEXCOORD1,\n"; // lodDelta, lodThreshold + + outStream << + "uniform float4x4 worldMatrix,\n" + "uniform float4x4 viewProjMatrix,\n" + "uniform float2 lodMorph,\n"; // morph amount, morph LOD target + + // uv multipliers + uint maxLayers = prof->getMaxLayers(terrain); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVMultipliers = (numLayers / 4); + if (numLayers % 4) + ++numUVMultipliers; + for (uint i = 0; i < numUVMultipliers; ++i) + outStream << "uniform float4 uvMul" << i << ", \n"; + + outStream << + "out float4 oPos : POSITION,\n" + "out float4 oPosObj : TEXCOORD0 \n"; + + uint texCoordSet = 1; + outStream << + ", out float4 oUVMisc : TEXCOORD" << texCoordSet++ <<" // xy = uv, z = camDepth\n"; + + // layer UV's premultiplied, packed as xy/zw + uint numUVSets = numLayers / 2; + if (numLayers % 2) + ++numUVSets; + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + outStream << + ", out float4 oUV" << i << " : TEXCOORD" << texCoordSet++ << "\n"; + } + } + + if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) + { + outStream << ", out float2 lodInfo : TEXCOORD" << texCoordSet++ << "\n"; + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << + ", uniform float4 fogParams\n" + ", out float fogVal : COLOR\n"; + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + texCoordSet = generateVpDynamicShadowsParams(texCoordSet, prof, terrain, tt, outStream); + } + + // check we haven't exceeded texture coordinates + if (texCoordSet > 8) + { + OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture coordinate sets! Try reducing the number of layers.", + __FUNCTION__); + } + + outStream << + ")\n" + "{\n" + " float4 worldPos = mul(worldMatrix, pos);\n" + " oPosObj = pos;\n"; + + if (tt != RENDER_COMPOSITE_MAP) + { + // determine whether to apply the LOD morph to this vertex + // we store the deltas against all vertices so we only want to apply + // the morph to the ones which would disappear. The target LOD which is + // being morphed to is stored in lodMorph.y, and the LOD at which + // the vertex should be morphed is stored in uv.w. If we subtract + // the former from the latter, and arrange to only morph if the + // result is negative (it will only be -1 in fact, since after that + // the vertex will never be indexed), we will achieve our aim. + // sign(vertexLOD - targetLOD) == -1 is to morph + outStream << + " float toMorph = -min(0, sign(delta.y - lodMorph.y));\n"; + // this will either be 1 (morph) or 0 (don't morph) + if (prof->getParent()->getDebugLevel()) + { + // x == LOD level (-1 since value is target level, we want to display actual) + outStream << "lodInfo.x = (lodMorph.y - 1) / " << terrain->getNumLodLevels() << ";\n"; + // y == LOD morph + outStream << "lodInfo.y = toMorph * lodMorph.x;\n"; + } + + // morph + switch (terrain->getAlignment()) + { + case Terrain::ALIGN_X_Y: + outStream << " worldPos.z += delta.x * toMorph * lodMorph.x;\n"; + break; + case Terrain::ALIGN_X_Z: + outStream << " worldPos.y += delta.x * toMorph * lodMorph.x;\n"; + break; + case Terrain::ALIGN_Y_Z: + outStream << " worldPos.x += delta.x * toMorph * lodMorph.x;\n"; + break; + }; + } + + + // generate UVs + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + uint layer = i * 2; + uint uvMulIdx = layer / 4; + + outStream << + " oUV" << i << ".xy = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer) << ";\n"; + outStream << + " oUV" << i << ".zw = " << " uv.xy * uvMul" << uvMulIdx << "." << getChannel(layer+1) << ";\n"; + + } + + } + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpHeader( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + // Main header + outStream << + // helpers + "float4 expand(float4 v)\n" + "{ \n" + " return v * 2 - 1;\n" + "}\n\n\n"; + + if (prof->isShadowingEnabled(tt, terrain)) + generateFpDynamicShadowsHelpers(prof, terrain, tt, outStream); + + + outStream << + "float4 main_fp(\n" + "float4 position : TEXCOORD0,\n"; + + uint texCoordSet = 1; + outStream << + "float4 uvMisc : TEXCOORD" << texCoordSet++ << ",\n"; + + // UV's premultiplied, packed as xy/zw + uint maxLayers = prof->getMaxLayers(terrain); + uint numBlendTextures = std::min(terrain->getBlendTextureCount(maxLayers), terrain->getBlendTextureCount()); + uint numLayers = std::min(maxLayers, static_cast(terrain->getLayerCount())); + uint numUVSets = numLayers / 2; + if (numLayers % 2) + ++numUVSets; + if (tt != LOW_LOD) + { + for (uint i = 0; i < numUVSets; ++i) + { + outStream << + "float4 layerUV" << i << " : TEXCOORD" << texCoordSet++ << ", \n"; + } + + } + if (prof->getParent()->getDebugLevel() && tt != RENDER_COMPOSITE_MAP) + { + outStream << "float2 lodInfo : TEXCOORD" << texCoordSet++ << ", \n"; + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << + "uniform float3 fogColour, \n" + "float fogVal : COLOR,\n"; + } + + uint currentSamplerIdx = 0; + + outStream << + // Only 1 light supported in this version + // deferred shading profile / generator later, ok? :) + "uniform float3 ambient,\n"; + + + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + outStream << + "uniform float4 lightPosObjSpace"<isGlobalColourMapEnabled()) + { + outStream << ", uniform sampler2D globalColourMap : register(s" + << currentSamplerIdx++ << ")\n"; + } + if (prof->isLightmapEnabled()) + { + outStream << ", uniform sampler2D lightMap : register(s" + << currentSamplerIdx++ << ")\n"; + } + // Blend textures - sampler definitions + for (uint i = 0; i < numBlendTextures; ++i) + { + outStream << ", uniform sampler2D blendTex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + } + + // Layer textures - sampler definitions & UV multipliers + for (uint i = 0; i < numLayers; ++i) + { + outStream << ", uniform sampler2D difftex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + + if (prof->mLayerNormalMappingEnabled || prof->mLayerParallaxMappingEnabled) + outStream << ", uniform sampler2D normtex" << i + << " : register(s" << currentSamplerIdx++ << ")\n"; + } + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadowsParams(&texCoordSet, ¤tSamplerIdx, prof, terrain, tt, outStream); + } + + // check we haven't exceeded samplers + if (currentSamplerIdx > 16) + { + OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, + "Requested options require too many texture samplers! Try reducing the number of layers.", + __FUNCTION__); + } + + outStream << + ") : COLOR\n" + "{\n" + " float4 outputCol;\n" + " float shadow = 1.0;\n" + " float2 uv = uvMisc.xy;\n" + // base colour + " outputCol = float4(0,0,0,1);\n"; + + if (tt != LOW_LOD) + { + outStream << + // global normal + " float3 normal = expand(tex2D(globalNormal, uv)).rgb;\n"; + + // not needed anymore apparently + //" normal = float3(normal.x, normal.z, -normal.y); \n"; // convert Ogre to MW coordinate system + + } + + for (int i=0; igetNumberOfLightsSupported(); ++i) + outStream << + " float3 lightDir"<isLayerNormalMappingEnabled()) + { + // derive the tangent space basis + // we do this in the pixel shader because we don't have per-vertex normals + // because of the LOD, we use a normal map + // tangent is always +x or -z in object space depending on alignment + switch(terrain->getAlignment()) + { + case Terrain::ALIGN_X_Y: + case Terrain::ALIGN_X_Z: + outStream << " float3 tangent = float3(1, 0, 0);\n"; + break; + case Terrain::ALIGN_Y_Z: + outStream << " float3 tangent = float3(0, 0, -1);\n"; + break; + }; + + outStream << " float3 binormal = normalize(cross(tangent, normal));\n"; + // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + outStream << " tangent = normalize(cross(normal, binormal));\n"; + // derive final matrix + outStream << " float3x3 TBN = float3x3(tangent, binormal, normal);\n"; + + // set up lighting result placeholders for interpolation + outStream << " float4 litRes, litResLayer;\n"; + outStream << " float3 TSlightDir, TSeyeDir, TShalfAngle, TSnormal;\n"; + if (prof->isLayerParallaxMappingEnabled()) + outStream << " float displacement;\n"; + // move + outStream << " TSlightDir = normalize(mul(TBN, lightDir));\n"; + outStream << " TSeyeDir = normalize(mul(TBN, eyeDir));\n"; + + } + else + { + #ifdef POINTLIGHTS + outStream << "float d; \n" + "float attn; \n"; + #endif + + outStream << + " eyeDir = normalize(eyeDir); \n"; + + // simple per-pixel lighting with no normal mapping + for (int i=0; igetNumberOfLightsSupported(); ++i) + { + outStream << " float3 halfAngle"<_isSM3Available()) + outStream << " if (" << blendWeightStr << " > 0.0003)\n { \n"; + + + // generate UV + outStream << " float2 uv" << layer << " = layerUV" << uvIdx << uvChannels << ";\n"; + + // calculate lighting here if normal mapping + if (prof->isLayerNormalMappingEnabled()) + { + if (prof->isLayerParallaxMappingEnabled() && tt != RENDER_COMPOSITE_MAP) + { + // modify UV - note we have to sample an extra time + outStream << " displacement = tex2D(normtex" << layer << ", uv" << layer << ").a\n" + " * scaleBiasSpecular.x + scaleBiasSpecular.y;\n"; + outStream << " uv" << layer << " += TSeyeDir.xy * displacement;\n"; + } + + // access TS normal map + outStream << " TSnormal = expand(tex2D(normtex" << layer << ", uv" << layer << ")).rgb;\n"; + outStream << " TShalfAngle = normalize(TSlightDir + TSeyeDir);\n"; + outStream << " litResLayer = lit(dot(TSlightDir, TSnormal), dot(TShalfAngle, TSnormal), scaleBiasSpecular.z);\n"; + if (!layer) + outStream << " litRes = litResLayer;\n"; + else + outStream << " litRes = lerp(litRes, litResLayer, " << blendWeightStr << ");\n"; + + } + + // sample diffuse texture + outStream << " float4 diffuseSpecTex" << layer + << " = tex2D(difftex" << layer << ", uv" << layer << ");\n"; + + // apply to common + if (!layer) + { + outStream << " diffuse = diffuseSpecTex0.rgb;\n"; + if (prof->isLayerSpecularMappingEnabled()) + outStream << " specular = diffuseSpecTex0.a;\n"; + } + else + { + outStream << " diffuse = lerp(diffuse, diffuseSpecTex" << layer + << ".rgb, " << blendWeightStr << ");\n"; + if (prof->isLayerSpecularMappingEnabled()) + outStream << " specular = lerp(specular, diffuseSpecTex" << layer + << ".a, " << blendWeightStr << ");\n"; + + } + + // End early-out + // Disable - causing some issues even when trying to force the use of texldd + + // comment by scrawl: + // on a NVIDIA card in opengl mode, didn't produce any problems, + // while increasing FPS from 170 to 185 (!!!) in the same area + // so let's try this out - if something does cause problems for + // someone else (with a different card / renderer) we can just + // add a vendor-specific check here + if (layer && prof->_isSM3Available()) + outStream << " } // early-out blend value\n"; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpFooter( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + outStream << + " oPos = mul(viewProjMatrix, worldPos);\n" + " oUVMisc.xy = uv.xy;\n"; + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + if (terrain->getSceneManager()->getFogMode() == FOG_LINEAR) + { + outStream << + " fogVal = saturate((oPos.z - fogParams.y) * fogParams.w);\n"; + } + else + { + outStream << + " fogVal = saturate(1 / (exp(oPos.z * fogParams.x)));\n"; + } + } + + if (prof->isShadowingEnabled(tt, terrain)) + generateVpDynamicShadows(prof, terrain, tt, outStream); + + outStream << + "}\n"; + + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpFooter( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + + if (tt == LOW_LOD) + { + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadows(prof, terrain, tt, outStream); + outStream << + " outputCol.rgb = diffuse * rtshadow;\n"; + } + else + { + outStream << + " outputCol.rgb = diffuse;\n"; + } + } + else + { + if (prof->isGlobalColourMapEnabled()) + { + // sample colour map and apply to diffuse + outStream << " diffuse *= tex2D(globalColourMap, uv).rgb;\n"; + } + if (prof->isLightmapEnabled()) + { + // sample lightmap + outStream << " shadow = tex2D(lightMap, uv).r;\n"; + } + + if (prof->isShadowingEnabled(tt, terrain)) + { + generateFpDynamicShadows(prof, terrain, tt, outStream); + } + + outStream << " outputCol.rgb += ambient * diffuse; \n"; + + // diffuse lighting + for (int i=0; igetNumberOfLightsSupported(); ++i) + outStream << " outputCol.rgb += litRes"<isLayerSpecularMappingEnabled()) + outStream << " specular = 0.0;\n"; + + if (tt == RENDER_COMPOSITE_MAP) + { + // Lighting embedded in alpha + outStream << + " outputCol.a = shadow;\n"; + + } + else + { + // Apply specular + //outStream << " outputCol.rgb += litRes.z * lightSpecularColour * specular * shadow;\n"; + + if (prof->getParent()->getDebugLevel()) + { + outStream << " outputCol.rg += lodInfo.xy;\n"; + } + } + } + + bool fog = terrain->getSceneManager()->getFogMode() != FOG_NONE && tt != RENDER_COMPOSITE_MAP; + if (fog) + { + outStream << " outputCol.rgb = lerp(outputCol.rgb, fogColour, fogVal);\n"; + } + + // Final return + outStream << " return outputCol;\n" + << "}\n"; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsHelpers( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + // TODO make filtering configurable + outStream << + "// Simple PCF \n" + "// Number of samples in one dimension (square for total samples) \n" + "#define NUM_SHADOW_SAMPLES_1D 2.0 \n" + "#define SHADOW_FILTER_SCALE 1 \n" + + "#define SHADOW_SAMPLES NUM_SHADOW_SAMPLES_1D*NUM_SHADOW_SAMPLES_1D \n" + + "float4 offsetSample(float4 uv, float2 offset, float invMapSize) \n" + "{ \n" + " return float4(uv.xy + offset * invMapSize * uv.w, uv.z, uv.w); \n" + "} \n"; + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + "float calcDepthShadow(sampler2D shadowMap, float4 uv, float invShadowMapSize) \n" + "{ \n" + " // 4-sample PCF \n" + + " float shadow = 0.0; \n" + " float offset = (NUM_SHADOW_SAMPLES_1D/2 - 0.5) * SHADOW_FILTER_SCALE; \n" + " for (float y = -offset; y <= offset; y += SHADOW_FILTER_SCALE) \n" + " for (float x = -offset; x <= offset; x += SHADOW_FILTER_SCALE) \n" + " { \n" + " float4 newUV = offsetSample(uv, float2(x, y), invShadowMapSize);\n" + " // manually project and assign derivatives \n" + " // to avoid gradient issues inside loops \n" + " newUV = newUV / newUV.w; \n" + " float depth = tex2D(shadowMap, newUV.xy, 1, 1).x; \n" + " if (depth >= 1 || depth >= uv.z)\n" + " shadow += 1.0;\n" + " } \n" + + " shadow /= SHADOW_SAMPLES; \n" + + " return shadow; \n" + "} \n"; + } + else + { + outStream << + "float calcSimpleShadow(sampler2D shadowMap, float4 shadowMapPos) \n" + "{ \n" + " return tex2Dproj(shadowMap, shadowMapPos).x; \n" + "} \n"; + + } + + if (prof->getReceiveDynamicShadowsPSSM()) + { + uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + "float calcPSSMDepthShadow("; + } + else + { + outStream << + "float calcPSSMSimpleShadow("; + } + + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "sampler2D shadowMap" << i << ", "; + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "float4 lsPos" << i << ", "; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "float invShadowmapSize" << i << ", "; + } + outStream << "\n" + " float4 pssmSplitPoints, float camDepth) \n" + "{ \n" + " float shadow; \n" + " // calculate shadow \n"; + + for (uint i = 0; i < numTextures; ++i) + { + if (!i) + outStream << " if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; + else if (i < numTextures - 1) + outStream << " else if (camDepth <= pssmSplitPoints." << ShaderHelper::getChannel(i) << ") \n"; + else + outStream << " else \n"; + + outStream << + " { \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " shadow = calcDepthShadow(shadowMap" << i << ", lsPos" << i << ", invShadowmapSize" << i << "); \n"; + } + else + { + outStream << + " shadow = calcSimpleShadow(shadowMap" << i << ", lsPos" << i << "); \n"; + } + outStream << + " } \n"; + + } + + outStream << + " return shadow; \n" + "} \n\n\n"; + } + + + } + //--------------------------------------------------------------------- + uint TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadowsParams( + uint texCoord, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + // out semantics & params + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + for (uint i = 0; i < numTextures; ++i) + { + outStream << + ", out float4 oLightSpacePos" << i << " : TEXCOORD" << texCoord++ << " \n" << + ", uniform float4x4 texViewProjMatrix" << i << " \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + ", uniform float4 depthRange" << i << " // x = min, y = max, z = range, w = 1/range \n"; + } + } + + return texCoord; + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateVpDynamicShadows( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + } + + // Calculate the position of vertex in light space + for (uint i = 0; i < numTextures; ++i) + { + outStream << + " oLightSpacePos" << i << " = mul(texViewProjMatrix" << i << ", worldPos); \n"; + if (prof->getReceiveDynamicShadowsDepth()) + { + // make linear + outStream << + "oLightSpacePos" << i << ".z = (oLightSpacePos" << i << ".z - depthRange" << i << ".x) * depthRange" << i << ".w;\n"; + + } + } + + + if (prof->getReceiveDynamicShadowsPSSM()) + { + outStream << + " // pass cam depth\n" + " oUVMisc.z = oPos.z;\n"; + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadowsParams( + uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + if (tt == HIGH_LOD) + mShadowSamplerStartHi = *sampler; + else if (tt == LOW_LOD) + mShadowSamplerStartLo = *sampler; + + // in semantics & params + uint numTextures = 1; + if (prof->getReceiveDynamicShadowsPSSM()) + { + numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + outStream << + ", uniform float4 pssmSplitPoints \n"; + } + for (uint i = 0; i < numTextures; ++i) + { + outStream << + ", float4 lightSpacePos" << i << " : TEXCOORD" << *texCoord << " \n" << + ", uniform sampler2D shadowMap" << i << " : register(s" << *sampler << ") \n"; + *sampler = *sampler + 1; + *texCoord = *texCoord + 1; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + ", uniform float inverseShadowmapSize" << i << " \n"; + } + } + + } + //--------------------------------------------------------------------- + void TerrainMaterialGeneratorB::SM2Profile::ShaderHelperCg::generateFpDynamicShadows( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) + { + if (prof->getReceiveDynamicShadowsPSSM()) + { + uint numTextures = prof->getReceiveDynamicShadowsPSSM()->getSplitCount(); + outStream << + " float camDepth = uvMisc.z;\n"; + + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " float rtshadow = calcPSSMDepthShadow("; + } + else + { + outStream << + " float rtshadow = calcPSSMSimpleShadow("; + } + for (uint i = 0; i < numTextures; ++i) + outStream << "shadowMap" << i << ", "; + outStream << "\n "; + + for (uint i = 0; i < numTextures; ++i) + outStream << "lightSpacePos" << i << ", "; + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << "\n "; + for (uint i = 0; i < numTextures; ++i) + outStream << "inverseShadowmapSize" << i << ", "; + } + outStream << "\n" << + " pssmSplitPoints, camDepth);\n"; + + } + else + { + if (prof->getReceiveDynamicShadowsDepth()) + { + outStream << + " float rtshadow = calcDepthShadow(shadowMap0, lightSpacePos0, inverseShadowmapSize0);"; + } + else + { + outStream << + " float rtshadow = calcSimpleShadow(shadowMap0, lightSpacePos0);"; + } + } + + outStream << + " shadow = min(shadow, rtshadow);\n"; + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "hlsl", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + if (prof->_isSM3Available()) + ret->setParameter("target", "vs_3_0"); + else + ret->setParameter("target", "vs_2_0"); + ret->setParameter("entry_point", "main_vp"); + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperHLSL::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "hlsl", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + if (prof->_isSM3Available()) + ret->setParameter("target", "ps_3_0"); + else + ret->setParameter("target", "ps_2_x"); + ret->setParameter("entry_point", "main_fp"); + + return ret; + + } + //--------------------------------------------------------------------- + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createVertexProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getVertexProgramName(prof, terrain, tt); + + switch(tt) + { + case HIGH_LOD: + progName += "/hlod"; + break; + case LOW_LOD: + progName += "/llod"; + break; + case RENDER_COMPOSITE_MAP: + progName += "/comp"; + break; + } + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "glsl", GPT_VERTEX_PROGRAM); + } + else + { + ret->unload(); + } + + return ret; + + } + //--------------------------------------------------------------------- + HighLevelGpuProgramPtr + TerrainMaterialGeneratorB::SM2Profile::ShaderHelperGLSL::createFragmentProgram( + const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) + { + HighLevelGpuProgramManager& mgr = HighLevelGpuProgramManager::getSingleton(); + String progName = getFragmentProgramName(prof, terrain, tt); + + HighLevelGpuProgramPtr ret = mgr.getByName(progName); + if (ret.isNull()) + { + ret = mgr.createProgram(progName, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + "glsl", GPT_FRAGMENT_PROGRAM); + } + else + { + ret->unload(); + } + + return ret; + + } + + +} diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp new file mode 100644 index 000000000..798821d61 --- /dev/null +++ b/apps/openmw/mwrender/terrainmaterial.hpp @@ -0,0 +1,266 @@ +/* +----------------------------------------------------------------------------- +This source file is part of OGRE +(Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org/ + +Copyright (c) 2000-2011 Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + +#ifndef __Ogre_TerrainMaterialGeneratorB_H__ +#define __Ogre_TerrainMaterialGeneratorB_H__ + +#include "OgreTerrainPrerequisites.h" +#include "OgreTerrainMaterialGenerator.h" +#include "OgreGpuProgramParams.h" + +namespace Ogre +{ + class PSSMShadowCameraSetup; + + /** \addtogroup Optional Components + * @{ + */ + /** \addtogroup Terrain + * Some details on the terrain component + * @{ + */ + + + /** A TerrainMaterialGenerator which can cope with normal mapped, specular mapped + terrain. + @note Requires the Cg plugin to render correctly + */ + class _OgreTerrainExport TerrainMaterialGeneratorB : public TerrainMaterialGenerator + { + public: + TerrainMaterialGeneratorB(); + ~TerrainMaterialGeneratorB(); + + /** Shader model 2 profile target. + */ + class _OgreTerrainExport SM2Profile : public TerrainMaterialGenerator::Profile + { + public: + SM2Profile(TerrainMaterialGenerator* parent, const String& name, const String& desc); + ~SM2Profile(); + + bool isVertexCompressionSupported() const {return false;} + + MaterialPtr generate(const Terrain* terrain); + MaterialPtr generateForCompositeMap(const Terrain* terrain); + uint8 getMaxLayers(const Terrain* terrain) const; + void updateParams(const MaterialPtr& mat, const Terrain* terrain); + void updateParamsForCompositeMap(const MaterialPtr& mat, const Terrain* terrain); + void requestOptions(Terrain* terrain); + + /** Whether to support normal mapping per layer in the shader (default true). + */ + bool isLayerNormalMappingEnabled() const { return mLayerNormalMappingEnabled; } + /** Whether to support normal mapping per layer in the shader (default true). + */ + void setLayerNormalMappingEnabled(bool enabled); + /** Whether to support parallax mapping per layer in the shader (default true). + */ + bool isLayerParallaxMappingEnabled() const { return mLayerParallaxMappingEnabled; } + /** Whether to support parallax mapping per layer in the shader (default true). + */ + void setLayerParallaxMappingEnabled(bool enabled); + /** Whether to support specular mapping per layer in the shader (default true). + */ + bool isLayerSpecularMappingEnabled() const { return mLayerSpecularMappingEnabled; } + /** Whether to support specular mapping per layer in the shader (default true). + */ + void setLayerSpecularMappingEnabled(bool enabled); + /** Whether to support a global colour map over the terrain in the shader, + if it's present (default true). + */ + bool isGlobalColourMapEnabled() const { return mGlobalColourMapEnabled; } + /** Whether to support a global colour map over the terrain in the shader, + if it's present (default true). + */ + void setGlobalColourMapEnabled(bool enabled); + /** Whether to support a light map over the terrain in the shader, + if it's present (default true). + */ + bool isLightmapEnabled() const { return mLightmapEnabled; } + /** Whether to support a light map over the terrain in the shader, + if it's present (default true). + */ + void setLightmapEnabled(bool enabled); + /** Whether to use the composite map to provide a lower LOD technique + in the distance (default true). + */ + bool isCompositeMapEnabled() const { return mCompositeMapEnabled; } + /** Whether to use the composite map to provide a lower LOD technique + in the distance (default true). + */ + void setCompositeMapEnabled(bool enabled); + /** Whether to support dynamic texture shadows received from other + objects, on the terrain (default true). + */ + bool getReceiveDynamicShadowsEnabled() const { return mReceiveDynamicShadows; } + /** Whether to support dynamic texture shadows received from other + objects, on the terrain (default true). + */ + void setReceiveDynamicShadowsEnabled(bool enabled); + + /** Whether to use PSSM support dynamic texture shadows, and if so the + settings to use (default 0). + */ + void setReceiveDynamicShadowsPSSM(PSSMShadowCameraSetup* pssmSettings); + /** Whether to use PSSM support dynamic texture shadows, and if so the + settings to use (default 0). + */ + PSSMShadowCameraSetup* getReceiveDynamicShadowsPSSM() const { return mPSSM; } + /** Whether to use depth shadows (default false). + */ + void setReceiveDynamicShadowsDepth(bool enabled); + /** Whether to use depth shadows (default false). + */ + bool getReceiveDynamicShadowsDepth() const { return mDepthShadows; } + /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). + */ + void setReceiveDynamicShadowsLowLod(bool enabled); + /** Whether to use shadows on low LOD material rendering (when using composite map) (default false). + */ + bool getReceiveDynamicShadowsLowLod() const { return mLowLodShadows; } + + int getNumberOfLightsSupported() const; + + /// Internal + bool _isSM3Available() const { return mSM3Available; } + + protected: + + enum TechniqueType + { + HIGH_LOD, + LOW_LOD, + RENDER_COMPOSITE_MAP + }; + void addTechnique(const MaterialPtr& mat, const Terrain* terrain, TechniqueType tt); + + /// Interface definition for helper class to generate shaders + class _OgreTerrainExport ShaderHelper : public TerrainAlloc + { + public: + ShaderHelper() {} + virtual ~ShaderHelper() {} + virtual HighLevelGpuProgramPtr generateVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual HighLevelGpuProgramPtr generateFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual void updateParams(const SM2Profile* prof, const MaterialPtr& mat, const Terrain* terrain, bool compositeMap); + protected: + virtual String getVertexProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual String getFragmentProgramName(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + virtual HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; + virtual HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt) = 0; + virtual void generateVertexProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + virtual void generateFragmentProgramSource(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + virtual void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) = 0; + virtual void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) = 0; + virtual void defaultVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); + virtual void defaultFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const HighLevelGpuProgramPtr& prog); + virtual void updateVpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); + virtual void updateFpParams(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, const GpuProgramParametersSharedPtr& params); + static String getChannel(uint idx); + + size_t mShadowSamplerStartHi; + size_t mShadowSamplerStartLo; + + }; + + /// Utility class to help with generating shaders for Cg / HLSL. + class _OgreTerrainExport ShaderHelperCg : public ShaderHelper + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); + void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream); + void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + uint generateVpDynamicShadowsParams(uint texCoordStart, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateVpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadowsHelpers(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadowsParams(uint* texCoord, uint* sampler, const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + void generateFpDynamicShadows(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream); + }; + + class _OgreTerrainExport ShaderHelperHLSL : public ShaderHelperCg + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + }; + + /// Utility class to help with generating shaders for GLSL. + class _OgreTerrainExport ShaderHelperGLSL : public ShaderHelper + { + protected: + HighLevelGpuProgramPtr createVertexProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + HighLevelGpuProgramPtr createFragmentProgram(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt); + void generateVpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateFpHeader(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateVpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} + void generateFpLayer(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, uint layer, StringUtil::StrStreamType& outStream) {} + void generateVpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + void generateFpFooter(const SM2Profile* prof, const Terrain* terrain, TechniqueType tt, StringUtil::StrStreamType& outStream) {} + }; + + ShaderHelper* mShaderGen; + bool mLayerNormalMappingEnabled; + bool mLayerParallaxMappingEnabled; + bool mLayerSpecularMappingEnabled; + bool mGlobalColourMapEnabled; + bool mLightmapEnabled; + bool mCompositeMapEnabled; + bool mReceiveDynamicShadows; + PSSMShadowCameraSetup* mPSSM; + bool mDepthShadows; + bool mLowLodShadows; + bool mSM3Available; + + bool isShadowingEnabled(TechniqueType tt, const Terrain* terrain) const; + + }; + + + + + }; + + + + /** @} */ + /** @} */ + + +} + +#endif diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index 155393f9e..d69c42ab3 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -142,8 +142,8 @@ namespace MWScript InterpreterContext& context = static_cast (runtime.getContext()); - const ESM::Cell *cell = context.getWorld().getPlayer().getPlayer().getCell()->cell; - runtime.push (cell->water); + MWWorld::Ptr::CellStore *cell = context.getWorld().getPlayer().getPlayer().getCell(); + runtime.push (cell->mWaterLevel); } }; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index 4184262d3..59cb7ecaf 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -255,11 +255,8 @@ namespace MWScript interpreter.installSegment5 (opcodeFadeIn, new OpFadeIn); interpreter.installSegment5 (opcodeFadeOut, new OpFadeOut); interpreter.installSegment5 (opcodeFadeTo, new OpFadeTo); -<<<<<<< HEAD interpreter.installSegment5 (opcodeTogglePathgrid, new OpTogglePathgrid); -======= interpreter.installSegment5 (opcodeToggleWater, new OpToggleWater); ->>>>>>> master } } } diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 6bc12c5cb..158cc0867 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -21,8 +21,14 @@ void Cell::load(ESMReader &esm) if (data.flags & Interior) { // Interior cells - - esm.getHNT(water, "WHGT", 4); + if (esm.isNextSub("INTV")) + { + int waterl; + esm.getHT(waterl); + water = (float) waterl; + } + else if (esm.isNextSub("WHGT")) + esm.getHT(water); // Quasi-exterior cells have a region (which determines the // weather), pure interior cells have ambient lighting diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index 57d495e94..8070f9c03 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..cd2cf1d91 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -4,6 +4,8 @@ namespace ESM { void Land::load(ESMReader &esm) { + mEsm = &esm; + // Get the grid location esm.getSubNameIs("INTV"); esm.getSubHeaderIs(8); @@ -19,14 +21,117 @@ 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() +{ + if (dataLoaded) + { + return; + } + + landData = new LandData; + + if (hasData) + { + mEsm->restoreContext(context); + + //esm.getHNExact(landData->normals, sizeof(VNML), "VNML"); + if (mEsm->isNextSub("VNML")) + { + mEsm->skipHSubSize(12675); + } + + VHGT rawHeights; + + mEsm->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; + } + } + + if (mEsm->isNextSub("WNAM")) + { + mEsm->skipHSubSize(81); + } + if (mEsm->isNextSub("VCLR")) + { + landData->usingColours = true; + mEsm->getHExact(&landData->colours, 3*LAND_NUM_VERTS); + }else{ + landData->usingColours = false; + } + //TODO fix magic numbers + uint16_t vtex[512]; + mEsm->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 + { + landData->usingColours = false; + memset(&landData->textures, 0, 512 * sizeof(uint16_t)); + 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..5ccd966d9 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -17,11 +17,66 @@ struct Land // File context. This allows the ESM reader to be 'reset' to this // location later when we are ready to load the full data set. + ESMReader* mEsm; ESM_Context context; 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; + + //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 + { + 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]; + uint16_t textures[LAND_NUM_TEXTURES]; + + bool usingColours; + char colours[3 * LAND_NUM_VERTS]; + }; + + LandData *landData; + void load(ESMReader &esm); + + /** + * Actually loads data + */ + void loadData(); + + /** + * Frees memory allocated for land data + */ + void unloadData(); }; } #endif diff --git a/components/esm_store/reclists.hpp b/components/esm_store/reclists.hpp index 76a2a1c9c..d7a4100aa 100644 --- a/components/esm_store/reclists.hpp +++ b/components/esm_store/reclists.hpp @@ -201,15 +201,21 @@ namespace ESMS // TODO: For multiple ESM/ESP files we need one list per file. std::vector ltex; - int count; - LTexList() : count(0) + LTexList() { // More than enough to hold Morrowind.esm. ltex.reserve(128); } - int getSize() { return count; } + const LandTexture* search(size_t index) const + { + assert(index < ltex.size()); + return <ex.at(index); + } + + int getSize() { return ltex.size(); } + int getSize() const { return ltex.size(); } virtual void listIdentifier (std::vector& identifier) const {} @@ -233,12 +239,18 @@ namespace ESMS */ struct LandList : RecList { - virtual ~LandList() {} + virtual ~LandList() + { + for ( LandMap::iterator itr = lands.begin(); itr != lands.end(); ++itr ) + { + delete itr->second; + } + } // Map containing all landscapes - typedef std::map LandsCol; - typedef std::map Lands; - Lands lands; + typedef std::pair LandCoord; + typedef std::map LandMap; + LandMap lands; int count; LandList() : count(0) {} @@ -247,17 +259,15 @@ 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()) - return NULL; - - LandsCol::const_iterator it2 = it->second.find(y); - if(it2 == it->second.end()) + LandMap::const_iterator itr = lands.find(std::make_pair(x, y)); + if ( itr == lands.end() ) + { return NULL; + } - return it2->second; + return itr->second; } void load(ESMReader &esm, const std::string &id) @@ -266,11 +276,11 @@ namespace ESMS // Create the structure and load it. This actually skips the // landscape data and remembers the file position for later. - Land *land = new Land; + Land *land = new Land(); land->load(esm); // Store the structure - lands[land->X][land->Y] = land; + lands[std::make_pair(land->X, land->Y)] = land; } }; diff --git a/components/esm_store/store.hpp b/components/esm_store/store.hpp index fab04d3e9..857682089 100644 --- a/components/esm_store/store.hpp +++ b/components/esm_store/store.hpp @@ -116,7 +116,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/mygui/openmw_dialogue_window_layout.xml b/files/mygui/openmw_dialogue_window_layout.xml index 11ac41cb3..29a3b511e 100644 --- a/files/mygui/openmw_dialogue_window_layout.xml +++ b/files/mygui/openmw_dialogue_window_layout.xml @@ -1,34 +1,29 @@ - - - - - - + - + - + - - + - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index a986dcffc..3ee33124c 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -294,7 +294,7 @@ - +