Merge branch 'toboldlygowherenowizardcathasgonebefore' into 'master'

Support Bethesda shader material files (#7777)

Closes #7777

See merge request OpenMW/openmw!4041
master
psi29a 2 weeks ago
commit 7a172b061f

@ -219,6 +219,7 @@
Feature #7652: Sort inactive post processing shaders list properly
Feature #7698: Implement sAbsorb, sDamage, sDrain, sFortify and sRestore
Feature #7709: Improve resolution selection in Launcher
Feature #7777: Support external Bethesda material files (BGSM/BGEM)
Feature #7792: Support Timescale Clouds
Feature #7795: Support MaxNumberRipples INI setting
Feature #7805: Lua Menu context

@ -12,6 +12,7 @@
#include <components/files/multidircollection.hpp>
#include <components/misc/strings/conversion.hpp>
#include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshape.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/foreachbulletobject.hpp>
@ -173,7 +174,8 @@ namespace
constexpr double expiryDelay = 0;
Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
Resource::forEachBulletObject(

@ -19,6 +19,7 @@
#include <components/files/conversion.hpp>
#include <components/files/multidircollection.hpp>
#include <components/platform/platform.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/bulletshapemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/resource/niffilemanager.hpp>
@ -220,7 +221,8 @@ namespace NavMeshTool
Resource::ImageManager imageManager(&vfs, expiryDelay);
Resource::NifFileManager nifFileManager(&vfs, &encoder.getStatelessEncoder());
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, expiryDelay);
Resource::BgsmFileManager bgsmFileManager(&vfs, expiryDelay);
Resource::SceneManager sceneManager(&vfs, &imageManager, &nifFileManager, &bgsmFileManager, expiryDelay);
Resource::BulletShapeManager bulletShapeManager(&vfs, &sceneManager, &nifFileManager, expiryDelay);
DetourNavigator::RecastGlobalAllocator::init();
DetourNavigator::Settings navigatorSettings = DetourNavigator::makeSettingsFromSettingsManager();

@ -8,6 +8,7 @@
#include <utility>
#include <vector>
#include <components/bgsm/reader.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/constrainedfilestream.hpp>
#include <components/files/conversion.hpp>
@ -25,7 +26,7 @@
namespace bpo = boost::program_options;
/// See if the file has the named extension
bool hasExtension(const std::filesystem::path& filename, const std::string& extensionToFind)
bool hasExtension(const std::filesystem::path& filename, std::string_view extensionToFind)
{
const auto extension = Files::pathToUnicodeString(filename.extension());
return Misc::StringUtils::ciEqual(extension, extensionToFind);
@ -36,6 +37,13 @@ bool isNIF(const std::filesystem::path& filename)
{
return hasExtension(filename, ".nif") || hasExtension(filename, ".kf");
}
/// Check if the file is a material file.
bool isMaterial(const std::filesystem::path& filename)
{
return hasExtension(filename, ".bgem") || hasExtension(filename, ".bgsm");
}
/// See if the file has the "bsa" extension.
bool isBSA(const std::filesystem::path& filename)
{
@ -51,16 +59,17 @@ std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
return nullptr;
}
void readNIF(
void readFile(
const std::filesystem::path& source, const std::filesystem::path& path, const VFS::Manager* vfs, bool quiet)
{
const std::string pathStr = Files::pathToUnicodeString(path);
const bool isNif = isNIF(path);
if (!quiet)
{
if (hasExtension(path, ".kf"))
std::cout << "Reading KF file '" << pathStr << "'";
if (isNif)
std::cout << "Reading " << (hasExtension(path, ".nif") ? "NIF" : "KF") << " file '" << pathStr << "'";
else
std::cout << "Reading NIF file '" << pathStr << "'";
std::cout << "Reading " << (hasExtension(path, ".bgsm") ? "BGSM" : "BGEM") << " file '" << pathStr << "'";
if (!source.empty())
std::cout << " from '" << Files::pathToUnicodeString(isBSA(source) ? source.filename() : source) << "'";
std::cout << std::endl;
@ -68,12 +77,23 @@ void readNIF(
const std::filesystem::path fullPath = !source.empty() ? source / path : path;
try
{
Nif::NIFFile file(Files::pathToUnicodeString(fullPath));
Nif::Reader reader(file, nullptr);
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
if (isNif)
{
Nif::NIFFile file(Files::pathToUnicodeString(fullPath));
Nif::Reader reader(file, nullptr);
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
else
reader.parse(Files::openConstrainedFileStream(fullPath));
}
else
reader.parse(Files::openConstrainedFileStream(fullPath));
{
Bgsm::Reader reader;
if (vfs != nullptr)
reader.parse(vfs->get(pathStr));
else
reader.parse(Files::openConstrainedFileStream(fullPath));
}
}
catch (std::exception& e)
{
@ -97,9 +117,9 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
for (const auto& name : vfs.getRecursiveDirectoryIterator(""))
{
if (isNIF(name.value()))
if (isNIF(name.value()) || isMaterial(name.value()))
{
readNIF(archivePath, name.value(), &vfs, quiet);
readFile(archivePath, name.value(), &vfs, quiet);
}
}
@ -129,10 +149,10 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
bool parseOptions(int argc, char** argv, Files::PathContainer& files, Files::PathContainer& archives,
bool& writeDebugLog, bool& quiet)
{
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF and BSA/BA2 files
bpo::options_description desc(R"(Ensure that OpenMW can use the provided NIF, KF, BGEM/BGSM and BSA/BA2 files
Usages:
niftest <nif files, kf files, BSA/BA2 files, or directories>
niftest <nif files, kf files, bgem/bgsm files, BSA/BA2 files, or directories>
Scan the file or directories for NIF errors.
Allowed options)");
@ -221,9 +241,9 @@ int main(int argc, char** argv)
const std::string pathStr = Files::pathToUnicodeString(path);
try
{
if (isNIF(path))
if (isNIF(path) || isMaterial(path))
{
readNIF({}, path, vfs.get(), quiet);
readFile({}, path, vfs.get(), quiet);
}
else if (auto archive = makeArchive(path))
{
@ -231,7 +251,7 @@ int main(int argc, char** argv)
}
else
{
std::cerr << "Error: '" << pathStr << "' is not a NIF/KF file, BSA/BA2 archive, or directory"
std::cerr << "Error: '" << pathStr << "' is not a NIF/KF/BGEM/BGSM file, BSA/BA2 archive, or directory"
<< std::endl;
}
}

@ -3,6 +3,7 @@
#include <components/nif/node.hpp>
#include <components/nif/property.hpp>
#include <components/nifosg/nifloader.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/imagemanager.hpp>
#include <components/sceneutil/serialize.hpp>
#include <components/vfs/manager.hpp>
@ -29,6 +30,7 @@ namespace
{
VFS::Manager mVfs;
Resource::ImageManager mImageManager{ &mVfs, 0 };
Resource::BgsmFileManager mMaterialManager{ &mVfs, 0 };
const osgDB::ReaderWriter* mReaderWriter = osgDB::Registry::instance()->getReaderWriterForExtension("osgt");
osg::ref_ptr<osgDB::Options> mOptions = new osgDB::Options;
@ -70,7 +72,7 @@ namespace
init(node);
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager);
auto result = Loader::load(file, &mImageManager, &mMaterialManager);
EXPECT_EQ(serialize(*result), R"(
osg::Group {
UniqueID 1
@ -259,7 +261,7 @@ osg::Group {
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager);
auto result = Loader::load(file, &mImageManager, &mMaterialManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix));
}
@ -289,7 +291,7 @@ osg::Group {
node.mProperties.push_back(Nif::RecordPtrT<Nif::NiProperty>(&property));
Nif::NIFFile file("test.nif");
file.mRoots.push_back(&node);
auto result = Loader::load(file, &mImageManager);
auto result = Loader::load(file, &mImageManager, &mMaterialManager);
EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix));
}

@ -107,6 +107,10 @@ add_component_dir (settings
windowmode
)
add_component_dir (bgsm
reader stream file
)
add_component_dir (bsa
bsa_file compressedbsafile ba2gnrlfile ba2dx10file ba2file memorystream
)
@ -125,7 +129,7 @@ add_component_dir (vfs
add_component_dir (resource
scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem
resourcemanager stats animation foreachbulletobject errormarker cachestats
resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager
)
add_component_dir (shader

@ -0,0 +1,208 @@
#include "file.hpp"
#include "stream.hpp"
namespace Bgsm
{
void MaterialFile::read(BGSMStream& stream)
{
stream.read(mVersion);
stream.read(mClamp);
stream.read(mUVOffset);
stream.read(mUVScale);
stream.read(mTransparency);
stream.read(mAlphaBlend);
stream.read(mSourceBlendMode);
stream.read(mDestinationBlendMode);
stream.read(mAlphaTestThreshold);
stream.read(mAlphaTest);
stream.read(mDepthWrite);
stream.read(mDepthTest);
stream.read(mSSR);
stream.read(mWetnessControlSSR);
stream.read(mDecal);
stream.read(mTwoSided);
stream.read(mDecalNoFade);
stream.read(mNonOccluder);
stream.read(mRefraction);
stream.read(mRefractionFalloff);
stream.read(mRefractionPower);
if (mVersion < 10)
{
stream.read(mEnvMapEnabled);
stream.read(mEnvMapMaskScale);
}
else
{
stream.read(mDepthBias);
}
stream.read(mGrayscaleToPaletteColor);
if (mVersion >= 6)
stream.read(mMaskWrites);
}
void BGSMFile::read(BGSMStream& stream)
{
MaterialFile::read(stream);
stream.read(mDiffuseMap);
stream.read(mNormalMap);
stream.read(mSmoothSpecMap);
stream.read(mGrayscaleMap);
if (mVersion >= 3)
{
stream.read(mGlowMap);
stream.read(mWrinkleMap);
stream.read(mSpecularMap);
stream.read(mLightingMap);
stream.read(mFlowMap);
if (mVersion >= 17)
stream.read(mDistanceFieldAlphaMap);
}
else
{
stream.read(mEnvMap);
stream.read(mGlowMap);
stream.read(mInnerLayerMap);
stream.read(mWrinkleMap);
stream.read(mDisplacementMap);
}
stream.read(mEnableEditorAlphaThreshold);
if (mVersion >= 8)
{
stream.read(mTranslucency);
stream.read(mTranslucencyThickObject);
stream.read(mTranslucencyMixAlbedoWithSubsurfaceColor);
stream.read(mTranslucencySubsurfaceColor);
stream.read(mTranslucencyTransmissiveScale);
stream.read(mTranslucencyTurbulence);
}
else
{
stream.read(mRimLighting);
stream.read(mRimPower);
stream.read(mBackLightPower);
stream.read(mSubsurfaceLighting);
stream.read(mSubsurfaceLightingRolloff);
}
stream.read(mSpecularEnabled);
stream.read(mSpecularColor);
stream.read(mSpecularMult);
stream.read(mSmoothness);
stream.read(mFresnelPower);
stream.read(mWetnessControlSpecScale);
stream.read(mWetnessControlSpecPowerScale);
stream.read(mWetnessControlSpecMinvar);
if (mVersion < 10)
stream.read(mWetnessControlEnvMapScale);
stream.read(mWetnessControlFresnelPower);
stream.read(mWetnessControlMetalness);
if (mVersion >= 3)
{
stream.read(mPBR);
if (mVersion >= 9)
{
stream.read(mCustomPorosity);
stream.read(mPorosityValue);
}
}
stream.read(mRootMaterialPath);
stream.read(mAnisoLighting);
stream.read(mEmitEnabled);
if (mEmitEnabled)
stream.read(mEmittanceColor);
stream.read(mEmittanceMult);
stream.read(mModelSpaceNormals);
stream.read(mExternalEmittance);
if (mVersion >= 12)
{
stream.read(mLumEmittance);
if (mVersion >= 13)
{
stream.read(mUseAdaptiveEmissive);
stream.read(mAdaptiveEmissiveExposureParams);
}
}
else if (mVersion < 8)
{
stream.read(mBackLighting);
}
stream.read(mReceiveShadows);
stream.read(mHideSecret);
stream.read(mCastShadows);
stream.read(mDissolveFade);
stream.read(mAssumeShadowmask);
stream.read(mGlowMapEnabled);
if (mVersion < 7)
{
stream.read(mEnvMapWindow);
stream.read(mEnvMapEye);
}
stream.read(mHair);
stream.read(mHairTintColor);
stream.read(mTree);
stream.read(mFacegen);
stream.read(mSkinTint);
stream.read(mTessellate);
if (mVersion < 3)
{
stream.read(mDisplacementMapParams);
stream.read(mTessellationParams);
}
stream.read(mGrayscaleToPaletteScale);
if (mVersion >= 1)
{
stream.read(mSkewSpecularAlpha);
stream.read(mTerrain);
if (mTerrain)
{
if (mVersion == 3)
stream.skip(4); // Unknown
stream.read(mTerrainParams);
}
}
}
void BGEMFile::read(BGSMStream& stream)
{
MaterialFile::read(stream);
stream.read(mBaseMap);
stream.read(mGrayscaleMap);
stream.read(mEnvMap);
stream.read(mNormalMap);
stream.read(mEnvMapMask);
if (mVersion >= 11)
{
stream.read(mSpecularMap);
stream.read(mLightingMap);
stream.read(mGlowMap);
}
if (mVersion >= 10)
{
stream.read(mEnvMapEnabled);
stream.read(mEnvMapMaskScale);
}
stream.read(mBlood);
stream.read(mEffectLighting);
stream.read(mFalloff);
stream.read(mFalloffColor);
stream.read(mGrayscaleToPaletteAlpha);
stream.read(mSoft);
stream.read(mBaseColor);
stream.read(mBaseColorScale);
stream.read(mFalloffParams);
stream.read(mLightingInfluence);
stream.read(mEnvmapMinLOD);
stream.read(mSoftDepth);
if (mVersion >= 11)
stream.read(mEmittanceColor);
if (mVersion >= 15)
stream.read(mAdaptiveEmissiveExposureParams);
if (mVersion >= 16)
stream.read(mGlowMapEnabled);
if (mVersion >= 20)
stream.read(mEffectPbrSpecular);
}
}

@ -0,0 +1,164 @@
#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP
#define OPENMW_COMPONENTS_BGSM_FILE_HPP
#include <cstdint>
#include <memory>
#include <string>
#include <string_view>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
namespace Bgsm
{
class BGSMStream;
enum class ShaderType
{
Lighting,
Effect,
};
struct MaterialFile
{
ShaderType mShaderType;
std::uint32_t mVersion;
std::uint32_t mClamp;
osg::Vec2f mUVOffset, mUVScale;
float mTransparency;
bool mAlphaBlend;
std::uint32_t mSourceBlendMode;
std::uint32_t mDestinationBlendMode;
std::uint8_t mAlphaTestThreshold;
bool mAlphaTest;
bool mDepthWrite, mDepthTest;
bool mSSR;
bool mWetnessControlSSR;
bool mDecal;
bool mTwoSided;
bool mDecalNoFade;
bool mNonOccluder;
bool mRefraction;
bool mRefractionFalloff;
float mRefractionPower;
bool mEnvMapEnabled;
float mEnvMapMaskScale;
bool mDepthBias;
bool mGrayscaleToPaletteColor;
std::uint8_t mMaskWrites;
MaterialFile() = default;
virtual void read(BGSMStream& stream);
virtual ~MaterialFile() = default;
};
struct BGSMFile : MaterialFile
{
std::string mDiffuseMap;
std::string mNormalMap;
std::string mSmoothSpecMap;
std::string mGrayscaleMap;
std::string mGlowMap;
std::string mWrinkleMap;
std::string mSpecularMap;
std::string mLightingMap;
std::string mFlowMap;
std::string mDistanceFieldAlphaMap;
std::string mEnvMap;
std::string mInnerLayerMap;
std::string mDisplacementMap;
bool mEnableEditorAlphaThreshold;
bool mTranslucency;
bool mTranslucencyThickObject;
bool mTranslucencyMixAlbedoWithSubsurfaceColor;
osg::Vec3f mTranslucencySubsurfaceColor;
float mTranslucencyTransmissiveScale;
float mTranslucencyTurbulence;
bool mRimLighting;
float mRimPower;
float mBackLightPower;
bool mSubsurfaceLighting;
float mSubsurfaceLightingRolloff;
bool mSpecularEnabled;
osg::Vec3f mSpecularColor;
float mSpecularMult;
float mSmoothness;
float mFresnelPower;
float mWetnessControlSpecScale;
float mWetnessControlSpecPowerScale;
float mWetnessControlSpecMinvar;
float mWetnessControlEnvMapScale;
float mWetnessControlFresnelPower;
float mWetnessControlMetalness;
bool mPBR;
bool mCustomPorosity;
float mPorosityValue;
std::string mRootMaterialPath;
bool mAnisoLighting;
bool mEmitEnabled;
osg::Vec3f mEmittanceColor;
float mEmittanceMult;
bool mModelSpaceNormals;
bool mExternalEmittance;
float mLumEmittance;
bool mUseAdaptiveEmissive;
osg::Vec3f mAdaptiveEmissiveExposureParams;
bool mBackLighting;
bool mReceiveShadows;
bool mHideSecret;
bool mCastShadows;
bool mDissolveFade;
bool mAssumeShadowmask;
bool mGlowMapEnabled;
bool mEnvMapWindow;
bool mEnvMapEye;
bool mHair;
osg::Vec3f mHairTintColor;
bool mTree;
bool mFacegen;
bool mSkinTint;
bool mTessellate;
osg::Vec2f mDisplacementMapParams;
osg::Vec3f mTessellationParams;
float mGrayscaleToPaletteScale;
bool mSkewSpecularAlpha;
bool mTerrain;
osg::Vec3f mTerrainParams;
void read(BGSMStream& stream) override;
};
struct BGEMFile : MaterialFile
{
std::string mBaseMap;
std::string mGrayscaleMap;
std::string mEnvMap;
std::string mNormalMap;
std::string mEnvMapMask;
std::string mSpecularMap;
std::string mLightingMap;
std::string mGlowMap;
bool mBlood;
bool mEffectLighting;
bool mFalloff;
bool mFalloffColor;
bool mGrayscaleToPaletteAlpha;
bool mSoft;
osg::Vec3f mBaseColor;
float mBaseColorScale;
osg::Vec4f mFalloffParams;
float mLightingInfluence;
std::uint8_t mEnvmapMinLOD;
float mSoftDepth;
osg::Vec3f mEmittanceColor;
osg::Vec3f mAdaptiveEmissiveExposureParams;
bool mGlowMapEnabled;
bool mEffectPbrSpecular;
void read(BGSMStream& stream) override;
};
using MaterialFilePtr = std::shared_ptr<const Bgsm::MaterialFile>;
}
#endif

@ -0,0 +1,33 @@
#include "reader.hpp"
#include <array>
#include <stdexcept>
#include <string>
#include "stream.hpp"
namespace Bgsm
{
void Reader::parse(Files::IStreamPtr&& inputStream)
{
BGSMStream stream(std::move(inputStream));
std::array<char, 4> signature;
stream.readArray(signature);
std::string shaderType(signature.data(), 4);
if (shaderType == "BGEM")
{
mFile = std::make_unique<BGEMFile>();
mFile->mShaderType = Bgsm::ShaderType::Effect;
}
else if (shaderType == "BGSM")
{
mFile = std::make_unique<BGSMFile>();
mFile->mShaderType = Bgsm::ShaderType::Lighting;
}
else
throw std::runtime_error("Invalid material file");
mFile->read(stream);
}
}

@ -0,0 +1,22 @@
#ifndef OPENMW_COMPONENTS_BGSM_READER_HPP
#define OPENMW_COMPONENTS_BGSM_READER_HPP
#include <memory>
#include <components/files/istreamptr.hpp>
#include "file.hpp"
namespace Bgsm
{
class Reader
{
std::unique_ptr<MaterialFile> mFile;
public:
void parse(Files::IStreamPtr&& stream);
std::unique_ptr<MaterialFile> getFile() { return std::move(mFile); }
};
}
#endif

@ -0,0 +1,39 @@
#include "stream.hpp"
namespace Bgsm
{
template <>
void BGSMStream::read<osg::Vec2f>(osg::Vec2f& vec)
{
readBufferOfType(mStream, vec._v);
}
template <>
void BGSMStream::read<osg::Vec3f>(osg::Vec3f& vec)
{
readBufferOfType(mStream, vec._v);
}
template <>
void BGSMStream::read<osg::Vec4f>(osg::Vec4f& vec)
{
readBufferOfType(mStream, vec._v);
}
template <>
void BGSMStream::read<std::string>(std::string& str)
{
std::uint32_t length;
read(length);
// Prevent potential memory allocation freezes; strings this long are not expected in BGSM
if (length > 1024)
throw std::runtime_error("Requested string length is too large: " + std::to_string(length));
str = std::string(length, '\0');
mStream->read(str.data(), length);
if (mStream->bad())
throw std::runtime_error("Failed to read sized string of " + std::to_string(length) + " chars");
std::size_t end = str.find('\0');
if (end != std::string::npos)
str.erase(end);
}
}

@ -0,0 +1,77 @@
#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP
#define OPENMW_COMPONENTS_BGSM_STREAM_HPP
#include <array>
#include <cassert>
#include <cstdint>
#include <istream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <components/files/istreamptr.hpp>
#include <components/misc/endianness.hpp>
#include <osg/Vec2f>
#include <osg/Vec3f>
#include <osg/Vec4f>
namespace Bgsm
{
template <std::size_t numInstances, typename T>
inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest)
{
static_assert(std::is_arithmetic_v<T>, "Buffer element type is not arithmetic");
pIStream->read(reinterpret_cast<char*>(dest), numInstances * sizeof(T));
if (pIStream->bad())
throw std::runtime_error("Failed to read typed (" + std::string(typeid(T).name()) + ") buffer of "
+ std::to_string(numInstances) + " instances");
if constexpr (Misc::IS_BIG_ENDIAN)
for (std::size_t i = 0; i < numInstances; i++)
Misc::swapEndiannessInplace(dest[i]);
}
template <std::size_t numInstances, typename T>
inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances])
{
readBufferOfType<numInstances>(pIStream, static_cast<T*>(dest));
}
class BGSMStream
{
Files::IStreamPtr mStream;
public:
explicit BGSMStream(Files::IStreamPtr&& stream)
: mStream(std::move(stream))
{
}
void skip(size_t size) { mStream->ignore(size); }
/// Read into a single instance of type
template <class T>
void read(T& data)
{
readBufferOfType<1>(mStream, &data);
}
/// Read multiple instances of type into an array
template <class T, size_t size>
void readArray(std::array<T, size>& arr)
{
readBufferOfType<size>(mStream, arr.data());
}
};
template <>
void BGSMStream::read<osg::Vec2f>(osg::Vec2f& vec);
template <>
void BGSMStream::read<osg::Vec3f>(osg::Vec3f& vec);
template <>
void BGSMStream::read<osg::Vec4f>(osg::Vec4f& vec);
template <>
void BGSMStream::read<std::string>(std::string& str);
}
#endif

@ -173,6 +173,11 @@ std::string Misc::ResourceHelpers::correctActorModelPath(const std::string& resP
return mdlname;
}
std::string Misc::ResourceHelpers::correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs)
{
return correctResourcePath({ { "materials" } }, resPath, vfs);
}
std::string Misc::ResourceHelpers::correctMeshPath(std::string_view resPath)
{
std::string res = "meshes\\";

@ -34,6 +34,7 @@ namespace Misc
/// Use "xfoo.nif" instead of "foo.nif" if "xfoo.kf" is available
/// Note that if "xfoo.nif" is actually unavailable, we can't fall back to "foo.nif". :(
std::string correctActorModelPath(const std::string& resPath, const VFS::Manager* vfs);
std::string correctMaterialPath(std::string_view resPath, const VFS::Manager* vfs);
// Adds "meshes\\".
std::string correctMeshPath(std::string_view resPath);

@ -21,6 +21,7 @@
#include <components/misc/strings/algorithm.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/nif/parent.hpp>
#include <components/resource/bgsmfilemanager.hpp>
#include <components/resource/imagemanager.hpp>
// particle
@ -42,6 +43,7 @@
#include <osg/TexEnvCombine>
#include <osg/Texture2D>
#include <components/bgsm/file.hpp>
#include <components/nif/effect.hpp>
#include <components/nif/exception.hpp>
#include <components/nif/extra.hpp>
@ -247,6 +249,8 @@ namespace NifOsg
}
std::filesystem::path mFilename;
unsigned int mVersion, mUserVersion, mBethVersion;
Resource::BgsmFileManager* mMaterialManager{ nullptr };
Resource::ImageManager* mImageManager{ nullptr };
size_t mFirstRootTextureIndex{ ~0u };
bool mFoundFirstRootTexturingProperty = false;
@ -339,7 +343,6 @@ namespace NifOsg
struct HandleNodeArgs
{
unsigned int mNifVersion;
Resource::ImageManager* mImageManager;
SceneUtil::TextKeyMap* mTextKeys;
std::vector<unsigned int> mBoundTextures = {};
int mAnimFlags = 0;
@ -349,7 +352,7 @@ namespace NifOsg
osg::Node* mRootNode = nullptr;
};
osg::ref_ptr<osg::Node> load(Nif::FileView nif, Resource::ImageManager* imageManager)
osg::ref_ptr<osg::Node> load(Nif::FileView nif)
{
const size_t numRoots = nif.numRoots();
std::vector<const Nif::NiAVObject*> roots;
@ -371,10 +374,8 @@ namespace NifOsg
created->setDataVariance(osg::Object::STATIC);
for (const Nif::NiAVObject* root : roots)
{
auto node = handleNode(root, nullptr, nullptr,
{ .mNifVersion = nif.getVersion(),
.mImageManager = imageManager,
.mTextKeys = &textkeys->mTextKeys });
auto node = handleNode(
root, nullptr, nullptr, { .mNifVersion = nif.getVersion(), .mTextKeys = &textkeys->mTextKeys });
created->addChild(node);
}
if (mHasNightDayLabel)
@ -405,8 +406,7 @@ namespace NifOsg
}
void applyNodeProperties(const Nif::NiAVObject* nifNode, osg::Node* applyTo,
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager,
std::vector<unsigned int>& boundTextures, int animflags)
SceneUtil::CompositeStateSetUpdater* composite, std::vector<unsigned int>& boundTextures, int animflags)
{
bool hasStencilProperty = false;
@ -444,8 +444,7 @@ namespace NifOsg
if (property.getPtr()->recIndex == mFirstRootTextureIndex)
applyTo->setUserValue("overrideFx", 1);
}
handleProperty(property.getPtr(), applyTo, composite, imageManager, boundTextures, animflags,
hasStencilProperty);
handleProperty(property.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty);
}
}
@ -457,8 +456,7 @@ namespace NifOsg
shaderprop = static_cast<const Nif::BSTriShape*>(nifNode)->mShaderProperty;
if (!shaderprop.empty())
handleProperty(shaderprop.getPtr(), applyTo, composite, imageManager, boundTextures, animflags,
hasStencilProperty);
handleProperty(shaderprop.getPtr(), applyTo, composite, boundTextures, animflags, hasStencilProperty);
}
static void setupController(const Nif::NiTimeController* ctrl, SceneUtil::Controller* toSetup, int animflags)
@ -522,32 +520,21 @@ namespace NifOsg
sequenceNode->setMode(osg::Sequence::START);
}
osg::ref_ptr<osg::Image> handleSourceTexture(
const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager)
osg::ref_ptr<osg::Image> handleSourceTexture(const Nif::NiSourceTexture* st) const
{
if (!st)
return nullptr;
osg::ref_ptr<osg::Image> image;
if (st->mExternal)
if (st)
{
std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS());
image = imageManager->getImage(filename);
}
else if (!st->mData.empty())
{
image = handleInternalTexture(st->mData.getPtr());
if (st->mExternal)
return getTextureImage(st->mFile);
if (!st->mData.empty())
return handleInternalTexture(st->mData.getPtr());
}
return image;
}
void handleTextureWrapping(osg::Texture2D* texture, bool wrapS, bool wrapT)
{
texture->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
return nullptr;
}
bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset, Resource::ImageManager* imageManager)
bool handleEffect(const Nif::NiAVObject* nifNode, osg::StateSet* stateset)
{
if (nifNode->recType != Nif::RC_NiTextureEffect)
{
@ -590,16 +577,12 @@ namespace NifOsg
return false;
}
osg::ref_ptr<osg::Image> image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager));
osg::ref_ptr<osg::Texture2D> texture2d(new osg::Texture2D(image));
if (image)
texture2d->setTextureSize(image->s(), image->t());
texture2d->setName("envMap");
handleTextureWrapping(texture2d, textureEffect->wrapS(), textureEffect->wrapT());
int texUnit = 3; // FIXME
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
const unsigned int uvSet = 0;
const unsigned int texUnit = 3; // FIXME
std::vector<unsigned int> boundTextures;
boundTextures.resize(3); // Dummy vector for attachNiSourceTexture
attachNiSourceTexture("envMap", textureEffect->mTexture.getPtr(), textureEffect->wrapS(),
textureEffect->wrapT(), uvSet, stateset, boundTextures);
stateset->setTextureAttributeAndModes(texUnit, texGen, osg::StateAttribute::ON);
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
@ -761,7 +744,7 @@ namespace NifOsg
osg::ref_ptr<SceneUtil::CompositeStateSetUpdater> composite = new SceneUtil::CompositeStateSetUpdater;
applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags);
applyNodeProperties(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags);
const bool isNiGeometry = isTypeNiGeometry(nifNode->recType);
const bool isBSGeometry = isTypeBSGeometry(nifNode->recType);
@ -769,7 +752,7 @@ namespace NifOsg
if (isGeometry && !args.mSkipMeshes)
{
bool skip;
bool skip = false;
if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW)
{
skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "tri editormarker"))
@ -777,7 +760,11 @@ namespace NifOsg
|| Misc::StringUtils::ciStartsWith(nifNode->mName, "tri shadow");
}
else
skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker");
{
if (args.mHasMarkers)
skip = Misc::StringUtils::ciStartsWith(nifNode->mName, "EditorMarker")
|| Misc::StringUtils::ciStartsWith(nifNode->mName, "VisibilityEditorMarker");
}
if (!skip)
{
if (isNiGeometry)
@ -859,7 +846,7 @@ namespace NifOsg
if (!effect.empty())
{
osg::ref_ptr<osg::StateSet> effectStateSet = new osg::StateSet;
if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager))
if (handleEffect(effect.getPtr(), effectStateSet))
for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i)
currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet);
}
@ -1025,9 +1012,56 @@ namespace NifOsg
}
}
osg::ref_ptr<osg::Image> getTextureImage(std::string_view path) const
{
if (!mImageManager)
return nullptr;
std::string filename = Misc::ResourceHelpers::correctTexturePath(path, mImageManager->getVFS());
return mImageManager->getImage(filename);
}
osg::ref_ptr<osg::Texture2D> attachTexture(const std::string& name, osg::ref_ptr<osg::Image> image, bool wrapS,
bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
{
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
unsigned int texUnit = boundTextures.size();
if (stateset)
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
texture2d->setName(name);
boundTextures.emplace_back(uvSet);
return texture2d;
}
osg::ref_ptr<osg::Texture2D> attachExternalTexture(const std::string& name, const std::string& path, bool wrapS,
bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures) const
{
return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures);
}
osg::ref_ptr<osg::Texture2D> attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st,
bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset,
std::vector<unsigned int>& boundTextures) const
{
return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures);
}
static void clearBoundTextures(osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
{
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
}
void handleTextureControllers(const Nif::NiProperty* texProperty,
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager,
osg::StateSet* stateset, int animflags)
SceneUtil::CompositeStateSetUpdater* composite, osg::StateSet* stateset, int animflags)
{
for (Nif::NiTimeControllerPtr ctrl = texProperty->mController; !ctrl.empty(); ctrl = ctrl->mNext)
{
@ -1056,17 +1090,16 @@ namespace NifOsg
wrapT = inherit->getWrap(osg::Texture2D::WRAP_T);
}
const unsigned int uvSet = 0;
std::vector<unsigned int> boundTextures; // Dummy list for attachTexture
for (const auto& source : flipctrl->mSources)
{
if (source.empty())
continue;
osg::ref_ptr<osg::Image> image(handleSourceTexture(source.getPtr(), imageManager));
osg::ref_ptr<osg::Texture2D> texture(new osg::Texture2D(image));
if (image)
texture->setTextureSize(image->s(), image->t());
texture->setWrap(osg::Texture::WRAP_S, wrapS);
texture->setWrap(osg::Texture::WRAP_T, wrapT);
// NB: not changing the stateset
osg::ref_ptr<osg::Texture2D> texture
= attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures);
textures.push_back(texture);
}
osg::ref_ptr<FlipController> callback(new FlipController(flipctrl, textures));
@ -1811,7 +1844,7 @@ namespace NifOsg
}
}
osg::ref_ptr<osg::Image> handleInternalTexture(const Nif::NiPixelData* pixelData)
osg::ref_ptr<osg::Image> handleInternalTexture(const Nif::NiPixelData* pixelData) const
{
if (pixelData->mMipmaps.empty())
return nullptr;
@ -1946,7 +1979,7 @@ namespace NifOsg
return image;
}
osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
static osg::ref_ptr<osg::TexEnvCombine> createEmissiveTexEnv()
{
osg::ref_ptr<osg::TexEnvCombine> texEnv(new osg::TexEnvCombine);
// Sum the previous colour and the emissive colour.
@ -1977,33 +2010,42 @@ namespace NifOsg
void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName,
osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite,
Resource::ImageManager* imageManager, std::vector<unsigned int>& boundTextures, int animflags)
std::vector<unsigned int>& boundTextures, int animflags)
{
if (!boundTextures.empty())
{
// overriding a parent NiTexturingProperty, so remove what was previously bound
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
// overriding a parent NiTexturingProperty, so remove what was previously bound
clearBoundTextures(stateset, boundTextures);
// If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the
// shadow casting shader will need to be updated accordingly.
for (size_t i = 0; i < texprop->mTextures.size(); ++i)
{
if (texprop->mTextures[i].mEnabled
|| (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty()))
const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i];
if (tex.mEnabled || (i == Nif::NiTexturingProperty::BaseTexture && !texprop->mController.empty()))
{
std::string textureName;
switch (i)
{
// These are handled later on
case Nif::NiTexturingProperty::BaseTexture:
textureName = "diffuseMap";
break;
case Nif::NiTexturingProperty::GlowTexture:
textureName = "glowMap";
break;
case Nif::NiTexturingProperty::DarkTexture:
textureName = "darkMap";
break;
case Nif::NiTexturingProperty::BumpTexture:
textureName = "bumpMap";
break;
case Nif::NiTexturingProperty::DetailTexture:
textureName = "detailMap";
break;
case Nif::NiTexturingProperty::DecalTexture:
textureName = "decalMap";
break;
case Nif::NiTexturingProperty::GlossTexture:
textureName = "glossMap";
break;
default:
{
@ -2013,12 +2055,9 @@ namespace NifOsg
}
}
unsigned int uvSet = 0;
// create a new texture, will later attempt to share using the SharedStateManager
osg::ref_ptr<osg::Texture2D> texture2d;
if (texprop->mTextures[i].mEnabled)
const unsigned int texUnit = boundTextures.size();
if (tex.mEnabled)
{
const Nif::NiTexturingProperty::Texture& tex = texprop->mTextures[i];
if (tex.mSourceTexture.empty() && texprop->mController.empty())
{
if (i == 0)
@ -2028,32 +2067,18 @@ namespace NifOsg
}
if (!tex.mSourceTexture.empty())
{
const Nif::NiSourceTexture* st = tex.mSourceTexture.getPtr();
osg::ref_ptr<osg::Image> image = handleSourceTexture(st, imageManager);
texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
}
attachNiSourceTexture(textureName, tex.mSourceTexture.getPtr(), tex.wrapS(), tex.wrapT(),
tex.mUVSet, stateset, boundTextures);
else
texture2d = new osg::Texture2D;
handleTextureWrapping(texture2d, tex.wrapS(), tex.wrapT());
uvSet = tex.mUVSet;
attachTexture(
textureName, nullptr, tex.wrapS(), tex.wrapT(), tex.mUVSet, stateset, boundTextures);
}
else
{
// Texture only comes from NiFlipController, so tex is ignored, set defaults
texture2d = new osg::Texture2D;
handleTextureWrapping(texture2d, true, true);
uvSet = 0;
attachTexture(textureName, nullptr, true, true, 0, stateset, boundTextures);
}
unsigned int texUnit = boundTextures.size();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
if (i == Nif::NiTexturingProperty::GlowTexture)
{
stateset->setTextureAttributeAndModes(texUnit, createEmissiveTexEnv(), osg::StateAttribute::ON);
@ -2121,51 +2146,165 @@ namespace NifOsg
texEnv->setOperand0_Alpha(osg::TexEnvCombine::SRC_ALPHA);
stateset->setTextureAttributeAndModes(texUnit, texEnv, osg::StateAttribute::ON);
}
}
}
handleTextureControllers(texprop, composite, stateset, animflags);
}
switch (i)
{
case Nif::NiTexturingProperty::BaseTexture:
texture2d->setName("diffuseMap");
break;
case Nif::NiTexturingProperty::BumpTexture:
texture2d->setName("bumpMap");
break;
case Nif::NiTexturingProperty::GlowTexture:
texture2d->setName("emissiveMap");
break;
case Nif::NiTexturingProperty::DarkTexture:
texture2d->setName("darkMap");
break;
case Nif::NiTexturingProperty::DetailTexture:
texture2d->setName("detailMap");
break;
case Nif::NiTexturingProperty::DecalTexture:
texture2d->setName("decalMap");
break;
case Nif::NiTexturingProperty::GlossTexture:
texture2d->setName("glossMap");
break;
default:
break;
}
static Bgsm::MaterialFilePtr getShaderMaterial(
std::string_view path, Resource::BgsmFileManager* materialManager)
{
if (!materialManager)
return nullptr;
if (!Misc::StringUtils::ciEndsWith(path, ".bgem") && !Misc::StringUtils::ciEndsWith(path, ".bgsm"))
return nullptr;
std::string normalizedPath = Misc::ResourceHelpers::correctMaterialPath(path, materialManager->getVFS());
try
{
return materialManager->get(VFS::Path::Normalized(normalizedPath));
}
catch (std::exception& e)
{
Log(Debug::Error) << "Failed to load shader material: " << e.what();
return nullptr;
}
}
void handleShaderMaterialNodeProperties(
Bgsm::MaterialFilePtr material, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
{
const unsigned int uvSet = 0;
const bool wrapS = (material->mClamp >> 1) & 0x1;
const bool wrapT = material->mClamp & 0x1;
if (material->mShaderType == Bgsm::ShaderType::Lighting)
{
const Bgsm::BGSMFile* bgsm = static_cast<const Bgsm::BGSMFile*>(material.get());
if (!bgsm->mDiffuseMap.empty())
attachExternalTexture(
"diffuseMap", bgsm->mDiffuseMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (!bgsm->mNormalMap.empty())
attachExternalTexture("normalMap", bgsm->mNormalMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (bgsm->mGlowMapEnabled && !bgsm->mGlowMap.empty())
attachExternalTexture("emissiveMap", bgsm->mGlowMap, wrapS, wrapT, uvSet, stateset, boundTextures);
if (bgsm->mTree)
stateset->addUniform(new osg::Uniform("useTreeAnim", true));
}
else if (material->mShaderType == Bgsm::ShaderType::Effect)
{
const Bgsm::BGEMFile* bgem = static_cast<const Bgsm::BGEMFile*>(material.get());
if (!bgem->mBaseMap.empty())
attachExternalTexture("diffuseMap", bgem->mBaseMap, wrapS, wrapT, uvSet, stateset, boundTextures);
bool useFalloff = bgem->mFalloff;
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
if (useFalloff)
stateset->addUniform(new osg::Uniform("falloffParams", bgem->mFalloffParams));
}
if (material->mTwoSided)
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
handleDepthFlags(stateset, material->mDepthTest, material->mDepthWrite);
}
void handleDecal(bool enabled, bool hasSortAlpha, osg::Node& node)
{
if (!enabled)
return;
osg::ref_ptr<osg::StateSet> stateset = node.getOrCreateStateSet();
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
polygonOffset = shareAttribute(polygonOffset);
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
if (!mPushedSorter && !hasSortAlpha)
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
}
void handleAlphaTesting(
bool enabled, osg::AlphaFunc::ComparisonFunction function, int threshold, osg::Node& node)
{
if (enabled)
{
osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(function, threshold / 255.f));
alphaFunc = shareAttribute(alphaFunc);
node.getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
}
else if (osg::StateSet* stateset = node.getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
stateset->removeMode(GL_ALPHA_TEST);
}
}
void handleAlphaBlending(
bool enabled, int sourceMode, int destMode, bool sort, bool& hasSortAlpha, osg::Node& node)
{
if (enabled)
{
osg::ref_ptr<osg::StateSet> stateset = node.getOrCreateStateSet();
osg::ref_ptr<osg::BlendFunc> blendFunc(
new osg::BlendFunc(getBlendMode(sourceMode), getBlendMode(destMode)));
// on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL.
// This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug.
// Either way, D3D8.1 doesn't do that, so adapt the destination factor.
if (blendFunc->getDestination() == GL_DST_ALPHA)
blendFunc->setDestination(GL_ONE);
blendFunc = shareAttribute(blendFunc);
stateset->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
boundTextures.push_back(uvSet);
if (sort)
{
hasSortAlpha = true;
if (!mPushedSorter)
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
else if (!mPushedSorter)
{
stateset->setRenderBinToInherit();
}
}
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
else if (osg::ref_ptr<osg::StateSet> stateset = node.getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
stateset->removeMode(GL_BLEND);
if (!mPushedSorter)
stateset->setRenderBinToInherit();
}
}
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp,
const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager,
std::vector<unsigned int>& boundTextures)
void handleShaderMaterialDrawableProperties(
Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr<osg::Material> mat, osg::Node& node, bool& hasSortAlpha)
{
if (!boundTextures.empty())
mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderMat->mTransparency);
handleAlphaTesting(shaderMat->mAlphaTest, osg::AlphaFunc::GREATER, shaderMat->mAlphaTestThreshold, node);
handleAlphaBlending(shaderMat->mAlphaBlend, shaderMat->mSourceBlendMode, shaderMat->mDestinationBlendMode,
true, hasSortAlpha, node);
handleDecal(shaderMat->mDecal, hasSortAlpha, node);
if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting)
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
auto bgsm = static_cast<const Bgsm::BGSMFile*>(shaderMat.get());
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mEmittanceColor, 1.f));
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgsm->mSpecularColor, 1.f));
}
else if (shaderMat->mShaderType == Bgsm::ShaderType::Effect)
{
auto bgem = static_cast<const Bgsm::BGEMFile*>(shaderMat.get());
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(bgem->mEmittanceColor, 1.f));
if (bgem->mSoft)
SceneUtil::setupSoftEffect(node, bgem->mSoftDepth, true, bgem->mSoftDepth);
}
}
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, bool wrapS, bool wrapT,
const std::string& nodeName, osg::StateSet* stateset, std::vector<unsigned int>& boundTextures)
{
const unsigned int uvSet = 0;
for (size_t i = 0; i < textureSet->mTextures.size(); ++i)
@ -2175,8 +2314,16 @@ namespace NifOsg
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
{
case Nif::BSShaderTextureSet::TextureType::Base:
attachExternalTexture(
"diffuseMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
break;
case Nif::BSShaderTextureSet::TextureType::Normal:
attachExternalTexture(
"normalMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
break;
case Nif::BSShaderTextureSet::TextureType::Glow:
attachExternalTexture(
"emissiveMap", textureSet->mTextures[i], wrapS, wrapT, uvSet, stateset, boundTextures);
break;
default:
{
@ -2185,31 +2332,6 @@ namespace NifOsg
continue;
}
}
std::string filename
= Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
handleTextureWrapping(texture2d, (clamp >> 1) & 0x1, clamp & 0x1);
unsigned int texUnit = boundTextures.size();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
// BSShaderTextureSet presence means there's no need for FFP support for the affected node
switch (static_cast<Nif::BSShaderTextureSet::TextureType>(i))
{
case Nif::BSShaderTextureSet::TextureType::Base:
texture2d->setName("diffuseMap");
break;
case Nif::BSShaderTextureSet::TextureType::Normal:
texture2d->setName("normalMap");
break;
case Nif::BSShaderTextureSet::TextureType::Glow:
texture2d->setName("emissiveMap");
break;
default:
break;
}
boundTextures.emplace_back(uvSet);
}
}
@ -2269,8 +2391,8 @@ namespace NifOsg
}
void handleProperty(const Nif::NiProperty* property, osg::Node* node,
SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager,
std::vector<unsigned int>& boundTextures, int animflags, bool hasStencilProperty)
SceneUtil::CompositeStateSetUpdater* composite, std::vector<unsigned int>& boundTextures, int animflags,
bool hasStencilProperty)
{
switch (property->recType)
{
@ -2352,8 +2474,7 @@ namespace NifOsg
{
const Nif::NiTexturingProperty* texprop = static_cast<const Nif::NiTexturingProperty*>(property);
osg::StateSet* stateset = node->getOrCreateStateSet();
handleTextureProperty(
texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags);
handleTextureProperty(texprop, node->getName(), stateset, composite, boundTextures, animflags);
node->setUserValue("applyMode", static_cast<int>(texprop->mApplyMode));
break;
}
@ -2364,13 +2485,13 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType)));
node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
const bool wrapT = texprop->mClamp & 0x1;
if (!texprop->mTextureSet.empty())
{
auto textureSet = texprop->mTextureSet.getPtr();
handleTextureSet(
textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures);
}
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures);
handleTextureControllers(texprop, composite, stateset, animflags);
if (texprop->refraction())
SceneUtil::setupDistortion(*node, texprop->mRefraction.mStrength);
break;
@ -2383,34 +2504,20 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string(getBSShaderPrefix(texprop->mType)));
node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
if (!texprop->mFilename.empty())
{
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
std::string filename
= Misc::ResourceHelpers::correctTexturePath(texprop->mFilename, imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
texture2d->setName("diffuseMap");
if (image)
texture2d->setTextureSize(image->s(), image->t());
handleTextureWrapping(texture2d, texprop->wrapS(), texprop->wrapT());
const unsigned int texUnit = 0;
const unsigned int uvSet = 0;
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
boundTextures.push_back(uvSet);
if (mBethVersion >= 27)
{
useFalloff = true;
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
}
attachExternalTexture("diffuseMap", texprop->mFilename, texprop->wrapS(), texprop->wrapT(),
uvSet, stateset, boundTextures);
}
if (mBethVersion >= 27)
{
useFalloff = true;
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
}
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
handleTextureControllers(texprop, composite, stateset, animflags);
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
break;
}
@ -2421,10 +2528,18 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string(getBSLightingShaderPrefix(texprop->mType)));
node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
{
handleShaderMaterialNodeProperties(material, stateset, boundTextures);
break;
}
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
const bool wrapT = texprop->mClamp & 0x1;
if (!texprop->mTextureSet.empty())
handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset,
imageManager, boundTextures);
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
handleTextureSet(
texprop->mTextureSet.getPtr(), wrapS, wrapT, node->getName(), stateset, boundTextures);
handleTextureControllers(texprop, composite, stateset, animflags);
if (texprop->doubleSided())
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
if (texprop->treeAnim())
@ -2442,27 +2557,20 @@ namespace NifOsg
node->setUserValue("shaderPrefix", std::string("bs/nolighting"));
node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet();
clearBoundTextures(stateset, boundTextures);
if (Bgsm::MaterialFilePtr material = getShaderMaterial(texprop->mName, mMaterialManager))
{
handleShaderMaterialNodeProperties(material, stateset, boundTextures);
break;
}
if (!texprop->mSourceTexture.empty())
{
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
std::string filename = Misc::ResourceHelpers::correctTexturePath(
texprop->mSourceTexture, imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
texture2d->setName("diffuseMap");
if (image)
texture2d->setTextureSize(image->s(), image->t());
handleTextureWrapping(texture2d, (texprop->mClamp >> 1) & 0x1, texprop->mClamp & 0x1);
const unsigned int texUnit = 0;
const unsigned int uvSet = 0;
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
boundTextures.push_back(uvSet);
const bool wrapS = (texprop->mClamp >> 1) & 0x1;
const bool wrapT = texprop->mClamp & 0x1;
unsigned int texUnit = boundTextures.size();
attachExternalTexture(
"diffuseMap", texprop->mSourceTexture, wrapS, wrapT, uvSet, stateset, boundTextures);
{
osg::ref_ptr<osg::TexMat> texMat(new osg::TexMat);
// This handles 20.2.0.7 UV settings like 4.0.0.2 UV settings (see NifOsg::UVController)
@ -2484,7 +2592,7 @@ namespace NifOsg
stateset->addUniform(new osg::Uniform("useFalloff", useFalloff));
if (useFalloff)
stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams));
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
handleTextureControllers(texprop, composite, stateset, animflags);
if (texprop->doubleSided())
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite());
@ -2566,12 +2674,9 @@ namespace NifOsg
bool hasMatCtrl = false;
bool hasSortAlpha = false;
osg::StateSet* blendFuncStateSet = nullptr;
auto setBin_Transparent = [](osg::StateSet* ss) { ss->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); };
auto setBin_BackToFront = [](osg::StateSet* ss) { ss->setRenderBinDetails(0, "SORT_BACK_TO_FRONT"); };
auto setBin_Traversal = [](osg::StateSet* ss) { ss->setRenderBinDetails(2, "TraversalOrderBin"); };
auto setBin_Inherit = [](osg::StateSet* ss) { ss->setRenderBinToInherit(); };
auto lightmode = Nif::NiVertexColorProperty::LightMode::LightMode_EmiAmbDif;
float emissiveMult = 1.f;
@ -2657,52 +2762,10 @@ namespace NifOsg
case Nif::RC_NiAlphaProperty:
{
const Nif::NiAlphaProperty* alphaprop = static_cast<const Nif::NiAlphaProperty*>(property);
if (alphaprop->useAlphaBlending())
{
osg::ref_ptr<osg::BlendFunc> blendFunc(
new osg::BlendFunc(getBlendMode(alphaprop->sourceBlendMode()),
getBlendMode(alphaprop->destinationBlendMode())));
// on AMD hardware, alpha still seems to be stored with an RGBA framebuffer with OpenGL.
// This might be mandated by the OpenGL 2.1 specification section 2.14.9, or might be a bug.
// Either way, D3D8.1 doesn't do that, so adapt the destination factor.
if (blendFunc->getDestination() == GL_DST_ALPHA)
blendFunc->setDestination(GL_ONE);
blendFunc = shareAttribute(blendFunc);
node->getOrCreateStateSet()->setAttributeAndModes(blendFunc, osg::StateAttribute::ON);
if (!alphaprop->noSorter())
{
hasSortAlpha = true;
if (!mPushedSorter)
setBin_Transparent(node->getStateSet());
}
else
{
if (!mPushedSorter)
setBin_Inherit(node->getStateSet());
}
}
else if (osg::StateSet* stateset = node->getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::BLENDFUNC);
stateset->removeMode(GL_BLEND);
blendFuncStateSet = stateset;
if (!mPushedSorter)
blendFuncStateSet->setRenderBinToInherit();
}
if (alphaprop->useAlphaTesting())
{
osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(
getTestMode(alphaprop->alphaTestMode()), alphaprop->mThreshold / 255.f));
alphaFunc = shareAttribute(alphaFunc);
node->getOrCreateStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
}
else if (osg::StateSet* stateset = node->getStateSet())
{
stateset->removeAttribute(osg::StateAttribute::ALPHAFUNC);
stateset->removeMode(GL_ALPHA_TEST);
}
handleAlphaBlending(alphaprop->useAlphaBlending(), alphaprop->sourceBlendMode(),
alphaprop->destinationBlendMode(), !alphaprop->noSorter(), hasSortAlpha, *node);
handleAlphaTesting(alphaprop->useAlphaTesting(), getTestMode(alphaprop->alphaTestMode()),
alphaprop->mThreshold, *node);
break;
}
case Nif::RC_BSShaderPPLightingProperty:
@ -2714,6 +2777,18 @@ namespace NifOsg
case Nif::RC_BSLightingShaderProperty:
{
auto shaderprop = static_cast<const Nif::BSLightingShaderProperty*>(property);
if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager))
{
handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha);
if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting)
{
auto bgsm = static_cast<const Bgsm::BGSMFile*>(shaderMat.get());
specEnabled = false; // bgsm->mSpecularEnabled; TODO: PBR specular lighting
specStrength = 1.f; // bgsm->mSpecularMult;
emissiveMult = bgsm->mEmittanceMult;
}
break;
}
mat->setAlpha(osg::Material::FRONT_AND_BACK, shaderprop->mAlpha);
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mEmissive, 1.f));
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(shaderprop->mSpecular, 1.f));
@ -2722,31 +2797,18 @@ namespace NifOsg
emissiveMult = shaderprop->mEmissiveMult;
specStrength = shaderprop->mSpecStrength;
specEnabled = shaderprop->specular();
if (shaderprop->decal())
{
osg::StateSet* stateset = node->getOrCreateStateSet();
if (!mPushedSorter && !hasSortAlpha)
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
}
handleDecal(shaderprop->decal(), hasSortAlpha, *node);
break;
}
case Nif::RC_BSEffectShaderProperty:
{
auto shaderprop = static_cast<const Nif::BSEffectShaderProperty*>(property);
if (shaderprop->decal())
if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager))
{
osg::StateSet* stateset = node->getOrCreateStateSet();
if (!mPushedSorter && !hasSortAlpha)
stateset->setRenderBinDetails(1, "SORT_BACK_TO_FRONT");
osg::ref_ptr<osg::PolygonOffset> polygonOffset(new osg::PolygonOffset);
polygonOffset->setUnits(SceneUtil::AutoDepth::isReversed() ? 1.f : -1.f);
polygonOffset->setFactor(SceneUtil::AutoDepth::isReversed() ? 0.65f : -0.65f);
stateset->setAttributeAndModes(polygonOffset, osg::StateAttribute::ON);
handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha);
break;
}
handleDecal(shaderprop->decal(), hasSortAlpha, *node);
if (shaderprop->softEffect())
SceneUtil::setupSoftEffect(
*node, shaderprop->mFalloffDepth, true, shaderprop->mFalloffDepth);
@ -2860,10 +2922,13 @@ namespace NifOsg
}
};
osg::ref_ptr<osg::Node> Loader::load(Nif::FileView file, Resource::ImageManager* imageManager)
osg::ref_ptr<osg::Node> Loader::load(
Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager)
{
LoaderImpl impl(file.getFilename(), file.getVersion(), file.getUserVersion(), file.getBethVersion());
return impl.load(file, imageManager);
impl.mMaterialManager = materialManager;
impl.mImageManager = imageManager;
return impl.load(file);
}
void Loader::loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target)

@ -18,6 +18,7 @@ namespace osg
namespace Resource
{
class ImageManager;
class BgsmFileManager;
}
namespace NifOsg
@ -30,7 +31,8 @@ namespace NifOsg
public:
/// Create a scene graph for the given NIF. Auto-detects when skinning is used and wraps the graph in a Skeleton
/// if so.
static osg::ref_ptr<osg::Node> load(Nif::FileView file, Resource::ImageManager* imageManager);
static osg::ref_ptr<osg::Node> load(
Nif::FileView file, Resource::ImageManager* imageManager, Resource::BgsmFileManager* materialManager);
/// Load keyframe controllers from the given kf file.
static void loadKf(Nif::FileView kf, SceneUtil::KeyframeHolder& target);

@ -0,0 +1,58 @@
#include "bgsmfilemanager.hpp"
#include <osg/Object>
#include <components/bgsm/reader.hpp>
#include <components/vfs/manager.hpp>
#include "objectcache.hpp"
namespace Resource
{
class BgsmFileHolder : public osg::Object
{
public:
BgsmFileHolder(const Bgsm::MaterialFilePtr& file)
: mBgsmFile(file)
{
}
BgsmFileHolder(const BgsmFileHolder& copy, const osg::CopyOp& copyop)
: mBgsmFile(copy.mBgsmFile)
{
}
BgsmFileHolder() = default;
META_Object(Resource, BgsmFileHolder)
Bgsm::MaterialFilePtr mBgsmFile;
};
BgsmFileManager::BgsmFileManager(const VFS::Manager* vfs, double expiryDelay)
: ResourceManager(vfs, expiryDelay)
{
}
Bgsm::MaterialFilePtr BgsmFileManager::get(VFS::Path::NormalizedView name)
{
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(name);
if (obj)
return static_cast<BgsmFileHolder*>(obj.get())->mBgsmFile;
else
{
Bgsm::Reader reader;
reader.parse(mVFS->get(name));
Bgsm::MaterialFilePtr file = reader.getFile();
obj = new BgsmFileHolder(file);
mCache->addEntryToObjectCache(name.value(), obj);
return file;
}
}
void BgsmFileManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
{
Resource::reportStats("BSShader Material", frameNumber, mCache->getStats(), *stats);
}
}

@ -0,0 +1,27 @@
#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H
#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H
#include <components/bgsm/file.hpp>
#include "resourcemanager.hpp"
namespace Resource
{
/// @brief Handles caching of material files.
/// @note May be used from any thread.
class BgsmFileManager : public ResourceManager
{
public:
BgsmFileManager(const VFS::Manager* vfs, double expiryDelay);
~BgsmFileManager() = default;
/// Retrieve a material file from the cache or load it from the VFS if not cached yet.
Bgsm::MaterialFilePtr get(VFS::Path::NormalizedView name);
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
};
}
#endif

@ -2,6 +2,7 @@
#include <algorithm>
#include "bgsmfilemanager.hpp"
#include "imagemanager.hpp"
#include "keyframemanager.hpp"
#include "niffilemanager.hpp"
@ -15,11 +16,14 @@ namespace Resource
: mVFS(vfs)
{
mNifFileManager = std::make_unique<NifFileManager>(vfs, encoder);
mBgsmFileManager = std::make_unique<BgsmFileManager>(vfs, expiryDelay);
mImageManager = std::make_unique<ImageManager>(vfs, expiryDelay);
mSceneManager = std::make_unique<SceneManager>(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay);
mSceneManager = std::make_unique<SceneManager>(
vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay);
mKeyframeManager = std::make_unique<KeyframeManager>(vfs, mSceneManager.get(), expiryDelay, encoder);
addResourceManager(mNifFileManager.get());
addResourceManager(mBgsmFileManager.get());
addResourceManager(mKeyframeManager.get());
// note, scene references images so add images afterwards for correct implementation of updateCache()
addResourceManager(mSceneManager.get());
@ -43,6 +47,11 @@ namespace Resource
return mImageManager.get();
}
BgsmFileManager* ResourceSystem::getBgsmFileManager()
{
return mBgsmFileManager.get();
}
NifFileManager* ResourceSystem::getNifFileManager()
{
return mNifFileManager.get();

@ -25,6 +25,7 @@ namespace Resource
class SceneManager;
class ImageManager;
class BgsmFileManager;
class NifFileManager;
class KeyframeManager;
class BaseResourceManager;
@ -41,6 +42,7 @@ namespace Resource
SceneManager* getSceneManager();
ImageManager* getImageManager();
BgsmFileManager* getBgsmFileManager();
NifFileManager* getNifFileManager();
KeyframeManager* getKeyframeManager();
@ -74,6 +76,7 @@ namespace Resource
private:
std::unique_ptr<SceneManager> mSceneManager;
std::unique_ptr<ImageManager> mImageManager;
std::unique_ptr<BgsmFileManager> mBgsmFileManager;
std::unique_ptr<NifFileManager> mNifFileManager;
std::unique_ptr<KeyframeManager> mKeyframeManager;

@ -55,6 +55,7 @@
#include <components/files/hash.hpp>
#include <components/files/memorystream.hpp>
#include "bgsmfilemanager.hpp"
#include "errormarker.hpp"
#include "imagemanager.hpp"
#include "niffilemanager.hpp"
@ -409,7 +410,7 @@ namespace Resource
};
SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
Resource::NifFileManager* nifFileManager, double expiryDelay)
Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay)
: ResourceManager(vfs, expiryDelay)
, mShaderManager(new Shader::ShaderManager)
, mForceShaders(false)
@ -424,6 +425,7 @@ namespace Resource
, mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager)
, mNifFileManager(nifFileManager)
, mBgsmFileManager(bgsmFileManager)
, mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR)
, mMagFilter(osg::Texture::LINEAR)
, mMaxAnisotropy(1)
@ -795,11 +797,12 @@ namespace Resource
}
osg::ref_ptr<osg::Node> load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs,
Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager)
Resource::ImageManager* imageManager, Resource::NifFileManager* nifFileManager,
Resource::BgsmFileManager* materialMgr)
{
const std::string_view ext = Misc::getFileExtension(normalizedFilename.value());
if (ext == "nif")
return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager);
return NifOsg::Loader::load(*nifFileManager->get(normalizedFilename), imageManager, materialMgr);
else if (ext == "spt")
{
Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename;
@ -921,7 +924,7 @@ namespace Resource
{
path.changeExtension(meshType);
if (mVFS->exists(path))
return load(path, mVFS, mImageManager, mNifFileManager);
return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
}
}
catch (const std::exception& e)
@ -953,7 +956,7 @@ namespace Resource
osg::ref_ptr<osg::Node> loaded;
try
{
loaded = load(normalized, mVFS, mImageManager, mNifFileManager);
loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this);
loaded->accept(extraDataVisitor);

@ -32,6 +32,7 @@ namespace Resource
{
class ImageManager;
class NifFileManager;
class BgsmFileManager;
class SharedStateManager;
}
@ -90,7 +91,7 @@ namespace Resource
{
public:
explicit SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
Resource::NifFileManager* nifFileManager, double expiryDelay);
Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay);
~SceneManager();
Shader::ShaderManager& getShaderManager();
@ -259,6 +260,7 @@ namespace Resource
Resource::ImageManager* mImageManager;
Resource::NifFileManager* mNifFileManager;
Resource::BgsmFileManager* mBgsmFileManager;
osg::Texture::FilterMode mMinFilter;
osg::Texture::FilterMode mMagFilter;

@ -87,6 +87,7 @@ namespace Resource
"Image",
"Nif",
"Keyframe",
"BSShader Material",
"Groundcover Chunk",
"Object Chunk",
"Terrain Chunk",

Loading…
Cancel
Save