mirror of https://github.com/OpenMW/openmw.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1249 lines
47 KiB
C++
1249 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 osg::Depth;
|
|
depth->setWriteMask(false);
|
|
|
|
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
|
|
}
|
|
else if (stateset->getRenderingHint() == osg::StateSet::OPAQUE_BIN)
|
|
{
|
|
osg::ref_ptr<osg::Depth> depth = new osg::Depth;
|
|
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;
|
|
}
|
|
}
|