mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-08-03 15:27:13 -04:00
ESM4 landscape textures
This commit is contained in:
parent
b160cee0b7
commit
0b5c8271e0
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>;
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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()
|
||||
//{
|
||||
// }
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user