diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 23e94cb51..62a15fbf9 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 4b4ffd0d3..e2b8a236b 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 4ce6ac0bf..3dfa17bad 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 42ed5bf6d..a585bda7e 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 9f7fde10a..63de044c8 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 c4fb05804..ea6e8d409 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 c28c01dcc..21bbe51b6 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 03ddf2aa9..07155d349 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 ced4b5faa..f0c55c43e 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 b2f6418a3..1e072e09a 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 c9a364dc4..7a81358d6 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 bd7501576..f85326492 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 749e806ad..068bb7a84 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 8ebfafeb4..ce94d62a7 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 b68d6ab82..1b7c71b5b 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 eb2763d94..800af3282 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 d55403d9c..419439e19 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 c0f6cf2eb..d06394aca 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 79e35fd8e..4844e0056 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 1f11217d2..dd016555f 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 e4fc9f724..ebfaf678a 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 ac56604d1..f191430df 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