ESM4 landscape textures

This commit is contained in:
Petr Mikheev 2025-07-18 21:53:21 +02:00
parent b160cee0b7
commit 0b5c8271e0
18 changed files with 304 additions and 107 deletions

View File

@ -1,6 +1,8 @@
#include "terrainstorage.hpp"
#include <components/esm3/loadland.hpp>
#include <components/esm4/loadltex.hpp>
#include <components/esm4/loadtxst.hpp>
#include <components/esm4/loadwrld.hpp>
#include "../mwbase/environment.hpp"
@ -111,4 +113,15 @@ namespace MWRender
return esmStore.get<ESM::LandTexture>().search(index, plugin);
}
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);
}
}

View File

@ -24,6 +24,9 @@ namespace MWRender
osg::ref_ptr<const ESMTerrain::LandObject> 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

View File

@ -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<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::Sound>, Store<ESM4::SoundReference>, Store<ESM4::Static>, Store<ESM4::StaticCollection>,
Store<ESM4::Terminal>, Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::World>>;
Store<ESM4::Terminal>, Store<ESM4::TextureSet>, Store<ESM4::Tree>, Store<ESM4::Weapon>, Store<ESM4::World>>;
private:
template <typename T>

View File

@ -1354,6 +1354,7 @@ template class MWWorld::TypedDynamicStore<ESM4::SoundReference>;
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>;

View File

@ -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
};

View File

@ -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<float>::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

View File

@ -1,15 +1,13 @@
#ifndef COMPONENTS_ESM_ESMTERRAIN
#define COMPONENTS_ESM_ESMTERRAIN
#include <array>
#include <cstdint>
#include <memory>
#include <span>
#include <vector>
namespace ESM4
{
struct Land;
}
#include <components/esm4/loadland.hpp>
namespace ESM
{
@ -28,7 +26,6 @@ namespace ESM
std::span<const float> getHeights() const { return mHeights; }
std::span<const std::int8_t> getNormals() const { return mNormals; }
std::span<const std::uint8_t> getColors() const { return mColors; }
std::span<const std::uint16_t> 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<const std::uint16_t> 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<const ESM::LandRecordData> mData;
int mLoadFlags = 0;
@ -49,6 +62,8 @@ namespace ESM
std::span<const std::int8_t> mNormals;
std::span<const std::uint8_t> mColors;
std::span<const std::uint16_t> mTextures;
std::array<ESM4::Land::Texture, 4> mEsm4Textures;
bool mIsEsm4;
};
}

View File

@ -79,6 +79,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>

View File

@ -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 <cassert>
#include <cstdint>
#include <stdexcept>
#include <components/debug/debuglog.hpp>
#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<FormId, int> 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<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
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<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;
}
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<unsigned>(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()
//{
// }

View File

@ -32,6 +32,7 @@
#include <components/esm/defs.hpp>
#include <components/esm/formid.hpp>
#include <components/vfs/pathutil.hpp>
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<ESM::FormId> mIds; // land texture (LTEX) formids
ESM::FormId mCell;
VFS::Path::NormalizedView mDefaultDiffuseMap;
VFS::Path::NormalizedView mDefaultNormalMap;
void load(Reader& reader);

View File

@ -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>
@ -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<Terrain::LayerInfo>& layerList, ESM::RefId worldspace)
{
const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize - 1, chunkSize + 1) * 0.5f;
const int startCellX = static_cast<int>(std::floor(origin.x()));
const int startCellY = static_cast<int>(std::floor(origin.y()));
constexpr int quadsPerCell = 2;
constexpr int quadSize = ESM4::Land::sVertsPerSide / quadsPerCell;
const int quadCount = static_cast<int>(chunkSize * quadsPerCell);
assert(quadCount > 0);
const int blendmapSize = quadCount * quadSize + 1;
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);
std::map<ESM::FormId, std::size_t> 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<osg::Image> 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<int>(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<int>(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<Terrain::LayerInfo>& 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<int>(std::floor(origin.x()));
const int startCellY = static_cast<int>(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<float>(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<int>(2 * ESM4::Land::sQuadTexturePerSide * chunkSize);
else
return static_cast<int>(ESM::Land::LAND_TEXTURE_SIZE * chunkSize);
}
}

View File

@ -4,6 +4,7 @@
#include <cassert>
#include <mutex>
#include <components/terrain/defs.hpp>
#include <components/terrain/storage.hpp>
#include <components/esm/esmterrain.hpp>
@ -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<const LandObject> 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<Terrain::LayerInfo>& layerList, ESM::RefId worldspace);
};
}

View File

@ -5,6 +5,7 @@
#include <osgUtil/IncrementalCompileOperation>
#include <components/esm/util.hpp>
#include <components/resource/objectcache.hpp>
#include <components/resource/scenemanager.hpp>
@ -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<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod,

View File

@ -224,7 +224,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, bool esm4terrain)
{
auto& shaderManager = sceneManager->getShaderManager();
std::vector<osg::ref_ptr<osg::StateSet>> passes;
@ -269,7 +269,8 @@ namespace Terrain
osg::ref_ptr<osg::Texture2D> 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);
}
}

View File

@ -20,14 +20,13 @@ namespace Terrain
{
osg::ref_ptr<osg::Texture2D> mDiffuseMap;
osg::ref_ptr<osg::Texture2D> mNormalMap; // optional
bool mParallax;
bool mSpecular;
bool mParallax = false;
bool mSpecular = false;
};
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, bool esm4terrain = false);
}
#endif

View File

@ -289,11 +289,12 @@ 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)
, 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());

View File

@ -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;
};
}

View File

@ -9,6 +9,8 @@
#include "heightcull.hpp"
#include "storage.hpp"
#include "view.hpp"
#include <components/esm/util.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
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)
{
}