mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 21:15:36 +00:00
initial commit applying changes from cc9cii
This commit is contained in:
parent
cbf0471d8a
commit
b8192138af
27 changed files with 1011 additions and 95 deletions
|
@ -322,7 +322,7 @@ namespace MWRender
|
|||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> Groundcover::getChunk(float size, const osg::Vec2f& center, unsigned char lod,
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/)
|
||||
{
|
||||
if (lod > getMaxLodLevel())
|
||||
return nullptr;
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace MWRender
|
|||
~Groundcover();
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags,
|
||||
bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) override;
|
||||
|
||||
unsigned int getNodeMask() override;
|
||||
|
||||
|
|
|
@ -49,6 +49,13 @@ namespace MWRender
|
|||
return landObj;
|
||||
}
|
||||
|
||||
const ESM4::Land *LandManager::getLandRecord(ESM::ExteriorCellLocation cellIndex) const
|
||||
{
|
||||
const MWBase::World& world = *MWBase::Environment::get().getWorld();
|
||||
const ESM4::Land* land = world.getStore().get<ESM4::Land>().search(cellIndex);
|
||||
return land;
|
||||
}
|
||||
|
||||
void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
|
||||
{
|
||||
Resource::reportStats("Land", frameNumber, mCache->getStats(), *stats);
|
||||
|
|
|
@ -12,6 +12,11 @@ namespace ESM
|
|||
struct Land;
|
||||
}
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
struct Land;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
|
@ -23,6 +28,9 @@ namespace MWRender
|
|||
/// @note Will return nullptr if not found.
|
||||
osg::ref_ptr<ESMTerrain::LandObject> getLand(ESM::ExteriorCellLocation cellIndex);
|
||||
|
||||
// FIXME: returning a pointer is probably not compatible with the rest of the codebase
|
||||
const ESM4::Land *getLandRecord(ESM::ExteriorCellLocation cellIndex) const;
|
||||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -78,7 +78,7 @@ namespace MWRender
|
|||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> ObjectPaging::getChunk(float size, const osg::Vec2f& center, unsigned char /*lod*/,
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/)
|
||||
{
|
||||
if (activeGrid && !mActiveGrid)
|
||||
return nullptr;
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace MWRender
|
|||
~ObjectPaging() = default;
|
||||
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags,
|
||||
bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/) override;
|
||||
|
||||
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, bool activeGrid,
|
||||
const osg::Vec3f& viewPoint, bool compile, unsigned char lod);
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include <components/terrain/quadtreeworld.hpp>
|
||||
#include <components/terrain/terraingrid.hpp>
|
||||
|
||||
#include <components/esm/util.hpp>
|
||||
#include <components/esm3/loadcell.hpp>
|
||||
#include <components/esm4/loadcell.hpp>
|
||||
|
||||
|
@ -490,6 +491,8 @@ namespace MWRender
|
|||
const bool useTerrainNormalMaps = Settings::shaders().mAutoUseTerrainNormalMaps;
|
||||
const bool useTerrainSpecularMaps = Settings::shaders().mAutoUseTerrainSpecularMaps;
|
||||
|
||||
// NOTE: Maybe we need to swap out this with a different storage type during
|
||||
// enableTerrain() if we are in a foreign (i.e. non-Morrowind) worldspace.
|
||||
mTerrainStorage = std::make_unique<TerrainStorage>(mResourceSystem, normalMapPattern, heightMapPattern,
|
||||
useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps);
|
||||
|
||||
|
@ -798,12 +801,20 @@ namespace MWRender
|
|||
mWater->removeCell(store);
|
||||
}
|
||||
|
||||
// NOTE: For cc9cii's fork, this is where the terrain storage type is decided when we know
|
||||
// the worldspace. But in this implementation the decision is made in the
|
||||
// constructor.
|
||||
void RenderingManager::enableTerrain(bool enable, ESM::RefId worldspace)
|
||||
{
|
||||
if (!enable)
|
||||
mWater->setCullCallback(nullptr);
|
||||
else
|
||||
{
|
||||
// need to set our ESM4 state in mTerrainStorage here to change some behaviours in
|
||||
// ESMTerrain::Storage
|
||||
if (ESM::isEsm4Ext(worldspace))
|
||||
mTerrainStorage->setIsEsm4Ext(true);
|
||||
|
||||
WorldspaceChunkMgr& newChunks = getWorldspaceChunkMgr(worldspace);
|
||||
if (newChunks.mTerrain.get() != mTerrain)
|
||||
{
|
||||
|
@ -1445,6 +1456,16 @@ namespace MWRender
|
|||
mStateUpdater->setFogColor(color);
|
||||
}
|
||||
|
||||
// NOTE: mTerrainStorage is a unique_ptr to MWRender::TerrainStorage which is a child
|
||||
// class of ESMTerrain::Storage. This makes switching out ESMTerrain::Storage with
|
||||
// another class impossible unless we decide to accept a man-in-the-middle type mess.
|
||||
//
|
||||
// mTerrainStorage is initialised in the constructor. It's probably not a good idea
|
||||
// to chnage that when we switch to another worldspace so we'll need a different
|
||||
// solution.
|
||||
//
|
||||
// So maybe we have to accept the "least bad option" and have the complications
|
||||
// within MWRender::TerrainStorage e.g. set a state variable (see enableTerrain())
|
||||
RenderingManager::WorldspaceChunkMgr& RenderingManager::getWorldspaceChunkMgr(ESM::RefId worldspace)
|
||||
{
|
||||
auto existingChunkMgr = mWorldspaceChunks.find(worldspace);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <components/esm3/loadland.hpp>
|
||||
#include <components/esm4/loadwrld.hpp>
|
||||
#include <components/esm4/loadltex.hpp>
|
||||
#include <components/esm4/loadtxst.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
@ -105,10 +107,36 @@ namespace MWRender
|
|||
return mLandManager->getLand(cellLocation);
|
||||
}
|
||||
|
||||
// NOTE: We need to return different land texture if mIsEsm4Ext is set.
|
||||
// But there isn't just one texture for a given land, so the method name
|
||||
// is misleading and may cause confusion for future maintainers.
|
||||
//
|
||||
// It's a pity we have to use pointers to string here. FormId would have been
|
||||
// more than adequate.
|
||||
//
|
||||
// Update: Decided to add new methods instead. See getEsm4Land(), getEsm4LandTexture()
|
||||
// and getEsm4TextureSet().
|
||||
const std::string* TerrainStorage::getLandTexture(std::uint16_t index, int plugin)
|
||||
{
|
||||
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
|
||||
return esmStore.get<ESM::LandTexture>().search(index, plugin);
|
||||
}
|
||||
|
||||
const ESM4::Land *TerrainStorage::getEsm4Land(ESM::ExteriorCellLocation cellLocation) const
|
||||
{
|
||||
return mLandManager->getLandRecord(cellLocation);
|
||||
}
|
||||
|
||||
const ESM4::LandTexture *TerrainStorage::getEsm4LandTexture(ESM::RefId ltexId) const
|
||||
{
|
||||
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
|
||||
return esmStore.get<ESM4::LandTexture>().search(ltexId);
|
||||
}
|
||||
|
||||
const ESM4::TextureSet *TerrainStorage::getEsm4TextureSet(ESM::RefId txstId) const
|
||||
{
|
||||
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
|
||||
return esmStore.get<ESM4::TextureSet>().search(txstId);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,6 +7,13 @@
|
|||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
namespace ESM4
|
||||
{
|
||||
struct Land;
|
||||
struct LandTexture;
|
||||
struct TextureSet;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
|
@ -31,6 +38,15 @@ namespace MWRender
|
|||
|
||||
LandManager* getLandManager() const;
|
||||
|
||||
const ESM4::Land *getEsm4Land(ESM::ExteriorCellLocation cellLocation) const override;
|
||||
const ESM4::LandTexture *getEsm4LandTexture(ESM::RefId ltexId) const override;
|
||||
const ESM4::TextureSet *getEsm4TextureSet(ESM::RefId txstId) const override;
|
||||
|
||||
// Intended to be set by RenderingManager. Ideally this should be part of the
|
||||
// construction but this class is initialised in the constructor and we man end up with
|
||||
// a different terrain during RenderingManager::enableTerrain().
|
||||
void setIsEsm4Ext(bool state) { mIsEsm4Ext = state; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<LandManager> mLandManager;
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ namespace ESM4
|
|||
struct ItemMod;
|
||||
struct Land;
|
||||
struct LandTexture;
|
||||
struct TextureSet;
|
||||
struct LevelledCreature;
|
||||
struct LevelledItem;
|
||||
struct LevelledNpc;
|
||||
|
@ -147,7 +148,7 @@ namespace MWWorld
|
|||
Store<ESM4::LevelledNpc>, Store<ESM4::Light>, Store<ESM4::MiscItem>, Store<ESM4::MovableStatic>,
|
||||
Store<ESM4::Npc>, Store<ESM4::Outfit>, Store<ESM4::Potion>, Store<ESM4::Race>, Store<ESM4::Reference>,
|
||||
Store<ESM4::Static>, Store<ESM4::StaticCollection>, Store<ESM4::Terminal>, Store<ESM4::Tree>,
|
||||
Store<ESM4::Weapon>, Store<ESM4::World>>;
|
||||
Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::TextureSet> >;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
|
|
|
@ -438,6 +438,10 @@ namespace MWWorld
|
|||
|
||||
if (cellVariant.isExterior())
|
||||
{
|
||||
// NOTE: LandObject may be of type ESM4
|
||||
// NOTE: It's probably a very bad idea to keep the pointer returned from
|
||||
// getLandManager() since it may change if the worldspace type changes from
|
||||
// TES3 to TES4 and vice-versa (depends on the final implementation).
|
||||
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellIndex);
|
||||
const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
|
||||
const int verts = ESM::getLandSize(worldspace);
|
||||
|
|
|
@ -1332,7 +1332,7 @@ template class MWWorld::TypedDynamicStore<ESM4::HeadPart>;
|
|||
template class MWWorld::TypedDynamicStore<ESM4::Ingredient>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::ItemMod>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Land>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::LandTexture>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::LandTexture>; // FIXME: maybe we need some special handling?
|
||||
template class MWWorld::TypedDynamicStore<ESM4::LevelledCreature>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::LevelledItem>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::LevelledNpc>;
|
||||
|
@ -1346,6 +1346,7 @@ template class MWWorld::TypedDynamicStore<ESM4::Race>;
|
|||
template class MWWorld::TypedDynamicStore<ESM4::Static>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::StaticCollection>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Terminal>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::TextureSet>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Tree>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::Weapon>;
|
||||
template class MWWorld::TypedDynamicStore<ESM4::World>;
|
||||
|
|
|
@ -77,6 +77,7 @@
|
|||
#include <components/esm4/loadstat.hpp>
|
||||
#include <components/esm4/loadterm.hpp>
|
||||
#include <components/esm4/loadtree.hpp>
|
||||
#include <components/esm4/loadtxst.hpp>
|
||||
#include <components/esm4/loadweap.hpp>
|
||||
#include <components/esm4/loadwrld.hpp>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2015-2016, 2018, 2020-2021 cc9cii
|
||||
Copyright (C) 2015 - 2024 cc9cii
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
|
@ -17,7 +17,7 @@
|
|||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
cc9cii cc9c@iinet.net.au
|
||||
cc9cii cc9cii@hotmail.com
|
||||
|
||||
Much of the information on the data structures are based on the information
|
||||
from Tes4Mod:Mod_File_Format and Tes5Mod:File_Formats but also refined by
|
||||
|
@ -27,13 +27,31 @@
|
|||
#include "loadland.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "reader.hpp"
|
||||
// #include "writer.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
std::uint32_t getDefaultTexture(bool isTES4, bool isFONV, bool isTES5)
|
||||
{
|
||||
// WARN: guessed for FO3/FONV (might be Dirt02)
|
||||
if (isTES4)
|
||||
return 0x000008C0; // TerrainHDDirt01.dds (LTEX)
|
||||
else if (isFONV)
|
||||
return 0x00000A0D; // Landscape\Dirt01.dds (TXST 0x00004453)
|
||||
else if (isTES5)
|
||||
return 0x00000C16; // Landscape\Dirt02.dds (TXST 0x00000C0F)
|
||||
else // FO3
|
||||
return 0x00000A0D; // Landscape\Dirt01.dds (prob. same as FONV)
|
||||
}
|
||||
}
|
||||
|
||||
// overlap north
|
||||
//
|
||||
// 32
|
||||
|
@ -53,13 +71,20 @@ void ESM4::Land::load(ESM4::Reader& reader)
|
|||
{
|
||||
mId = reader.getFormIdFromHeader();
|
||||
mFlags = reader.hdr().record.flags;
|
||||
|
||||
std::uint32_t esmVer = reader.esmVersion();
|
||||
bool isTES4 = (esmVer == ESM::VER_080 || esmVer == ESM::VER_100);
|
||||
bool isFONV = (esmVer == ESM::VER_132 || esmVer == ESM::VER_133 || esmVer == ESM::VER_134);
|
||||
bool isTES5 = (esmVer == ESM::VER_094 || esmVer == ESM::VER_170); // WARN: FO3 is also VER_094
|
||||
// WARN: below workaround assumes the data directory path has "Fallout" somewhere
|
||||
if (esmVer == ESM4::VER_094 && reader.getContext().filename.find("allout") != std::string::npos)
|
||||
isTES5 = false; // FIXME: terrible hack
|
||||
|
||||
mDataTypes = 0;
|
||||
mCell = reader.currCell();
|
||||
TxtLayer layer;
|
||||
std::int8_t currentAddQuad = -1; // for VTXT following ATXT
|
||||
|
||||
// std::map<FormId, int> uniqueTextures; // FIXME: for temp testing only
|
||||
|
||||
while (reader.getSubRecordHeader())
|
||||
{
|
||||
const ESM4::SubRecordHeader& subHdr = reader.subRecordHeader();
|
||||
|
@ -78,12 +103,6 @@ void ESM4::Land::load(ESM4::Reader& reader)
|
|||
}
|
||||
case ESM::fourCC("VHGT"): // vertex height gradient, 4+33x33+3 = 4+1089+3 = 1096
|
||||
{
|
||||
#if 0
|
||||
reader.get(mHeightMap.heightOffset);
|
||||
reader.get(mHeightMap.gradientData);
|
||||
reader.get(mHeightMap.unknown1);
|
||||
reader.get(mHeightMap.unknown2);
|
||||
#endif
|
||||
reader.get(mHeightMap);
|
||||
mDataTypes |= LAND_VHGT;
|
||||
break;
|
||||
|
@ -104,11 +123,6 @@ void ESM4::Land::load(ESM4::Reader& reader)
|
|||
|
||||
reader.adjustFormId(base.formId);
|
||||
mTextures[base.quadrant].base = std::move(base);
|
||||
#if 0
|
||||
std::cout << "Base Texture formid: 0x"
|
||||
<< std::hex << mTextures[base.quadrant].base.formId
|
||||
<< ", quad " << std::dec << (int)base.quadrant << std::endl;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -116,31 +130,27 @@ void ESM4::Land::load(ESM4::Reader& reader)
|
|||
{
|
||||
if (currentAddQuad != -1)
|
||||
{
|
||||
// FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now
|
||||
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex;
|
||||
// NOTE: sometimes there are no VTXT following an ATXT
|
||||
//Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex
|
||||
//<< " FormId " << ESM::FormId::toString(mFormId) << std::endl;
|
||||
if (!layer.texture.formId)
|
||||
layer.texture.formId = getDefaultTexture(isTES4, isFONV, isTES5);
|
||||
|
||||
layer.data.resize(1); // just one spot
|
||||
layer.data.back().position = 0; // this corner
|
||||
layer.data.back().opacity = 0.f; // transparent
|
||||
|
||||
assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()
|
||||
&& "additional texture skipping layer");
|
||||
|
||||
mTextures[currentAddQuad].layers.push_back(layer);
|
||||
}
|
||||
|
||||
reader.get(layer.texture);
|
||||
reader.adjustFormId(layer.texture.formId);
|
||||
if (layer.texture.quadrant >= 4)
|
||||
throw std::runtime_error("additional texture quadrant index error");
|
||||
#if 0
|
||||
FormId txt = layer.texture.formId;
|
||||
std::map<FormId, int>::iterator lb = uniqueTextures.lower_bound(txt);
|
||||
if (lb != uniqueTextures.end() && !(uniqueTextures.key_comp()(txt, lb->first)))
|
||||
{
|
||||
lb->second += 1;
|
||||
}
|
||||
else
|
||||
uniqueTextures.insert(lb, std::make_pair(txt, 1));
|
||||
#endif
|
||||
#if 0
|
||||
std::cout << "Additional Texture formId: 0x"
|
||||
<< std::hex << layer.texture.formId
|
||||
<< ", quad " << std::dec << (int)layer.texture.quadrant << std::endl;
|
||||
std::cout << "Additional Texture layer: "
|
||||
<< std::dec << (int)layer.texture.layerIndex << std::endl;
|
||||
#endif
|
||||
|
||||
currentAddQuad = layer.texture.quadrant;
|
||||
break;
|
||||
}
|
||||
|
@ -158,23 +168,16 @@ void ESM4::Land::load(ESM4::Reader& reader)
|
|||
layer.data.resize(count);
|
||||
std::vector<ESM4::Land::VTXT>::iterator it = layer.data.begin();
|
||||
for (; it != layer.data.end(); ++it)
|
||||
{
|
||||
reader.get(*it);
|
||||
// FIXME: debug only
|
||||
// std::cout << "pos: " << std::dec << (int)(*it).position << std::endl;
|
||||
}
|
||||
}
|
||||
mTextures[currentAddQuad].layers.push_back(layer);
|
||||
|
||||
// Assumed that the layers are added in the correct sequence
|
||||
// FIXME: Knights.esp doesn't seem to observe this - investigate more
|
||||
// assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()-1
|
||||
//&& "additional texture layer index error");
|
||||
assert(layer.texture.layerIndex == mTextures[currentAddQuad].layers.size()
|
||||
&& "additional texture skipping layer");
|
||||
|
||||
mTextures[currentAddQuad].layers.push_back(layer);
|
||||
|
||||
currentAddQuad = -1;
|
||||
layer.data.clear();
|
||||
// FIXME: debug only
|
||||
// std::cout << "VTXT: count " << std::dec << count << std::endl;
|
||||
break;
|
||||
}
|
||||
case ESM::fourCC("VTEX"): // only in Oblivion?
|
||||
|
@ -199,34 +202,22 @@ void ESM4::Land::load(ESM4::Reader& reader)
|
|||
}
|
||||
}
|
||||
|
||||
//if (mCell.toUint32() == 0x00005e1f)
|
||||
//std::cout << "vilverin exterior" << std::endl;
|
||||
|
||||
if (currentAddQuad != -1)
|
||||
{
|
||||
// FIXME: not sure if it happens here as well
|
||||
// not sure if it happens here as well, if so just ignore
|
||||
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex << " quad "
|
||||
<< static_cast<unsigned>(layer.texture.quadrant);
|
||||
mTextures[currentAddQuad].layers.push_back(layer);
|
||||
}
|
||||
|
||||
bool missing = false;
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
// just use some defaults
|
||||
if (mTextures[i].base.formId == 0)
|
||||
{
|
||||
// std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " missing base, quad " << i << std::endl;
|
||||
// std::cout << "layers " << mTextures[i].layers.size() << std::endl;
|
||||
// NOTE: can't set the default here since FO3/FONV may have different defaults
|
||||
// mTextures[i].base.formId = 0x000008C0; // TerrainHDDirt01.dds
|
||||
missing = true;
|
||||
}
|
||||
// else
|
||||
//{
|
||||
// std::cout << "ESM4::LAND " << ESM4::formIdToString(mFormId) << " base, quad " << i << std::endl;
|
||||
// std::cout << "layers " << mTextures[i].layers.size() << std::endl;
|
||||
// }
|
||||
mTextures[i].base.formId = getDefaultTexture(isTES4, isFONV, isTES5);
|
||||
}
|
||||
// at least one of the quadrants do not have a base texture, return without setting the flag
|
||||
if (!missing)
|
||||
mDataTypes |= LAND_VTEX;
|
||||
}
|
||||
|
||||
// void ESM4::Land::save(ESM4::Writer& writer) const
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include <components/esm/util.hpp>
|
||||
#include <components/esm3/loadland.hpp>
|
||||
#include <components/esm4/loadland.hpp>
|
||||
#include <components/esm4/loadltex.hpp>
|
||||
#include <components/esm4/loadtxst.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/misc/strings/algorithm.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
@ -40,6 +42,26 @@ namespace ESMTerrain
|
|||
|
||||
return { tex, land->getPlugin() };
|
||||
}
|
||||
#if 0
|
||||
UniqueTextureId getQuadTextureIdAt(const ESM4::Land* land, std::size_t x, std::size_t y)
|
||||
{
|
||||
assert(x < 17);
|
||||
assert(y < 17);
|
||||
|
||||
if (land == nullptr)
|
||||
return { 0, 0 };
|
||||
|
||||
const ESM::LandData* data = land->getData(ESM::Land::DATA_VTEX);
|
||||
if (data == nullptr)
|
||||
return { 0, 0 };
|
||||
|
||||
const std::uint16_t tex = data->getTextures()[y * ESM::Land::LAND_TEXTURE_SIZE + x];
|
||||
if (tex == 0)
|
||||
return { 0, 0 }; // vtex 0 is always the base texture, regardless of plugin
|
||||
|
||||
return { tex, land->getPlugin() };
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
class LandCache
|
||||
|
@ -106,7 +128,8 @@ namespace ESMTerrain
|
|||
Storage::Storage(const VFS::Manager* vfs, std::string_view normalMapPattern,
|
||||
std::string_view normalHeightMapPattern, bool autoUseNormalMaps, std::string_view specularMapPattern,
|
||||
bool autoUseSpecularMaps)
|
||||
: mVFS(vfs)
|
||||
: mIsEsm4Ext(false)
|
||||
, mVFS(vfs)
|
||||
, mNormalMapPattern(normalMapPattern)
|
||||
, mNormalHeightMapPattern(normalHeightMapPattern)
|
||||
, mAutoUseNormalMaps(autoUseNormalMaps)
|
||||
|
@ -366,6 +389,10 @@ namespace ESMTerrain
|
|||
std::fill(positions.begin(), positions.end(), osg::Vec3f());
|
||||
}
|
||||
|
||||
// NOTE: getLandTexture() is implemented by our child class. Also note that ESM4 doesn't
|
||||
// want to call correctTexturePath(). We need a way of figuring out that we are in
|
||||
// ESM4 worldspace, either via a parameter or via a state kept as a member to Storage
|
||||
// or TerrainStorage (i.e. our child class).
|
||||
std::string Storage::getTextureName(UniqueTextureId id)
|
||||
{
|
||||
std::string_view texture = "_land_default.dds";
|
||||
|
@ -385,6 +412,37 @@ namespace ESMTerrain
|
|||
return Misc::ResourceHelpers::correctTexturePath(texture, mVFS);
|
||||
}
|
||||
|
||||
// FIXME: for FO3/FONV/TES5 this is rather inefficient since the TextureSet indicates
|
||||
// whether normal map exists, etc, saving us the need to do any searching
|
||||
// in getLayerInfo()
|
||||
//
|
||||
// maybe if ltex->mTextureFile is empty simply return a null string and process
|
||||
// differently?
|
||||
std::string Storage::getEsm4TextureName(ESM::RefId id)
|
||||
{
|
||||
//if (mIsEsm4Ext)
|
||||
if (const ESM4::LandTexture *ltex = getEsm4LandTexture(id))
|
||||
{
|
||||
if (ltex->mTextureFile.empty()) // WARN: we assume FO3/FONV/TES5
|
||||
{
|
||||
if (const ESM4::TextureSet *txst = getEsm4TextureSet(ltex->mTexture))
|
||||
{
|
||||
return "textures\\"+txst->mDiffuse;
|
||||
}
|
||||
}
|
||||
else
|
||||
return "textures\\landscape\\"+ltex->mTextureFile;
|
||||
}
|
||||
|
||||
// FIXME: add a debug log here
|
||||
return "";
|
||||
}
|
||||
|
||||
// FIXME: May need some changes here to support ESM4 terrain. Not sure how to deal with many
|
||||
// chunks (i.e. 4 ESM4 quads). Maybe we just go with the flow here, but do
|
||||
// things 4 times as much?
|
||||
//
|
||||
// For now decided to create another method instead (getQuadBlendmaps).
|
||||
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace)
|
||||
{
|
||||
|
@ -577,6 +635,7 @@ namespace ESMTerrain
|
|||
Terrain::LayerInfo info;
|
||||
info.mParallax = false;
|
||||
info.mSpecular = false;
|
||||
//info.mIsEsm4 = false; // hint for Terrain::createPasses()
|
||||
info.mDiffuseMap = texture;
|
||||
|
||||
if (mAutoUseNormalMaps)
|
||||
|
@ -613,6 +672,46 @@ namespace ESMTerrain
|
|||
return info;
|
||||
}
|
||||
|
||||
Terrain::LayerInfo Storage::getLayerInfo(const ESM4::TextureSet *txst)
|
||||
{
|
||||
Terrain::LayerInfo info;
|
||||
info.mDiffuseMap = "";
|
||||
info.mNormalMap = "";
|
||||
info.mParallax = false;
|
||||
info.mSpecular = false;
|
||||
//info.mIsEsm4 = true; // hint for Terrain::createPasses()
|
||||
|
||||
if (txst)
|
||||
{
|
||||
assert(!txst->mDiffuseMap.empty() && "getlayerInfo: empty diffuse map");
|
||||
|
||||
std::string diffuse = "textures\\landscape\\"+txst->mDiffuse;
|
||||
std::map<std::string, Terrain::LayerInfo>::iterator found = mLayerInfoMap.find(diffuse);
|
||||
if (found != mLayerInfoMap.end())
|
||||
return found->second;
|
||||
|
||||
info.mDiffuseMap = diffuse;
|
||||
if (!txst->mNormalMap.empty())
|
||||
info.mNormalMap = "textures\\landscape\\"+txst->mNormalMap;
|
||||
|
||||
// FIXME: this flag indicates height info in alpha channel of normal map
|
||||
// but the normal map alpha channel has specular info instead
|
||||
// (probably needs some flag in the terrain shader to fix)
|
||||
info.mParallax = false;
|
||||
// FIXME: this flag indicates specular info in alpha channel of diffuse
|
||||
// but the diffuse alpha channel has transparency data instead
|
||||
// (probably needs some flag in the terrain shader to fix)
|
||||
info.mSpecular = false;
|
||||
|
||||
// FIXME: should support other features of ESM4::TextureSet
|
||||
// probably need corresponding support in the terrain shader
|
||||
|
||||
mLayerInfoMap[diffuse] = info;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
float Storage::getCellWorldSize(ESM::RefId worldspace)
|
||||
{
|
||||
return static_cast<float>(ESM::getCellSize(worldspace));
|
||||
|
@ -623,9 +722,401 @@ namespace ESMTerrain
|
|||
return ESM::getLandSize(worldspace);
|
||||
}
|
||||
|
||||
// NOTE: For now we are only conident when chunkSize is 1. Needs more testing to see
|
||||
// if this will work with different chunkSize values.
|
||||
//
|
||||
// This is called by ChunkManager::createPasses() which then calls
|
||||
// Terrain::createPasses() which ultimately calls BlendmapTexMat::value().
|
||||
// I suspect that is where UV mapping is done (just a guess; LayerTexMat
|
||||
// may need to be looked at as well).
|
||||
//
|
||||
// WARN: the value sQuadTexturePerSide was determined empirically for TES4 only
|
||||
// FO3/FONV/TES5 may well have a different value - needs testing
|
||||
int Storage::getBlendmapScale(float chunkSize)
|
||||
{
|
||||
if (mIsEsm4Ext)
|
||||
{
|
||||
//std::cout << "blendmap scale "
|
||||
//<< std::to_string(ESM4::Land::sQuadTexturePerSide * chunkSize) << std::endl;
|
||||
return ESM4::Land::sQuadTexturePerSide;// * chunkSize;
|
||||
}
|
||||
|
||||
return ESM::Land::LAND_TEXTURE_SIZE * chunkSize;
|
||||
}
|
||||
|
||||
void Storage::fillQuadVertexBuffers(float size, const osg::Vec2f& center, ESM::RefId worldspace,
|
||||
osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad)
|
||||
{
|
||||
// sampleSize is not used but declared here in order to keep the code as close to
|
||||
// fillVertexBuffers() as possible
|
||||
const std::size_t sampleSize = 1;
|
||||
|
||||
// DEBUG NOTES: cellSize should be 33 for ESM4
|
||||
// numVerts should be 17 for ESM4
|
||||
const std::size_t cellSize = static_cast<std::size_t>(ESM::getLandSize(worldspace));
|
||||
const std::size_t numVerts = static_cast<std::size_t>(size * (cellSize - 1) / sampleSize) + 1;
|
||||
|
||||
positions.resize(numVerts*numVerts*3);
|
||||
normals.resize(numVerts*numVerts*3);
|
||||
colours.resize(numVerts*numVerts*4);
|
||||
|
||||
const bool alteration = useAlteration(); // Does nothing by default, override in OpenMW-CS
|
||||
const int landSizeInUnits = ESM::getCellSize(worldspace);
|
||||
|
||||
// I think the current code copied from fillVertexBuffers() works fine
|
||||
#if 0
|
||||
// NOTE: here center is the center of the ESM4 cell (in terms of cell grid position) which
|
||||
// is subtly different to the way fillVertexBuffers() treats it because we don't
|
||||
// worry about chunk sizes or LOD
|
||||
//
|
||||
// center is wrong here due to the way TerrainGrid::buildTerrain() calculates the
|
||||
// new center
|
||||
osg::Vec2f realCenter;
|
||||
switch (quad)
|
||||
{
|
||||
case 3: realCenter = center - osg::Vec2f( 0.25f, 0.25f); break;
|
||||
case 1: realCenter = center - osg::Vec2f( 0.25f, -0.25f); break;
|
||||
case 2: realCenter = center - osg::Vec2f(-0.25f, 0.25f); break;
|
||||
case 0: realCenter = center - osg::Vec2f(-0.25f, -0.25f); break;
|
||||
default: realCenter = center; break;
|
||||
}
|
||||
const osg::Vec2f origin2 = realCenter - osg::Vec2f(1.f, 1.f) * 0.5f; // assumed to be bottom left corner
|
||||
//std::cout << origin2.x() << ", " << origin2.y() << std::endl;
|
||||
#endif
|
||||
const osg::Vec2f origin = center - osg::Vec2f(size, size) * 0.5f;
|
||||
//std::cout << origin.x() << ", " << origin.y() << std::endl;
|
||||
const int startCellX = static_cast<int>(std::floor(origin.x()));
|
||||
const int startCellY = static_cast<int>(std::floor(origin.y()));
|
||||
LandCache cache(startCellX - 1, startCellY - 1, static_cast<std::size_t>(std::ceil(size)) + 2);
|
||||
std::pair lastCell{ startCellX, startCellY };
|
||||
const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache);
|
||||
const ESM::LandData* heightData = nullptr;
|
||||
const ESM::LandData* normalData = nullptr;
|
||||
const ESM::LandData* colourData = nullptr;
|
||||
bool validHeightDataExists = false;
|
||||
|
||||
if (land != nullptr)
|
||||
{
|
||||
heightData = land->getData(ESM::Land::DATA_VHGT);
|
||||
normalData = land->getData(ESM::Land::DATA_VNML);
|
||||
colourData = land->getData(ESM::Land::DATA_VCLR);
|
||||
validHeightDataExists = true;
|
||||
}
|
||||
|
||||
int rowStart = 0;
|
||||
int colStart = 0;
|
||||
int rowEnd, colEnd;
|
||||
|
||||
// FIXME: how to ignore the repeat of left/bottom quad?
|
||||
switch (quad)
|
||||
{
|
||||
case 0: // bottom left
|
||||
{
|
||||
rowStart = 0;
|
||||
colStart = 0;
|
||||
|
||||
rowEnd = int(cellSize / 2) + 1; // int(33 / 2) + 1 = 17
|
||||
colEnd = int(cellSize / 2) + 1;
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // bottom right
|
||||
{
|
||||
rowStart = 0;
|
||||
colStart = int(cellSize / 2); // 16, repeat the last of the left quad
|
||||
|
||||
rowEnd = int(cellSize / 2) + 1; // 17
|
||||
colEnd = cellSize;
|
||||
|
||||
break;
|
||||
}
|
||||
case 1: // top left
|
||||
{
|
||||
rowStart = int(cellSize / 2); // 16, repeat the last of the bottom quad
|
||||
colStart = 0;
|
||||
|
||||
rowEnd = cellSize;
|
||||
colEnd = int(cellSize / 2) + 1; // 17
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // top right
|
||||
{
|
||||
rowStart = int(cellSize / 2); // 16
|
||||
colStart = int(cellSize / 2); // 16
|
||||
|
||||
rowEnd = cellSize; // 33
|
||||
colEnd = cellSize; // 33
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
std::fill(positions.begin(), positions.end(), osg::Vec3f());
|
||||
return; // FIXME: throw instead?
|
||||
}
|
||||
|
||||
osg::Vec3f normal(0, 0, 1);
|
||||
osg::Vec4ub color(255, 255, 255, 255);
|
||||
|
||||
// ESM4::Land::mLandData.mHeights start at the bottom left hand corner
|
||||
//
|
||||
// row
|
||||
// |
|
||||
// v
|
||||
// 1056 ..1088 32
|
||||
// 1023 ..1055 31
|
||||
// ..
|
||||
// 99 .. 131 3
|
||||
// 66 .. 98 2
|
||||
// 33 .. 65 1
|
||||
// 0 .. 32 0
|
||||
//
|
||||
// 0 .. 32 <- col
|
||||
//
|
||||
// row and col represent cell space (i.e. mHeights, mVertNorm and mVertColr)
|
||||
// vertX and vertY represent quad space
|
||||
float vertY = 0;
|
||||
float vertX = 0;
|
||||
for (int col = colStart; col < colEnd; col += 1)
|
||||
{
|
||||
vertX = 0;
|
||||
for (int row = rowStart; row < rowEnd; row += 1)
|
||||
{
|
||||
float height = -2048;
|
||||
if (land && heightData) // validHeightDataExists
|
||||
height = heightData->getHeights()[col*cellSize + row];
|
||||
|
||||
// FIXME: I suspect landSizeInUnits should be 2048
|
||||
const std::size_t vertIndex = vertX * numVerts + vertY;
|
||||
positions[vertIndex]
|
||||
= osg::Vec3f((vertX / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits,
|
||||
(vertY / static_cast<float>(numVerts - 1) - 0.5f) * size * landSizeInUnits,
|
||||
height);
|
||||
|
||||
if (land && normalData)
|
||||
{
|
||||
normal.x() = normalData->getNormals()[col * cellSize * 3 + row * 3 + 0];
|
||||
normal.y() = normalData->getNormals()[col * cellSize * 3 + row * 3 + 1];
|
||||
normal.z() = normalData->getNormals()[col * cellSize * 3 + row * 3 + 2];
|
||||
normal.normalize();
|
||||
}
|
||||
else
|
||||
normal = osg::Vec3f(0, 0, 1);
|
||||
|
||||
// FIXME: not sure if below normal fixes for Morrowind also applies to TES4
|
||||
// TODO: needs testing
|
||||
#if 0
|
||||
// Normals apparently don't connect seamlessly between cells
|
||||
if (col == cellSize - 1 || row == cellSize - 1)
|
||||
fixNormal(normal, cellLocation, col, row, cache);
|
||||
|
||||
// some corner normals appear to be complete garbage (z < 0)
|
||||
if ((row == 0 || row == cellSize - 1) && (col == 0 || col == cellSize - 1))
|
||||
averageNormal(normal, cellLocation, col, row, cache);
|
||||
#endif
|
||||
//assert(normal.z() > 0); // ToddLand triggers this
|
||||
if (normal.z() < 0)
|
||||
normal.z() = 0;
|
||||
|
||||
normals[vertIndex] = normal;
|
||||
|
||||
if (land && colourData)
|
||||
{
|
||||
color.r() = colourData->getColors()[col * cellSize * 3 + row * 3 + 0];
|
||||
color.g() = colourData->getColors()[col * cellSize * 3 + row * 3 + 1];
|
||||
color.b() = colourData->getColors()[col * cellSize * 3 + row * 3 + 2];
|
||||
}
|
||||
else
|
||||
{
|
||||
color.r() = 1;
|
||||
color.g() = 1;
|
||||
color.b() = 1;
|
||||
}
|
||||
|
||||
// FIXME: not sure if below colour fixes for Morrowind also applies to TES4
|
||||
// TODO: needs testing
|
||||
#if 0
|
||||
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
|
||||
if (col == cellSize - 1 || row == cellSize - 1)
|
||||
fixColour(color, cellLocation, col, row, cache);
|
||||
#endif
|
||||
// color.a() = 1;
|
||||
colours[vertIndex] = color;
|
||||
|
||||
++vertX;
|
||||
}
|
||||
++vertY;
|
||||
}
|
||||
}
|
||||
|
||||
void Storage::getQuadBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace, int quad)
|
||||
{
|
||||
// VTXT info indicates texture size is 17x17 - but the cell grid is 33x33
|
||||
// (cf. TES3 has 65x65 cell) do we discard one row and column or overlap?
|
||||
//
|
||||
// NOTE: each base texture does not completely "fill" a quadrant. The observations in
|
||||
// TES4 vanilla indicates that the texture repeats (or "wraps") 6 times each side
|
||||
//
|
||||
// ///////////////// //////////////// <-- discard texture row?
|
||||
// +-----------------+----------------+/
|
||||
// 32 |\ \| |/
|
||||
// 31 |\ \| |/
|
||||
// |\ 17x16 \| 16x16 |/
|
||||
// . |\ \| |/
|
||||
// . |\ 2 \| 3 |/
|
||||
// . |\ \| |/
|
||||
// . |\ \<---------------------- overlap column instead?
|
||||
// 17 |\ \| |/
|
||||
// +-----------------+----------------+
|
||||
// 16 |\ |\\\\\\\\\\\\\\\\|<---- overlap row instead?
|
||||
// 15 |\ | |/
|
||||
// . |\ 17x17 | 16x17 |/
|
||||
// . |\ | |/
|
||||
// . |\ 0 | 1 |/
|
||||
// . |\ | |/
|
||||
// 2 |\ | |/
|
||||
// 1 |\ | |/
|
||||
// 0 |\\\\\\\\\\\\\\\\\|\\\\\\\\\\\\\\\\|<---- this row of vertices is a copy of cell below
|
||||
// +-----------------+----------------+
|
||||
// 111 1 33 ^
|
||||
// 0123 ...... 456 7 ..... 12 |
|
||||
// ^ discard texture column?
|
||||
// |
|
||||
// this column of vertices is a copy of the cell to the left
|
||||
//
|
||||
const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize, chunkSize) * 0.5f;
|
||||
const int startCellX = static_cast<int>(std::floor(origin.x()));
|
||||
const int startCellY = static_cast<int>(std::floor(origin.y()));
|
||||
const int realTextureSize = 17; // FIXME: should be defined in Land record
|
||||
//const std::size_t blendmapSize = getBlendmapSize(chunkSize, realTextureSize);
|
||||
const std::size_t blendmapSize = realTextureSize;
|
||||
|
||||
// FIXME: temp testing
|
||||
//if (startCellX == 12 && startCellY == 21)
|
||||
//std::cout << "vilverin exterior" << std::endl;
|
||||
|
||||
// FIXME: I don't think ESM4 needs this?
|
||||
#if 0
|
||||
// We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla
|
||||
constexpr std::size_t imageScaleFactor = 2;
|
||||
#else
|
||||
constexpr std::size_t imageScaleFactor = 1;
|
||||
#endif
|
||||
const std::size_t blendmapImageSize = blendmapSize * imageScaleFactor;
|
||||
std::vector<UniqueTextureId> textureIds(blendmapSize * blendmapSize);
|
||||
|
||||
// NOTE: we need all the texture data which are missing in LandObject
|
||||
#if 0
|
||||
LandCache cache(startCellX - 1, startCellY - 1, static_cast<std::size_t>(std::ceil(chunkSize)) + 2);
|
||||
std::pair lastCell{ startCellX, startCellY };
|
||||
const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache);
|
||||
#endif
|
||||
// FIXME: do we need to cache this data? (already in ESMStore, why cache again?)
|
||||
// alternatively modify LandObject with all the extra data rather than use getEsm4Land()?
|
||||
const ESM4::Land* land = getEsm4Land(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace));
|
||||
if (!land)
|
||||
return; // FIXME: throw instead?
|
||||
|
||||
// I don't think we need this?
|
||||
#if 0
|
||||
const auto handleSample = [&](const CellSample& sample) {
|
||||
const std::pair cell{ sample.mCellX, sample.mCellY };
|
||||
if (lastCell != cell)
|
||||
{
|
||||
land = getEsm4Land(ESM::ExteriorCellLocation(sample.mCellX, sample.mCellY, worldspace));
|
||||
lastCell = cell;
|
||||
}
|
||||
|
||||
textureIds[sample.mDstCol * blendmapSize + sample.mDstRow]
|
||||
= getQuadTextureIdAt(land, sample.mSrcRow, sample.mSrcCol);
|
||||
};
|
||||
|
||||
sampleBlendmaps(chunkSize, origin.x(), origin.y(), realTextureSize, handleSample);
|
||||
|
||||
std::map<UniqueTextureId, std::size_t> textureIndicesMap;
|
||||
#endif
|
||||
// FIXME: debugging only
|
||||
//std::cout << "quad " << quad << std::endl;
|
||||
|
||||
// base texture
|
||||
Terrain::LayerInfo info;
|
||||
ESM::FormId ltexId = ESM::FormId::fromUint32(land->mTextures[quad].base.formId);
|
||||
std::string texture = getEsm4TextureName(ltexId);
|
||||
if (texture == "")
|
||||
info = getLayerInfo(getEsm4TextureSet(ltexId)); // FO3/FONV/TES5
|
||||
else
|
||||
info = getLayerInfo(texture); // TES4
|
||||
|
||||
// FIXME: debugging only
|
||||
//std::cout << "base " << info.mDiffuseMap << std::endl;
|
||||
osg::ref_ptr<osg::Image> image(new osg::Image);
|
||||
image->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize),
|
||||
1, GL_ALPHA, GL_UNSIGNED_BYTE);
|
||||
std::memset(image->data(), 255, image->getTotalDataSize()); // fully opaque for base texture
|
||||
blendmaps.push_back(std::move(image));
|
||||
layerList.push_back(std::move(info));
|
||||
|
||||
// additional textures
|
||||
|
||||
std::size_t numLayers = land->mTextures[quad].layers.size();
|
||||
for (std::size_t i = 0; i < numLayers; ++i)
|
||||
{
|
||||
Terrain::LayerInfo layerInfo;
|
||||
/*ESM::FormId*/ ltexId = ESM::FormId::fromUint32(land->mTextures[quad].layers[i].texture.formId);
|
||||
std::string layerTexture = getEsm4TextureName(ltexId);
|
||||
if (layerTexture == "")
|
||||
layerInfo = getLayerInfo(getEsm4TextureSet(ltexId)); // FO3/FONV/TES5
|
||||
else
|
||||
layerInfo = getLayerInfo(layerTexture); // TES4
|
||||
|
||||
// FIXME: debugging only
|
||||
//std::cout << "layer " << i << ", " << layerInfo.mDiffuseMap << std::endl;
|
||||
|
||||
osg::ref_ptr<osg::Image> layerImage(new osg::Image);
|
||||
layerImage->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize),
|
||||
1, GL_ALPHA, GL_UNSIGNED_BYTE);
|
||||
std::memset(layerImage->data(), 0, layerImage->getTotalDataSize());
|
||||
blendmaps.push_back(std::move(layerImage));
|
||||
layerList.push_back(std::move(layerInfo));
|
||||
|
||||
const std::size_t layerIndex = blendmaps.size() - 1;
|
||||
unsigned char* const data = blendmaps[layerIndex]->data();
|
||||
|
||||
// osg::Image default origin is bottom left and VTXT data also starts at bottom left
|
||||
// corner i.e. there should be no conversion required
|
||||
//
|
||||
// FIXME: but the observed behaviour is different - either VTXT starts at top left
|
||||
// corner or osg::Image is being interpreted differently by the shader
|
||||
//
|
||||
// Image guessed VTXT
|
||||
// index position y'
|
||||
//
|
||||
// 272 ..288 0 .. 16 0
|
||||
// .. ..
|
||||
// 51 .. 67 221 ..237 13
|
||||
// 34 .. 50 238 ..254 14
|
||||
// 17 .. 33 255 ..271 15
|
||||
// 0 .. 16 272 ..288 16
|
||||
//
|
||||
// y = floor(position / 17)
|
||||
// y' = 17 - 1 - y
|
||||
// x = position % 17
|
||||
//
|
||||
// e.g. position = 275, y = 16, y' = 0, x = 3
|
||||
// position = 50, y = 2, y' = 14, x = 16
|
||||
const std::vector<ESM4::Land::VTXT>& opacityData = land->mTextures[quad].layers[i].data;
|
||||
for (std::size_t j = 0; j < opacityData.size(); ++j)
|
||||
{
|
||||
// NOTE: blendmapImageSize, blendmapSize and realTextureSize are all the same (17)
|
||||
|
||||
int position = opacityData[j].position;
|
||||
|
||||
std::size_t y = realTextureSize - 1 - std::floor(position / realTextureSize);
|
||||
std::size_t x = position % realTextureSize;
|
||||
data[y*realTextureSize + x] = unsigned char(opacityData[j].opacity * 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
namespace ESM4
|
||||
{
|
||||
struct Land;
|
||||
struct LandTexture;
|
||||
struct TextureSet;
|
||||
}
|
||||
|
||||
namespace ESM
|
||||
|
@ -130,6 +132,19 @@ namespace ESMTerrain
|
|||
return data->getHeights()[y * landSize + x];
|
||||
}
|
||||
|
||||
virtual const ESM4::Land *getEsm4Land(ESM::ExteriorCellLocation cellLocation) const = 0;
|
||||
virtual const ESM4::LandTexture *getEsm4LandTexture(ESM::RefId ltexId) const = 0;
|
||||
virtual const ESM4::TextureSet *getEsm4TextureSet(ESM::RefId txstId) const = 0;
|
||||
|
||||
void fillQuadVertexBuffers(float size, const osg::Vec2f& center, ESM::RefId worldspace,
|
||||
osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours, int quad);
|
||||
|
||||
void getQuadBlendmaps(float size, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
|
||||
std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace, int quad);
|
||||
|
||||
protected:
|
||||
bool mIsEsm4Ext; // intended to be used by MWRender::TerrainStorage
|
||||
|
||||
private:
|
||||
const VFS::Manager* mVFS;
|
||||
|
||||
|
@ -148,6 +163,8 @@ namespace ESMTerrain
|
|||
|
||||
std::string getTextureName(UniqueTextureId id);
|
||||
|
||||
std::string getEsm4TextureName(ESM::RefId id);
|
||||
|
||||
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;
|
||||
std::mutex mLayerInfoMutex;
|
||||
|
||||
|
@ -159,6 +176,8 @@ namespace ESMTerrain
|
|||
bool mAutoUseSpecularMaps;
|
||||
|
||||
Terrain::LayerInfo getLayerInfo(const std::string& texture);
|
||||
|
||||
Terrain::LayerInfo getLayerInfo(const ESM4::TextureSet *txst);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#include <components/sceneutil/lightmanager.hpp>
|
||||
|
||||
#include <components/esmterrain/storage.hpp>
|
||||
|
||||
#include "compositemaprenderer.hpp"
|
||||
#include "material.hpp"
|
||||
#include "storage.hpp"
|
||||
|
@ -39,6 +41,32 @@ namespace Terrain
|
|||
mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON);
|
||||
}
|
||||
|
||||
// FIXME: don't know which is worse, adding duplicated code here or adding a parameter to
|
||||
// Terrain::QuadTreeWorld::getChunk().
|
||||
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod,
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile, int quad)
|
||||
{
|
||||
// Override lod with the vertexLodMod adjusted value.
|
||||
// TODO: maybe we can refactor this code by moving all vertexLodMod code into this class.
|
||||
lod = static_cast<unsigned char>(lodFlags >> (4 * 4));
|
||||
|
||||
const ChunkKey key{ .mCenter = center, .mLod = lod, .mLodFlags = lodFlags };
|
||||
if (osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(key))
|
||||
return static_cast<osg::Node*>(obj.get());
|
||||
|
||||
const TerrainDrawable* templateGeometry = nullptr;
|
||||
const TemplateKey templateKey{ .mCenter = center, .mLod = lod };
|
||||
const auto pair = mCache->lowerBound(templateKey);
|
||||
if (pair.has_value() && templateKey == TemplateKey{ .mCenter = pair->first.mCenter, .mLod = pair->first.mLod })
|
||||
templateGeometry = static_cast<const TerrainDrawable*>(pair->second.get());
|
||||
|
||||
osg::ref_ptr<osg::Node> node = createChunk(size, center, lod, lodFlags, compile, templateGeometry, quad);
|
||||
mCache->addEntryToObjectCache(key, node.get());
|
||||
return node;
|
||||
}
|
||||
|
||||
// called from either TerrainGrid::buildTerrain() or QuadTreeWorld::loadRenderingNode()
|
||||
// calls createChunk()
|
||||
osg::ref_ptr<osg::Node> ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod,
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
{
|
||||
|
@ -137,12 +165,27 @@ namespace Terrain
|
|||
}
|
||||
}
|
||||
|
||||
// > openmw.exe!Terrain::ChunkManager::createPasses() Line 188
|
||||
// openmw.exe!Terrain::ChunkManager::createCompositeMapGeometry() Line 123
|
||||
// openmw.exe!Terrain::ChunkManager::createChunk() Line 275
|
||||
// openmw.exe!Terrain::ChunkManager::getChunk() Line 59
|
||||
// openmw.exe!Terrain::QuadTreeWorld::loadRenderingNode() Line 397
|
||||
// openmw.exe!Terrain::QuadTreeWorld::preload() Line 558
|
||||
// openmw.exe!MWWorld::TerrainPreloadItem::doWork() Line 184
|
||||
// openmw.exe!SceneUtil::WorkThread::run() Line 135
|
||||
std::vector<osg::ref_ptr<osg::StateSet>> ChunkManager::createPasses(
|
||||
float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap)
|
||||
float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap, int quad)
|
||||
{
|
||||
std::vector<LayerInfo> layerList;
|
||||
std::vector<osg::ref_ptr<osg::Image>> blendmaps;
|
||||
mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace);
|
||||
|
||||
if (quad >= 0) // NOTE: quad == -1 has a special meaning of "no quads"
|
||||
{
|
||||
static_cast<ESMTerrain::Storage*>(mStorage)
|
||||
->getQuadBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace, quad);
|
||||
}
|
||||
else
|
||||
mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace);
|
||||
|
||||
bool useShaders = mSceneManager->getForceShaders();
|
||||
if (!mSceneManager->getClampLighting())
|
||||
|
@ -183,14 +226,26 @@ namespace Terrain
|
|||
blendmapTextures.push_back(texture);
|
||||
}
|
||||
|
||||
// NOTE: This needs to get different values for TES4. That is, blendmapScale should
|
||||
// be 16 for TES3 and 6 for TES4 after calling getBlendmapScale() if we were
|
||||
// using TerrainGrid (i.e. chunksize of 1.f). See the way Terrain::createPasses()
|
||||
// uses BlendmapTexMat::value(blendmapScale). This scaling won't work if using
|
||||
// QuadTree with chunkSize other than 1.f (in which case need to do sampling).
|
||||
//
|
||||
// We should remember that in TES4/ESM4 the land "chunk" size is not the same
|
||||
// size as the texture. So we may have to do some maths here but it is unclear
|
||||
// whether it will work out properly until some testing is done.
|
||||
//
|
||||
// FIXME: TES5 and FO3/FONV may have different texture scaling - requires testing.
|
||||
float blendmapScale = mStorage->getBlendmapScale(chunkSize);
|
||||
|
||||
// TODO: not so sure about (i.e. don't understand) using blendmapScale for layerTileSize
|
||||
return ::Terrain::createPasses(
|
||||
useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale);
|
||||
useShaders, mSceneManager, layers, blendmapTextures, blendmapScale, blendmapScale, quad);
|
||||
}
|
||||
|
||||
osg::ref_ptr<osg::Node> ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod,
|
||||
unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry)
|
||||
unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry, int quad)
|
||||
{
|
||||
osg::ref_ptr<TerrainDrawable> geometry(new TerrainDrawable);
|
||||
|
||||
|
@ -201,7 +256,18 @@ namespace Terrain
|
|||
osg::ref_ptr<osg::Vec4ubArray> colors(new osg::Vec4ubArray);
|
||||
colors->setNormalize(true);
|
||||
|
||||
mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors);
|
||||
// FIXME: I have a suspicion that existing fillVertexBuffers() probably already works even with
|
||||
// the unwanted Morrowind specific "fixes".
|
||||
#if 1
|
||||
// NOTE: decided on a new method rather than pass quad to fillVertexBuffers() just
|
||||
// in case the Morrowind specific "fixes" causes problems
|
||||
// NOTE: LOD is not supported
|
||||
if (quad >= 0) // NOTE: quad == -1 has a special meaning of "no quads"
|
||||
static_cast<ESMTerrain::Storage*>(mStorage)
|
||||
->fillQuadVertexBuffers(chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors, quad);
|
||||
else
|
||||
#endif
|
||||
mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors);
|
||||
|
||||
osg::ref_ptr<osg::VertexBufferObject> vbo(new osg::VertexBufferObject);
|
||||
positions->setVertexBufferObject(vbo);
|
||||
|
@ -284,7 +350,9 @@ namespace Terrain
|
|||
}
|
||||
else
|
||||
{
|
||||
geometry->setPasses(createPasses(chunkSize, chunkCenter, false));
|
||||
// FIXME: maybe we need to pass quad here or call a new method
|
||||
// e.g. createQuadPasses()
|
||||
geometry->setPasses(createPasses(chunkSize, chunkCenter, false, quad));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -81,6 +81,10 @@ namespace Terrain
|
|||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags,
|
||||
bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;
|
||||
|
||||
// NOTE: created to avoid adding another parameter to getChunk() in Terrain::QuadTreeWorld::ChunkManager
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags,
|
||||
bool activeGrid, const osg::Vec3f& viewPoint, bool compile, int quad);
|
||||
|
||||
void setCompositeMapSize(unsigned int size) { mCompositeMapSize = size; }
|
||||
void setCompositeMapLevel(float level) { mCompositeMapLevel = level; }
|
||||
void setMaxCompositeGeometrySize(float maxCompGeometrySize) { mMaxCompGeometrySize = maxCompGeometrySize; }
|
||||
|
@ -95,8 +99,9 @@ namespace Terrain
|
|||
void releaseGLObjects(osg::State* state) override;
|
||||
|
||||
private:
|
||||
// NOTE: quad == -1 has a special meaning that we're not dealing with ESM4 quad structure
|
||||
osg::ref_ptr<osg::Node> createChunk(float size, const osg::Vec2f& center, unsigned char lod,
|
||||
unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry);
|
||||
unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry, int quad = -1);
|
||||
|
||||
osg::ref_ptr<osg::Texture2D> createCompositeMapRTT();
|
||||
|
||||
|
@ -104,7 +109,7 @@ namespace Terrain
|
|||
float chunkSize, const osg::Vec2f& chunkCenter, const osg::Vec4f& texCoords, CompositeMap& map);
|
||||
|
||||
std::vector<osg::ref_ptr<osg::StateSet>> createPasses(
|
||||
float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap);
|
||||
float chunkSize, const osg::Vec2f& chunkCenter, bool forCompositeMap, int quad = -1);
|
||||
|
||||
Terrain::Storage* mStorage;
|
||||
Resource::SceneManager* mSceneManager;
|
||||
|
|
|
@ -20,6 +20,7 @@ namespace Terrain
|
|||
std::string mNormalMap;
|
||||
bool mParallax; // Height info in normal map alpha channel?
|
||||
bool mSpecular; // Specular info in diffuse map alpha channel?
|
||||
//bool mIsEsm4; // intended to be used in Terrain::createPasses()
|
||||
|
||||
bool requiresShaders() const { return !mNormalMap.empty() || mSpecular; }
|
||||
};
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace
|
|||
return instance.get(blendmapScale);
|
||||
}
|
||||
|
||||
// FIXME: Not sure if this pre-multiplication is needed for TES4
|
||||
// (needs A-B testing to confirm)
|
||||
const osg::ref_ptr<osg::TexMat>& get(const int blendmapScale)
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
@ -223,7 +225,7 @@ namespace Terrain
|
|||
{
|
||||
std::vector<osg::ref_ptr<osg::StateSet>> createPasses(bool useShaders, Resource::SceneManager* sceneManager,
|
||||
const std::vector<TextureLayer>& layers, const std::vector<osg::ref_ptr<osg::Texture2D>>& blendmaps,
|
||||
int blendmapScale, float layerTileSize)
|
||||
int blendmapScale, float layerTileSize, int quad)
|
||||
{
|
||||
auto& shaderManager = sceneManager->getShaderManager();
|
||||
std::vector<osg::ref_ptr<osg::StateSet>> passes;
|
||||
|
@ -243,7 +245,12 @@ namespace Terrain
|
|||
stateset->setRenderBinDetails(firstLayer ? 0 : 1, "RenderBin");
|
||||
if (!firstLayer)
|
||||
{
|
||||
stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON);
|
||||
if (quad >= 0)
|
||||
stateset->setAttributeAndModes(
|
||||
new osg::BlendFunc(osg::BlendFunc::SRC_ALPHA, osg::BlendFunc::ONE_MINUS_SRC_ALPHA),
|
||||
osg::StateAttribute::ON);
|
||||
else
|
||||
stateset->setAttributeAndModes(BlendFunc::value(), osg::StateAttribute::ON);
|
||||
stateset->setAttributeAndModes(EqualDepth::value(), osg::StateAttribute::ON);
|
||||
}
|
||||
else
|
||||
|
@ -303,13 +310,20 @@ namespace Terrain
|
|||
defineMap["parallax"] = parallax ? "1" : "0";
|
||||
defineMap["writeNormals"] = (it == layers.end() - 1) ? "1" : "0";
|
||||
defineMap["reconstructNormalZ"] = reconstructNormalZ ? "1" : "0";
|
||||
if (quad >= 0)
|
||||
defineMap["baseLayer"] = (firstLayer) ? "1" : "0";
|
||||
Stereo::shaderStereoDefines(defineMap);
|
||||
|
||||
stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap));
|
||||
if (quad >= 0)
|
||||
stateset->setAttributeAndModes(shaderManager.getProgram("esm4terrain", defineMap));
|
||||
else
|
||||
stateset->setAttributeAndModes(shaderManager.getProgram("terrain", defineMap));
|
||||
stateset->addUniform(UniformCollection::value().mColorMode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// FIXME: needs some changes for ESM4
|
||||
|
||||
// Add the actual layer texture
|
||||
osg::ref_ptr<osg::Texture2D> tex = it->mDiffuseMap;
|
||||
stateset->setTextureAttributeAndModes(0, tex.get());
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Terrain
|
|||
|
||||
std::vector<osg::ref_ptr<osg::StateSet>> createPasses(bool useShaders, Resource::SceneManager* sceneManager,
|
||||
const std::vector<TextureLayer>& layers, const std::vector<osg::ref_ptr<osg::Texture2D>>& blendmaps,
|
||||
int blendmapScale, float layerTileSize);
|
||||
int blendmapScale, float layerTileSize, int quad = -1);
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "quadtreeworld.hpp"
|
||||
|
||||
#include <cmath> // std::floor
|
||||
|
||||
#include <osg/Material>
|
||||
#include <osg/PolygonMode>
|
||||
#include <osg/ShapeDrawable>
|
||||
|
@ -260,7 +262,7 @@ namespace Terrain
|
|||
{
|
||||
}
|
||||
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& chunkCenter, unsigned char lod,
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad*/)
|
||||
{
|
||||
osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 };
|
||||
auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size,
|
||||
|
@ -289,7 +291,7 @@ namespace Terrain
|
|||
, mLodFactor(lodFactor)
|
||||
, mVertexLodMod(vertexLodMod)
|
||||
, mViewDistance(std::numeric_limits<float>::max())
|
||||
, mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 4.f : 1 / 8.f)
|
||||
, mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 2.f : 1 / 8.f) // NOTE: increased min for ESM4
|
||||
, mDebugTerrainChunks(debugChunks)
|
||||
{
|
||||
mChunkManager->setCompositeMapSize(compMapResolution);
|
||||
|
@ -394,11 +396,32 @@ namespace Terrain
|
|||
|
||||
for (QuadTreeWorld::ChunkManager* m : mChunkManagers)
|
||||
{
|
||||
osg::ref_ptr<osg::Node> n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(),
|
||||
DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid,
|
||||
vd->getViewPoint(), compile);
|
||||
if (n)
|
||||
pat->addChild(n);
|
||||
osg::ref_ptr<osg::Node> n;
|
||||
if (ESM::isEsm4Ext(mWorldspace) && entry.mNode->getSize() == 0.5f && m == mChunkManager.get())
|
||||
{
|
||||
osg::Vec2 chunkCenter = entry.mNode->getCenter();
|
||||
float originX = std::floor(chunkCenter.x());
|
||||
float originY = std::floor(chunkCenter.y());
|
||||
int quad = -1;
|
||||
if (chunkCenter.x() - originX == 0.25f)
|
||||
quad = (chunkCenter.y() - originY == 0.25f) ? 0 : 2;
|
||||
else
|
||||
quad = (chunkCenter.y() - originY == 0.25f) ? 1 : 3;
|
||||
|
||||
n = static_cast<Terrain::ChunkManager*>(m)->getChunk(0.5f, entry.mNode->getCenter(),
|
||||
DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid,
|
||||
vd->getViewPoint(), compile, quad);
|
||||
if (n)
|
||||
pat->addChild(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
n = m->getChunk(entry.mNode->getSize(), entry.mNode->getCenter(),
|
||||
DefaultLodCallback::getNativeLodLevel(entry.mNode, mMinSize), entry.mLodFlags, activeGrid,
|
||||
vd->getViewPoint(), compile);
|
||||
if (n)
|
||||
pat->addChild(n);
|
||||
}
|
||||
}
|
||||
entry.mRenderingNode = pat;
|
||||
}
|
||||
|
@ -536,6 +559,8 @@ namespace Terrain
|
|||
return mViewDataMap->createIndependentView();
|
||||
}
|
||||
|
||||
// FIXME: I guess this is where it all starts? We need to somehow deal with entry of chunk
|
||||
// size 1 to be split into 4 smaller "quads".
|
||||
void QuadTreeWorld::preload(View* view, const osg::Vec3f& viewPoint, const osg::Vec4i& grid,
|
||||
std::atomic<bool>& abort, Loading::Reporter& reporter)
|
||||
{
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace Terrain
|
|||
mWorldspace = worldspace;
|
||||
}
|
||||
virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod,
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
|
||||
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile/*, int quad = -1*/)
|
||||
= 0;
|
||||
virtual unsigned int getNodeMask() { return 0; }
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "storage.hpp"
|
||||
#include "view.hpp"
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/esm/util.hpp>
|
||||
|
||||
namespace Terrain
|
||||
{
|
||||
|
@ -51,10 +52,36 @@ namespace Terrain
|
|||
static_cast<MyView*>(view)->mLoaded = buildTerrain(nullptr, 1.f, center);
|
||||
}
|
||||
|
||||
// I think this should be where we decide to split the land into 4 quads.
|
||||
// Alternatively, we can do it in loadCell() and call a different kind of buildTerrain().
|
||||
//
|
||||
// Need to do some experiments to see if the existing code will produe the correct quads or
|
||||
// special code needs to be added (depens on column/row start and ends). But I think we
|
||||
// still need to pass more info to getChunk() because we need to know which quadrant for
|
||||
// the textures?
|
||||
osg::ref_ptr<osg::Node> TerrainGrid::buildTerrain(
|
||||
osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter)
|
||||
osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter, int quad)
|
||||
{
|
||||
if (chunkSize * mNumSplits > 1.f)
|
||||
if (ESM::isEsm4Ext(mWorldspace) && chunkSize == 1.f && mNumSplits == 4) // WARN: hard coded values for ESM4
|
||||
{
|
||||
osg::ref_ptr<osg::Group> group(new osg::Group);
|
||||
if (parent)
|
||||
parent->addChild(group); // should never happen
|
||||
|
||||
float newChunkSize = chunkSize / 2.f;
|
||||
{
|
||||
buildTerrain(group, // top right
|
||||
newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, newChunkSize / 2.f), 3);
|
||||
buildTerrain(group, // top left
|
||||
newChunkSize, chunkCenter + osg::Vec2f(newChunkSize / 2.f, -newChunkSize / 2.f), 1);
|
||||
buildTerrain(group, // bottom right
|
||||
newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, newChunkSize / 2.f), 2);
|
||||
buildTerrain(group, // bottom left
|
||||
newChunkSize, chunkCenter + osg::Vec2f(-newChunkSize / 2.f, -newChunkSize / 2.f), 0);
|
||||
}
|
||||
return group;
|
||||
}
|
||||
else if (!ESM::isEsm4Ext(mWorldspace) && chunkSize * mNumSplits > 1.f) // FIXME: needs better logic
|
||||
{
|
||||
// keep splitting
|
||||
osg::ref_ptr<osg::Group> group(new osg::Group);
|
||||
|
@ -70,8 +97,10 @@ namespace Terrain
|
|||
}
|
||||
else
|
||||
{
|
||||
osg::ref_ptr<osg::Node> node
|
||||
= mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true);
|
||||
// FIXME: not sure which is worse, this mess or adding a parameter to Terrain::QuadTreeWorld::getChunk()
|
||||
osg::ref_ptr<osg::Node> node = ESM::isEsm4Ext(mWorldspace)
|
||||
? mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true, quad)
|
||||
: mChunkManager->getChunk(chunkSize, chunkCenter, 0, 0, false, osg::Vec3f(), true);
|
||||
if (!node)
|
||||
return nullptr;
|
||||
|
||||
|
@ -85,6 +114,8 @@ namespace Terrain
|
|||
}
|
||||
}
|
||||
|
||||
// Use ESM::isEsm4Ext(World::getWorldspace())
|
||||
// or just ESM::isEsm4Ext(mWorldspace) since mWorldspace is declared as protected.
|
||||
void TerrainGrid::loadCell(int x, int y)
|
||||
{
|
||||
if (mGrid.find(std::make_pair(x, y)) != mGrid.end())
|
||||
|
|
|
@ -46,7 +46,8 @@ namespace Terrain
|
|||
bool isGridEmpty() const { return mGrid.empty(); }
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Node> buildTerrain(osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter);
|
||||
// quad is meant to be used for ESM4 terrain only; if -1 it is ignored, should be [0..3]
|
||||
osg::ref_ptr<osg::Node> buildTerrain(osg::Group* parent, float chunkSize, const osg::Vec2f& chunkCenter, int quad = -1);
|
||||
void updateWaterCulling();
|
||||
|
||||
// split each ESM::Cell into mNumSplits*mNumSplits terrain chunks
|
||||
|
|
110
files/shaders/compatibility/esm4terrain.frag
Normal file
110
files/shaders/compatibility/esm4terrain.frag
Normal 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();
|
||||
}
|
73
files/shaders/compatibility/esm4terrain.vert
Normal file
73
files/shaders/compatibility/esm4terrain.vert
Normal 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
|
||||
}
|
Loading…
Reference in a new issue