From b8192138afbbad58f01288a7e7a30c75ea7441e2 Mon Sep 17 00:00:00 2001 From: "florent.teppe" <teppe.florent@hotmail.fr> Date: Mon, 12 Aug 2024 20:32:27 +0200 Subject: [PATCH 1/6] initial commit applying changes from cc9cii --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/groundcover.hpp | 2 +- apps/openmw/mwrender/landmanager.cpp | 7 + apps/openmw/mwrender/landmanager.hpp | 8 + apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwrender/objectpaging.hpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 21 + apps/openmw/mwrender/terrainstorage.cpp | 28 ++ apps/openmw/mwrender/terrainstorage.hpp | 16 + apps/openmw/mwworld/esmstore.hpp | 3 +- apps/openmw/mwworld/scene.cpp | 4 + apps/openmw/mwworld/store.cpp | 3 +- components/esm/records.hpp | 1 + components/esm4/loadland.cpp | 117 ++--- components/esmterrain/storage.cpp | 493 ++++++++++++++++++- components/esmterrain/storage.hpp | 19 + components/terrain/chunkmanager.cpp | 80 ++- components/terrain/chunkmanager.hpp | 9 +- components/terrain/defs.hpp | 1 + components/terrain/material.cpp | 20 +- components/terrain/material.hpp | 2 +- components/terrain/quadtreeworld.cpp | 39 +- components/terrain/quadtreeworld.hpp | 2 +- components/terrain/terraingrid.cpp | 39 +- components/terrain/terraingrid.hpp | 3 +- files/shaders/compatibility/esm4terrain.frag | 110 +++++ files/shaders/compatibility/esm4terrain.vert | 73 +++ 27 files changed, 1011 insertions(+), 95 deletions(-) create mode 100644 files/shaders/compatibility/esm4terrain.frag create mode 100644 files/shaders/compatibility/esm4terrain.vert diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 7a29f1bb07..97d2025a31 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -322,7 +322,7 @@ namespace MWRender } osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) { if (lod > getMaxLodLevel()) return nullptr; diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index df40d9d529..51c2cb4628 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -27,7 +27,7 @@ namespace MWRender ~Groundcover(); osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, - bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) override; unsigned int getNodeMask() override; diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index d17933b2b7..ed7d0aa71e 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -49,6 +49,13 @@ namespace MWRender return landObj; } + const ESM4::Land *LandManager::getLandRecord(ESM::ExteriorCellLocation cellIndex) const + { + const MWBase::World& world = *MWBase::Environment::get().getWorld(); + const ESM4::Land* land = world.getStore().get<ESM4::Land>().search(cellIndex); + return land; + } + void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const { Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats); diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index 1b82f32ce9..21bb80cf48 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -12,6 +12,11 @@ namespace ESM struct Land; } +namespace ESM4 +{ + struct Land; +} + namespace MWRender { @@ -23,6 +28,9 @@ namespace MWRender /// @note Will return nullptr if not found. osg::ref_ptr<ESMTerrain::LandObject> getLand(ESM::ExteriorCellLocation cellIndex); + // FIXME: returning a pointer is probably not compatible with the rest of the codebase + const ESM4::Land *getLandRecord(ESM::ExteriorCellLocation cellIndex) const; + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index 6799b2358b..ff7436e862 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -78,7 +78,7 @@ namespace MWRender } osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char /*lod*/, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) { if (activeGrid && !mActiveGrid) return nullptr; diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index 11be6009ca..34214bc476 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -24,7 +24,7 @@ namespace MWRender ~ObjectPaging() = default; osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, - bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) override; osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, unsigned char lod); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 469d71fc4f..0516bb9700 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -49,6 +49,7 @@ #include <components/terrain/quadtreeworld.hpp> #include <components/terrain/terraingrid.hpp> +#include <components/esm/util.hpp> #include <components/esm3/loadcell.hpp> #include <components/esm4/loadcell.hpp> @@ -490,6 +491,8 @@ namespace MWRender const bool useTerrainNormalMaps = Settings::shaders().mAutoUseTerrainNormalMaps; const bool useTerrainSpecularMaps = Settings::shaders().mAutoUseTerrainSpecularMaps; + // NOTE: Maybe we need to swap out this with a different storage type during + // enableTerrain() if we are in a foreign (i.e. non-Morrowind) worldspace. mTerrainStorage = std::make_unique<TerrainStorage>(mResourceSystem, normalMapPattern, heightMapPattern, useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); @@ -798,12 +801,20 @@ namespace MWRender mWater->removeCell(store); } + // NOTE: For cc9cii's fork, this is where the terrain storage type is decided when we know + // the worldspace. But in this implementation the decision is made in the + // constructor. void RenderingManager::enableTerrain(bool enable, ESM::RefId worldspace) { if (!enable) mWater->setCullCallback(nullptr); else { + // need to set our ESM4 state in mTerrainStorage here to change some behaviours in + // ESMTerrain::Storage + if (ESM::isEsm4Ext(worldspace)) + mTerrainStorage->setIsEsm4Ext(true); + WorldspaceChunkMgr& newChunks = getWorldspaceChunkMgr(worldspace); if (newChunks.mTerrain.get() != mTerrain) { @@ -1445,6 +1456,16 @@ namespace MWRender mStateUpdater->setFogColor(color); } + // NOTE: mTerrainStorage is a unique_ptr to MWRender::TerrainStorage which is a child + // class of ESMTerrain::Storage. This makes switching out ESMTerrain::Storage with + // another class impossible unless we decide to accept a man-in-the-middle type mess. + // + // mTerrainStorage is initialised in the constructor. It's probably not a good idea + // to chnage that when we switch to another worldspace so we'll need a different + // solution. + // + // So maybe we have to accept the "least bad option" and have the complications + // within MWRender::TerrainStorage e.g. set a state variable (see enableTerrain()) RenderingManager::WorldspaceChunkMgr& RenderingManager::getWorldspaceChunkMgr(ESM::RefId worldspace) { auto existingChunkMgr = mWorldspaceChunks.find(worldspace); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 9776d7e632..a4e0c52025 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -2,6 +2,8 @@ #include <components/esm3/loadland.hpp> #include <components/esm4/loadwrld.hpp> +#include <components/esm4/loadltex.hpp> +#include <components/esm4/loadtxst.hpp> #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" @@ -105,10 +107,36 @@ namespace MWRender return mLandManager->getLand(cellLocation); } + // NOTE: We need to return different land texture if mIsEsm4Ext is set. + // But there isn't just one texture for a given land, so the method name + // is misleading and may cause confusion for future maintainers. + // + // It's a pity we have to use pointers to string here. FormId would have been + // more than adequate. + // + // Update: Decided to add new methods instead. See getEsm4Land(), getEsm4LandTexture() + // and getEsm4TextureSet(). const std::string* TerrainStorage::getLandTexture(std::uint16_t index, int plugin) { const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); return esmStore.get<ESM::LandTexture>().search(index, plugin); } + const ESM4::Land *TerrainStorage::getEsm4Land(ESM::ExteriorCellLocation cellLocation) const + { + return mLandManager->getLandRecord(cellLocation); + } + + const ESM4::LandTexture *TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get<ESM4::LandTexture>().search(ltexId); + } + + const ESM4::TextureSet *TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get<ESM4::TextureSet>().search(txstId); + } + } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 731f396713..85968ded96 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -7,6 +7,13 @@ #include <components/resource/resourcesystem.hpp> +namespace ESM4 +{ + struct Land; + struct LandTexture; + struct TextureSet; +} + namespace MWRender { @@ -31,6 +38,15 @@ namespace MWRender LandManager* getLandManager() const; + const ESM4::Land *getEsm4Land(ESM::ExteriorCellLocation cellLocation) const override; + const ESM4::LandTexture *getEsm4LandTexture(ESM::RefId ltexId) const override; + const ESM4::TextureSet *getEsm4TextureSet(ESM::RefId txstId) const override; + + // Intended to be set by RenderingManager. Ideally this should be part of the + // construction but this class is initialised in the constructor and we man end up with + // a different terrain during RenderingManager::enableTerrain(). + void setIsEsm4Ext(bool state) { mIsEsm4Ext = state; } + private: std::unique_ptr<LandManager> mLandManager; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index d8cfd1dcdf..a1e3f1fc03 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -94,6 +94,7 @@ namespace ESM4 struct ItemMod; struct Land; struct LandTexture; + struct TextureSet; struct LevelledCreature; struct LevelledItem; struct LevelledNpc; @@ -147,7 +148,7 @@ namespace MWWorld Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::MiscItem>, Store<ESM4::MovableStatic>, Store<ESM4::Npc>, Store<ESM4::Outfit>, Store<ESM4::Potion>, Store<ESM4::Race>, Store<ESM4::Reference>, Store<ESM4::Static>, Store<ESM4::StaticCollection>, Store<ESM4::Terminal>, Store<ESM4::Tree>, - Store<ESM4::Weapon>, Store<ESM4::World>>; + Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::TextureSet> >; private: template <typename T> diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 52917e92e3..694d076c33 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -438,6 +438,10 @@ namespace MWWorld if (cellVariant.isExterior()) { + // NOTE: LandObject may be of type ESM4 + // NOTE: It's probably a very bad idea to keep the pointer returned from + // getLandManager() since it may change if the worldspace type changes from + // TES3 to TES4 and vice-versa (depends on the final implementation). osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellIndex); const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; const int verts = ESM::getLandSize(worldspace); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 1e92df85ec..6718db86c8 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1332,7 +1332,7 @@ template class MWWorld::TypedDynamicStore<ESM4::HeadPart>; template class MWWorld::TypedDynamicStore<ESM4::Ingredient>; template class MWWorld::TypedDynamicStore<ESM4::ItemMod>; template class MWWorld::TypedDynamicStore<ESM4::Land>; -template class MWWorld::TypedDynamicStore<ESM4::LandTexture>; +template class MWWorld::TypedDynamicStore<ESM4::LandTexture>; // FIXME: maybe we need some special handling? template class MWWorld::TypedDynamicStore<ESM4::LevelledCreature>; template class MWWorld::TypedDynamicStore<ESM4::LevelledItem>; template class MWWorld::TypedDynamicStore<ESM4::LevelledNpc>; @@ -1346,6 +1346,7 @@ template class MWWorld::TypedDynamicStore<ESM4::Race>; template class MWWorld::TypedDynamicStore<ESM4::Static>; template class MWWorld::TypedDynamicStore<ESM4::StaticCollection>; template class MWWorld::TypedDynamicStore<ESM4::Terminal>; +template class MWWorld::TypedDynamicStore<ESM4::TextureSet>; template class MWWorld::TypedDynamicStore<ESM4::Tree>; template class MWWorld::TypedDynamicStore<ESM4::Weapon>; template class MWWorld::TypedDynamicStore<ESM4::World>; diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 0b60b44cf0..2f67629be5 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -77,6 +77,7 @@ #include <components/esm4/loadstat.hpp> #include <components/esm4/loadterm.hpp> #include <components/esm4/loadtree.hpp> +#include <components/esm4/loadtxst.hpp> #include <components/esm4/loadweap.hpp> #include <components/esm4/loadwrld.hpp> diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 53fb1de083..e3e6bb65d5 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -1,5 +1,5 @@ /* - Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii + Copyright (C) 2015 - 2024 cc9cii This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -17,7 +17,7 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. - cc9cii cc9c@iinet.net.au + cc9cii cc9cii@hotmail.com Much of the information on the data structures are based on the information from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by @@ -27,13 +27,31 @@ #include "loadland.hpp" #include <cstdint> +#include <cassert> #include <stdexcept> +#include <iostream> #include <components/debug/debuglog.hpp> #include "reader.hpp" // #include "writer.hpp" +namespace +{ + std::uint32_t getDefaultTexture(bool isTES4, bool isFONV, bool isTES5) + { + // WARN: guessed for FO3/FONV (might be Dirt02) + if (isTES4) + return 0x000008C0; // TerrainHDDirt01.dds (LTEX) + else if (isFONV) + return 0x00000A0D; // Landscape\Dirt01.dds (TXST 0x00004453) + else if (isTES5) + return 0x00000C16; // Landscape\Dirt02.dds (TXST 0x00000C0F) + else // FO3 + return 0x00000A0D; // Landscape\Dirt01.dds (prob. same as FONV) + } +} + // overlap north // // 32 @@ -53,13 +71,20 @@ void ESM4::Land::load(ESM4::Reader& reader) { mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; + + std::uint32_t esmVer = reader.esmVersion(); + bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100); + bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134); + bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094 + // WARN: below workaround assumes the data directory path has "Fallout" somewhere + if (esmVer == ESM4::VER_094 && reader.getContext().filename.find("allout") != std::string::npos) + isTES5 = false; // FIXME: terrible hack + mDataTypes = 0; mCell = reader.currCell(); TxtLayer layer; std::int8_t currentAddQuad = -1; // for VTXT following ATXT - // std::map<FormId, int> uniqueTextures; // FIXME: for temp testing only - while (reader.getSubRecordHeader()) { const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); @@ -78,12 +103,6 @@ void ESM4::Land::load(ESM4::Reader& reader) } case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096 { -#if 0 - reader.get(mHeightMap.heightOffset); - reader.get(mHeightMap.gradientData); - reader.get(mHeightMap.unknown1); - reader.get(mHeightMap.unknown2); -#endif reader.get(mHeightMap); mDataTypes |= LAND_VHGT; break; @@ -104,11 +123,6 @@ void ESM4::Land::load(ESM4::Reader& reader) reader.adjustFormId(base.formId); mTextures[base.quadrant].base = std::move(base); -#if 0 - std::cout << "Base Texture formid: 0x" - << std::hex << mTextures[base.quadrant].base.formId - << ", quad " << std::dec << (int)base.quadrant << std::endl; -#endif } break; } @@ -116,31 +130,27 @@ void ESM4::Land::load(ESM4::Reader& reader) { if (currentAddQuad != -1) { - // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now - Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex; + // NOTE: sometimes there are no VTXT following an ATXT + //Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex + //<< " FormId " << ESM::FormId::toString(mFormId) << std::endl; + if (!layer.texture.formId) + layer.texture.formId = getDefaultTexture(isTES4, isFONV, isTES5); + + layer.data.resize(1); // just one spot + layer.data.back().position = 0; // this corner + layer.data.back().opacity = 0.f; // transparent + + assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size() + && "additional texture skipping layer"); + mTextures[currentAddQuad].layers.push_back(layer); } + reader.get(layer.texture); reader.adjustFormId(layer.texture.formId); if (layer.texture.quadrant >= 4) throw std::runtime_error("additional texture quadrant index error"); -#if 0 - FormId txt = layer.texture.formId; - std::map<FormId, int>::iterator lb = uniqueTextures.lower_bound(txt); - if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first))) - { - lb->second += 1; - } - else - uniqueTextures.insert(lb, std::make_pair(txt, 1)); -#endif -#if 0 - std::cout << "Additional Texture formId: 0x" - << std::hex << layer.texture.formId - << ", quad " << std::dec << (int)layer.texture.quadrant << std::endl; - std::cout << "Additional Texture layer: " - << std::dec << (int)layer.texture.layerIndex << std::endl; -#endif + currentAddQuad = layer.texture.quadrant; break; } @@ -158,23 +168,16 @@ void ESM4::Land::load(ESM4::Reader& reader) layer.data.resize(count); std::vector<ESM4::Land::VTXT>::iterator it = layer.data.begin(); for (; it != layer.data.end(); ++it) - { reader.get(*it); - // FIXME: debug only - // std::cout << "pos: " << std::dec << (int)(*it).position << std::endl; - } } - mTextures[currentAddQuad].layers.push_back(layer); - // Assumed that the layers are added in the correct sequence - // FIXME: Knights.esp doesn't seem to observe this - investigate more - // assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1 - //&& "additional texture layer index error"); + assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size() + && "additional texture skipping layer"); + + mTextures[currentAddQuad].layers.push_back(layer); currentAddQuad = -1; layer.data.clear(); - // FIXME: debug only - // std::cout << "VTXT: count " << std::dec << count << std::endl; break; } case ESM::fourCC("VTEX"): // only in Oblivion? @@ -199,34 +202,22 @@ void ESM4::Land::load(ESM4::Reader& reader) } } + //if (mCell.toUint32() == 0x00005e1f) + //std::cout << "vilverin exterior" << std::endl; + if (currentAddQuad != -1) { - // FIXME: not sure if it happens here as well + // not sure if it happens here as well, if so just ignore Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex << " quad " << static_cast<unsigned>(layer.texture.quadrant); - mTextures[currentAddQuad].layers.push_back(layer); } - bool missing = false; for (int i = 0; i < 4; ++i) { + // just use some defaults if (mTextures[i].base.formId == 0) - { - // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl; - // std::cout << "layers " << mTextures[i].layers.size() << std::endl; - // NOTE: can't set the default here since FO3/FONV may have different defaults - // mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds - missing = true; - } - // else - //{ - // std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl; - // std::cout << "layers " << mTextures[i].layers.size() << std::endl; - // } + mTextures[i].base.formId = getDefaultTexture(isTES4, isFONV, isTES5); } - // at least one of the quadrants do not have a base texture, return without setting the flag - if (!missing) - mDataTypes |= LAND_VTEX; } // void ESM4::Land::save(ESM4::Writer& writer) const diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 35ec814aa2..d4e6f935cf 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -12,6 +12,8 @@ #include <components/esm/util.hpp> #include <components/esm3/loadland.hpp> #include <components/esm4/loadland.hpp> +#include <components/esm4/loadltex.hpp> +#include <components/esm4/loadtxst.hpp> #include <components/misc/resourcehelpers.hpp> #include <components/misc/strings/algorithm.hpp> #include <components/vfs/manager.hpp> @@ -40,6 +42,26 @@ namespace ESMTerrain return { tex, land->getPlugin() }; } +#if 0 + UniqueTextureId getQuadTextureIdAt(const ESM4::Land* land, std::size_t x, std::size_t y) + { + assert(x < 17); + assert(y < 17); + + if (land == nullptr) + return { 0, 0 }; + + const ESM::LandData* data = land->getData(ESM::Land::DATA_VTEX); + if (data == nullptr) + return { 0, 0 }; + + const std::uint16_t tex = data->getTextures()[y * ESM::Land::LAND_TEXTURE_SIZE + x]; + if (tex == 0) + return { 0, 0 }; // vtex 0 is always the base texture, regardless of plugin + + return { tex, land->getPlugin() }; + } +#endif } class LandCache @@ -106,7 +128,8 @@ namespace ESMTerrain Storage::Storage(const VFS::Manager* vfs, std::string_view normalMapPattern, std::string_view normalHeightMapPattern, bool autoUseNormalMaps, std::string_view specularMapPattern, bool autoUseSpecularMaps) - : mVFS(vfs) + : mIsEsm4Ext(false) + , mVFS(vfs) , mNormalMapPattern(normalMapPattern) , mNormalHeightMapPattern(normalHeightMapPattern) , mAutoUseNormalMaps(autoUseNormalMaps) @@ -366,6 +389,10 @@ namespace ESMTerrain std::fill(positions.begin(), positions.end(), osg::Vec3f()); } + // NOTE: getLandTexture() is implemented by our child class. Also note that ESM4 doesn't + // want to call correctTexturePath(). We need a way of figuring out that we are in + // ESM4 worldspace, either via a parameter or via a state kept as a member to Storage + // or TerrainStorage (i.e. our child class). std::string Storage::getTextureName(UniqueTextureId id) { std::string_view texture = "_land_default.dds"; @@ -385,6 +412,37 @@ namespace ESMTerrain return Misc::ResourceHelpers::correctTexturePath(texture, mVFS); } + // FIXME: for FO3/FONV/TES5 this is rather inefficient since the TextureSet indicates + // whether normal map exists, etc, saving us the need to do any searching + // in getLayerInfo() + // + // maybe if ltex->mTextureFile is empty simply return a null string and process + // differently? + std::string Storage::getEsm4TextureName(ESM::RefId id) + { + //if (mIsEsm4Ext) + if (const ESM4::LandTexture *ltex = getEsm4LandTexture(id)) + { + if (ltex->mTextureFile.empty()) // WARN: we assume FO3/FONV/TES5 + { + if (const ESM4::TextureSet *txst = getEsm4TextureSet(ltex->mTexture)) + { + return "textures\\"+txst->mDiffuse; + } + } + else + return "textures\\landscape\\"+ltex->mTextureFile; + } + + // FIXME: add a debug log here + return ""; + } + + // FIXME: May need some changes here to support ESM4 terrain. Not sure how to deal with many + // chunks (i.e. 4 ESM4 quads). Maybe we just go with the flow here, but do + // things 4 times as much? + // + // For now decided to create another method instead (getQuadBlendmaps). void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace) { @@ -577,6 +635,7 @@ namespace ESMTerrain Terrain::LayerInfo info; info.mParallax = false; info.mSpecular = false; + //info.mIsEsm4 = false; // hint for Terrain::createPasses() info.mDiffuseMap = texture; if (mAutoUseNormalMaps) @@ -613,6 +672,46 @@ namespace ESMTerrain return info; } + Terrain::LayerInfo Storage::getLayerInfo(const ESM4::TextureSet *txst) + { + Terrain::LayerInfo info; + info.mDiffuseMap = ""; + info.mNormalMap = ""; + info.mParallax = false; + info.mSpecular = false; + //info.mIsEsm4 = true; // hint for Terrain::createPasses() + + if (txst) + { + assert(!txst->mDiffuseMap.empty() && "getlayerInfo: empty diffuse map"); + + std::string diffuse = "textures\\landscape\\"+txst->mDiffuse; + std::map<std::string, Terrain::LayerInfo>::iterator found = mLayerInfoMap.find(diffuse); + if (found != mLayerInfoMap.end()) + return found->second; + + info.mDiffuseMap = diffuse; + if (!txst->mNormalMap.empty()) + info.mNormalMap = "textures\\landscape\\"+txst->mNormalMap; + + // FIXME: this flag indicates height info in alpha channel of normal map + // but the normal map alpha channel has specular info instead + // (probably needs some flag in the terrain shader to fix) + info.mParallax = false; + // FIXME: this flag indicates specular info in alpha channel of diffuse + // but the diffuse alpha channel has transparency data instead + // (probably needs some flag in the terrain shader to fix) + info.mSpecular = false; + + // FIXME: should support other features of ESM4::TextureSet + // probably need corresponding support in the terrain shader + + mLayerInfoMap[diffuse] = info; + } + + return info; + } + float Storage::getCellWorldSize(ESM::RefId worldspace) { return static_cast<float>(ESM::getCellSize(worldspace)); @@ -623,9 +722,401 @@ namespace ESMTerrain return ESM::getLandSize(worldspace); } + // NOTE: For now we are only conident when chunkSize is 1. Needs more testing to see + // if this will work with different chunkSize values. + // + // This is called by ChunkManager::createPasses() which then calls + // Terrain::createPasses() which ultimately calls BlendmapTexMat::value(). + // I suspect that is where UV mapping is done (just a guess; LayerTexMat + // may need to be looked at as well). + // + // WARN: the value sQuadTexturePerSide was determined empirically for TES4 only + // FO3/FONV/TES5 may well have a different value - needs testing int Storage::getBlendmapScale(float chunkSize) { + if (mIsEsm4Ext) + { + //std::cout << "blendmap scale " + //<< std::to_string(ESM4::Land::sQuadTexturePerSide * chunkSize) << std::endl; + return ESM4::Land::sQuadTexturePerSide;// * chunkSize; + } + return ESM::Land::LAND_TEXTURE_SIZE * chunkSize; } + void Storage::fillQuadVertexBuffers(float size, const osg::Vec2f& center, ESM::RefId worldspace, + osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad) + { + // sampleSize is not used but declared here in order to keep the code as close to + // fillVertexBuffers() as possible + const std::size_t sampleSize = 1; + + // DEBUG NOTES: cellSize should be 33 for ESM4 + // numVerts should be 17 for ESM4 + const std::size_t cellSize = static_cast<std::size_t>(ESM::getLandSize(worldspace)); + const std::size_t numVerts = static_cast<std::size_t>(size * (cellSize - 1) / sampleSize) + 1; + + positions.resize(numVerts*numVerts*3); + normals.resize(numVerts*numVerts*3); + colours.resize(numVerts*numVerts*4); + + const bool alteration = useAlteration(); // Does nothing by default, override in OpenMW-CS + const int landSizeInUnits = ESM::getCellSize(worldspace); + +// I think the current code copied from fillVertexBuffers() works fine +#if 0 + // NOTE: here center is the center of the ESM4 cell (in terms of cell grid position) which + // is subtly different to the way fillVertexBuffers() treats it because we don't + // worry about chunk sizes or LOD + // + // center is wrong here due to the way TerrainGrid::buildTerrain() calculates the + // new center + osg::Vec2f realCenter; + switch (quad) + { + case 3: realCenter = center - osg::Vec2f( 0.25f, 0.25f); break; + case 1: realCenter = center - osg::Vec2f( 0.25f, -0.25f); break; + case 2: realCenter = center - osg::Vec2f(-0.25f, 0.25f); break; + case 0: realCenter = center - osg::Vec2f(-0.25f, -0.25f); break; + default: realCenter = center; break; + } + const osg::Vec2f origin2 = realCenter - osg::Vec2f(1.f, 1.f) * 0.5f; // assumed to be bottom left corner + //std::cout << origin2.x() << ", " << origin2.y() << std::endl; +#endif + const osg::Vec2f origin = center - osg::Vec2f(size, size) * 0.5f; + //std::cout << origin.x() << ", " << origin.y() << std::endl; + const int startCellX = static_cast<int>(std::floor(origin.x())); + const int startCellY = static_cast<int>(std::floor(origin.y())); + LandCache cache(startCellX - 1, startCellY - 1, static_cast<std::size_t>(std::ceil(size)) + 2); + std::pair lastCell{ startCellX, startCellY }; + const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache); + const ESM::LandData* heightData = nullptr; + const ESM::LandData* normalData = nullptr; + const ESM::LandData* colourData = nullptr; + bool validHeightDataExists = false; + + if (land != nullptr) + { + heightData = land->getData(ESM::Land::DATA_VHGT); + normalData = land->getData(ESM::Land::DATA_VNML); + colourData = land->getData(ESM::Land::DATA_VCLR); + validHeightDataExists = true; + } + + int rowStart = 0; + int colStart = 0; + int rowEnd, colEnd; + + // FIXME: how to ignore the repeat of left/bottom quad? + switch (quad) + { + case 0: // bottom left + { + rowStart = 0; + colStart = 0; + + rowEnd = int(cellSize / 2) + 1; // int(33 / 2) + 1 = 17 + colEnd = int(cellSize / 2) + 1; + + break; + } + case 2: // bottom right + { + rowStart = 0; + colStart = int(cellSize / 2); // 16, repeat the last of the left quad + + rowEnd = int(cellSize / 2) + 1; // 17 + colEnd = cellSize; + + break; + } + case 1: // top left + { + rowStart = int(cellSize / 2); // 16, repeat the last of the bottom quad + colStart = 0; + + rowEnd = cellSize; + colEnd = int(cellSize / 2) + 1; // 17 + + break; + } + case 3: // top right + { + rowStart = int(cellSize / 2); // 16 + colStart = int(cellSize / 2); // 16 + + rowEnd = cellSize; // 33 + colEnd = cellSize; // 33 + + break; + } + default: + std::fill(positions.begin(), positions.end(), osg::Vec3f()); + return; // FIXME: throw instead? + } + + osg::Vec3f normal(0, 0, 1); + osg::Vec4ub color(255, 255, 255, 255); + + // ESM4::Land::mLandData.mHeights start at the bottom left hand corner + // + // row + // | + // v + // 1056 ..1088 32 + // 1023 ..1055 31 + // .. + // 99 .. 131 3 + // 66 .. 98 2 + // 33 .. 65 1 + // 0 .. 32 0 + // + // 0 .. 32 <- col + // + // row and col represent cell space (i.e. mHeights, mVertNorm and mVertColr) + // vertX and vertY represent quad space + float vertY = 0; + float vertX = 0; + for (int col = colStart; col < colEnd; col += 1) + { + vertX = 0; + for (int row = rowStart; row < rowEnd; row += 1) + { + float height = -2048; + if (land && heightData) // validHeightDataExists + height = heightData->getHeights()[col*cellSize + row]; + + // FIXME: I suspect landSizeInUnits should be 2048 + const std::size_t vertIndex = vertX * numVerts + vertY; + positions[vertIndex] + = osg::Vec3f((vertX / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits, + (vertY / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits, + height); + + if (land && normalData) + { + normal.x() = normalData->getNormals()[col * cellSize * 3 + row * 3 + 0]; + normal.y() = normalData->getNormals()[col * cellSize * 3 + row * 3 + 1]; + normal.z() = normalData->getNormals()[col * cellSize * 3 + row * 3 + 2]; + normal.normalize(); + } + else + normal = osg::Vec3f(0, 0, 1); + +// FIXME: not sure if below normal fixes for Morrowind also applies to TES4 +// TODO: needs testing +#if 0 + // Normals apparently don't connect seamlessly between cells + if (col == cellSize - 1 || row == cellSize - 1) + fixNormal(normal, cellLocation, col, row, cache); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == cellSize - 1) && (col == 0 || col == cellSize - 1)) + averageNormal(normal, cellLocation, col, row, cache); +#endif + //assert(normal.z() > 0); // ToddLand triggers this + if (normal.z() < 0) + normal.z() = 0; + + normals[vertIndex] = normal; + + if (land && colourData) + { + color.r() = colourData->getColors()[col * cellSize * 3 + row * 3 + 0]; + color.g() = colourData->getColors()[col * cellSize * 3 + row * 3 + 1]; + color.b() = colourData->getColors()[col * cellSize * 3 + row * 3 + 2]; + } + else + { + color.r() = 1; + color.g() = 1; + color.b() = 1; + } + +// FIXME: not sure if below colour fixes for Morrowind also applies to TES4 +// TODO: needs testing +#if 0 + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == cellSize - 1 || row == cellSize - 1) + fixColour(color, cellLocation, col, row, cache); +#endif +// color.a() = 1; + colours[vertIndex] = color; + + ++vertX; + } + ++vertY; + } + } + + void Storage::getQuadBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace, int quad) + { + // VTXT info indicates texture size is 17x17 - but the cell grid is 33x33 + // (cf. TES3 has 65x65 cell) do we discard one row and column or overlap? + // + // NOTE: each base texture does not completely "fill" a quadrant. The observations in + // TES4 vanilla indicates that the texture repeats (or "wraps") 6 times each side + // + // ///////////////// //////////////// <-- discard texture row? + // +-----------------+----------------+/ + // 32 |\ \| |/ + // 31 |\ \| |/ + // |\ 17x16 \| 16x16 |/ + // . |\ \| |/ + // . |\ 2 \| 3 |/ + // . |\ \| |/ + // . |\ \<---------------------- overlap column instead? + // 17 |\ \| |/ + // +-----------------+----------------+ + // 16 |\ |\\\\\\\\\\\\\\\\|<---- overlap row instead? + // 15 |\ | |/ + // . |\ 17x17 | 16x17 |/ + // . |\ | |/ + // . |\ 0 | 1 |/ + // . |\ | |/ + // 2 |\ | |/ + // 1 |\ | |/ + // 0 |\\\\\\\\\\\\\\\\\|\\\\\\\\\\\\\\\\|<---- this row of vertices is a copy of cell below + // +-----------------+----------------+ + // 111 1 33 ^ + // 0123 ...... 456 7 ..... 12 | + // ^ discard texture column? + // | + // this column of vertices is a copy of the cell to the left + // + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize, chunkSize) * 0.5f; + const int startCellX = static_cast<int>(std::floor(origin.x())); + const int startCellY = static_cast<int>(std::floor(origin.y())); + const int realTextureSize = 17; // FIXME: should be defined in Land record + //const std::size_t blendmapSize = getBlendmapSize(chunkSize, realTextureSize); + const std::size_t blendmapSize = realTextureSize; + + // FIXME: temp testing + //if (startCellX == 12 && startCellY == 21) + //std::cout << "vilverin exterior" << std::endl; + +// FIXME: I don't think ESM4 needs this? +#if 0 + // We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla + constexpr std::size_t imageScaleFactor = 2; +#else + constexpr std::size_t imageScaleFactor = 1; +#endif + const std::size_t blendmapImageSize = blendmapSize * imageScaleFactor; + std::vector<UniqueTextureId> textureIds(blendmapSize * blendmapSize); + +// NOTE: we need all the texture data which are missing in LandObject +#if 0 + LandCache cache(startCellX - 1, startCellY - 1, static_cast<std::size_t>(std::ceil(chunkSize)) + 2); + std::pair lastCell{ startCellX, startCellY }; + const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache); +#endif + // FIXME: do we need to cache this data? (already in ESMStore, why cache again?) + // alternatively modify LandObject with all the extra data rather than use getEsm4Land()? + const ESM4::Land* land = getEsm4Land(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace)); + if (!land) + return; // FIXME: throw instead? + +// I don't think we need this? +#if 0 + const auto handleSample = [&](const CellSample& sample) { + const std::pair cell{ sample.mCellX, sample.mCellY }; + if (lastCell != cell) + { + land = getEsm4Land(ESM::ExteriorCellLocation(sample.mCellX, sample.mCellY, worldspace)); + lastCell = cell; + } + + textureIds[sample.mDstCol * blendmapSize + sample.mDstRow] + = getQuadTextureIdAt(land, sample.mSrcRow, sample.mSrcCol); + }; + + sampleBlendmaps(chunkSize, origin.x(), origin.y(), realTextureSize, handleSample); + + std::map<UniqueTextureId, std::size_t> textureIndicesMap; +#endif + // FIXME: debugging only + //std::cout << "quad " << quad << std::endl; + + // base texture + Terrain::LayerInfo info; + ESM::FormId ltexId = ESM::FormId::fromUint32(land->mTextures[quad].base.formId); + std::string texture = getEsm4TextureName(ltexId); + if (texture == "") + info = getLayerInfo(getEsm4TextureSet(ltexId)); // FO3/FONV/TES5 + else + info = getLayerInfo(texture); // TES4 + + // FIXME: debugging only + //std::cout << "base " << info.mDiffuseMap << std::endl; + osg::ref_ptr<osg::Image> image(new osg::Image); + image->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize), + 1, GL_ALPHA, GL_UNSIGNED_BYTE); + std::memset(image->data(), 255, image->getTotalDataSize()); // fully opaque for base texture + blendmaps.push_back(std::move(image)); + layerList.push_back(std::move(info)); + + // additional textures + + std::size_t numLayers = land->mTextures[quad].layers.size(); + for (std::size_t i = 0; i < numLayers; ++i) + { + Terrain::LayerInfo layerInfo; + /*ESM::FormId*/ ltexId = ESM::FormId::fromUint32(land->mTextures[quad].layers[i].texture.formId); + std::string layerTexture = getEsm4TextureName(ltexId); + if (layerTexture == "") + layerInfo = getLayerInfo(getEsm4TextureSet(ltexId)); // FO3/FONV/TES5 + else + layerInfo = getLayerInfo(layerTexture); // TES4 + + // FIXME: debugging only + //std::cout << "layer " << i << ", " << layerInfo.mDiffuseMap << std::endl; + + osg::ref_ptr<osg::Image> layerImage(new osg::Image); + layerImage->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize), + 1, GL_ALPHA, GL_UNSIGNED_BYTE); + std::memset(layerImage->data(), 0, layerImage->getTotalDataSize()); + blendmaps.push_back(std::move(layerImage)); + layerList.push_back(std::move(layerInfo)); + + const std::size_t layerIndex = blendmaps.size() - 1; + unsigned char* const data = blendmaps[layerIndex]->data(); + + // osg::Image default origin is bottom left and VTXT data also starts at bottom left + // corner i.e. there should be no conversion required + // + // FIXME: but the observed behaviour is different - either VTXT starts at top left + // corner or osg::Image is being interpreted differently by the shader + // + // Image guessed VTXT + // index position y' + // + // 272 ..288 0 .. 16 0 + // .. .. + // 51 .. 67 221 ..237 13 + // 34 .. 50 238 ..254 14 + // 17 .. 33 255 ..271 15 + // 0 .. 16 272 ..288 16 + // + // y = floor(position / 17) + // y' = 17 - 1 - y + // x = position % 17 + // + // e.g. position = 275, y = 16, y' = 0, x = 3 + // position = 50, y = 2, y' = 14, x = 16 + const std::vector<ESM4::Land::VTXT>& opacityData = land->mTextures[quad].layers[i].data; + for (std::size_t j = 0; j < opacityData.size(); ++j) + { + // NOTE: blendmapImageSize, blendmapSize and realTextureSize are all the same (17) + + int position = opacityData[j].position; + + std::size_t y = realTextureSize - 1 - std::floor(position / realTextureSize); + std::size_t x = position % realTextureSize; + data[y*realTextureSize + x] = unsigned char(opacityData[j].opacity * 255); + } + } + } + } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 402f2147ab..27ec297613 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -13,6 +13,8 @@ namespace ESM4 { struct Land; + struct LandTexture; + struct TextureSet; } namespace ESM @@ -130,6 +132,19 @@ namespace ESMTerrain return data->getHeights()[y * landSize + x]; } + virtual const ESM4::Land *getEsm4Land(ESM::ExteriorCellLocation cellLocation) const = 0; + virtual const ESM4::LandTexture *getEsm4LandTexture(ESM::RefId ltexId) const = 0; + virtual const ESM4::TextureSet *getEsm4TextureSet(ESM::RefId txstId) const = 0; + + void fillQuadVertexBuffers(float size, const osg::Vec2f& center, ESM::RefId worldspace, + osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad); + + void getQuadBlendmaps(float size, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace, int quad); + + protected: + bool mIsEsm4Ext; // intended to be used by MWRender::TerrainStorage + private: const VFS::Manager* mVFS; @@ -148,6 +163,8 @@ namespace ESMTerrain std::string getTextureName(UniqueTextureId id); + std::string getEsm4TextureName(ESM::RefId id); + std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::mutex mLayerInfoMutex; @@ -159,6 +176,8 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); + + Terrain::LayerInfo getLayerInfo(const ESM4::TextureSet *txst); }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 7ccd89ac21..4cb4439ed3 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -10,6 +10,8 @@ #include <components/sceneutil/lightmanager.hpp> +#include <components/esmterrain/storage.hpp> + #include "compositemaprenderer.hpp" #include "material.hpp" #include "storage.hpp" @@ -39,6 +41,32 @@ namespace Terrain mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } + // FIXME: don't know which is worse, adding duplicated code here or adding a parameter to + // Terrain::QuadTreeWorld::getChunk(). + osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, int quad) + { + // Override lod with the vertexLodMod adjusted value. + // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. + lod = static_cast<unsigned char>(lodFlags >> (4 * 4)); + + const ChunkKey key{ .mCenter = center, .mLod = lod, .mLodFlags = lodFlags }; + if (osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(key)) + return static_cast<osg::Node*>(obj.get()); + + const TerrainDrawable* templateGeometry = nullptr; + const TemplateKey templateKey{ .mCenter = center, .mLod = lod }; + const auto pair = mCache->lowerBound(templateKey); + if (pair.has_value() && templateKey == TemplateKey{ .mCenter = pair->first.mCenter, .mLod = pair->first.mLod }) + templateGeometry = static_cast<const TerrainDrawable*>(pair->second.get()); + + osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags, compile, templateGeometry, quad); + mCache->addEntryToObjectCache(key, node.get()); + return node; + } + + // called from either TerrainGrid::buildTerrain() or QuadTreeWorld::loadRenderingNode() + // calls createChunk() osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { @@ -137,12 +165,27 @@ namespace Terrain } } +// > openmw.exe!Terrain::ChunkManager::createPasses() Line 188 +// openmw.exe!Terrain::ChunkManager::createCompositeMapGeometry() Line 123 +// openmw.exe!Terrain::ChunkManager::createChunk() Line 275 +// openmw.exe!Terrain::ChunkManager::getChunk() Line 59 +// openmw.exe!Terrain::QuadTreeWorld::loadRenderingNode() Line 397 +// openmw.exe!Terrain::QuadTreeWorld::preload() Line 558 +// openmw.exe!MWWorld::TerrainPreloadItem::doWork() Line 184 +// openmw.exe!SceneUtil::WorkThread::run() Line 135 std::vector<osg::ref_ptr<osg::StateSet>> ChunkManager::createPasses( - float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap) + float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap, int quad) { std::vector<LayerInfo> layerList; std::vector<osg::ref_ptr<osg::Image>> blendmaps; - mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace); + + if (quad >= 0) // NOTE: quad == -1 has a special meaning of "no quads" + { + static_cast<ESMTerrain::Storage*>(mStorage) + ->getQuadBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace, quad); + } + else + mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace); bool useShaders = mSceneManager->getForceShaders(); if (!mSceneManager->getClampLighting()) @@ -183,14 +226,26 @@ namespace Terrain blendmapTextures.push_back(texture); } + // NOTE: This needs to get different values for TES4. That is, blendmapScale should + // be 16 for TES3 and 6 for TES4 after calling getBlendmapScale() if we were + // using TerrainGrid (i.e. chunksize of 1.f). See the way Terrain::createPasses() + // uses BlendmapTexMat::value(blendmapScale). This scaling won't work if using + // QuadTree with chunkSize other than 1.f (in which case need to do sampling). + // + // We should remember that in TES4/ESM4 the land "chunk" size is not the same + // size as the texture. So we may have to do some maths here but it is unclear + // whether it will work out properly until some testing is done. + // + // FIXME: TES5 and FO3/FONV may have different texture scaling - requires testing. float blendmapScale = mStorage->getBlendmapScale(chunkSize); + // TODO: not so sure about (i.e. don't understand) using blendmapScale for layerTileSize return ::Terrain::createPasses( - useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); + useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale, quad); } osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, - unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry) + unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry, int quad) { osg::ref_ptr<TerrainDrawable> geometry(new TerrainDrawable); @@ -201,7 +256,18 @@ namespace Terrain osg::ref_ptr<osg::Vec4ubArray> colors(new osg::Vec4ubArray); colors->setNormalize(true); - mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors); +// FIXME: I have a suspicion that existing fillVertexBuffers() probably already works even with +// the unwanted Morrowind specific "fixes". +#if 1 + // NOTE: decided on a new method rather than pass quad to fillVertexBuffers() just + // in case the Morrowind specific "fixes" causes problems + // NOTE: LOD is not supported + if (quad >= 0) // NOTE: quad == -1 has a special meaning of "no quads" + static_cast<ESMTerrain::Storage*>(mStorage) + ->fillQuadVertexBuffers(chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors, quad); + else +#endif + mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors); osg::ref_ptr<osg::VertexBufferObject> vbo(new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); @@ -284,7 +350,9 @@ namespace Terrain } else { - geometry->setPasses(createPasses(chunkSize, chunkCenter, false)); + // FIXME: maybe we need to pass quad here or call a new method + // e.g. createQuadPasses() + geometry->setPasses(createPasses(chunkSize, chunkCenter, false, quad)); } } diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 20d6ba9327..e8833c2772 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -81,6 +81,10 @@ namespace Terrain osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; + // NOTE: created to avoid adding another parameter to getChunk() in Terrain::QuadTreeWorld::ChunkManager + osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, + bool activeGrid, const osg::Vec3f& viewPoint, bool compile, int quad); + void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } @@ -95,8 +99,9 @@ namespace Terrain void releaseGLObjects(osg::State* state) override; private: + // NOTE: quad == -1 has a special meaning that we're not dealing with ESM4 quad structure osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry); + unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry, int quad = -1); osg::ref_ptr<osg::Texture2D> createCompositeMapRTT(); @@ -104,7 +109,7 @@ namespace Terrain float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map); std::vector<osg::ref_ptr<osg::StateSet>> createPasses( - float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap); + float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap, int quad = -1); Terrain::Storage* mStorage; Resource::SceneManager* mSceneManager; diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index c2342c50d2..fdc74cf8c3 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -20,6 +20,7 @@ namespace Terrain std::string mNormalMap; bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? + //bool mIsEsm4; // intended to be used in Terrain::createPasses() bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; } }; diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 09d2680acd..f7536a08fa 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -27,6 +27,8 @@ namespace return instance.get(blendmapScale); } + // FIXME: Not sure if this pre-multiplication is needed for TES4 + // (needs A-B testing to confirm) const osg::ref_ptr<osg::TexMat>& get(const int blendmapScale) { const std::lock_guard<std::mutex> lock(mMutex); @@ -223,7 +225,7 @@ namespace Terrain { std::vector<osg::ref_ptr<osg::StateSet>> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector<TextureLayer>& layers, const std::vector<osg::ref_ptr<osg::Texture2D>>& blendmaps, - int blendmapScale, float layerTileSize) + int blendmapScale, float layerTileSize, int quad) { auto& shaderManager = sceneManager->getShaderManager(); std::vector<osg::ref_ptr<osg::StateSet>> passes; @@ -243,7 +245,12 @@ namespace Terrain stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); if (!firstLayer) { - stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); + if (quad >= 0) + stateset->setAttributeAndModes( + new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA), + osg::StateAttribute::ON); + else + stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON); stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON); } else @@ -303,13 +310,20 @@ namespace Terrain defineMap["parallax"] = parallax ? "1" : "0"; defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0"; + if (quad >= 0) + defineMap["baseLayer"] = (firstLayer) ? "1" : "0"; Stereo::shaderStereoDefines(defineMap); - stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap)); + if (quad >= 0) + stateset->setAttributeAndModes(shaderManager.getProgram("esm4terrain", defineMap)); + else + stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap)); stateset->addUniform(UniformCollection::value().mColorMode); } else { + // FIXME: needs some changes for ESM4 + // Add the actual layer texture osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap; stateset->setTextureAttributeAndModes(0, tex.get()); diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 1dbf6d8fc8..23d687b123 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -26,7 +26,7 @@ namespace Terrain std::vector<osg::ref_ptr<osg::StateSet>> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector<TextureLayer>& layers, const std::vector<osg::ref_ptr<osg::Texture2D>>& blendmaps, - int blendmapScale, float layerTileSize); + int blendmapScale, float layerTileSize, int quad = -1); } diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 63b55abb21..70db339336 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -1,5 +1,7 @@ #include "quadtreeworld.hpp" +#include <cmath> // std::floor + #include <osg/Material> #include <osg/PolygonMode> #include <osg/ShapeDrawable> @@ -260,7 +262,7 @@ namespace Terrain { } osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) { osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, @@ -289,7 +291,7 @@ namespace Terrain , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits<float>::max()) - , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 4.f : 1 / 8.f) + , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 2.f : 1 / 8.f) // NOTE: increased min for ESM4 , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); @@ -394,11 +396,32 @@ namespace Terrain for (QuadTreeWorld::ChunkManager* m : mChunkManagers) { - osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), - DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, - vd->getViewPoint(), compile); - if (n) - pat->addChild(n); + osg::ref_ptr<osg::Node> n; + if (ESM::isEsm4Ext(mWorldspace) && entry.mNode->getSize() == 0.5f && m == mChunkManager.get()) + { + osg::Vec2 chunkCenter = entry.mNode->getCenter(); + float originX = std::floor(chunkCenter.x()); + float originY = std::floor(chunkCenter.y()); + int quad = -1; + if (chunkCenter.x() - originX == 0.25f) + quad = (chunkCenter.y() - originY == 0.25f) ? 0 : 2; + else + quad = (chunkCenter.y() - originY == 0.25f) ? 1 : 3; + + n = static_cast<Terrain::ChunkManager*>(m)->getChunk(0.5f, entry.mNode->getCenter(), + DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, + vd->getViewPoint(), compile, quad); + if (n) + pat->addChild(n); + } + else + { + n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), + DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, + vd->getViewPoint(), compile); + if (n) + pat->addChild(n); + } } entry.mRenderingNode = pat; } @@ -536,6 +559,8 @@ namespace Terrain return mViewDataMap->createIndependentView(); } + // FIXME: I guess this is where it all starts? We need to somehow deal with entry of chunk + // size 1 to be split into 4 smaller "quads". void QuadTreeWorld::preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i& grid, std::atomic<bool>& abort, Loading::Reporter& reporter) { diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index fa800d2655..31db0d8878 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -67,7 +67,7 @@ namespace Terrain mWorldspace = worldspace; } virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad = -1*/) = 0; virtual unsigned int getNodeMask() { return 0; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 2849c4d401..8f34599d06 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -10,6 +10,7 @@ #include "storage.hpp" #include "view.hpp" #include <components/sceneutil/positionattitudetransform.hpp> +#include <components/esm/util.hpp> namespace Terrain { @@ -51,10 +52,36 @@ namespace Terrain static_cast<MyView*>(view)->mLoaded = buildTerrain(nullptr, 1.f, center); } + // I think this should be where we decide to split the land into 4 quads. + // Alternatively, we can do it in loadCell() and call a different kind of buildTerrain(). + // + // Need to do some experiments to see if the existing code will produe the correct quads or + // special code needs to be added (depens on column/row start and ends). But I think we + // still need to pass more info to getChunk() because we need to know which quadrant for + // the textures? osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain( - osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter) + osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter, int quad) { - if (chunkSize * mNumSplits > 1.f) + if (ESM::isEsm4Ext(mWorldspace) && chunkSize == 1.f && mNumSplits == 4) // WARN: hard coded values for ESM4 + { + osg::ref_ptr<osg::Group> group(new osg::Group); + if (parent) + parent->addChild(group); // should never happen + + float newChunkSize = chunkSize / 2.f; + { + buildTerrain(group, // top right + newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, newChunkSize / 2.f), 3); + buildTerrain(group, // top left + newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, -newChunkSize / 2.f), 1); + buildTerrain(group, // bottom right + newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, newChunkSize / 2.f), 2); + buildTerrain(group, // bottom left + newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, -newChunkSize / 2.f), 0); + } + return group; + } + else if (!ESM::isEsm4Ext(mWorldspace) && chunkSize * mNumSplits > 1.f) // FIXME: needs better logic { // keep splitting osg::ref_ptr<osg::Group> group(new osg::Group); @@ -70,8 +97,10 @@ namespace Terrain } else { - osg::ref_ptr<osg::Node> node - = mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true); + // FIXME: not sure which is worse, this mess or adding a parameter to Terrain::QuadTreeWorld::getChunk() + osg::ref_ptr<osg::Node> node = ESM::isEsm4Ext(mWorldspace) + ? mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true, quad) + : mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true); if (!node) return nullptr; @@ -85,6 +114,8 @@ namespace Terrain } } + // Use ESM::isEsm4Ext(World::getWorldspace()) + // or just ESM::isEsm4Ext(mWorldspace) since mWorldspace is declared as protected. void TerrainGrid::loadCell(int x, int y) { if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 8483338f23..23a58d34e6 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -46,7 +46,8 @@ namespace Terrain bool isGridEmpty() const { return mGrid.empty(); } private: - osg::ref_ptr<osg::Node> buildTerrain(osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter); + // quad is meant to be used for ESM4 terrain only; if -1 it is ignored, should be [0..3] + osg::ref_ptr<osg::Node> buildTerrain(osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter, int quad = -1); void updateWaterCulling(); // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks diff --git a/files/shaders/compatibility/esm4terrain.frag b/files/shaders/compatibility/esm4terrain.frag new file mode 100644 index 0000000000..243e0e9ba1 --- /dev/null +++ b/files/shaders/compatibility/esm4terrain.frag @@ -0,0 +1,110 @@ +#version 120 + +#if @useUBO + #extension GL_ARB_uniform_buffer_object : require +#endif + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + +varying vec2 uv; + +uniform sampler2D diffuseMap; + +#if @normalMap +uniform sampler2D normalMap; +#endif + +#if @blendMap +uniform sampler2D blendMap; +#endif + +varying float euclideanDepth; +varying float linearDepth; + +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; +centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; +#endif +varying vec3 passViewPos; +varying vec3 passNormal; + +uniform vec2 screenRes; +uniform float far; + +#include "vertexcolors.glsl" +#include "shadows_fragment.glsl" +#include "lib/light/lighting.glsl" +#include "lib/material/parallax.glsl" +#include "fog.glsl" +#include "compatibility/normals.glsl" + +void main() +{ + vec2 adjustedUV = (gl_TextureMatrix[0] * vec4(uv, 0.0, 1.0)).xy; + +#if @parallax + adjustedUV += getParallaxOffset(transpose(normalToViewMatrix) * normalize(-passViewPos), texture2D(normalMap, adjustedUV).a, 1.f); +#endif + vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); + gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); + +#if @baseLayer + vec4 diffuseColor = getDiffuseColor(); + gl_FragData[0].a *= diffuseColor.a; +#endif + +#if @blendMap + vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; +#if @baseLayer +#else + gl_FragData[0].a = texture2D(blendMap, blendMapUV).a; +#endif +#endif + +#if @normalMap + vec4 normalTex = texture2D(normalMap, adjustedUV); + vec3 normal = normalTex.xyz * 2.0 - 1.0; +#if @reconstructNormalZ + normal.z = sqrt(1.0 - dot(normal.xy, normal.xy)); +#endif + vec3 viewNormal = normalToView(normal); +#else + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); +#endif + + float shadowing = unshadowedLightRatio(linearDepth); + vec3 lighting, specular; +#if !PER_PIXEL_LIGHTING + lighting = passLighting + shadowDiffuseLighting * shadowing; + specular = passSpecular + shadowSpecularLighting * shadowing; +#else +#if @specularMap + float shininess = 128.0; // TODO: make configurable + vec3 specularColor = vec3(diffuseTex.a); +#else + float shininess = gl_FrontMaterial.shininess; + vec3 specularColor = getSpecularColor().xyz; +#endif + vec3 diffuseLight, ambientLight, specularLight; + doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); + lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + specular = specularColor * specularLight; +#endif + + clampLightingResult(lighting); + gl_FragData[0].xyz = gl_FragData[0].xyz * lighting + specular; + + gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); + +#if !@disableNormals && @writeNormals + gl_FragData[1].xyz = viewNormal * 0.5 + 0.5; +#endif + + applyShadowDebugOverlay(); +} diff --git a/files/shaders/compatibility/esm4terrain.vert b/files/shaders/compatibility/esm4terrain.vert new file mode 100644 index 0000000000..cbfb7769ba --- /dev/null +++ b/files/shaders/compatibility/esm4terrain.vert @@ -0,0 +1,73 @@ +#version 120 + +#if @useUBO + #extension GL_ARB_uniform_buffer_object : require +#endif + +#if @useGPUShader4 + #extension GL_EXT_gpu_shader4: require +#endif + +#include "lib/core/vertex.h.glsl" +varying vec2 uv; +varying float euclideanDepth; +varying float linearDepth; + +#define PER_PIXEL_LIGHTING (@normalMap || @specularMap || @forcePPL) + +#if !PER_PIXEL_LIGHTING +centroid varying vec3 passLighting; +centroid varying vec3 passSpecular; +centroid varying vec3 shadowDiffuseLighting; +centroid varying vec3 shadowSpecularLighting; +#endif +varying vec3 passViewPos; +varying vec3 passNormal; + +#include "vertexcolors.glsl" +#include "shadows_vertex.glsl" +#include "compatibility/normals.glsl" + +#include "lib/light/lighting.glsl" +#include "lib/view/depth.glsl" + +void main(void) +{ + gl_Position = modelToClip(gl_Vertex); + + vec4 viewPos = modelToView(gl_Vertex); + gl_ClipVertex = viewPos; + euclideanDepth = length(viewPos.xyz); + linearDepth = getLinearDepth(gl_Position.z, viewPos.z); + + passColor = gl_Color; + passNormal = gl_Normal.xyz; + passViewPos = viewPos.xyz; + normalToViewMatrix = gl_NormalMatrix; + +#if @normalMap + mat3 tbnMatrix = generateTangentSpace(vec4(1.0, 0.0, 0.0, 1.0), passNormal); + tbnMatrix[0] = normalize(cross(tbnMatrix[2], tbnMatrix[1])); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + normalToViewMatrix *= tbnMatrix; +#endif + +#if !PER_PIXEL_LIGHTING || @shadows_enabled + vec3 viewNormal = normalize(gl_NormalMatrix * passNormal); +#endif + +#if !PER_PIXEL_LIGHTING + vec3 diffuseLight, ambientLight, specularLight; + doLighting(viewPos.xyz, viewNormal, gl_FrontMaterial.shininess, diffuseLight, ambientLight, specularLight, shadowDiffuseLighting, shadowSpecularLighting); + passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + passSpecular = getSpecularColor().xyz * specularLight; + clampLightingResult(passLighting); + shadowDiffuseLighting *= getDiffuseColor().xyz; + shadowSpecularLighting *= getSpecularColor().xyz; +#endif + + uv = gl_MultiTexCoord0.xy; + +#if (@shadows_enabled) + setupShadowCoords(viewPos, viewNormal); +#endif +} From 735b5fbf897e2f7c94f8afb2d80b16372074a574 Mon Sep 17 00:00:00 2001 From: "florent.teppe" <teppe.florent@hotmail.fr> Date: Mon, 12 Aug 2024 20:38:44 +0200 Subject: [PATCH 2/6] Small fix so it compiles --- components/esm4/loadland.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index e3e6bb65d5..41bd8ff30e 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -77,8 +77,8 @@ void ESM4::Land::load(ESM4::Reader& reader) bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134); bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094 // WARN: below workaround assumes the data directory path has "Fallout" somewhere - if (esmVer == ESM4::VER_094 && reader.getContext().filename.find("allout") != std::string::npos) - isTES5 = false; // FIXME: terrible hack + //if (esmVer == ESM::VER_094 && reader.getContext().filename.find("allout") != std::string::npos) + // isTES5 = false; // FIXME: terrible hack mDataTypes = 0; mCell = reader.currCell(); From 5630bf8f6b93654a2e34779fd63daf38d237f526 Mon Sep 17 00:00:00 2001 From: "florent.teppe" <teppe.florent@hotmail.fr> Date: Mon, 12 Aug 2024 21:39:04 +0200 Subject: [PATCH 3/6] clang format clang format --- apps/openmw/mwrender/groundcover.cpp | 2 +- apps/openmw/mwrender/groundcover.hpp | 2 +- apps/openmw/mwrender/landmanager.cpp | 2 +- apps/openmw/mwrender/landmanager.hpp | 2 +- apps/openmw/mwrender/objectpaging.cpp | 2 +- apps/openmw/mwrender/objectpaging.hpp | 2 +- apps/openmw/mwrender/terrainstorage.cpp | 8 +-- apps/openmw/mwrender/terrainstorage.hpp | 6 +-- apps/openmw/mwworld/esmstore.hpp | 2 +- components/esm4/loadland.cpp | 24 ++++----- components/esmterrain/storage.cpp | 69 ++++++++++++------------- components/esmterrain/storage.hpp | 8 +-- components/terrain/chunkmanager.cpp | 24 ++++----- components/terrain/defs.hpp | 2 +- components/terrain/quadtreeworld.cpp | 2 +- components/terrain/quadtreeworld.hpp | 2 +- components/terrain/terraingrid.cpp | 10 ++-- components/terrain/terraingrid.hpp | 3 +- 18 files changed, 86 insertions(+), 86 deletions(-) diff --git a/apps/openmw/mwrender/groundcover.cpp b/apps/openmw/mwrender/groundcover.cpp index 97d2025a31..115f27149b 100644 --- a/apps/openmw/mwrender/groundcover.cpp +++ b/apps/openmw/mwrender/groundcover.cpp @@ -322,7 +322,7 @@ namespace MWRender } osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile /*, int quad*/) { if (lod > getMaxLodLevel()) return nullptr; diff --git a/apps/openmw/mwrender/groundcover.hpp b/apps/openmw/mwrender/groundcover.hpp index 51c2cb4628..f51abe48d0 100644 --- a/apps/openmw/mwrender/groundcover.hpp +++ b/apps/openmw/mwrender/groundcover.hpp @@ -27,7 +27,7 @@ namespace MWRender ~Groundcover(); osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, - bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) override; + bool activeGrid, const osg::Vec3f& viewPoint, bool compile /*, int quad*/) override; unsigned int getNodeMask() override; diff --git a/apps/openmw/mwrender/landmanager.cpp b/apps/openmw/mwrender/landmanager.cpp index ed7d0aa71e..3c927601e3 100644 --- a/apps/openmw/mwrender/landmanager.cpp +++ b/apps/openmw/mwrender/landmanager.cpp @@ -49,7 +49,7 @@ namespace MWRender return landObj; } - const ESM4::Land *LandManager::getLandRecord(ESM::ExteriorCellLocation cellIndex) const + const ESM4::Land* LandManager::getLandRecord(ESM::ExteriorCellLocation cellIndex) const { const MWBase::World& world = *MWBase::Environment::get().getWorld(); const ESM4::Land* land = world.getStore().get<ESM4::Land>().search(cellIndex); diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index 21bb80cf48..1d70f11e08 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -29,7 +29,7 @@ namespace MWRender osg::ref_ptr<ESMTerrain::LandObject> getLand(ESM::ExteriorCellLocation cellIndex); // FIXME: returning a pointer is probably not compatible with the rest of the codebase - const ESM4::Land *getLandRecord(ESM::ExteriorCellLocation cellIndex) const; + const ESM4::Land* getLandRecord(ESM::ExteriorCellLocation cellIndex) const; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index ff7436e862..7263a95ad9 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -78,7 +78,7 @@ namespace MWRender } osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char /*lod*/, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile /*, int quad*/) { if (activeGrid && !mActiveGrid) return nullptr; diff --git a/apps/openmw/mwrender/objectpaging.hpp b/apps/openmw/mwrender/objectpaging.hpp index 34214bc476..b93de48e47 100644 --- a/apps/openmw/mwrender/objectpaging.hpp +++ b/apps/openmw/mwrender/objectpaging.hpp @@ -24,7 +24,7 @@ namespace MWRender ~ObjectPaging() = default; osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, - bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) override; + bool activeGrid, const osg::Vec3f& viewPoint, bool compile /*, int quad*/) override; osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, unsigned char lod); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index a4e0c52025..5c9570b2ad 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,9 +1,9 @@ #include "terrainstorage.hpp" #include <components/esm3/loadland.hpp> -#include <components/esm4/loadwrld.hpp> #include <components/esm4/loadltex.hpp> #include <components/esm4/loadtxst.hpp> +#include <components/esm4/loadwrld.hpp> #include "../mwbase/environment.hpp" #include "../mwworld/esmstore.hpp" @@ -122,18 +122,18 @@ namespace MWRender return esmStore.get<ESM::LandTexture>().search(index, plugin); } - const ESM4::Land *TerrainStorage::getEsm4Land(ESM::ExteriorCellLocation cellLocation) const + const ESM4::Land* TerrainStorage::getEsm4Land(ESM::ExteriorCellLocation cellLocation) const { return mLandManager->getLandRecord(cellLocation); } - const ESM4::LandTexture *TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const + const ESM4::LandTexture* TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const { const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); return esmStore.get<ESM4::LandTexture>().search(ltexId); } - const ESM4::TextureSet *TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const + const ESM4::TextureSet* TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const { const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); return esmStore.get<ESM4::TextureSet>().search(txstId); diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 85968ded96..621948afcc 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -38,9 +38,9 @@ namespace MWRender LandManager* getLandManager() const; - const ESM4::Land *getEsm4Land(ESM::ExteriorCellLocation cellLocation) const override; - const ESM4::LandTexture *getEsm4LandTexture(ESM::RefId ltexId) const override; - const ESM4::TextureSet *getEsm4TextureSet(ESM::RefId txstId) const override; + const ESM4::Land* getEsm4Land(ESM::ExteriorCellLocation cellLocation) const override; + const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const override; + const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const override; // Intended to be set by RenderingManager. Ideally this should be part of the // construction but this class is initialised in the constructor and we man end up with diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index a1e3f1fc03..8f08ba3a03 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -148,7 +148,7 @@ namespace MWWorld Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::MiscItem>, Store<ESM4::MovableStatic>, Store<ESM4::Npc>, Store<ESM4::Outfit>, Store<ESM4::Potion>, Store<ESM4::Race>, Store<ESM4::Reference>, Store<ESM4::Static>, Store<ESM4::StaticCollection>, Store<ESM4::Terminal>, Store<ESM4::Tree>, - Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::TextureSet> >; + Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::TextureSet>>; private: template <typename T> diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 41bd8ff30e..d4fb72b107 100644 --- a/components/esm4/loadland.cpp +++ b/components/esm4/loadland.cpp @@ -26,10 +26,10 @@ */ #include "loadland.hpp" -#include <cstdint> #include <cassert> -#include <stdexcept> +#include <cstdint> #include <iostream> +#include <stdexcept> #include <components/debug/debuglog.hpp> @@ -77,7 +77,7 @@ void ESM4::Land::load(ESM4::Reader& reader) bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134); bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094 // WARN: below workaround assumes the data directory path has "Fallout" somewhere - //if (esmVer == ESM::VER_094 && reader.getContext().filename.find("allout") != std::string::npos) + // if (esmVer == ESM::VER_094 && reader.getContext().filename.find("allout") != std::string::npos) // isTES5 = false; // FIXME: terrible hack mDataTypes = 0; @@ -131,17 +131,17 @@ void ESM4::Land::load(ESM4::Reader& reader) if (currentAddQuad != -1) { // NOTE: sometimes there are no VTXT following an ATXT - //Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex - //<< " FormId " << ESM::FormId::toString(mFormId) << std::endl; + // Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex + //<< " FormId " << ESM::FormId::toString(mFormId) << std::endl; if (!layer.texture.formId) - layer.texture.formId = getDefaultTexture(isTES4, isFONV, isTES5); + layer.texture.formId = getDefaultTexture(isTES4, isFONV, isTES5); - layer.data.resize(1); // just one spot - layer.data.back().position = 0; // this corner + layer.data.resize(1); // just one spot + layer.data.back().position = 0; // this corner layer.data.back().opacity = 0.f; // transparent assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size() - && "additional texture skipping layer"); + && "additional texture skipping layer"); mTextures[currentAddQuad].layers.push_back(layer); } @@ -172,7 +172,7 @@ void ESM4::Land::load(ESM4::Reader& reader) } assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size() - && "additional texture skipping layer"); + && "additional texture skipping layer"); mTextures[currentAddQuad].layers.push_back(layer); @@ -202,8 +202,8 @@ void ESM4::Land::load(ESM4::Reader& reader) } } - //if (mCell.toUint32() == 0x00005e1f) - //std::cout << "vilverin exterior" << std::endl; + // if (mCell.toUint32() == 0x00005e1f) + // std::cout << "vilverin exterior" << std::endl; if (currentAddQuad != -1) { diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index d4e6f935cf..ff2ccceb54 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -420,18 +420,18 @@ namespace ESMTerrain // differently? std::string Storage::getEsm4TextureName(ESM::RefId id) { - //if (mIsEsm4Ext) - if (const ESM4::LandTexture *ltex = getEsm4LandTexture(id)) + // if (mIsEsm4Ext) + if (const ESM4::LandTexture* ltex = getEsm4LandTexture(id)) { if (ltex->mTextureFile.empty()) // WARN: we assume FO3/FONV/TES5 { - if (const ESM4::TextureSet *txst = getEsm4TextureSet(ltex->mTexture)) + if (const ESM4::TextureSet* txst = getEsm4TextureSet(ltex->mTexture)) { - return "textures\\"+txst->mDiffuse; + return "textures\\" + txst->mDiffuse; } } else - return "textures\\landscape\\"+ltex->mTextureFile; + return "textures\\landscape\\" + ltex->mTextureFile; } // FIXME: add a debug log here @@ -635,7 +635,7 @@ namespace ESMTerrain Terrain::LayerInfo info; info.mParallax = false; info.mSpecular = false; - //info.mIsEsm4 = false; // hint for Terrain::createPasses() + // info.mIsEsm4 = false; // hint for Terrain::createPasses() info.mDiffuseMap = texture; if (mAutoUseNormalMaps) @@ -672,27 +672,27 @@ namespace ESMTerrain return info; } - Terrain::LayerInfo Storage::getLayerInfo(const ESM4::TextureSet *txst) + Terrain::LayerInfo Storage::getLayerInfo(const ESM4::TextureSet* txst) { Terrain::LayerInfo info; info.mDiffuseMap = ""; info.mNormalMap = ""; info.mParallax = false; info.mSpecular = false; - //info.mIsEsm4 = true; // hint for Terrain::createPasses() + // info.mIsEsm4 = true; // hint for Terrain::createPasses() if (txst) { assert(!txst->mDiffuseMap.empty() && "getlayerInfo: empty diffuse map"); - std::string diffuse = "textures\\landscape\\"+txst->mDiffuse; + std::string diffuse = "textures\\landscape\\" + txst->mDiffuse; std::map<std::string, Terrain::LayerInfo>::iterator found = mLayerInfoMap.find(diffuse); if (found != mLayerInfoMap.end()) return found->second; info.mDiffuseMap = diffuse; if (!txst->mNormalMap.empty()) - info.mNormalMap = "textures\\landscape\\"+txst->mNormalMap; + info.mNormalMap = "textures\\landscape\\" + txst->mNormalMap; // FIXME: this flag indicates height info in alpha channel of normal map // but the normal map alpha channel has specular info instead @@ -736,16 +736,16 @@ namespace ESMTerrain { if (mIsEsm4Ext) { - //std::cout << "blendmap scale " - //<< std::to_string(ESM4::Land::sQuadTexturePerSide * chunkSize) << std::endl; - return ESM4::Land::sQuadTexturePerSide;// * chunkSize; + // std::cout << "blendmap scale " + //<< std::to_string(ESM4::Land::sQuadTexturePerSide * chunkSize) << std::endl; + return ESM4::Land::sQuadTexturePerSide; // * chunkSize; } return ESM::Land::LAND_TEXTURE_SIZE * chunkSize; } void Storage::fillQuadVertexBuffers(float size, const osg::Vec2f& center, ESM::RefId worldspace, - osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad) + osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad) { // sampleSize is not used but declared here in order to keep the code as close to // fillVertexBuffers() as possible @@ -756,9 +756,9 @@ namespace ESMTerrain const std::size_t cellSize = static_cast<std::size_t>(ESM::getLandSize(worldspace)); const std::size_t numVerts = static_cast<std::size_t>(size * (cellSize - 1) / sampleSize) + 1; - positions.resize(numVerts*numVerts*3); - normals.resize(numVerts*numVerts*3); - colours.resize(numVerts*numVerts*4); + positions.resize(numVerts * numVerts * 3); + normals.resize(numVerts * numVerts * 3); + colours.resize(numVerts * numVerts * 4); const bool alteration = useAlteration(); // Does nothing by default, override in OpenMW-CS const int landSizeInUnits = ESM::getCellSize(worldspace); @@ -784,7 +784,7 @@ namespace ESMTerrain //std::cout << origin2.x() << ", " << origin2.y() << std::endl; #endif const osg::Vec2f origin = center - osg::Vec2f(size, size) * 0.5f; - //std::cout << origin.x() << ", " << origin.y() << std::endl; + // std::cout << origin.x() << ", " << origin.y() << std::endl; const int startCellX = static_cast<int>(std::floor(origin.x())); const int startCellY = static_cast<int>(std::floor(origin.y())); LandCache cache(startCellX - 1, startCellY - 1, static_cast<std::size_t>(std::ceil(size)) + 2); @@ -884,14 +884,13 @@ namespace ESMTerrain { float height = -2048; if (land && heightData) // validHeightDataExists - height = heightData->getHeights()[col*cellSize + row]; + height = heightData->getHeights()[col * cellSize + row]; // FIXME: I suspect landSizeInUnits should be 2048 const std::size_t vertIndex = vertX * numVerts + vertY; positions[vertIndex] = osg::Vec3f((vertX / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits, - (vertY / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits, - height); + (vertY / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); if (land && normalData) { @@ -914,7 +913,7 @@ namespace ESMTerrain if ((row == 0 || row == cellSize - 1) && (col == 0 || col == cellSize - 1)) averageNormal(normal, cellLocation, col, row, cache); #endif - //assert(normal.z() > 0); // ToddLand triggers this + // assert(normal.z() > 0); // ToddLand triggers this if (normal.z() < 0) normal.z() = 0; @@ -940,7 +939,7 @@ namespace ESMTerrain if (col == cellSize - 1 || row == cellSize - 1) fixColour(color, cellLocation, col, row, cache); #endif -// color.a() = 1; + // color.a() = 1; colours[vertIndex] = color; ++vertX; @@ -950,7 +949,7 @@ namespace ESMTerrain } void Storage::getQuadBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, - std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace, int quad) + std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace, int quad) { // VTXT info indicates texture size is 17x17 - but the cell grid is 33x33 // (cf. TES3 has 65x65 cell) do we discard one row and column or overlap? @@ -989,12 +988,12 @@ namespace ESMTerrain const int startCellX = static_cast<int>(std::floor(origin.x())); const int startCellY = static_cast<int>(std::floor(origin.y())); const int realTextureSize = 17; // FIXME: should be defined in Land record - //const std::size_t blendmapSize = getBlendmapSize(chunkSize, realTextureSize); + // const std::size_t blendmapSize = getBlendmapSize(chunkSize, realTextureSize); const std::size_t blendmapSize = realTextureSize; // FIXME: temp testing - //if (startCellX == 12 && startCellY == 21) - //std::cout << "vilverin exterior" << std::endl; + // if (startCellX == 12 && startCellY == 21) + // std::cout << "vilverin exterior" << std::endl; // FIXME: I don't think ESM4 needs this? #if 0 @@ -1037,7 +1036,7 @@ namespace ESMTerrain std::map<UniqueTextureId, std::size_t> textureIndicesMap; #endif // FIXME: debugging only - //std::cout << "quad " << quad << std::endl; + // std::cout << "quad " << quad << std::endl; // base texture Terrain::LayerInfo info; @@ -1049,10 +1048,10 @@ namespace ESMTerrain info = getLayerInfo(texture); // TES4 // FIXME: debugging only - //std::cout << "base " << info.mDiffuseMap << std::endl; + // std::cout << "base " << info.mDiffuseMap << std::endl; osg::ref_ptr<osg::Image> image(new osg::Image); - image->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize), - 1, GL_ALPHA, GL_UNSIGNED_BYTE); + image->allocateImage( + static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize), 1, GL_ALPHA, GL_UNSIGNED_BYTE); std::memset(image->data(), 255, image->getTotalDataSize()); // fully opaque for base texture blendmaps.push_back(std::move(image)); layerList.push_back(std::move(info)); @@ -1071,11 +1070,11 @@ namespace ESMTerrain layerInfo = getLayerInfo(layerTexture); // TES4 // FIXME: debugging only - //std::cout << "layer " << i << ", " << layerInfo.mDiffuseMap << std::endl; + // std::cout << "layer " << i << ", " << layerInfo.mDiffuseMap << std::endl; osg::ref_ptr<osg::Image> layerImage(new osg::Image); - layerImage->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize), - 1, GL_ALPHA, GL_UNSIGNED_BYTE); + layerImage->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize), 1, + GL_ALPHA, GL_UNSIGNED_BYTE); std::memset(layerImage->data(), 0, layerImage->getTotalDataSize()); blendmaps.push_back(std::move(layerImage)); layerList.push_back(std::move(layerInfo)); @@ -1114,7 +1113,7 @@ namespace ESMTerrain std::size_t y = realTextureSize - 1 - std::floor(position / realTextureSize); std::size_t x = position % realTextureSize; - data[y*realTextureSize + x] = unsigned char(opacityData[j].opacity * 255); + data[y * realTextureSize + x] = unsigned char(opacityData[j].opacity * 255); } } } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 27ec297613..6872e1a039 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -132,9 +132,9 @@ namespace ESMTerrain return data->getHeights()[y * landSize + x]; } - virtual const ESM4::Land *getEsm4Land(ESM::ExteriorCellLocation cellLocation) const = 0; - virtual const ESM4::LandTexture *getEsm4LandTexture(ESM::RefId ltexId) const = 0; - virtual const ESM4::TextureSet *getEsm4TextureSet(ESM::RefId txstId) const = 0; + virtual const ESM4::Land* getEsm4Land(ESM::ExteriorCellLocation cellLocation) const = 0; + virtual const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const = 0; + virtual const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const = 0; void fillQuadVertexBuffers(float size, const osg::Vec2f& center, ESM::RefId worldspace, osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad); @@ -177,7 +177,7 @@ namespace ESMTerrain Terrain::LayerInfo getLayerInfo(const std::string& texture); - Terrain::LayerInfo getLayerInfo(const ESM4::TextureSet *txst); + Terrain::LayerInfo getLayerInfo(const ESM4::TextureSet* txst); }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 4cb4439ed3..9e057adc13 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -165,14 +165,14 @@ namespace Terrain } } -// > openmw.exe!Terrain::ChunkManager::createPasses() Line 188 -// openmw.exe!Terrain::ChunkManager::createCompositeMapGeometry() Line 123 -// openmw.exe!Terrain::ChunkManager::createChunk() Line 275 -// openmw.exe!Terrain::ChunkManager::getChunk() Line 59 -// openmw.exe!Terrain::QuadTreeWorld::loadRenderingNode() Line 397 -// openmw.exe!Terrain::QuadTreeWorld::preload() Line 558 -// openmw.exe!MWWorld::TerrainPreloadItem::doWork() Line 184 -// openmw.exe!SceneUtil::WorkThread::run() Line 135 + // > openmw.exe!Terrain::ChunkManager::createPasses() Line 188 + // openmw.exe!Terrain::ChunkManager::createCompositeMapGeometry() Line 123 + // openmw.exe!Terrain::ChunkManager::createChunk() Line 275 + // openmw.exe!Terrain::ChunkManager::getChunk() Line 59 + // openmw.exe!Terrain::QuadTreeWorld::loadRenderingNode() Line 397 + // openmw.exe!Terrain::QuadTreeWorld::preload() Line 558 + // openmw.exe!MWWorld::TerrainPreloadItem::doWork() Line 184 + // openmw.exe!SceneUtil::WorkThread::run() Line 135 std::vector<osg::ref_ptr<osg::StateSet>> ChunkManager::createPasses( float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap, int quad) { @@ -181,8 +181,8 @@ namespace Terrain if (quad >= 0) // NOTE: quad == -1 has a special meaning of "no quads" { - static_cast<ESMTerrain::Storage*>(mStorage) - ->getQuadBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace, quad); + static_cast<ESMTerrain::Storage*>(mStorage)->getQuadBlendmaps( + chunkSize, chunkCenter, blendmaps, layerList, mWorldspace, quad); } else mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace); @@ -263,8 +263,8 @@ namespace Terrain // in case the Morrowind specific "fixes" causes problems // NOTE: LOD is not supported if (quad >= 0) // NOTE: quad == -1 has a special meaning of "no quads" - static_cast<ESMTerrain::Storage*>(mStorage) - ->fillQuadVertexBuffers(chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors, quad); + static_cast<ESMTerrain::Storage*>(mStorage)->fillQuadVertexBuffers( + chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors, quad); else #endif mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors); diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index fdc74cf8c3..69c35da02f 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -20,7 +20,7 @@ namespace Terrain std::string mNormalMap; bool mParallax; // Height info in normal map alpha channel? bool mSpecular; // Specular info in diffuse map alpha channel? - //bool mIsEsm4; // intended to be used in Terrain::createPasses() + // bool mIsEsm4; // intended to be used in Terrain::createPasses() bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; } }; diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 70db339336..f9861fc4a0 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -262,7 +262,7 @@ namespace Terrain { } osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile /*, int quad*/) { osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 31db0d8878..7bdbae82e7 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -67,7 +67,7 @@ namespace Terrain mWorldspace = worldspace; } virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad = -1*/) + unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile /*, int quad = -1*/) = 0; virtual unsigned int getNodeMask() { return 0; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 8f34599d06..1938e6ed7e 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -9,8 +9,8 @@ #include "heightcull.hpp" #include "storage.hpp" #include "view.hpp" -#include <components/sceneutil/positionattitudetransform.hpp> #include <components/esm/util.hpp> +#include <components/sceneutil/positionattitudetransform.hpp> namespace Terrain { @@ -71,13 +71,13 @@ namespace Terrain float newChunkSize = chunkSize / 2.f; { buildTerrain(group, // top right - newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, newChunkSize / 2.f), 3); + newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, newChunkSize / 2.f), 3); buildTerrain(group, // top left - newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, -newChunkSize / 2.f), 1); + newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, -newChunkSize / 2.f), 1); buildTerrain(group, // bottom right - newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, newChunkSize / 2.f), 2); + newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, newChunkSize / 2.f), 2); buildTerrain(group, // bottom left - newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, -newChunkSize / 2.f), 0); + newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, -newChunkSize / 2.f), 0); } return group; } diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 23a58d34e6..7ca9ffa56f 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -47,7 +47,8 @@ namespace Terrain private: // quad is meant to be used for ESM4 terrain only; if -1 it is ignored, should be [0..3] - osg::ref_ptr<osg::Node> buildTerrain(osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter, int quad = -1); + osg::ref_ptr<osg::Node> buildTerrain( + osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter, int quad = -1); void updateWaterCulling(); // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks From a1486fc975af9a002c4e003fea3df225051ec789 Mon Sep 17 00:00:00 2001 From: fteppe <teppe.florent@hotmail.fr> Date: Mon, 12 Aug 2024 23:11:54 +0200 Subject: [PATCH 4/6] Fix linux compilation --- apps/opencs/view/render/terrainstorage.cpp | 18 ++++++++++++++++++ apps/opencs/view/render/terrainstorage.hpp | 4 ++++ components/esmterrain/storage.cpp | 7 ++----- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index ff9a8e09b1..c1d6f1d2d7 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -170,4 +170,22 @@ namespace CSVRender { return mAlteredHeight[static_cast<unsigned int>(col * ESM::Land::LAND_SIZE + row)]; } + + const ESM4::Land* TerrainStorage::getEsm4Land(ESM::ExteriorCellLocation cellLocation) const + { + assert(false && "ESM4 land not supported by CS"); + return nullptr; + } + + const ESM4::LandTexture* TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const + { + assert(false && "ESM4 land not supported by CS"); + return nullptr; + } + + const ESM4::TextureSet* TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const + { + assert(false && "ESM4 land not supported by CS"); + return nullptr; + } } diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index f7a7f72201..f9df281e43 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -33,6 +33,10 @@ namespace CSVRender float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); float* getAlteredHeight(int inCellX, int inCellY); + const ESM4::Land* getEsm4Land(ESM::ExteriorCellLocation cellLocation) const override; + const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const override; + const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const override; + private: const CSMWorld::Data& mData; std::array<float, ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE> mAlteredHeight; diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index ff2ccceb54..7dd30cc86d 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -683,7 +683,7 @@ namespace ESMTerrain if (txst) { - assert(!txst->mDiffuseMap.empty() && "getlayerInfo: empty diffuse map"); + assert(!txst->mDiffuse.empty() && "getlayerInfo: empty diffuse map"); std::string diffuse = "textures\\landscape\\" + txst->mDiffuse; std::map<std::string, Terrain::LayerInfo>::iterator found = mLayerInfoMap.find(diffuse); @@ -760,7 +760,6 @@ namespace ESMTerrain normals.resize(numVerts * numVerts * 3); colours.resize(numVerts * numVerts * 4); - const bool alteration = useAlteration(); // Does nothing by default, override in OpenMW-CS const int landSizeInUnits = ESM::getCellSize(worldspace); // I think the current code copied from fillVertexBuffers() works fine @@ -793,14 +792,12 @@ namespace ESMTerrain const ESM::LandData* heightData = nullptr; const ESM::LandData* normalData = nullptr; const ESM::LandData* colourData = nullptr; - bool validHeightDataExists = false; if (land != nullptr) { heightData = land->getData(ESM::Land::DATA_VHGT); normalData = land->getData(ESM::Land::DATA_VNML); colourData = land->getData(ESM::Land::DATA_VCLR); - validHeightDataExists = true; } int rowStart = 0; @@ -1113,7 +1110,7 @@ namespace ESMTerrain std::size_t y = realTextureSize - 1 - std::floor(position / realTextureSize); std::size_t x = position % realTextureSize; - data[y * realTextureSize + x] = unsigned char(opacityData[j].opacity * 255); + data[y * realTextureSize + x] = (unsigned char)(opacityData[j].opacity * 255); } } } From d81160cbff30e746771919d0c841a488e1dda234 Mon Sep 17 00:00:00 2001 From: "florent.teppe" <teppe.florent@hotmail.fr> Date: Tue, 13 Aug 2024 16:43:13 +0200 Subject: [PATCH 5/6] Fix prurple texture + shader compilation Gives a default diffuse texture if none is given Fixes a shader compilation bug that made terrain big single texture squares instead of multiple textures blended together. --- components/terrain/chunkmanager.cpp | 3 ++- files/shaders/compatibility/esm4terrain.frag | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 9e057adc13..79fca6752f 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -200,7 +200,8 @@ namespace Terrain textureLayer.mParallax = it->mParallax; textureLayer.mSpecular = it->mSpecular; - textureLayer.mDiffuseMap = mTextureManager->getTexture(it->mDiffuseMap); + textureLayer.mDiffuseMap = mTextureManager->getTexture( + (it->mDiffuseMap.empty() ? "textures\\landscape\\dirt02.dds" : it->mDiffuseMap)); if (!forCompositeMap && !it->mNormalMap.empty()) textureLayer.mNormalMap = mTextureManager->getTexture(it->mNormalMap); diff --git a/files/shaders/compatibility/esm4terrain.frag b/files/shaders/compatibility/esm4terrain.frag index 243e0e9ba1..f40f6bf6a9 100644 --- a/files/shaders/compatibility/esm4terrain.frag +++ b/files/shaders/compatibility/esm4terrain.frag @@ -93,7 +93,7 @@ void main() #endif vec3 diffuseLight, ambientLight, specularLight; doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); - lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + lighting = diffuseTex.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; specular = specularColor * specularLight; #endif From 5df675e8416b200816b0ebad80f671502027b89e Mon Sep 17 00:00:00 2001 From: "florent.teppe" <teppe.florent@hotmail.fr> Date: Thu, 15 Aug 2024 13:39:30 +0200 Subject: [PATCH 6/6] TEST: print the layer index of the blending --- components/terrain/material.cpp | 4 ++ files/shaders/compatibility/esm4terrain.frag | 53 +++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index f7536a08fa..2e19b00cd3 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -315,7 +315,11 @@ namespace Terrain Stereo::shaderStereoDefines(defineMap); if (quad >= 0) + { stateset->setAttributeAndModes(shaderManager.getProgram("esm4terrain", defineMap)); + int LayerIndex = it - layers.begin(); + stateset->addUniform(new osg::Uniform("layerIndex", LayerIndex)); + } else stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap)); stateset->addUniform(UniformCollection::value().mColorMode); diff --git a/files/shaders/compatibility/esm4terrain.frag b/files/shaders/compatibility/esm4terrain.frag index f40f6bf6a9..108b3ffb4f 100644 --- a/files/shaders/compatibility/esm4terrain.frag +++ b/files/shaders/compatibility/esm4terrain.frag @@ -36,6 +36,7 @@ varying vec3 passNormal; uniform vec2 screenRes; uniform float far; +uniform int layerIndex; #include "vertexcolors.glsl" #include "shadows_fragment.glsl" @@ -53,17 +54,57 @@ void main() #endif vec4 diffuseTex = texture2D(diffuseMap, adjustedUV); gl_FragData[0] = vec4(diffuseTex.xyz, 1.0); - -#if @baseLayer + vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; -#endif #if @blendMap vec2 blendMapUV = (gl_TextureMatrix[1] * vec4(uv, 0.0, 1.0)).xy; + //blendMapUV.x = 1.f - blendMapUV.x; #if @baseLayer + gl_FragData[0].a *= diffuseColor.a; #else - gl_FragData[0].a = texture2D(blendMap, blendMapUV).a; + float blendingValue = texture2D(blendMap, blendMapUV).a; + bool debug = false; + if(debug) + { + gl_FragData[0].rgb = vec3(0.f); + if(layerIndex == 1) + { + gl_FragData[0].r = blendingValue; + } + if(layerIndex == 2) + { + gl_FragData[0].g = blendingValue; + } + if(layerIndex == 3) + { + gl_FragData[0].b = blendingValue; + } + if(layerIndex == 4) + { + gl_FragData[0].rg = vec2(blendingValue); + } + if(layerIndex == 5) + { + gl_FragData[0].rb = vec2(blendingValue); + } + if(layerIndex == 6) + { + gl_FragData[0].gb = vec2(blendingValue); + } + if(layerIndex > 6) + { + gl_FragData[0].rgb = vec3(0.f); + } + } + + gl_FragData[0].a = blendingValue; + + + //gl_FragData[0].a = 1.f; + //gl_FragData[0].a = 0.f ; //texture2D(blendMap, blendMapUV).a; + + #endif #endif @@ -93,7 +134,7 @@ void main() #endif vec3 diffuseLight, ambientLight, specularLight; doLighting(passViewPos, viewNormal, shininess, shadowing, diffuseLight, ambientLight, specularLight); - lighting = diffuseTex.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; + lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz; specular = specularColor * specularLight; #endif