mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-25 17:56:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1248 lines
		
	
	
	
		
			47 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1248 lines
		
	
	
	
		
			47 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "scenemanager.hpp"
 | |
| 
 | |
| #include <cstdlib>
 | |
| #include <filesystem>
 | |
| 
 | |
| #include <osg/AlphaFunc>
 | |
| #include <osg/ColorMaski>
 | |
| #include <osg/Group>
 | |
| #include <osg/Node>
 | |
| #include <osg/UserDataContainer>
 | |
| 
 | |
| #include <osgAnimation/BasicAnimationManager>
 | |
| #include <osgAnimation/Bone>
 | |
| #include <osgAnimation/RigGeometry>
 | |
| #include <osgAnimation/UpdateBone>
 | |
| 
 | |
| #include <osgParticle/ParticleSystem>
 | |
| 
 | |
| #include <osgUtil/IncrementalCompileOperation>
 | |
| 
 | |
| #include <osgDB/FileUtils>
 | |
| #include <osgDB/Registry>
 | |
| #include <osgDB/SharedStateManager>
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| 
 | |
| #include <components/nifosg/controller.hpp>
 | |
| #include <components/nifosg/nifloader.hpp>
 | |
| 
 | |
| #include <components/nif/niffile.hpp>
 | |
| 
 | |
| #include <components/misc/algorithm.hpp>
 | |
| #include <components/misc/osguservalues.hpp>
 | |
| #include <components/misc/pathhelpers.hpp>
 | |
| #include <components/misc/strings/algorithm.hpp>
 | |
| #include <components/misc/strings/conversion.hpp>
 | |
| 
 | |
| #include <components/vfs/manager.hpp>
 | |
| #include <components/vfs/pathutil.hpp>
 | |
| 
 | |
| #include <components/sceneutil/clone.hpp>
 | |
| #include <components/sceneutil/controller.hpp>
 | |
| #include <components/sceneutil/depth.hpp>
 | |
| #include <components/sceneutil/extradata.hpp>
 | |
| #include <components/sceneutil/lightmanager.hpp>
 | |
| #include <components/sceneutil/optimizer.hpp>
 | |
| #include <components/sceneutil/riggeometryosgaextension.hpp>
 | |
| #include <components/sceneutil/util.hpp>
 | |
| #include <components/sceneutil/visitor.hpp>
 | |
| 
 | |
| #include <components/shader/shadermanager.hpp>
 | |
| #include <components/shader/shadervisitor.hpp>
 | |
| 
 | |
| #include <components/files/conversion.hpp>
 | |
| #include <components/files/hash.hpp>
 | |
| #include <components/files/memorystream.hpp>
 | |
| 
 | |
| #include "bgsmfilemanager.hpp"
 | |
| #include "errormarker.hpp"
 | |
| #include "imagemanager.hpp"
 | |
| #include "niffilemanager.hpp"
 | |
| #include "objectcache.hpp"
 | |
| 
 | |
| namespace
 | |
| {
 | |
|     class InitWorldSpaceParticlesCallback
 | |
|         : public SceneUtil::NodeCallback<InitWorldSpaceParticlesCallback, osgParticle::ParticleSystem*>
 | |
|     {
 | |
|     public:
 | |
|         void operator()(osgParticle::ParticleSystem* node, osg::NodeVisitor* nv)
 | |
|         {
 | |
|             // HACK: Ignore the InverseWorldMatrix transform the particle system is attached to
 | |
|             if (node->getNumParents() && node->getParent(0)->getNumParents())
 | |
|                 transformInitialParticles(node, node->getParent(0)->getParent(0));
 | |
| 
 | |
|             node->removeUpdateCallback(this);
 | |
|         }
 | |
| 
 | |
|         void transformInitialParticles(osgParticle::ParticleSystem* partsys, osg::Node* node)
 | |
|         {
 | |
|             osg::NodePathList nodepaths = node->getParentalNodePaths();
 | |
|             if (nodepaths.empty())
 | |
|                 return;
 | |
|             osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]);
 | |
|             worldMat.orthoNormalize(worldMat); // scale is already applied on the particle node
 | |
|             for (int i = 0; i < partsys->numParticles(); ++i)
 | |
|             {
 | |
|                 partsys->getParticle(i)->transformPositionVelocity(worldMat);
 | |
|             }
 | |
| 
 | |
|             // transform initial bounds to worldspace
 | |
|             osg::BoundingSphere sphere(partsys->getInitialBound());
 | |
|             SceneUtil::transformBoundingSphere(worldMat, sphere);
 | |
|             osg::BoundingBox box;
 | |
|             box.expandBy(sphere);
 | |
|             partsys->setInitialBound(box);
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     class InitParticlesVisitor : public osg::NodeVisitor
 | |
|     {
 | |
|     public:
 | |
|         /// @param mask The node mask to set on ParticleSystem nodes.
 | |
|         InitParticlesVisitor(unsigned int mask)
 | |
|             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
 | |
|             , mMask(mask)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         bool isWorldSpaceParticleSystem(osgParticle::ParticleSystem* partsys)
 | |
|         {
 | |
|             // HACK: ParticleSystem has no getReferenceFrame()
 | |
|             return (partsys->getUserDataContainer() && partsys->getUserDataContainer()->getNumDescriptions() > 0
 | |
|                 && partsys->getUserDataContainer()->getDescriptions()[0] == "worldspace");
 | |
|         }
 | |
| 
 | |
|         void apply(osg::Drawable& drw) override
 | |
|         {
 | |
|             if (osgParticle::ParticleSystem* partsys = dynamic_cast<osgParticle::ParticleSystem*>(&drw))
 | |
|             {
 | |
|                 if (isWorldSpaceParticleSystem(partsys))
 | |
|                 {
 | |
|                     partsys->addUpdateCallback(new InitWorldSpaceParticlesCallback);
 | |
|                 }
 | |
|                 partsys->setNodeMask(mMask);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         unsigned int mMask;
 | |
|     };
 | |
| }
 | |
| 
 | |
| namespace Resource
 | |
| {
 | |
|     void TemplateMultiRef::addRef(const osg::Node* node)
 | |
|     {
 | |
|         mObjects.emplace_back(node);
 | |
|     }
 | |
| 
 | |
|     class SharedStateManager : public osgDB::SharedStateManager
 | |
|     {
 | |
|     public:
 | |
|         unsigned int getNumSharedTextures() const { return _sharedTextureList.size(); }
 | |
| 
 | |
|         unsigned int getNumSharedStateSets() const { return _sharedStateSetList.size(); }
 | |
| 
 | |
|         void clearCache()
 | |
|         {
 | |
|             std::lock_guard<OpenThreads::Mutex> lock(_listMutex);
 | |
|             _sharedTextureList.clear();
 | |
|             _sharedStateSetList.clear();
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     /// Set texture filtering settings on textures contained in a FlipController.
 | |
|     class SetFilterSettingsControllerVisitor : public SceneUtil::ControllerVisitor
 | |
|     {
 | |
|     public:
 | |
|         SetFilterSettingsControllerVisitor(
 | |
|             osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy)
 | |
|             : mMinFilter(minFilter)
 | |
|             , mMagFilter(magFilter)
 | |
|             , mMaxAnisotropy(maxAnisotropy)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         void visit(osg::Node& node, SceneUtil::Controller& ctrl) override
 | |
|         {
 | |
|             if (NifOsg::FlipController* flipctrl = dynamic_cast<NifOsg::FlipController*>(&ctrl))
 | |
|             {
 | |
|                 for (std::vector<osg::ref_ptr<osg::Texture2D>>::iterator it = flipctrl->getTextures().begin();
 | |
|                      it != flipctrl->getTextures().end(); ++it)
 | |
|                 {
 | |
|                     osg::Texture* tex = *it;
 | |
|                     tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter);
 | |
|                     tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter);
 | |
|                     tex->setMaxAnisotropy(mMaxAnisotropy);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         osg::Texture::FilterMode mMinFilter;
 | |
|         osg::Texture::FilterMode mMagFilter;
 | |
|         int mMaxAnisotropy;
 | |
|     };
 | |
| 
 | |
|     /// Set texture filtering settings on textures contained in StateSets.
 | |
|     class SetFilterSettingsVisitor : public osg::NodeVisitor
 | |
|     {
 | |
|     public:
 | |
|         SetFilterSettingsVisitor(
 | |
|             osg::Texture::FilterMode minFilter, osg::Texture::FilterMode magFilter, int maxAnisotropy)
 | |
|             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
 | |
|             , mMinFilter(minFilter)
 | |
|             , mMagFilter(magFilter)
 | |
|             , mMaxAnisotropy(maxAnisotropy)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         void apply(osg::Node& node) override
 | |
|         {
 | |
|             osg::StateSet* stateset = node.getStateSet();
 | |
|             if (stateset)
 | |
|                 applyStateSet(stateset);
 | |
| 
 | |
|             traverse(node);
 | |
|         }
 | |
| 
 | |
|         void applyStateSet(osg::StateSet* stateset)
 | |
|         {
 | |
|             const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList();
 | |
|             for (unsigned int unit = 0; unit < texAttributes.size(); ++unit)
 | |
|             {
 | |
|                 osg::StateAttribute* texture = stateset->getTextureAttribute(unit, osg::StateAttribute::TEXTURE);
 | |
|                 if (texture)
 | |
|                     applyStateAttribute(texture);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void applyStateAttribute(osg::StateAttribute* attr)
 | |
|         {
 | |
|             osg::Texture* tex = attr->asTexture();
 | |
|             if (tex)
 | |
|             {
 | |
|                 tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter);
 | |
|                 tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter);
 | |
|                 tex->setMaxAnisotropy(mMaxAnisotropy);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         osg::Texture::FilterMode mMinFilter;
 | |
|         osg::Texture::FilterMode mMagFilter;
 | |
|         int mMaxAnisotropy;
 | |
|     };
 | |
| 
 | |
|     // Check Collada extra descriptions
 | |
|     class ColladaDescriptionVisitor : public osg::NodeVisitor
 | |
|     {
 | |
|     public:
 | |
|         ColladaDescriptionVisitor()
 | |
|             : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
 | |
|             , mSkeleton(nullptr)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         osg::AlphaFunc::ComparisonFunction getTestMode(std::string mode)
 | |
|         {
 | |
|             if (mode == "ALWAYS")
 | |
|                 return osg::AlphaFunc::ALWAYS;
 | |
|             if (mode == "LESS")
 | |
|                 return osg::AlphaFunc::LESS;
 | |
|             if (mode == "EQUAL")
 | |
|                 return osg::AlphaFunc::EQUAL;
 | |
|             if (mode == "LEQUAL")
 | |
|                 return osg::AlphaFunc::LEQUAL;
 | |
|             if (mode == "GREATER")
 | |
|                 return osg::AlphaFunc::GREATER;
 | |
|             if (mode == "NOTEQUAL")
 | |
|                 return osg::AlphaFunc::NOTEQUAL;
 | |
|             if (mode == "GEQUAL")
 | |
|                 return osg::AlphaFunc::GEQUAL;
 | |
|             if (mode == "NEVER")
 | |
|                 return osg::AlphaFunc::NEVER;
 | |
| 
 | |
|             Log(Debug::Warning) << "Unexpected alpha testing mode: " << mode;
 | |
|             return osg::AlphaFunc::LEQUAL;
 | |
|         }
 | |
| 
 | |
|         void apply(osg::Node& node) override
 | |
|         {
 | |
|             if (osg::StateSet* stateset = node.getStateSet())
 | |
|             {
 | |
|                 if (stateset->getRenderingHint() == osg::StateSet::TRANSPARENT_BIN)
 | |
|                 {
 | |
|                     osg::ref_ptr<osg::Depth> depth = new SceneUtil::AutoDepth;
 | |
|                     depth->setWriteMask(false);
 | |
| 
 | |
|                     stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
 | |
|                 }
 | |
|                 else if (stateset->getRenderingHint() == osg::StateSet::OPAQUE_BIN)
 | |
|                 {
 | |
|                     osg::ref_ptr<osg::Depth> depth = new SceneUtil::AutoDepth;
 | |
|                     depth->setWriteMask(true);
 | |
| 
 | |
|                     stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
 | |
|                 }
 | |
|             }
 | |
|             /* Check if the <node> has <extra type="Node"> <technique profile="OpenSceneGraph"> <Descriptions>
 | |
|                <Description> correct format for OpenMW: <Description>alphatest mode value MaterialName</Description> e.g
 | |
|                <Description>alphatest GEQUAL 0.8 MyAlphaTestedMaterial</Description> */
 | |
|             std::vector<std::string> descriptions = node.getDescriptions();
 | |
|             for (const auto& description : descriptions)
 | |
|             {
 | |
|                 mDescriptions.emplace_back(description);
 | |
|             }
 | |
| 
 | |
|             // Iterate each description, and see if the current node uses the specified material for alpha testing
 | |
|             if (node.getStateSet())
 | |
|             {
 | |
|                 for (const auto& description : mDescriptions)
 | |
|                 {
 | |
|                     std::vector<std::string> descriptionParts;
 | |
|                     std::istringstream descriptionStringStream(description);
 | |
|                     for (std::string part; std::getline(descriptionStringStream, part, ' ');)
 | |
|                     {
 | |
|                         descriptionParts.emplace_back(part);
 | |
|                     }
 | |
| 
 | |
|                     if (descriptionParts.size() > (3) && descriptionParts.at(3) == node.getStateSet()->getName())
 | |
|                     {
 | |
|                         if (descriptionParts.at(0) == "alphatest")
 | |
|                         {
 | |
|                             osg::AlphaFunc::ComparisonFunction mode = getTestMode(descriptionParts.at(1));
 | |
|                             osg::ref_ptr<osg::AlphaFunc> alphaFunc(new osg::AlphaFunc(
 | |
|                                 mode, Misc::StringUtils::toNumeric<float>(descriptionParts.at(2), 0.0f)));
 | |
|                             node.getStateSet()->setAttributeAndModes(alphaFunc, osg::StateAttribute::ON);
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     if (descriptionParts.size() > (0) && descriptionParts.at(0) == "bodypart")
 | |
|                     {
 | |
|                         SceneUtil::FindByClassVisitor osgaRigFinder("RigGeometryHolder");
 | |
|                         node.accept(osgaRigFinder);
 | |
|                         for (osg::Node* foundRigNode : osgaRigFinder.mFoundNodes)
 | |
|                         {
 | |
|                             if (SceneUtil::RigGeometryHolder* rigGeometryHolder
 | |
|                                 = dynamic_cast<SceneUtil::RigGeometryHolder*>(foundRigNode))
 | |
|                                 mRigGeometryHolders.emplace_back(
 | |
|                                     osg::ref_ptr<SceneUtil::RigGeometryHolder>(rigGeometryHolder));
 | |
|                             else
 | |
|                                 Log(Debug::Error) << "Converted RigGeometryHolder is of a wrong type.";
 | |
|                         }
 | |
| 
 | |
|                         if (!mRigGeometryHolders.empty())
 | |
|                         {
 | |
|                             osgAnimation::RigGeometry::FindNearestParentSkeleton skeletonFinder;
 | |
|                             mRigGeometryHolders[0]->accept(skeletonFinder);
 | |
|                             if (skeletonFinder._root.valid())
 | |
|                                 mSkeleton = skeletonFinder._root;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             traverse(node);
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         std::vector<std::string> mDescriptions;
 | |
| 
 | |
|     public:
 | |
|         osgAnimation::Skeleton* mSkeleton; // pointer is valid only if the model is a bodypart, osg::ref_ptr<Skeleton>
 | |
|         std::vector<osg::ref_ptr<SceneUtil::RigGeometryHolder>> mRigGeometryHolders;
 | |
|     };
 | |
| 
 | |
|     class ReplaceAnimationUnderscoresVisitor : public osg::NodeVisitor
 | |
|     {
 | |
|     public:
 | |
|         ReplaceAnimationUnderscoresVisitor()
 | |
|             : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         void apply(osg::Node& node) override
 | |
|         {
 | |
|             // NOTE: MUST update the animation manager names first!
 | |
|             if (auto* animationManager = dynamic_cast<osgAnimation::BasicAnimationManager*>(node.getUpdateCallback()))
 | |
|                 renameAnimationChannelTargets(*animationManager);
 | |
| 
 | |
|             // Then, any applicable node names
 | |
|             if (auto* rigGeometry = dynamic_cast<osgAnimation::RigGeometry*>(&node))
 | |
|             {
 | |
|                 renameNode(*rigGeometry);
 | |
|                 updateVertexInfluenceMap(*rigGeometry);
 | |
|             }
 | |
|             else if (auto* matrixTransform = dynamic_cast<osg::MatrixTransform*>(&node))
 | |
|             {
 | |
|                 renameNode(*matrixTransform);
 | |
|                 renameUpdateCallbacks(*matrixTransform);
 | |
|             }
 | |
| 
 | |
|             traverse(node);
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         inline void renameNode(osg::Node& node)
 | |
|         {
 | |
|             node.setName(Misc::StringUtils::underscoresToSpaces(node.getName()));
 | |
|         }
 | |
| 
 | |
|         void renameUpdateCallbacks(osg::MatrixTransform& node)
 | |
|         {
 | |
|             osg::Callback* cb = node.getUpdateCallback();
 | |
|             while (cb)
 | |
|             {
 | |
|                 auto* animCb = dynamic_cast<osgAnimation::AnimationUpdateCallback<osg::NodeCallback>*>(cb);
 | |
|                 if (animCb)
 | |
|                 {
 | |
|                     std::string newAnimCbName = Misc::StringUtils::underscoresToSpaces(animCb->getName());
 | |
|                     animCb->setName(newAnimCbName);
 | |
|                 }
 | |
|                 cb = cb->getNestedCallback();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void updateVertexInfluenceMap(osgAnimation::RigGeometry& rig)
 | |
|         {
 | |
|             osgAnimation::VertexInfluenceMap* vertexInfluenceMap = rig.getInfluenceMap();
 | |
|             if (!vertexInfluenceMap)
 | |
|                 return;
 | |
| 
 | |
|             std::vector<std::pair<std::string, std::string>> renameList;
 | |
|             for (const auto& [oldBoneName, _] : *vertexInfluenceMap)
 | |
|             {
 | |
|                 const std::string newBoneName = Misc::StringUtils::underscoresToSpaces(oldBoneName);
 | |
|                 if (newBoneName != oldBoneName)
 | |
|                     renameList.emplace_back(oldBoneName, newBoneName);
 | |
|             }
 | |
| 
 | |
|             for (const auto& [oldName, newName] : renameList)
 | |
|             {
 | |
|                 if (vertexInfluenceMap->find(newName) == vertexInfluenceMap->end())
 | |
|                     (*vertexInfluenceMap)[newName] = std::move((*vertexInfluenceMap)[oldName]);
 | |
|                 vertexInfluenceMap->erase(oldName);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void renameAnimationChannelTargets(osgAnimation::BasicAnimationManager& animManager)
 | |
|         {
 | |
|             for (const osgAnimation::Animation* animation : animManager.getAnimationList())
 | |
|             {
 | |
|                 if (animation)
 | |
|                 {
 | |
|                     const osgAnimation::ChannelList& channels = animation->getChannels();
 | |
|                     for (osgAnimation::Channel* channel : channels)
 | |
|                         channel->setTargetName(Misc::StringUtils::underscoresToSpaces(channel->getTargetName()));
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     SceneManager::SceneManager(const VFS::Manager* vfs, Resource::ImageManager* imageManager,
 | |
|         Resource::NifFileManager* nifFileManager, Resource::BgsmFileManager* bgsmFileManager, double expiryDelay)
 | |
|         : ResourceManager(vfs, expiryDelay)
 | |
|         , mShaderManager(new Shader::ShaderManager)
 | |
|         , mForceShaders(false)
 | |
|         , mClampLighting(true)
 | |
|         , mAutoUseNormalMaps(false)
 | |
|         , mAutoUseSpecularMaps(false)
 | |
|         , mApplyLightingToEnvMaps(false)
 | |
|         , mLightingMethod(SceneUtil::LightingMethod::FFP)
 | |
|         , mConvertAlphaTestToAlphaToCoverage(false)
 | |
|         , mAdjustCoverageForAlphaTest(false)
 | |
|         , mSupportsNormalsRT(false)
 | |
|         , mSharedStateManager(new SharedStateManager)
 | |
|         , mImageManager(imageManager)
 | |
|         , mNifFileManager(nifFileManager)
 | |
|         , mBgsmFileManager(bgsmFileManager)
 | |
|         , mMinFilter(osg::Texture::LINEAR_MIPMAP_LINEAR)
 | |
|         , mMagFilter(osg::Texture::LINEAR)
 | |
|         , mMaxAnisotropy(1)
 | |
|         , mUnRefImageDataAfterApply(false)
 | |
|         , mParticleSystemMask(~0u)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setForceShaders(bool force)
 | |
|     {
 | |
|         mForceShaders = force;
 | |
|     }
 | |
| 
 | |
|     bool SceneManager::getForceShaders() const
 | |
|     {
 | |
|         return mForceShaders;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::recreateShaders(osg::ref_ptr<osg::Node> node, const std::string& shaderPrefix,
 | |
|         bool forceShadersForNode, const osg::Program* programTemplate)
 | |
|     {
 | |
|         osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor(createShaderVisitor(shaderPrefix));
 | |
|         shaderVisitor->setAllowedToModifyStateSets(false);
 | |
|         shaderVisitor->setProgramTemplate(programTemplate);
 | |
|         if (forceShadersForNode)
 | |
|             shaderVisitor->setForceShaders(true);
 | |
|         node->accept(*shaderVisitor);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::reinstateRemovedState(osg::ref_ptr<osg::Node> node)
 | |
|     {
 | |
|         osg::ref_ptr<Shader::ReinstateRemovedStateVisitor> reinstateRemovedStateVisitor
 | |
|             = new Shader::ReinstateRemovedStateVisitor(false);
 | |
|         node->accept(*reinstateRemovedStateVisitor);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setClampLighting(bool clamp)
 | |
|     {
 | |
|         mClampLighting = clamp;
 | |
|     }
 | |
| 
 | |
|     bool SceneManager::getClampLighting() const
 | |
|     {
 | |
|         return mClampLighting;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setAutoUseNormalMaps(bool use)
 | |
|     {
 | |
|         mAutoUseNormalMaps = use;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setNormalMapPattern(const std::string& pattern)
 | |
|     {
 | |
|         mNormalMapPattern = pattern;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setNormalHeightMapPattern(const std::string& pattern)
 | |
|     {
 | |
|         mNormalHeightMapPattern = pattern;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setAutoUseSpecularMaps(bool use)
 | |
|     {
 | |
|         mAutoUseSpecularMaps = use;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setSpecularMapPattern(const std::string& pattern)
 | |
|     {
 | |
|         mSpecularMapPattern = pattern;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setApplyLightingToEnvMaps(bool apply)
 | |
|     {
 | |
|         mApplyLightingToEnvMaps = apply;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setSupportedLightingMethods(const SceneUtil::LightManager::SupportedMethods& supported)
 | |
|     {
 | |
|         mSupportedLightingMethods = supported;
 | |
|     }
 | |
| 
 | |
|     bool SceneManager::isSupportedLightingMethod(SceneUtil::LightingMethod method) const
 | |
|     {
 | |
|         return mSupportedLightingMethods[static_cast<int>(method)];
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setLightingMethod(SceneUtil::LightingMethod method)
 | |
|     {
 | |
|         mLightingMethod = method;
 | |
| 
 | |
|         if (mLightingMethod == SceneUtil::LightingMethod::SingleUBO)
 | |
|         {
 | |
|             osg::ref_ptr<osg::Program> program = new osg::Program;
 | |
|             program->addBindUniformBlock("LightBufferBinding", static_cast<int>(UBOBinding::LightBuffer));
 | |
|             mShaderManager->setProgramTemplate(program);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     SceneUtil::LightingMethod SceneManager::getLightingMethod() const
 | |
|     {
 | |
|         return mLightingMethod;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setConvertAlphaTestToAlphaToCoverage(bool convert)
 | |
|     {
 | |
|         mConvertAlphaTestToAlphaToCoverage = convert;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setAdjustCoverageForAlphaTest(bool adjustCoverage)
 | |
|     {
 | |
|         mAdjustCoverageForAlphaTest = adjustCoverage;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setOpaqueDepthTex(osg::ref_ptr<osg::Texture> texturePing, osg::ref_ptr<osg::Texture> texturePong)
 | |
|     {
 | |
|         mOpaqueDepthTex = { texturePing, texturePong };
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Texture> SceneManager::getOpaqueDepthTex(size_t frame)
 | |
|     {
 | |
|         return mOpaqueDepthTex[frame % 2];
 | |
|     }
 | |
| 
 | |
|     SceneManager::~SceneManager()
 | |
|     {
 | |
|         // this has to be defined in the .cpp file as we can't delete incomplete types
 | |
|     }
 | |
| 
 | |
|     Shader::ShaderManager& SceneManager::getShaderManager()
 | |
|     {
 | |
|         return *mShaderManager.get();
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setShaderPath(const std::filesystem::path& path)
 | |
|     {
 | |
|         mShaderManager->setShaderPath(path);
 | |
|     }
 | |
| 
 | |
|     bool SceneManager::checkLoaded(VFS::Path::NormalizedView name, double timeStamp)
 | |
|     {
 | |
|         return mCache->checkInObjectCache(name, timeStamp);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setUpNormalsRTForStateSet(osg::StateSet* stateset, bool enabled)
 | |
|     {
 | |
|         if (!getSupportsNormalsRT())
 | |
|             return;
 | |
|         stateset->setAttributeAndModes(new osg::ColorMaski(1, enabled, enabled, enabled, enabled));
 | |
|     }
 | |
| 
 | |
|     /// @brief Callback to read image files from the VFS.
 | |
|     class ImageReadCallback : public osgDB::ReadFileCallback
 | |
|     {
 | |
|     public:
 | |
|         ImageReadCallback(Resource::ImageManager* imageMgr)
 | |
|             : mImageManager(imageMgr)
 | |
|         {
 | |
|         }
 | |
| 
 | |
|         osgDB::ReaderWriter::ReadResult readImage(const std::string& filename, const osgDB::Options* options) override
 | |
|         {
 | |
|             auto filePath = Files::pathFromUnicodeString(filename);
 | |
|             if (filePath.is_absolute())
 | |
|                 // It is a hack. Needed because either OSG or libcollada-dom tries to make an absolute path from
 | |
|                 // our relative VFS path by adding current working directory.
 | |
|                 filePath = std::filesystem::relative(filename, osgDB::getCurrentWorkingDirectory());
 | |
|             try
 | |
|             {
 | |
|                 return osgDB::ReaderWriter::ReadResult(
 | |
|                     mImageManager->getImage(VFS::Path::toNormalized(Files::pathToUnicodeString(filePath))),
 | |
|                     osgDB::ReaderWriter::ReadResult::FILE_LOADED);
 | |
|             }
 | |
|             catch (std::exception& e)
 | |
|             {
 | |
|                 return osgDB::ReaderWriter::ReadResult(e.what());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     private:
 | |
|         Resource::ImageManager* mImageManager;
 | |
|     };
 | |
| 
 | |
|     namespace
 | |
|     {
 | |
|         osg::ref_ptr<osg::Node> loadNonNif(
 | |
|             VFS::Path::NormalizedView normalizedFilename, std::istream& model, Resource::ImageManager* imageManager)
 | |
|         {
 | |
|             const std::string_view ext = Misc::getFileExtension(normalizedFilename.value());
 | |
|             const bool isColladaFile = ext == "dae";
 | |
|             osgDB::ReaderWriter* reader = osgDB::Registry::instance()->getReaderWriterForExtension(std::string(ext));
 | |
|             if (!reader)
 | |
|             {
 | |
|                 std::stringstream errormsg;
 | |
|                 errormsg << "Error loading " << normalizedFilename << ": no readerwriter for '" << ext << "' found"
 | |
|                          << std::endl;
 | |
|                 throw std::runtime_error(errormsg.str());
 | |
|             }
 | |
| 
 | |
|             osg::ref_ptr<osgDB::Options> options(new osgDB::Options);
 | |
|             // Set a ReadFileCallback so that image files referenced in the model are read from our virtual file system
 | |
|             // instead of the osgDB. Note, for some formats (.obj/.mtl) that reference other (non-image) files a
 | |
|             // findFileCallback would be necessary. but findFileCallback does not support virtual files, so we can't
 | |
|             // implement it.
 | |
|             options->setReadFileCallback(new ImageReadCallback(imageManager));
 | |
|             if (isColladaFile)
 | |
|                 options->setOptionString("daeUseSequencedTextureUnits");
 | |
| 
 | |
|             const std::array<std::uint64_t, 2> fileHash = Files::getHash(normalizedFilename.value(), model);
 | |
| 
 | |
|             osgDB::ReaderWriter::ReadResult result = reader->readNode(model, options);
 | |
|             if (!result.success())
 | |
|             {
 | |
|                 std::stringstream errormsg;
 | |
|                 errormsg << "Error loading " << normalizedFilename << ": " << result.message() << " code "
 | |
|                          << result.status() << std::endl;
 | |
|                 throw std::runtime_error(errormsg.str());
 | |
|             }
 | |
| 
 | |
|             // Recognize and hide collision node
 | |
|             unsigned int hiddenNodeMask = 0;
 | |
|             SceneUtil::FindByNameVisitor nameFinder("Collision");
 | |
| 
 | |
|             auto node = result.getNode();
 | |
|             node->accept(nameFinder);
 | |
|             if (nameFinder.mFoundNode)
 | |
|                 nameFinder.mFoundNode->setNodeMask(hiddenNodeMask);
 | |
| 
 | |
|             // Recognize and convert osgAnimation::RigGeometry to OpenMW-optimized type
 | |
|             SceneUtil::FindByClassVisitor rigFinder("RigGeometry");
 | |
|             node->accept(rigFinder);
 | |
| 
 | |
|             // If a collada file with rigs, we should replace underscores with spaces
 | |
|             if (isColladaFile && !rigFinder.mFoundNodes.empty())
 | |
|             {
 | |
|                 ReplaceAnimationUnderscoresVisitor renamingVisitor;
 | |
|                 node->accept(renamingVisitor);
 | |
|             }
 | |
| 
 | |
|             for (osg::Node* foundRigNode : rigFinder.mFoundNodes)
 | |
|             {
 | |
|                 if (foundRigNode->libraryName() == std::string_view("osgAnimation"))
 | |
|                 {
 | |
|                     osgAnimation::RigGeometry* foundRigGeometry = static_cast<osgAnimation::RigGeometry*>(foundRigNode);
 | |
| 
 | |
|                     osg::ref_ptr<SceneUtil::RigGeometryHolder> newRig
 | |
|                         = new SceneUtil::RigGeometryHolder(*foundRigGeometry, osg::CopyOp::DEEP_COPY_ALL);
 | |
| 
 | |
|                     if (foundRigGeometry->getStateSet())
 | |
|                         newRig->setStateSet(foundRigGeometry->getStateSet());
 | |
| 
 | |
|                     if (osg::Group* parent = dynamic_cast<osg::Group*>(foundRigGeometry->getParent(0)))
 | |
|                     {
 | |
|                         parent->removeChild(foundRigGeometry);
 | |
|                         parent->addChild(newRig);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (isColladaFile)
 | |
|             {
 | |
|                 Resource::ColladaDescriptionVisitor colladaDescriptionVisitor;
 | |
|                 node->accept(colladaDescriptionVisitor);
 | |
| 
 | |
|                 if (colladaDescriptionVisitor.mSkeleton)
 | |
|                 {
 | |
|                     if (osg::Group* group = dynamic_cast<osg::Group*>(node))
 | |
|                     {
 | |
|                         group->removeChildren(0, group->getNumChildren());
 | |
|                         for (osg::ref_ptr<SceneUtil::RigGeometryHolder> newRiggeometryHolder :
 | |
|                             colladaDescriptionVisitor.mRigGeometryHolders)
 | |
|                         {
 | |
|                             osg::ref_ptr<osg::MatrixTransform> backToOriginTrans = new osg::MatrixTransform();
 | |
| 
 | |
|                             newRiggeometryHolder->getOrCreateUserDataContainer()->addUserObject(
 | |
|                                 new TemplateRef(newRiggeometryHolder->getGeometry(0)));
 | |
|                             backToOriginTrans->getOrCreateUserDataContainer()->addUserObject(
 | |
|                                 new TemplateRef(newRiggeometryHolder->getGeometry(0)));
 | |
| 
 | |
|                             newRiggeometryHolder->setBodyPart(true);
 | |
| 
 | |
|                             for (int i = 0; i < 2; ++i)
 | |
|                             {
 | |
|                                 if (newRiggeometryHolder->getGeometry(i))
 | |
|                                     newRiggeometryHolder->getGeometry(i)->setSkeleton(nullptr);
 | |
|                             }
 | |
| 
 | |
|                             backToOriginTrans->addChild(newRiggeometryHolder);
 | |
|                             group->addChild(backToOriginTrans);
 | |
| 
 | |
|                             node->getOrCreateUserDataContainer()->addUserObject(
 | |
|                                 new TemplateRef(newRiggeometryHolder->getGeometry(0)));
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 node->getOrCreateStateSet()->addUniform(new osg::Uniform("emissiveMult", 1.f));
 | |
|                 node->getOrCreateStateSet()->addUniform(new osg::Uniform("specStrength", 1.f));
 | |
|                 node->getOrCreateStateSet()->addUniform(new osg::Uniform("envMapColor", osg::Vec4f(1, 1, 1, 1)));
 | |
|                 node->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false));
 | |
|                 node->getOrCreateStateSet()->addUniform(new osg::Uniform("distortionStrength", 0.f));
 | |
|             }
 | |
| 
 | |
|             node->setUserValue(Misc::OsgUserValues::sFileHash,
 | |
|                 std::string(reinterpret_cast<const char*>(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)));
 | |
| 
 | |
|             return node;
 | |
|         }
 | |
| 
 | |
|         std::vector<std::string> makeSortedReservedNames()
 | |
|         {
 | |
|             static constexpr std::string_view names[] = {
 | |
|                 "Head",
 | |
|                 "Neck",
 | |
|                 "Chest",
 | |
|                 "Groin",
 | |
|                 "Right Hand",
 | |
|                 "Left Hand",
 | |
|                 "Right Wrist",
 | |
|                 "Left Wrist",
 | |
|                 "Shield Bone",
 | |
|                 "Right Forearm",
 | |
|                 "Left Forearm",
 | |
|                 "Right Upper Arm",
 | |
|                 "Left Upper Arm",
 | |
|                 "Right Foot",
 | |
|                 "Left Foot",
 | |
|                 "Right Ankle",
 | |
|                 "Left Ankle",
 | |
|                 "Right Knee",
 | |
|                 "Left Knee",
 | |
|                 "Right Upper Leg",
 | |
|                 "Left Upper Leg",
 | |
|                 "Right Clavicle",
 | |
|                 "Left Clavicle",
 | |
|                 "Weapon Bone",
 | |
|                 "Tail",
 | |
|                 "Bip01",
 | |
|                 "Root Bone",
 | |
|                 "BoneOffset",
 | |
|                 "AttachLight",
 | |
|                 "Arrow",
 | |
|                 "Camera",
 | |
|                 "Collision",
 | |
|                 "Right_Wrist",
 | |
|                 "Left_Wrist",
 | |
|                 "Shield_Bone",
 | |
|                 "Right_Forearm",
 | |
|                 "Left_Forearm",
 | |
|                 "Right_Upper_Arm",
 | |
|                 "Left_Clavicle",
 | |
|                 "Weapon_Bone",
 | |
|                 "Root_Bone",
 | |
|             };
 | |
| 
 | |
|             std::vector<std::string> result;
 | |
|             result.reserve(2 * std::size(names));
 | |
| 
 | |
|             for (std::string_view name : names)
 | |
|             {
 | |
|                 result.emplace_back(name);
 | |
|                 std::string prefixedName("Tri ");
 | |
|                 prefixedName += name;
 | |
|                 result.push_back(std::move(prefixedName));
 | |
|             }
 | |
| 
 | |
|             std::sort(result.begin(), result.end(), Misc::StringUtils::ciLess);
 | |
| 
 | |
|             return result;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> load(VFS::Path::NormalizedView normalizedFilename, const VFS::Manager* vfs,
 | |
|         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, materialMgr);
 | |
|         else if (ext == "spt")
 | |
|         {
 | |
|             Log(Debug::Warning) << "Ignoring SpeedTree data file " << normalizedFilename;
 | |
|             return new osg::Node();
 | |
|         }
 | |
|         else
 | |
|             return loadNonNif(normalizedFilename, *vfs->get(normalizedFilename), imageManager);
 | |
|     }
 | |
| 
 | |
|     class CanOptimizeCallback : public SceneUtil::Optimizer::IsOperationPermissibleForObjectCallback
 | |
|     {
 | |
|     public:
 | |
|         bool isReservedName(const std::string& name) const
 | |
|         {
 | |
|             if (name.empty())
 | |
|                 return false;
 | |
| 
 | |
|             static const std::vector<std::string> reservedNames = makeSortedReservedNames();
 | |
| 
 | |
|             const auto it = Misc::partialBinarySearch(reservedNames.begin(), reservedNames.end(), name);
 | |
|             return it != reservedNames.end();
 | |
|         }
 | |
| 
 | |
|         bool isOperationPermissibleForObjectImplementation(
 | |
|             const SceneUtil::Optimizer* optimizer, const osg::Drawable* node, unsigned int option) const override
 | |
|         {
 | |
|             if (option & SceneUtil::Optimizer::FLATTEN_STATIC_TRANSFORMS)
 | |
|             {
 | |
|                 if (node->asGeometry() && node->className() == std::string("Geometry"))
 | |
|                     return true;
 | |
|                 else
 | |
|                     return false; // ParticleSystem would have to convert space of all the processors, RigGeometry would
 | |
|                                   // have to convert bones... theoretically possible, but very complicated
 | |
|             }
 | |
|             return (option & optimizer->getPermissibleOptimizationsForObject(node)) != 0;
 | |
|         }
 | |
| 
 | |
|         bool isOperationPermissibleForObjectImplementation(
 | |
|             const SceneUtil::Optimizer* optimizer, const osg::Node* node, unsigned int option) const override
 | |
|         {
 | |
|             if (node->getNumDescriptions() > 0)
 | |
|                 return false;
 | |
|             if (node->getDataVariance() == osg::Object::DYNAMIC)
 | |
|                 return false;
 | |
|             if (isReservedName(node->getName()))
 | |
|                 return false;
 | |
| 
 | |
|             return (option & optimizer->getPermissibleOptimizationsForObject(node)) != 0;
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     static bool canOptimize(std::string_view filename)
 | |
|     {
 | |
|         const std::string_view::size_type slashpos = filename.find_last_of('/');
 | |
|         if (slashpos != std::string_view::npos && slashpos + 1 < filename.size())
 | |
|         {
 | |
|             const std::string_view basename = filename.substr(slashpos + 1);
 | |
|             // xmesh.nif can not be optimized because there are keyframes added in post
 | |
|             if (!basename.empty() && basename[0] == 'x')
 | |
|                 return false;
 | |
| 
 | |
|             // NPC skeleton files can not be optimized because of keyframes added in post
 | |
|             // (most of them are usually named like 'xbase_anim.nif' anyway, but not all of them :( )
 | |
|             if (basename.starts_with("base_anim") || basename.starts_with("skin"))
 | |
|                 return false;
 | |
|         }
 | |
| 
 | |
|         // For spell VFX, DummyXX nodes must remain intact. Not adding those to reservedNames to avoid being overly
 | |
|         // cautious - instead, decide on filename
 | |
|         if (filename.find("vfx_pattern") != std::string_view::npos)
 | |
|             return false;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     unsigned int getOptimizationOptions()
 | |
|     {
 | |
|         using namespace SceneUtil;
 | |
|         const char* env = getenv("OPENMW_OPTIMIZE");
 | |
|         unsigned int options
 | |
|             = Optimizer::FLATTEN_STATIC_TRANSFORMS | Optimizer::REMOVE_REDUNDANT_NODES | Optimizer::MERGE_GEOMETRY;
 | |
|         if (env)
 | |
|         {
 | |
|             std::string str(env);
 | |
| 
 | |
|             if (str.find("OFF") != std::string::npos || str.find('0') != std::string::npos)
 | |
|                 options = 0;
 | |
| 
 | |
|             if (str.find("~FLATTEN_STATIC_TRANSFORMS") != std::string::npos)
 | |
|                 options ^= Optimizer::FLATTEN_STATIC_TRANSFORMS;
 | |
|             else if (str.find("FLATTEN_STATIC_TRANSFORMS") != std::string::npos)
 | |
|                 options |= Optimizer::FLATTEN_STATIC_TRANSFORMS;
 | |
| 
 | |
|             if (str.find("~REMOVE_REDUNDANT_NODES") != std::string::npos)
 | |
|                 options ^= Optimizer::REMOVE_REDUNDANT_NODES;
 | |
|             else if (str.find("REMOVE_REDUNDANT_NODES") != std::string::npos)
 | |
|                 options |= Optimizer::REMOVE_REDUNDANT_NODES;
 | |
| 
 | |
|             if (str.find("~MERGE_GEOMETRY") != std::string::npos)
 | |
|                 options ^= Optimizer::MERGE_GEOMETRY;
 | |
|             else if (str.find("MERGE_GEOMETRY") != std::string::npos)
 | |
|                 options |= Optimizer::MERGE_GEOMETRY;
 | |
|         }
 | |
|         return options;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::shareState(osg::ref_ptr<osg::Node> node)
 | |
|     {
 | |
|         mSharedStateMutex.lock();
 | |
|         mSharedStateManager->share(node.get());
 | |
|         mSharedStateMutex.unlock();
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> SceneManager::loadErrorMarker()
 | |
|     {
 | |
|         try
 | |
|         {
 | |
|             VFS::Path::Normalized path("meshes/marker_error.****");
 | |
|             for (const auto meshType : { "nif", "osg", "osgt", "osgb", "osgx", "osg2", "dae" })
 | |
|             {
 | |
|                 path.changeExtension(meshType);
 | |
|                 if (mVFS->exists(path))
 | |
|                     return load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
 | |
|             }
 | |
|         }
 | |
|         catch (const std::exception& e)
 | |
|         {
 | |
|             Log(Debug::Warning) << "Failed to load error marker:" << e.what()
 | |
|                                 << ", using embedded marker_error instead";
 | |
|         }
 | |
|         Files::IMemStream file(ErrorMarker::sValue.data(), ErrorMarker::sValue.size());
 | |
|         constexpr VFS::Path::NormalizedView errorMarker("error_marker.osgt");
 | |
|         return loadNonNif(errorMarker, file, mImageManager);
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> SceneManager::cloneErrorMarker()
 | |
|     {
 | |
|         if (!mErrorMarker)
 | |
|             mErrorMarker = loadErrorMarker();
 | |
| 
 | |
|         return static_cast<osg::Node*>(mErrorMarker->clone(osg::CopyOp::DEEP_COPY_ALL));
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<const osg::Node> SceneManager::getTemplate(VFS::Path::NormalizedView path, bool compile)
 | |
|     {
 | |
|         osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(path);
 | |
|         if (obj)
 | |
|             return osg::ref_ptr<const osg::Node>(static_cast<osg::Node*>(obj.get()));
 | |
|         else
 | |
|         {
 | |
|             osg::ref_ptr<osg::Node> loaded;
 | |
|             try
 | |
|             {
 | |
|                 loaded = load(path, mVFS, mImageManager, mNifFileManager, mBgsmFileManager);
 | |
| 
 | |
|                 SceneUtil::ProcessExtraDataVisitor extraDataVisitor(this);
 | |
|                 loaded->accept(extraDataVisitor);
 | |
|             }
 | |
|             catch (const std::exception& e)
 | |
|             {
 | |
|                 Log(Debug::Error) << "Failed to load '" << path << "': " << e.what() << ", using marker_error instead";
 | |
|                 loaded = cloneErrorMarker();
 | |
|             }
 | |
| 
 | |
|             // set filtering settings
 | |
|             SetFilterSettingsVisitor setFilterSettingsVisitor(mMinFilter, mMagFilter, mMaxAnisotropy);
 | |
|             loaded->accept(setFilterSettingsVisitor);
 | |
|             SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(
 | |
|                 mMinFilter, mMagFilter, mMaxAnisotropy);
 | |
|             loaded->accept(setFilterSettingsControllerVisitor);
 | |
| 
 | |
|             SceneUtil::ReplaceDepthVisitor replaceDepthVisitor;
 | |
|             loaded->accept(replaceDepthVisitor);
 | |
| 
 | |
|             osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor(createShaderVisitor());
 | |
|             loaded->accept(*shaderVisitor);
 | |
| 
 | |
|             if (canOptimize(path.value()))
 | |
|             {
 | |
|                 SceneUtil::Optimizer optimizer;
 | |
|                 optimizer.setSharedStateManager(mSharedStateManager, &mSharedStateMutex);
 | |
|                 optimizer.setIsOperationPermissibleForObjectCallback(new CanOptimizeCallback);
 | |
| 
 | |
|                 static const unsigned int options
 | |
|                     = getOptimizationOptions() | SceneUtil::Optimizer::SHARE_DUPLICATE_STATE;
 | |
| 
 | |
|                 optimizer.optimize(loaded, options);
 | |
|             }
 | |
|             else
 | |
|                 shareState(loaded);
 | |
| 
 | |
|             if (compile && mIncrementalCompileOperation)
 | |
|                 mIncrementalCompileOperation->add(loaded);
 | |
|             else
 | |
|                 loaded->getBound();
 | |
| 
 | |
|             mCache->addEntryToObjectCache(path.value(), loaded);
 | |
|             return loaded;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> SceneManager::getInstance(VFS::Path::NormalizedView path)
 | |
|     {
 | |
|         return getInstance(getTemplate(path));
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> SceneManager::cloneNode(const osg::Node* base)
 | |
|     {
 | |
|         SceneUtil::CopyOp copyop;
 | |
|         if (const osg::Drawable* drawable = base->asDrawable())
 | |
|         {
 | |
|             if (drawable->asGeometry())
 | |
|             {
 | |
|                 Log(Debug::Warning) << "SceneManager::cloneNode: attempting to clone osg::Geometry. For safety reasons "
 | |
|                                        "this will be expensive. Consider avoiding this call.";
 | |
|                 copyop.setCopyFlags(
 | |
|                     copyop.getCopyFlags() | osg::CopyOp::DEEP_COPY_ARRAYS | osg::CopyOp::DEEP_COPY_PRIMITIVES);
 | |
|             }
 | |
|         }
 | |
|         osg::ref_ptr<osg::Node> cloned = static_cast<osg::Node*>(base->clone(copyop));
 | |
|         // add a ref to the original template to help verify the safety of shallow cloning operations
 | |
|         // in addition, if this node is managed by a cache, we hint to the cache that it's still being used and should
 | |
|         // be kept in cache
 | |
|         cloned->getOrCreateUserDataContainer()->addUserObject(new TemplateRef(base));
 | |
|         return cloned;
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> SceneManager::getInstance(const osg::Node* base)
 | |
|     {
 | |
|         osg::ref_ptr<osg::Node> cloned = cloneNode(base);
 | |
|         // we can skip any scene graphs without update callbacks since we know that particle emitters will have an
 | |
|         // update callback set
 | |
|         if (cloned->getNumChildrenRequiringUpdateTraversal() > 0)
 | |
|         {
 | |
|             InitParticlesVisitor visitor(mParticleSystemMask);
 | |
|             cloned->accept(visitor);
 | |
|         }
 | |
| 
 | |
|         return cloned;
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<osg::Node> SceneManager::getInstance(VFS::Path::NormalizedView path, osg::Group* parentNode)
 | |
|     {
 | |
|         osg::ref_ptr<osg::Node> cloned = getInstance(path);
 | |
|         attachTo(cloned, parentNode);
 | |
|         return cloned;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::attachTo(osg::Node* instance, osg::Group* parentNode) const
 | |
|     {
 | |
|         parentNode->addChild(instance);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::releaseGLObjects(osg::State* state)
 | |
|     {
 | |
|         mCache->releaseGLObjects(state);
 | |
| 
 | |
|         mShaderManager->releaseGLObjects(state);
 | |
| 
 | |
|         std::lock_guard<std::mutex> lock(mSharedStateMutex);
 | |
|         mSharedStateManager->releaseGLObjects(state);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setIncrementalCompileOperation(osgUtil::IncrementalCompileOperation* ico)
 | |
|     {
 | |
|         mIncrementalCompileOperation = ico;
 | |
|     }
 | |
| 
 | |
|     osgUtil::IncrementalCompileOperation* SceneManager::getIncrementalCompileOperation()
 | |
|     {
 | |
|         return mIncrementalCompileOperation.get();
 | |
|     }
 | |
| 
 | |
|     Resource::ImageManager* SceneManager::getImageManager()
 | |
|     {
 | |
|         return mImageManager;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setParticleSystemMask(unsigned int mask)
 | |
|     {
 | |
|         mParticleSystemMask = mask;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setFilterSettings(
 | |
|         const std::string& magfilter, const std::string& minfilter, const std::string& mipmap, int maxAnisotropy)
 | |
|     {
 | |
|         osg::Texture::FilterMode min = osg::Texture::LINEAR;
 | |
|         osg::Texture::FilterMode mag = osg::Texture::LINEAR;
 | |
| 
 | |
|         if (magfilter == "nearest")
 | |
|             mag = osg::Texture::NEAREST;
 | |
|         else if (magfilter != "linear")
 | |
|             Log(Debug::Warning) << "Warning: Invalid texture mag filter: " << magfilter;
 | |
| 
 | |
|         if (minfilter == "nearest")
 | |
|             min = osg::Texture::NEAREST;
 | |
|         else if (minfilter != "linear")
 | |
|             Log(Debug::Warning) << "Warning: Invalid texture min filter: " << minfilter;
 | |
| 
 | |
|         if (mipmap == "nearest")
 | |
|         {
 | |
|             if (min == osg::Texture::NEAREST)
 | |
|                 min = osg::Texture::NEAREST_MIPMAP_NEAREST;
 | |
|             else if (min == osg::Texture::LINEAR)
 | |
|                 min = osg::Texture::LINEAR_MIPMAP_NEAREST;
 | |
|         }
 | |
|         else if (mipmap != "none")
 | |
|         {
 | |
|             if (mipmap != "linear")
 | |
|                 Log(Debug::Warning) << "Warning: Invalid texture mipmap: " << mipmap;
 | |
|             if (min == osg::Texture::NEAREST)
 | |
|                 min = osg::Texture::NEAREST_MIPMAP_LINEAR;
 | |
|             else if (min == osg::Texture::LINEAR)
 | |
|                 min = osg::Texture::LINEAR_MIPMAP_LINEAR;
 | |
|         }
 | |
| 
 | |
|         mMinFilter = min;
 | |
|         mMagFilter = mag;
 | |
|         mMaxAnisotropy = std::max(1, maxAnisotropy);
 | |
| 
 | |
|         SetFilterSettingsControllerVisitor setFilterSettingsControllerVisitor(mMinFilter, mMagFilter, mMaxAnisotropy);
 | |
|         SetFilterSettingsVisitor setFilterSettingsVisitor(mMinFilter, mMagFilter, mMaxAnisotropy);
 | |
| 
 | |
|         mCache->accept(setFilterSettingsVisitor);
 | |
|         mCache->accept(setFilterSettingsControllerVisitor);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::applyFilterSettings(osg::Texture* tex)
 | |
|     {
 | |
|         tex->setFilter(osg::Texture::MIN_FILTER, mMinFilter);
 | |
|         tex->setFilter(osg::Texture::MAG_FILTER, mMagFilter);
 | |
|         tex->setMaxAnisotropy(mMaxAnisotropy);
 | |
|     }
 | |
| 
 | |
|     void SceneManager::setUnRefImageDataAfterApply(bool unref)
 | |
|     {
 | |
|         mUnRefImageDataAfterApply = unref;
 | |
|     }
 | |
| 
 | |
|     void SceneManager::updateCache(double referenceTime)
 | |
|     {
 | |
|         ResourceManager::updateCache(referenceTime);
 | |
| 
 | |
|         mSharedStateMutex.lock();
 | |
|         mSharedStateManager->prune();
 | |
|         mSharedStateMutex.unlock();
 | |
| 
 | |
|         if (mIncrementalCompileOperation)
 | |
|         {
 | |
|             std::lock_guard<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
 | |
|             osgUtil::IncrementalCompileOperation::CompileSets& sets = mIncrementalCompileOperation->getToCompile();
 | |
|             for (osgUtil::IncrementalCompileOperation::CompileSets::iterator it = sets.begin(); it != sets.end();)
 | |
|             {
 | |
|                 int refcount = (*it)->_subgraphToCompile->referenceCount();
 | |
|                 if ((*it)->_subgraphToCompile->asDrawable())
 | |
|                     refcount -= 1; // ref by CompileList.
 | |
|                 if (refcount <= 2) // ref by ObjectCache + ref by _subgraphToCompile.
 | |
|                 {
 | |
|                     // no other ref = not needed anymore.
 | |
|                     it = sets.erase(it);
 | |
|                 }
 | |
|                 else
 | |
|                     ++it;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void SceneManager::clearCache()
 | |
|     {
 | |
|         ResourceManager::clearCache();
 | |
| 
 | |
|         std::lock_guard<std::mutex> lock(mSharedStateMutex);
 | |
|         mSharedStateManager->clearCache();
 | |
|     }
 | |
| 
 | |
|     void SceneManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const
 | |
|     {
 | |
|         if (mIncrementalCompileOperation)
 | |
|         {
 | |
|             std::lock_guard<OpenThreads::Mutex> lock(*mIncrementalCompileOperation->getToCompiledMutex());
 | |
|             stats->setAttribute(frameNumber, "Compiling", mIncrementalCompileOperation->getToCompile().size());
 | |
|         }
 | |
| 
 | |
|         {
 | |
|             std::lock_guard<std::mutex> lock(mSharedStateMutex);
 | |
|             stats->setAttribute(frameNumber, "Texture", mSharedStateManager->getNumSharedTextures());
 | |
|             stats->setAttribute(frameNumber, "StateSet", mSharedStateManager->getNumSharedStateSets());
 | |
|         }
 | |
| 
 | |
|         Resource::reportStats("Node", frameNumber, mCache->getStats(), *stats);
 | |
|     }
 | |
| 
 | |
|     osg::ref_ptr<Shader::ShaderVisitor> SceneManager::createShaderVisitor(const std::string& shaderPrefix)
 | |
|     {
 | |
|         osg::ref_ptr<Shader::ShaderVisitor> shaderVisitor(
 | |
|             new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix));
 | |
|         shaderVisitor->setForceShaders(mForceShaders);
 | |
|         shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps);
 | |
|         shaderVisitor->setNormalMapPattern(mNormalMapPattern);
 | |
|         shaderVisitor->setNormalHeightMapPattern(mNormalHeightMapPattern);
 | |
|         shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps);
 | |
|         shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
 | |
|         shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
 | |
|         shaderVisitor->setConvertAlphaTestToAlphaToCoverage(mConvertAlphaTestToAlphaToCoverage);
 | |
|         shaderVisitor->setAdjustCoverageForAlphaTest(mAdjustCoverageForAlphaTest);
 | |
|         shaderVisitor->setSupportsNormalsRT(mSupportsNormalsRT);
 | |
|         shaderVisitor->setWeatherParticleOcclusion(mWeatherParticleOcclusion);
 | |
|         return shaderVisitor;
 | |
|     }
 | |
| }
 |