diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index 9776d7e632..0e752701c7 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -1,6 +1,8 @@ #include "terrainstorage.hpp" #include +#include +#include #include #include "../mwbase/environment.hpp" @@ -111,4 +113,15 @@ namespace MWRender return esmStore.get().search(index, plugin); } + const ESM4::LandTexture* TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get().search(ltexId); + } + + const ESM4::TextureSet* TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const + { + const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); + return esmStore.get().search(txstId); + } } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 731f396713..406894fd9b 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -24,6 +24,9 @@ namespace MWRender osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) override; const std::string* getLandTexture(std::uint16_t index, int plugin) override; + const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const override; + const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const override; + bool hasData(ESM::ExteriorCellLocation cellLocation) override; /// Get bounds of the whole terrain in cell units diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 0c37f243e8..fc4ef2713b 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -110,6 +110,7 @@ namespace ESM4 struct Static; struct StaticCollection; struct Terminal; + struct TextureSet; struct Tree; struct Weapon; struct World; @@ -149,7 +150,7 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store, Store>; + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index 80bcdb056a..e4e67c2f3d 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1354,6 +1354,7 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/esm/common.hpp b/components/esm/common.hpp index 90c60ad6ab..7d149a865c 100644 --- a/components/esm/common.hpp +++ b/components/esm/common.hpp @@ -31,6 +31,7 @@ namespace ESM VER_134 = 0x3fab851f, // FONV, GunRunnersArsenal, LonesomeRoad, OldWorldBlues VER_094 = 0x3f70a3d7, // TES5/FO3 VER_170 = 0x3fd9999a, // TES5 + VER_171 = 0x3fdae148, // TES5 VER_095 = 0x3f733333, // FO4 }; diff --git a/components/esm/esmterrain.cpp b/components/esm/esmterrain.cpp index 4bc6768e51..ec824d5112 100644 --- a/components/esm/esmterrain.cpp +++ b/components/esm/esmterrain.cpp @@ -33,6 +33,7 @@ ESM::LandData::LandData(const ESM::Land& land, int loadFlags) , mNormals(mData->mNormals) , mColors(mData->mColours) , mTextures(mData->mTextures) + , mIsEsm4(false) { } @@ -43,9 +44,11 @@ ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/) , mMaxHeight(std::numeric_limits::lowest()) , mSize(Constants::ESM4CellSizeInUnits) , mLandSize(ESM4::Land::sVertsPerSide) + , mPlugin(land.mId.mContentFile) , mNormals(land.mVertNorm) , mColors(land.mVertColr) , mTextures(textures) + , mIsEsm4(true) { float rowOffset = land.mHeightMap.heightOffset; for (int y = 0; y < mLandSize; y++) @@ -69,6 +72,9 @@ ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/) } mHeights = mHeightsData; + + for (int i = 0; i < 4; ++i) + mEsm4Textures[i] = land.mTextures[i]; } namespace ESM diff --git a/components/esm/esmterrain.hpp b/components/esm/esmterrain.hpp index 991df54c8f..b6b62773d5 100644 --- a/components/esm/esmterrain.hpp +++ b/components/esm/esmterrain.hpp @@ -1,15 +1,13 @@ #ifndef COMPONENTS_ESM_ESMTERRAIN #define COMPONENTS_ESM_ESMTERRAIN +#include #include #include #include #include -namespace ESM4 -{ - struct Land; -} +#include namespace ESM { @@ -28,7 +26,6 @@ namespace ESM std::span getHeights() const { return mHeights; } std::span getNormals() const { return mNormals; } std::span getColors() const { return mColors; } - std::span getTextures() const { return mTextures; } float getSize() const { return mSize; } float getMinHeight() const { return mMinHeight; } float getMaxHeight() const { return mMaxHeight; } @@ -36,6 +33,22 @@ namespace ESM int getLoadFlags() const { return mLoadFlags; } int getPlugin() const { return mPlugin; } + bool isEsm4() const { return mIsEsm4; } + + std::span getTextures() const + { + if (mIsEsm4) + throw std::logic_error("ESM3 textures requested from ESM4 LandData"); + return mTextures; + } + + const ESM4::Land::Texture& getEsm4Texture(std::size_t quad) const + { + if (!mIsEsm4) + throw std::logic_error("ESM4 texture requested from ESM3 LandData"); + return mEsm4Textures[quad]; + } + private: std::unique_ptr mData; int mLoadFlags = 0; @@ -49,6 +62,8 @@ namespace ESM std::span mNormals; std::span mColors; std::span mTextures; + std::array mEsm4Textures; + bool mIsEsm4; }; } diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 0b76fab0ff..3b9f8ccd15 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -79,6 +79,7 @@ #include #include #include +#include #include #include diff --git a/components/esm4/loadland.cpp b/components/esm4/loadland.cpp index 53fb1de083..a92b2b5960 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 @@ -26,13 +26,51 @@ */ #include "loadland.hpp" +#include #include #include #include #include "reader.hpp" -// #include "writer.hpp" + +namespace +{ + void assignDefaultTextures(ESM4::Land& land, ESM4::Reader& reader) + { + std::uint32_t esmVer = reader.esmVersion(); + + // Note: in games after TES4 it can be configured in ini file (sDefaultLandDiffuseTexture) + if (!reader.hasFormVersion() && (esmVer == ESM::VER_080 || esmVer == ESM::VER_100)) // TES4 + { + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/terrainhddirt01.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/terrainhddirt01_n.dds"); + } + else if (reader.hasFormVersion() && reader.formVersion() >= 16 + && (esmVer == ESM::VER_094 || esmVer == ESM::VER_170 || esmVer == ESM::VER_171)) // TES5 + { + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/dirt02.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/dirt02_n.dds"); + } + else if (esmVer == ESM::VER_095 || esmVer == ESM::VER_100) // FO4 + { + land.mDefaultDiffuseMap + = VFS::Path::NormalizedView("textures/landscape/ground/commonwealthdefault01_d.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/ground/commonwealthdefault01_n.dds"); + } + else if (esmVer == ESM::VER_094 || esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134) + { // FO3, FONV + land.mDefaultDiffuseMap = VFS::Path::NormalizedView("textures/landscape/dirtwasteland01.dds"); + land.mDefaultNormalMap = VFS::Path::NormalizedView("textures/landscape/dirtwasteland01_n.dds"); + } + else + { + // Nothing especially bad happens if default texture is not set (except of the missing texture of course), + // but we throw an error because this case is unexpected and detection logic needs to be updated. + throw std::runtime_error("ESM4::Land unknown ESM version"); + } + } +} // overlap north // @@ -53,12 +91,16 @@ void ESM4::Land::load(ESM4::Reader& reader) { mId = reader.getFormIdFromHeader(); mFlags = reader.hdr().record.flags; + mDataTypes = 0; mCell = reader.currCell(); TxtLayer layer; std::int8_t currentAddQuad = -1; // for VTXT following ATXT + assignDefaultTextures(*this, reader); - // std::map uniqueTextures; // FIXME: for temp testing only + layer.texture.formId = 0; + for (int i = 0; i < 4; ++i) + mTextures[i].base.formId = 0; while (reader.getSubRecordHeader()) { @@ -78,12 +120,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; @@ -102,13 +138,9 @@ void ESM4::Land::load(ESM4::Reader& reader) if (base.quadrant >= 4) throw std::runtime_error("base texture quadrant index error"); - 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 + if (base.formId != 0) + reader.adjustFormId(base.formId); + mTextures[base.quadrant].base = base; } break; } @@ -116,31 +148,23 @@ 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 + layer.data.resize(1); // just one spot + layer.data.back().position = 0; // this corner + layer.data.back().opacity = 0.f; // transparent + + if (layer.texture.layerIndex != mTextures[currentAddQuad].layers.size()) + throw std::runtime_error("ESM4::LAND additional texture skipping layer"); + mTextures[currentAddQuad].layers.push_back(layer); } + reader.get(layer.texture); - reader.adjustFormId(layer.texture.formId); + if (layer.texture.formId != 0) + 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::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 + throw std::runtime_error("ESM4::LAND additional texture quadrant index error"); + currentAddQuad = layer.texture.quadrant; break; } @@ -156,25 +180,17 @@ void ESM4::Land::load(ESM4::Reader& reader) if (count) { layer.data.resize(count); - std::vector::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; - } + for (ESM4::Land::VTXT& vtxt : layer.data) + reader.get(vtxt); } - 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"); + if (layer.texture.layerIndex != mTextures[currentAddQuad].layers.size()) + throw std::runtime_error("ESM4::LAND 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? @@ -195,44 +211,14 @@ void ESM4::Land::load(ESM4::Reader& reader) reader.skipSubRecordData(); break; default: - throw std::runtime_error("ESM4::LAND::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); + throw std::runtime_error("ESM4::LAND - Unknown subrecord " + ESM::printName(subHdr.typeId)); } } 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(layer.texture.quadrant); - mTextures[currentAddQuad].layers.push_back(layer); } - - bool missing = false; - for (int i = 0; i < 4; ++i) - { - 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; - // } - } - // 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 -//{ -// } - -// void ESM4::Land::blank() -//{ -// } diff --git a/components/esm4/loadland.hpp b/components/esm4/loadland.hpp index ccfa009d80..600a235f1c 100644 --- a/components/esm4/loadland.hpp +++ b/components/esm4/loadland.hpp @@ -32,6 +32,7 @@ #include #include +#include namespace ESM4 { @@ -124,6 +125,8 @@ namespace ESM4 Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right std::vector mIds; // land texture (LTEX) formids ESM::FormId mCell; + VFS::Path::NormalizedView mDefaultDiffuseMap; + VFS::Path::NormalizedView mDefaultNormalMap; void load(Reader& reader); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 35ec814aa2..1a07f6fe0a 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -89,6 +91,8 @@ namespace ESMTerrain LandObject::LandObject(const ESM4::Land& land, int loadFlags) : mData(land, loadFlags) { + mEsm4DefaultLayerInfo.mDiffuseMap = land.mDefaultDiffuseMap; + mEsm4DefaultLayerInfo.mNormalMap = land.mDefaultNormalMap; } LandObject::LandObject(const ESM::Land& land, int loadFlags) @@ -385,9 +389,105 @@ namespace ESMTerrain return Misc::ResourceHelpers::correctTexturePath(texture, mVFS); } + void Storage::getEsm4Blendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList, ESM::RefId worldspace) + { + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize - 1, chunkSize + 1) * 0.5f; + const int startCellX = static_cast(std::floor(origin.x())); + const int startCellY = static_cast(std::floor(origin.y())); + + constexpr int quadsPerCell = 2; + constexpr int quadSize = ESM4::Land::sVertsPerSide / quadsPerCell; + const int quadCount = static_cast(chunkSize * quadsPerCell); + assert(quadCount > 0); + + const int blendmapSize = quadCount * quadSize + 1; + + LandCache cache(startCellX - 1, startCellY - 1, static_cast(std::ceil(chunkSize)) + 2); + std::pair lastCell{ startCellX, startCellY }; + const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache); + + std::map textureIndicesMap; + + auto getOrCreateBlendmap = [&](ESM::FormId texId) -> unsigned char* { + auto found = textureIndicesMap.find(texId); + if (found != textureIndicesMap.end()) + return blendmaps[found->second]->data(); + Terrain::LayerInfo info + = texId.isZeroOrUnset() ? land->getEsm4DefaultLayerInfo() : getLandTextureLayerInfo(texId); + osg::ref_ptr image(new osg::Image); + image->allocateImage(blendmapSize, blendmapSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE); + std::memset(image->data(), 0, image->getTotalDataSize()); + textureIndicesMap.emplace(texId, blendmaps.size()); + blendmaps.push_back(std::move(image)); + layerList.push_back(std::move(info)); + return blendmaps.back()->data(); + }; + + const auto handleSample = [&](const CellSample& sample) { + const std::pair cell{ sample.mCellX, sample.mCellY }; + if (lastCell != cell) + { + land = getLand(ESM::ExteriorCellLocation(sample.mCellX, sample.mCellY, worldspace), cache); + lastCell = cell; + } + if (!land) + return; + const ESM::LandData* ldata = land->getData(0); + if (!ldata) + return; + int quad; + if (sample.mSrcRow == 0) + quad = sample.mSrcCol == 0 ? 0 : 2; + else + quad = sample.mSrcCol == 0 ? 1 : 3; + const ESM4::Land::Texture& ltex = ldata->getEsm4Texture(quad); + + unsigned char* const baseBlendmap = getOrCreateBlendmap(ESM::FormId::fromUint32(ltex.base.formId)); + int starty = (static_cast(sample.mDstCol) - 1) * quadSize; + int startx = sample.mDstRow * quadSize; + for (int y = std::max(0, starty + 1); y <= starty + quadSize && y < blendmapSize; ++y) + { + unsigned char* const row = baseBlendmap + (blendmapSize - y - 1) * blendmapSize; + for (int x = startx; x < startx + quadSize && x < blendmapSize; ++x) + row[x] = 255; + } + + for (const auto& layer : ltex.layers) + { + unsigned char* const layerBlendmap = getOrCreateBlendmap(ESM::FormId::fromUint32(layer.texture.formId)); + for (const ESM4::Land::VTXT& v : layer.data) + { + int y = v.position / (quadSize + 1); + int x = v.position % (quadSize + 1); + if (x == quadSize || startx + x >= blendmapSize || y == 0 || starty + y >= blendmapSize + || starty + y < 0) + { + continue; + } + int index = (blendmapSize - starty - y - 1) * blendmapSize + startx + x; + int delta = std::clamp(static_cast(v.opacity * 255), 0, 255); + baseBlendmap[index] = std::max(0, baseBlendmap[index] - delta); + layerBlendmap[index] = delta; + } + } + }; + + sampleBlendmaps(chunkSize, origin.x(), origin.y(), quadsPerCell, handleSample); + + if (blendmaps.size() == 1) + blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend + } + void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector& layerList, ESM::RefId worldspace) { + if (ESM::isEsm4Ext(worldspace)) + { + getEsm4Blendmaps(chunkSize, chunkCenter, blendmaps, layerList, worldspace); + return; + } + const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize, chunkSize) * 0.5f; const int startCellX = static_cast(std::floor(origin.x())); const int startCellY = static_cast(std::floor(origin.y())); @@ -613,6 +713,47 @@ namespace ESMTerrain return info; } + Terrain::LayerInfo Storage::getLandTextureLayerInfo(ESM::FormId id) + { + if (const ESM4::LandTexture* ltex = getEsm4LandTexture(id)) + { + if (!ltex->mTextureFile.empty()) + return getLayerInfo("textures/landscape/" + ltex->mTextureFile); // TES4 + if (const ESM4::TextureSet* txst = getEsm4TextureSet(ltex->mTexture)) + return getTextureSetLayerInfo(*txst); // TES5 + else + Log(Debug::Warning) << "TextureSet not found: " << ltex->mTexture.toString(); + } + else + Log(Debug::Warning) << "LandTexture not found: " << id.toString(); + return getLayerInfo(""); + } + + Terrain::LayerInfo Storage::getTextureSetLayerInfo(const ESM4::TextureSet& txst) + { + Terrain::LayerInfo info; + + assert(!txst.mDiffuse.empty() && "getlayerInfo: empty diffuse map"); + info.mDiffuseMap = "textures/" + txst.mDiffuse; + + if (!txst.mNormalMap.empty()) + info.mNormalMap = "textures/" + 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 + + return info; + } + float Storage::getCellWorldSize(ESM::RefId worldspace) { return static_cast(ESM::getCellSize(worldspace)); @@ -623,9 +764,12 @@ namespace ESMTerrain return ESM::getLandSize(worldspace); } - int Storage::getBlendmapScale(float chunkSize) + int Storage::getTextureTileCount(float chunkSize, ESM::RefId worldspace) { - return ESM::Land::LAND_TEXTURE_SIZE * chunkSize; + if (ESM::isEsm4Ext(worldspace)) + return static_cast(2 * ESM4::Land::sQuadTexturePerSide * chunkSize); + else + return static_cast(ESM::Land::LAND_TEXTURE_SIZE * chunkSize); } } diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 402f2147ab..be3ba87751 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -13,6 +14,8 @@ namespace ESM4 { struct Land; + struct LandTexture; + struct TextureSet; } namespace ESM @@ -51,9 +54,13 @@ namespace ESMTerrain int getPlugin() const { return mData.getPlugin(); } + const Terrain::LayerInfo& getEsm4DefaultLayerInfo() const { return mEsm4DefaultLayerInfo; } + private: ESM::LandData mData; + Terrain::LayerInfo mEsm4DefaultLayerInfo; + LandObject(const LandObject& copy, const osg::CopyOp& copyOp); }; @@ -74,6 +81,11 @@ namespace ESMTerrain // Not implemented in this class, because we need different Store implementations for game and editor virtual osg::ref_ptr getLand(ESM::ExteriorCellLocation cellLocation) = 0; virtual const std::string* getLandTexture(std::uint16_t index, int plugin) = 0; + + // Not implemented in this class because requires ESMStore + virtual const ESM4::LandTexture* getEsm4LandTexture(ESM::RefId ltexId) const { return nullptr; } + virtual const ESM4::TextureSet* getEsm4TextureSet(ESM::RefId txstId) const { return nullptr; } + /// Get bounds of the whole terrain in cell units void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override = 0; @@ -120,7 +132,7 @@ namespace ESMTerrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 int getCellVertices(ESM::RefId worldspace) override; - int getBlendmapScale(float chunkSize) override; + int getTextureTileCount(float chunkSize, ESM::RefId worldspace) override; float getVertexHeight(const ESM::LandData* data, int x, int y) { @@ -159,6 +171,11 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); + Terrain::LayerInfo getTextureSetLayerInfo(const ESM4::TextureSet& txst); + Terrain::LayerInfo getLandTextureLayerInfo(ESM::FormId id); + + void getEsm4Blendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, + std::vector& layerList, ESM::RefId worldspace); }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 3d7c2b1dfc..242f3e5700 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -5,6 +5,7 @@ #include +#include #include #include @@ -205,10 +206,10 @@ namespace Terrain blendmapTextures.push_back(texture); } - float blendmapScale = mStorage->getBlendmapScale(chunkSize); + float tileCount = mStorage->getTextureTileCount(chunkSize, mWorldspace); return ::Terrain::createPasses( - useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale); + useShaders, mSceneManager, layers, blendmapTextures, tileCount, tileCount, ESM::isEsm4Ext(mWorldspace)); } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index 9c3a7f589d..350b174d69 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -224,7 +224,7 @@ namespace Terrain { std::vector> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector>& blendmaps, - int blendmapScale, float layerTileSize) + int blendmapScale, float layerTileSize, bool esm4terrain) { auto& shaderManager = sceneManager->getShaderManager(); std::vector> passes; @@ -269,7 +269,8 @@ namespace Terrain osg::ref_ptr blendmap = blendmaps.at(blendmapIndex++); stateset->setTextureAttributeAndModes(1, blendmap.get()); - stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + if (!esm4terrain) + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->addUniform(UniformCollection::value().mBlendMap); } @@ -329,7 +330,8 @@ namespace Terrain stateset->setTextureAttributeAndModes(1, blendmap.get()); // This is to map corner vertices directly to the center of a blendmap texel. - stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); + if (!esm4terrain) + stateset->setTextureAttributeAndModes(1, BlendmapTexMat::value(blendmapScale)); stateset->setTextureAttributeAndModes(1, TexEnvCombine::value(), osg::StateAttribute::ON); } } diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 1dbf6d8fc8..fca14d8b3e 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -20,14 +20,13 @@ namespace Terrain { osg::ref_ptr mDiffuseMap; osg::ref_ptr mNormalMap; // optional - bool mParallax; - bool mSpecular; + bool mParallax = false; + bool mSpecular = false; }; std::vector> createPasses(bool useShaders, Resource::SceneManager* sceneManager, const std::vector& layers, const std::vector>& blendmaps, - int blendmapScale, float layerTileSize); - + int blendmapScale, float layerTileSize, bool esm4terrain = false); } #endif diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 99938d7b3a..0d7f1b7762 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -289,11 +289,12 @@ namespace Terrain , mLodFactor(lodFactor) , mVertexLodMod(vertexLodMod) , mViewDistance(std::numeric_limits::max()) - , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 4.f : 1 / 8.f) + , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 2.f : 1 / 8.f) , mDebugTerrainChunks(debugChunks) { mChunkManager->setCompositeMapSize(compMapResolution); - mChunkManager->setCompositeMapLevel(compMapLevel); + mChunkManager->setCompositeMapLevel( + ESM::isEsm4Ext(worldspace) ? compMapLevel * 2 /*because cells are twice smaller*/ : compMapLevel); mChunkManager->setMaxCompositeGeometrySize(maxCompGeometrySize); mChunkManagers.push_back(mChunkManager.get()); diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b1007c13e6..38aee20b8b 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -88,7 +88,8 @@ namespace Terrain /// Get the number of vertices on one side for each cell. Should be (power of two)+1 virtual int getCellVertices(ESM::RefId worldspace) = 0; - virtual int getBlendmapScale(float chunkSize) = 0; + /// Get the number of texture tiles on one side per chunk (chunkSize 1.0 = 1 cell). + virtual int getTextureTileCount(float chunkSize, ESM::RefId worldspace) = 0; }; } diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 88fcaf667d..aff72d2d79 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -9,6 +9,8 @@ #include "heightcull.hpp" #include "storage.hpp" #include "view.hpp" + +#include #include namespace Terrain @@ -27,13 +29,13 @@ namespace Terrain unsigned int borderMask) : Terrain::World( parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask, worldspace, expiryDelay) - , mNumSplits(4) + , mNumSplits(ESM::isEsm4Ext(worldspace) ? 2 : 4) { } TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, ESM::RefId worldspace, unsigned int nodeMask) : Terrain::World(parent, storage, nodeMask, worldspace) - , mNumSplits(4) + , mNumSplits(ESM::isEsm4Ext(worldspace) ? 2 : 4) { }