diff --git a/CHANGELOG.md b/CHANGELOG.md index 35cf145f82..caa3299c97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/apps/bulletobjecttool/main.cpp b/apps/bulletobjecttool/main.cpp index b27c8135d6..4dbdb56350 100644 --- a/apps/bulletobjecttool/main.cpp +++ b/apps/bulletobjecttool/main.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -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( diff --git a/apps/navmeshtool/main.cpp b/apps/navmeshtool/main.cpp index 94ab7ef082..d75a1af5e2 100644 --- a/apps/navmeshtool/main.cpp +++ b/apps/navmeshtool/main.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -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(); diff --git a/apps/niftest/niftest.cpp b/apps/niftest/niftest.cpp index b37d85d739..8f8c408a87 100644 --- a/apps/niftest/niftest.cpp +++ b/apps/niftest/niftest.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -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 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&& 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&& 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 + niftest 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; } } diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index f05d651301..fa023fff0d 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -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 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(&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(&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)); } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 68411be2fc..df23121c5c 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -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 diff --git a/components/bgsm/file.cpp b/components/bgsm/file.cpp new file mode 100644 index 0000000000..6b763321be --- /dev/null +++ b/components/bgsm/file.cpp @@ -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); + } +} diff --git a/components/bgsm/file.hpp b/components/bgsm/file.hpp new file mode 100644 index 0000000000..d3fb189256 --- /dev/null +++ b/components/bgsm/file.hpp @@ -0,0 +1,164 @@ +#ifndef OPENMW_COMPONENTS_BGSM_FILE_HPP +#define OPENMW_COMPONENTS_BGSM_FILE_HPP + +#include +#include +#include +#include + +#include +#include +#include + +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; +} +#endif diff --git a/components/bgsm/reader.cpp b/components/bgsm/reader.cpp new file mode 100644 index 0000000000..eefc8b48b5 --- /dev/null +++ b/components/bgsm/reader.cpp @@ -0,0 +1,33 @@ +#include "reader.hpp" + +#include +#include +#include + +#include "stream.hpp" + +namespace Bgsm +{ + void Reader::parse(Files::IStreamPtr&& inputStream) + { + BGSMStream stream(std::move(inputStream)); + + std::array signature; + stream.readArray(signature); + std::string shaderType(signature.data(), 4); + if (shaderType == "BGEM") + { + mFile = std::make_unique(); + mFile->mShaderType = Bgsm::ShaderType::Effect; + } + else if (shaderType == "BGSM") + { + mFile = std::make_unique(); + mFile->mShaderType = Bgsm::ShaderType::Lighting; + } + else + throw std::runtime_error("Invalid material file"); + + mFile->read(stream); + } +} diff --git a/components/bgsm/reader.hpp b/components/bgsm/reader.hpp new file mode 100644 index 0000000000..48508c9143 --- /dev/null +++ b/components/bgsm/reader.hpp @@ -0,0 +1,22 @@ +#ifndef OPENMW_COMPONENTS_BGSM_READER_HPP +#define OPENMW_COMPONENTS_BGSM_READER_HPP + +#include + +#include + +#include "file.hpp" + +namespace Bgsm +{ + class Reader + { + std::unique_ptr mFile; + + public: + void parse(Files::IStreamPtr&& stream); + + std::unique_ptr getFile() { return std::move(mFile); } + }; +} +#endif diff --git a/components/bgsm/stream.cpp b/components/bgsm/stream.cpp new file mode 100644 index 0000000000..c4fa9c1d8c --- /dev/null +++ b/components/bgsm/stream.cpp @@ -0,0 +1,39 @@ +#include "stream.hpp" + +namespace Bgsm +{ + template <> + void BGSMStream::read(osg::Vec2f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec3f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(osg::Vec4f& vec) + { + readBufferOfType(mStream, vec._v); + } + + template <> + void BGSMStream::read(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); + } +} diff --git a/components/bgsm/stream.hpp b/components/bgsm/stream.hpp new file mode 100644 index 0000000000..a355523367 --- /dev/null +++ b/components/bgsm/stream.hpp @@ -0,0 +1,77 @@ +#ifndef OPENMW_COMPONENTS_BGSM_STREAM_HPP +#define OPENMW_COMPONENTS_BGSM_STREAM_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Bgsm +{ + template + inline void readBufferOfType(Files::IStreamPtr& pIStream, T* dest) + { + static_assert(std::is_arithmetic_v, "Buffer element type is not arithmetic"); + pIStream->read(reinterpret_cast(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 + inline void readBufferOfType(Files::IStreamPtr& pIStream, T (&dest)[numInstances]) + { + readBufferOfType(pIStream, static_cast(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 + void read(T& data) + { + readBufferOfType<1>(mStream, &data); + } + + /// Read multiple instances of type into an array + template + void readArray(std::array& arr) + { + readBufferOfType(mStream, arr.data()); + } + }; + + template <> + void BGSMStream::read(osg::Vec2f& vec); + template <> + void BGSMStream::read(osg::Vec3f& vec); + template <> + void BGSMStream::read(osg::Vec4f& vec); + template <> + void BGSMStream::read(std::string& str); +} + +#endif diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 1d5b57bfd9..5c3f87b3e7 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -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\\"; diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index cda99d928d..a2e05610a6 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -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); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 8d46b0f751..05a8378c11 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include // particle @@ -42,6 +43,7 @@ #include #include +#include #include #include #include @@ -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 mBoundTextures = {}; int mAnimFlags = 0; @@ -349,7 +352,7 @@ namespace NifOsg osg::Node* mRootNode = nullptr; }; - osg::ref_ptr load(Nif::FileView nif, Resource::ImageManager* imageManager) + osg::ref_ptr load(Nif::FileView nif) { const size_t numRoots = nif.numRoots(); std::vector 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& boundTextures, int animflags) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& 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(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 handleSourceTexture( - const Nif::NiSourceTexture* st, Resource::ImageManager* imageManager) + osg::ref_ptr handleSourceTexture(const Nif::NiSourceTexture* st) const { - if (!st) - return nullptr; - - osg::ref_ptr 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 image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); - osg::ref_ptr 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 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 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 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 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 attachTexture(const std::string& name, osg::ref_ptr image, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + osg::ref_ptr 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 attachExternalTexture(const std::string& name, const std::string& path, bool wrapS, + bool wrapT, unsigned int uvSet, osg::StateSet* stateset, std::vector& boundTextures) const + { + return attachTexture(name, getTextureImage(path), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + osg::ref_ptr attachNiSourceTexture(const std::string& name, const Nif::NiSourceTexture* st, + bool wrapS, bool wrapT, unsigned int uvSet, osg::StateSet* stateset, + std::vector& boundTextures) const + { + return attachTexture(name, handleSourceTexture(st), wrapS, wrapT, uvSet, stateset, boundTextures); + } + + static void clearBoundTextures(osg::StateSet* stateset, std::vector& 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 boundTextures; // Dummy list for attachTexture for (const auto& source : flipctrl->mSources) { if (source.empty()) continue; - osg::ref_ptr image(handleSourceTexture(source.getPtr(), imageManager)); - osg::ref_ptr 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 texture + = attachNiSourceTexture({}, source.getPtr(), wrapS, wrapT, uvSet, nullptr, boundTextures); textures.push_back(texture); } osg::ref_ptr callback(new FlipController(flipctrl, textures)); @@ -1811,7 +1844,7 @@ namespace NifOsg } } - osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) + osg::ref_ptr handleInternalTexture(const Nif::NiPixelData* pixelData) const { if (pixelData->mMipmaps.empty()) return nullptr; @@ -1946,7 +1979,7 @@ namespace NifOsg return image; } - osg::ref_ptr createEmissiveTexEnv() + static osg::ref_ptr createEmissiveTexEnv() { osg::ref_ptr 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& boundTextures, int animflags) + std::vector& 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 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 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& 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(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(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 stateset = node.getOrCreateStateSet(); + osg::ref_ptr 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 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 stateset = node.getOrCreateStateSet(); + osg::ref_ptr 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 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& boundTextures) + void handleShaderMaterialDrawableProperties( + Bgsm::MaterialFilePtr shaderMat, osg::ref_ptr 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(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(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& 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(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 image = imageManager->getImage(filename); - osg::ref_ptr 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(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& boundTextures, int animflags, bool hasStencilProperty) + SceneUtil::CompositeStateSetUpdater* composite, std::vector& boundTextures, int animflags, + bool hasStencilProperty) { switch (property->recType) { @@ -2352,8 +2474,7 @@ namespace NifOsg { const Nif::NiTexturingProperty* texprop = static_cast(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(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 image = imageManager->getImage(filename); - osg::ref_ptr 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 image = imageManager->getImage(filename); - osg::ref_ptr 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 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(property); - if (alphaprop->useAlphaBlending()) - { - osg::ref_ptr 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 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(property); + if (Bgsm::MaterialFilePtr shaderMat = getShaderMaterial(shaderprop->mName, mMaterialManager)) + { + handleShaderMaterialDrawableProperties(shaderMat, mat, *node, hasSortAlpha); + if (shaderMat->mShaderType == Bgsm::ShaderType::Lighting) + { + auto bgsm = static_cast(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 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(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 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 Loader::load(Nif::FileView file, Resource::ImageManager* imageManager) + osg::ref_ptr 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) diff --git a/components/nifosg/nifloader.hpp b/components/nifosg/nifloader.hpp index 21e0ae097c..14f16088cc 100644 --- a/components/nifosg/nifloader.hpp +++ b/components/nifosg/nifloader.hpp @@ -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 load(Nif::FileView file, Resource::ImageManager* imageManager); + static osg::ref_ptr 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); diff --git a/components/resource/bgsmfilemanager.cpp b/components/resource/bgsmfilemanager.cpp new file mode 100644 index 0000000000..2d439ccc8a --- /dev/null +++ b/components/resource/bgsmfilemanager.cpp @@ -0,0 +1,58 @@ +#include "bgsmfilemanager.hpp" + +#include + +#include +#include + +#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 obj = mCache->getRefFromObjectCache(name); + if (obj) + return static_cast(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); + } + +} diff --git a/components/resource/bgsmfilemanager.hpp b/components/resource/bgsmfilemanager.hpp new file mode 100644 index 0000000000..3c77c2c665 --- /dev/null +++ b/components/resource/bgsmfilemanager.hpp @@ -0,0 +1,27 @@ +#ifndef OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H +#define OPENMW_COMPONENTS_RESOURCE_BGSMFILEMANAGER_H + +#include + +#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 diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index 65a83a60ab..f012627efb 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -2,6 +2,7 @@ #include +#include "bgsmfilemanager.hpp" #include "imagemanager.hpp" #include "keyframemanager.hpp" #include "niffilemanager.hpp" @@ -15,11 +16,14 @@ namespace Resource : mVFS(vfs) { mNifFileManager = std::make_unique(vfs, encoder); + mBgsmFileManager = std::make_unique(vfs, expiryDelay); mImageManager = std::make_unique(vfs, expiryDelay); - mSceneManager = std::make_unique(vfs, mImageManager.get(), mNifFileManager.get(), expiryDelay); + mSceneManager = std::make_unique( + vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(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(); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index f7f09b9277..5609176a89 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -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 mSceneManager; std::unique_ptr mImageManager; + std::unique_ptr mBgsmFileManager; std::unique_ptr mNifFileManager; std::unique_ptr mKeyframeManager; diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index ab3f92f10d..690e38bbf8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -55,6 +55,7 @@ #include #include +#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 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 loaded; try { - loaded = load(normalized, mVFS, mImageManager, mNifFileManager); + loaded = load(normalized, mVFS, mImageManager, mNifFileManager, mBgsmFileManager); SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this); loaded->accept(extraDataVisitor); diff --git a/components/resource/scenemanager.hpp b/components/resource/scenemanager.hpp index 3ad8a24892..31ad51694c 100644 --- a/components/resource/scenemanager.hpp +++ b/components/resource/scenemanager.hpp @@ -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; diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 9bb90635d1..6730ddb303 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -87,6 +87,7 @@ namespace Resource "Image", "Nif", "Keyframe", + "BSShader Material", "Groundcover Chunk", "Object Chunk", "Terrain Chunk",