From a41a23c90afaa42b03362455073bdbdbd9090c32 Mon Sep 17 00:00:00 2001 From: scrawl Date: Thu, 8 Aug 2013 03:10:25 +0200 Subject: [PATCH 01/26] Deleted old code --- components/terrain/esm_land_factory.cpp | 14 --- components/terrain/esm_land_factory.hpp | 37 ------- components/terrain/heightmap.hpp | 38 ------- components/terrain/heightmapbuf.hpp | 72 ------------ components/terrain/land_factory.hpp | 43 -------- components/terrain/tests/.gitignore | 1 - components/terrain/tests/Makefile | 14 --- components/terrain/tests/esm_test.cpp | 10 -- components/terrain/tests/output/esm_test.out | 1 - .../terrain/tests/output/triangle_test.out | 55 --------- components/terrain/tests/test.sh | 18 --- components/terrain/tests/triangle_test.cpp | 93 ---------------- components/terrain/triangulator.hpp | 104 ------------------ 13 files changed, 500 deletions(-) delete mode 100644 components/terrain/esm_land_factory.cpp delete mode 100644 components/terrain/esm_land_factory.hpp delete mode 100644 components/terrain/heightmap.hpp delete mode 100644 components/terrain/heightmapbuf.hpp delete mode 100644 components/terrain/land_factory.hpp delete mode 100644 components/terrain/tests/.gitignore delete mode 100644 components/terrain/tests/Makefile delete mode 100644 components/terrain/tests/esm_test.cpp delete mode 100644 components/terrain/tests/output/esm_test.out delete mode 100644 components/terrain/tests/output/triangle_test.out delete mode 100755 components/terrain/tests/test.sh delete mode 100644 components/terrain/tests/triangle_test.cpp delete mode 100644 components/terrain/triangulator.hpp diff --git a/components/terrain/esm_land_factory.cpp b/components/terrain/esm_land_factory.cpp deleted file mode 100644 index 5cab7ed5df..0000000000 --- a/components/terrain/esm_land_factory.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "esm_land_factory.hpp" - -// The first one already includes the others implicitly, but it -// doesn't hurt to be explicit. -#include "../esm_store/store.hpp" -#include "../esm/esmreader.hpp" -#include "../esm/loadland.hpp" - -using namespace Terrain; - -bool ESMLandFactory::has(int x, int y) -{ - return store.landscapes.has(x,y); -} diff --git a/components/terrain/esm_land_factory.hpp b/components/terrain/esm_land_factory.hpp deleted file mode 100644 index bb1f9a8c64..0000000000 --- a/components/terrain/esm_land_factory.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef TERRAIN_ESM_LAND_FACTORY_H -#define TERRAIN_ESM_LAND_FACTORY_H - -#include "land_factory.hpp" - -namespace ESMS -{ - struct ESMStore; -} - -namespace ESM -{ - class ESMReader; -} - -namespace Terrain -{ - /* - Land factory that loads data from ESM files. - */ - class ESMLandFactory - { - ESMS::ESMStore &store; - ESM::ESMReader &reader; - - public: - // Initialize the land factory. Note that refrences to the given - // store and reader are stored in the class, so the given objects - // must be valid for a long as you plan to use this factory. - ESMLandFactory(ESMS::ESMStore &st, ESM::ESMReader &rd) - : store(st), reader(rd) {} - - // True if this factory has any data for the given grid cell. - bool has(int x, int y); - }; -} -#endif diff --git a/components/terrain/heightmap.hpp b/components/terrain/heightmap.hpp deleted file mode 100644 index e395b541e2..0000000000 --- a/components/terrain/heightmap.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef TERRAIN_HEIGHTMAP_H -#define TERRAIN_HEIGHTMAP_H - -/* - Generic interface for a structure holding heightmap data. - - A HeightMap returns information about landscape data in the form of - a regular grid of float heights. - */ - -namespace Terrain -{ - struct HeightMap - { - // Get height from grid position, counted from 0 to getNumX/Y(). - virtual float getHeight(int x, int y) = 0; - - // Get heigth from vertex index, assumed to be y*getNumX() + x. - virtual float getHeight(int index) = 0; - - virtual float getMinX() = 0; - virtual float getMaxX() = 0; - virtual float getMinY() = 0; - virtual float getMaxY() = 0; - - virtual int getNumX() = 0; - virtual int getNumY() = 0; - - // True if the given coordinate is within the grid - bool isWithin(float x, float y) - { - return - x >= getMinX() && x < getMaxX() && - y >= getMinY() && y < getMaxY(); - } - }; -} -#endif diff --git a/components/terrain/heightmapbuf.hpp b/components/terrain/heightmapbuf.hpp deleted file mode 100644 index d147e60158..0000000000 --- a/components/terrain/heightmapbuf.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef TERRAIN_HEIGHTMAPBUF_H -#define TERRAIN_HEIGHTMAPBUF_H - -/* - A HeightMap implementation that stores heigths in a buffer. - */ - -#include "heightmap.hpp" -#include "land_factory.hpp" -#include -#include - -namespace Terrain -{ - class HeightMapBuffer : public HeightMap - { - std::vector buf; - - float beginX, sizeX, endX; - float beginY, sizeY, endY; - - int numX, numY; - - public: - void load(LandDataPtr data, const LandInfo &info) - { - // We don't support other kinds of grid data yet. - assert(info.grid == LGT_Quadratic); - assert(info.data == LDT_Float); - - // Set up internal data - beginX = info.xoffset; - sizeX = info.xsize; - endX = beginX+sizeX; - numX = info.numx; - - beginY = info.yoffset; - sizeY = info.ysize; - endY = beginY+sizeY; - numY = info.numy; - - // Prepare the buffer and load it - buf.resize(numX*numY); - - data.read(&buf[0], buf.size()*sizeof(float)); - } - - // Functions inherited from HeightMap: - - float getHeight(int x, int y) - { - assert(x>=0 && x=0 && y= 0 && index < buf.size()); - return buf[index]; - } - - float getMinX() { return beginX; } - float getMaxX() { return endX; } - float getMinY() { return beginY; } - float getMaxY() { return endY; } - - int getNumX() { return numX; } - int getNumY() { return numY; } - }; -} -#endif diff --git a/components/terrain/land_factory.hpp b/components/terrain/land_factory.hpp deleted file mode 100644 index c4d160443e..0000000000 --- a/components/terrain/land_factory.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef TERRAIN_LAND_FACTORY_H -#define TERRAIN_LAND_FACTORY_H - -namespace Terrain -{ - enum LandInfoGridType - { - LGT_Quadratic - }; - - enum LandInfoDataType - { - LDT_Float - }; - - struct LandInfo - { - // Type information - LandInfoGridType grid; - LandInfoDataType data; - - // Landscape size and number of vertices. Note that xsize and - // ysize may be negative, signaling a flipped landscape in that - // direction. - float xsize, ysize; - int numx, numy; - - // World offset along the same x/y axes. Whether these are set or - // used depends on the client implementation. - float xoffset, yoffset; - }; - - /* - Factory class that provides streams to land data cells. Each - "cell" has a unique integer coordinate in the plane. - */ - struct LandFactory - { - // True if this factory has any data for the given grid cell. - virtual bool has(int x, int y) = 0; - }; -} -#endif diff --git a/components/terrain/tests/.gitignore b/components/terrain/tests/.gitignore deleted file mode 100644 index 8144904045..0000000000 --- a/components/terrain/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_test diff --git a/components/terrain/tests/Makefile b/components/terrain/tests/Makefile deleted file mode 100644 index c886f392f0..0000000000 --- a/components/terrain/tests/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -GCC=g++ - -all: triangle_test esm_test - -LIB_INC=-I../../../libs/ - -triangle_test: triangle_test.cpp - $(GCC) $^ -o $@ - -esm_test: esm_test.cpp - $(GCC) $^ -o $@ $(LIB_INC) - -clean: - rm *_test diff --git a/components/terrain/tests/esm_test.cpp b/components/terrain/tests/esm_test.cpp deleted file mode 100644 index 509aa8aa95..0000000000 --- a/components/terrain/tests/esm_test.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -using namespace std; - -#include "../esm_land_factory.hpp" - -int main() -{ - cout << "under development\n"; - return 0; -} diff --git a/components/terrain/tests/output/esm_test.out b/components/terrain/tests/output/esm_test.out deleted file mode 100644 index c6fec4b4d6..0000000000 --- a/components/terrain/tests/output/esm_test.out +++ /dev/null @@ -1 +0,0 @@ -under development diff --git a/components/terrain/tests/output/triangle_test.out b/components/terrain/tests/output/triangle_test.out deleted file mode 100644 index 0012150434..0000000000 --- a/components/terrain/tests/output/triangle_test.out +++ /dev/null @@ -1,55 +0,0 @@ -Cell types: -\ / \ / -/ \ / \ -\ / \ / -/ \ / \ - -Full index list: -0 -6 -5 -0 -1 -6 -1 -2 -6 -6 -2 -7 -2 -8 -7 -2 -3 -8 -3 -4 -8 -8 -4 -9 -5 -6 -10 -10 -6 -11 -6 -12 -11 -6 -7 -12 -7 -8 -12 -12 -8 -13 -8 -14 -13 -8 -9 -14 diff --git a/components/terrain/tests/test.sh b/components/terrain/tests/test.sh deleted file mode 100755 index 2d07708adc..0000000000 --- a/components/terrain/tests/test.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -make || exit - -mkdir -p output - -PROGS=*_test - -for a in $PROGS; do - if [ -f "output/$a.out" ]; then - echo "Running $a:" - ./$a | diff output/$a.out - - else - echo "Creating $a.out" - ./$a > "output/$a.out" - git add "output/$a.out" - fi -done diff --git a/components/terrain/tests/triangle_test.cpp b/components/terrain/tests/triangle_test.cpp deleted file mode 100644 index 464bc87095..0000000000 --- a/components/terrain/tests/triangle_test.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include -using namespace std; - -#include "../triangulator.hpp" - -const int X = 4; -const int Y = 4; - -typedef Terrain::Triangulator Triangles4x4; - -int main() -{ - Triangles4x4 t; - - cout << "Cell types:\n"; - for(int y=0;y= 0 && trinum < TriNum); - trinum *= 3; - - p1 = array[trinum++]; - p2 = array[trinum++]; - p3 = array[trinum]; - } - - /* - Get height interpolation weights for a given grid square. The - input is the grid square number (x,y) and the relative position - within that square (xrel,yrel = [0.0..1.0].) The weights are - returned as three vertex index + weight factor pairs. - - A more user-friendly version for HeightMap structs is given - below. - * / - void getWeights(int x, int y, float xrel, float yrel, - Index &p1, float w1, - Index &p2, float w2, - Index &p3, float w3) - { - // Find cell index - int index = y*SizeX + x; - - // First triangle in cell - index *= 2; - - // The rest depends on how the cell is triangulated - if(cellType(x,y)) - { - } - else - { - // Cell is divided as \ from 0,0 to 1,1 - if(xrel < yrel) - { - // Bottom left triangle. - - // Order is (0,0),(1,1),(0,1). - getTriangle(index, p1,p2,p3); - - - } - else - { - // Top right triangle - - // Order is (0,0),(1,0),(1,1). - getTriangle(index+1, p1,p2,p3); - } - } - } - - */ diff --git a/components/terrain/triangulator.hpp b/components/terrain/triangulator.hpp deleted file mode 100644 index c5c0e699b8..0000000000 --- a/components/terrain/triangulator.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef TERRAIN_TRIANGULATOR_H -#define TERRAIN_TRIANGULATOR_H - -/* - The triangulator is a simple math helper class, used for dividing a - regular square grid into alternating set of triangles. It divides a - grid like this: - - +----+----+ - | | | - | | | - +----+----+ - | | | - | | | - +----+----+ - - into this: - - +----+----+ - | \ 2|3 / | - |1 \ | / 4| - +----+----+ - |5 / | \ 8| - | / 6|7 \ | - +----+----+ - - Since the triangulation information is typically the same for all - terrains of the same size, once instance can usually be shared. -*/ - -#include - -namespace Terrain -{ - // Index number type, number of grid cells (not vertices) in X and Y - // directions. - template - class Triangulator - { - // Number of triangles - static const int TriNum = SizeX * SizeY * 2; - - // 3 indices per triangle - Index array[TriNum * 3]; - - public: - // Get raw triangle data pointer. Typically used for creating - // meshes. - const Index *getData() { return array; } - - // Return whether a given cell is divided as / (true) or \ - // (false). - static bool cellType(int x, int y) - { - assert(x >= 0 && x < SizeX); - assert(y >= 0 && y < SizeY); - - bool even = (x & 1) == 1; - if((y & 1) == 1) even = !even; - return even; - } - - // Constructor sets up the index buffer - Triangulator() - { - int index = 0; - for ( int y = 0; y < SizeX; y++ ) - for ( int x = 0; x < SizeY; x++ ) - { - // Get vertex indices - Index line1 = y*(SizeX+1) + x; - Index line2 = line1 + SizeX+1; - - if(cellType(x,y)) - { - // Top left - array[index++] = line1; - array[index++] = line1 + 1; - array[index++] = line2; - - // Bottom right - array[index++] = line2; - array[index++] = line1 + 1; - array[index++] = line2 + 1; - } - else - { - // Bottom left - array[index++] = line1; - array[index++] = line2 + 1; - array[index++] = line2; - - // Top right - array[index++] = line1; - array[index++] = line1 + 1; - array[index++] = line2 + 1; - } - } - assert(index == TriNum*3); - } - }; -} // Namespace - -#endif From e27437f8ede5da7d7e58280f8f96559171e654ef Mon Sep 17 00:00:00 2001 From: scrawl Date: Fri, 16 Aug 2013 13:01:52 +0200 Subject: [PATCH 02/26] New terrain renderer - improvements: - Consistent triangle alignment, fixes a noticable crack near the census and excise office. Note that alignment is still not the same as vanilla. Vanilla uses a weird diagonal pattern. I hope there aren't more trouble spots that will force us to replicate vanilla, but at least we can do that now. - Fixes several blending issues and cell border seams - Fix map render to use the terrain bounding box instead of an arbitrary height - Different LODs are now properly connected instead of using skirts - Support self shadowing - Normals and colors are stored in the vertices instead of a texture, this enables per-vertex lighting which should improve performance, fix compatibility issues due to the PS getting too large and mimic vanilla better - Support a fixed function fallback (though the splatting shader usually performs better) - Designed for distant land support - test: https://www.youtube.com/watch?v=2wnd9EuPJIY - we can't really enable this yet due to depth precision issues when using a large view distance --- apps/openmw/CMakeLists.txt | 3 +- apps/openmw/engine.cpp | 6 +- apps/openmw/mwrender/globalmap.cpp | 11 +- apps/openmw/mwrender/localmap.cpp | 8 +- apps/openmw/mwrender/localmap.hpp | 10 +- apps/openmw/mwrender/renderingmanager.cpp | 37 +- apps/openmw/mwrender/renderingmanager.hpp | 8 +- apps/openmw/mwrender/shadows.cpp | 1 - apps/openmw/mwrender/terrain.cpp | 531 ---------------------- apps/openmw/mwrender/terrain.hpp | 128 ------ apps/openmw/mwrender/terrainmaterial.cpp | 246 ---------- apps/openmw/mwrender/terrainmaterial.hpp | 88 ---- apps/openmw/mwrender/terrainstorage.cpp | 56 +++ apps/openmw/mwrender/terrainstorage.hpp | 22 + apps/openmw/mwrender/water.cpp | 5 +- apps/openmw/mwworld/scene.cpp | 4 +- apps/openmw/mwworld/worldimp.cpp | 6 +- components/CMakeLists.txt | 4 + components/esm/loadland.hpp | 2 +- components/terrain/chunk.cpp | 171 +++++++ components/terrain/chunk.hpp | 63 +++ components/terrain/material.cpp | 291 ++++++++++++ components/terrain/material.hpp | 54 +++ components/terrain/quadtreenode.cpp | 387 ++++++++++++++++ components/terrain/quadtreenode.hpp | 141 ++++++ components/terrain/storage.cpp | 318 +++++++++++++ components/terrain/storage.hpp | 76 ++++ components/terrain/terrain.cpp | 359 +++++++++++++++ components/terrain/terrain.hpp | 121 +++++ files/materials/objects.shader | 1 - files/materials/terrain.shader | 164 ++++--- libs/openengine/ogre/imagerotate.cpp | 4 +- 32 files changed, 2204 insertions(+), 1122 deletions(-) delete mode 100644 apps/openmw/mwrender/terrain.cpp delete mode 100644 apps/openmw/mwrender/terrain.hpp delete mode 100644 apps/openmw/mwrender/terrainmaterial.cpp delete mode 100644 apps/openmw/mwrender/terrainmaterial.hpp create mode 100644 apps/openmw/mwrender/terrainstorage.cpp create mode 100644 apps/openmw/mwrender/terrainstorage.hpp create mode 100644 components/terrain/chunk.cpp create mode 100644 components/terrain/chunk.hpp create mode 100644 components/terrain/material.cpp create mode 100644 components/terrain/material.hpp create mode 100644 components/terrain/quadtreenode.cpp create mode 100644 components/terrain/quadtreenode.hpp create mode 100644 components/terrain/storage.cpp create mode 100644 components/terrain/storage.hpp create mode 100644 components/terrain/terrain.cpp create mode 100644 components/terrain/terrain.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index eebecc09c4..8259ac8cfe 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -15,8 +15,9 @@ source_group(game FILES ${GAME} ${GAME_HEADER}) add_openmw_dir (mwrender renderingmanager debugging sky camera animation npcanimation creatureanimation activatoranimation - actors objects renderinginterface localmap occlusionquery terrain terrainmaterial water shadows + actors objects renderinginterface localmap occlusionquery water shadows compositors characterpreview externalrendering globalmap videoplayer ripplesimulation refraction + terrainstorage ) add_openmw_dir (mwinput diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 9516da5ae0..23e94cb51d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -91,12 +91,12 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) MWBase::Environment::get().getSoundManager()->update(frametime); // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + //MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); // local scripts - executeLocalScripts(); // This does not handle the case where a global script causes a cell + //executeLocalScripts(); // This does not handle the case where a global script causes a cell // change, followed by a cell change in a local script during the same // frame. @@ -597,4 +597,4 @@ void OMW::Engine::setStartupScript (const std::string& path) void OMW::Engine::setActivationDistanceOverride (int distance) { mActivationDistanceOverride = distance; -} \ No newline at end of file +} diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 1ff99dda8c..78b13ec07e 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -58,8 +58,6 @@ namespace MWRender //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) if (1) { - Ogre::Image image; - std::vector data (mWidth * mHeight * 3); for (int x = mMinX; x <= mMaxX; ++x) @@ -144,9 +142,6 @@ namespace MWRender b = waterDeepColour.b * 255; } - // uncomment this line to outline cell borders - //if (cellX == 0 || cellX == cellSize-1 || cellY == 0|| cellY == cellSize-1) r = 255; - data[texelY * mWidth * 3 + texelX * 3] = r; data[texelY * mWidth * 3 + texelX * 3+1] = g; data[texelY * mWidth * 3 + texelX * 3+2] = b; @@ -155,13 +150,11 @@ namespace MWRender } } - image.loadDynamicImage (&data[0], mWidth, mHeight, Ogre::PF_B8G8R8); - - //image.save (mCacheDir + "/GlobalMap.png"); + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC); - tex->loadImage(image); + tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8); } else tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png"); diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 8043f8b122..c414844520 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -2,6 +2,10 @@ #include #include +#include +#include +#include +#include #include "../mwworld/esmstore.hpp" @@ -104,7 +108,7 @@ void LocalMap::saveFogOfWar(MWWorld::Ptr::CellStore* cell) } } -void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) +void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, float zMin, float zMax) { mInterior = false; @@ -118,7 +122,7 @@ void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell) mCameraPosNode->setPosition(Vector3(0,0,0)); - render((x+0.5)*sSize, (y+0.5)*sSize, -10000, 10000, sSize, sSize, name); + render((x+0.5)*sSize, (y+0.5)*sSize, zMin, zMax, sSize, sSize, name); } void LocalMap::requestMap(MWWorld::Ptr::CellStore* cell, diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 72e637d9ab..5384896407 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -28,16 +28,18 @@ namespace MWRender * Request the local map for an exterior cell. * @remarks It will either be loaded from a disk cache, * or rendered if it is not already cached. - * @param exterior cell + * @param cell exterior cell + * @param zMin min height of objects or terrain in cell + * @param zMax max height of objects or terrain in cell */ - void requestMap (MWWorld::CellStore* cell); + void requestMap (MWWorld::CellStore* cell, float zMin, float zMax); /** * Request the local map for an interior cell. * @remarks It will either be loaded from a disk cache, * or rendered if it is not already cached. - * @param interior cell - * @param bounding box of the cell + * @param cell interior cell + * @param bounds bounding box of the cell */ void requestMap (MWWorld::CellStore* cell, Ogre::AxisAlignedBox bounds); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index f3e5188005..9f7fde10ad 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -25,6 +25,8 @@ #include #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/class.hpp" @@ -46,6 +48,7 @@ #include "externalrendering.hpp" #include "globalmap.hpp" #include "videoplayer.hpp" +#include "terrainstorage.hpp" using namespace MWRender; using namespace Ogre; @@ -63,6 +66,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b , mAmbientMode(0) , mSunEnabled(0) , mPhysicsEngine(engine) + , mTerrain(NULL) { // select best shader mode bool openGL = (Ogre::Root::getSingleton ().getRenderSystem ()->getName().find("OpenGL") != std::string::npos); @@ -78,7 +82,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl"); } - mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); + mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 50); mRendering.getWindow()->addListener(this); mRendering.setWindowListener(this); @@ -166,8 +170,6 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mShadows = new Shadows(&mRendering); - mTerrainManager = new TerrainManager(mRendering.getScene(), this); - mSkyManager = new SkyManager(mRootNode, mRendering.getCamera()); mOcclusionQuery = new OcclusionQuery(&mRendering, mSkyManager->getSunNode()); @@ -194,7 +196,7 @@ RenderingManager::~RenderingManager () delete mSkyManager; delete mDebugging; delete mShadows; - delete mTerrainManager; + delete mTerrain; delete mLocalMap; delete mOcclusionQuery; delete mCompositors; @@ -225,8 +227,6 @@ void RenderingManager::removeCell (MWWorld::Ptr::CellStore *store) mObjects.removeCell(store); mActors.removeCell(store); mDebugging->cellRemoved(store); - if (store->mCell->isExterior()) - mTerrainManager->cellRemoved(store); } void RenderingManager::removeWater () @@ -244,8 +244,14 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) mObjects.buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); - if (store->mCell->isExterior()) - mTerrainManager->cellAdded(store); + if (store->isExterior()) + { + if (!mTerrain) + { + mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain); + mTerrain->update(mRendering.getCamera()); + } + } waterAdded(store); } @@ -550,7 +556,8 @@ void RenderingManager::setAmbientMode() float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) { - return mTerrainManager->getTerrainHeightAt(worldPos); + assert(mTerrain); + return mTerrain->getHeightAt(worldPos); } @@ -595,7 +602,6 @@ void RenderingManager::setSunColour(const Ogre::ColourValue& colour) if (!mSunEnabled) return; mSun->setDiffuseColour(colour); mSun->setSpecularColour(colour); - mTerrainManager->setDiffuse(colour); } void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) @@ -608,7 +614,6 @@ void RenderingManager::setAmbientColour(const Ogre::ColourValue& colour) final += Ogre::ColourValue(0.7,0.7,0.7,0) * std::min(1.f, (nightEye/100.f)); mRendering.getScene()->setAmbientLight(final); - mTerrainManager->setAmbient(final); } void RenderingManager::sunEnable(bool real) @@ -652,7 +657,12 @@ void RenderingManager::setGlare(bool glare) void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) { if (cell->mCell->isExterior()) - mLocalMap->requestMap(cell); + { + Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell); + Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, -cell->mCell->getGridY() + 1 - 0.5); + dims.merge(mTerrain->getWorldBoundingBox(center)); + mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); + } else mLocalMap->requestMap(cell, mObjects.getDimensions(cell)); } @@ -984,6 +994,9 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con void RenderingManager::frameStarted(float dt) { + if (mTerrain) + mTerrain->update(mRendering.getCamera()); + mWater->frameStarted(dt); } diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index 17cdfff4ae..c4fb05804c 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -2,7 +2,6 @@ #define _GAME_RENDERING_MANAGER_H #include "sky.hpp" -#include "terrain.hpp" #include "debugging.hpp" #include @@ -39,6 +38,11 @@ namespace sh class Factory; } +namespace Terrain +{ + class Terrain; +} + namespace MWRender { class Shadows; @@ -228,7 +232,7 @@ private: OcclusionQuery* mOcclusionQuery; - TerrainManager* mTerrainManager; + Terrain::Terrain* mTerrain; MWRender::Water *mWater; diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index 0d066a0ecb..c28c01dccb 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -108,7 +108,6 @@ void Shadows::recreate() int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows"); - for (int i = 0; i < (split ? 3 : 1); ++i) { TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i); diff --git a/apps/openmw/mwrender/terrain.cpp b/apps/openmw/mwrender/terrain.cpp deleted file mode 100644 index 1b829a2c25..0000000000 --- a/apps/openmw/mwrender/terrain.cpp +++ /dev/null @@ -1,531 +0,0 @@ -#include - -#include -#include -#include -#include - -#include "../mwworld/esmstore.hpp" - -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - -#include "terrainmaterial.hpp" -#include "terrain.hpp" -#include "renderconst.hpp" -#include "shadows.hpp" -#include "renderingmanager.hpp" - -using namespace Ogre; - -namespace MWRender -{ - - //---------------------------------------------------------------------------------------------- - - TerrainManager::TerrainManager(Ogre::SceneManager* mgr, RenderingManager* rend) : - mTerrainGroup(TerrainGroup(mgr, Terrain::ALIGN_X_Y, mLandSize, mWorldSize)), mRendering(rend) - { - mTerrainGlobals = OGRE_NEW TerrainGlobalOptions(); - - TerrainMaterialGeneratorPtr matGen; - TerrainMaterial* matGenP = new TerrainMaterial(); - matGen.bind(matGenP); - mTerrainGlobals->setDefaultMaterialGenerator(matGen); - - TerrainMaterialGenerator::Profile* const activeProfile = - mTerrainGlobals->getDefaultMaterialGenerator() - ->getActiveProfile(); - mActiveProfile = static_cast(activeProfile); - - // We don't want any pixel error at all. Really, LOD makes no sense here - morrowind uses 65x65 verts in one cell, - // so applying LOD is most certainly slower than doing no LOD at all. - // Setting this to 0 seems to cause glitches though. :/ - mTerrainGlobals->setMaxPixelError(1); - - mTerrainGlobals->setLayerBlendMapSize(ESM::Land::LAND_TEXTURE_SIZE/2 + 1); - - //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); - - mTerrainGroup.setOrigin(Vector3(mWorldSize/2, - mWorldSize/2, - 0)); - - Terrain::ImportData& importSettings = mTerrainGroup.getDefaultImportSettings(); - - importSettings.inputBias = 0; - importSettings.terrainSize = mLandSize; - importSettings.worldSize = mWorldSize; - importSettings.minBatchSize = 9; - importSettings.maxBatchSize = mLandSize; - - importSettings.deleteInputData = true; - } - - //---------------------------------------------------------------------------------------------- - - float TerrainManager::getTerrainHeightAt(Vector3 worldPos) - { - Ogre::Terrain* terrain = NULL; - float height = mTerrainGroup.getHeightAtWorldPosition(worldPos, &terrain); - if (terrain == NULL) - return std::numeric_limits().min(); - return height; - } - - //---------------------------------------------------------------------------------------------- - - TerrainManager::~TerrainManager() - { - OGRE_DELETE mTerrainGlobals; - } - - //---------------------------------------------------------------------------------------------- - - 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->mCell->getGridX(); - const int cellY = store->mCell->getGridY(); - - ESM::Land* land = - MWBase::Environment::get().getWorld()->getStore().get().search(cellX, cellY); - if (land == NULL) // no land data means we're not going to create any terrain. - return; - - int dataRequired = ESM::Land::DATA_VHGT | ESM::Land::DATA_VCLR; - if (!land->isDataLoaded(dataRequired)) - { - land->loadData(dataRequired); - } - - //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); - - //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->mLandData->mHeights[yOffset + xOffset], - mLandSize*sizeof(float)); - } - - std::map indexes; - initTerrainTextures(&terrainData, cellX, cellY, - x * numTextures, y * numTextures, - numTextures, indexes, land->mPlugin); - - 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); - terrain->setVisibilityFlags(RV_Terrain); - terrain->setRenderQueueGroup(RQG_Main); - - // disable or enable global colour map (depends on available vertex colours) - if ( land->mLandData->mUsingColours ) - { - TexturePtr vertex = getVertexColours(land, - cellX, cellY, - x*(mLandSize-1), - y*(mLandSize-1), - mLandSize); - - mActiveProfile->setGlobalColourMapEnabled(true); - mActiveProfile->setGlobalColourMap (terrain, vertex->getName()); - } - else - mActiveProfile->setGlobalColourMapEnabled (false); - } - } - } - - // when loading from a heightmap, Ogre::Terrain does not update the derived data (normal map, LOD) - // synchronously, even if we supply synchronous = true parameter to loadTerrain. - // the following to be the only way to make sure derived data is ready when rendering the next frame. - while (mTerrainGroup.isDerivedDataUpdateInProgress()) - { - // we need to wait for this to finish - OGRE_THREAD_SLEEP(5); - Root::getSingleton().getWorkQueue()->processResponses(); - } - - mTerrainGroup.freeTemporaryResources(); - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::cellRemoved(MWWorld::Ptr::CellStore *store) - { - for ( int x = 0; x < 2; x++ ) - { - for ( int y = 0; y < 2; y++ ) - { - int terrainX = store->mCell->getGridX() * 2 + x; - int terrainY = store->mCell->getGridY() * 2 + y; - if (mTerrainGroup.getTerrain(terrainX, terrainY) != NULL) - mTerrainGroup.unloadTerrain(terrainX, terrainY); - } - } - } - - //---------------------------------------------------------------------------------------------- - - void TerrainManager::initTerrainTextures(Terrain::ImportData* terrainData, - int cellX, int cellY, - int fromX, int fromY, int size, - std::map& indexes, size_t plugin) - { - // FIXME: In a multiple esm configuration, we have multiple palettes. Since this code - // crosses cell boundaries, we no longer have a unique terrain palette. Instead, we need - // to adopt the following code for a dynamic palette. And this is evil - the current design - // does not work well for this task... - - 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 - int num = MWBase::Environment::get().getWorld()->getStore().get().getSize(plugin); - std::set ltexIndexes; - for ( int y = fromY; y < fromY + size + 1; y++ ) - { - for ( int x = fromX - 1; x < fromX + size; x++ ) // NB we wrap X from the other side because Y is reversed - { - int idx = getLtexIndexAt(cellX, cellY, x, y); - // This is a quick hack to prevent the program from trying to fetch textures - // from a neighboring cell, which might originate from a different plugin, - // and use a separate texture palette. Right now, we simply cast it to the - // default texture (i.e. 0). - if (idx > num) - idx = 0; - ltexIndexes.insert(idx); - } - } - - //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 ) - { - 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 - - const MWWorld::Store <exStore = - MWBase::Environment::get().getWorld()->getStore().get(); - - // NOTE: using the quick hack above, we should no longer end up with textures indices - // that are out of bounds. However, I haven't updated the test to a multi-palette - // system yet. We probably need more work here, so we skip it for now. - //assert( (int)ltexStore.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 = ltexStore.search(ltexIndex-1, plugin)->mTexture; - //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(); - - //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; texY < fromY + size + 1; texY++ ) - { - for ( int texX = fromX - 1; texX < fromX + size; texX++ ) // NB we wrap X from the other side because Y is reversed - { - 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 + 1; - const int relY = texY - fromY; - - const int layerIndex = indexes.find(ltexIndex)->second; - - float* const pBlend = terrain->getLayerBlendMap(layerIndex) - ->getBlendPointer(); - - //Note: Y is reversed - const int splatY = blendMapSize - relY - 1; - const int splatX = relX; - - assert(splatX >= 0 && splatX < blendMapSize); - assert(splatY >= 0 && splatY < blendMapSize); - - const int index = (splatY)*blendMapSize + splatX; - pBlend[index] = 1; - } - } - - 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 = - MWBase::Environment::get().getWorld()->getStore().get().search(cellX, cellY); - if ( land != NULL ) - { - if (!land->isDataLoaded(ESM::Land::DATA_VTEX)) - { - land->loadData(ESM::Land::DATA_VTEX); - } - - return land->mLandData - ->mTextures[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->mLandData->mColours; - 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 deleted file mode 100644 index 45c56390e8..0000000000 --- a/apps/openmw/mwrender/terrain.hpp +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef _GAME_RENDER_TERRAIN_H -#define _GAME_RENDER_TERRAIN_H - -#include -#include - -#include - -#include "terrainmaterial.hpp" - -namespace Ogre{ - class SceneManager; - class TerrainGroup; - class TerrainGlobalOptions; - class Terrain; -} - -namespace MWWorld -{ - class CellStore; -} - -namespace MWRender{ - - class RenderingManager; - - /** - * 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, RenderingManager* rend); - virtual ~TerrainManager(); - - void setDiffuse(const Ogre::ColourValue& diffuse); - void setAmbient(const Ogre::ColourValue& ambient); - - void cellAdded(MWWorld::CellStore* store); - void cellRemoved(MWWorld::CellStore* store); - - float getTerrainHeightAt (Ogre::Vector3 worldPos); - - private: - Ogre::TerrainGlobalOptions* mTerrainGlobals; - Ogre::TerrainGroup mTerrainGroup; - - RenderingManager* mRendering; - - TerrainMaterial::Profile* 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, size_t plugin); - - /** - * 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 deleted file mode 100644 index 892dab7cfc..0000000000 --- a/apps/openmw/mwrender/terrainmaterial.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#include "terrainmaterial.hpp" - -#include - -#include - -#include - -namespace -{ - Ogre::String getComponent (int num) - { - if (num == 0) - return "x"; - else if (num == 1) - return "y"; - else if (num == 2) - return "z"; - else - return "w"; - } -} - - -namespace MWRender -{ - - TerrainMaterial::TerrainMaterial() - { - mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("albedo_specular", Ogre::PF_BYTE_RGBA)); - //mLayerDecl.samplers.push_back(Ogre::TerrainLayerSampler("normal_height", Ogre::PF_BYTE_RGBA)); - - mLayerDecl.elements.push_back( - Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_ALBEDO, 0, 3)); - //mLayerDecl.elements.push_back( - // Ogre::TerrainLayerSamplerElement(0, Ogre::TLSS_SPECULAR, 3, 1)); - //mLayerDecl.elements.push_back( - // Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_NORMAL, 0, 3)); - //mLayerDecl.elements.push_back( - // Ogre::TerrainLayerSamplerElement(1, Ogre::TLSS_HEIGHT, 3, 1)); - - - mProfiles.push_back(OGRE_NEW Profile(this, "SM2", "Profile for rendering on Shader Model 2 capable cards")); - setActiveProfile("SM2"); - } - - // ----------------------------------------------------------------------------------------------------------------------- - - TerrainMaterial::Profile::Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc) - : Ogre::TerrainMaterialGenerator::Profile(parent, name, desc) - , mGlobalColourMap(false) - , mMaterial(0) - { - } - - TerrainMaterial::Profile::~Profile() - { - if (mMaterial) - sh::Factory::getInstance().destroyMaterialInstance(mMaterial->getName()); - } - - Ogre::MaterialPtr TerrainMaterial::Profile::generate(const Ogre::Terrain* terrain) - { - const Ogre::String& matName = terrain->getMaterialName(); - - sh::Factory::getInstance().destroyMaterialInstance (matName); - - Ogre::MaterialPtr mat = Ogre::MaterialManager::getSingleton().getByName(matName); - if (!mat.isNull()) - Ogre::MaterialManager::getSingleton().remove(matName); - - mMaterial = sh::Factory::getInstance().createMaterialInstance (matName); - mMaterial->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); - - int numPasses = getRequiredPasses(terrain); - int maxLayersInOnePass = getMaxLayersPerPass(terrain); - - for (int pass=0; passcreatePass (); - - p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); - p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); - if (pass != 0) - { - p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); - // Only write if depth is equal to the depth value written by the previous pass. - p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal"))); - } - - p->mShaderProperties.setProperty ("colour_map", sh::makeProperty(new sh::BooleanValue(mGlobalColourMap))); - p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0))); - - // global colour map - sh::MaterialInstanceTextureUnit* colourMap = p->createTextureUnit ("colourMap"); - colourMap->setProperty ("texture_alias", sh::makeProperty (new sh::StringValue(mMaterial->getName() + "_colourMap"))); - colourMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - - // global normal map - sh::MaterialInstanceTextureUnit* normalMap = p->createTextureUnit ("normalMap"); - normalMap->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getTerrainNormalMap ()->getName()))); - normalMap->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - - Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, terrain->getLayerCount()-layerOffset); - - // HACK: Terrain::getLayerBlendTextureIndex should be const, but it is not. - // Remove this once ogre got fixed. - Ogre::Terrain* nonconstTerrain = const_cast(terrain); - - // a blend map might be shared between two passes - // so we can't just use terrain->getBlendTextureCount() - Ogre::uint numBlendTextures=0; - std::vector blendTextures; - for (unsigned int layer=blendmapOffset; layergetBlendTextureName(nonconstTerrain->getLayerBlendTextureIndex( - static_cast(layerOffset+layer)).first); - if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end()) - { - blendTextures.push_back(blendTextureName); - ++numBlendTextures; - } - } - - p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); - p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); - - // blend maps - // the index of the first blend map used in this pass - int blendmapStart; - if (terrain->getLayerCount() == 1) // special case. if there's only one layer, we don't need blend maps at all - blendmapStart = 0; - else - blendmapStart = nonconstTerrain->getLayerBlendTextureIndex(static_cast(layerOffset+blendmapOffset)).first; - for (Ogre::uint i = 0; i < numBlendTextures; ++i) - { - sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); - blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getBlendTextureName(blendmapStart+i)))); - blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); - } - - // layer maps - for (Ogre::uint i = 0; i < numLayersInThisPass; ++i) - { - sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(terrain->getLayerTextureName(layerOffset+i, 0)))); - - if (i+layerOffset > 0) - { - int blendTextureIndex = nonconstTerrain->getLayerBlendTextureIndex(static_cast(layerOffset+i)).first; - int blendTextureComponent = nonconstTerrain->getLayerBlendTextureIndex(static_cast(layerOffset+i)).second; - p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + getComponent(blendTextureComponent)))); - } - else - { - // just to make it shut up about blendmap_component_0 not existing in the first pass. - // it might be retrieved, but will never survive the preprocessing step. - p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(""))); - } - } - - // shadow - for (Ogre::uint i = 0; i < 3; ++i) - { - sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); - shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); - } - - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( - Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass + 2)))); - - // make sure the pass index is fed to the permutation handler, because blendmap components may be different - p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(pass))); - } - - return Ogre::MaterialManager::getSingleton().getByName(matName); - } - - void TerrainMaterial::Profile::setGlobalColourMapEnabled (bool enabled) - { - mGlobalColourMap = enabled; - mParent->_markChanged(); - } - - void TerrainMaterial::Profile::setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name) - { - sh::Factory::getInstance ().setTextureAlias (terrain->getMaterialName () + "_colourMap", name); - } - - Ogre::MaterialPtr TerrainMaterial::Profile::generateForCompositeMap(const Ogre::Terrain* terrain) - { - throw std::runtime_error ("composite map not supported"); - } - - Ogre::uint8 TerrainMaterial::Profile::getMaxLayers(const Ogre::Terrain* terrain) const - { - return 255; - } - - int TerrainMaterial::Profile::getMaxLayersPerPass (const Ogre::Terrain* terrain) - { - // count the texture units free - Ogre::uint8 freeTextureUnits = 16; - // normalmap - --freeTextureUnits; - // colourmap - --freeTextureUnits; - // shadow - --freeTextureUnits; - --freeTextureUnits; - --freeTextureUnits; - - // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) - return static_cast(freeTextureUnits / (1.25f)); - } - - int TerrainMaterial::Profile::getRequiredPasses (const Ogre::Terrain* terrain) - { - int maxLayersPerPass = getMaxLayersPerPass(terrain); - assert(terrain->getLayerCount()); - assert(maxLayersPerPass); - return std::ceil(static_cast(terrain->getLayerCount()) / maxLayersPerPass); - } - - void TerrainMaterial::Profile::updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) - { - } - - void TerrainMaterial::Profile::updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain) - { - } - - void TerrainMaterial::Profile::requestOptions(Ogre::Terrain* terrain) - { - terrain->_setMorphRequired(true); - terrain->_setNormalMapRequired(true); // global normal map - terrain->_setLightMapRequired(false); - terrain->_setCompositeMapRequired(false); - } - -} diff --git a/apps/openmw/mwrender/terrainmaterial.hpp b/apps/openmw/mwrender/terrainmaterial.hpp deleted file mode 100644 index c90499baeb..0000000000 --- a/apps/openmw/mwrender/terrainmaterial.hpp +++ /dev/null @@ -1,88 +0,0 @@ -/* ------------------------------------------------------------------------------ -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 MWRENDER_TERRAINMATERIAL_H -#define MWRENDER_TERRAINMATERIAL_H - -#include "OgreTerrainPrerequisites.h" -#include "OgreTerrainMaterialGenerator.h" -#include "OgreGpuProgramParams.h" - -namespace sh -{ - class MaterialInstance; -} - -namespace MWRender -{ - - class TerrainMaterial : public Ogre::TerrainMaterialGenerator - { - public: - - class Profile : public Ogre::TerrainMaterialGenerator::Profile - { - public: - Profile(Ogre::TerrainMaterialGenerator* parent, const Ogre::String& name, const Ogre::String& desc); - virtual ~Profile(); - - virtual bool isVertexCompressionSupported() const { return false; } - - virtual Ogre::MaterialPtr generate(const Ogre::Terrain* terrain); - - virtual Ogre::MaterialPtr generateForCompositeMap(const Ogre::Terrain* terrain); - - virtual Ogre::uint8 getMaxLayers(const Ogre::Terrain* terrain) const; - - virtual void updateParams(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain); - - virtual void updateParamsForCompositeMap(const Ogre::MaterialPtr& mat, const Ogre::Terrain* terrain); - - virtual void requestOptions(Ogre::Terrain* terrain); - - void setGlobalColourMapEnabled(bool enabled); - void setGlobalColourMap (Ogre::Terrain* terrain, const std::string& name); - virtual void setLightmapEnabled(bool) {} - - private: - sh::MaterialInstance* mMaterial; - - int getRequiredPasses (const Ogre::Terrain* terrain); - int getMaxLayersPerPass (const Ogre::Terrain* terrain); - - bool mGlobalColourMap; - - }; - - TerrainMaterial(); - }; - -} - - -#endif diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp new file mode 100644 index 0000000000..318627fc70 --- /dev/null +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -0,0 +1,56 @@ +#include "terrainstorage.hpp" + +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" +#include "../mwworld/esmstore.hpp" + +namespace MWRender +{ + + Ogre::AxisAlignedBox TerrainStorage::getBounds() + { + int minX = 0, minY = 0, maxX = 0, maxY = 0; + + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + MWWorld::Store::iterator it = esmStore.get().extBegin(); + for (; it != esmStore.get().extEnd(); ++it) + { + if (it->getGridX() < minX) + minX = it->getGridX(); + if (it->getGridX() > maxX) + maxX = it->getGridX(); + if (it->getGridY() < minY) + minY = it->getGridY(); + if (it->getGridY() > maxY) + maxY = it->getGridY(); + } + + // since grid coords are at cell origin, we need to add 1 cell + maxX += 1; + maxY += 1; + + return Ogre::AxisAlignedBox(minX, minY, 0, maxX, maxY, 0); + } + + ESM::Land* TerrainStorage::getLand(int cellX, int cellY) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + ESM::Land* land = esmStore.get().search(cellX, cellY); + // Load the data we are definitely going to need + int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX; + if (land && !land->isDataLoaded(mask)) + land->loadData(mask); + return land; + } + + const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + return esmStore.get().find(index, plugin); + } + +} diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp new file mode 100644 index 0000000000..ebf5e26ab7 --- /dev/null +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -0,0 +1,22 @@ +#ifndef MWRENDER_TERRAINSTORAGE_H +#define MWRENDER_TERRAINSTORAGE_H + +#include + +namespace MWRender +{ + + class TerrainStorage : public Terrain::Storage + { + private: + virtual ESM::Land* getLand (int cellX, int cellY); + virtual const ESM::LandTexture* getLandTexture(int index, short plugin); + public: + virtual Ogre::AxisAlignedBox getBounds(); + ///< Get bounds in cell units + }; + +} + + +#endif diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 772eaf623e..082551f371 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -202,7 +202,10 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend) : mWaterPlane = Plane(Vector3::UNIT_Z, 0); - MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, CELL_SIZE*5, CELL_SIZE * 5, 10, 10, true, 1, 3,3, Vector3::UNIT_Y); + int waterScale = 300; + + MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, + CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 10, 10, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y); mWater = mSceneMgr->createEntity("water"); mWater->setVisibilityFlags(RV_Water); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 5fa1400b16..03ddf2aa9e 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -85,6 +85,7 @@ namespace MWWorld std::cout << "Unloading cell\n"; ListAndResetHandles functor; + /* (*iter)->forEach(functor); { // silence annoying g++ warning @@ -95,6 +96,7 @@ namespace MWWorld mPhysics->removeObject (node->getName()); } } + */ if ((*iter)->mCell->isExterior()) { @@ -148,7 +150,7 @@ namespace MWWorld // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST - insertCell (*cell, true); + //insertCell (*cell, true); mRendering.cellAdded (cell); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index bd234c0b6f..ced4b5faa4 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -1,5 +1,7 @@ #include "worldimp.hpp" +#include + #include #include @@ -833,7 +835,7 @@ namespace MWWorld bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); - if (*currCell != newCell) + if (false ) //*currCell != newCell) { removeContainerScripts(ptr); @@ -1024,7 +1026,7 @@ namespace MWWorld return; } - float terrainHeight = mRendering->getTerrainHeightAt(pos); + float terrainHeight = -std::numeric_limits().max();// mRendering->getTerrainHeightAt(pos); if (pos.z < terrainHeight) pos.z = terrainHeight; diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 529891b4cb..baf905aa7f 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -65,6 +65,10 @@ add_component_dir (interpreter add_component_dir (translation translation ) + +add_component_dir (terrain + quadtreenode chunk terrain storage material + ) find_package(Qt4 COMPONENTS QtCore QtGui) diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index c1cce5e7ee..9c1fd1f5c6 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -70,7 +70,7 @@ struct Land }; #pragma pack(pop) - typedef uint8_t VNML[LAND_NUM_VERTS * 3]; + typedef signed char VNML[LAND_NUM_VERTS * 3]; struct LandData { diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp new file mode 100644 index 0000000000..c9a364dc44 --- /dev/null +++ b/components/terrain/chunk.cpp @@ -0,0 +1,171 @@ +#include "chunk.hpp" + +#include +#include +#include +#include +#include + +#include "quadtreenode.hpp" +#include "terrain.hpp" +#include "storage.hpp" + +namespace Terrain +{ + + Chunk::Chunk(QuadTreeNode* node, short lodLevel) + : mNode(node) + , mVertexLod(lodLevel) + , mAdditionalLod(0) + { + mVertexData = OGRE_NEW Ogre::VertexData; + mVertexData->vertexStart = 0; + + // Set the total number of vertices + size_t numVertsOneSide = mNode->getSize() * (ESM::Land::LAND_SIZE-1); + numVertsOneSide /= std::pow(2, lodLevel); + numVertsOneSide += 1; + assert((int)numVertsOneSide == ESM::Land::LAND_SIZE); + mVertexData->vertexCount = numVertsOneSide * numVertsOneSide; + + // Set up the vertex declaration, which specifies the info for each vertex (normals, colors, UVs, etc) + Ogre::VertexDeclaration* vertexDecl = mVertexData->vertexDeclaration; + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + size_t nextBuffer = 0; + + // Positions + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_POSITION); + mVertexBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + // Normals + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT3, Ogre::VES_NORMAL); + mNormalBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3), + mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + // UV texture coordinates + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_FLOAT2, + Ogre::VES_TEXTURE_COORDINATES, 0); + Ogre::HardwareVertexBufferSharedPtr uvBuf = mNode->getTerrain()->getVertexBuffer(numVertsOneSide); + + // Colours + vertexDecl->addElement(nextBuffer++, 0, Ogre::VET_COLOUR, Ogre::VES_DIFFUSE); + mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), + mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + + mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), + mVertexBuffer, mNormalBuffer, mColourBuffer); + + mVertexData->vertexBufferBinding->setBinding(0, mVertexBuffer); + mVertexData->vertexBufferBinding->setBinding(1, mNormalBuffer); + mVertexData->vertexBufferBinding->setBinding(2, uvBuf); + mVertexData->vertexBufferBinding->setBinding(3, mColourBuffer); + + mIndexData = OGRE_NEW Ogre::IndexData(); + mIndexData->indexStart = 0; + } + + void Chunk::updateIndexBuffer() + { + // Fetch a suitable index buffer (which may be shared) + size_t ourLod = mVertexLod + mAdditionalLod; + + int flags = 0; + + for (int i=0; i<4; ++i) + { + QuadTreeNode* neighbour = mNode->searchNeighbour((Direction)i); + + // If the neighbour isn't currently rendering itself, + // go up until we find one. NOTE: We don't need to go down, + // because in that case neighbour's detail would be higher than + // our detail and the neighbour would handle stitching by itself. + while (neighbour && !neighbour->hasChunk()) + neighbour = neighbour->getParent(); + + size_t lod = 0; + if (neighbour) + lod = neighbour->getActualLodLevel(); + + if (lod <= ourLod) // We only need to worry about neighbours less detailed than we are - + lod = 0; // neighbours with more detail will do the stitching themselves + + // Use 4 bits for each LOD delta + if (lod > 0) + { + assert (lod - ourLod < std::pow(2,4)); + flags |= int(lod - ourLod) << (4*i); + } + } + + flags |= ((int)mAdditionalLod) << (4*4); + + size_t numIndices; + mIndexBuffer = mNode->getTerrain()->getIndexBuffer(flags, numIndices); + mIndexData->indexCount = numIndices; + mIndexData->indexBuffer = mIndexBuffer; + } + + Chunk::~Chunk() + { + OGRE_DELETE mVertexData; + OGRE_DELETE mIndexData; + } + + void Chunk::setMaterial(const Ogre::MaterialPtr &material) + { + mMaterial = material; + } + + const Ogre::AxisAlignedBox& Chunk::getBoundingBox(void) const + { + return mNode->getBoundingBox(); + } + + Ogre::Real Chunk::getBoundingRadius(void) const + { + return mNode->getBoundingBox().getHalfSize().length(); + } + + void Chunk::_updateRenderQueue(Ogre::RenderQueue* queue) + { + queue->addRenderable(this, mRenderQueueID); + } + + void Chunk::visitRenderables(Ogre::Renderable::Visitor* visitor, + bool debugRenderables) + { + visitor->visit(this, 0, false); + } + + const Ogre::MaterialPtr& Chunk::getMaterial(void) const + { + return mMaterial; + } + + void Chunk::getRenderOperation(Ogre::RenderOperation& op) + { + assert (!mIndexBuffer.isNull() && "Trying to render, but no index buffer set!"); + op.useIndexes = true; + op.operationType = Ogre::RenderOperation::OT_TRIANGLE_LIST; + op.vertexData = mVertexData; + op.indexData = mIndexData; + } + + void Chunk::getWorldTransforms(Ogre::Matrix4* xform) const + { + *xform = getParentSceneNode()->_getFullTransform(); + } + + Ogre::Real Chunk::getSquaredViewDepth(const Ogre::Camera* cam) const + { + return getParentSceneNode()->getSquaredViewDepth(cam); + } + + const Ogre::LightList& Chunk::getLights(void) const + { + return queryLights(); + } + +} diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp new file mode 100644 index 0000000000..d74c65ba6d --- /dev/null +++ b/components/terrain/chunk.hpp @@ -0,0 +1,63 @@ +#ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H +#define COMPONENTS_TERRAIN_TERRAINBATCH_H + +#include +#include + +namespace Terrain +{ + + class QuadTreeNode; + + /** + * @brief Renders a chunk of terrain, either using alpha splatting or a composite map. + */ + class Chunk : public Ogre::Renderable, public Ogre::MovableObject + { + public: + /// @param lodLevel LOD level for the vertex buffer. + Chunk (QuadTreeNode* node, short lodLevel); + virtual ~Chunk(); + + void setMaterial (const Ogre::MaterialPtr& material); + + /// Set additional LOD applied on top of vertex LOD. \n + /// This is achieved by changing the index buffer to omit vertices. + void setAdditionalLod (size_t lod) { mAdditionalLod = lod; } + size_t getAdditionalLod() { return mAdditionalLod; } + + void updateIndexBuffer(); + + // Inherited from MovableObject + virtual const Ogre::String& getMovableType(void) const { static Ogre::String t = "MW_TERRAIN"; return t; } + virtual const Ogre::AxisAlignedBox& getBoundingBox(void) const; + virtual Ogre::Real getBoundingRadius(void) const; + virtual void _updateRenderQueue(Ogre::RenderQueue* queue); + virtual void visitRenderables(Renderable::Visitor* visitor, + bool debugRenderables = false); + + // Inherited from Renderable + virtual const Ogre::MaterialPtr& getMaterial(void) const; + virtual void getRenderOperation(Ogre::RenderOperation& op); + virtual void getWorldTransforms(Ogre::Matrix4* xform) const; + virtual Ogre::Real getSquaredViewDepth(const Ogre::Camera* cam) const; + virtual const Ogre::LightList& getLights(void) const; + + private: + QuadTreeNode* mNode; + Ogre::MaterialPtr mMaterial; + + size_t mVertexLod; + size_t mAdditionalLod; + + Ogre::VertexData* mVertexData; + Ogre::IndexData* mIndexData; + Ogre::HardwareVertexBufferSharedPtr mVertexBuffer; + Ogre::HardwareVertexBufferSharedPtr mNormalBuffer; + Ogre::HardwareVertexBufferSharedPtr mColourBuffer; + Ogre::HardwareIndexBufferSharedPtr mIndexBuffer; + }; + +} + +#endif diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp new file mode 100644 index 0000000000..bd75015768 --- /dev/null +++ b/components/terrain/material.cpp @@ -0,0 +1,291 @@ +#include "material.hpp" + +#include +#include +#include + +#include + +namespace +{ + +int getBlendmapIndexForLayer (int layerIndex) +{ + return std::floor((layerIndex-1)/4.f); +} + +std::string getBlendmapComponentForLayer (int layerIndex) +{ + int n = (layerIndex-1)%4; + if (n == 0) + return "x"; + if (n == 1) + return "y"; + if (n == 2) + return "z"; + else + return "w"; +} + +} + +namespace Terrain +{ + + MaterialGenerator::MaterialGenerator(bool shaders) + : mShaders(shaders) + { + + } + + int MaterialGenerator::getMaxLayersPerPass () + { + // count the texture units free + Ogre::uint8 freeTextureUnits = 16; + + // first layer doesn't need blendmap + --freeTextureUnits; + + // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) + return static_cast(freeTextureUnits / (1.25f)) + 1; + } + + int MaterialGenerator::getRequiredPasses () + { + int maxLayersPerPass = getMaxLayersPerPass(); + return std::max(1.f, std::ceil(static_cast(mLayerList.size()) / maxLayersPerPass)); + } + + Ogre::MaterialPtr MaterialGenerator::generate(Ogre::MaterialPtr mat) + { + return create(mat, false, false); + } + + Ogre::MaterialPtr MaterialGenerator::generateForCompositeMapRTT(Ogre::MaterialPtr mat) + { + return create(mat, true, false); + } + + Ogre::MaterialPtr MaterialGenerator::generateForCompositeMap(Ogre::MaterialPtr mat) + { + return create(mat, false, true); + } + + Ogre::MaterialPtr MaterialGenerator::create(Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap) + { + assert(!renderCompositeMap || !displayCompositeMap); + if (!mat.isNull()) + { + sh::Factory::getInstance().destroyMaterialInstance(mat->getName()); + Ogre::MaterialManager::getSingleton().remove(mat->getName()); + } + + static int count = 0; + std::stringstream name; + name << "terrain/mat" << count++; + + if (!mShaders) + { + mat = Ogre::MaterialManager::getSingleton().create(name.str(), + Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); + Ogre::Technique* technique = mat->getTechnique(0); + technique->removeAllPasses(); + + if (displayCompositeMap) + { + Ogre::Pass* pass = technique->createPass(); + pass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); + pass->createTextureUnitState(mCompositeMap)->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + } + else + { + assert(mLayerList.size() == mBlendmapList.size()+1); + std::vector::iterator blend = mBlendmapList.begin(); + for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) + { + Ogre::Pass* pass = technique->createPass(); + pass->setLightingEnabled(false); + pass->setVertexColourTracking(Ogre::TVC_NONE); + + bool first = (layer == mLayerList.begin()); + + Ogre::TextureUnitState* tus; + + if (!first) + { + pass->setSceneBlending(Ogre::SBT_TRANSPARENT_ALPHA); + pass->setDepthFunction(Ogre::CMPF_EQUAL); + + tus = pass->createTextureUnitState((*blend)->getName()); + tus->setAlphaOperation(Ogre::LBX_BLEND_TEXTURE_ALPHA, + Ogre::LBS_TEXTURE, + Ogre::LBS_TEXTURE); + tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, + Ogre::LBS_TEXTURE, + Ogre::LBS_TEXTURE); + tus->setIsAlpha(true); + tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); + + float scale = (16/(16.f+1.f)); + float scroll = 1/16.f*0.5; + tus->setTextureScale(scale,scale); + tus->setTextureScroll(-scroll,-scroll); + } + + // Add the actual layer texture on top of the alpha map. + tus = pass->createTextureUnitState("textures\\" + *layer); + if (!first) + tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, + Ogre::LBS_TEXTURE, + Ogre::LBS_CURRENT); + + tus->setTextureScale(1/16.f,1/16.f); + + if (!first) + ++blend; + } + + if (!renderCompositeMap) + { + Ogre::Pass* lightingPass = technique->createPass(); + lightingPass->setSceneBlending(Ogre::SBT_MODULATE); + lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); + } + } + + return mat; + } + else + { + + sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str()); + material->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); + + if (displayCompositeMap) + { + sh::MaterialInstancePass* p = material->createPass (); + + p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); + p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); + p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(true))); + p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); + p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); + p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); + + sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); + tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); + tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + + // shadow. TODO: repeated, put in function + for (Ogre::uint i = 0; i < 3; ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } + + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( + Ogre::StringConverter::toString(1)))); + + p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(0))); + } + else + { + + int numPasses = getRequiredPasses(); + assert(numPasses); + int maxLayersInOnePass = getMaxLayersPerPass(); + + for (int pass=0; passcreatePass (); + + p->setProperty ("vertex_program", sh::makeProperty(new sh::StringValue("terrain_vertex"))); + p->setProperty ("fragment_program", sh::makeProperty(new sh::StringValue("terrain_fragment"))); + if (pass != 0) + { + p->setProperty ("scene_blend", sh::makeProperty(new sh::StringValue("alpha_blend"))); + // Only write if depth is equal to the depth value written by the previous pass. + p->setProperty ("depth_func", sh::makeProperty(new sh::StringValue("equal"))); + } + + p->mShaderProperties.setProperty ("is_first_pass", sh::makeProperty(new sh::BooleanValue(pass == 0))); + p->mShaderProperties.setProperty ("render_composite_map", sh::makeProperty(new sh::BooleanValue(renderCompositeMap))); + p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(displayCompositeMap))); + + Ogre::uint numLayersInThisPass = std::min(maxLayersInOnePass, (int)mLayerList.size()-layerOffset); + + // a blend map might be shared between two passes + Ogre::uint numBlendTextures=0; + std::vector blendTextures; + for (unsigned int layer=blendmapOffset; layergetName(); + if (std::find(blendTextures.begin(), blendTextures.end(), blendTextureName) == blendTextures.end()) + { + blendTextures.push_back(blendTextureName); + ++numBlendTextures; + } + } + + p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); + p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + + // blend maps + // the index of the first blend map used in this pass + int blendmapStart; + if (mLayerList.size() == 1) // special case. if there's only one layer, we don't need blend maps at all + blendmapStart = 0; + else + blendmapStart = getBlendmapIndexForLayer(layerOffset+blendmapOffset); + for (Ogre::uint i = 0; i < numBlendTextures; ++i) + { + sh::MaterialInstanceTextureUnit* blendTex = p->createTextureUnit ("blendMap" + Ogre::StringConverter::toString(i)); + blendTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mBlendmapList[blendmapStart+i]->getName()))); + blendTex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); + } + + // layer maps + for (Ogre::uint i = 0; i < numLayersInThisPass; ++i) + { + sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i]))); + + if (i+layerOffset > 0) + { + int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); + std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); + p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); + } + else + { + // just to make it shut up about blendmap_component_0 not existing in the first pass. + // it might be retrieved, but will never survive the preprocessing step. + p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::StringValue(""))); + } + } + + // shadow + for (Ogre::uint i = 0; i < 3; ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } + + p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( + Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass)))); + + // Make sure the pass index is fed to the permutation handler, because blendmap components may be different + p->mShaderProperties.setProperty ("pass_index", sh::makeProperty(new sh::IntValue(pass))); + } + } + } + return Ogre::MaterialManager::getSingleton().getByName(name.str()); + } + +} diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp new file mode 100644 index 0000000000..749e806ad7 --- /dev/null +++ b/components/terrain/material.hpp @@ -0,0 +1,54 @@ +#ifndef COMPONENTS_TERRAIN_MATERIAL_H +#define COMPONENTS_TERRAIN_MATERIAL_H + +#include + +namespace Terrain +{ + + class MaterialGenerator + { + public: + /// @param layerList layer textures + /// @param blendmapList blend textures + /// @param shaders Whether to use shaders. With a shader, blendmap packing can be used (4 channels instead of one), + /// so if this parameter is true, then the supplied blend maps are expected to be packed. + MaterialGenerator (bool shaders); + + void setLayerList (const std::vector& layerList) { mLayerList = layerList; } + bool hasLayers() { return mLayerList.size(); } + void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } + void setCompositeMap (const std::string& name) { mCompositeMap = name; } + + /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. + /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case + /// a new material is created. + Ogre::MaterialPtr generate (Ogre::MaterialPtr mat); + + /// Creates a material suitable for displaying a chunk of terrain using a ready-made composite map. + /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case + /// a new material is created. + Ogre::MaterialPtr generateForCompositeMap (Ogre::MaterialPtr mat); + + /// Creates a material suitable for rendering composite maps, i.e. for "baking" several layer textures + /// into one. The main difference compared to a normal material is that no shading is applied at this point. + /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case + /// a new material is created. + Ogre::MaterialPtr generateForCompositeMapRTT (Ogre::MaterialPtr mat); + + private: + Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap); + + int getRequiredPasses (); + int getMaxLayersPerPass (); + + int mNumLayers; + std::vector mLayerList; + std::vector mBlendmapList; + std::string mCompositeMap; + bool mShaders; + }; + +} + +#endif diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp new file mode 100644 index 0000000000..8ebfafeb44 --- /dev/null +++ b/components/terrain/quadtreenode.cpp @@ -0,0 +1,387 @@ +#include "quadtreenode.hpp" + +#include +#include + +#include "terrain.hpp" +#include "chunk.hpp" +#include "storage.hpp" + +#include "material.hpp" + +using namespace Terrain; + +namespace +{ + + // Utility functions for neighbour finding algorithm + ChildDirection reflect(ChildDirection dir, Direction dir2) + { + assert(dir != Root); + + const int lookupTable[4][4] = + { + // NW NE SW SE + { SW, SE, NW, NE }, // N + { NE, NW, SE, SW }, // E + { SW, SE, NW, NE }, // S + { NE, NW, SE, SW } // W + }; + return (ChildDirection)lookupTable[dir2][dir]; + } + + bool adjacent(ChildDirection dir, Direction dir2) + { + assert(dir != Root); + const bool lookupTable[4][4] = + { + // NW NE SW SE + { true, true, false, false }, // N + { false, true, false, true }, // E + { false, false, true, true }, // S + { true, false, true, false } // W + }; + return lookupTable[dir2][dir]; + } + + // Algorithm described by Hanan Samet - 'Neighbour Finding in Quadtrees' + // http://www.cs.umd.edu/~hjs/pubs/SametPRIP81.pdf + Terrain::QuadTreeNode* searchNeighbourRecursive (Terrain::QuadTreeNode* currentNode, Terrain::Direction dir) + { + if (!currentNode->getParent()) + return NULL; // Arrived at root node, the root node does not have neighbours + + Terrain::QuadTreeNode* nextNode; + if (adjacent(currentNode->getDirection(), dir)) + nextNode = searchNeighbourRecursive(currentNode->getParent(), dir); + else + nextNode = currentNode->getParent(); + + if (nextNode && nextNode->hasChildren()) + return nextNode->getChild(reflect(currentNode->getDirection(), dir)); + else + return NULL; + } + + + // Ogre::AxisAlignedBox::distance is broken in 1.8. + Ogre::Real distance(const Ogre::AxisAlignedBox& box, const Ogre::Vector3& v) + { + + if (box.contains(v)) + return 0; + else + { + Ogre::Vector3 maxDist(0,0,0); + const Ogre::Vector3& minimum = box.getMinimum(); + const Ogre::Vector3& maximum = box.getMaximum(); + + if (v.x < minimum.x) + maxDist.x = minimum.x - v.x; + else if (v.x > maximum.x) + maxDist.x = v.x - maximum.x; + + if (v.y < minimum.y) + maxDist.y = minimum.y - v.y; + else if (v.y > maximum.y) + maxDist.y = v.y - maximum.y; + + if (v.z < minimum.z) + maxDist.z = minimum.z - v.z; + else if (v.z > maximum.z) + maxDist.z = v.z - maximum.z; + + return maxDist.length(); + } + } + + // Create a 2D quad + void makeQuad(Ogre::SceneManager* sceneMgr, float left, float top, float right, float bottom, Ogre::MaterialPtr material) + { + Ogre::ManualObject* manual = sceneMgr->createManualObject(); + + // Use identity view/projection matrices to get a 2d quad + manual->setUseIdentityProjection(true); + manual->setUseIdentityView(true); + + manual->begin(material->getName()); + + float normLeft = left*2-1; + float normTop = top*2-1; + float normRight = right*2-1; + float normBottom = bottom*2-1; + + manual->position(normLeft, normTop, 0.0); + manual->textureCoord(0, 1); + manual->position(normRight, normTop, 0.0); + manual->textureCoord(1, 1); + manual->position(normRight, normBottom, 0.0); + manual->textureCoord(1, 0); + manual->position(normLeft, normBottom, 0.0); + manual->textureCoord(0, 0); + + manual->quad(0,1,2,3); + + manual->end(); + + Ogre::AxisAlignedBox aabInf; + aabInf.setInfinite(); + manual->setBoundingBox(aabInf); + + sceneMgr->getRootSceneNode()->createChildSceneNode()->attachObject(manual); + } +} + +QuadTreeNode::QuadTreeNode(Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2 ¢er, QuadTreeNode* parent) + : mSize(size) + , mCenter(center) + , mParent(parent) + , mDirection(dir) + , mIsDummy(false) + , mSceneNode(NULL) + , mTerrain(terrain) + , mChunk(NULL) + , mMaterialGenerator(NULL) +{ + mBounds.setNull(); + for (int i=0; i<4; ++i) + mChildren[i] = NULL; + + mSceneNode = mTerrain->getSceneManager()->getRootSceneNode()->createChildSceneNode( + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); + + mLodLevel = log2(mSize); + + mMaterialGenerator = new MaterialGenerator(true); +} + +void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) +{ + mChildren[id] = new QuadTreeNode(mTerrain, id, size, center, this); +} + +QuadTreeNode::~QuadTreeNode() +{ + for (int i=0; i<4; ++i) + delete mChildren[i]; + delete mChunk; + delete mMaterialGenerator; +} + +QuadTreeNode* QuadTreeNode::searchNeighbour(Direction dir) +{ + return searchNeighbourRecursive(this, dir); +} + +const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox() +{ + if (mIsDummy) + return Ogre::AxisAlignedBox::BOX_NULL; + if (mBounds.isNull()) + { + if (hasChildren()) + { + // X and Y are obvious, just need Z + float min = std::numeric_limits().max(); + float max = -std::numeric_limits().max(); + for (int i=0; i<4; ++i) + { + QuadTreeNode* child = getChild((ChildDirection)i); + float v = child->getBoundingBox().getMaximum().z; + if (v > max) + max = v; + v = child->getBoundingBox().getMinimum().z; + if (v < min) + min = v; + } + mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, min), + Ogre::Vector3(mSize/2*8192, mSize/2*8192, max)); + } + else + throw std::runtime_error("Leaf node should have bounds set!"); + } + return mBounds; +} + +void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) +{ + const Ogre::AxisAlignedBox& bounds = getBoundingBox(); + if (bounds.isNull()) + return; + + Ogre::AxisAlignedBox worldBounds (bounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0), + bounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); + + float dist = distance(worldBounds, cameraPos); + /// \todo implement error metrics or some other means of not using arbitrary values + size_t wantedLod = 0; + if (dist > 8192*1) + wantedLod = 1; + if (dist > 8192*2) + wantedLod = 2; + if (dist > 8192*5) + wantedLod = 3; + if (dist > 8192*12) + wantedLod = 4; + if (dist > 8192*32) + wantedLod = 5; + if (dist > 8192*64) + wantedLod = 6; + + if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod) + { + // Wanted LOD is small enough to render this node in one chunk + if (!mChunk) + { + mChunk = new Chunk(this, mLodLevel); + mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); + mChunk->setCastShadows(true); + mSceneNode->attachObject(mChunk); + if (mSize == 1) + { + ensureLayerInfo(); + mChunk->setMaterial(mMaterialGenerator->generate(mChunk->getMaterial())); + } + else + { + ensureCompositeMap(); + mMaterialGenerator->setCompositeMap(mCompositeMap->getName()); + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(mChunk->getMaterial())); + } + } + + + mChunk->setAdditionalLod(wantedLod - mLodLevel); + mChunk->setVisible(true); + + if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->removeChunks(); + } + } + else + { + // Wanted LOD is too detailed to be rendered in one chunk, + // so split it up by delegating to child nodes + if (mChunk) + mChunk->setVisible(false); + assert(hasChildren() && "Leaf node's LOD needs to be 0"); + for (int i=0; i<4; ++i) + mChildren[i]->update(cameraPos); + } +} + +void QuadTreeNode::removeChunks() +{ + if (mChunk) + mChunk->setVisible(false); + if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->removeChunks(); + } +} + +void QuadTreeNode::updateIndexBuffers() +{ + if (hasChunk()) + mChunk->updateIndexBuffer(); + else if (hasChildren()) + { + for (int i=0; i<4; ++i) + mChildren[i]->updateIndexBuffers(); + } +} + +bool QuadTreeNode::hasChunk() +{ + return mChunk && mChunk->getVisible(); +} + +size_t QuadTreeNode::getActualLodLevel() +{ + assert(hasChunk() && "Can't get actual LOD level if this node has no render chunk"); + return mLodLevel + mChunk->getAdditionalLod(); +} + +void QuadTreeNode::ensureLayerInfo() +{ + if (mMaterialGenerator->hasLayers()) + return; + + std::vector blendmaps; + std::vector layerList; + mTerrain->getStorage()->getBlendmaps(mSize, mCenter, true, blendmaps, layerList); + + mMaterialGenerator->setLayerList(layerList); + mMaterialGenerator->setBlendmapList(blendmaps); +} + +void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) +{ + Ogre::SceneManager* sceneMgr = mTerrain->getCompositeMapSceneManager(); + + if (mIsDummy) + { + MaterialGenerator matGen(true); + std::vector layer; + layer.push_back("_land_default.dds"); + matGen.setLayerList(layer); + makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generate(Ogre::MaterialPtr())); + return; + } + if (mSize > 1) + { + assert(hasChildren()); + + // 0,0 -------- 1,0 + // | | | + // |-----|------| + // | | | + // 0,1 -------- 1,1 + + float halfW = area.width()/2.f; + float halfH = area.height()/2.f; + mChildren[NW]->prepareForCompositeMap(Ogre::TRect(area.left, area.top, area.right-halfW, area.bottom-halfH)); + mChildren[NE]->prepareForCompositeMap(Ogre::TRect(area.left+halfW, area.top, area.right, area.bottom-halfH)); + mChildren[SW]->prepareForCompositeMap(Ogre::TRect(area.left, area.top+halfH, area.right-halfW, area.bottom)); + mChildren[SE]->prepareForCompositeMap(Ogre::TRect(area.left+halfW, area.top+halfH, area.right, area.bottom)); + } + else + { + ensureLayerInfo(); + + Ogre::MaterialPtr material = mMaterialGenerator->generateForCompositeMapRTT(Ogre::MaterialPtr()); + makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, material); + } +} + + +bool QuadTreeNode::hasCompositeMap() +{ + return !mCompositeMap.isNull(); +} + +void QuadTreeNode::ensureCompositeMap() +{ + if (!mCompositeMap.isNull()) + return; + + static int i=0; + std::stringstream name; + name << "terrain/comp" << i++; + + const int size = 128; + mCompositeMap = Ogre::TextureManager::getSingleton().createManual( + name.str(), Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, size, size, Ogre::MIP_DEFAULT, Ogre::PF_A8B8G8R8); + + // Create quads for each cell + prepareForCompositeMap(Ogre::TRect(0,0,1,1)); + + mTerrain->renderCompositeMap(mCompositeMap); + + mTerrain->clearCompositeMapSceneManager(); + +} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp new file mode 100644 index 0000000000..b68d6ab827 --- /dev/null +++ b/components/terrain/quadtreenode.hpp @@ -0,0 +1,141 @@ +#ifndef COMPONENTS_TERRAIN_QUADTREENODE_H +#define COMPONENTS_TERRAIN_QUADTREENODE_H + +#include +#include +#include + +namespace Ogre +{ + class Rectangle2D; +} + +namespace Terrain +{ + class Terrain; + class Chunk; + class MaterialGenerator; + + enum Direction + { + North = 0, + East = 1, + South = 2, + West = 3 + }; + + enum ChildDirection + { + NW = 0, + NE = 1, + SW = 2, + SE = 3, + Root + }; + + /** + * @brief A node in the quad tree for our terrain. Depending on LOD, + * a node can either choose to render itself in one batch (merging its children), + * or delegate the render process to its children, rendering each child in at least one batch. + */ + class QuadTreeNode + { + public: + /// @param terrain + /// @param dir relative to parent, or Root if we are the root node + /// @param size size (in *cell* units!) + /// @param center center (in *cell* units!) + /// @param parent parent node + QuadTreeNode (Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); + ~QuadTreeNode(); + + /// @note takes ownership of \a child + void createChild (ChildDirection id, float size, const Ogre::Vector2& center); + + /// Mark this node as a dummy node. This can happen if the terrain size isn't a power of two. + /// For the QuadTree to work, we need to round the size up to a power of two, which means we'll + /// end up with empty nodes that don't actually render anything. + void markAsDummy() { mIsDummy = true; } + bool isDummy() { return mIsDummy; } + + QuadTreeNode* getParent() { return mParent; } + + int getSize() { return mSize; } + Ogre::Vector2 getCenter() { return mCenter; } + + bool hasChildren() { return mChildren[0] != 0; } + QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; } + + /// Search for a neighbour node in this direction + QuadTreeNode* searchNeighbour (Direction dir); + + /// Returns our direction relative to the parent node, or Root if we are the root node. + ChildDirection getDirection() { return mDirection; } + + /// Set bounding box in local coordinates. Should be done at load time for leaf nodes. + /// Other nodes can merge AABB of child nodes. + void setBoundingBox (const Ogre::AxisAlignedBox& box) { mBounds = box; } + + /// Get bounding box in local coordinates + const Ogre::AxisAlignedBox& getBoundingBox(); + + Terrain* getTerrain() { return mTerrain; } + + /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. + void update (const Ogre::Vector3& cameraPos); + + /// Adjust index buffers of chunks to stitch together chunks of different LOD, so that cracks are avoided. + /// Call after QuadTreeNode::update! + void updateIndexBuffers(); + + /// Remove chunks rendered by this node and all its children + void removeChunks(); + + /// Get the effective LOD level if this node was rendered in one chunk + /// with ESM::Land::LAND_SIZE^2 vertices + size_t getNativeLodLevel() { return mLodLevel; } + + /// Get the effective current LOD level used by the chunk rendering this node + size_t getActualLodLevel(); + + /// Is this node currently configured to render itself? + bool hasChunk(); + + bool hasCompositeMap(); + + /// Add a textured quad to a specific 2d area in the composite map scenemanager. + /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply + /// call this method on their children. + /// @param area area in image space to put the quad + /// @param quads collect quads here so they can be deleted later + void prepareForCompositeMap(Ogre::TRect area); + + private: + // Stored here for convenience in case we need layer list again + MaterialGenerator* mMaterialGenerator; + + bool mIsDummy; + float mSize; + size_t mLodLevel; // LOD if we were to render this node in one chunk + Ogre::AxisAlignedBox mBounds; + ChildDirection mDirection; + Ogre::Vector2 mCenter; + + Ogre::SceneNode* mSceneNode; + + QuadTreeNode* mParent; + QuadTreeNode* mChildren[4]; + + Chunk* mChunk; + + Terrain* mTerrain; + + Ogre::TexturePtr mCompositeMap; + + void ensureLayerInfo(); + void ensureCompositeMap(); + }; + +} + +#endif diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp new file mode 100644 index 0000000000..eb2763d948 --- /dev/null +++ b/components/terrain/storage.cpp @@ -0,0 +1,318 @@ +#include "storage.hpp" + +#include +#include +#include +#include +#include + +#include + +namespace Terrain +{ + + struct VertexElement + { + Ogre::Vector3 pos; + Ogre::Vector3 normal; + Ogre::ColourValue colour; + }; + + bool Storage::getMinMaxHeights(float size, const Ogre::Vector2 ¢er, float &min, float &max) + { + assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); + + /// \todo investigate if min/max heights should be stored at load time in ESM::Land instead + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int cellX = origin.x; + int cellY = origin.y; + + const ESM::Land* land = getLand(cellX, cellY); + if (!land) + return false; + + min = std::numeric_limits().max(); + max = -std::numeric_limits().max(); + for (int row=0; rowmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + if (h > max) + max = h; + if (h < min) + min = h; + } + } + return true; + } + + void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + } + } + + void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + Ogre::HardwareVertexBufferSharedPtr vertexBuffer, + Ogre::HardwareVertexBufferSharedPtr normalBuffer, + Ogre::HardwareVertexBufferSharedPtr colourBuffer) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = std::pow(2, lodLevel); + + Ogre::Vector2 origin = center - Ogre::Vector2(size/2.f, size/2.f); + assert(origin.x == (int) origin.x); + assert(origin.y == (int) origin.y); + + int startX = origin.x; + int startY = origin.y; + + size_t numVerts = size*(ESM::Land::LAND_SIZE-1)/increment + 1; + + std::vector colors; + colors.resize(numVerts*numVerts*4); + std::vector positions; + positions.resize(numVerts*numVerts*3); + std::vector normals; + normals.resize(numVerts*numVerts*3); + + Ogre::Vector3 normal; + Ogre::ColourValue color; + + float vertY; + float vertX; + + float vertY_ = 0; // of current cell corner + for (int cellY = startY; cellY < startY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) + { + ESM::Land* land = getLand(cellX, cellY); + if (land && !land->mHasData) + land = NULL; + bool hasColors = land && land->mLandData->mUsingColours; + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + if (colStart == 0 && vertY_ != 0) + colStart += increment; + if (rowStart == 0 && vertX_ != 0) + rowStart += increment; + + vertY = vertY_; + for (int col=colStart; colmLandData->mHeights[col*ESM::Land::LAND_SIZE+row]; + else + positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; + + if (land) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + // Normals don't connect seamlessly between cells - wtf? + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + // z < 0 should never happen, but it does - I hate this data set... + if (normal.z < 0) + normal *= -1; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + + normals[vertX*numVerts*3 + vertY*3] = normal.x; + normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; + normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; + + if (hasColors) + { + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; + } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + color.a = 1; + Ogre::uint32 rsColor; + Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); + memcpy(&colors[vertX*numVerts*4 + vertY*4], &rsColor, sizeof(Ogre::uint32)); + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area + + vertexBuffer->writeData(0, vertexBuffer->getSizeInBytes(), &positions[0], true); + normalBuffer->writeData(0, normalBuffer->getSizeInBytes(), &normals[0], true); + colourBuffer->writeData(0, colourBuffer->getSizeInBytes(), &colors[0], true); + } + + Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, + int x, int y) + { + // If we're at the last row (or last column), we need to get the texture from the neighbour cell + // to get consistent blending at the border + if (x >= ESM::Land::LAND_TEXTURE_SIZE) + { + cellX++; + x -= ESM::Land::LAND_TEXTURE_SIZE; + } + if (y >= ESM::Land::LAND_TEXTURE_SIZE) + { + cellY++; + y -= ESM::Land::LAND_TEXTURE_SIZE; + } + assert(xisDataLoaded(ESM::Land::DATA_VTEX)) + land->loadData(ESM::Land::DATA_VTEX); + + int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin + return std::make_pair(tex, land->mPlugin); + } + else + return std::make_pair(0,0); + } + + std::string Storage::getTextureName(UniqueTextureId id) + { + if (id.first == 0) + return "_land_default.dds"; // Not sure if the default texture really is hardcoded? + + // NB: All vtex ids are +1 compared to the ltex ids + const ESM::LandTexture* ltex = getLandTexture(id.first-1, id.second); + + std::string texture = ltex->mTexture; + //TODO this is needed due to MWs messed up texture handling + texture = texture.substr(0, texture.rfind(".")) + ".dds"; + + return texture; + } + + void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, + bool pack, std::vector &blendmaps, std::vector &layerList) + { + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); + int cellX = origin.x; + int cellY = origin.y; + + // Save the used texture indices so we know the total number of textures + // and number of required blend maps + std::set textureIndices; + // Due to the way the blending works, the base layer will always shine through in between + // blend transitions (eg halfway between two texels, both blend values will be 0.5, so 25% of base layer visible). + // To get a consistent look, we need to make sure to use the same base layer in all cells. + // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. + textureIndices.insert(std::make_pair(0,0)); + // NB +1 to get the last index from neighbour cell (see getVtexIndexAt) + for (int y=0; y textureIndicesMap; + for (std::set::iterator it = textureIndices.begin(); it != textureIndices.end(); ++it) + { + int size = textureIndicesMap.size(); + textureIndicesMap[*it] = size; + layerList.push_back(getTextureName(*it)); + } + + int numTextures = textureIndices.size(); + // numTextures-1 since the base layer doesn't need blending + int numBlendmaps = pack ? std::ceil((numTextures-1) / 4.f) : (numTextures-1); + + int channels = pack ? 4 : 1; + + // Second iteration - create and fill in the blend maps + const int blendmapSize = ESM::Land::LAND_TEXTURE_SIZE+1; + std::vector data; + data.resize(blendmapSize * blendmapSize * channels, 0); + + for (int i=0; isecond; + int blendIndex = (pack ? std::floor((layerIndex-1)/4.f) : layerIndex-1); + int channel = pack ? std::max(0, (layerIndex-1) % 4) : 0; + + if (blendIndex == i) + data[y*blendmapSize*channels + x*channels + channel] = 255; + else + data[y*blendmapSize*channels + x*channels + channel] = 0; + } + } + + // All done, upload to GPU + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + map->loadRawData(stream, blendmapSize, blendmapSize, format); + blendmaps.push_back(map); + } + } + + +} diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp new file mode 100644 index 0000000000..d55403d9c4 --- /dev/null +++ b/components/terrain/storage.hpp @@ -0,0 +1,76 @@ +#ifndef COMPONENTS_TERRAIN_STORAGE_H +#define COMPONENTS_TERRAIN_STORAGE_H + +#include +#include + +#include + +#include + +namespace Terrain +{ + + /// We keep storage of terrain data abstract here since we need different implementations for game and editor + class Storage + { + private: + virtual ESM::Land* getLand (int cellX, int cellY) = 0; + virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; + + public: + /// Get bounds of the whole terrain in cell units + virtual Ogre::AxisAlignedBox getBounds() = 0; + + /// Get the minimum and maximum heights of a terrain chunk. + /// @note Should only be called for chunks <= 1 cell, i.e. leafs of the quad tree. + /// Larger chunks can simply merge AABB of children. + /// @param size size of the chunk in cell units + /// @param center center of the chunk in cell units + /// @param min min height will be stored here + /// @param max max height will be stored here + /// @return true if there was data available for this terrain chunk + bool getMinMaxHeights (float size, const Ogre::Vector2& center, float& min, float& max); + + /// Fill vertex buffers for a terrain chunk. + /// @param lodLevel LOD level, 0 = most detailed + /// @param size size of the terrain chunk in cell units + /// @param center center of the chunk in cell units + /// @param vertexBuffer buffer to write vertices + /// @param normalBuffer buffer to write vertex normals + /// @param colourBuffer buffer to write vertex colours + void fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, + Ogre::HardwareVertexBufferSharedPtr vertexBuffer, + Ogre::HardwareVertexBufferSharedPtr normalBuffer, + Ogre::HardwareVertexBufferSharedPtr colourBuffer); + + /// Create textures holding layer blend values for a terrain chunk. + /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might + /// have to do a ridiculous amount of different layers. For larger chunks, composite maps should be used. + /// @param chunkSize size of the terrain chunk in cell units + /// @param chunkCenter center of the chunk in cell units + /// @param pack Whether to pack blend values for up to 4 layers into one texture (one in each channel) - + /// otherwise, each texture contains blend values for one layer only. Shader-based rendering + /// can utilize packing, FFP can't. + /// @param blendmaps created blendmaps will be written here + /// @param layerList names of the layer textures used will be written here + void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, + std::vector& blendmaps, + std::vector& layerList); + + private: + void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + // Since plugins can define new texture palettes, we need to know the plugin index too + // in order to retrieve the correct texture name. + // pair + typedef std::pair UniqueTextureId; + + UniqueTextureId getVtexIndexAt(int cellX, int cellY, + int x, int y); + std::string getTextureName (UniqueTextureId id); + }; + +} + +#endif diff --git a/components/terrain/terrain.cpp b/components/terrain/terrain.cpp new file mode 100644 index 0000000000..c0f6cf2eb9 --- /dev/null +++ b/components/terrain/terrain.cpp @@ -0,0 +1,359 @@ +#include "terrain.hpp" + +#include +#include +#include +#include +#include + +#include + +#include "storage.hpp" +#include "quadtreenode.hpp" + +namespace +{ + + bool isPowerOfTwo(int x) + { + return ( (x > 0) && ((x & (x - 1)) == 0) ); + } + + int nextPowerOfTwo (int v) + { + if (isPowerOfTwo(v)) return v; + int depth=0; + while(v) + { + v >>= 1; + depth++; + } + return 1 << depth; + } + + Terrain::QuadTreeNode* findNode (const Ogre::Vector2& center, Terrain::QuadTreeNode* node) + { + if (center == node->getCenter()) + return node; + + if (center.x > node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NE)); + else if (center.x > node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SE)); + else if (center.x < node->getCenter().x && center.y > node->getCenter().y) + return findNode(center, node->getChild(Terrain::NW)); + else //if (center.x < node->getCenter().x && center.y < node->getCenter().y) + return findNode(center, node->getChild(Terrain::SW)); + } + +} + +namespace Terrain +{ + + Terrain::Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags) + : mStorage(storage) + , mMinBatchSize(1) + , mMaxBatchSize(64) + , mSceneMgr(sceneMgr) + , mVisibilityFlags(visibilityFlags) + { + mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + + Ogre::Camera* compositeMapCam = mCompositeMapSceneMgr->createCamera("a"); + mCompositeMapRenderTexture = Ogre::TextureManager::getSingleton().createManual( + "terrain/comp/rt", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, 128, 128, 0, Ogre::PF_A8B8G8R8, Ogre::TU_RENDERTARGET); + mCompositeMapRenderTarget = mCompositeMapRenderTexture->getBuffer()->getRenderTarget(); + mCompositeMapRenderTarget->setAutoUpdated(false); + mCompositeMapRenderTarget->addViewport(compositeMapCam); + + mBounds = storage->getBounds(); + + int origSizeX = mBounds.getSize().x; + int origSizeY = mBounds.getSize().y; + + // Dividing a quad tree only works well for powers of two, so round up to the nearest one + int size = nextPowerOfTwo(std::max(origSizeX, origSizeY)); + + // Adjust the center according to the new size + Ogre::Vector3 center = mBounds.getCenter() + Ogre::Vector3((size-origSizeX)/2.f, (size-origSizeY)/2.f, 0); + + mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL); + buildQuadTree(mRootNode); + } + + Terrain::~Terrain() + { + delete mRootNode; + delete mStorage; + } + + void Terrain::buildQuadTree(QuadTreeNode *node) + { + float halfSize = node->getSize()/2.f; + + if (node->getSize() <= mMinBatchSize) + { + // We arrived at a leaf + float minZ,maxZ; + Ogre::Vector2 center = node->getCenter(); + if (mStorage->getMinMaxHeights(node->getSize(), center, minZ, maxZ)) + node->setBoundingBox(Ogre::AxisAlignedBox(Ogre::Vector3(-halfSize*8192, -halfSize*8192, minZ), + Ogre::Vector3(halfSize*8192, halfSize*8192, maxZ))); + else + node->markAsDummy(); // no data available for this node, skip it + return; + } + + if (node->getCenter().x - halfSize > mBounds.getMaximum().x + || node->getCenter().x + halfSize < mBounds.getMinimum().x + || node->getCenter().y - halfSize > mBounds.getMaximum().y + || node->getCenter().y + halfSize < mBounds.getMinimum().y ) + // Out of bounds of the actual terrain - this will happen because + // we rounded the size up to the next power of two + { + node->markAsDummy(); + return; + } + + // Not a leaf, create its children + node->createChild(SW, halfSize, node->getCenter() - halfSize/2.f); + node->createChild(SE, halfSize, node->getCenter() + Ogre::Vector2(halfSize/2.f, -halfSize/2.f)); + node->createChild(NW, halfSize, node->getCenter() + Ogre::Vector2(-halfSize/2.f, halfSize/2.f)); + node->createChild(NE, halfSize, node->getCenter() + halfSize/2.f); + buildQuadTree(node->getChild(SW)); + buildQuadTree(node->getChild(SE)); + buildQuadTree(node->getChild(NW)); + buildQuadTree(node->getChild(NE)); + + // if all children are dummy, we are also dummy + for (int i=0; i<4; ++i) + { + if (!node->getChild((ChildDirection)i)->isDummy()) + return; + } + node->markAsDummy(); + } + + void Terrain::update(Ogre::Camera *camera) + { + mRootNode->update(camera->getRealPosition()); + mRootNode->updateIndexBuffers(); + } + + Ogre::AxisAlignedBox Terrain::getWorldBoundingBox (const Ogre::Vector2& center) + { + QuadTreeNode* node = findNode(center, mRootNode); + Ogre::AxisAlignedBox box = node->getBoundingBox(); + box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192, + box.getMaximum() + Ogre::Vector3(center.x, center.y, 0) * 8192); + return box; + } + + Ogre::HardwareVertexBufferSharedPtr Terrain::getVertexBuffer(int numVertsOneSide) + { + if (mUvBufferMap.find(numVertsOneSide) != mUvBufferMap.end()) + { + return mUvBufferMap[numVertsOneSide]; + } + + int vertexCount = numVertsOneSide * numVertsOneSide; + + std::vector uvs; + uvs.reserve(vertexCount*2); + + for (int col = 0; col < numVertsOneSide; ++col) + { + for (int row = 0; row < numVertsOneSide; ++row) + { + uvs.push_back(col / static_cast(numVertsOneSide-1)); // U + uvs.push_back(row / static_cast(numVertsOneSide-1)); // V + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareVertexBufferSharedPtr buffer = mgr->createVertexBuffer( + Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2), + vertexCount, Ogre::HardwareBuffer::HBU_STATIC); + + buffer->writeData(0, buffer->getSizeInBytes(), &uvs[0], true); + + mUvBufferMap[numVertsOneSide] = buffer; + return buffer; + } + + Ogre::HardwareIndexBufferSharedPtr Terrain::getIndexBuffer(int flags, size_t& numIndices) + { + if (mIndexBufferMap.find(flags) != mIndexBufferMap.end()) + { + numIndices = mIndexBufferMap[flags]->getNumIndexes(); + return mIndexBufferMap[flags]; + } + + // LOD level n means every 2^n-th vertex is kept + size_t lodLevel = (flags >> (4*4)); + + size_t lodDeltas[4]; + for (int i=0; i<4; ++i) + lodDeltas[i] = (flags >> (4*i)) & (0xf); + + bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); + + size_t increment = std::pow(2, lodLevel); + assert((int)increment < ESM::Land::LAND_SIZE); + std::vector indices; + indices.reserve((ESM::Land::LAND_SIZE-1)*(ESM::Land::LAND_SIZE-1)*2*3 / increment); + + size_t rowStart = 0, colStart = 0, rowEnd = ESM::Land::LAND_SIZE-1, colEnd = ESM::Land::LAND_SIZE-1; + // If any edge needs stitching we'll skip all edges at this point, + // mainly because stitching one edge would have an effect on corners and on the adjacent edges + if (anyDeltas) + { + colStart += increment; + colEnd -= increment; + rowEnd -= increment; + rowStart += increment; + } + for (size_t row = rowStart; row < rowEnd; row += increment) + { + for (size_t col = colStart; col < colEnd; col += increment) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); + indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); + + indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); + } + } + + size_t innerStep = increment; + if (anyDeltas) + { + // Now configure LOD transitions at the edges - this is pretty tedious, + // and some very long and boring code, but it works great + + // South + size_t row = 0; + size_t outerStep = std::pow(2, lodDeltas[South] + lodLevel); + for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + // Make sure not to touch the left edge + if (col == 0) + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*col+row+innerStep); + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep); + } + } + + // North + row = ESM::Land::LAND_SIZE-1; + outerStep = std::pow(2, lodDeltas[North] + lodLevel); + for (size_t col = 0; col < ESM::Land::LAND_SIZE-1; col += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + indices.push_back(ESM::Land::LAND_SIZE*col+row); + // Make sure not to touch the right edge + if (col+outerStep == ESM::Land::LAND_SIZE-1) + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row-innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row-innerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the left or right edges + if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep); + indices.push_back(ESM::Land::LAND_SIZE*col+row); + } + } + + // West + size_t col = 0; + outerStep = std::pow(2, lodDeltas[West] + lodLevel); + for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + indices.push_back(ESM::Land::LAND_SIZE*col+row); + // Make sure not to touch the bottom edge + if (row == 0) + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i); + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep); + } + } + + // East + col = ESM::Land::LAND_SIZE-1; + outerStep = std::pow(2, lodDeltas[East] + lodLevel); + for (size_t row = 0; row < ESM::Land::LAND_SIZE-1; row += outerStep) + { + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + // Make sure not to touch the top edge + if (row+outerStep == ESM::Land::LAND_SIZE-1) + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+outerStep-innerStep); + else + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+outerStep); + + for (size_t i = 0; i < outerStep; i += innerStep) + { + // Make sure not to touch the top or bottom edges + if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) + continue; + indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i); + } + } + } + + + + numIndices = indices.size(); + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, + numIndices, Ogre::HardwareBuffer::HBU_STATIC); + buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); + mIndexBufferMap[flags] = buffer; + return buffer; + } + + void Terrain::renderCompositeMap(Ogre::TexturePtr target) + { + mCompositeMapRenderTarget->update(); + target->getBuffer()->blit(mCompositeMapRenderTexture->getBuffer()); + } + + void Terrain::clearCompositeMapSceneManager() + { + mCompositeMapSceneMgr->destroyAllManualObjects(); + mCompositeMapSceneMgr->clearScene(); + } + + +} diff --git a/components/terrain/terrain.hpp b/components/terrain/terrain.hpp new file mode 100644 index 0000000000..79e35fd8e9 --- /dev/null +++ b/components/terrain/terrain.hpp @@ -0,0 +1,121 @@ +#ifndef COMPONENTS_TERRAIN_H +#define COMPONENTS_TERRAIN_H + +#include +#include +#include +#include + +namespace Ogre +{ + class Camera; +} + +namespace Terrain +{ + + class QuadTreeNode; + class Storage; + + /** + * @brief A quadtree-based terrain implementation suitable for large data sets. \n + * Near cells are rendered with alpha splatting, distant cells are merged + * together in batches and have their layers pre-rendered onto a composite map. \n + * Cracks at LOD transitions are avoided using stitching. + * @note Multiple cameras are not supported yet + */ + class Terrain + { + public: + /// @note takes ownership of \a storage + Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags); + ~Terrain(); + + /// Update chunk LODs according to this camera position + /// @note Calling this method might lead to composite textures being rendered, so it is best + /// not to call it when render commands are still queued, since that would cause a flush. + void update (Ogre::Camera* camera); + + /// \todo + float getHeightAt (const Ogre::Vector3& worldPos) { return 0; } + + /// Get the world bounding box of a chunk of terrain centered at \a center + Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); + + Ogre::SceneManager* getSceneManager() { return mSceneMgr; } + + Storage* getStorage() { return mStorage; } + + /// Show or hide the whole terrain + void setVisible(bool visible); + + /// Recreate materials used by terrain chunks. This should be called whenever settings of + /// the material factory are changed. (Relying on the factory to update those materials is not + /// enough, since turning a feature on/off can change the number of texture units available for layer/blend + /// textures, and to properly respond to this we may need to change the structure of the material, such as + /// adding or removing passes. This can only be achieved by a full rebuild.) + void applyMaterials(); + + int getVisiblityFlags() { return mVisibilityFlags; } + + int getMaxBatchSize() { return mMaxBatchSize; } + + void enableSplattingShader(bool enabled); + + private: + QuadTreeNode* mRootNode; + Storage* mStorage; + + int mVisibilityFlags; + + Ogre::SceneManager* mSceneMgr; + Ogre::SceneManager* mCompositeMapSceneMgr; + + /// Bounds in cell units + Ogre::AxisAlignedBox mBounds; + + /// Minimum size of a terrain batch along one side (in cell units) + float mMinBatchSize; + /// Maximum size of a terrain batch along one side (in cell units) + float mMaxBatchSize; + + void buildQuadTree(QuadTreeNode* node); + + public: + // ----INTERNAL---- + + enum IndexBufferFlags + { + IBF_North = 1 << 0, + IBF_East = 1 << 1, + IBF_South = 1 << 2, + IBF_West = 1 << 3 + }; + + /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) + /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) + /// @param numIndices number of indices that were used will be written here + Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags, size_t& numIndices); + + Ogre::HardwareVertexBufferSharedPtr getVertexBuffer (int numVertsOneSide); + + Ogre::SceneManager* getCompositeMapSceneManager() { return mCompositeMapSceneMgr; } + + // Delete all quads + void clearCompositeMapSceneManager(); + void renderCompositeMap (Ogre::TexturePtr target); + + private: + // Index buffers are shared across terrain batches where possible. There is one index buffer for each + // combination of LOD deltas and index buffer LOD we may need. + std::map mIndexBufferMap; + + std::map mUvBufferMap; + + Ogre::RenderTarget* mCompositeMapRenderTarget; + Ogre::TexturePtr mCompositeMapRenderTexture; + }; + +} + +#endif diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 0c869d2cb8..36f92bfd91 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -80,7 +80,6 @@ #endif #if VERTEX_LIGHTING - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 1330229570..1f11217d23 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -2,7 +2,7 @@ #define IS_FIRST_PASS (@shPropertyString(pass_index) == 0) -#define FOG @shGlobalSettingBool(fog) +#define FOG (@shGlobalSettingBool(fog) && !@shPropertyBool(render_composite_map)) #define SHADOWS_PSSM @shGlobalSettingBool(shadows_pssm) #define SHADOWS @shGlobalSettingBool(shadows) @@ -11,8 +11,6 @@ #include "shadows.h" #endif -#define COLOUR_MAP @shPropertyBool(colour_map) - #define NUM_LAYERS @shPropertyString(num_layers) #if FOG || SHADOWS_PSSM @@ -23,9 +21,12 @@ #define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix) -#if !IS_FIRST_PASS -// This is not the first pass. -#endif +#define RENDERCMP @shPropertyBool(render_composite_map) + +#define LIGHTING !RENDERCMP + +#define COMPOSITE_MAP @shPropertyBool(display_composite_map) + #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -35,6 +36,11 @@ @shAllocatePassthrough(3, worldPos) +#if LIGHTING +@shAllocatePassthrough(3, lightResult) +@shAllocatePassthrough(3, directionalResult) +#endif + #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) #endif @@ -55,11 +61,19 @@ #if VIEWPROJ_FIX shUniform(float4, vpRow2Fix) @shSharedParameter(vpRow2Fix, vpRow2Fix) #endif - - shUniform(float2, lodMorph) @shAutoConstant(lodMorph, custom, 1001) shVertexInput(float2, uv0) shVertexInput(float2, uv1) // lodDelta, lodThreshold + +#if LIGHTING + shNormalInput(float4) + shColourInput(float4) + + shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) + shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_space_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights)) + shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour) #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) @@ -71,31 +85,15 @@ @shEndForeach #endif +#endif + @shPassthroughVertexOutputs SH_START_PROGRAM { - - float4 worldPos = shMatrixMult(worldMatrix, shInputPosition); - // 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 - float toMorph = -min(0, sign(uv1.y - lodMorph.y)); - - // morph - // this assumes XY terrain alignment - worldPos.z += uv1.x * toMorph * lodMorph.x; - - shOutputPosition = shMatrixMult(viewProjMatrix, worldPos); #if NEED_DEPTH @@ -124,6 +122,8 @@ @shPassthroughAssign(worldPos, worldPos.xyz); +#if LIGHTING + #if SHADOWS float4 lightSpacePos = shMatrixMult(texViewProjMatrix0, shMatrixMult(worldMatrix, shInputPosition)); @shPassthroughAssign(lightSpacePos0, lightSpacePos); @@ -138,6 +138,33 @@ @shEndForeach #endif + + // Lighting + float3 lightDir; + float d; + float3 lightResult = float3(0,0,0); + float3 directionalResult = float3(0,0,0); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (shInputPosition.xyz * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + + lightResult.xyz += lightDiffuse[@shIterator].xyz + * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normal.xyz, lightDir), 0); + +#if @shIterator == 0 + directionalResult = lightResult.xyz; +#endif + @shEndForeach + lightResult.xyz += lightAmbient.xyz; + lightResult.xyz *= colour.xyz; + + @shPassthroughAssign(lightResult, lightResult); + @shPassthroughAssign(directionalResult, directionalResult); + +#endif } #else @@ -151,12 +178,9 @@ SH_BEGIN_PROGRAM -#if COLOUR_MAP - shSampler2D(colourMap) -#endif - - shSampler2D(normalMap) // global normal map - +#if COMPOSITE_MAP + shSampler2D(compositeMap) +#else @shForeach(@shPropertyString(num_blendmaps)) shSampler2D(blendMap@shIterator) @@ -165,6 +189,8 @@ @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) @shEndForeach + +#endif #if FOG shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour) @@ -215,9 +241,6 @@ float2 UV = @shPassthroughReceive(UV); float3 worldPos = @shPassthroughReceive(worldPos); - - float3 normal = shSample(normalMap, UV).rgb * 2 - 1; - normal = normalize(normal); #if UNDERWATER @@ -230,17 +253,26 @@ float previousAlpha = 1.f; #endif + +shOutputColour(0) = float4(1,1,1,1); + +#if COMPOSITE_MAP + shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; +#else + // Layer calculations -// rescale UV to directly map vertices to texel centers +// rescale UV to directly map edge vertices to texel centers - this is +// important to get correct blending at cell transitions // TODO: parameterize texel size -float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; +float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; @shForeach(@shPropertyString(num_blendmaps)) - float4 blendValues@shIterator = shSample(blendMap@shIterator, blendUV); + float4 blendValues@shIterator = shSaturate(shSample(blendMap@shIterator, blendUV)); @shEndForeach + float3 albedo = float3(0,0,0); - float2 layerUV = UV * 8; + float2 layerUV = UV * 16; @shForeach(@shPropertyString(num_layers)) @@ -262,25 +294,14 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; #endif @shEndForeach - shOutputColour(0) = float4(1,1,1,1); - - -#if COLOUR_MAP - // Since we're emulating vertex colors here, - // rescale UV to directly map vertices to texel centers. TODO: parameterize texel size - const float colourmapSize = 33.f; - float2 colourUV = (UV - 0.5) * (colourmapSize / (colourmapSize+1.f)) + 0.5; - shOutputColour(0).rgb *= shSample(colourMap, colourUV).rgb; -#endif - shOutputColour(0).rgb *= albedo; - - - - - +#endif + +#if LIGHTING // Lighting + float3 lightResult = @shPassthroughReceive(lightResult); + float3 directionalResult = @shPassthroughReceive(directionalResult); // shadows only for the first (directional) light #if SHADOWS @@ -305,40 +326,9 @@ float2 blendUV = (UV - 0.5) * (8.0 / (8.0+1.0)) + 0.5; float shadow = 1.0; #endif - - - float3 lightDir; - float3 diffuse = float3(0,0,0); - float d; - - @shForeach(@shGlobalSettingString(terrain_num_lights)) - - lightDir = lightPosObjSpace@shIterator.xyz - (worldPos.xyz * lightPosObjSpace@shIterator.w); - d = length(lightDir); - - - lightDir = normalize(lightDir); - -#if @shIterator == 0 - - #if (SHADOWS || SHADOWS_PSSM) - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0) * shadow; - - #else - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); - - #endif - -#else - diffuse += lightDiffuse@shIterator.xyz * (1.0 / ((lightAttenuation@shIterator.y) + (lightAttenuation@shIterator.z * d) + (lightAttenuation@shIterator.w * d * d))) * max(dot(normal, lightDir), 0); + shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); #endif - @shEndForeach - - shOutputColour(0).xyz *= (lightAmbient.xyz + diffuse); - - - #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); diff --git a/libs/openengine/ogre/imagerotate.cpp b/libs/openengine/ogre/imagerotate.cpp index 3dd5840785..9c32924f1f 100644 --- a/libs/openengine/ogre/imagerotate.cpp +++ b/libs/openengine/ogre/imagerotate.cpp @@ -56,7 +56,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest TEX_TYPE_2D, width, height, 0, - PF_FLOAT16_RGBA, + PF_A8B8G8R8, TU_RENDERTARGET); RenderTarget* rtt = destTextureRot->getBuffer()->getRenderTarget(); @@ -75,7 +75,7 @@ void ImageRotate::rotate(const std::string& sourceImage, const std::string& dest TEX_TYPE_2D, width, height, 0, - PF_FLOAT16_RGBA, + PF_A8B8G8R8, Ogre::TU_STATIC); destTexture->getBuffer()->blit(destTextureRot->getBuffer()); From 584eec37434409f982150c3d60d88f2705b953b5 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Fri, 16 Aug 2013 04:18:48 -0700 Subject: [PATCH 03/26] Store the object class in the LiveCellRef --- apps/openmw/mwworld/class.cpp | 5 +++-- apps/openmw/mwworld/class.hpp | 8 +++++++- apps/openmw/mwworld/livecellref.hpp | 7 +++---- apps/openmw/mwworld/ptr.cpp | 15 +++++++++++++++ apps/openmw/mwworld/ptr.hpp | 10 ++++++---- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index c7b8d341c1..c739ea831c 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -245,9 +245,10 @@ namespace MWWorld throw std::runtime_error ("class does not support persistence"); } - void Class::registerClass (const std::string& key, boost::shared_ptr instance) + void Class::registerClass(const std::string& key, boost::shared_ptr instance) { - sClasses.insert (std::make_pair (key, instance)); + instance->mTypeName = key; + sClasses.insert(std::make_pair(key, instance)); } std::string Class::getUpSoundId (const Ptr& ptr) const diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index c910000525..28e37cbf3c 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -50,6 +50,8 @@ namespace MWWorld { static std::map > sClasses; + std::string mTypeName; + // not implemented Class (const Class&); Class& operator= (const Class&); @@ -73,6 +75,10 @@ namespace MWWorld virtual ~Class(); + const std::string& getTypeName() const { + return mTypeName; + } + virtual std::string getId (const Ptr& ptr) const; ///< Return ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) @@ -292,7 +298,7 @@ namespace MWWorld static const Class& get (const Ptr& ptr) { - return get(ptr.getTypeName()); + return ptr.getClass(); } ///< If there is no class for this pointer, an exception is thrown. diff --git a/apps/openmw/mwworld/livecellref.hpp b/apps/openmw/mwworld/livecellref.hpp index 64462cb3fd..415351e783 100644 --- a/apps/openmw/mwworld/livecellref.hpp +++ b/apps/openmw/mwworld/livecellref.hpp @@ -11,11 +11,12 @@ namespace MWWorld { class Ptr; class ESMStore; + class Class; /// Used to create pointers to hold any type of LiveCellRef<> object. struct LiveCellRefBase { - std::string mTypeName; + const Class *mClass; /** Information about this instance, such as 3D location and rotation * and individual type-dependent data. @@ -25,9 +26,7 @@ namespace MWWorld /** runtime-data */ RefData mData; - LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef()) - : mTypeName(type), mRef(cref), mData(mRef) - { } + LiveCellRefBase(std::string type, const ESM::CellRef &cref=ESM::CellRef()); /* Need this for the class to be recognized as polymorphic */ virtual ~LiveCellRefBase() { } }; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 34fe7fa08a..127ab1364c 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -4,8 +4,23 @@ #include #include "containerstore.hpp" +#include "class.hpp" +/* This shouldn't really be here. */ +MWWorld::LiveCellRefBase::LiveCellRefBase(std::string type, const ESM::CellRef &cref) + : mClass(&Class::get(type)), mRef(cref), mData(mRef) +{ +} + + +const std::string& MWWorld::Ptr::getTypeName() const +{ + if(mRef != 0) + return mRef->mClass->getTypeName(); + throw std::runtime_error("Can't get type name from an empty object."); +} + ESM::CellRef& MWWorld::Ptr::getCellRef() const { assert(mRef); diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 51c1530d7e..e5352da280 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -32,11 +32,13 @@ namespace MWWorld return mRef == 0; } - const std::string& getTypeName() const + const std::string& getTypeName() const; + + const Class& getClass() const { if(mRef != 0) - return mRef->mTypeName; - throw std::runtime_error("Can't get type name from an empty object."); + return *(mRef->mClass); + throw std::runtime_error("Cannot get class of an empty object"); } template @@ -47,7 +49,7 @@ namespace MWWorld std::stringstream str; str<< "Bad LiveCellRef cast to "<mTypeName; + if(mRef != 0) str<< getTypeName(); else str<< "an empty object"; throw std::runtime_error(str.str()); From 0076c558d61da4080b807a336318dacb9021d9dc Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 16 Aug 2013 22:32:16 +0200 Subject: [PATCH 04/26] Re-introduce lost functionality The branch merged in 5a863589b4 removed fine-grained configure-time control over install paths. This is necessary to accomodate various linux distros' policies, eg. Gentoo wants games installed in /usr/games, but with resource files in /usr/share/games. DOCDIR and MANDIR appear to be unused, and were not re-introduced. --- CMakeLists.txt | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 575fecd0c5..85860609be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -334,12 +334,18 @@ IF(NOT WIN32 AND NOT APPLE) IF (DPKG_PROGRAM) ## Debian specific SET(CMAKE_INSTALL_PREFIX "/usr") + SET(DATAROOTDIR "share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "share/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "share/pixmaps" CACHE PATH "Set icon dir") SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir") ELSE () ## Non debian specific SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(LICDIR "${CMAKE_INSTALL_PREFIX}/share/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") + SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") + SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") + SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") # Install binaries INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) @@ -367,11 +373,11 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF (DPKG_PROGRAM) # Install icon and desktop file - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "share/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "share/pixmaps/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "share/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "share/pixmaps/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "${DATAROOTDIR}/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "${ICONDIR}/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install global configuration files @@ -383,8 +389,8 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(BUILD_OPENCS) # Install resources - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "share/games/openmw/" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") - INSTALL(DIRECTORY DESTINATION "share/games/openmw/data/" COMPONENT "Resources") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}/" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") + INSTALL(DIRECTORY DESTINATION "${DATADIR}/data/" COMPONENT "Resources") IF (DPKG_PROGRAM) ## Debian Specific From 8d925b7fd640b75bae682ba6bda9b16dcd371ed0 Mon Sep 17 00:00:00 2001 From: eroen Date: Fri, 16 Aug 2013 22:32:16 +0200 Subject: [PATCH 05/26] cleanup - drop trailing slashes from paths for consistency - sort entries that got unsorted --- CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 85860609be..dd58ed4168 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -340,12 +340,12 @@ IF(NOT WIN32 AND NOT APPLE) SET(SYSCONFDIR "../etc/openmw" CACHE PATH "Set config dir") ELSE () ## Non debian specific - SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") SET(DATADIR "${DATAROOTDIR}/games/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") SET(ICONDIR "${DATAROOTDIR}/pixmaps" CACHE PATH "Set icon dir") SET(LICDIR "${DATAROOTDIR}/licenses/openmw" CACHE PATH "Sets the openmw license directory to a non-default location.") + SET(SYSCONFDIR "/etc/openmw" CACHE PATH "Set config dir") # Install binaries INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) @@ -373,11 +373,11 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF (DPKG_PROGRAM) # Install icon and desktop file - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "${DATAROOTDIR}/applications/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "${ICONDIR}/" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install global configuration files @@ -389,8 +389,8 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF(BUILD_OPENCS) # Install resources - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}/" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") - INSTALL(DIRECTORY DESTINATION "${DATADIR}/data/" COMPONENT "Resources") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") + INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") IF (DPKG_PROGRAM) ## Debian Specific From 76b812f75fa1c6acab36605ac9359ce37869a23d Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 01:11:57 -0700 Subject: [PATCH 06/26] Improve actor movement collision handling --- apps/openmw/mwworld/physicssystem.cpp | 127 ++++++++++++-------------- 1 file changed, 56 insertions(+), 71 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 371783bc54..e8450e1d29 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -31,17 +31,22 @@ namespace MWWorld static const float sMaxSlope = 60.0f; static const float sStepSize = 30.0f; // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. - static const int sMaxIterations = 50; + static const int sMaxIterations = 4; class MovementSolver { private: - static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, const Ogre::Vector3 &velocity, float remainingTime, + static float getSlope(const Ogre::Vector3 &normal) + { + return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); + } + + static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, + const Ogre::Vector3 &velocity, float &remainingTime, const Ogre::Vector3 &halfExtents, bool isInterior, OEngine::Physic::PhysicEngine *engine) { - traceResults trace; // no initialization needed - + traceResults trace; newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); if(trace.fraction == 0.0f) @@ -51,44 +56,33 @@ namespace MWWorld halfExtents, isInterior, engine); if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) return false; + float movefrac = trace.fraction; newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); if(getSlope(trace.planenormal) <= sMaxSlope) { // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. position = trace.endpos; + remainingTime *= (1.0f-movefrac); return true; } return false; } - static void clipVelocity(Ogre::Vector3& inout, const Ogre::Vector3& normal, float overbounce=1.0f) - { - //Math stuff. Basically just project the velocity vector onto the plane represented by the normal. - //More specifically, it projects velocity onto the normal, takes that result, multiplies it by overbounce and then subtracts it from velocity. - float backoff = inout.dotProduct(normal); - if(backoff < 0.0f) - backoff *= overbounce; - else - backoff /= overbounce; - inout -= normal*backoff; + ///Project a vector u on another vector v + static inline Ogre::Vector3 project(const Ogre::Vector3 u, const Ogre::Vector3 &v) + { + return v * u.dotProduct(v); } - static void projectVelocity(Ogre::Vector3& velocity, const Ogre::Vector3& direction) + ///Helper for computing the character sliding + static inline Ogre::Vector3 slide(Ogre::Vector3 direction, const Ogre::Vector3 &planeNormal) { - Ogre::Vector3 normalizedDirection(direction); - normalizedDirection.normalise(); - - // no divide by normalizedDirection.length necessary because it's normalized - velocity = normalizedDirection * velocity.dotProduct(normalizedDirection); + return direction - project(direction, planeNormal); } - static float getSlope(const Ogre::Vector3 &normal) - { - return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); - } public: static Ogre::Vector3 traceDown(const MWWorld::Ptr &ptr, OEngine::Physic::PhysicEngine *engine) @@ -104,7 +98,6 @@ namespace MWWorld return position; bool wasCollisionMode = physicActor->getCollisionMode(); - physicActor->enableCollisions(false); Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); @@ -124,10 +117,9 @@ namespace MWWorld if (wasCollisionMode) physicActor->enableCollisions(true); - if (hit) - return newPosition+Ogre::Vector3(0,0,4); - else + if(!hit) return position; + return newPosition+Ogre::Vector3(0,0,4); } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, @@ -147,14 +139,13 @@ namespace MWWorld movement; } - traceResults trace; //no initialization needed - bool onground = false; - float remainingTime = time; bool isInterior = !ptr.getCell()->isExterior(); Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); - bool wasCollisionMode = physicActor->getCollisionMode(); physicActor->enableCollisions(false); - Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(ptr.getRefData().getPosition().rot[2]), Ogre::Vector3::UNIT_Z); + + traceResults trace; + bool onground = false; + const Ogre::Quaternion orient; // Don't rotate actor collision boxes Ogre::Vector3 velocity; if(!gravity) { @@ -175,53 +166,46 @@ namespace MWWorld velocity.z += physicActor->getVerticalForce(); } - Ogre::Vector3 clippedVelocity(velocity); if(onground) { - // if we're on the ground, force velocity to track it - clippedVelocity.z = velocity.z = std::max(0.0f, velocity.z); - clipVelocity(clippedVelocity, trace.planenormal); + // if we're on the ground, don't try to fall + velocity.z = std::max(0.0f, velocity.z); } const Ogre::Vector3 up(0.0f, 0.0f, 1.0f); Ogre::Vector3 newPosition = position; - int iterations = 0; - do { + float remainingTime = time; + for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) + { // trace to where character would go if there were no obstructions - newtrace(&trace, orient, newPosition, newPosition+clippedVelocity*remainingTime, halfExtents, isInterior, engine); - newPosition = trace.endpos; - remainingTime = remainingTime * (1.0f-trace.fraction); + newtrace(&trace, orient, newPosition, newPosition+velocity*remainingTime, halfExtents, isInterior, engine); // check for obstructions - if(trace.fraction < 1.0f) + if(trace.fraction >= 1.0f) { - //std::cout<<"angle: "< 0.0f); + //std::cout<<"angle: "<setOnGround(onground); - physicActor->setVerticalForce(!onground ? clippedVelocity.z - time*627.2f : 0.0f); - if (wasCollisionMode) - physicActor->enableCollisions(true); + physicActor->setVerticalForce(!onground ? velocity.z - time*627.2f : 0.0f); + physicActor->enableCollisions(true); + return newPosition; } }; From 14acacf4012b0466302bbd892585f1b3ee3114a6 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 02:51:19 -0700 Subject: [PATCH 07/26] Use a better method to do actor physics traces --- apps/openmw/mwworld/physicssystem.cpp | 35 ++++++------ libs/openengine/bullet/physic.hpp | 48 +++++++++------- libs/openengine/bullet/trace.cpp | 82 +++++++++++++++++++++++++-- libs/openengine/bullet/trace.h | 4 ++ 4 files changed, 124 insertions(+), 45 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index e8450e1d29..ea013bc7a4 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -41,24 +41,21 @@ namespace MWWorld return normal.angleBetween(Ogre::Vector3(0.0f,0.0f,1.0f)).valueDegrees(); } - static bool stepMove(Ogre::Vector3& position, const Ogre::Quaternion& orient, + static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position, const Ogre::Vector3 &velocity, float &remainingTime, - const Ogre::Vector3 &halfExtents, bool isInterior, OEngine::Physic::PhysicEngine *engine) { traceResults trace; - newtrace(&trace, orient, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), - halfExtents, isInterior, engine); + actortrace(&trace, colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine); if(trace.fraction == 0.0f) return false; - newtrace(&trace, orient, trace.endpos, trace.endpos + velocity*remainingTime, - halfExtents, isInterior, engine); + actortrace(&trace, colobj, trace.endpos, trace.endpos + velocity*remainingTime, engine); if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) return false; float movefrac = trace.fraction; - newtrace(&trace, orient, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), halfExtents, isInterior, engine); + actortrace(&trace, colobj, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); if(getSlope(trace.planenormal) <= sMaxSlope) { // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. @@ -100,7 +97,7 @@ namespace MWWorld bool wasCollisionMode = physicActor->getCollisionMode(); physicActor->enableCollisions(false); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); + Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); halfExtents.z = 1; // we trace the feet only, so we use a very thin box Ogre::Vector3 newPosition = position; @@ -133,15 +130,15 @@ namespace MWWorld if(!physicActor || !physicActor->getCollisionMode()) { // FIXME: This works, but it's inconcsistent with how the rotations are applied elsewhere. Why? - return position + (Ogre::Quaternion(Ogre::Radian( -refpos.rot[2]), Ogre::Vector3::UNIT_Z)* - Ogre::Quaternion(Ogre::Radian( -refpos.rot[1]), Ogre::Vector3::UNIT_Y)* - Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * + return position + (Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)* + Ogre::Quaternion(Ogre::Radian(-refpos.rot[1]), Ogre::Vector3::UNIT_Y)* + Ogre::Quaternion(Ogre::Radian( refpos.rot[0]), Ogre::Vector3::UNIT_X)) * movement; } - bool isInterior = !ptr.getCell()->isExterior(); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents();// + Vector3(1,1,1); - physicActor->enableCollisions(false); + btCollisionObject *colobj = physicActor->getCollisionBody(); + Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); + position.z += halfExtents.z; traceResults trace; bool onground = false; @@ -158,7 +155,7 @@ namespace MWWorld { if(!(movement.z > 0.0f)) { - newtrace(&trace, orient, position, position-Ogre::Vector3(0,0,4), halfExtents, isInterior, engine); + actortrace(&trace, colobj, position, position-Ogre::Vector3(0,0,4), engine); if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) onground = true; } @@ -178,7 +175,7 @@ namespace MWWorld for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) { // trace to where character would go if there were no obstructions - newtrace(&trace, orient, newPosition, newPosition+velocity*remainingTime, halfExtents, isInterior, engine); + actortrace(&trace, colobj, newPosition, newPosition+velocity*remainingTime, engine); // check for obstructions if(trace.fraction >= 1.0f) @@ -190,7 +187,7 @@ namespace MWWorld //std::cout<<"angle: "<setOnGround(onground); physicActor->setVerticalForce(!onground ? velocity.z - time*627.2f : 0.0f); - physicActor->enableCollisions(true); + newPosition.z -= halfExtents.z; return newPosition; } }; diff --git a/libs/openengine/bullet/physic.hpp b/libs/openengine/bullet/physic.hpp index 32af0da4e3..9b1541ea2f 100644 --- a/libs/openengine/bullet/physic.hpp +++ b/libs/openengine/bullet/physic.hpp @@ -66,6 +66,20 @@ namespace Physic std::string mName; }; + /** + *This class is just an extension of normal btRigidBody in order to add extra info. + *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, + *so one never should use btRigidBody directly! + */ + class RigidBody: public btRigidBody + { + public: + RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); + virtual ~RigidBody(); + std::string mName; + bool mPlaceable; + }; + /** * A physic actor uses a rigid body based on box shapes. * Pmove is used to move the physic actor around the dynamic world. @@ -129,47 +143,41 @@ namespace Physic bool getOnGround() const; + btCollisionObject *getCollisionBody() const + { + return mBody; + } + private: void disableCollisionBody(); void enableCollisionBody(); public: //HACK: in Visual Studio 2010 and presumably above, this structures alignment -// must be 16, but the built in operator new & delete don't properly -// perform this alignment. +// must be 16, but the built in operator new & delete don't properly +// perform this alignment. #if _MSC_VER >= 1600 - void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } - void operator delete (void * Data) { _aligned_free (Data); } + void * operator new (size_t Size) { return _aligned_malloc (Size, 16); } + void operator delete (void * Data) { _aligned_free (Data); } #endif private: - OEngine::Physic::RigidBody* mBody; OEngine::Physic::RigidBody* mRaycastingBody; + Ogre::Vector3 mBoxScaledTranslation; - btQuaternion mBoxRotationInverse; Ogre::Quaternion mBoxRotation; + btQuaternion mBoxRotationInverse; + float verticalForce; bool onGround; bool collisionMode; + std::string mMesh; - PhysicEngine* mEngine; std::string mName; + PhysicEngine *mEngine; }; - /** - *This class is just an extension of normal btRigidBody in order to add extra info. - *When bullet give back a btRigidBody, you can just do a static_cast to RigidBody, - *so one never should use btRigidBody directly! - */ - class RigidBody: public btRigidBody - { - public: - RigidBody(btRigidBody::btRigidBodyConstructionInfo& CI,std::string name); - virtual ~RigidBody(); - std::string mName; - bool mPlaceable; - }; struct HeightField { diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index d246417c7a..08c42d9d03 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -8,12 +8,6 @@ #include "physic.hpp" -enum traceWorldType -{ - collisionWorldTrace = 1, - pickWorldTrace = 2, - bothWorldTrace = collisionWorldTrace | pickWorldTrace -}; void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine *enginePass) //Traceobj was a Aedra Object { @@ -46,3 +40,79 @@ void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre: results->fraction = 1.0f; } } + + +class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback +{ +public: + ClosestNotMeConvexResultCallback(btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot) + : btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)), + mMe(me), mUp(up), mMinSlopeDot(minSlopeDot) + { + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace) + { + if(convexResult.m_hitCollisionObject == mMe) + return btScalar( 1 ); + + btVector3 hitNormalWorld; + if(normalInWorldSpace) + hitNormalWorld = convexResult.m_hitNormalLocal; + else + { + ///need to transform normal into worldspace + hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + } + + // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... + + btScalar dotUp = mUp.dot(hitNormalWorld); + if(dotUp < mMinSlopeDot) + return btScalar(1); + + return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace); + } + +protected: + btCollisionObject *mMe; + const btVector3 mUp; + const btScalar mMinSlopeDot; +}; + +void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, OEngine::Physic::PhysicEngine *enginePass) +{ + const btVector3 btstart(start.x, start.y, start.z); + const btVector3 btend(end.x, end.y, end.z); + + const btTransform &trans = actor->getWorldTransform(); + btTransform from(trans); + btTransform to(trans); + from.setOrigin(btstart); + to.setOrigin(btend); + + ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0)); + newTraceCallback.m_collisionFilterMask = OEngine::Physic::CollisionType_World | + OEngine::Physic::CollisionType_HeightMap | + OEngine::Physic::CollisionType_Actor; + + btCollisionShape *shape = actor->getCollisionShape(); + assert(shape->isConvex()); + enginePass->dynamicsWorld->convexSweepTest(static_cast(shape), + from, to, newTraceCallback); + + // Copy the hit data over to our trace results struct: + if(newTraceCallback.hasHit()) + { + const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; + results->fraction = newTraceCallback.m_closestHitFraction; + results->planenormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + results->endpos = (end-start)*results->fraction + start; + } + else + { + results->endpos = end; + results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + results->fraction = 1.0f; + } +} diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index cd2547f8c0..79c6e72ef1 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -13,6 +13,9 @@ namespace OEngine } +class btCollisionObject; + + struct traceResults { Ogre::Vector3 endpos; @@ -22,5 +25,6 @@ struct traceResults }; void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); +void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3& start, const Ogre::Vector3& end, OEngine::Physic::PhysicEngine *enginePass); #endif From b35110964908240e954138603e7a14f5b7fdaf75 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 03:55:04 -0700 Subject: [PATCH 08/26] Get rid of the old newtrace method --- apps/openmw/mwworld/physicssystem.cpp | 37 ++++++++++----------------- libs/openengine/bullet/trace.cpp | 35 +------------------------ libs/openengine/bullet/trace.h | 1 - 3 files changed, 14 insertions(+), 59 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index ea013bc7a4..65cc806ea6 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -87,36 +87,25 @@ namespace MWWorld const ESM::Position &refpos = ptr.getRefData().getPosition(); Ogre::Vector3 position(refpos.pos); - bool hit=false; - bool isInterior = !ptr.getCell()->isExterior(); - OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); if (!physicActor) return position; - bool wasCollisionMode = physicActor->getCollisionMode(); - physicActor->enableCollisions(false); + const int maxHeight = 64.f; + Ogre::Vector3 newPosition = position+Ogre::Vector3(0.0f, 0.0f, 4.0f); - Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); - halfExtents.z = 1; // we trace the feet only, so we use a very thin box - - Ogre::Vector3 newPosition = position; - - traceResults trace; //no initialization needed - - int maxHeight = 200.f; - newtrace(&trace, Ogre::Quaternion::IDENTITY, newPosition, newPosition-Ogre::Vector3(0,0,maxHeight), halfExtents, isInterior, engine); - if(trace.fraction < 1.0f) - hit = true; - newPosition = trace.endpos; - - physicActor->setOnGround(hit && getSlope(trace.planenormal) <= sMaxSlope); - if (wasCollisionMode) - physicActor->enableCollisions(true); - - if(!hit) + traceResults trace; + actortrace(&trace, physicActor->getCollisionBody(), newPosition, newPosition-Ogre::Vector3(0,0,maxHeight), engine); + if(trace.fraction >= 1.0f) return position; - return newPosition+Ogre::Vector3(0,0,4); + + physicActor->setOnGround(getSlope(trace.planenormal) <= sMaxSlope); + + newPosition = trace.endpos; + newPosition.z -= physicActor->getHalfExtents().z; + newPosition.z += 4.0f; + + return newPosition; } static Ogre::Vector3 move(const MWWorld::Ptr &ptr, const Ogre::Vector3 &movement, float time, diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 08c42d9d03..744462a0ca 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -9,39 +9,6 @@ #include "physic.hpp" -void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine *enginePass) //Traceobj was a Aedra Object -{ - const btVector3 btstart(start.x, start.y, start.z + BBHalfExtents.z); - const btVector3 btend(end.x, end.y, end.z + BBHalfExtents.z); - const btQuaternion btorient (orient.x, orient.y, orient.z, orient.w); - - const btBoxShape newshape(btVector3(BBHalfExtents.x, BBHalfExtents.y, BBHalfExtents.z)); - //const btCapsuleShapeZ newshape(BBHalfExtents.x, BBHalfExtents.z * 2 - BBHalfExtents.x * 2); - const btTransform from(btorient, btstart); - const btTransform to(btorient, btend); - - btCollisionWorld::ClosestConvexResultCallback newTraceCallback(btstart, btend); - newTraceCallback.m_collisionFilterMask = OEngine::Physic::CollisionType_World|OEngine::Physic::CollisionType_HeightMap|OEngine::Physic::CollisionType_Actor; - - enginePass->dynamicsWorld->convexSweepTest(&newshape, from, to, newTraceCallback); - - // Copy the hit data over to our trace results struct: - if(newTraceCallback.hasHit()) - { - const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; - results->fraction = newTraceCallback.m_closestHitFraction; - results->planenormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); - results->endpos = (end-start)*results->fraction + start; - } - else - { - results->endpos = end; - results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); - results->fraction = 1.0f; - } -} - - class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: @@ -62,7 +29,7 @@ public: else { ///need to transform normal into worldspace - hitNormalWorld = m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; + hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal; } // NOTE : m_hitNormalLocal is not always vertical on the ground with a capsule or a box... diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index 79c6e72ef1..6353d6cfa3 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -24,7 +24,6 @@ struct traceResults float fraction; }; -void newtrace(traceResults *results, const Ogre::Quaternion& orient, const Ogre::Vector3& start, const Ogre::Vector3& end, const Ogre::Vector3& BBHalfExtents, bool isInterior, OEngine::Physic::PhysicEngine* enginePass); void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3& start, const Ogre::Vector3& end, OEngine::Physic::PhysicEngine *enginePass); #endif From 394fc7569778e092b52a8d1073cea31513b29b27 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 04:42:35 -0700 Subject: [PATCH 09/26] Clean up the trace struct --- apps/openmw/mwworld/physicssystem.cpp | 55 ++++++++++++++------------- libs/openengine/bullet/trace.cpp | 23 +++++++---- libs/openengine/bullet/trace.h | 30 +++++++-------- 3 files changed, 59 insertions(+), 49 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index 65cc806ea6..ee6514590f 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -45,21 +45,22 @@ namespace MWWorld const Ogre::Vector3 &velocity, float &remainingTime, OEngine::Physic::PhysicEngine *engine) { - traceResults trace; - actortrace(&trace, colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine); - if(trace.fraction == 0.0f) + OEngine::Physic::ActorTracer tracer; + tracer.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine); + if(tracer.mFraction == 0.0f) return false; - actortrace(&trace, colobj, trace.endpos, trace.endpos + velocity*remainingTime, engine); - if(trace.fraction == 0.0f || (trace.fraction != 1.0f && getSlope(trace.planenormal) > sMaxSlope)) + tracer.doTrace(colobj, tracer.mEndPos, tracer.mEndPos + velocity*remainingTime, engine); + if(tracer.mFraction < std::numeric_limits::epsilon() || + (tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) > sMaxSlope)) return false; - float movefrac = trace.fraction; + float movefrac = tracer.mFraction; - actortrace(&trace, colobj, trace.endpos, trace.endpos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); - if(getSlope(trace.planenormal) <= sMaxSlope) + tracer.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); + if(getSlope(tracer.mPlaneNormal) <= sMaxSlope) { // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. - position = trace.endpos; + position = tracer.mEndPos; remainingTime *= (1.0f-movefrac); return true; } @@ -94,16 +95,16 @@ namespace MWWorld const int maxHeight = 64.f; Ogre::Vector3 newPosition = position+Ogre::Vector3(0.0f, 0.0f, 4.0f); - traceResults trace; - actortrace(&trace, physicActor->getCollisionBody(), newPosition, newPosition-Ogre::Vector3(0,0,maxHeight), engine); - if(trace.fraction >= 1.0f) + OEngine::Physic::ActorTracer tracer; + tracer.doTrace(physicActor->getCollisionBody(), newPosition, newPosition-Ogre::Vector3(0,0,maxHeight), engine); + if(tracer.mFraction >= 1.0f) return position; - physicActor->setOnGround(getSlope(trace.planenormal) <= sMaxSlope); + physicActor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); - newPosition = trace.endpos; + newPosition = tracer.mEndPos; newPosition.z -= physicActor->getHalfExtents().z; - newPosition.z += 4.0f; + newPosition.z += 2.0f; return newPosition; } @@ -129,7 +130,7 @@ namespace MWWorld Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); position.z += halfExtents.z; - traceResults trace; + OEngine::Physic::ActorTracer tracer; bool onground = false; const Ogre::Quaternion orient; // Don't rotate actor collision boxes Ogre::Vector3 velocity; @@ -144,8 +145,8 @@ namespace MWWorld { if(!(movement.z > 0.0f)) { - actortrace(&trace, colobj, position, position-Ogre::Vector3(0,0,4), engine); - if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) + tracer.doTrace(colobj, position, position-Ogre::Vector3(0,0,4), engine); + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) onground = true; } velocity = Ogre::Quaternion(Ogre::Radian(-refpos.rot[2]), Ogre::Vector3::UNIT_Z)*movement / time; @@ -164,13 +165,13 @@ namespace MWWorld for(int iterations = 0;iterations < sMaxIterations && remainingTime > 0.01f;++iterations) { // trace to where character would go if there were no obstructions - actortrace(&trace, colobj, newPosition, newPosition+velocity*remainingTime, engine); + tracer.doTrace(colobj, newPosition, newPosition+velocity*remainingTime, engine); // check for obstructions - if(trace.fraction >= 1.0f) + if(tracer.mFraction >= 1.0f) { - newPosition = trace.endpos; - remainingTime *= (1.0f-trace.fraction); + newPosition = tracer.mEndPos; + remainingTime *= (1.0f-tracer.mFraction); break; } @@ -182,9 +183,9 @@ namespace MWWorld { // Can't move this way, try to find another spot along the plane Ogre::Real movelen = velocity.normalise(); - Ogre::Vector3 reflectdir = velocity.reflect(trace.planenormal); + Ogre::Vector3 reflectdir = velocity.reflect(tracer.mPlaneNormal); reflectdir.normalise(); - velocity = slide(reflectdir, trace.planenormal)*movelen; + velocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; // Do not allow sliding upward if there is gravity. Stepping will have taken // care of that. @@ -195,9 +196,9 @@ namespace MWWorld if(onground) { - actortrace(&trace, colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+4.0f), engine); - if(trace.fraction < 1.0f && getSlope(trace.planenormal) <= sMaxSlope) - newPosition.z = trace.endpos.z + 2.0f; + tracer.doTrace(colobj, newPosition, newPosition-Ogre::Vector3(0,0,sStepSize+4.0f), engine); + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + newPosition.z = tracer.mEndPos.z + 2.0f; else onground = false; } diff --git a/libs/openengine/bullet/trace.cpp b/libs/openengine/bullet/trace.cpp index 744462a0ca..5edf536200 100644 --- a/libs/openengine/bullet/trace.cpp +++ b/libs/openengine/bullet/trace.cpp @@ -9,6 +9,11 @@ #include "physic.hpp" +namespace OEngine +{ +namespace Physic +{ + class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: @@ -47,7 +52,8 @@ protected: const btScalar mMinSlopeDot; }; -void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, OEngine::Physic::PhysicEngine *enginePass) + +void ActorTracer::doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, const PhysicEngine *enginePass) { const btVector3 btstart(start.x, start.y, start.z); const btVector3 btend(end.x, end.y, end.z); @@ -72,14 +78,17 @@ void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vec if(newTraceCallback.hasHit()) { const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld; - results->fraction = newTraceCallback.m_closestHitFraction; - results->planenormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); - results->endpos = (end-start)*results->fraction + start; + mFraction = newTraceCallback.m_closestHitFraction; + mPlaneNormal = Ogre::Vector3(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z()); + mEndPos = (end-start)*mFraction + start; } else { - results->endpos = end; - results->planenormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); - results->fraction = 1.0f; + mEndPos = end; + mPlaneNormal = Ogre::Vector3(0.0f, 0.0f, 1.0f); + mFraction = 1.0f; } } + +} +} diff --git a/libs/openengine/bullet/trace.h b/libs/openengine/bullet/trace.h index 6353d6cfa3..f81579c2e3 100644 --- a/libs/openengine/bullet/trace.h +++ b/libs/openengine/bullet/trace.h @@ -4,26 +4,26 @@ #include -namespace OEngine -{ - namespace Physic - { - class PhysicEngine; - } -} - - class btCollisionObject; -struct traceResults +namespace OEngine { - Ogre::Vector3 endpos; - Ogre::Vector3 planenormal; +namespace Physic +{ + class PhysicEngine; - float fraction; -}; + struct ActorTracer + { + Ogre::Vector3 mEndPos; + Ogre::Vector3 mPlaneNormal; -void actortrace(traceResults *results, btCollisionObject *actor, const Ogre::Vector3& start, const Ogre::Vector3& end, OEngine::Physic::PhysicEngine *enginePass); + float mFraction; + + void doTrace(btCollisionObject *actor, const Ogre::Vector3 &start, const Ogre::Vector3 &end, + const PhysicEngine *enginePass); + }; +} +} #endif From d727b155805d23f07a95115896ea88b0715cd7e9 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Sat, 17 Aug 2013 04:55:35 -0700 Subject: [PATCH 10/26] Fix tracing down --- apps/openmw/mwworld/physicssystem.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index ee6514590f..1beb5a7e53 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -92,9 +92,10 @@ namespace MWWorld if (!physicActor) return position; - const int maxHeight = 64.f; - Ogre::Vector3 newPosition = position+Ogre::Vector3(0.0f, 0.0f, 4.0f); + const Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); + Ogre::Vector3 newPosition = position+Ogre::Vector3(0.0f, 0.0f, halfExtents.z); + const int maxHeight = 200.f; OEngine::Physic::ActorTracer tracer; tracer.doTrace(physicActor->getCollisionBody(), newPosition, newPosition-Ogre::Vector3(0,0,maxHeight), engine); if(tracer.mFraction >= 1.0f) @@ -103,7 +104,7 @@ namespace MWWorld physicActor->setOnGround(getSlope(tracer.mPlaneNormal) <= sMaxSlope); newPosition = tracer.mEndPos; - newPosition.z -= physicActor->getHalfExtents().z; + newPosition.z -= halfExtents.z; newPosition.z += 2.0f; return newPosition; From ebf9debb80b510113dcef60a4d5c3d6dd2febc15 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 19 Aug 2013 20:30:22 +0200 Subject: [PATCH 11/26] Enabled terrain self shadows, implemented getHeightAt, some optimizations --- apps/openmw/engine.cpp | 8 +- apps/openmw/mwbase/world.hpp | 2 +- apps/openmw/mwgui/settingswindow.cpp | 10 +- apps/openmw/mwgui/settingswindow.hpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 35 ++-- apps/openmw/mwrender/renderingmanager.hpp | 6 +- apps/openmw/mwrender/shadows.cpp | 3 +- apps/openmw/mwworld/scene.cpp | 6 +- apps/openmw/mwworld/worldimp.cpp | 26 ++- apps/openmw/mwworld/worldimp.hpp | 2 +- components/terrain/chunk.cpp | 8 +- components/terrain/material.cpp | 7 +- components/terrain/material.hpp | 1 + components/terrain/quadtreenode.cpp | 147 +++++++++++------ components/terrain/quadtreenode.hpp | 24 ++- components/terrain/storage.cpp | 190 +++++++++++++++++++--- components/terrain/storage.hpp | 6 + components/terrain/terrain.cpp | 65 +++++--- components/terrain/terrain.hpp | 22 ++- files/materials/terrain.shader | 1 + files/mygui/openmw_settings_window.layout | 8 +- files/settings-default.cfg | 6 +- 22 files changed, 421 insertions(+), 164 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 23e94cb51d..62a15fbf9e 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -69,8 +69,8 @@ void OMW::Engine::setAnimationVerbose(bool animverbose) bool OMW::Engine::frameStarted (const Ogre::FrameEvent& evt) { - if (!MWBase::Environment::get().getWindowManager()->isGuiMode()) - MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame); + bool paused = MWBase::Environment::get().getWindowManager()->isGuiMode(); + MWBase::Environment::get().getWorld()->frameStarted(evt.timeSinceLastFrame, paused); MWBase::Environment::get().getWindowManager ()->frameStarted(evt.timeSinceLastFrame); return true; } @@ -91,12 +91,12 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) MWBase::Environment::get().getSoundManager()->update(frametime); // global scripts - //MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); bool changed = MWBase::Environment::get().getWorld()->hasCellChanged(); // local scripts - //executeLocalScripts(); // This does not handle the case where a global script causes a cell + executeLocalScripts(); // This does not handle the case where a global script causes a cell // change, followed by a cell change in a local script during the same // frame. diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 4b4ffd0d3c..e2b8a236b5 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -371,7 +371,7 @@ namespace MWBase /// \todo this does not belong here virtual void playVideo(const std::string& name, bool allowSkipping) = 0; virtual void stopVideo() = 0; - virtual void frameStarted (float dt) = 0; + virtual void frameStarted (float dt, bool paused) = 0; /// Find default position inside exterior cell specified by name /// \return false if exterior with given name not exists, true otherwise diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 4ce6ac0bfe..3dfa17badc 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -125,7 +125,7 @@ namespace MWGui getWidget(mActorShadows, "ActorShadows"); getWidget(mStaticsShadows, "StaticsShadows"); getWidget(mMiscShadows, "MiscShadows"); - getWidget(mShadowsDebug, "ShadowsDebug"); + getWidget(mTerrainShadows, "TerrainShadows"); getWidget(mControlsBox, "ControlsBox"); getWidget(mResetControlsButton, "ResetControlsButton"); getWidget(mInvertYButton, "InvertYButton"); @@ -161,7 +161,7 @@ namespace MWGui mActorShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mStaticsShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mMiscShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); - mShadowsDebug->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); + mTerrainShadows->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onButtonToggled); mMasterVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); mVoiceVolumeSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &SettingsWindow::onSliderChangePosition); @@ -238,7 +238,7 @@ namespace MWGui mActorShadows->setCaptionWithReplacing(Settings::Manager::getBool("actor shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); mStaticsShadows->setCaptionWithReplacing(Settings::Manager::getBool("statics shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); mMiscShadows->setCaptionWithReplacing(Settings::Manager::getBool("misc shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); - mShadowsDebug->setCaptionWithReplacing(Settings::Manager::getBool("debug", "Shadows") ? "#{sOn}" : "#{sOff}"); + mTerrainShadows->setCaptionWithReplacing(Settings::Manager::getBool("terrain shadows", "Shadows") ? "#{sOn}" : "#{sOff}"); float cameraSens = (Settings::Manager::getFloat("camera sensitivity", "Input")-0.2)/(5.0-0.2); mCameraSensitivitySlider->setScrollPosition (cameraSens * (mCameraSensitivitySlider->getScrollRange()-1)); @@ -394,8 +394,8 @@ namespace MWGui Settings::Manager::setBool("statics shadows", "Shadows", newState); else if (_sender == mMiscShadows) Settings::Manager::setBool("misc shadows", "Shadows", newState); - else if (_sender == mShadowsDebug) - Settings::Manager::setBool("debug", "Shadows", newState); + else if (_sender == mTerrainShadows) + Settings::Manager::setBool("terrain shadows", "Shadows", newState); else if (_sender == mInvertYButton) Settings::Manager::setBool("invert y axis", "Input", newState); else if (_sender == mCrosshairButton) diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 42ed5bf6d9..a585bda7e1 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -59,7 +59,7 @@ namespace MWGui MyGUI::Button* mActorShadows; MyGUI::Button* mStaticsShadows; MyGUI::Button* mMiscShadows; - MyGUI::Button* mShadowsDebug; + MyGUI::Button* mTerrainShadows; // audio MyGUI::ScrollBar* mMasterVolumeSlider; diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 9f7fde10ad..63de044c8d 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -82,7 +82,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b Settings::Manager::setString("shader mode", "General", openGL ? (glES ? "glsles" : "glsl") : "hlsl"); } - mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 50); + mRendering.createScene("PlayerCam", Settings::Manager::getFloat("field of view", "General"), 5); mRendering.getWindow()->addListener(this); mRendering.setWindowListener(this); @@ -248,8 +248,10 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) { if (!mTerrain) { - mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain); - mTerrain->update(mRendering.getCamera()); + mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("distant land", "Terrain"), + Settings::Manager::getBool("shader", "Terrain")); + mTerrain->update(mRendering.getCamera()->getRealPosition()); } } waterAdded(store); @@ -554,13 +556,6 @@ void RenderingManager::setAmbientMode() } } -float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) -{ - assert(mTerrain); - return mTerrain->getHeightAt(worldPos); -} - - void RenderingManager::configureAmbient(MWWorld::Ptr::CellStore &mCell) { mAmbientColor.setAsABGR (mCell.mCell->mAmbi.mAmbient); @@ -658,9 +653,14 @@ void RenderingManager::requestMap(MWWorld::Ptr::CellStore* cell) { if (cell->mCell->isExterior()) { + assert(mTerrain); + Ogre::AxisAlignedBox dims = mObjects.getDimensions(cell); - Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, -cell->mCell->getGridY() + 1 - 0.5); + Ogre::Vector2 center(cell->mCell->getGridX() + 0.5, cell->mCell->getGridY() + 0.5); dims.merge(mTerrain->getWorldBoundingBox(center)); + + mTerrain->update(dims.getCenter()); + mLocalMap->requestMap(cell, dims.getMinimum().z, dims.getMaximum().z); } else @@ -992,12 +992,13 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con mWater->updateEmitterPtr(old, ptr); } -void RenderingManager::frameStarted(float dt) +void RenderingManager::frameStarted(float dt, bool paused) { if (mTerrain) - mTerrain->update(mRendering.getCamera()); + mTerrain->update(mRendering.getCamera()->getRealPosition()); - mWater->frameStarted(dt); + if (!paused) + mWater->frameStarted(dt); } void RenderingManager::resetCamera() @@ -1005,4 +1006,10 @@ void RenderingManager::resetCamera() mCamera->reset(); } +float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) +{ + assert(mTerrain); + return mTerrain->getHeightAt(worldPos); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index c4fb05804c..ea6e8d409f 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -157,6 +157,8 @@ public: bool occlusionQuerySupported() { return mOcclusionQuery->supported(); } OcclusionQuery* getOcclusionQuery() { return mOcclusionQuery; } + float getTerrainHeightAt (Ogre::Vector3 worldPos); + Shadows* getShadows(); void switchToInterior(); @@ -164,8 +166,6 @@ public: void getTriangleBatchCount(unsigned int &triangles, unsigned int &batches); - float getTerrainHeightAt (Ogre::Vector3 worldPos); - void setGlare(bool glare); void skyEnable (); void skyDisable (); @@ -209,7 +209,7 @@ public: void playVideo(const std::string& name, bool allowSkipping); void stopVideo(); - void frameStarted(float dt); + void frameStarted(float dt, bool paused); protected: virtual void windowResized(int x, int y); diff --git a/apps/openmw/mwrender/shadows.cpp b/apps/openmw/mwrender/shadows.cpp index c28c01dccb..21bbe51b63 100644 --- a/apps/openmw/mwrender/shadows.cpp +++ b/apps/openmw/mwrender/shadows.cpp @@ -107,7 +107,8 @@ void Shadows::recreate() // Set visibility mask for the shadow render textures int visibilityMask = RV_Actors * Settings::Manager::getBool("actor shadows", "Shadows") + (RV_Statics + RV_StaticsSmall) * Settings::Manager::getBool("statics shadows", "Shadows") - + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows"); + + RV_Misc * Settings::Manager::getBool("misc shadows", "Shadows") + + RV_Terrain * (Settings::Manager::getBool("terrain shadows", "Shadows")); for (int i = 0; i < (split ? 3 : 1); ++i) { TexturePtr shadowTexture = mSceneMgr->getShadowTexture(i); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 03ddf2aa9e..07155d3490 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -1,5 +1,7 @@ #include "scene.hpp" +#include + #include #include @@ -85,7 +87,6 @@ namespace MWWorld std::cout << "Unloading cell\n"; ListAndResetHandles functor; - /* (*iter)->forEach(functor); { // silence annoying g++ warning @@ -96,7 +97,6 @@ namespace MWWorld mPhysics->removeObject (node->getName()); } } - */ if ((*iter)->mCell->isExterior()) { @@ -150,7 +150,7 @@ namespace MWWorld // ... then references. This is important for adjustPosition to work correctly. /// \todo rescale depending on the state of a new GMST - //insertCell (*cell, true); + insertCell (*cell, true); mRendering.cellAdded (cell); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index ced4b5faa4..f0c55c43e7 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -835,7 +835,7 @@ namespace MWWorld bool isPlayer = ptr == mPlayer->getPlayer(); bool haveToMove = isPlayer || mWorldScene->isCellActive(*currCell); - if (false ) //*currCell != newCell) + if (*currCell != newCell) { removeContainerScripts(ptr); @@ -1026,10 +1026,13 @@ namespace MWWorld return; } - float terrainHeight = -std::numeric_limits().max();// mRendering->getTerrainHeightAt(pos); + if (ptr.getCell()->isExterior()) + { + float terrainHeight = mRendering->getTerrainHeightAt(pos); - if (pos.z < terrainHeight) - pos.z = terrainHeight; + if (pos.z < terrainHeight) + pos.z = terrainHeight; + } ptr.getRefData().getPosition().pos[2] = pos.z + 20; // place slightly above. will snap down to ground with code below @@ -1074,15 +1077,8 @@ namespace MWWorld { const int cellSize = 8192; - cellX = static_cast (x/cellSize); - - if (x<0) - --cellX; - - cellY = static_cast (y/cellSize); - - if (y<0) - --cellY; + cellX = std::floor(x/cellSize); + cellY = std::floor(y/cellSize); } void World::doPhysics(const PtrMovementList &actors, float duration) @@ -1682,9 +1678,9 @@ namespace MWWorld mRendering->stopVideo(); } - void World::frameStarted (float dt) + void World::frameStarted (float dt, bool paused) { - mRendering->frameStarted(dt); + mRendering->frameStarted(dt, paused); } void World::activateDoor(const MWWorld::Ptr& door) diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index b2f6418a3e..1e072e09a0 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -415,7 +415,7 @@ namespace MWWorld /// \todo this does not belong here virtual void playVideo(const std::string& name, bool allowSkipping); virtual void stopVideo(); - virtual void frameStarted (float dt); + virtual void frameStarted (float dt, bool paused); /// Find center of exterior cell above land surface /// \return false if exterior with given name not exists, true otherwise diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index c9a364dc44..7a81358d60 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -2,9 +2,6 @@ #include #include -#include -#include -#include #include "quadtreenode.hpp" #include "terrain.hpp" @@ -53,7 +50,6 @@ namespace Terrain mColourBuffer = mgr->createVertexBuffer(Ogre::VertexElement::getTypeSize(Ogre::VET_COLOUR), mVertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC); - mNode->getTerrain()->getStorage()->fillVertexBuffers(lodLevel, mNode->getSize(), mNode->getCenter(), mVertexBuffer, mNormalBuffer, mColourBuffer); @@ -66,6 +62,8 @@ namespace Terrain mIndexData->indexStart = 0; } + + void Chunk::updateIndexBuffer() { // Fetch a suitable index buffer (which may be shared) @@ -75,7 +73,7 @@ namespace Terrain for (int i=0; i<4; ++i) { - QuadTreeNode* neighbour = mNode->searchNeighbour((Direction)i); + QuadTreeNode* neighbour = mNode->getNeighbour((Direction)i); // If the neighbour isn't currently rendering itself, // go up until we find one. NOTE: We don't need to go down, diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index bd75015768..f85326492e 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -106,6 +106,8 @@ namespace Terrain Ogre::Pass* pass = technique->createPass(); pass->setLightingEnabled(false); pass->setVertexColourTracking(Ogre::TVC_NONE); + // TODO: How to handle fog? + pass->setFog(true, Ogre::FOG_NONE); bool first = (layer == mLayerList.begin()); @@ -127,9 +129,7 @@ namespace Terrain tus->setTextureAddressingMode(Ogre::TextureUnitState::TAM_CLAMP); float scale = (16/(16.f+1.f)); - float scroll = 1/16.f*0.5; - tus->setTextureScale(scale,scale); - tus->setTextureScroll(-scroll,-scroll); + tus->setTextureScale(1.f/scale,1.f/scale); } // Add the actual layer texture on top of the alpha map. @@ -150,6 +150,7 @@ namespace Terrain Ogre::Pass* lightingPass = technique->createPass(); lightingPass->setSceneBlending(Ogre::SBT_MODULATE); lightingPass->setVertexColourTracking(Ogre::TVC_AMBIENT|Ogre::TVC_DIFFUSE); + lightingPass->setFog(true, Ogre::FOG_NONE); } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 749e806ad7..068bb7a84c 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -18,6 +18,7 @@ namespace Terrain void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } + const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 8ebfafeb44..ce94d62a70 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -142,17 +142,21 @@ QuadTreeNode::QuadTreeNode(Terrain* terrain, ChildDirection dir, float size, con , mTerrain(terrain) , mChunk(NULL) , mMaterialGenerator(NULL) + , mBounds(Ogre::AxisAlignedBox::BOX_NULL) + , mWorldBounds(Ogre::AxisAlignedBox::BOX_NULL) { mBounds.setNull(); for (int i=0; i<4; ++i) mChildren[i] = NULL; + for (int i=0; i<4; ++i) + mNeighbours[i] = NULL; mSceneNode = mTerrain->getSceneManager()->getRootSceneNode()->createChildSceneNode( Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); mLodLevel = log2(mSize); - mMaterialGenerator = new MaterialGenerator(true); + mMaterialGenerator = new MaterialGenerator(mTerrain->getShadersEnabled()); } void QuadTreeNode::createChild(ChildDirection id, float size, const Ogre::Vector2 ¢er) @@ -168,38 +172,40 @@ QuadTreeNode::~QuadTreeNode() delete mMaterialGenerator; } -QuadTreeNode* QuadTreeNode::searchNeighbour(Direction dir) +QuadTreeNode* QuadTreeNode::getNeighbour(Direction dir) { - return searchNeighbourRecursive(this, dir); + return mNeighbours[static_cast(dir)]; +} + +void QuadTreeNode::initNeighbours() +{ + for (int i=0; i<4; ++i) + mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i); +} + +void QuadTreeNode::initAabb() +{ + if (hasChildren()) + { + for (int i=0; i<4; ++i) + { + mChildren[i]->initAabb(); + mBounds.merge(mChildren[i]->getBoundingBox()); + } + mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, mBounds.getMinimum().z), + Ogre::Vector3(mSize/2*8192, mSize/2*8192, mBounds.getMaximum().z)); + } + mWorldBounds = Ogre::AxisAlignedBox(mBounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0), + mBounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); +} + +void QuadTreeNode::setBoundingBox(const Ogre::AxisAlignedBox &box) +{ + mBounds = box; } const Ogre::AxisAlignedBox& QuadTreeNode::getBoundingBox() { - if (mIsDummy) - return Ogre::AxisAlignedBox::BOX_NULL; - if (mBounds.isNull()) - { - if (hasChildren()) - { - // X and Y are obvious, just need Z - float min = std::numeric_limits().max(); - float max = -std::numeric_limits().max(); - for (int i=0; i<4; ++i) - { - QuadTreeNode* child = getChild((ChildDirection)i); - float v = child->getBoundingBox().getMaximum().z; - if (v > max) - max = v; - v = child->getBoundingBox().getMinimum().z; - if (v < min) - min = v; - } - mBounds = Ogre::AxisAlignedBox (Ogre::Vector3(-mSize/2*8192, -mSize/2*8192, min), - Ogre::Vector3(mSize/2*8192, mSize/2*8192, max)); - } - else - throw std::runtime_error("Leaf node should have bounds set!"); - } return mBounds; } @@ -209,11 +215,19 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) if (bounds.isNull()) return; - Ogre::AxisAlignedBox worldBounds (bounds.getMinimum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0), - bounds.getMaximum() + Ogre::Vector3(mCenter.x*8192, mCenter.y*8192, 0)); + float dist = distance(mWorldBounds, cameraPos); + + if (!mTerrain->getDistantLandEnabled()) + { + if (dist > 8192*2) + { + destroyChunks(); + return; + } + } - float dist = distance(worldBounds, cameraPos); /// \todo implement error metrics or some other means of not using arbitrary values + /// (general quality needs to be user configurable as well) size_t wantedLod = 0; if (dist > 8192*1) wantedLod = 1; @@ -230,6 +244,7 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) if (mSize <= mTerrain->getMaxBatchSize() && mLodLevel <= wantedLod) { + bool hadChunk = hasChunk(); // Wanted LOD is small enough to render this node in one chunk if (!mChunk) { @@ -250,14 +265,32 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) } } + // Additional (index buffer) LOD is currently disabled. + // This is due to a problem with the LOD selection when a node splits. + // After splitting, the distance is measured from the children's bounding boxes, which are possibly + // further away than the original node's bounding box, possibly causing a child to switch to a *lower* LOD + // than the original node. + // In short, we'd sometimes get a switch to a lesser detail when actually moving closer. + // This wouldn't be so bad, but unfortunately it also breaks LOD edge connections if a neighbour + // node hasn't split yet, and has a higher LOD than our node's child: + // ----- ----- ------------ + // | LOD | LOD | | + // | 1 | 1 | | + // |-----|-----| LOD 0 | + // | LOD | LOD | | + // | 0 | 0 | | + // ----- ----- ------------ + // To prevent this, nodes of the same size need to always select the same LOD, which is basically what we're + // doing here. + // But this "solution" does increase triangle overhead, so eventually we need to find a more clever way. + //mChunk->setAdditionalLod(wantedLod - mLodLevel); - mChunk->setAdditionalLod(wantedLod - mLodLevel); mChunk->setVisible(true); - if (hasChildren()) + if (!hadChunk && hasChildren()) { for (int i=0; i<4; ++i) - mChildren[i]->removeChunks(); + mChildren[i]->hideChunks(); } } else @@ -272,15 +305,41 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) } } -void QuadTreeNode::removeChunks() +void QuadTreeNode::hideChunks() { if (mChunk) mChunk->setVisible(false); - if (hasChildren()) - { + else if (hasChildren()) for (int i=0; i<4; ++i) - mChildren[i]->removeChunks(); + mChildren[i]->hideChunks(); +} + +void QuadTreeNode::destroyChunks() +{ + if (mChunk) + { + Ogre::MaterialManager::getSingleton().remove(mChunk->getMaterial()->getName()); + mSceneNode->detachObject(mChunk); + + delete mChunk; + mChunk = NULL; + // destroy blendmaps + if (mMaterialGenerator) + { + const std::vector& list = mMaterialGenerator->getBlendmapList(); + for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) + Ogre::TextureManager::getSingleton().remove((*it)->getName()); + mMaterialGenerator->setBlendmapList(std::vector()); + mMaterialGenerator->setLayerList(std::vector()); + mMaterialGenerator->setCompositeMap(""); + } + + Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); + mCompositeMap.setNull(); } + else if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->destroyChunks(); } void QuadTreeNode::updateIndexBuffers() @@ -312,7 +371,7 @@ void QuadTreeNode::ensureLayerInfo() std::vector blendmaps; std::vector layerList; - mTerrain->getStorage()->getBlendmaps(mSize, mCenter, true, blendmaps, layerList); + mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); mMaterialGenerator->setLayerList(layerList); mMaterialGenerator->setBlendmapList(blendmaps); @@ -324,7 +383,9 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { - MaterialGenerator matGen(true); + // TODO - why is this completely black? + // TODO - store this default material somewhere instead of creating one for each empty cell + MaterialGenerator matGen(mTerrain->getShadersEnabled()); std::vector layer; layer.push_back("_land_default.dds"); matGen.setLayerList(layer); @@ -357,12 +418,6 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) } } - -bool QuadTreeNode::hasCompositeMap() -{ - return !mCompositeMap.isNull(); -} - void QuadTreeNode::ensureCompositeMap() { if (!mCompositeMap.isNull()) diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index b68d6ab827..1b7c71b5b4 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -49,6 +49,13 @@ namespace Terrain QuadTreeNode (Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); + /// Initialize neighbours - do this after the quadtree is built + void initNeighbours(); + /// Initialize bounding boxes of non-leafs by merging children bounding boxes. + /// Do this after the quadtree is built - note that leaf bounding boxes + /// need to be set first via setBoundingBox! + void initAabb(); + /// @note takes ownership of \a child void createChild (ChildDirection id, float size, const Ogre::Vector2& center); @@ -66,15 +73,15 @@ namespace Terrain bool hasChildren() { return mChildren[0] != 0; } QuadTreeNode* getChild(ChildDirection dir) { return mChildren[dir]; } - /// Search for a neighbour node in this direction - QuadTreeNode* searchNeighbour (Direction dir); + /// Get neighbour node in this direction + QuadTreeNode* getNeighbour (Direction dir); /// Returns our direction relative to the parent node, or Root if we are the root node. ChildDirection getDirection() { return mDirection; } /// Set bounding box in local coordinates. Should be done at load time for leaf nodes. /// Other nodes can merge AABB of child nodes. - void setBoundingBox (const Ogre::AxisAlignedBox& box) { mBounds = box; } + void setBoundingBox (const Ogre::AxisAlignedBox& box); /// Get bounding box in local coordinates const Ogre::AxisAlignedBox& getBoundingBox(); @@ -88,8 +95,11 @@ namespace Terrain /// Call after QuadTreeNode::update! void updateIndexBuffers(); - /// Remove chunks rendered by this node and all its children - void removeChunks(); + /// Hide chunks rendered by this node and all its children + void hideChunks(); + + /// Destroy chunks rendered by this node and all its children + void destroyChunks(); /// Get the effective LOD level if this node was rendered in one chunk /// with ESM::Land::LAND_SIZE^2 vertices @@ -101,8 +111,6 @@ namespace Terrain /// Is this node currently configured to render itself? bool hasChunk(); - bool hasCompositeMap(); - /// Add a textured quad to a specific 2d area in the composite map scenemanager. /// Only nodes with size <= 1 can be rendered with alpha blending, so larger nodes will simply /// call this method on their children. @@ -118,6 +126,7 @@ namespace Terrain float mSize; size_t mLodLevel; // LOD if we were to render this node in one chunk Ogre::AxisAlignedBox mBounds; + Ogre::AxisAlignedBox mWorldBounds; ChildDirection mDirection; Ogre::Vector2 mCenter; @@ -125,6 +134,7 @@ namespace Terrain QuadTreeNode* mParent; QuadTreeNode* mChildren[4]; + QuadTreeNode* mNeighbours[4]; Chunk* mChunk; diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index eb2763d948..800af32824 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -53,6 +53,51 @@ namespace Terrain } void Storage::fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + ESM::Land* land = getLand(cellX, cellY); + if (land && land->mHasData) + { + normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalise(); + } + else + normal = Ogre::Vector3(0,0,1); + } + + void Storage::averageNormal(Ogre::Vector3 &normal, int cellX, int cellY, int col, int row) + { + Ogre::Vector3 n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row); + fixNormal(n2, cellX, cellY, col-1, row); + fixNormal(n3, cellX, cellY, col, row+1); + fixNormal(n4, cellX, cellY, col, row-1); + normal = (n1+n2+n3+n4); + normal.normalise(); + } + + void Storage::fixColour (Ogre::ColourValue& color, int cellX, int cellY, int col, int row) { if (col == ESM::Land::LAND_SIZE-1) { @@ -65,12 +110,19 @@ namespace Terrain row = 0; } ESM::Land* land = getLand(cellX, cellY); - if (land && land->mHasData) + if (land && land->mLandData->mUsingColours) { - normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + color.r = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f; + color.g = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f; + color.b = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f; } + else + { + color.r = 1; + color.g = 1; + color.b = 1; + } + } void Storage::fillVertexBuffers (int lodLevel, float size, const Ogre::Vector2& center, @@ -141,17 +193,21 @@ namespace Terrain normal.x = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; normal.y = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; normal.z = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - // Normals don't connect seamlessly between cells - wtf? - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixNormal(normal, cellX, cellY, col, row); - // z < 0 should never happen, but it does - I hate this data set... - if (normal.z < 0) - normal *= -1; normal.normalise(); } else normal = Ogre::Vector3(0,0,1); + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row); + + assert(normal.z > 0); + normals[vertX*numVerts*3 + vertY*3] = normal.x; normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; @@ -168,6 +224,11 @@ namespace Terrain color.g = 1; color.b = 1; } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row); + color.a = 1; Ogre::uint32 rsColor; Ogre::Root::getSingleton().getRenderSystem()->convertColourValue(color, &rsColor); @@ -193,18 +254,20 @@ namespace Terrain Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, int x, int y) { - // If we're at the last row (or last column), we need to get the texture from the neighbour cell - // to get consistent blending at the border - if (x >= ESM::Land::LAND_TEXTURE_SIZE) + // For the first/last row/column, we need to get the texture from the neighbour cell + // to get consistent blending at the borders + --x; + if (x < 0) { - cellX++; - x -= ESM::Land::LAND_TEXTURE_SIZE; + --cellX; + x += ESM::Land::LAND_TEXTURE_SIZE; } - if (y >= ESM::Land::LAND_TEXTURE_SIZE) + if (y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? { - cellY++; + ++cellY; y -= ESM::Land::LAND_TEXTURE_SIZE; } + assert(x &blendmaps, std::vector &layerList) { + // TODO - blending isn't completely right yet; the blending radius appears to be + // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap + // and interpolate the rest of the cell by hand? :/ + Ogre::Vector2 origin = chunkCenter - Ogre::Vector2(chunkSize/2.f, chunkSize/2.f); int cellX = origin.x; int cellY = origin.y; @@ -253,7 +320,7 @@ namespace Terrain // To get a consistent look, we need to make sure to use the same base layer in all cells. // So we're always adding _land_default.dds as the base layer here, even if it's not referenced in this cell. textureIndices.insert(std::make_pair(0,0)); - // NB +1 to get the last index from neighbour cell (see getVtexIndexAt) + for (int y=0; y(nX * factor); + int startY = static_cast(nY * factor); + int endX = startX + 1; + int endY = startY + 1; + + assert(endX < ESM::Land::LAND_SIZE); + assert(endY < ESM::Land::LAND_SIZE); + + // now get points in terrain space (effectively rounding them to boundaries) + float startXTS = startX * invFactor; + float startYTS = startY * invFactor; + float endXTS = endX * invFactor; + float endYTS = endY * invFactor; + + // get parametric from start coord to next point + float xParam = (nX - startXTS) * factor; + float yParam = (nY - startYTS) * factor; + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + + // Build all 4 positions in terrain space, using point-sampled height + Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); + Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); + Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); + Ogre::Vector3 v3 (startXTS, endYTS, getVertexHeight(land, startX, endY) / 8192.f); + // define this plane in terrain space + Ogre::Plane plane; + // (At the moment, all rows have the same triangle alignment) + if (true) + { + // odd row + bool secondTri = ((1.0 - yParam) > xParam); + if (secondTri) + plane.redefine(v0, v1, v3); + else + plane.redefine(v1, v2, v3); + } + else + { + // even row + bool secondTri = (yParam > xParam); + if (secondTri) + plane.redefine(v0, v2, v3); + else + plane.redefine(v0, v1, v2); + } + + // Solve plane equation for z + return (-plane.normal.x * nX + -plane.normal.y * nY + - plane.d) / plane.normal.z * 8192; + + } + + float Storage::getVertexHeight(const ESM::Land *land, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + } diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index d55403d9c4..419439e19a 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -58,8 +58,14 @@ namespace Terrain std::vector& blendmaps, std::vector& layerList); + float getHeightAt (const Ogre::Vector3& worldPos); + private: void fixNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + void fixColour (Ogre::ColourValue& colour, int cellX, int cellY, int col, int row); + void averageNormal (Ogre::Vector3& normal, int cellX, int cellY, int col, int row); + + float getVertexHeight (const ESM::Land* land, int x, int y); // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. diff --git a/components/terrain/terrain.cpp b/components/terrain/terrain.cpp index c0f6cf2eb9..d06394acaa 100644 --- a/components/terrain/terrain.cpp +++ b/components/terrain/terrain.cpp @@ -51,12 +51,14 @@ namespace namespace Terrain { - Terrain::Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags) + Terrain::Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visibilityFlags, bool distantLand, bool shaders) : mStorage(storage) , mMinBatchSize(1) , mMaxBatchSize(64) , mSceneMgr(sceneMgr) , mVisibilityFlags(visibilityFlags) + , mDistantLand(distantLand) + , mShaders(shaders) { mCompositeMapSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); @@ -81,6 +83,8 @@ namespace Terrain mRootNode = new QuadTreeNode(this, Root, size, Ogre::Vector2(center.x, center.y), NULL); buildQuadTree(mRootNode); + mRootNode->initAabb(); + mRootNode->initNeighbours(); } Terrain::~Terrain() @@ -136,14 +140,19 @@ namespace Terrain node->markAsDummy(); } - void Terrain::update(Ogre::Camera *camera) + void Terrain::update(const Ogre::Vector3& cameraPos) { - mRootNode->update(camera->getRealPosition()); + mRootNode->update(cameraPos); mRootNode->updateIndexBuffers(); } Ogre::AxisAlignedBox Terrain::getWorldBoundingBox (const Ogre::Vector2& center) { + if (center.x > mBounds.getMaximum().x + || center.x < mBounds.getMinimum().x + || center.y > mBounds.getMaximum().y + || center.y < mBounds.getMinimum().y) + return Ogre::AxisAlignedBox::BOX_NULL; QuadTreeNode* node = findNode(center, mRootNode); Ogre::AxisAlignedBox box = node->getBoundingBox(); box.setExtents(box.getMinimum() + Ogre::Vector3(center.x, center.y, 0) * 8192, @@ -220,10 +229,10 @@ namespace Terrain for (size_t col = colStart; col < colEnd; col += increment) { indices.push_back(ESM::Land::LAND_SIZE*col+row); - indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); - indices.push_back(ESM::Land::LAND_SIZE*col+row+increment); + indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row); indices.push_back(ESM::Land::LAND_SIZE*(col+increment)+row+increment); } @@ -242,17 +251,18 @@ namespace Terrain { indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); - // Make sure not to touch the left edge - if (col == 0) - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+innerStep); + // Make sure not to touch the right edge + if (col+outerStep == ESM::Land::LAND_SIZE-1) + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row+innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*col+row+innerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row+innerStep); + for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the left or right edges if (col+i == 0 || col+i == ESM::Land::LAND_SIZE-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col)+row); indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row+innerStep); indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row+innerStep); } @@ -265,11 +275,11 @@ namespace Terrain { indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); indices.push_back(ESM::Land::LAND_SIZE*col+row); - // Make sure not to touch the right edge - if (col+outerStep == ESM::Land::LAND_SIZE-1) - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep-innerStep)+row-innerStep); + // Make sure not to touch the left edge + if (col == 0) + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row-innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row-innerStep); + indices.push_back(ESM::Land::LAND_SIZE*col+row-innerStep); for (size_t i = 0; i < outerStep; i += innerStep) { @@ -278,7 +288,7 @@ namespace Terrain continue; indices.push_back(ESM::Land::LAND_SIZE*(col+i)+row-innerStep); indices.push_back(ESM::Land::LAND_SIZE*(col+i+innerStep)+row-innerStep); - indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+outerStep)+row); } } @@ -289,18 +299,18 @@ namespace Terrain { indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); indices.push_back(ESM::Land::LAND_SIZE*col+row); - // Make sure not to touch the bottom edge - if (row == 0) - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+innerStep); + // Make sure not to touch the top edge + if (row+outerStep == ESM::Land::LAND_SIZE-1) + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep-innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row); + indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+outerStep); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); + indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i); indices.push_back(ESM::Land::LAND_SIZE*(col+innerStep)+row+i+innerStep); } @@ -313,18 +323,18 @@ namespace Terrain { indices.push_back(ESM::Land::LAND_SIZE*col+row); indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); - // Make sure not to touch the top edge - if (row+outerStep == ESM::Land::LAND_SIZE-1) - indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+outerStep-innerStep); + // Make sure not to touch the bottom edge + if (row == 0) + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+innerStep); else - indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+outerStep); + indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row); for (size_t i = 0; i < outerStep; i += innerStep) { // Make sure not to touch the top or bottom edges if (row+i == 0 || row+i == ESM::Land::LAND_SIZE-1-innerStep) continue; - indices.push_back(ESM::Land::LAND_SIZE*col+row); + indices.push_back(ESM::Land::LAND_SIZE*col+row+outerStep); indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i+innerStep); indices.push_back(ESM::Land::LAND_SIZE*(col-innerStep)+row+i); } @@ -355,5 +365,10 @@ namespace Terrain mCompositeMapSceneMgr->clearScene(); } + float Terrain::getHeightAt(const Ogre::Vector3 &worldPos) + { + return mStorage->getHeightAt(worldPos); + } + } diff --git a/components/terrain/terrain.hpp b/components/terrain/terrain.hpp index 79e35fd8e9..4844e0056b 100644 --- a/components/terrain/terrain.hpp +++ b/components/terrain/terrain.hpp @@ -28,16 +28,25 @@ namespace Terrain { public: /// @note takes ownership of \a storage - Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags); + /// @param sceneMgr scene manager to use + /// @param storage Storage instance to get terrain data from (heights, normals, colors, textures..) + /// @param visbilityFlags visibility flags for the created meshes + /// @param distantLand Whether to draw all of the terrain, or only a 3x3 grid around the camera. + /// This is a temporary option until it can be streamlined. + /// @param shaders Whether to use splatting shader, or multi-pass fixed function splatting. Shader is usually + /// faster so this is just here for compatibility. + Terrain(Ogre::SceneManager* sceneMgr, Storage* storage, int visiblityFlags, bool distantLand, bool shaders); ~Terrain(); + bool getDistantLandEnabled() { return mDistantLand; } + bool getShadersEnabled() { return mShaders; } + + float getHeightAt (const Ogre::Vector3& worldPos); + /// Update chunk LODs according to this camera position /// @note Calling this method might lead to composite textures being rendered, so it is best /// not to call it when render commands are still queued, since that would cause a flush. - void update (Ogre::Camera* camera); - - /// \todo - float getHeightAt (const Ogre::Vector3& worldPos) { return 0; } + void update (const Ogre::Vector3& cameraPos); /// Get the world bounding box of a chunk of terrain centered at \a center Ogre::AxisAlignedBox getWorldBoundingBox (const Ogre::Vector2& center); @@ -63,6 +72,9 @@ namespace Terrain void enableSplattingShader(bool enabled); private: + bool mDistantLand; + bool mShaders; + QuadTreeNode* mRootNode; Storage* mStorage; diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 1f11217d23..dd016555f8 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -160,6 +160,7 @@ @shEndForeach lightResult.xyz += lightAmbient.xyz; lightResult.xyz *= colour.xyz; + directionalResult.xyz *= colour.xyz; @shPassthroughAssign(lightResult, lightResult); @shPassthroughAssign(directionalResult, directionalResult); diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index e4fc9f724c..ebfaf678a7 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -335,12 +335,12 @@ - - + + - + - + diff --git a/files/settings-default.cfg b/files/settings-default.cfg index ac56604d11..f191430df1 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -80,6 +80,7 @@ texture size = 1024 actor shadows = true misc shadows = true statics shadows = true +terrain shadows = true # Fraction of the total shadow distance after which the shadow starts to fade out fade start = 0.8 @@ -125,8 +126,9 @@ fog start factor = 0.5 fog end factor = 1.0 [Terrain] -# Max. number of lights that affect the terrain. Setting to 1 will only reflect sunlight -num lights = 8 +distant land = false + +shader = true [Water] shader = true From b92da9ae93e877d54eb4db9f7f966520759ebad9 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 19 Aug 2013 21:08:44 +0200 Subject: [PATCH 12/26] Neighbour fix --- components/terrain/quadtreenode.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index ce94d62a70..007977b0cf 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -181,6 +181,10 @@ void QuadTreeNode::initNeighbours() { for (int i=0; i<4; ++i) mNeighbours[i] = searchNeighbourRecursive(this, (Direction)i); + + if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->initNeighbours(); } void QuadTreeNode::initAabb() From ceb3317807876e95180f1371257fe14ac2a7d54a Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sat, 17 Aug 2013 22:56:50 +0100 Subject: [PATCH 13/26] Integrate unshield with launcher --- CMakeLists.txt | 1 + apps/launcher/CMakeLists.txt | 14 +- apps/launcher/maindialog.cpp | 124 +++++++- apps/launcher/text_slot_msg_box.cpp | 6 + apps/launcher/text_slot_msg_box.hpp | 13 + apps/launcher/unshield_thread.cpp | 441 ++++++++++++++++++++++++++++ apps/launcher/unshield_thread.hpp | 55 ++++ 7 files changed, 648 insertions(+), 6 deletions(-) create mode 100644 apps/launcher/text_slot_msg_box.cpp create mode 100644 apps/launcher/text_slot_msg_box.hpp create mode 100644 apps/launcher/unshield_thread.cpp create mode 100644 apps/launcher/unshield_thread.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 575fecd0c5..99a7e85a46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -499,6 +499,7 @@ endif(WIN32) add_subdirectory (extern/shiny) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) +add_subdirectory (extern/unshield) # Components add_subdirectory (components) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index bff26b63c8..bfe9324485 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -4,6 +4,8 @@ set(LAUNCHER main.cpp maindialog.cpp playpage.cpp + unshield_thread.cpp + text_slot_msg_box.cpp settings/gamesettings.cpp settings/graphicssettings.cpp @@ -20,6 +22,8 @@ set(LAUNCHER_HEADER graphicspage.hpp maindialog.hpp playpage.hpp + unshield_thread.hpp + text_slot_msg_box.hpp settings/gamesettings.hpp settings/graphicssettings.hpp @@ -37,6 +41,8 @@ set(LAUNCHER_HEADER_MOC graphicspage.hpp maindialog.hpp playpage.hpp + unshield_thread.hpp + text_slot_msg_box.hpp utils/checkablemessagebox.hpp utils/textinputdialog.hpp @@ -64,8 +70,11 @@ QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) + +message(STATUS "hello ${LIBUNSHIELD_INCLUDE}") + include(${QT_USE_FILE}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBUNSHIELD_INCLUDE}) # Main executable IF(OGRE_STATIC) @@ -93,8 +102,11 @@ target_link_libraries(omwlauncher ${SDL2_LIBRARY} ${QT_LIBRARIES} components + libunshield ) + + if(DPKG_PROGRAM) INSTALL(TARGETS omwlauncher RUNTIME DESTINATION games COMPONENT omwlauncher) endif() diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index b75d09c51c..e274a28963 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -11,6 +11,9 @@ #include +#include "unshield_thread.hpp" +#include "text_slot_msg_box.hpp" + #include "utils/checkablemessagebox.hpp" #include "playpage.hpp" @@ -128,11 +131,16 @@ bool MainDialog::showFirstRunDialog() QDir dir(path); dir.setPath(dir.canonicalPath()); // Resolve symlinks - if (!dir.cdUp()) - continue; // Cannot move from Data Files - if (dir.exists(QString("Morrowind.ini"))) iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + else + { + if (!dir.cdUp()) + continue; // Cannot move from Data Files + + if (dir.exists(QString("Morrowind.ini"))) + iniPaths.append(dir.absoluteFilePath(QString("Morrowind.ini"))); + } } // Ask the user where the Morrowind.ini is @@ -344,6 +352,76 @@ bool MainDialog::setupLauncherSettings() return true; } +bool expansions(UnshieldThread& cd) +{ + if(cd.BloodmoonDone()) + { + cd.Done(); + return false; + } + + QMessageBox expansionsBox; + expansionsBox.setText(QObject::tr("
Would you like to install expansions now ? (make sure you have the disc)
\ + If you want to install both Bloodmoon and Tribunal, you have to install Tribunal first.
")); + + QAbstractButton* tribunalButton = NULL; + if(!cd.TribunalDone()) + tribunalButton = expansionsBox.addButton(QObject::tr("&Tribunal"), QMessageBox::ActionRole); + + QAbstractButton* bloodmoonButton = expansionsBox.addButton(QObject::tr("&Bloodmoon"), QMessageBox::ActionRole); + QAbstractButton* noneButton = expansionsBox.addButton(QObject::tr("&None"), QMessageBox::ActionRole); + + expansionsBox.exec(); + + if(expansionsBox.clickedButton() == noneButton) + { + cd.Done(); + return false; + } + else if(expansionsBox.clickedButton() == tribunalButton) + { + + TextSlotMsgBox cdbox; + cdbox.setStandardButtons(QMessageBox::Cancel); + + QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); + QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); + + cd.SetTribunalPath( + QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select data1.hdr from Tribunal Installation CD (Tribunal/data1.hdr on GOTY CDs)"), + QDir::currentPath(), + QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); + + cd.start(); + cdbox.exec(); + } + else if(expansionsBox.clickedButton() == bloodmoonButton) + { + + TextSlotMsgBox cdbox; + cdbox.setStandardButtons(QMessageBox::Cancel); + + QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); + QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); + + cd.SetBloodmoonPath( + QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select data1.hdr from Bloodmoon Installation CD (Bloodmoon/data1.hdr on GOTY CDs)"), + QDir::currentPath(), + QString(QObject::tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); + + cd.start(); + cdbox.exec(); + } + + + + return true; +} + bool MainDialog::setupGameSettings() { QString userPath = QString::fromStdString(mCfgMgr.getUserPath().string()); @@ -401,9 +479,13 @@ bool MainDialog::setupGameSettings() Press \"Browse...\" to specify the location manually.
")); QAbstractButton *dirSelectButton = - msgBox.addButton(QObject::tr("B&rowse..."), QMessageBox::ActionRole); + msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); - msgBox.exec(); + QAbstractButton *cdSelectButton = + msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); + + + msgBox.exec(); QString selectedFile; if (msgBox.clickedButton() == dirSelectButton) { @@ -413,6 +495,38 @@ bool MainDialog::setupGameSettings() QDir::currentPath(), QString(tr("Morrowind master file (*.esm)"))); } + else if(msgBox.clickedButton() == cdSelectButton) { + UnshieldThread cd; + + { + TextSlotMsgBox cdbox; + cdbox.setStandardButtons(QMessageBox::Cancel); + + QObject::connect(&cd,SIGNAL(signalGUI(const QString&)), &cdbox, SLOT(setTextSlot(const QString&))); + QObject::connect(&cd,SIGNAL(close()), &cdbox, SLOT(reject())); + + cd.SetMorrowindPath( + QFileDialog::getOpenFileName( + NULL, + QObject::tr("Select data1.hdr from Morrowind Installation CD"), + QDir::currentPath(), + QString(tr("Installshield hdr file (*.hdr)"))).toUtf8().constData()); + + cd.SetOutputPath( + QFileDialog::getExistingDirectory( + NULL, + QObject::tr("Select where to extract files to"), + QDir::currentPath(), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks).toUtf8().constData()); + + cd.start(); + cdbox.exec(); + } + + while(expansions(cd)); + + selectedFile = QString::fromStdString(cd.GetMWEsmPath()); + } if (selectedFile.isEmpty()) return false; // Cancel was clicked; diff --git a/apps/launcher/text_slot_msg_box.cpp b/apps/launcher/text_slot_msg_box.cpp new file mode 100644 index 0000000000..25bd5d970f --- /dev/null +++ b/apps/launcher/text_slot_msg_box.cpp @@ -0,0 +1,6 @@ +#include "text_slot_msg_box.hpp" + +void TextSlotMsgBox::setTextSlot(const QString& string) +{ + setText(string); +} diff --git a/apps/launcher/text_slot_msg_box.hpp b/apps/launcher/text_slot_msg_box.hpp new file mode 100644 index 0000000000..a29e2c3543 --- /dev/null +++ b/apps/launcher/text_slot_msg_box.hpp @@ -0,0 +1,13 @@ +#ifndef TEXT_SLOT_MSG_BOX +#define TEXT_SLOT_MSG_BOX + +#include + +class TextSlotMsgBox : public QMessageBox +{ +Q_OBJECT + public slots: + void setTextSlot(const QString& string); +}; + +#endif diff --git a/apps/launcher/unshield_thread.cpp b/apps/launcher/unshield_thread.cpp new file mode 100644 index 0000000000..f8166dba8b --- /dev/null +++ b/apps/launcher/unshield_thread.cpp @@ -0,0 +1,441 @@ +#include "unshield_thread.hpp" + +#include +#include + +namespace bfs = boost::filesystem; + + +typedef enum +{ + FORMAT_NEW, + FORMAT_OLD, + FORMAT_RAW +} FORMAT; + + +static bool make_sure_directory_exists(bfs::path directory) +{ + + if(!bfs::exists(directory)) + { + bfs::create_directories(directory); + } + + return bfs::exists(directory); +} + +void fill_path(bfs::path& path, const std::string& name) +{ + size_t start = 0; + + size_t i; + for(i = 0; i < name.length(); i++) + { + switch(name[i]) + { + case '\\': + path /= name.substr(start, i-start); + start = i+1; + break; + } + } + + path /= name.substr(start, i-start); +} + +bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) +{ + bool success; + bfs::path dirname; + bfs::path filename; + int directory = unshield_file_directory(unshield, index); + + dirname = output_dir; + + if (prefix && prefix[0]) + dirname /= prefix; + + if (directory >= 0) + { + const char* tmp = unshield_directory_name(unshield, directory); + if (tmp && tmp[0]) + fill_path(dirname, tmp); + } + + make_sure_directory_exists(dirname); + + filename = dirname; + filename /= unshield_file_name(unshield, index); + + emit signalGUI(QString("Extracting: ") + QString(filename.c_str())); + + FORMAT format = FORMAT_NEW; + switch (format) + { + case FORMAT_NEW: + success = unshield_file_save(unshield, index, filename.c_str()); + break; + case FORMAT_OLD: + success = unshield_file_save_old(unshield, index, filename.c_str()); + break; + case FORMAT_RAW: + success = unshield_file_save_raw(unshield, index, filename.c_str()); + break; + } + + if (!success) + bfs::remove(filename); + + return success; +} + +void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) +{ + Unshield * unshield; + unshield = unshield_open_force_version(cab.c_str(), -1); + + int i; + for (i = 0; i < unshield_file_group_count(unshield); i++) + { + UnshieldFileGroup* file_group = unshield_file_group_get(unshield, i); + + for (size_t j = file_group->first_file; j <= file_group->last_file; j++) + { + if (unshield_file_is_valid(unshield, j)) + extract_file(unshield, output_dir, file_group->name, j); + } + } + unshield_close(unshield); +} + +std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx) +{ + size_t start = inx.find(category); + start = inx.find(setting, start) + setting.length() + 3; + + size_t end = inx.find("!", start); + + return inx.substr(start, end-start); +} + +std::string read_to_string(const bfs::path& path) +{ + std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary); + std::string str; + + strstream.seekg(0, std::ios::end); + str.resize(strstream.tellg()); + strstream.seekg(0, std::ios::beg); + strstream.read(&str[0], str.size()); + strstream.close(); + + return str; +} + +void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini) +{ + size_t loc; + loc = ini.find("[" + category + "]"); + + // If category is not found, create it + if(loc == std::string::npos) + { + loc = ini.size() + 2; + ini += ("\r\n[" + category + "]\r\n"); + } + + loc += category.length() +2 +2; + ini.insert(loc, setting + "=" + val + "\r\n"); +} + +void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath) +{ + std::string inx = read_to_string(inxPath); + + // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones) + size_t start = ini.find("[Weather Blight]"); + start = ini.find("Ambient Loop Sound ID", start); + size_t end = ini.find("\r\n", start) +2; + ini.erase(start, end-start); + + std::string category; + std::string setting; + + category = "General"; + { + setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Moons"; + { + setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather"; + { + setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Blight"; + { + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Snow"; + { + setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Blizzard"; + { + setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } +} + + +void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon) +{ + bfs::path ini_path = output_dir; + ini_path /= "Morrowind.ini"; + + std::string ini = read_to_string(ini_path.string()); + + if(tribunal) + { + add_setting("Game Files", "GameFile1", "Tribunal.esm", ini); + add_setting("Archives", "Archive 0", "Tribunal.bsa", ini); + } + if(bloodmoon) + { + bloodmoon_fix_ini(ini, cdPath / "setup.inx"); + add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini); + add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); + } + + std::ofstream inistream(ini_path.c_str()); + inistream << ini; + inistream.close(); +} + +bool UnshieldThread::SetMorrowindPath(const std::string& path) +{ + mMorrowindPath = path; + return true; +} + +bool UnshieldThread::SetTribunalPath(const std::string& path) +{ + mTribunalPath = path; + return true; +} + +bool UnshieldThread::SetBloodmoonPath(const std::string& path) +{ + mBloodmoonPath = path; + return true; +} + +void UnshieldThread::SetOutputPath(const std::string& path) +{ + mOutputPath = path; +} + +void installToPath(const bfs::path& from, const bfs::path& to) +{ + make_sure_directory_exists(to); + + for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) + { + if(bfs::is_directory(dir->path())) + installToPath(dir->path(), to / dir->path().filename()); + else + bfs::rename(dir->path(), to / dir->path().filename()); + } +} + +bfs::path findFile(const bfs::path& in, std::string filename) +{ + for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } + + return ""; +} + +bool contains(const bfs::path& in, std::string filename) +{ + for(bfs::directory_iterator end, dir(in); dir != end; ++dir) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return true; + } + + return false; +} + +bool UnshieldThread::extract() +{ + bfs::path outputDataFilesDir = mOutputPath; + outputDataFilesDir /= "Data Files"; + bfs::path extractPath = mOutputPath; + extractPath /= "extract-temp"; + + if(!mMorrowindDone && mMorrowindPath.string().length() > 0) + { + mMorrowindDone = true; + + bfs::path mwExtractPath = extractPath / "morrowind"; + extract_cab(mMorrowindPath, mwExtractPath, true); + + bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); + + + installToPath(dFilesDir, outputDataFilesDir); + + bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); + + mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); + mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); + + } + + else if(!mTribunalDone && mTribunalPath.string().length() > 0) + { + mTribunalDone = true; + + bfs::path tbExtractPath = extractPath / "tribunal"; + extract_cab(mTribunalPath, tbExtractPath, true); + + bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path(); + + installToPath(dFilesDir, outputDataFilesDir); + + mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); + + fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone); + } + + else if(!mBloodmoonDone && mBloodmoonPath.string().length() > 0) + { + mBloodmoonDone = true; + + bfs::path bmExtractPath = extractPath / "bloodmoon"; + extract_cab(mBloodmoonPath, bmExtractPath, true); + + bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path(); + + installToPath(dFilesDir, outputDataFilesDir); + + fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone); + } + + + return true; +} + +time_t getTime(const char* time) +{ + struct tm tms; + memset(&tms, 0, sizeof(struct tm)); + strptime(time, "%d %B %Y", &tms); + return mktime(&tms); +} + +void UnshieldThread::Done() +{ + // Get rid of unnecessary files + bfs::remove_all(mOutputPath / "extract-temp"); + + // Set modified time to release dates, to preserve load order + if(mMorrowindDone) + bfs::last_write_time(findFile(mOutputPath, "morrowind.esm"), getTime("1 May 2002")); + + if(mTribunalDone) + bfs::last_write_time(findFile(mOutputPath, "tribunal.esm"), getTime("6 November 2002")); + + if(mBloodmoonDone) + bfs::last_write_time(findFile(mOutputPath, "bloodmoon.esm"), getTime("3 June 2003")); +} + +std::string UnshieldThread::GetMWEsmPath() +{ + return findFile(mOutputPath / "Data Files", "morrowind.esm").string(); +} + +bool UnshieldThread::TribunalDone() +{ + return mTribunalDone; +} + +bool UnshieldThread::BloodmoonDone() +{ + return mBloodmoonDone; +} + +void UnshieldThread::run() +{ + extract(); + emit close(); +} diff --git a/apps/launcher/unshield_thread.hpp b/apps/launcher/unshield_thread.hpp new file mode 100644 index 0000000000..0def3ab1db --- /dev/null +++ b/apps/launcher/unshield_thread.hpp @@ -0,0 +1,55 @@ +#ifndef UNSHIELD_THREAD_H +#define UNSHIELD_THREAD_H + +#include + +#include + +#include + + +class UnshieldThread : public QThread +{ + Q_OBJECT + + public: + bool SetMorrowindPath(const std::string& path); + bool SetTribunalPath(const std::string& path); + bool SetBloodmoonPath(const std::string& path); + + void SetOutputPath(const std::string& path); + + bool extract(); + + bool TribunalDone(); + bool BloodmoonDone(); + + void Done(); + + std::string GetMWEsmPath(); + + private: + + void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); + bool extract_file(Unshield* unshield, boost::filesystem::path output_dir, const char* prefix, int index); + + boost::filesystem::path mMorrowindPath; + boost::filesystem::path mTribunalPath; + boost::filesystem::path mBloodmoonPath; + + bool mMorrowindDone = false; + bool mTribunalDone = false; + bool mBloodmoonDone = false; + + boost::filesystem::path mOutputPath; + + + protected: + virtual void run(); + + signals: + void signalGUI(QString); + void close(); +}; + +#endif From 90a892d3041eb0727794395776101ceaff42a0e5 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sun, 18 Aug 2013 00:16:20 +0100 Subject: [PATCH 14/26] unshield fixes --- apps/launcher/unshield_thread.cpp | 48 ++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/apps/launcher/unshield_thread.cpp b/apps/launcher/unshield_thread.cpp index f8166dba8b..57ba849dce 100644 --- a/apps/launcher/unshield_thread.cpp +++ b/apps/launcher/unshield_thread.cpp @@ -300,7 +300,7 @@ void UnshieldThread::SetOutputPath(const std::string& path) mOutputPath = path; } -void installToPath(const bfs::path& from, const bfs::path& to) +void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false) { make_sure_directory_exists(to); @@ -309,16 +309,32 @@ void installToPath(const bfs::path& from, const bfs::path& to) if(bfs::is_directory(dir->path())) installToPath(dir->path(), to / dir->path().filename()); else - bfs::rename(dir->path(), to / dir->path().filename()); + { + if(!copy) + bfs::rename(dir->path(), to / dir->path().filename()); + else + bfs::copy_file(dir->path(), to / dir->path().filename()); + } } } -bfs::path findFile(const bfs::path& in, std::string filename) +bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true) { - for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + if(recursive) { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) - return dir->path(); + for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } + } + else + { + for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } } return ""; @@ -350,10 +366,17 @@ bool UnshieldThread::extract() extract_cab(mMorrowindPath, mwExtractPath, true); bfs::path dFilesDir = findFile(mwExtractPath, "morrowind.esm").parent_path(); - installToPath(dFilesDir, outputDataFilesDir); + // Videos are often kept uncompressed on the cd + bfs::path videosPath = findFile(mMorrowindPath.parent_path(), "video", false); + if(videosPath.string() != "") + { + emit signalGUI(QString("Installing Videos...")); + installToPath(videosPath, outputDataFilesDir / "Video", true); + } + bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); @@ -371,6 +394,11 @@ bool UnshieldThread::extract() bfs::path dFilesDir = findFile(tbExtractPath, "tribunal.esm").parent_path(); installToPath(dFilesDir, outputDataFilesDir); + + // Mt GOTY CD has Sounds in a seperate folder from the rest of the data files + bfs::path soundsPath = findFile(tbExtractPath, "sounds", false); + if(soundsPath.string() != "") + installToPath(soundsPath, outputDataFilesDir / "Sounds"); mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); @@ -387,6 +415,12 @@ bool UnshieldThread::extract() bfs::path dFilesDir = findFile(bmExtractPath, "bloodmoon.esm").parent_path(); installToPath(dFilesDir, outputDataFilesDir); + + // My GOTY CD contains a folder within cab files called Tribunal patch, + // which contains Tribunal.esm + bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm"); + if(tbPatchPath.string() != "") + bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm"); fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone); } From 3264b5974e6a6060fb95b67aac00984ca879bc21 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sun, 18 Aug 2013 00:29:46 +0100 Subject: [PATCH 15/26] fix invalid syntax --- apps/launcher/unshield_thread.cpp | 7 +++++++ apps/launcher/unshield_thread.hpp | 8 +++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/launcher/unshield_thread.cpp b/apps/launcher/unshield_thread.cpp index 57ba849dce..34b6a6d461 100644 --- a/apps/launcher/unshield_thread.cpp +++ b/apps/launcher/unshield_thread.cpp @@ -473,3 +473,10 @@ void UnshieldThread::run() extract(); emit close(); } + +UnshieldThread::UnshieldThread() +{ + mMorrowindDone = false; + mTribunalDone = false; + mBloodmoonDone = false; +} diff --git a/apps/launcher/unshield_thread.hpp b/apps/launcher/unshield_thread.hpp index 0def3ab1db..b48d3d9878 100644 --- a/apps/launcher/unshield_thread.hpp +++ b/apps/launcher/unshield_thread.hpp @@ -28,6 +28,8 @@ class UnshieldThread : public QThread std::string GetMWEsmPath(); + UnshieldThread(); + private: void extract_cab(const boost::filesystem::path& cab, const boost::filesystem::path& output_dir, bool extract_ini = false); @@ -37,9 +39,9 @@ class UnshieldThread : public QThread boost::filesystem::path mTribunalPath; boost::filesystem::path mBloodmoonPath; - bool mMorrowindDone = false; - bool mTribunalDone = false; - bool mBloodmoonDone = false; + bool mMorrowindDone; + bool mTribunalDone; + bool mBloodmoonDone; boost::filesystem::path mOutputPath; From 454b64974d56cb5aaa7b70cbc73de00219521287 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sun, 18 Aug 2013 12:11:39 +0100 Subject: [PATCH 16/26] filenames --- apps/launcher/CMakeLists.txt | 12 ++++++------ apps/launcher/maindialog.cpp | 4 ++-- .../{text_slot_msg_box.cpp => textslotmsgbox.cpp} | 2 +- .../{text_slot_msg_box.hpp => textslotmsgbox.hpp} | 0 .../{unshield_thread.cpp => unshieldthread.cpp} | 2 +- .../{unshield_thread.hpp => unshieldthread.hpp} | 0 6 files changed, 10 insertions(+), 10 deletions(-) rename apps/launcher/{text_slot_msg_box.cpp => textslotmsgbox.cpp} (71%) rename apps/launcher/{text_slot_msg_box.hpp => textslotmsgbox.hpp} (100%) rename apps/launcher/{unshield_thread.cpp => unshieldthread.cpp} (99%) rename apps/launcher/{unshield_thread.hpp => unshieldthread.hpp} (100%) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index bfe9324485..e11b713bc4 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -4,8 +4,8 @@ set(LAUNCHER main.cpp maindialog.cpp playpage.cpp - unshield_thread.cpp - text_slot_msg_box.cpp + unshieldthread.cpp + textslotmsgbox.cpp settings/gamesettings.cpp settings/graphicssettings.cpp @@ -22,8 +22,8 @@ set(LAUNCHER_HEADER graphicspage.hpp maindialog.hpp playpage.hpp - unshield_thread.hpp - text_slot_msg_box.hpp + unshieldthread.hpp + textslotmsgbox.hpp settings/gamesettings.hpp settings/graphicssettings.hpp @@ -41,8 +41,8 @@ set(LAUNCHER_HEADER_MOC graphicspage.hpp maindialog.hpp playpage.hpp - unshield_thread.hpp - text_slot_msg_box.hpp + unshieldthread.hpp + textslotmsgbox.hpp utils/checkablemessagebox.hpp utils/textinputdialog.hpp diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index e274a28963..857a15196f 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -11,8 +11,8 @@ #include -#include "unshield_thread.hpp" -#include "text_slot_msg_box.hpp" +#include "unshieldthread.hpp" +#include "textslotmsgbox.hpp" #include "utils/checkablemessagebox.hpp" diff --git a/apps/launcher/text_slot_msg_box.cpp b/apps/launcher/textslotmsgbox.cpp similarity index 71% rename from apps/launcher/text_slot_msg_box.cpp rename to apps/launcher/textslotmsgbox.cpp index 25bd5d970f..0607d1cc6e 100644 --- a/apps/launcher/text_slot_msg_box.cpp +++ b/apps/launcher/textslotmsgbox.cpp @@ -1,4 +1,4 @@ -#include "text_slot_msg_box.hpp" +#include "textslotmsgbox.hpp" void TextSlotMsgBox::setTextSlot(const QString& string) { diff --git a/apps/launcher/text_slot_msg_box.hpp b/apps/launcher/textslotmsgbox.hpp similarity index 100% rename from apps/launcher/text_slot_msg_box.hpp rename to apps/launcher/textslotmsgbox.hpp diff --git a/apps/launcher/unshield_thread.cpp b/apps/launcher/unshieldthread.cpp similarity index 99% rename from apps/launcher/unshield_thread.cpp rename to apps/launcher/unshieldthread.cpp index 34b6a6d461..2033023883 100644 --- a/apps/launcher/unshield_thread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -1,4 +1,4 @@ -#include "unshield_thread.hpp" +#include "unshieldthread.hpp" #include #include diff --git a/apps/launcher/unshield_thread.hpp b/apps/launcher/unshieldthread.hpp similarity index 100% rename from apps/launcher/unshield_thread.hpp rename to apps/launcher/unshieldthread.hpp From 641b7b0336ff95f56493883d424f132bbdb56072 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sun, 18 Aug 2013 12:23:09 +0100 Subject: [PATCH 17/26] anonymous namespace --- apps/launcher/unshieldthread.cpp | 559 ++++++++++++++++--------------- 1 file changed, 281 insertions(+), 278 deletions(-) diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 2033023883..10470dab90 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -5,43 +5,295 @@ namespace bfs = boost::filesystem; - -typedef enum -{ - FORMAT_NEW, - FORMAT_OLD, - FORMAT_RAW -} FORMAT; - - -static bool make_sure_directory_exists(bfs::path directory) -{ - - if(!bfs::exists(directory)) +namespace +{ + typedef enum { - bfs::create_directories(directory); + FORMAT_NEW, + FORMAT_OLD, + FORMAT_RAW + } FORMAT; + + + static bool make_sure_directory_exists(bfs::path directory) + { + + if(!bfs::exists(directory)) + { + bfs::create_directories(directory); + } + + return bfs::exists(directory); } - return bfs::exists(directory); -} - -void fill_path(bfs::path& path, const std::string& name) -{ - size_t start = 0; - - size_t i; - for(i = 0; i < name.length(); i++) + void fill_path(bfs::path& path, const std::string& name) { - switch(name[i]) + size_t start = 0; + + size_t i; + for(i = 0; i < name.length(); i++) { - case '\\': - path /= name.substr(start, i-start); - start = i+1; - break; + switch(name[i]) + { + case '\\': + path /= name.substr(start, i-start); + start = i+1; + break; + } + } + + path /= name.substr(start, i-start); + } + + std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx) + { + size_t start = inx.find(category); + start = inx.find(setting, start) + setting.length() + 3; + + size_t end = inx.find("!", start); + + return inx.substr(start, end-start); + } + + std::string read_to_string(const bfs::path& path) + { + std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary); + std::string str; + + strstream.seekg(0, std::ios::end); + str.resize(strstream.tellg()); + strstream.seekg(0, std::ios::beg); + strstream.read(&str[0], str.size()); + strstream.close(); + + return str; + } + + void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini) + { + size_t loc; + loc = ini.find("[" + category + "]"); + + // If category is not found, create it + if(loc == std::string::npos) + { + loc = ini.size() + 2; + ini += ("\r\n[" + category + "]\r\n"); + } + + loc += category.length() +2 +2; + ini.insert(loc, setting + "=" + val + "\r\n"); + } + + void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath) + { + std::string inx = read_to_string(inxPath); + + // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones) + size_t start = ini.find("[Weather Blight]"); + start = ini.find("Ambient Loop Sound ID", start); + size_t end = ini.find("\r\n", start) +2; + ini.erase(start, end-start); + + std::string category; + std::string setting; + + category = "General"; + { + setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Moons"; + { + setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather"; + { + setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Blight"; + { + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Snow"; + { + setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini); + } + category = "Weather Blizzard"; + { + setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); + setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); } } - path /= name.substr(start, i-start); + + void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon) + { + bfs::path ini_path = output_dir; + ini_path /= "Morrowind.ini"; + + std::string ini = read_to_string(ini_path.string()); + + if(tribunal) + { + add_setting("Game Files", "GameFile1", "Tribunal.esm", ini); + add_setting("Archives", "Archive 0", "Tribunal.bsa", ini); + } + if(bloodmoon) + { + bloodmoon_fix_ini(ini, cdPath / "setup.inx"); + add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini); + add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); + } + + std::ofstream inistream(ini_path.c_str()); + inistream << ini; + inistream.close(); + } + + void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false) + { + make_sure_directory_exists(to); + + for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) + { + if(bfs::is_directory(dir->path())) + installToPath(dir->path(), to / dir->path().filename()); + else + { + if(!copy) + bfs::rename(dir->path(), to / dir->path().filename()); + else + bfs::copy_file(dir->path(), to / dir->path().filename()); + } + } + } + + bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true) + { + if(recursive) + { + for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } + } + else + { + for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return dir->path(); + } + } + + return ""; + } + + bool contains(const bfs::path& in, std::string filename) + { + for(bfs::directory_iterator end, dir(in); dir != end; ++dir) + { + if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) + return true; + } + + return false; + } + + time_t getTime(const char* time) + { + struct tm tms; + memset(&tms, 0, sizeof(struct tm)); + strptime(time, "%d %B %Y", &tms); + return mktime(&tms); + } +} + +bool UnshieldThread::SetMorrowindPath(const std::string& path) +{ + mMorrowindPath = path; + return true; +} + +bool UnshieldThread::SetTribunalPath(const std::string& path) +{ + mTribunalPath = path; + return true; +} + +bool UnshieldThread::SetBloodmoonPath(const std::string& path) +{ + mBloodmoonPath = path; + return true; +} + +void UnshieldThread::SetOutputPath(const std::string& path) +{ + mOutputPath = path; } bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, const char* prefix, int index) @@ -109,247 +361,6 @@ void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_d unshield_close(unshield); } -std::string get_setting(const std::string& category, const std::string& setting, const std::string& inx) -{ - size_t start = inx.find(category); - start = inx.find(setting, start) + setting.length() + 3; - - size_t end = inx.find("!", start); - - return inx.substr(start, end-start); -} - -std::string read_to_string(const bfs::path& path) -{ - std::ifstream strstream(path.c_str(), std::ios::in | std::ios::binary); - std::string str; - - strstream.seekg(0, std::ios::end); - str.resize(strstream.tellg()); - strstream.seekg(0, std::ios::beg); - strstream.read(&str[0], str.size()); - strstream.close(); - - return str; -} - -void add_setting(const std::string& category, const std::string& setting, const std::string& val, std::string& ini) -{ - size_t loc; - loc = ini.find("[" + category + "]"); - - // If category is not found, create it - if(loc == std::string::npos) - { - loc = ini.size() + 2; - ini += ("\r\n[" + category + "]\r\n"); - } - - loc += category.length() +2 +2; - ini.insert(loc, setting + "=" + val + "\r\n"); -} - -void bloodmoon_fix_ini(std::string& ini, const bfs::path inxPath) -{ - std::string inx = read_to_string(inxPath); - - // Remove this one setting (the only one actually changed by bloodmoon, as opposed to just adding new ones) - size_t start = ini.find("[Weather Blight]"); - start = ini.find("Ambient Loop Sound ID", start); - size_t end = ini.find("\r\n", start) +2; - ini.erase(start, end-start); - - std::string category; - std::string setting; - - category = "General"; - { - setting = "Werewolf FOV"; add_setting(category, setting, get_setting(category, setting, inx), ini); - } - category = "Moons"; - { - setting = "Script Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - } - category = "Weather"; - { - setting = "Snow Ripples"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Ripple Radius"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Ripples Per Flake"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Ripple Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Ripple Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Gravity Scale"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow High Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Low Kill"; add_setting(category, setting, get_setting(category, setting, inx), ini); - } - category = "Weather Blight"; - { - setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); - } - category = "Weather Snow"; - { - setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Diameter"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Height Min"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Height Max"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Snow Entrance Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Max Snowflakes"; add_setting(category, setting, get_setting(category, setting, inx), ini); - } - category = "Weather Blizzard"; - { - setting = "Sky Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sky Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sky Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sky Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Fog Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Sunrise Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Day Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Night Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Sun Disc Sunset Color"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Transition Delta"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Land Fog Day Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Land Fog Night Depth"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Clouds Maximum Percent"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Wind Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Cloud Speed"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Glare View"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Cloud Texture"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Ambient Loop Sound ID"; add_setting(category, setting, get_setting(category, setting, inx), ini); - setting = "Storm Threshold"; add_setting(category, setting, get_setting(category, setting, inx), ini); - } -} - - -void fix_ini(const bfs::path& output_dir, bfs::path cdPath, bool tribunal, bool bloodmoon) -{ - bfs::path ini_path = output_dir; - ini_path /= "Morrowind.ini"; - - std::string ini = read_to_string(ini_path.string()); - - if(tribunal) - { - add_setting("Game Files", "GameFile1", "Tribunal.esm", ini); - add_setting("Archives", "Archive 0", "Tribunal.bsa", ini); - } - if(bloodmoon) - { - bloodmoon_fix_ini(ini, cdPath / "setup.inx"); - add_setting("Game Files", "GameFile2", "Bloodmoon.esm", ini); - add_setting("Archives", "Archive 1", "Bloodmoon.bsa", ini); - } - - std::ofstream inistream(ini_path.c_str()); - inistream << ini; - inistream.close(); -} - -bool UnshieldThread::SetMorrowindPath(const std::string& path) -{ - mMorrowindPath = path; - return true; -} - -bool UnshieldThread::SetTribunalPath(const std::string& path) -{ - mTribunalPath = path; - return true; -} - -bool UnshieldThread::SetBloodmoonPath(const std::string& path) -{ - mBloodmoonPath = path; - return true; -} - -void UnshieldThread::SetOutputPath(const std::string& path) -{ - mOutputPath = path; -} - -void installToPath(const bfs::path& from, const bfs::path& to, bool copy = false) -{ - make_sure_directory_exists(to); - - for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) - { - if(bfs::is_directory(dir->path())) - installToPath(dir->path(), to / dir->path().filename()); - else - { - if(!copy) - bfs::rename(dir->path(), to / dir->path().filename()); - else - bfs::copy_file(dir->path(), to / dir->path().filename()); - } - } -} - -bfs::path findFile(const bfs::path& in, std::string filename, bool recursive = true) -{ - if(recursive) - { - for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) - { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) - return dir->path(); - } - } - else - { - for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) - { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) - return dir->path(); - } - } - - return ""; -} - -bool contains(const bfs::path& in, std::string filename) -{ - for(bfs::directory_iterator end, dir(in); dir != end; ++dir) - { - if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) - return true; - } - - return false; -} bool UnshieldThread::extract() { @@ -429,14 +440,6 @@ bool UnshieldThread::extract() return true; } -time_t getTime(const char* time) -{ - struct tm tms; - memset(&tms, 0, sizeof(struct tm)); - strptime(time, "%d %B %Y", &tms); - return mktime(&tms); -} - void UnshieldThread::Done() { // Get rid of unnecessary files From 9d1daf7dc2e9be1d6375abf931aafe63d01de9f2 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sun, 18 Aug 2013 12:29:12 +0100 Subject: [PATCH 18/26] enum was unnecessary --- apps/launcher/unshieldthread.cpp | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 10470dab90..4ccb6b04ef 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -7,14 +7,6 @@ namespace bfs = boost::filesystem; namespace { - typedef enum - { - FORMAT_NEW, - FORMAT_OLD, - FORMAT_RAW - } FORMAT; - - static bool make_sure_directory_exists(bfs::path directory) { @@ -322,19 +314,7 @@ bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, cons emit signalGUI(QString("Extracting: ") + QString(filename.c_str())); - FORMAT format = FORMAT_NEW; - switch (format) - { - case FORMAT_NEW: - success = unshield_file_save(unshield, index, filename.c_str()); - break; - case FORMAT_OLD: - success = unshield_file_save_old(unshield, index, filename.c_str()); - break; - case FORMAT_RAW: - success = unshield_file_save_raw(unshield, index, filename.c_str()); - break; - } + success = unshield_file_save(unshield, index, filename.c_str()); if (!success) bfs::remove(filename); From d3748cd5bb8b8c7be40ca2d843346fff5ee587c1 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Sun, 18 Aug 2013 12:46:10 +0100 Subject: [PATCH 19/26] Install uncompressed data files from cd --- apps/launcher/unshieldthread.cpp | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 4ccb6b04ef..5a9475d96f 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -212,13 +212,13 @@ namespace for ( bfs::directory_iterator end, dir(from); dir != end; ++dir ) { if(bfs::is_directory(dir->path())) - installToPath(dir->path(), to / dir->path().filename()); + installToPath(dir->path(), to / dir->path().filename(), copy); else { - if(!copy) - bfs::rename(dir->path(), to / dir->path().filename()); - else + if(copy) bfs::copy_file(dir->path(), to / dir->path().filename()); + else + bfs::rename(dir->path(), to / dir->path().filename()); } } } @@ -235,7 +235,7 @@ namespace } else { - for ( bfs::recursive_directory_iterator end, dir(in); dir != end; ++dir ) + for ( bfs::directory_iterator end, dir(in); dir != end; ++dir ) { if(Misc::StringUtils::lowerCase(dir->path().filename().string()) == filename) return dir->path(); @@ -368,6 +368,14 @@ bool UnshieldThread::extract() installToPath(videosPath, outputDataFilesDir / "Video", true); } + bfs::path cdDFiles = findFile(mMorrowindPath.parent_path(), "data files", false); + if(cdDFiles.string() != "") + { + emit signalGUI(QString("Installing Uncompressed Data files from CD...")); + installToPath(cdDFiles, outputDataFilesDir, true); + } + + bfs::rename(findFile(mwExtractPath, "morrowind.ini"), outputDataFilesDir / "Morrowind.ini"); mTribunalDone = contains(outputDataFilesDir, "tribunal.esm"); @@ -391,6 +399,13 @@ bool UnshieldThread::extract() if(soundsPath.string() != "") installToPath(soundsPath, outputDataFilesDir / "Sounds"); + bfs::path cdDFiles = findFile(mTribunalPath.parent_path(), "data files", false); + if(cdDFiles.string() != "") + { + emit signalGUI(QString("Installing Uncompressed Data files from CD...")); + installToPath(cdDFiles, outputDataFilesDir, true); + } + mBloodmoonDone = contains(outputDataFilesDir, "bloodmoon.esm"); fix_ini(outputDataFilesDir, bfs::path(mTribunalPath).parent_path(), mTribunalDone, mBloodmoonDone); @@ -412,6 +427,13 @@ bool UnshieldThread::extract() bfs::path tbPatchPath = findFile(bmExtractPath, "tribunal.esm"); if(tbPatchPath.string() != "") bfs::rename(tbPatchPath, outputDataFilesDir / "Tribunal.esm"); + + bfs::path cdDFiles = findFile(mBloodmoonPath.parent_path(), "data files", false); + if(cdDFiles.string() != "") + { + emit signalGUI(QString("Installing Uncompressed Data files from CD...")); + installToPath(cdDFiles, outputDataFilesDir, true); + } fix_ini(outputDataFilesDir, bfs::path(mBloodmoonPath).parent_path(), false, mBloodmoonDone); } From 865a7c63df278214f2585804374089ad126da271 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Mon, 19 Aug 2013 19:59:58 +0100 Subject: [PATCH 20/26] cmake for system libunshield --- CMakeLists.txt | 8 +++++- apps/launcher/CMakeLists.txt | 4 +-- cmake/FindLibUnshield.cmake | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 cmake/FindLibUnshield.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 99a7e85a46..148a56d3cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -499,7 +499,6 @@ endif(WIN32) add_subdirectory (extern/shiny) add_subdirectory (extern/oics) add_subdirectory (extern/sdl4ogre) -add_subdirectory (extern/unshield) # Components add_subdirectory (components) @@ -516,6 +515,13 @@ if (BUILD_ESMTOOL) endif() if (BUILD_LAUNCHER) + if(NOT WIN32) + find_package(LIBUNSHIELD REQUIRED) + if(NOT LIBUNSHIELD_FOUND) + message(SEND_ERROR "Failed to find libunshield") + endif(NOT LIBUNSHIELD_FOUND) + endif(NOT WIN32) + add_subdirectory( apps/launcher ) endif() diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index e11b713bc4..9b812e4bab 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -71,8 +71,6 @@ QT4_WRAP_CPP(MOC_SRCS ${LAUNCHER_HEADER_MOC}) QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) -message(STATUS "hello ${LIBUNSHIELD_INCLUDE}") - include(${QT_USE_FILE}) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBUNSHIELD_INCLUDE}) @@ -102,7 +100,7 @@ target_link_libraries(omwlauncher ${SDL2_LIBRARY} ${QT_LIBRARIES} components - libunshield + ${LIBUNSHIELD_LIBRARY} ) diff --git a/cmake/FindLibUnshield.cmake b/cmake/FindLibUnshield.cmake new file mode 100644 index 0000000000..4f4e98a1cd --- /dev/null +++ b/cmake/FindLibUnshield.cmake @@ -0,0 +1,48 @@ +# Locate LIBUNSHIELD +# This module defines +# LIBUNSHIELD_LIBRARY +# LIBUNSHIELD_FOUND, if false, do not try to link to LibUnshield +# LIBUNSHIELD_INCLUDE_DIR, where to find the headers +# +# Created by Tom Mason (wheybags) for OpenMW (http://openmw.com), based on FindMPG123.cmake +# +# Ripped off from other sources. In fact, this file is so generic (I +# just did a search and replace on another file) that I wonder why the +# CMake guys haven't wrapped this entire thing in a single +# function. Do we really need to repeat this stuff for every single +# library when they all work the same? + +FIND_PATH(LIBUNSHIELD_INCLUDE_DIR libunshield.h + HINTS + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw # Fink + /opt/local # DarwinPorts + /opt/csw # Blastwave + /opt + /usr/include +) + +FIND_LIBRARY(LIBUNSHIELD_LIBRARY + unshield + HINTS +# PATH_SUFFIXES lib64 lib libs64 libs libs/Win32 libs/Win64 + PATHS + ~/Library/Frameworks + /Library/Frameworks + /usr/local + /usr + /sw + /opt/local + /opt/csw + /opt + /usr/lib +) + +IF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR) + SET(LIBUNSHIELD_FOUND "YES") +ENDIF(LIBUNSHIELD_LIBRARY AND LIBUNSHIELD_INCLUDE_DIR) + From 06ff40eda731a657658c5ba930fd514f36aaf94f Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Mon, 19 Aug 2013 20:11:52 +0100 Subject: [PATCH 21/26] only use unshield on not windows --- apps/launcher/CMakeLists.txt | 24 +++++++++++++++++++++--- apps/launcher/maindialog.cpp | 17 +++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index 9b812e4bab..5908deb907 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -4,7 +4,6 @@ set(LAUNCHER main.cpp maindialog.cpp playpage.cpp - unshieldthread.cpp textslotmsgbox.cpp settings/gamesettings.cpp @@ -16,6 +15,9 @@ set(LAUNCHER ${CMAKE_SOURCE_DIR}/files/launcher/launcher.rc ) +if(NOT WIN32) + LIST(APPEND LAUNCHER unshieldthread.cpp) +endif(NOT WIN32) set(LAUNCHER_HEADER datafilespage.hpp @@ -34,6 +36,10 @@ set(LAUNCHER_HEADER utils/textinputdialog.hpp ) +if(NOT WIN32) + LIST(APPEND LAUNCHER_HEADER unshieldthread.hpp) +endif(NOT WIN32) + # Headers that must be pre-processed set(LAUNCHER_HEADER_MOC @@ -48,6 +54,11 @@ set(LAUNCHER_HEADER_MOC utils/textinputdialog.hpp ) +if(NOT WIN32) + LIST(APPEND LAUNCHER_HEADER_MOC unshieldthread.hpp) +endif(NOT WIN32) + + set(LAUNCHER_UI ${CMAKE_SOURCE_DIR}/files/ui/datafilespage.ui ${CMAKE_SOURCE_DIR}/files/ui/graphicspage.ui @@ -72,7 +83,10 @@ QT4_WRAP_UI(UI_HDRS ${LAUNCHER_UI}) include(${QT_USE_FILE}) -include_directories(${CMAKE_CURRENT_BINARY_DIR} ${LIBUNSHIELD_INCLUDE}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(NOT WIN32) + include_directories(${LIBUNSHIELD_INCLUDE}) +endif(NOT WIN32) # Main executable IF(OGRE_STATIC) @@ -100,8 +114,12 @@ target_link_libraries(omwlauncher ${SDL2_LIBRARY} ${QT_LIBRARIES} components - ${LIBUNSHIELD_LIBRARY} ) +if(NOT WIN32) + target_link_libraries(omwlauncher + ${LIBUNSHIELD_LIBRARY} + ) +endif(NOT WIN32) diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 857a15196f..032f70916f 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -11,7 +11,10 @@ #include -#include "unshieldthread.hpp" +#ifndef WIN32 + #include "unshieldthread.hpp" +#endif + #include "textslotmsgbox.hpp" #include "utils/checkablemessagebox.hpp" @@ -352,6 +355,7 @@ bool MainDialog::setupLauncherSettings() return true; } +#ifndef WIN32 bool expansions(UnshieldThread& cd) { if(cd.BloodmoonDone()) @@ -421,6 +425,7 @@ bool expansions(UnshieldThread& cd) return true; } +#endif // WIN32 bool MainDialog::setupGameSettings() { @@ -480,9 +485,11 @@ bool MainDialog::setupGameSettings() QAbstractButton *dirSelectButton = msgBox.addButton(QObject::tr("Browse to &Install..."), QMessageBox::ActionRole); - - QAbstractButton *cdSelectButton = - msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); + + #ifndef WIN32 + QAbstractButton *cdSelectButton = + msgBox.addButton(QObject::tr("Browse to &CD..."), QMessageBox::ActionRole); + #endif msgBox.exec(); @@ -495,6 +502,7 @@ bool MainDialog::setupGameSettings() QDir::currentPath(), QString(tr("Morrowind master file (*.esm)"))); } + #ifndef WIN32 else if(msgBox.clickedButton() == cdSelectButton) { UnshieldThread cd; @@ -527,6 +535,7 @@ bool MainDialog::setupGameSettings() selectedFile = QString::fromStdString(cd.GetMWEsmPath()); } + #endif // WIN32 if (selectedFile.isEmpty()) return false; // Cancel was clicked; From 603ce4105474f0ae4864bed8d6e25538d53a67a9 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Mon, 19 Aug 2013 20:32:19 +0100 Subject: [PATCH 22/26] added libunshield to travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5c69f49f04..caf2e33899 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_install: - sudo apt-get install -qq libqt4-dev libxaw7-dev libxrandr-dev libfreeimage-dev libpng-dev - sudo apt-get install -qq libopenal-dev libmpg123-dev libsndfile1-dev - sudo apt-get install -qq libavcodec-dev libavformat-dev libavdevice-dev libavutil-dev libswscale-dev libpostproc-dev - - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev + - sudo apt-get install -qq libbullet-dev libogre-static-dev libmygui-static-dev libsdl2-static-dev libunshield-dev - sudo mkdir /usr/src/gtest/build - cd /usr/src/gtest/build - sudo cmake .. -DBUILD_SHARED_LIBS=1 From 8d232aca35c679ae48b4402b77462aed51a131c7 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Mon, 19 Aug 2013 20:48:20 +0100 Subject: [PATCH 23/26] changed libunshield filename to satisfy travis --- cmake/{FindLibUnshield.cmake => FindLIBUNSHIELD.cmake} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cmake/{FindLibUnshield.cmake => FindLIBUNSHIELD.cmake} (100%) diff --git a/cmake/FindLibUnshield.cmake b/cmake/FindLIBUNSHIELD.cmake similarity index 100% rename from cmake/FindLibUnshield.cmake rename to cmake/FindLIBUNSHIELD.cmake From 13afcc932473d745d77eb95f07792e19a8769996 Mon Sep 17 00:00:00 2001 From: scrawl Date: Mon, 19 Aug 2013 22:22:14 +0200 Subject: [PATCH 24/26] Don't link to OgreTerrain --- CMakeLists.txt | 1 - apps/openmw/CMakeLists.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dd58ed4168..9e5e5fcc12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,7 +216,6 @@ 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} ${SDL2_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${PLATFORM_INCLUDE_DIR} diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 8259ac8cfe..a44fd4b343 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -105,7 +105,6 @@ add_definitions(${SOUND_DEFINE}) target_link_libraries(openmw ${OGRE_LIBRARIES} - ${OGRE_Terrain_LIBRARY} ${OGRE_STATIC_PLUGINS} ${Boost_LIBRARIES} ${OPENAL_LIBRARY} From 7f0f9037be06db17016813dc4ff52962d73937c4 Mon Sep 17 00:00:00 2001 From: Tom Mason Date: Mon, 19 Aug 2013 22:57:21 +0100 Subject: [PATCH 25/26] fix for older versions of unshield --- apps/launcher/unshieldthread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/launcher/unshieldthread.cpp b/apps/launcher/unshieldthread.cpp index 5a9475d96f..ab9d984e1a 100644 --- a/apps/launcher/unshieldthread.cpp +++ b/apps/launcher/unshieldthread.cpp @@ -325,7 +325,7 @@ bool UnshieldThread::extract_file(Unshield* unshield, bfs::path output_dir, cons void UnshieldThread::extract_cab(const bfs::path& cab, const bfs::path& output_dir, bool extract_ini) { Unshield * unshield; - unshield = unshield_open_force_version(cab.c_str(), -1); + unshield = unshield_open(cab.c_str()); int i; for (i = 0; i < unshield_file_group_count(unshield); i++) From 8c8653160d90cb5b37cb164861f3f2ae4f26ba2f Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 20 Aug 2013 09:52:27 +0200 Subject: [PATCH 26/26] Crash fix, material fix --- apps/openmw/mwrender/renderingmanager.cpp | 37 ++++++++++++++++------- apps/openmw/mwrender/renderingmanager.hpp | 2 ++ apps/openmw/mwworld/scene.cpp | 4 +++ components/terrain/material.cpp | 28 +++++++++++------ components/terrain/material.hpp | 5 +++ components/terrain/quadtreenode.cpp | 37 +++++++++++++++++++++-- components/terrain/quadtreenode.hpp | 5 +++ components/terrain/storage.cpp | 2 +- components/terrain/terrain.cpp | 18 +++++++++++ components/terrain/terrain.hpp | 9 +++++- files/materials/terrain.shader | 1 - 11 files changed, 123 insertions(+), 25 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 63de044c8d..2d129a3fbc 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -244,16 +244,6 @@ void RenderingManager::cellAdded (MWWorld::Ptr::CellStore *store) mObjects.buildStaticGeometry (*store); sh::Factory::getInstance().unloadUnreferencedMaterials(); mDebugging->cellAdded(store); - if (store->isExterior()) - { - if (!mTerrain) - { - mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, - Settings::Manager::getBool("distant land", "Terrain"), - Settings::Manager::getBool("shader", "Terrain")); - mTerrain->update(mRendering.getCamera()->getRealPosition()); - } - } waterAdded(store); } @@ -848,7 +838,12 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec mWater->processChangedSettings(settings); if (rebuild) + { mObjects.rebuildStaticGeometry(); + if (mTerrain) + mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), + Settings::Manager::getBool("split", "Shadows")); + } } void RenderingManager::setMenuTransparency(float val) @@ -994,7 +989,7 @@ void RenderingManager::updateWaterRippleEmitterPtr (const MWWorld::Ptr& old, con void RenderingManager::frameStarted(float dt, bool paused) { - if (mTerrain) + if (mTerrain && mTerrain->getVisible()) mTerrain->update(mRendering.getCamera()->getRealPosition()); if (!paused) @@ -1012,4 +1007,24 @@ float RenderingManager::getTerrainHeightAt(Ogre::Vector3 worldPos) return mTerrain->getHeightAt(worldPos); } +void RenderingManager::enableTerrain(bool enable) +{ + if (enable) + { + if (!mTerrain) + { + mTerrain = new Terrain::Terrain(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + Settings::Manager::getBool("distant land", "Terrain"), + Settings::Manager::getBool("shader", "Terrain")); + mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), + Settings::Manager::getBool("split", "Shadows")); + mTerrain->update(mRendering.getCamera()->getRealPosition()); + } + mTerrain->setVisible(true); + } + else + if (mTerrain) + mTerrain->setVisible(false); +} + } // namespace diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index ea6e8d409f..45929f0640 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -110,6 +110,8 @@ public: void cellAdded (MWWorld::CellStore *store); void waterAdded(MWWorld::CellStore *store); + void enableTerrain(bool enable); + void removeWater(); void preCellChange (MWWorld::CellStore* store); diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 07155d3490..9e96eebf17 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -362,6 +362,8 @@ namespace MWWorld const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + mRendering.enableTerrain(false); + std::string loadingInteriorText; loadingInteriorText = gmst.find ("sLoadingMessage2")->getString(); @@ -440,6 +442,8 @@ namespace MWWorld MWBase::Environment::get().getWorld()->positionToIndex (position.pos[0], position.pos[1], x, y); + mRendering.enableTerrain(true); + changeCell (x, y, position, true); } diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index f85326492e..4a91ad99a6 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -34,6 +34,8 @@ namespace Terrain MaterialGenerator::MaterialGenerator(bool shaders) : mShaders(shaders) + , mShadows(false) + , mSplitShadows(false) { } @@ -46,6 +48,11 @@ namespace Terrain // first layer doesn't need blendmap --freeTextureUnits; + if (mSplitShadows) + freeTextureUnits -= 3; + else if (mShadows) + --freeTextureUnits; + // each layer needs 1.25 units (1xdiffusespec, 0.25xblend) return static_cast(freeTextureUnits / (1.25f)) + 1; } @@ -158,7 +165,6 @@ namespace Terrain } else { - sh::MaterialInstance* material = sh::Factory::getInstance().createMaterialInstance (name.str()); material->setProperty ("allow_fixed_function", sh::makeProperty(new sh::BooleanValue(false))); @@ -179,12 +185,14 @@ namespace Terrain tex->setProperty ("tex_address_mode", sh::makeProperty (new sh::StringValue("clamp"))); // shadow. TODO: repeated, put in function - for (Ogre::uint i = 0; i < 3; ++i) + if (mShadows) { - sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); - shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } } - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( Ogre::StringConverter::toString(1)))); @@ -272,12 +280,14 @@ namespace Terrain } // shadow - for (Ogre::uint i = 0; i < 3; ++i) + if (mShadows) { - sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); - shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + for (Ogre::uint i = 0; i < (mSplitShadows ? 3 : 1); ++i) + { + sh::MaterialInstanceTextureUnit* shadowTex = p->createTextureUnit ("shadowMap" + Ogre::StringConverter::toString(i)); + shadowTex->setProperty ("content_type", sh::makeProperty (new sh::StringValue("shadow"))); + } } - p->mShaderProperties.setProperty ("shadowtexture_offset", sh::makeProperty (new sh::StringValue( Ogre::StringConverter::toString(numBlendTextures + numLayersInThisPass)))); diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 068bb7a84c..2788b79c49 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -21,6 +21,9 @@ namespace Terrain const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } + void enableShadows(bool shadows) { mShadows = shadows; } + void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } + /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. /// @param mat Material that will be replaced by the generated material. May be empty as well, in which case /// a new material is created. @@ -48,6 +51,8 @@ namespace Terrain std::vector mBlendmapList; std::string mCompositeMap; bool mShaders; + bool mShadows; + bool mSplitShadows; }; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 007977b0cf..ec6a670dca 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -256,6 +256,10 @@ void QuadTreeNode::update(const Ogre::Vector3 &cameraPos) mChunk->setVisibilityFlags(mTerrain->getVisiblityFlags()); mChunk->setCastShadows(true); mSceneNode->attachObject(mChunk); + + mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); + mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + if (mSize == 1) { ensureLayerInfo(); @@ -338,8 +342,11 @@ void QuadTreeNode::destroyChunks() mMaterialGenerator->setCompositeMap(""); } - Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); - mCompositeMap.setNull(); + if (!mCompositeMap.isNull()) + { + Ogre::TextureManager::getSingleton().remove(mCompositeMap->getName()); + mCompositeMap.setNull(); + } } else if (hasChildren()) for (int i=0; i<4; ++i) @@ -444,3 +451,29 @@ void QuadTreeNode::ensureCompositeMap() mTerrain->clearCompositeMapSceneManager(); } + +void QuadTreeNode::applyMaterials() +{ + if (mChunk) + { + mMaterialGenerator->enableShadows(mTerrain->getShadowsEnabled()); + mMaterialGenerator->enableSplitShadows(mTerrain->getSplitShadowsEnabled()); + if (mSize <= 1) + mChunk->setMaterial(mMaterialGenerator->generate(Ogre::MaterialPtr())); + else + mChunk->setMaterial(mMaterialGenerator->generateForCompositeMap(Ogre::MaterialPtr())); + } + if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->applyMaterials(); +} + +void QuadTreeNode::setVisible(bool visible) +{ + if (!visible && mChunk) + mChunk->setVisible(false); + + if (hasChildren()) + for (int i=0; i<4; ++i) + mChildren[i]->setVisible(visible); +} diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 1b7c71b5b4..bccd26642b 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -49,6 +49,11 @@ namespace Terrain QuadTreeNode (Terrain* terrain, ChildDirection dir, float size, const Ogre::Vector2& center, QuadTreeNode* parent); ~QuadTreeNode(); + void setVisible(bool visible); + + /// Rebuild all materials + void applyMaterials(); + /// Initialize neighbours - do this after the quadtree is built void initNeighbours(); /// Initialize bounding boxes of non-leafs by merging children bounding boxes. diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index 800af32824..900e536ec6 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -425,7 +425,7 @@ namespace Terrain 0---1 0---1 */ - // Build all 4 positions in terrain space, using point-sampled height + // Build all 4 positions in normalized cell space, using point-sampled height Ogre::Vector3 v0 (startXTS, startYTS, getVertexHeight(land, startX, startY) / 8192.f); Ogre::Vector3 v1 (endXTS, startYTS, getVertexHeight(land, endX, startY) / 8192.f); Ogre::Vector3 v2 (endXTS, endYTS, getVertexHeight(land, endX, endY) / 8192.f); diff --git a/components/terrain/terrain.cpp b/components/terrain/terrain.cpp index d06394acaa..3d65fbfc12 100644 --- a/components/terrain/terrain.cpp +++ b/components/terrain/terrain.cpp @@ -370,5 +370,23 @@ namespace Terrain return mStorage->getHeightAt(worldPos); } + void Terrain::applyMaterials(bool shadows, bool splitShadows) + { + mShadows = shadows; + mSplitShadows = splitShadows; + mRootNode->applyMaterials(); + } + + void Terrain::setVisible(bool visible) + { + mVisible = visible; + mRootNode->setVisible(visible); + } + + bool Terrain::getVisible() + { + return mVisible; + } + } diff --git a/components/terrain/terrain.hpp b/components/terrain/terrain.hpp index 4844e0056b..37ea2742a5 100644 --- a/components/terrain/terrain.hpp +++ b/components/terrain/terrain.hpp @@ -40,6 +40,8 @@ namespace Terrain bool getDistantLandEnabled() { return mDistantLand; } bool getShadersEnabled() { return mShaders; } + bool getShadowsEnabled() { return mShadows; } + bool getSplitShadowsEnabled() { return mSplitShadows; } float getHeightAt (const Ogre::Vector3& worldPos); @@ -56,14 +58,16 @@ namespace Terrain Storage* getStorage() { return mStorage; } /// Show or hide the whole terrain + /// @note this setting will be invalidated once you call Terrain::update, so do not call it while the terrain should be hidden void setVisible(bool visible); + bool getVisible(); /// Recreate materials used by terrain chunks. This should be called whenever settings of /// the material factory are changed. (Relying on the factory to update those materials is not /// enough, since turning a feature on/off can change the number of texture units available for layer/blend /// textures, and to properly respond to this we may need to change the structure of the material, such as /// adding or removing passes. This can only be achieved by a full rebuild.) - void applyMaterials(); + void applyMaterials(bool shadows, bool splitShadows); int getVisiblityFlags() { return mVisibilityFlags; } @@ -74,6 +78,9 @@ namespace Terrain private: bool mDistantLand; bool mShaders; + bool mShadows; + bool mSplitShadows; + bool mVisible; QuadTreeNode* mRootNode; Storage* mStorage; diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index dd016555f8..80837a2cb0 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -348,7 +348,6 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; #else shOutputColour(0).a = 1.f-previousAlpha; #endif - } #endif