1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-31 01:15:34 +00:00

initial commit applying changes from cc9cii

This commit is contained in:
florent.teppe 2024-08-12 20:32:27 +02:00
parent cbf0471d8a
commit b8192138af
27 changed files with 1011 additions and 95 deletions

View file

@ -322,7 +322,7 @@ namespace MWRender
} }
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod, 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()) if (lod > getMaxLodLevel())
return nullptr; return nullptr;

View file

@ -27,7 +27,7 @@ namespace MWRender
~Groundcover(); ~Groundcover();
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, 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; unsigned int getNodeMask() override;

View file

@ -49,6 +49,13 @@ namespace MWRender
return landObj; 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 void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
{ {
Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats); Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats);

View file

@ -12,6 +12,11 @@ namespace ESM
struct Land; struct Land;
} }
namespace ESM4
{
struct Land;
}
namespace MWRender namespace MWRender
{ {
@ -23,6 +28,9 @@ namespace MWRender
/// @note Will return nullptr if not found. /// @note Will return nullptr if not found.
osg::ref_ptr<ESMTerrain::LandObject> getLand(ESM::ExteriorCellLocation cellIndex); 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; void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
private: private:

View file

@ -78,7 +78,7 @@ namespace MWRender
} }
osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char /*lod*/, 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) if (activeGrid && !mActiveGrid)
return nullptr; return nullptr;

View file

@ -24,7 +24,7 @@ namespace MWRender
~ObjectPaging() = default; ~ObjectPaging() = default;
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, 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, osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid,
const osg::Vec3f& viewPoint, bool compile, unsigned char lod); const osg::Vec3f& viewPoint, bool compile, unsigned char lod);

View file

@ -49,6 +49,7 @@
#include <components/terrain/quadtreeworld.hpp> #include <components/terrain/quadtreeworld.hpp>
#include <components/terrain/terraingrid.hpp> #include <components/terrain/terraingrid.hpp>
#include <components/esm/util.hpp>
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
#include <components/esm4/loadcell.hpp> #include <components/esm4/loadcell.hpp>
@ -490,6 +491,8 @@ namespace MWRender
const bool useTerrainNormalMaps = Settings::shaders().mAutoUseTerrainNormalMaps; const bool useTerrainNormalMaps = Settings::shaders().mAutoUseTerrainNormalMaps;
const bool useTerrainSpecularMaps = Settings::shaders().mAutoUseTerrainSpecularMaps; 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, mTerrainStorage = std::make_unique<TerrainStorage>(mResourceSystem, normalMapPattern, heightMapPattern,
useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps);
@ -798,12 +801,20 @@ namespace MWRender
mWater->removeCell(store); 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) void RenderingManager::enableTerrain(bool enable, ESM::RefId worldspace)
{ {
if (!enable) if (!enable)
mWater->setCullCallback(nullptr); mWater->setCullCallback(nullptr);
else 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); WorldspaceChunkMgr& newChunks = getWorldspaceChunkMgr(worldspace);
if (newChunks.mTerrain.get() != mTerrain) if (newChunks.mTerrain.get() != mTerrain)
{ {
@ -1445,6 +1456,16 @@ namespace MWRender
mStateUpdater->setFogColor(color); 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) RenderingManager::WorldspaceChunkMgr& RenderingManager::getWorldspaceChunkMgr(ESM::RefId worldspace)
{ {
auto existingChunkMgr = mWorldspaceChunks.find(worldspace); auto existingChunkMgr = mWorldspaceChunks.find(worldspace);

View file

@ -2,6 +2,8 @@
#include <components/esm3/loadland.hpp> #include <components/esm3/loadland.hpp>
#include <components/esm4/loadwrld.hpp> #include <components/esm4/loadwrld.hpp>
#include <components/esm4/loadltex.hpp>
#include <components/esm4/loadtxst.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -105,10 +107,36 @@ namespace MWRender
return mLandManager->getLand(cellLocation); 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 std::string* TerrainStorage::getLandTexture(std::uint16_t index, int plugin)
{ {
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
return esmStore.get<ESM::LandTexture>().search(index, plugin); 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);
}
} }

View file

@ -7,6 +7,13 @@
#include <components/resource/resourcesystem.hpp> #include <components/resource/resourcesystem.hpp>
namespace ESM4
{
struct Land;
struct LandTexture;
struct TextureSet;
}
namespace MWRender namespace MWRender
{ {
@ -31,6 +38,15 @@ namespace MWRender
LandManager* getLandManager() const; 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: private:
std::unique_ptr<LandManager> mLandManager; std::unique_ptr<LandManager> mLandManager;

View file

@ -94,6 +94,7 @@ namespace ESM4
struct ItemMod; struct ItemMod;
struct Land; struct Land;
struct LandTexture; struct LandTexture;
struct TextureSet;
struct LevelledCreature; struct LevelledCreature;
struct LevelledItem; struct LevelledItem;
struct LevelledNpc; struct LevelledNpc;
@ -147,7 +148,7 @@ namespace MWWorld
Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::MiscItem>, Store<ESM4::MovableStatic>, 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::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::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: private:
template <typename T> template <typename T>

View file

@ -438,6 +438,10 @@ namespace MWWorld
if (cellVariant.isExterior()) 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); osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellIndex);
const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
const int verts = ESM::getLandSize(worldspace); const int verts = ESM::getLandSize(worldspace);

View file

@ -1332,7 +1332,7 @@ template class MWWorld::TypedDynamicStore<ESM4::HeadPart>;
template class MWWorld::TypedDynamicStore<ESM4::Ingredient>; template class MWWorld::TypedDynamicStore<ESM4::Ingredient>;
template class MWWorld::TypedDynamicStore<ESM4::ItemMod>; template class MWWorld::TypedDynamicStore<ESM4::ItemMod>;
template class MWWorld::TypedDynamicStore<ESM4::Land>; 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::LevelledCreature>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledItem>; template class MWWorld::TypedDynamicStore<ESM4::LevelledItem>;
template class MWWorld::TypedDynamicStore<ESM4::LevelledNpc>; 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::Static>;
template class MWWorld::TypedDynamicStore<ESM4::StaticCollection>; template class MWWorld::TypedDynamicStore<ESM4::StaticCollection>;
template class MWWorld::TypedDynamicStore<ESM4::Terminal>; template class MWWorld::TypedDynamicStore<ESM4::Terminal>;
template class MWWorld::TypedDynamicStore<ESM4::TextureSet>;
template class MWWorld::TypedDynamicStore<ESM4::Tree>; template class MWWorld::TypedDynamicStore<ESM4::Tree>;
template class MWWorld::TypedDynamicStore<ESM4::Weapon>; template class MWWorld::TypedDynamicStore<ESM4::Weapon>;
template class MWWorld::TypedDynamicStore<ESM4::World>; template class MWWorld::TypedDynamicStore<ESM4::World>;

View file

@ -77,6 +77,7 @@
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>
#include <components/esm4/loadterm.hpp> #include <components/esm4/loadterm.hpp>
#include <components/esm4/loadtree.hpp> #include <components/esm4/loadtree.hpp>
#include <components/esm4/loadtxst.hpp>
#include <components/esm4/loadweap.hpp> #include <components/esm4/loadweap.hpp>
#include <components/esm4/loadwrld.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 This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages warranty. In no event will the authors be held liable for any damages
@ -17,7 +17,7 @@
misrepresented as being the original software. misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution. 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 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 from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
@ -27,13 +27,31 @@
#include "loadland.hpp" #include "loadland.hpp"
#include <cstdint> #include <cstdint>
#include <cassert>
#include <stdexcept> #include <stdexcept>
#include <iostream>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include "reader.hpp" #include "reader.hpp"
// #include "writer.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 // overlap north
// //
// 32 // 32
@ -53,13 +71,20 @@ void ESM4::Land::load(ESM4::Reader& reader)
{ {
mId = reader.getFormIdFromHeader(); mId = reader.getFormIdFromHeader();
mFlags = reader.hdr().record.flags; 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; mDataTypes = 0;
mCell = reader.currCell(); mCell = reader.currCell();
TxtLayer layer; TxtLayer layer;
std::int8_t currentAddQuad = -1; // for VTXT following ATXT std::int8_t currentAddQuad = -1; // for VTXT following ATXT
// std::map<FormId, int> uniqueTextures; // FIXME: for temp testing only
while (reader.getSubRecordHeader()) while (reader.getSubRecordHeader())
{ {
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader(); 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 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); reader.get(mHeightMap);
mDataTypes |= LAND_VHGT; mDataTypes |= LAND_VHGT;
break; break;
@ -104,11 +123,6 @@ void ESM4::Land::load(ESM4::Reader& reader)
reader.adjustFormId(base.formId); reader.adjustFormId(base.formId);
mTextures[base.quadrant].base = std::move(base); 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; break;
} }
@ -116,31 +130,27 @@ void ESM4::Land::load(ESM4::Reader& reader)
{ {
if (currentAddQuad != -1) if (currentAddQuad != -1)
{ {
// FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now // NOTE: sometimes there are no VTXT following an ATXT
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex; //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); mTextures[currentAddQuad].layers.push_back(layer);
} }
reader.get(layer.texture); reader.get(layer.texture);
reader.adjustFormId(layer.texture.formId); reader.adjustFormId(layer.texture.formId);
if (layer.texture.quadrant >= 4) if (layer.texture.quadrant >= 4)
throw std::runtime_error("additional texture quadrant index error"); 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; currentAddQuad = layer.texture.quadrant;
break; break;
} }
@ -158,23 +168,16 @@ void ESM4::Land::load(ESM4::Reader& reader)
layer.data.resize(count); layer.data.resize(count);
std::vector<ESM4::Land::VTXT>::iterator it = layer.data.begin(); std::vector<ESM4::Land::VTXT>::iterator it = layer.data.begin();
for (; it != layer.data.end(); ++it) for (; it != layer.data.end(); ++it)
{
reader.get(*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 assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()
// FIXME: Knights.esp doesn't seem to observe this - investigate more && "additional texture skipping layer");
// assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1
//&& "additional texture layer index error"); mTextures[currentAddQuad].layers.push_back(layer);
currentAddQuad = -1; currentAddQuad = -1;
layer.data.clear(); layer.data.clear();
// FIXME: debug only
// std::cout << "VTXT: count " << std::dec << count << std::endl;
break; break;
} }
case ESM::fourCC("VTEX"): // only in Oblivion? 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) 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 " Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex << " quad "
<< static_cast<unsigned>(layer.texture.quadrant); << static_cast<unsigned>(layer.texture.quadrant);
mTextures[currentAddQuad].layers.push_back(layer);
} }
bool missing = false;
for (int i = 0; i < 4; ++i) for (int i = 0; i < 4; ++i)
{ {
// just use some defaults
if (mTextures[i].base.formId == 0) if (mTextures[i].base.formId == 0)
{ mTextures[i].base.formId = getDefaultTexture(isTES4, isFONV, isTES5);
// 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::save(ESM4::Writer& writer) const

View file

@ -12,6 +12,8 @@
#include <components/esm/util.hpp> #include <components/esm/util.hpp>
#include <components/esm3/loadland.hpp> #include <components/esm3/loadland.hpp>
#include <components/esm4/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/resourcehelpers.hpp>
#include <components/misc/strings/algorithm.hpp> #include <components/misc/strings/algorithm.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
@ -40,6 +42,26 @@ namespace ESMTerrain
return { tex, land->getPlugin() }; 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 class LandCache
@ -106,7 +128,8 @@ namespace ESMTerrain
Storage::Storage(const VFS::Manager* vfs, std::string_view normalMapPattern, Storage::Storage(const VFS::Manager* vfs, std::string_view normalMapPattern,
std::string_view normalHeightMapPattern, bool autoUseNormalMaps, std::string_view specularMapPattern, std::string_view normalHeightMapPattern, bool autoUseNormalMaps, std::string_view specularMapPattern,
bool autoUseSpecularMaps) bool autoUseSpecularMaps)
: mVFS(vfs) : mIsEsm4Ext(false)
, mVFS(vfs)
, mNormalMapPattern(normalMapPattern) , mNormalMapPattern(normalMapPattern)
, mNormalHeightMapPattern(normalHeightMapPattern) , mNormalHeightMapPattern(normalHeightMapPattern)
, mAutoUseNormalMaps(autoUseNormalMaps) , mAutoUseNormalMaps(autoUseNormalMaps)
@ -366,6 +389,10 @@ namespace ESMTerrain
std::fill(positions.begin(), positions.end(), osg::Vec3f()); 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 Storage::getTextureName(UniqueTextureId id)
{ {
std::string_view texture = "_land_default.dds"; std::string_view texture = "_land_default.dds";
@ -385,6 +412,37 @@ namespace ESMTerrain
return Misc::ResourceHelpers::correctTexturePath(texture, mVFS); 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, void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace) std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace)
{ {
@ -577,6 +635,7 @@ namespace ESMTerrain
Terrain::LayerInfo info; Terrain::LayerInfo info;
info.mParallax = false; info.mParallax = false;
info.mSpecular = false; info.mSpecular = false;
//info.mIsEsm4 = false; // hint for Terrain::createPasses()
info.mDiffuseMap = texture; info.mDiffuseMap = texture;
if (mAutoUseNormalMaps) if (mAutoUseNormalMaps)
@ -613,6 +672,46 @@ namespace ESMTerrain
return info; 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) float Storage::getCellWorldSize(ESM::RefId worldspace)
{ {
return static_cast<float>(ESM::getCellSize(worldspace)); return static_cast<float>(ESM::getCellSize(worldspace));
@ -623,9 +722,401 @@ namespace ESMTerrain
return ESM::getLandSize(worldspace); 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) 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; 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);
}
}
}
} }

View file

@ -13,6 +13,8 @@
namespace ESM4 namespace ESM4
{ {
struct Land; struct Land;
struct LandTexture;
struct TextureSet;
} }
namespace ESM namespace ESM
@ -130,6 +132,19 @@ namespace ESMTerrain
return data->getHeights()[y * landSize + x]; 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: private:
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
@ -148,6 +163,8 @@ namespace ESMTerrain
std::string getTextureName(UniqueTextureId id); std::string getTextureName(UniqueTextureId id);
std::string getEsm4TextureName(ESM::RefId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
std::mutex mLayerInfoMutex; std::mutex mLayerInfoMutex;
@ -159,6 +176,8 @@ namespace ESMTerrain
bool mAutoUseSpecularMaps; bool mAutoUseSpecularMaps;
Terrain::LayerInfo getLayerInfo(const std::string& texture); Terrain::LayerInfo getLayerInfo(const std::string& texture);
Terrain::LayerInfo getLayerInfo(const ESM4::TextureSet *txst);
}; };
} }

View file

@ -10,6 +10,8 @@
#include <components/sceneutil/lightmanager.hpp> #include <components/sceneutil/lightmanager.hpp>
#include <components/esmterrain/storage.hpp>
#include "compositemaprenderer.hpp" #include "compositemaprenderer.hpp"
#include "material.hpp" #include "material.hpp"
#include "storage.hpp" #include "storage.hpp"
@ -39,6 +41,32 @@ namespace Terrain
mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); 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, 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) 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( 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<LayerInfo> layerList;
std::vector<osg::ref_ptr<osg::Image>> blendmaps; 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(); bool useShaders = mSceneManager->getForceShaders();
if (!mSceneManager->getClampLighting()) if (!mSceneManager->getClampLighting())
@ -183,14 +226,26 @@ namespace Terrain
blendmapTextures.push_back(texture); 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); float blendmapScale = mStorage->getBlendmapScale(chunkSize);
// TODO: not so sure about (i.e. don't understand) using blendmapScale for layerTileSize
return ::Terrain::createPasses( 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, 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); osg::ref_ptr<TerrainDrawable> geometry(new TerrainDrawable);
@ -201,7 +256,18 @@ namespace Terrain
osg::ref_ptr<osg::Vec4ubArray> colors(new osg::Vec4ubArray); osg::ref_ptr<osg::Vec4ubArray> colors(new osg::Vec4ubArray);
colors->setNormalize(true); 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); osg::ref_ptr<osg::VertexBufferObject> vbo(new osg::VertexBufferObject);
positions->setVertexBufferObject(vbo); positions->setVertexBufferObject(vbo);
@ -284,7 +350,9 @@ namespace Terrain
} }
else 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));
} }
} }

View file

@ -81,6 +81,10 @@ namespace Terrain
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, 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) 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 setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; }
void setCompositeMapLevel(float level) { mCompositeMapLevel = level; } void setCompositeMapLevel(float level) { mCompositeMapLevel = level; }
void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; } void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; }
@ -95,8 +99,9 @@ namespace Terrain
void releaseGLObjects(osg::State* state) override; void releaseGLObjects(osg::State* state) override;
private: 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, 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(); osg::ref_ptr<osg::Texture2D> createCompositeMapRTT();
@ -104,7 +109,7 @@ namespace Terrain
float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map); float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map);
std::vector<osg::ref_ptr<osg::StateSet>> createPasses( 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; Terrain::Storage* mStorage;
Resource::SceneManager* mSceneManager; Resource::SceneManager* mSceneManager;

View file

@ -20,6 +20,7 @@ namespace Terrain
std::string mNormalMap; std::string mNormalMap;
bool mParallax; // Height info in normal map alpha channel? bool mParallax; // Height info in normal map alpha channel?
bool mSpecular; // Specular info in diffuse 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; } bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; }
}; };

View file

@ -27,6 +27,8 @@ namespace
return instance.get(blendmapScale); 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 osg::ref_ptr<osg::TexMat>& get(const int blendmapScale)
{ {
const std::lock_guard<std::mutex> lock(mMutex); 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, 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, 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(); auto& shaderManager = sceneManager->getShaderManager();
std::vector<osg::ref_ptr<osg::StateSet>> passes; std::vector<osg::ref_ptr<osg::StateSet>> passes;
@ -243,7 +245,12 @@ namespace Terrain
stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin"); stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin");
if (!firstLayer) 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); stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON);
} }
else else
@ -303,13 +310,20 @@ namespace Terrain
defineMap["parallax"] = parallax ? "1" : "0"; defineMap["parallax"] = parallax ? "1" : "0";
defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0"; defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0";
defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0"; defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0";
if (quad >= 0)
defineMap["baseLayer"] = (firstLayer) ? "1" : "0";
Stereo::shaderStereoDefines(defineMap); 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); stateset->addUniform(UniformCollection::value().mColorMode);
} }
else else
{ {
// FIXME: needs some changes for ESM4
// Add the actual layer texture // Add the actual layer texture
osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap; osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
stateset->setTextureAttributeAndModes(0, tex.get()); stateset->setTextureAttributeAndModes(0, tex.get());

View file

@ -26,7 +26,7 @@ namespace Terrain
std::vector<osg::ref_ptr<osg::StateSet>> createPasses(bool useShaders, Resource::SceneManager* sceneManager, 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, 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);
} }

View file

@ -1,5 +1,7 @@
#include "quadtreeworld.hpp" #include "quadtreeworld.hpp"
#include <cmath> // std::floor
#include <osg/Material> #include <osg/Material>
#include <osg/PolygonMode> #include <osg/PolygonMode>
#include <osg/ShapeDrawable> #include <osg/ShapeDrawable>
@ -260,7 +262,7 @@ namespace Terrain
{ {
} }
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod, 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 }; osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 };
auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size,
@ -289,7 +291,7 @@ namespace Terrain
, mLodFactor(lodFactor) , mLodFactor(lodFactor)
, mVertexLodMod(vertexLodMod) , mVertexLodMod(vertexLodMod)
, mViewDistance(std::numeric_limits<float>::max()) , 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) , mDebugTerrainChunks(debugChunks)
{ {
mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapSize(compMapResolution);
@ -394,11 +396,32 @@ namespace Terrain
for (QuadTreeWorld::ChunkManager* m : mChunkManagers) for (QuadTreeWorld::ChunkManager* m : mChunkManagers)
{ {
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(), osg::ref_ptr<osg::Node> n;
DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid, if (ESM::isEsm4Ext(mWorldspace) && entry.mNode->getSize() == 0.5f && m == mChunkManager.get())
vd->getViewPoint(), compile); {
if (n) osg::Vec2 chunkCenter = entry.mNode->getCenter();
pat->addChild(n); 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; entry.mRenderingNode = pat;
} }
@ -536,6 +559,8 @@ namespace Terrain
return mViewDataMap->createIndependentView(); 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, void QuadTreeWorld::preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i& grid,
std::atomic<bool>& abort, Loading::Reporter& reporter) std::atomic<bool>& abort, Loading::Reporter& reporter)
{ {

View file

@ -67,7 +67,7 @@ namespace Terrain
mWorldspace = worldspace; mWorldspace = worldspace;
} }
virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, 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; = 0;
virtual unsigned int getNodeMask() { return 0; } virtual unsigned int getNodeMask() { return 0; }

View file

@ -10,6 +10,7 @@
#include "storage.hpp" #include "storage.hpp"
#include "view.hpp" #include "view.hpp"
#include <components/sceneutil/positionattitudetransform.hpp> #include <components/sceneutil/positionattitudetransform.hpp>
#include <components/esm/util.hpp>
namespace Terrain namespace Terrain
{ {
@ -51,10 +52,36 @@ namespace Terrain
static_cast<MyView*>(view)->mLoaded = buildTerrain(nullptr, 1.f, center); 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::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 // keep splitting
osg::ref_ptr<osg::Group> group(new osg::Group); osg::ref_ptr<osg::Group> group(new osg::Group);
@ -70,8 +97,10 @@ namespace Terrain
} }
else else
{ {
osg::ref_ptr<osg::Node> node // FIXME: not sure which is worse, this mess or adding a parameter to Terrain::QuadTreeWorld::getChunk()
= mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true); 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) if (!node)
return nullptr; 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) void TerrainGrid::loadCell(int x, int y)
{ {
if (mGrid.find(std::make_pair(x, y)) != mGrid.end()) if (mGrid.find(std::make_pair(x, y)) != mGrid.end())

View file

@ -46,7 +46,8 @@ namespace Terrain
bool isGridEmpty() const { return mGrid.empty(); } bool isGridEmpty() const { return mGrid.empty(); }
private: 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(); void updateWaterCulling();
// split each ESM::Cell into mNumSplits*mNumSplits terrain chunks // split each ESM::Cell into mNumSplits*mNumSplits terrain chunks

View file

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

View file

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