Add OpenMW commits up to 14 Nov 2020

# Conflicts:
#   .travis.yml
#   apps/openmw/mwmechanics/tickableeffects.cpp
pull/593/head
David Cernat 4 years ago
commit 676481d061

@ -24,7 +24,7 @@ addons:
# FFmpeg # FFmpeg
libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev, libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev,
# Audio, Video and Misc. deps # Audio, Video and Misc. deps
libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev libsdl2-dev, libqt5opengl5-dev, libopenal-dev, libunshield-dev, libtinyxml-dev, liblz4-dev,
# The other ones from OpenMW ppa # The other ones from OpenMW ppa
libbullet-dev, libopenscenegraph-3.4-dev, libmygui-dev, libbullet-dev, libopenscenegraph-3.4-dev, libmygui-dev,
# tes3mp stuff # tes3mp stuff
@ -71,11 +71,11 @@ before_script:
- ./CI/before_script.${TRAVIS_OS_NAME}.sh - ./CI/before_script.${TRAVIS_OS_NAME}.sh
script: script:
- cd ./build - cd ./build
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi - ${ANALYZE} make -j3; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi - if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}" - cd "${TRAVIS_BUILD_DIR}"
- ccache -s - ccache -s
#deploy: #deploy:

@ -10,10 +10,12 @@
Bug #2976 [reopened]: Issues combining settings from the command line and both config files Bug #2976 [reopened]: Issues combining settings from the command line and both config files
Bug #3676: NiParticleColorModifier isn't applied properly Bug #3676: NiParticleColorModifier isn't applied properly
Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects Bug #3714: Savegame fails to load due to conflict between SpellState and MagicEffects
Bug #3789: Crash in visitEffectSources while in battle
Bug #3862: Random container contents behave differently than vanilla Bug #3862: Random container contents behave differently than vanilla
Bug #3929: Leveled list merchant containers respawn on barter Bug #3929: Leveled list merchant containers respawn on barter
Bug #4021: Attributes and skills are not stored as floats Bug #4021: Attributes and skills are not stored as floats
Bug #4055: Local scripts don't inherit variables from their base record Bug #4055: Local scripts don't inherit variables from their base record
Bug #4083: Door animation freezes when colliding with actors
Bug #4623: Corprus implementation is incorrect Bug #4623: Corprus implementation is incorrect
Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level Bug #4631: Setting MSAA level too high doesn't fall back to highest supported level
Bug #4764: Data race in osg ParticleSystem Bug #4764: Data race in osg ParticleSystem

@ -5,8 +5,5 @@ command -v ccache >/dev/null 2>&1 || brew install ccache
command -v cmake >/dev/null 2>&1 || brew install cmake command -v cmake >/dev/null 2>&1 || brew install cmake
command -v qmake >/dev/null 2>&1 || brew install qt command -v qmake >/dev/null 2>&1 || brew install qt
brew link --overwrite lz4 # overwrite system lz4; use brew curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip
brew reinstall lz4
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-ef2462c.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

@ -25,5 +25,6 @@ cmake \
-D BUILD_BSATOOL=TRUE \ -D BUILD_BSATOOL=TRUE \
-D BUILD_ESSIMPORTER=TRUE \ -D BUILD_ESSIMPORTER=TRUE \
-D BUILD_NIFTEST=TRUE \ -D BUILD_NIFTEST=TRUE \
-D BULLET_USE_DOUBLES=TRUE \
-G"Unix Makefiles" \ -G"Unix Makefiles" \
.. ..

@ -132,6 +132,7 @@ int main(int argc, char **argv)
if(!parseOptions (argc, argv, files)) if(!parseOptions (argc, argv, files))
return 1; return 1;
Nif::NIFFile::setLoadUnsupportedFiles(true);
// std::cout << "Reading Files" << std::endl; // std::cout << "Reading Files" << std::endl;
for(std::vector<std::string>::const_iterator it=files.begin(); it!=files.end(); ++it) for(std::vector<std::string>::const_iterator it=files.begin(); it!=files.end(); ++it)
{ {

@ -961,12 +961,54 @@ namespace MWMechanics
} }
}; };
void Actors::applyCureEffects(const MWWorld::Ptr& actor)
{
CreatureStats &creatureStats = actor.getClass().getCreatureStats(actor);
const MagicEffects &effects = creatureStats.getMagicEffects();
if (effects.get(ESM::MagicEffect::CurePoison).getModifier() > 0)
{
creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Poison);
if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Poison);
}
else if (effects.get(ESM::MagicEffect::CureParalyzation).getModifier() > 0)
{
creatureStats.getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
creatureStats.getSpells().purgeEffect(ESM::MagicEffect::Paralyze);
if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Paralyze);
}
else if (effects.get(ESM::MagicEffect::CureCommonDisease).getModifier() > 0)
{
creatureStats.getSpells().purgeCommonDisease();
}
else if (effects.get(ESM::MagicEffect::CureBlightDisease).getModifier() > 0)
{
creatureStats.getSpells().purgeBlightDisease();
}
else if (effects.get(ESM::MagicEffect::CureCorprusDisease).getModifier() > 0)
{
creatureStats.getActiveSpells().purgeCorprusDisease();
creatureStats.getSpells().purgeCorprusDisease();
if (actor.getClass().hasInventoryStore(actor))
actor.getClass().getInventoryStore(actor).purgeEffect(ESM::MagicEffect::Corprus, true);
}
else if (effects.get(ESM::MagicEffect::RemoveCurse).getModifier() > 0)
{
creatureStats.getSpells().purgeCurses();
}
}
void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration) void Actors::calculateCreatureStatModifiers (const MWWorld::Ptr& ptr, float duration)
{ {
CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr); CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
const MagicEffects &effects = creatureStats.getMagicEffects(); const MagicEffects &effects = creatureStats.getMagicEffects();
bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState(); bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
applyCureEffects(ptr);
bool wasDead = creatureStats.isDead(); bool wasDead = creatureStats.isDead();
if (duration > 0) if (duration > 0)
@ -1810,6 +1852,9 @@ namespace MWMechanics
void Actors::predictAndAvoidCollisions() void Actors::predictAndAvoidCollisions()
{ {
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
return;
const float minGap = 10.f; const float minGap = 10.f;
const float maxDistForPartialAvoiding = 200.f; const float maxDistForPartialAvoiding = 200.f;
const float maxDistForStrictAvoiding = 100.f; const float maxDistForStrictAvoiding = 100.f;

@ -226,6 +226,7 @@ namespace MWMechanics
private: private:
void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl); void updateVisibility (const MWWorld::Ptr& ptr, CharacterController* ctrl);
void applyCureEffects (const MWWorld::Ptr& actor);
PtrActorMap mActors; PtrActorMap mActors;
float mTimerDisposeSummonsCorpses; float mTimerDisposeSummonsCorpses;

@ -217,40 +217,6 @@ namespace MWMechanics
break; break;
} }
case ESM::MagicEffect::CurePoison:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Poison);
break;
case ESM::MagicEffect::CureParalyzation:
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
break;
case ESM::MagicEffect::CureCommonDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCommonDisease();
break;
case ESM::MagicEffect::CureBlightDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeBlightDisease();
break;
case ESM::MagicEffect::CureCorprusDisease:
actor.getClass().getCreatureStats(actor).getSpells().purgeCorprusDisease();
break;
case ESM::MagicEffect::RemoveCurse:
actor.getClass().getCreatureStats(actor).getSpells().purgeCurses();
break;
/*
Start of tes3mp addition
Don't apply paralysis to DedicatedPlayers and DedicatedActors unilterally on this client
*/
case ESM::MagicEffect::Paralyze:
{
if (mwmp::PlayerList::isDedicatedPlayer(actor) || mwmp::Main::get().getCellController()->isDedicatedActor(actor))
{
actor.getClass().getCreatureStats(actor).getActiveSpells().purgeEffect(ESM::MagicEffect::Paralyze);
break;
}
}
/*
End of tes3mp addition
*/
default: default:
return false; return false;
} }

@ -12,6 +12,7 @@ namespace MWMechanics
struct EffectKey; struct EffectKey;
/// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed /// Apply a magic effect that is applied in tick intervals until its remaining time ends or it is removed
/// Note: this function works in loop, so magic effects should not be removed here to avoid iterator invalidation.
/// @return Was the effect a tickable effect with a magnitude? /// @return Was the effect a tickable effect with a magnitude?
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude); bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
} }

@ -2,6 +2,8 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h> #include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "components/misc/convert.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
namespace MWPhysics namespace MWPhysics
@ -20,7 +22,7 @@ namespace MWPhysics
collisionObject = col1Wrap->m_collisionObject; collisionObject = col1Wrap->m_collisionObject;
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer()); PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
if (holder) if (holder)
mResult.push_back(holder->getPtr()); mResult.emplace_back(ContactPoint{holder->getPtr(), Misc::Convert::toOsg(cp.m_positionWorldOnB), Misc::Convert::toOsg(cp.m_normalWorldOnB)});
return 0.f; return 0.f;
} }

@ -7,6 +7,8 @@
#include "../mwworld/ptr.hpp" #include "../mwworld/ptr.hpp"
#include "physicssystem.hpp"
class btCollisionObject; class btCollisionObject;
struct btCollisionObjectWrapper; struct btCollisionObjectWrapper;
@ -23,7 +25,7 @@ namespace MWPhysics
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0, const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) override; const btCollisionObjectWrapper* col1Wrap,int partId1,int index1) override;
std::vector<MWWorld::Ptr> mResult; std::vector<ContactPoint> mResult;
}; };
} }

@ -427,15 +427,15 @@ namespace MWPhysics
return osg::Vec3f(); return osg::Vec3f();
} }
std::vector<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const std::vector<ContactPoint> PhysicsSystem::getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
{ {
btCollisionObject* me = nullptr; btCollisionObject* me = nullptr;
ObjectMap::const_iterator found = mObjects.find(ptr); auto found = mObjects.find(ptr);
if (found != mObjects.end()) if (found != mObjects.end())
me = found->second->getCollisionObject(); me = found->second->getCollisionObject();
else else
return std::vector<MWWorld::Ptr>(); return {};
ContactTestResultCallback resultCallback (me); ContactTestResultCallback resultCallback (me);
resultCallback.m_collisionFilterGroup = collisionGroup; resultCallback.m_collisionFilterGroup = collisionGroup;
@ -444,6 +444,14 @@ namespace MWPhysics
return resultCallback.mResult; return resultCallback.mResult;
} }
std::vector<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
{
std::vector<MWWorld::Ptr> actors;
for (auto& [actor, point, normal] : getCollisionsPoints(ptr, collisionGroup, collisionMask))
actors.emplace_back(actor);
return actors;
}
osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight) osg::Vec3f PhysicsSystem::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight)
{ {
ActorMap::iterator found = mActors.find(ptr); ActorMap::iterator found = mActors.find(ptr);

@ -57,6 +57,13 @@ namespace MWPhysics
class Actor; class Actor;
class PhysicsTaskScheduler; class PhysicsTaskScheduler;
struct ContactPoint
{
MWWorld::Ptr mObject;
osg::Vec3f mPoint;
osg::Vec3f mNormal;
};
struct LOSRequest struct LOSRequest
{ {
LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2); LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2);
@ -145,6 +152,7 @@ namespace MWPhysics
void debugDraw(); void debugDraw();
std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with std::vector<MWWorld::Ptr> getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const; ///< get handles this object collides with
std::vector<ContactPoint> getCollisionsPoints(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const;
osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight); osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, float maxHeight);
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor, std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor,

@ -16,7 +16,6 @@
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/resource/keyframemanager.hpp> #include <components/resource/keyframemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/misc/constants.hpp> #include <components/misc/constants.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
@ -37,8 +36,6 @@
#include <components/settings/settings.hpp> #include <components/settings/settings.hpp>
#include <components/shader/shadermanager.hpp>
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
@ -493,9 +490,8 @@ namespace MWRender
class TransparencyUpdater : public SceneUtil::StateSetUpdater class TransparencyUpdater : public SceneUtil::StateSetUpdater
{ {
public: public:
TransparencyUpdater(const float alpha, osg::ref_ptr<osg::Uniform> shadowUniform) TransparencyUpdater(const float alpha)
: mAlpha(alpha) : mAlpha(alpha)
, mShadowUniform(shadowUniform)
{ {
} }
@ -509,9 +505,6 @@ namespace MWRender
{ {
osg::BlendFunc* blendfunc (new osg::BlendFunc); osg::BlendFunc* blendfunc (new osg::BlendFunc);
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
// TODO: don't do this anymore once custom shadow renderbin is handling it
if (mShadowUniform)
stateset->addUniform(mShadowUniform);
stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS); stateset->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
@ -533,7 +526,6 @@ namespace MWRender
private: private:
float mAlpha; float mAlpha;
osg::ref_ptr<osg::Uniform> mShadowUniform;
}; };
struct Animation::AnimSource struct Animation::AnimSource
@ -1773,7 +1765,7 @@ namespace MWRender
{ {
if (mTransparencyUpdater == nullptr) if (mTransparencyUpdater == nullptr)
{ {
mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform()); mTransparencyUpdater = new TransparencyUpdater(alpha);
mObjectRoot->addCullCallback(mTransparencyUpdater); mObjectRoot->addCullCallback(mTransparencyUpdater);
} }
else else

@ -216,6 +216,7 @@ namespace MWRender
resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders")); resourceSystem->getSceneManager()->setNormalHeightMapPattern(Settings::Manager::getString("normal height map pattern", "Shaders"));
resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders")); resourceSystem->getSceneManager()->setAutoUseSpecularMaps(Settings::Manager::getBool("auto use object specular maps", "Shaders"));
resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders")); resourceSystem->getSceneManager()->setSpecularMapPattern(Settings::Manager::getString("specular map pattern", "Shaders"));
resourceSystem->getSceneManager()->setApplyLightingToEnvMaps(Settings::Manager::getBool("apply lighting to environment maps", "Shaders"));
osg::ref_ptr<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager; osg::ref_ptr<SceneUtil::LightManager> sceneRoot = new SceneUtil::LightManager;
sceneRoot->setLightingMask(Mask_Lighting); sceneRoot->setLightingMask(Mask_Lighting);
@ -361,6 +362,7 @@ namespace MWRender
mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater)); mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater));
NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor); NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor);
NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect); NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect);
Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models"));
mNearClip = Settings::Manager::getFloat("near clip", "Camera"); mNearClip = Settings::Manager::getFloat("near clip", "Camera");
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera"); mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");

@ -1771,19 +1771,26 @@ namespace MWWorld
float minRot = door.getCellRef().getPosition().rot[2]; float minRot = door.getCellRef().getPosition().rot[2];
float maxRot = minRot + osg::DegreesToRadians(90.f); float maxRot = minRot + osg::DegreesToRadians(90.f);
float diff = duration * osg::DegreesToRadians(90.f); float diff = duration * osg::DegreesToRadians(90.f) * (state == MWWorld::DoorState::Opening ? 1 : -1);
float targetRot = std::min(std::max(minRot, oldRot + diff * (state == MWWorld::DoorState::Opening ? 1 : -1)), maxRot); float targetRot = std::min(std::max(minRot, oldRot + diff), maxRot);
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none); rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none);
bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot; bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot;
/// \todo should use convexSweepTest here /// \todo should use convexSweepTest here
bool collisionWithActor = false; bool collisionWithActor = false;
std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor); for (auto& [ptr, point, normal] : mPhysics->getCollisionsPoints(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor))
for (MWWorld::Ptr& ptr : collisions)
{ {
if (ptr.getClass().isActor()) if (ptr.getClass().isActor())
{ {
auto localPoint = objPos.asVec3() - point;
osg::Vec3f direction = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * localPoint - localPoint;
direction.normalize();
mPhysics->reportCollision(Misc::Convert::toBullet(point), Misc::Convert::toBullet(normal));
if (direction * normal < 0) // door is turning away from actor
continue;
collisionWithActor = true; collisionWithActor = true;
// Collided with actor, ask actor to try to avoid door // Collided with actor, ask actor to try to avoid door

@ -373,6 +373,7 @@ namespace
TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default)
{ {
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
@ -422,11 +423,13 @@ namespace
{ {
mNode.hasBounds = true; mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision; mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3); mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.boundPos = osg::Vec3f(-1, -2, -3); mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
@ -444,12 +447,14 @@ namespace
{ {
mNode.hasBounds = true; mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision; mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3); mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.boundPos = osg::Vec3f(-1, -2, -3); mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode)})); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
@ -467,16 +472,19 @@ namespace
{ {
mNode.hasBounds = true; mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision; mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3); mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.boundPos = osg::Vec3f(-1, -2, -3); mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNiNode.hasBounds = true; mNiNode.hasBounds = true;
mNiNode.boundXYZ = osg::Vec3f(4, 5, 6); mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNiNode.boundPos = osg::Vec3f(-4, -5, -6); mNiNode.bounds.box.extents = osg::Vec3f(4, 5, 6);
mNiNode.bounds.box.center = osg::Vec3f(-4, -5, -6);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode)})); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
@ -494,20 +502,24 @@ namespace
{ {
mNode.hasBounds = true; mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision; mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3); mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.boundPos = osg::Vec3f(-1, -2, -3); mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNode2.hasBounds = true; mNode2.hasBounds = true;
mNode2.boundXYZ = osg::Vec3f(4, 5, 6); mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode2.boundPos = osg::Vec3f(-4, -5, -6); mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6);
mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6);
mNiNode.hasBounds = true; mNiNode.hasBounds = true;
mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNiNode.boundPos = osg::Vec3f(-7, -8, -9); mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9);
mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
@ -524,21 +536,25 @@ namespace
TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds)
{ {
mNode.hasBounds = true; mNode.hasBounds = true;
mNode.boundXYZ = osg::Vec3f(1, 2, 3); mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.boundPos = osg::Vec3f(-1, -2, -3); mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNode2.hasBounds = true; mNode2.hasBounds = true;
mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; mNode2.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode2.boundXYZ = osg::Vec3f(4, 5, 6); mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode2.boundPos = osg::Vec3f(-4, -5, -6); mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6);
mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6);
mNiNode.hasBounds = true; mNiNode.hasBounds = true;
mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNiNode.boundPos = osg::Vec3f(-7, -8, -9); mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9);
mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9);
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile); const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected; Resource::BulletShape expected;
@ -555,8 +571,9 @@ namespace
TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape)
{ {
mNode.hasBounds = true; mNode.hasBounds = true;
mNode.boundXYZ = osg::Vec3f(1, 2, 3); mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.boundPos = osg::Vec3f(-1, -2, -3); mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode));
@ -588,8 +605,9 @@ namespace
TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape) TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape)
{ {
mNiTriShape.hasBounds = true; mNiTriShape.hasBounds = true;
mNiTriShape.boundXYZ = osg::Vec3f(1, 2, 3); mNiTriShape.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNiTriShape.boundPos = osg::Vec3f(-1, -2, -3); mNiTriShape.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNiTriShape.bounds.box.center = osg::Vec3f(-1, -2, -3);
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape));

@ -52,7 +52,7 @@ add_component_dir (shader
add_component_dir (sceneutil add_component_dir (sceneutil
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin
) )
add_component_dir (nif add_component_dir (nif

@ -11,11 +11,22 @@
namespace Debug namespace Debug
{ {
#ifdef _WIN32 #ifdef _WIN32
bool isRedirected(DWORD nStdHandle)
{
DWORD fileType = GetFileType(GetStdHandle(nStdHandle));
return (fileType == FILE_TYPE_DISK) || (fileType == FILE_TYPE_PIPE);
}
bool attachParentConsole() bool attachParentConsole()
{ {
if (GetConsoleWindow() != nullptr) if (GetConsoleWindow() != nullptr)
return true; return true;
bool inRedirected = isRedirected(STD_INPUT_HANDLE);
bool outRedirected = isRedirected(STD_OUTPUT_HANDLE);
bool errRedirected = isRedirected(STD_ERROR_HANDLE);
if (AttachConsole(ATTACH_PARENT_PROCESS)) if (AttachConsole(ATTACH_PARENT_PROCESS))
{ {
fflush(stdout); fflush(stdout);
@ -24,12 +35,21 @@ namespace Debug
std::cerr.flush(); std::cerr.flush();
// this looks dubious but is really the right way // this looks dubious but is really the right way
_wfreopen(L"CON", L"w", stdout); if (!inRedirected)
_wfreopen(L"CON", L"w", stderr); {
_wfreopen(L"CON", L"r", stdin); _wfreopen(L"CON", L"r", stdin);
freopen("CON", "r", stdin);
}
if (!outRedirected)
{
_wfreopen(L"CON", L"w", stdout);
freopen("CON", "w", stdout); freopen("CON", "w", stdout);
}
if (!errRedirected)
{
_wfreopen(L"CON", L"w", stderr);
freopen("CON", "w", stderr); freopen("CON", "w", stderr);
freopen("CON", "r", stdin); }
return true; return true;
} }

@ -92,6 +92,8 @@ namespace Nif
void NiMaterialColorController::read(NIFStream *nif) void NiMaterialColorController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
if (nif->getVersion() > NIFStream::generateVersion(10,1,0,103))
interpolator.read(nif);
// Two bits that correspond to the controlled material color. // Two bits that correspond to the controlled material color.
// 00: Ambient // 00: Ambient
// 01: Diffuse // 01: Diffuse
@ -101,12 +103,14 @@ namespace Nif
targetColor = nif->getUShort() & 3; targetColor = nif->getUShort() & 3;
else else
targetColor = (flags >> 4) & 3; targetColor = (flags >> 4) & 3;
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
data.read(nif); data.read(nif);
} }
void NiMaterialColorController::post(NIFFile *nif) void NiMaterialColorController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
interpolator.post(nif);
data.post(nif); data.post(nif);
} }
@ -161,25 +165,33 @@ namespace Nif
void NiKeyframeController::read(NIFStream *nif) void NiKeyframeController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
data.read(nif); data.read(nif);
else
interpolator.read(nif);
} }
void NiKeyframeController::post(NIFFile *nif) void NiKeyframeController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
data.post(nif); data.post(nif);
interpolator.post(nif);
} }
void NiFloatInterpController::read(NIFStream *nif) void NiFloatInterpController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
data.read(nif); data.read(nif);
else
interpolator.read(nif);
} }
void NiFloatInterpController::post(NIFFile *nif) void NiFloatInterpController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
data.post(nif); data.post(nif);
interpolator.post(nif);
} }
void NiGeomMorpherController::read(NIFStream *nif) void NiGeomMorpherController::read(NIFStream *nif)
@ -189,13 +201,34 @@ namespace Nif
/*bool updateNormals = !!*/nif->getUShort(); /*bool updateNormals = !!*/nif->getUShort();
data.read(nif); data.read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW) if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW)
{
/*bool alwaysActive = */nif->getChar(); // Always 0 /*bool alwaysActive = */nif->getChar(); // Always 0
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,106))
{
if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB)
{
interpolators.read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,2,0,0) && nif->getBethVersion() > 9)
{
unsigned int numUnknown = nif->getUInt();
nif->skip(4 * numUnknown);
}
}
else
{
// TODO: handle weighted interpolators
unsigned int numInterps = nif->getUInt();
nif->skip(8 * numInterps);
}
}
}
} }
void NiGeomMorpherController::post(NIFFile *nif) void NiGeomMorpherController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
data.post(nif); data.post(nif);
interpolators.post(nif);
} }
void NiVisController::read(NIFStream *nif) void NiVisController::read(NIFStream *nif)
@ -213,6 +246,8 @@ namespace Nif
void NiFlipController::read(NIFStream *nif) void NiFlipController::read(NIFStream *nif)
{ {
Controller::read(nif); Controller::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,2,0,0))
mInterpolator.read(nif);
mTexSlot = nif->getUInt(); mTexSlot = nif->getUInt();
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103)) if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
{ {
@ -225,7 +260,69 @@ namespace Nif
void NiFlipController::post(NIFFile *nif) void NiFlipController::post(NIFFile *nif)
{ {
Controller::post(nif); Controller::post(nif);
mInterpolator.post(nif);
mSources.post(nif); mSources.post(nif);
} }
void bhkBlendController::read(NIFStream *nif)
{
Controller::read(nif);
nif->getUInt(); // Zero
}
void NiPoint3Interpolator::read(NIFStream *nif)
{
defaultVal = nif->getVector3();
data.read(nif);
}
void NiPoint3Interpolator::post(NIFFile *nif)
{
data.post(nif);
}
void NiBoolInterpolator::read(NIFStream *nif)
{
defaultVal = nif->getBoolean();
data.read(nif);
}
void NiBoolInterpolator::post(NIFFile *nif)
{
data.post(nif);
}
void NiFloatInterpolator::read(NIFStream *nif)
{
defaultVal = nif->getFloat();
data.read(nif);
}
void NiFloatInterpolator::post(NIFFile *nif)
{
data.post(nif);
}
void NiTransformInterpolator::read(NIFStream *nif)
{
defaultPos = nif->getVector3();
defaultRot = nif->getQuaternion();
defaultScale = nif->getFloat();
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,109))
{
if (!nif->getBoolean())
defaultPos = osg::Vec3f();
if (!nif->getBoolean())
defaultRot = osg::Quat();
if (!nif->getBoolean())
defaultScale = 1.f;
}
data.read(nif);
}
void NiTransformInterpolator::post(NIFFile *nif)
{
data.post(nif);
}
} }

@ -83,6 +83,7 @@ using NiBSPArrayController = NiParticleSystemController;
class NiMaterialColorController : public Controller class NiMaterialColorController : public Controller
{ {
public: public:
NiPoint3InterpolatorPtr interpolator;
NiPosDataPtr data; NiPosDataPtr data;
unsigned int targetColor; unsigned int targetColor;
@ -138,6 +139,7 @@ class NiKeyframeController : public Controller
{ {
public: public:
NiKeyframeDataPtr data; NiKeyframeDataPtr data;
NiTransformInterpolatorPtr interpolator;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
@ -146,6 +148,7 @@ public:
struct NiFloatInterpController : public Controller struct NiFloatInterpController : public Controller
{ {
NiFloatDataPtr data; NiFloatDataPtr data;
NiFloatInterpolatorPtr interpolator;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
@ -158,6 +161,7 @@ class NiGeomMorpherController : public Controller
{ {
public: public:
NiMorphDataPtr data; NiMorphDataPtr data;
NiFloatInterpolatorList interpolators;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
@ -175,6 +179,7 @@ public:
class NiFlipController : public Controller class NiFlipController : public Controller
{ {
public: public:
NiFloatInterpolatorPtr mInterpolator;
int mTexSlot; // NiTexturingProperty::TextureType int mTexSlot; // NiTexturingProperty::TextureType
float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources
NiSourceTextureList mSources; NiSourceTextureList mSources;
@ -183,5 +188,47 @@ public:
void post(NIFFile *nif) override; void post(NIFFile *nif) override;
}; };
struct bhkBlendController : public Controller
{
void read(NIFStream *nif) override;
};
struct Interpolator : public Record { };
struct NiPoint3Interpolator : public Interpolator
{
osg::Vec3f defaultVal;
NiPosDataPtr data;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
};
struct NiBoolInterpolator : public Interpolator
{
bool defaultVal;
NiBoolDataPtr data;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
};
struct NiFloatInterpolator : public Interpolator
{
float defaultVal;
NiFloatDataPtr data;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
};
struct NiTransformInterpolator : public Interpolator
{
osg::Vec3f defaultPos;
osg::Quat defaultRot;
float defaultScale;
NiKeyframeDataPtr data;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
};
} // Namespace } // Namespace
#endif #endif

@ -6,6 +6,8 @@ namespace Nif
void NiSkinInstance::read(NIFStream *nif) void NiSkinInstance::read(NIFStream *nif)
{ {
data.read(nif); data.read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,101))
partitions.read(nif);
root.read(nif); root.read(nif);
bones.read(nif); bones.read(nif);
} }
@ -13,6 +15,7 @@ void NiSkinInstance::read(NIFStream *nif)
void NiSkinInstance::post(NIFFile *nif) void NiSkinInstance::post(NIFFile *nif)
{ {
data.post(nif); data.post(nif);
partitions.post(nif);
root.post(nif); root.post(nif);
bones.post(nif); bones.post(nif);
@ -320,7 +323,7 @@ void NiSkinData::read(NIFStream *nif)
int boneNum = nif->getInt(); int boneNum = nif->getInt();
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0)) if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW && nif->getVersion() <= NIFStream::generateVersion(10,1,0,0))
nif->skip(4); // NiSkinPartition link partitions.read(nif);
// Has vertex weights flag // Has vertex weights flag
if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean()) if (nif->getVersion() > NIFStream::generateVersion(4,2,1,0) && !nif->getBoolean())
@ -345,6 +348,69 @@ void NiSkinData::read(NIFStream *nif)
} }
} }
void NiSkinData::post(NIFFile *nif)
{
partitions.post(nif);
}
void NiSkinPartition::read(NIFStream *nif)
{
unsigned int num = nif->getUInt();
data.resize(num);
for (auto& partition : data)
partition.read(nif);
}
void NiSkinPartition::Partition::read(NIFStream *nif)
{
unsigned short numVertices = nif->getUShort();
unsigned short numTriangles = nif->getUShort();
unsigned short numBones = nif->getUShort();
unsigned short numStrips = nif->getUShort();
unsigned short bonesPerVertex = nif->getUShort();
if (numBones)
nif->getUShorts(bones, numBones);
bool hasVertexMap = true;
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
hasVertexMap = nif->getBoolean();
if (hasVertexMap && numVertices)
nif->getUShorts(vertexMap, numVertices);
bool hasVertexWeights = true;
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
hasVertexWeights = nif->getBoolean();
if (hasVertexWeights && numVertices && bonesPerVertex)
nif->getFloats(weights, numVertices * bonesPerVertex);
std::vector<unsigned short> stripLengths;
if (numStrips)
nif->getUShorts(stripLengths, numStrips);
bool hasFaces = true;
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,0))
hasFaces = nif->getBoolean();
if (hasFaces)
{
if (numStrips)
{
strips.resize(numStrips);
for (unsigned short i = 0; i < numStrips; i++)
nif->getUShorts(strips[i], stripLengths[i]);
}
else if (numTriangles)
nif->getUShorts(triangles, numTriangles * 3);
}
bool hasBoneIndices = nif->getChar() != 0;
if (hasBoneIndices && numVertices && bonesPerVertex)
nif->getChars(boneIndices, numVertices * bonesPerVertex);
if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3)
{
nif->getChar(); // LOD level
nif->getBoolean(); // Global VB
}
}
void NiMorphData::read(NIFStream *nif) void NiMorphData::read(NIFStream *nif)
{ {
int morphCount = nif->getInt(); int morphCount = nif->getInt();
@ -355,7 +421,7 @@ void NiMorphData::read(NIFStream *nif)
for(int i = 0;i < morphCount;i++) for(int i = 0;i < morphCount;i++)
{ {
mMorphs[i].mKeyFrames = std::make_shared<FloatKeyMap>(); mMorphs[i].mKeyFrames = std::make_shared<FloatKeyMap>();
mMorphs[i].mKeyFrames->read(nif, true); mMorphs[i].mKeyFrames->read(nif, true, /*morph*/true);
nif->getVector3s(mMorphs[i].mVertices, vertCount); nif->getVector3s(mMorphs[i].mVertices, vertCount);
} }
} }
@ -392,4 +458,17 @@ void NiPalette::read(NIFStream *nif)
colors[i] = nif->getUInt() | alphaMask; colors[i] = nif->getUInt() | alphaMask;
} }
void NiStringPalette::read(NIFStream *nif)
{
palette = nif->getString();
if (nif->getUInt() != palette.size())
nif->file->warn("Failed size check in NiStringPalette");
}
void NiBoolData::read(NIFStream *nif)
{
mKeyList = std::make_shared<ByteKeyMap>();
mKeyList->read(nif);
}
} // Namespace } // Namespace

@ -174,6 +174,7 @@ class NiSkinInstance : public Record
{ {
public: public:
NiSkinDataPtr data; NiSkinDataPtr data;
NiSkinPartitionPtr partitions;
NodePtr root; NodePtr root;
NodeList bones; NodeList bones;
@ -200,6 +201,25 @@ public:
Transformation trafo; Transformation trafo;
std::vector<BoneInfo> bones; std::vector<BoneInfo> bones;
NiSkinPartitionPtr partitions;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
};
struct NiSkinPartition : public Record
{
struct Partition
{
std::vector<unsigned short> bones;
std::vector<unsigned short> vertexMap;
std::vector<float> weights;
std::vector<std::vector<unsigned short>> strips;
std::vector<unsigned short> triangles;
std::vector<char> boneIndices;
void read(NIFStream *nif);
};
std::vector<Partition> data;
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
@ -240,5 +260,17 @@ public:
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
struct NiStringPalette : public Record
{
std::string palette;
void read(NIFStream *nif) override;
};
struct NiBoolData : public Record
{
ByteKeyMapPtr mKeyList;
void read(NIFStream *nif) override;
};
} // Namespace } // Namespace
#endif #endif

@ -80,5 +80,11 @@ void NiFloatsExtraData::read(NIFStream *nif)
nif->getFloats(data, num); nif->getFloats(data, num);
} }
void BSBound::read(NIFStream *nif)
{
Extra::read(nif);
center = nif->getVector3();
halfExtents = nif->getVector3();
}
} }

@ -109,5 +109,12 @@ struct NiFloatsExtraData : public Extra
void read(NIFStream *nif) override; void read(NIFStream *nif) override;
}; };
struct BSBound : public Extra
{
osg::Vec3f center, halfExtents;
void read(NIFStream *nif) override;
};
} // Namespace } // Namespace
#endif #endif

@ -1,6 +1,7 @@
#include "niffile.hpp" #include "niffile.hpp"
#include "effect.hpp" #include "effect.hpp"
#include <array>
#include <map> #include <map>
#include <sstream> #include <sstream>
@ -113,6 +114,19 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
factory["NiColorExtraData"] = {&construct <NiVectorExtraData> , RC_NiColorExtraData }; factory["NiColorExtraData"] = {&construct <NiVectorExtraData> , RC_NiColorExtraData };
factory["NiFloatExtraData"] = {&construct <NiFloatExtraData> , RC_NiFloatExtraData }; factory["NiFloatExtraData"] = {&construct <NiFloatExtraData> , RC_NiFloatExtraData };
factory["NiFloatsExtraData"] = {&construct <NiFloatsExtraData> , RC_NiFloatsExtraData }; factory["NiFloatsExtraData"] = {&construct <NiFloatsExtraData> , RC_NiFloatsExtraData };
factory["NiStringPalette"] = {&construct <NiStringPalette> , RC_NiStringPalette };
factory["NiBoolData"] = {&construct <NiBoolData> , RC_NiBoolData };
factory["NiSkinPartition"] = {&construct <NiSkinPartition> , RC_NiSkinPartition };
factory["BSXFlags"] = {&construct <NiIntegerExtraData> , RC_BSXFlags };
factory["BSBound"] = {&construct <BSBound> , RC_BSBound };
factory["NiTransformData"] = {&construct <NiKeyframeData> , RC_NiKeyframeData };
factory["BSFadeNode"] = {&construct <NiNode> , RC_NiNode };
factory["bhkBlendController"] = {&construct <bhkBlendController> , RC_bhkBlendController };
factory["NiFloatInterpolator"] = {&construct <NiFloatInterpolator> , RC_NiFloatInterpolator };
factory["NiBoolInterpolator"] = {&construct <NiBoolInterpolator> , RC_NiBoolInterpolator };
factory["NiPoint3Interpolator"] = {&construct <NiPoint3Interpolator> , RC_NiPoint3Interpolator };
factory["NiTransformController"] = {&construct <NiKeyframeController> , RC_NiKeyframeController };
factory["NiTransformInterpolator"] = {&construct <NiTransformInterpolator> , RC_NiTransformInterpolator };
return factory; return factory;
} }
@ -137,15 +151,45 @@ void NIFFile::parse(Files::IStreamPtr stream)
// Check the header string // Check the header string
std::string head = nif.getVersionString(); std::string head = nif.getVersionString();
if(head.compare(0, 22, "NetImmerse File Format") != 0) static const std::array<std::string, 2> verStrings =
{
"NetImmerse File Format",
"Gamebryo File Format"
};
bool supported = false;
for (const std::string& verString : verStrings)
{
supported = (head.compare(0, verString.size(), verString) == 0);
if (supported)
break;
}
if (!supported)
fail("Invalid NIF header: " + head); fail("Invalid NIF header: " + head);
supported = false;
// Get BCD version // Get BCD version
ver = nif.getUInt(); ver = nif.getUInt();
// 4.0.0.0 is an older, practically identical version of the format. // 4.0.0.0 is an older, practically identical version of the format.
// It's not used by Morrowind assets but Morrowind supports it. // It's not used by Morrowind assets but Morrowind supports it.
if(ver != NIFStream::generateVersion(4,0,0,0) && ver != VER_MW) static const std::array<uint32_t, 2> supportedVers =
{
NIFStream::generateVersion(4,0,0,0),
VER_MW
};
for (uint32_t supportedVer : supportedVers)
{
supported = (ver == supportedVer);
if (supported)
break;
}
if (!supported)
{
if (sLoadUnsupportedFiles)
warn("Unsupported NIF version: " + printVersion(ver) + ". Proceed with caution!");
else
fail("Unsupported NIF version: " + printVersion(ver)); fail("Unsupported NIF version: " + printVersion(ver));
}
// NIF data endianness // NIF data endianness
if (ver >= NIFStream::generateVersion(20,0,0,4)) if (ver >= NIFStream::generateVersion(20,0,0,4))
@ -245,6 +289,9 @@ void NIFFile::parse(Files::IStreamPtr stream)
else else
fail("Unknown record type " + rec); fail("Unknown record type " + rec);
if (!supported)
Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")";
assert(r != nullptr); assert(r != nullptr);
assert(r->recType != RC_MISSING); assert(r->recType != RC_MISSING);
r->recName = rec; r->recName = rec;
@ -286,4 +333,11 @@ bool NIFFile::getUseSkinning() const
return mUseSkinning; return mUseSkinning;
} }
bool NIFFile::sLoadUnsupportedFiles = false;
void NIFFile::setLoadUnsupportedFiles(bool load)
{
sLoadUnsupportedFiles = load;
}
} }

@ -62,6 +62,8 @@ class NIFFile final : public File
bool mUseSkinning = false; bool mUseSkinning = false;
static bool sLoadUnsupportedFiles;
/// Parse the file /// Parse the file
void parse(Files::IStreamPtr stream); void parse(Files::IStreamPtr stream);
@ -149,6 +151,8 @@ public:
/// Get the Bethesda version of the NIF format used /// Get the Bethesda version of the NIF format used
unsigned int getBethVersion() const override { return bethVer; } unsigned int getBethVersion() const override { return bethVer; }
static void setLoadUnsupportedFiles(bool load);
}; };
using NIFFilePtr = std::shared_ptr<const Nif::NIFFile>; using NIFFilePtr = std::shared_ptr<const Nif::NIFFile>;

@ -52,16 +52,27 @@ struct KeyMapT {
MapType mKeys; MapType mKeys;
//Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html) //Read in a KeyGroup (see http://niftools.sourceforge.net/doc/nif/NiKeyframeData.html)
void read(NIFStream *nif, bool force=false) void read(NIFStream *nif, bool force = false, bool morph = false)
{ {
assert(nif); assert(nif);
mInterpolationType = InterpolationType_Unknown; mInterpolationType = InterpolationType_Unknown;
if (morph && nif->getVersion() >= NIFStream::generateVersion(10,1,0,106))
nif->getString(); // Frame name
size_t count = nif->getUInt(); size_t count = nif->getUInt();
if(count == 0 && !force) if (count == 0 && !force && !morph)
return; return;
if (morph && nif->getVersion() > NIFStream::generateVersion(10,1,0,0))
{
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,104) &&
nif->getVersion() <= NIFStream::generateVersion(20,1,0,2) && nif->getBethVersion() < 10)
nif->getFloat(); // Legacy weight
return;
}
mKeys.clear(); mKeys.clear();
mInterpolationType = nif->getUInt(); mInterpolationType = nif->getUInt();

@ -16,6 +16,117 @@ namespace Nif
struct NiNode; struct NiNode;
struct NiBoundingVolume
{
enum Type
{
SPHERE_BV = 0,
BOX_BV = 1,
CAPSULE_BV = 2,
LOZENGE_BV = 3,
UNION_BV = 4,
HALFSPACE_BV = 5
};
struct NiSphereBV
{
osg::Vec3f center;
float radius{0.f};
};
struct NiBoxBV
{
osg::Vec3f center;
Matrix3 axis;
osg::Vec3f extents;
};
struct NiCapsuleBV
{
osg::Vec3f center, axis;
float extent{0.f}, radius{0.f};
};
struct NiLozengeBV
{
float radius{0.f}, extent0{0.f}, extent1{0.f};
osg::Vec3f center, axis0, axis1;
};
struct NiHalfSpaceBV
{
osg::Vec3f center, normal;
};
unsigned int type;
NiSphereBV sphere;
NiBoxBV box;
NiCapsuleBV capsule;
NiLozengeBV lozenge;
std::vector<NiBoundingVolume> children;
NiHalfSpaceBV plane;
void read(NIFStream* nif)
{
type = nif->getUInt();
switch (type)
{
case SPHERE_BV:
{
sphere.center = nif->getVector3();
sphere.radius = nif->getFloat();
break;
}
case BOX_BV:
{
box.center = nif->getVector3();
box.axis = nif->getMatrix3();
box.extents = nif->getVector3();
break;
}
case CAPSULE_BV:
{
capsule.center = nif->getVector3();
capsule.axis = nif->getVector3();
capsule.extent = nif->getFloat();
capsule.radius = nif->getFloat();
break;
}
case LOZENGE_BV:
{
lozenge.radius = nif->getFloat();
lozenge.extent0 = nif->getFloat();
lozenge.extent1 = nif->getFloat();
lozenge.center = nif->getVector3();
lozenge.axis0 = nif->getVector3();
lozenge.axis1 = nif->getVector3();
break;
}
case UNION_BV:
{
unsigned int numChildren = nif->getUInt();
if (numChildren == 0)
break;
children.resize(numChildren);
for (NiBoundingVolume& child : children)
child.read(nif);
break;
}
case HALFSPACE_BV:
{
plane.center = nif->getVector3();
plane.normal = nif->getVector3();
break;
}
default:
{
std::stringstream error;
error << "Unhandled NiBoundingVolume type: " << type;
nif->file->fail(error.str());
}
}
}
};
/** A Node is an object that's part of the main NIF tree. It has /** A Node is an object that's part of the main NIF tree. It has
parent node (unless it's the root), and transformation (location parent node (unless it's the root), and transformation (location
and rotation) relative to it's parent. and rotation) relative to it's parent.
@ -31,9 +142,7 @@ public:
// Bounding box info // Bounding box info
bool hasBounds{false}; bool hasBounds{false};
osg::Vec3f boundPos; NiBoundingVolume bounds;
Matrix3 boundRot;
osg::Vec3f boundXYZ; // Box size
void read(NIFStream *nif) override void read(NIFStream *nif) override
{ {
@ -49,12 +158,7 @@ public:
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
hasBounds = nif->getBoolean(); hasBounds = nif->getBoolean();
if (hasBounds) if (hasBounds)
{ bounds.read(nif);
nif->getInt(); // always 1
boundPos = nif->getVector3();
boundRot = nif->getMatrix3();
boundXYZ = nif->getVector3();
}
// Reference to the collision object in Gamebryo files. // Reference to the collision object in Gamebryo files.
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->skip(4); nif->skip(4);

@ -109,7 +109,17 @@ enum RecordType
RC_NiVectorExtraData, RC_NiVectorExtraData,
RC_NiColorExtraData, RC_NiColorExtraData,
RC_NiFloatExtraData, RC_NiFloatExtraData,
RC_NiFloatsExtraData RC_NiFloatsExtraData,
RC_NiStringPalette,
RC_NiBoolData,
RC_NiSkinPartition,
RC_BSXFlags,
RC_BSBound,
RC_bhkBlendController,
RC_NiFloatInterpolator,
RC_NiPoint3Interpolator,
RC_NiBoolInterpolator,
RC_NiTransformInterpolator,
}; };
/// Base class for all records /// Base class for all records

@ -143,6 +143,11 @@ class NiAutoNormalParticlesData;
class NiPalette; class NiPalette;
struct NiParticleModifier; struct NiParticleModifier;
struct NiLinesData; struct NiLinesData;
struct NiBoolData;
struct NiSkinPartition;
struct NiFloatInterpolator;
struct NiPoint3Interpolator;
struct NiTransformInterpolator;
using NodePtr = RecordPtrT<Node>; using NodePtr = RecordPtrT<Node>;
using ExtraPtr = RecordPtrT<Extra>; using ExtraPtr = RecordPtrT<Extra>;
@ -166,11 +171,17 @@ using NiRotatingParticlesDataPtr = RecordPtrT<NiRotatingParticlesData>;
using NiAutoNormalParticlesDataPtr = RecordPtrT<NiAutoNormalParticlesData>; using NiAutoNormalParticlesDataPtr = RecordPtrT<NiAutoNormalParticlesData>;
using NiPalettePtr = RecordPtrT<NiPalette>; using NiPalettePtr = RecordPtrT<NiPalette>;
using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>; using NiParticleModifierPtr = RecordPtrT<NiParticleModifier>;
using NiBoolDataPtr = RecordPtrT<NiBoolData>;
using NiSkinPartitionPtr = RecordPtrT<NiSkinPartition>;
using NiFloatInterpolatorPtr = RecordPtrT<NiFloatInterpolator>;
using NiPoint3InterpolatorPtr = RecordPtrT<NiPoint3Interpolator>;
using NiTransformInterpolatorPtr = RecordPtrT<NiTransformInterpolator>;
using NodeList = RecordListT<Node>; using NodeList = RecordListT<Node>;
using PropertyList = RecordListT<Property>; using PropertyList = RecordListT<Property>;
using ExtraList = RecordListT<Extra>; using ExtraList = RecordListT<Extra>;
using NiSourceTextureList = RecordListT<NiSourceTexture>; using NiSourceTextureList = RecordListT<NiSourceTexture>;
using NiFloatInterpolatorList = RecordListT<NiFloatInterpolator>;
} // Namespace } // Namespace
#endif #endif

@ -8,6 +8,7 @@
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/stringops.hpp> #include <components/misc/stringops.hpp>
#include <components/nif/node.hpp> #include <components/nif/node.hpp>
@ -24,11 +25,6 @@ osg::Matrixf getWorldTransform(const Nif::Node *node)
return node->trafo.toMatrix(); return node->trafo.toMatrix();
} }
btVector3 getbtVector(const osg::Vec3f &v)
{
return btVector3(v.x(), v.y(), v.z());
}
bool pathFileNameStartsWithX(const std::string& path) bool pathFileNameStartsWithX(const std::string& path)
{ {
const std::size_t slashpos = path.find_last_of("/\\"); const std::size_t slashpos = path.find_last_of("/\\");
@ -36,7 +32,7 @@ bool pathFileNameStartsWithX(const std::string& path)
return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X'); return letterPos < path.size() && (path[letterPos] == 'x' || path[letterPos] == 'X');
} }
void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform) void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriShapeData& data, const osg::Matrixf &transform)
{ {
mesh.preallocateVertices(static_cast<int>(data.vertices.size())); mesh.preallocateVertices(static_cast<int>(data.vertices.size()));
mesh.preallocateIndices(static_cast<int>(data.triangles.size())); mesh.preallocateIndices(static_cast<int>(data.triangles.size()));
@ -47,20 +43,20 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriShapeDa
for (std::size_t i = 0; i < triangles.size(); i += 3) for (std::size_t i = 0; i < triangles.size(); i += 3)
{ {
mesh.addTriangle( mesh.addTriangle(
getbtVector(vertices[triangles[i + 0]] * transform), Misc::Convert::toBullet(vertices[triangles[i + 0]] * transform),
getbtVector(vertices[triangles[i + 1]] * transform), Misc::Convert::toBullet(vertices[triangles[i + 1]] * transform),
getbtVector(vertices[triangles[i + 2]] * transform) Misc::Convert::toBullet(vertices[triangles[i + 2]] * transform)
); );
} }
} }
void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform) void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, const osg::Matrixf &transform)
{ {
const std::vector<osg::Vec3f> &vertices = data.vertices; const std::vector<osg::Vec3f> &vertices = data.vertices;
const std::vector<std::vector<unsigned short>> &strips = data.strips; const std::vector<std::vector<unsigned short>> &strips = data.strips;
if (vertices.empty() || strips.empty()) if (vertices.empty() || strips.empty())
return; return;
mesh.preallocateVertices(static_cast<int>(data.vertices.size())); mesh.preallocateVertices(static_cast<int>(vertices.size()));
int numTriangles = 0; int numTriangles = 0;
for (const std::vector<unsigned short>& strip : strips) for (const std::vector<unsigned short>& strip : strips)
{ {
@ -88,17 +84,17 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsD
if (i%2==0) if (i%2==0)
{ {
mesh.addTriangle( mesh.addTriangle(
getbtVector(vertices[a] * transform), Misc::Convert::toBullet(vertices[a] * transform),
getbtVector(vertices[b] * transform), Misc::Convert::toBullet(vertices[b] * transform),
getbtVector(vertices[c] * transform) Misc::Convert::toBullet(vertices[c] * transform)
); );
} }
else else
{ {
mesh.addTriangle( mesh.addTriangle(
getbtVector(vertices[a] * transform), Misc::Convert::toBullet(vertices[a] * transform),
getbtVector(vertices[c] * transform), Misc::Convert::toBullet(vertices[c] * transform),
getbtVector(vertices[b] * transform) Misc::Convert::toBullet(vertices[b] * transform)
); );
} }
} }
@ -106,17 +102,12 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsD
} }
} }
void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform) void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* nifNode, const osg::Matrixf &transform = osg::Matrixf())
{ {
if (nifNode->recType == Nif::RC_NiTriShape) if (nifNode->recType == Nif::RC_NiTriShape)
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform); fillTriangleMesh(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
else // if (nifNode->recType == Nif::RC_NiTriStrips) else if (nifNode->recType == Nif::RC_NiTriStrips)
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform); fillTriangleMesh(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform);
}
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* node)
{
fillTriangleMeshWithTransform(mesh, node, osg::Matrixf());
} }
} }
@ -141,18 +132,21 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
if ((node = dynamic_cast<Nif::Node*>(r))) if ((node = dynamic_cast<Nif::Node*>(r)))
break; break;
} }
const std::string filename = nif.getFilename();
if (!node) if (!node)
{ {
warn("Found no root nodes in NIF."); warn("Found no root nodes in NIF file " + filename);
return mShape; return mShape;
} }
if (findBoundingBox(node)) if (findBoundingBox(node, filename))
{ {
const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents);
const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate);
std::unique_ptr<btCompoundShape> compound (new btCompoundShape); std::unique_ptr<btCompoundShape> compound (new btCompoundShape);
std::unique_ptr<btBoxShape> boxShape(new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents))); std::unique_ptr<btBoxShape> boxShape(new btBoxShape(halfExtents));
btTransform transform = btTransform::getIdentity(); btTransform transform = btTransform::getIdentity();
transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate)); transform.setOrigin(origin);
compound->addChildShape(transform, boxShape.get()); compound->addChildShape(transform, boxShape.get());
boxShape.release(); boxShape.release();
@ -165,7 +159,6 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
// files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource).
// assume all nodes in the file will be animated // assume all nodes in the file will be animated
const auto filename = nif.getFilename();
const bool isAnimated = pathFileNameStartsWithX(filename); const bool isAnimated = pathFileNameStartsWithX(filename);
handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated);
@ -201,12 +194,25 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
// Find a boundingBox in the node hierarchy. // Find a boundingBox in the node hierarchy.
// Return: use bounding box for collision? // Return: use bounding box for collision?
bool BulletNifLoader::findBoundingBox(const Nif::Node* node) bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& filename)
{ {
if (node->hasBounds) if (node->hasBounds)
{ {
mShape->mCollisionBoxHalfExtents = node->boundXYZ; unsigned int type = node->bounds.type;
mShape->mCollisionBoxTranslate = node->boundPos; switch (type)
{
case Nif::NiBoundingVolume::Type::BOX_BV:
mShape->mCollisionBoxHalfExtents = node->bounds.box.extents;
mShape->mCollisionBoxTranslate = node->bounds.box.center;
break;
default:
{
std::stringstream warning;
warning << "Unsupported NiBoundingVolume type " << type << " in node " << node->recIndex;
warning << " in file " << filename;
warn(warning.str());
}
}
if (node->flags & Nif::NiNode::Flag_BBoxCollision) if (node->flags & Nif::NiNode::Flag_BBoxCollision)
{ {
@ -222,7 +228,7 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node)
{ {
if(!list[i].empty()) if(!list[i].empty())
{ {
bool found = findBoundingBox (list[i].getPtr()); bool found = findBoundingBox (list[i].getPtr(), filename);
if (found) if (found)
return true; return true;
} }
@ -383,7 +389,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
if (!mAvoidStaticMesh) if (!mAvoidStaticMesh)
mAvoidStaticMesh.reset(new btTriangleMesh(false)); mAvoidStaticMesh.reset(new btTriangleMesh(false));
fillTriangleMeshWithTransform(*mAvoidStaticMesh, nifNode, transform); fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform);
} }
else else
{ {
@ -391,7 +397,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
mStaticMesh.reset(new btTriangleMesh(false)); mStaticMesh.reset(new btTriangleMesh(false));
// Static shape, just transform all vertices into position // Static shape, just transform all vertices into position
fillTriangleMeshWithTransform(*mStaticMesh, nifNode, transform); fillTriangleMesh(*mStaticMesh, nifNode, transform);
} }
} }

@ -52,7 +52,7 @@ public:
osg::ref_ptr<Resource::BulletShape> load(const Nif::File& file); osg::ref_ptr<Resource::BulletShape> load(const Nif::File& file);
private: private:
bool findBoundingBox(const Nif::Node* node); bool findBoundingBox(const Nif::Node* node, const std::string& filename);
void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode,
bool isAnimated=false, bool autogenerated=false, bool avoid=false); bool isAnimated=false, bool autogenerated=false, bool avoid=false);

@ -92,6 +92,26 @@ KeyframeController::KeyframeController(const Nif::NiKeyframeData *data)
{ {
} }
KeyframeController::KeyframeController(const Nif::NiTransformInterpolator* interpolator)
: mRotations(interpolator->data->mRotations, interpolator->defaultRot)
, mXRotations(interpolator->data->mXRotations, 0.f)
, mYRotations(interpolator->data->mYRotations, 0.f)
, mZRotations(interpolator->data->mZRotations, 0.f)
, mTranslations(interpolator->data->mTranslations, interpolator->defaultPos)
, mScales(interpolator->data->mScales, interpolator->defaultScale)
{
}
KeyframeController::KeyframeController(const float scale, const osg::Vec3f& pos, const osg::Quat& rot)
: mRotations(Nif::QuaternionKeyMapPtr(), rot)
, mXRotations(Nif::FloatKeyMapPtr(), 0.f)
, mYRotations(Nif::FloatKeyMapPtr(), 0.f)
, mZRotations(Nif::FloatKeyMapPtr(), 0.f)
, mTranslations(Nif::Vector3KeyMapPtr(), pos)
, mScales(Nif::FloatKeyMapPtr(), scale)
{
}
osg::Quat KeyframeController::getXYZRotation(float time) const osg::Quat KeyframeController::getXYZRotation(float time) const
{ {
float xrot = 0, yrot = 0, zrot = 0; float xrot = 0, yrot = 0, zrot = 0;
@ -177,10 +197,25 @@ GeomMorpherController::GeomMorpherController(const GeomMorpherController &copy,
{ {
} }
GeomMorpherController::GeomMorpherController(const Nif::NiMorphData *data) GeomMorpherController::GeomMorpherController(const Nif::NiGeomMorpherController* ctrl)
{
if (ctrl->interpolators.length() == 0)
{ {
for (unsigned int i=0; i<data->mMorphs.size(); ++i) if (ctrl->data.empty())
mKeyFrames.emplace_back(data->mMorphs[i].mKeyFrames); return;
for (const auto& morph : ctrl->data->mMorphs)
mKeyFrames.emplace_back(morph.mKeyFrames);
}
else
{
for (size_t i = 0; i < ctrl->interpolators.length(); ++i)
{
if (!ctrl->interpolators[i].empty())
mKeyFrames.emplace_back(ctrl->interpolators[i].getPtr());
else
mKeyFrames.emplace_back();
}
}
} }
void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable) void GeomMorpherController::update(osg::NodeVisitor *nv, osg::Drawable *drawable)
@ -313,6 +348,11 @@ RollController::RollController(const Nif::NiFloatData *data)
{ {
} }
RollController::RollController(const Nif::NiFloatInterpolator* interpolator)
: mData(interpolator)
{
}
RollController::RollController(const RollController &copy, const osg::CopyOp &copyop) RollController::RollController(const RollController &copy, const osg::CopyOp &copyop)
: osg::NodeCallback(copy, copyop) : osg::NodeCallback(copy, copyop)
, Controller(copy) , Controller(copy)
@ -344,6 +384,10 @@ void RollController::operator() (osg::Node* node, osg::NodeVisitor* nv)
} }
} }
AlphaController::AlphaController()
{
}
AlphaController::AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial) AlphaController::AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial)
: mData(data->mKeyList, 1.f) : mData(data->mKeyList, 1.f)
, mBaseMaterial(baseMaterial) , mBaseMaterial(baseMaterial)
@ -351,7 +395,9 @@ AlphaController::AlphaController(const Nif::NiFloatData *data, const osg::Materi
} }
AlphaController::AlphaController() AlphaController::AlphaController(const Nif::NiFloatInterpolator* interpolator, const osg::Material* baseMaterial)
: mData(interpolator)
, mBaseMaterial(baseMaterial)
{ {
} }
@ -379,6 +425,10 @@ void AlphaController::apply(osg::StateSet *stateset, osg::NodeVisitor *nv)
} }
} }
MaterialColorController::MaterialColorController()
{
}
MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial) MaterialColorController::MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial)
: mData(data->mKeyList, osg::Vec3f(1,1,1)) : mData(data->mKeyList, osg::Vec3f(1,1,1))
, mTargetColor(color) , mTargetColor(color)
@ -386,7 +436,10 @@ MaterialColorController::MaterialColorController(const Nif::NiPosData *data, Tar
{ {
} }
MaterialColorController::MaterialColorController() MaterialColorController::MaterialColorController(const Nif::NiPoint3Interpolator* interpolator, TargetColor color, const osg::Material* baseMaterial)
: mData(interpolator)
, mTargetColor(color)
, mBaseMaterial(baseMaterial)
{ {
} }
@ -448,6 +501,8 @@ FlipController::FlipController(const Nif::NiFlipController *ctrl, const std::vec
, mDelta(ctrl->mDelta) , mDelta(ctrl->mDelta)
, mTextures(textures) , mTextures(textures)
{ {
if (!ctrl->mInterpolator.empty())
mData = ctrl->mInterpolator.getPtr();
} }
FlipController::FlipController(int texSlot, float delta, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures) FlipController::FlipController(int texSlot, float delta, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures)
@ -463,14 +518,19 @@ FlipController::FlipController(const FlipController &copy, const osg::CopyOp &co
, mTexSlot(copy.mTexSlot) , mTexSlot(copy.mTexSlot)
, mDelta(copy.mDelta) , mDelta(copy.mDelta)
, mTextures(copy.mTextures) , mTextures(copy.mTextures)
, mData(copy.mData)
{ {
} }
void FlipController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv) void FlipController::apply(osg::StateSet* stateset, osg::NodeVisitor* nv)
{ {
if (hasInput() && mDelta != 0 && !mTextures.empty()) if (hasInput() && !mTextures.empty())
{ {
int curTexture = int(getInputValue(nv) / mDelta) % mTextures.size(); int curTexture = 0;
if (mDelta != 0)
curTexture = int(getInputValue(nv) / mDelta) % mTextures.size();
else
curTexture = int(mData.interpKey(getInputValue(nv))) % mTextures.size();
stateset->setTextureAttribute(mTexSlot, mTextures[curTexture]); stateset->setTextureAttribute(mTexSlot, mTextures[curTexture]);
} }
} }

@ -10,6 +10,7 @@
#include <components/sceneutil/statesetupdater.hpp> #include <components/sceneutil/statesetupdater.hpp>
#include <set> #include <set>
#include <type_traits>
#include <osg/Texture2D> #include <osg/Texture2D>
@ -59,6 +60,31 @@ namespace NifOsg
ValueInterpolator() = default; ValueInterpolator() = default;
template<
class T,
typename = std::enable_if_t<
std::conjunction_v<
std::disjunction<
std::is_same<ValueT, float>,
std::is_same<ValueT, osg::Vec3f>
>,
std::is_same<decltype(T::defaultVal), ValueT>
>,
T
>
>
ValueInterpolator(const T* interpolator) : mDefaultVal(interpolator->defaultVal)
{
if (interpolator->data.empty())
return;
mKeys = interpolator->data->mKeyList;
if (mKeys)
{
mLastLowKey = mKeys->mKeys.end();
mLastHighKey = mKeys->mKeys.end();
}
}
ValueInterpolator(std::shared_ptr<const MapT> keys, ValueT defaultVal = ValueT()) ValueInterpolator(std::shared_ptr<const MapT> keys, ValueT defaultVal = ValueT())
: mKeys(keys) : mKeys(keys)
, mDefaultVal(defaultVal) , mDefaultVal(defaultVal)
@ -188,7 +214,7 @@ namespace NifOsg
class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller
{ {
public: public:
GeomMorpherController(const Nif::NiMorphData* data); GeomMorpherController(const Nif::NiGeomMorpherController* ctrl);
GeomMorpherController(); GeomMorpherController();
GeomMorpherController(const GeomMorpherController& copy, const osg::CopyOp& copyop); GeomMorpherController(const GeomMorpherController& copy, const osg::CopyOp& copyop);
@ -203,7 +229,14 @@ namespace NifOsg
class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller
{ {
public: public:
// This is used if there's no interpolator but there is data (Morrowind meshes).
KeyframeController(const Nif::NiKeyframeData *data); KeyframeController(const Nif::NiKeyframeData *data);
// This is used if the interpolator has data.
KeyframeController(const Nif::NiTransformInterpolator* interpolator);
// This is used if there are default values available (e.g. from a data-less interpolator).
// If there's neither keyframe data nor an interpolator a KeyframeController must not be created.
KeyframeController(const float scale, const osg::Vec3f& pos, const osg::Quat& rot);
KeyframeController(); KeyframeController();
KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop); KeyframeController(const KeyframeController& copy, const osg::CopyOp& copyop);
@ -272,6 +305,7 @@ namespace NifOsg
public: public:
RollController(const Nif::NiFloatData *data); RollController(const Nif::NiFloatData *data);
RollController(const Nif::NiFloatInterpolator* interpolator);
RollController() = default; RollController() = default;
RollController(const RollController& copy, const osg::CopyOp& copyop); RollController(const RollController& copy, const osg::CopyOp& copyop);
@ -287,6 +321,7 @@ namespace NifOsg
osg::ref_ptr<const osg::Material> mBaseMaterial; osg::ref_ptr<const osg::Material> mBaseMaterial;
public: public:
AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial); AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial);
AlphaController(const Nif::NiFloatInterpolator* interpolator, const osg::Material* baseMaterial);
AlphaController(); AlphaController();
AlphaController(const AlphaController& copy, const osg::CopyOp& copyop); AlphaController(const AlphaController& copy, const osg::CopyOp& copyop);
@ -308,6 +343,7 @@ namespace NifOsg
Emissive = 3 Emissive = 3
}; };
MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial); MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial);
MaterialColorController(const Nif::NiPoint3Interpolator* interpolator, TargetColor color, const osg::Material* baseMaterial);
MaterialColorController(); MaterialColorController();
MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop); MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop);
@ -329,6 +365,7 @@ namespace NifOsg
int mTexSlot{0}; int mTexSlot{0};
float mDelta{0.f}; float mDelta{0.f};
std::vector<osg::ref_ptr<osg::Texture2D> > mTextures; std::vector<osg::ref_ptr<osg::Texture2D> > mTextures;
FloatInterpolator mData;
public: public:
FlipController(const Nif::NiFlipController* ctrl, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures); FlipController(const Nif::NiFlipController* ctrl, const std::vector<osg::ref_ptr<osg::Texture2D> >& textures);

@ -60,6 +60,18 @@ namespace
} }
} }
bool isTypeGeometry(int type)
{
switch (type)
{
case Nif::RC_NiTriShape:
case Nif::RC_NiTriStrips:
case Nif::RC_NiLines:
return true;
}
return false;
}
// Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it.
void collectDrawableProperties(const Nif::Node* nifNode, std::vector<const Nif::Property*>& out) void collectDrawableProperties(const Nif::Node* nifNode, std::vector<const Nif::Property*>& out)
{ {
@ -269,10 +281,10 @@ namespace NifOsg
const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr()); const Nif::NiStringExtraData *strdata = static_cast<const Nif::NiStringExtraData*>(extra.getPtr());
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr()); const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
if(key->data.empty()) if (key->data.empty() && key->interpolator.empty())
continue; continue;
osg::ref_ptr<NifOsg::KeyframeController> callback(new NifOsg::KeyframeController(key->data.getPtr())); osg::ref_ptr<NifOsg::KeyframeController> callback(handleKeyframeController(key));
callback->setFunction(std::shared_ptr<NifOsg::ControllerFunction>(new NifOsg::ControllerFunction(key))); callback->setFunction(std::shared_ptr<NifOsg::ControllerFunction>(new NifOsg::ControllerFunction(key)));
if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) if (!target.mKeyframeControllers.emplace(strdata->string, callback).second)
@ -528,7 +540,19 @@ namespace NifOsg
// - finding a random child NiNode in NiBspArrayController // - finding a random child NiNode in NiBspArrayController
node->setUserValue("recIndex", nifNode->recIndex); node->setUserValue("recIndex", nifNode->recIndex);
std::vector<Nif::ExtraPtr> extraCollection;
for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next)
extraCollection.emplace_back(e);
for (size_t i = 0; i < nifNode->extralist.length(); ++i)
{
Nif::ExtraPtr e = nifNode->extralist[i];
if (!e.empty())
extraCollection.emplace_back(e);
}
for (const auto& e : extraCollection)
{ {
if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys)
{ {
@ -584,7 +608,7 @@ namespace NifOsg
applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags);
const bool isGeometry = nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines; const bool isGeometry = isTypeGeometry(nifNode->recType);
if (isGeometry && !skipMeshes) if (isGeometry && !skipMeshes)
{ {
@ -701,6 +725,24 @@ namespace NifOsg
} }
} }
static osg::ref_ptr<KeyframeController> handleKeyframeController(const Nif::NiKeyframeController* keyctrl)
{
osg::ref_ptr<NifOsg::KeyframeController> ctrl;
if (!keyctrl->interpolator.empty())
{
const Nif::NiTransformInterpolator* interp = keyctrl->interpolator.getPtr();
if (!interp->data.empty())
ctrl = new NifOsg::KeyframeController(interp);
else
ctrl = new NifOsg::KeyframeController(interp->defaultScale, interp->defaultPos, interp->defaultRot);
}
else if (!keyctrl->data.empty())
{
ctrl = new NifOsg::KeyframeController(keyctrl->data.getPtr());
}
return ctrl;
}
void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated) void handleNodeControllers(const Nif::Node* nifNode, osg::Node* node, int animflags, bool& isAnimated)
{ {
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
@ -710,9 +752,9 @@ namespace NifOsg
if (ctrl->recType == Nif::RC_NiKeyframeController) if (ctrl->recType == Nif::RC_NiKeyframeController)
{ {
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr()); const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
if (key->data.empty()) if (key->data.empty() && key->interpolator.empty())
continue; continue;
osg::ref_ptr<KeyframeController> callback(new KeyframeController(key->data.getPtr())); osg::ref_ptr<KeyframeController> callback(handleKeyframeController(key));
setupController(key, callback, animflags); setupController(key, callback, animflags);
node->addUpdateCallback(callback); node->addUpdateCallback(callback);
isAnimated = true; isAnimated = true;
@ -739,9 +781,13 @@ namespace NifOsg
else if (ctrl->recType == Nif::RC_NiRollController) else if (ctrl->recType == Nif::RC_NiRollController)
{ {
const Nif::NiRollController *rollctrl = static_cast<const Nif::NiRollController*>(ctrl.getPtr()); const Nif::NiRollController *rollctrl = static_cast<const Nif::NiRollController*>(ctrl.getPtr());
if (rollctrl->data.empty()) if (rollctrl->data.empty() && rollctrl->interpolator.empty())
continue; continue;
osg::ref_ptr<RollController> callback(new RollController(rollctrl->data.getPtr())); osg::ref_ptr<RollController> callback;
if (!rollctrl->interpolator.empty())
callback = new RollController(rollctrl->interpolator.getPtr());
else // if (!rollctrl->data.empty())
callback = new RollController(rollctrl->data.getPtr());
setupController(rollctrl, callback, animflags); setupController(rollctrl, callback, animflags);
node->addUpdateCallback(callback); node->addUpdateCallback(callback);
isAnimated = true; isAnimated = true;
@ -767,19 +813,27 @@ namespace NifOsg
if (ctrl->recType == Nif::RC_NiAlphaController) if (ctrl->recType == Nif::RC_NiAlphaController)
{ {
const Nif::NiAlphaController* alphactrl = static_cast<const Nif::NiAlphaController*>(ctrl.getPtr()); const Nif::NiAlphaController* alphactrl = static_cast<const Nif::NiAlphaController*>(ctrl.getPtr());
if (alphactrl->data.empty()) if (alphactrl->data.empty() && alphactrl->interpolator.empty())
continue; continue;
osg::ref_ptr<AlphaController> osgctrl(new AlphaController(alphactrl->data.getPtr(), baseMaterial)); osg::ref_ptr<AlphaController> osgctrl;
if (!alphactrl->interpolator.empty())
osgctrl = new AlphaController(alphactrl->interpolator.getPtr(), baseMaterial);
else // if (!alphactrl->data.empty())
osgctrl = new AlphaController(alphactrl->data.getPtr(), baseMaterial);
setupController(alphactrl, osgctrl, animflags); setupController(alphactrl, osgctrl, animflags);
composite->addController(osgctrl); composite->addController(osgctrl);
} }
else if (ctrl->recType == Nif::RC_NiMaterialColorController) else if (ctrl->recType == Nif::RC_NiMaterialColorController)
{ {
const Nif::NiMaterialColorController* matctrl = static_cast<const Nif::NiMaterialColorController*>(ctrl.getPtr()); const Nif::NiMaterialColorController* matctrl = static_cast<const Nif::NiMaterialColorController*>(ctrl.getPtr());
if (matctrl->data.empty()) if (matctrl->data.empty() && matctrl->interpolator.empty())
continue; continue;
osg::ref_ptr<MaterialColorController> osgctrl;
auto targetColor = static_cast<MaterialColorController::TargetColor>(matctrl->targetColor); auto targetColor = static_cast<MaterialColorController::TargetColor>(matctrl->targetColor);
osg::ref_ptr<MaterialColorController> osgctrl(new MaterialColorController(matctrl->data.getPtr(), targetColor, baseMaterial)); if (!matctrl->interpolator.empty())
osgctrl = new MaterialColorController(matctrl->interpolator.getPtr(), targetColor, baseMaterial);
else // if (!matctrl->data.empty())
osgctrl = new MaterialColorController(matctrl->data.getPtr(), targetColor, baseMaterial);
setupController(matctrl, osgctrl, animflags); setupController(matctrl, osgctrl, animflags);
composite->addController(osgctrl); composite->addController(osgctrl);
} }
@ -1175,7 +1229,7 @@ namespace NifOsg
void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags) void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector<unsigned int>& boundTextures, int animflags)
{ {
assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Drawable> drawable; osg::ref_ptr<osg::Drawable> drawable;
osg::ref_ptr<osg::Geometry> geom (new osg::Geometry); osg::ref_ptr<osg::Geometry> geom (new osg::Geometry);
handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags);
@ -1190,7 +1244,7 @@ namespace NifOsg
continue; continue;
drawable = handleMorphGeometry(nimorphctrl, geom, parentNode, composite, boundTextures, animflags); drawable = handleMorphGeometry(nimorphctrl, geom, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(nimorphctrl->data.getPtr()); osg::ref_ptr<GeomMorpherController> morphctrl = new GeomMorpherController(nimorphctrl);
setupController(ctrl.getPtr(), morphctrl, animflags); setupController(ctrl.getPtr(), morphctrl, animflags);
drawable->setUpdateCallback(morphctrl); drawable->setUpdateCallback(morphctrl);
break; break;
@ -1220,7 +1274,7 @@ namespace NifOsg
void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite,
const std::vector<unsigned int>& boundTextures, int animflags) const std::vector<unsigned int>& boundTextures, int animflags)
{ {
assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips || nifNode->recType == Nif::RC_NiLines); assert(isTypeGeometry(nifNode->recType));
osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry); osg::ref_ptr<osg::Geometry> geometry (new osg::Geometry);
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry); osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);

@ -220,6 +220,7 @@ namespace Resource
, mClampLighting(true) , mClampLighting(true)
, mAutoUseNormalMaps(false) , mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false) , mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mInstanceCache(new MultiObjectCache) , mInstanceCache(new MultiObjectCache)
, mSharedStateManager(new SharedStateManager) , mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager) , mImageManager(imageManager)
@ -284,6 +285,11 @@ namespace Resource
mSpecularMapPattern = pattern; mSpecularMapPattern = pattern;
} }
void SceneManager::setApplyLightingToEnvMaps(bool apply)
{
mApplyLightingToEnvMaps = apply;
}
SceneManager::~SceneManager() SceneManager::~SceneManager()
{ {
// this has to be defined in the .cpp file as we can't delete incomplete types // this has to be defined in the .cpp file as we can't delete incomplete types
@ -770,6 +776,7 @@ namespace Resource
shaderVisitor->setNormalHeightMapPattern(mNormalHeightMapPattern); shaderVisitor->setNormalHeightMapPattern(mNormalHeightMapPattern);
shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps); shaderVisitor->setAutoUseSpecularMaps(mAutoUseSpecularMaps);
shaderVisitor->setSpecularMapPattern(mSpecularMapPattern); shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
return shaderVisitor; return shaderVisitor;
} }

@ -73,6 +73,8 @@ namespace Resource
void setSpecularMapPattern(const std::string& pattern); void setSpecularMapPattern(const std::string& pattern);
void setApplyLightingToEnvMaps(bool apply);
void setShaderPath(const std::string& path); void setShaderPath(const std::string& path);
/// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded /// Check if a given scene is loaded and if so, update its usage timestamp to prevent it from being unloaded
@ -156,6 +158,7 @@ namespace Resource
std::string mNormalHeightMapPattern; std::string mNormalHeightMapPattern;
bool mAutoUseSpecularMaps; bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern; std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps;
osg::ref_ptr<MultiObjectCache> mInstanceCache; osg::ref_ptr<MultiObjectCache> mInstanceCache;

@ -25,6 +25,7 @@
#include <osg/Depth> #include <osg/Depth>
#include <sstream> #include <sstream>
#include "shadowsbin.hpp"
namespace { namespace {
@ -273,10 +274,20 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
cv->pushCullingSet(); cv->pushCullingSet();
} }
#endif #endif
// bin has to go inside camera cull or the rendertexture stage will override it
static osg::ref_ptr<osg::StateSet> ss;
if (!ss)
{
ShadowsBinAdder adder("ShadowsBin");
ss = new osg::StateSet;
ss->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "ShadowsBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);
}
cv->pushStateSet(ss);
if (_vdsm->getShadowedScene()) if (_vdsm->getShadowedScene())
{ {
_vdsm->getShadowedScene()->osg::Group::traverse(*nv); _vdsm->getShadowedScene()->osg::Group::traverse(*nv);
} }
cv->popStateSet();
#if 1 #if 1
if (!_polytope.empty()) if (!_polytope.empty())
{ {
@ -555,6 +566,7 @@ MWShadowTechnique::ShadowData::ShadowData(MWShadowTechnique::ViewDependentData*
_camera = new osg::Camera; _camera = new osg::Camera;
_camera->setName("ShadowCamera"); _camera->setName("ShadowCamera");
_camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT); _camera->setReferenceFrame(osg::Camera::ABSOLUTE_RF_INHERIT_VIEWPOINT);
_camera->setImplicitBufferAttachmentMask(0, 0);
//_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f)); //_camera->setClearColor(osg::Vec4(1.0f,1.0f,1.0f,1.0f));
_camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f)); _camera->setClearColor(osg::Vec4(0.0f,0.0f,0.0f,0.0f));
@ -874,15 +886,6 @@ void SceneUtil::MWShadowTechnique::setupCastingShader(Shader::ShaderManager & sh
_castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX)); _castingProgram->addShader(shaderManager.getShader("shadowcasting_vertex.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::VERTEX));
_castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT)); _castingProgram->addShader(shaderManager.getShader("shadowcasting_fragment.glsl", Shader::ShaderManager::DefineMap(), osg::Shader::FRAGMENT));
_shadowMapAlphaTestDisableUniform = shaderManager.getShadowMapAlphaTestDisableUniform();
_shadowMapAlphaTestDisableUniform->setName("alphaTestShadows");
_shadowMapAlphaTestDisableUniform->setType(osg::Uniform::BOOL);
_shadowMapAlphaTestDisableUniform->set(false);
shaderManager.getShadowMapAlphaTestEnableUniform()->setName("alphaTestShadows");
shaderManager.getShadowMapAlphaTestEnableUniform()->setType(osg::Uniform::BOOL);
shaderManager.getShadowMapAlphaTestEnableUniform()->set(true);
} }
MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/) MWShadowTechnique::ViewDependentData* MWShadowTechnique::createViewDependentData(osgUtil::CullVisitor* /*cv*/)
@ -1583,17 +1586,14 @@ void MWShadowTechnique::createShaders()
_shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setAttributeAndModes(_castingProgram, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
// The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied // The casting program uses a sampler, so to avoid undefined behaviour, we must bind a dummy texture in case no other is supplied
_shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON); _shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON);
_shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false)); _shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true));
_shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform); _shadowCastingStateSet->addUniform(new osg::Uniform("alphaTestShadows", false));
osg::ref_ptr<osg::Depth> depth = new osg::Depth; osg::ref_ptr<osg::Depth> depth = new osg::Depth;
depth->setWriteMask(true); depth->setWriteMask(true);
_shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); _shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
_shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON); _shadowCastingStateSet->setMode(GL_DEPTH_CLAMP, osg::StateAttribute::ON);
_shadowCastingStateSet->setRenderBinDetails(osg::StateSet::OPAQUE_BIN, "RenderBin", osg::StateSet::OVERRIDE_PROTECTED_RENDERBIN_DETAILS);
// TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader // TODO: compare performance when alpha testing is handled here versus using a discard in the fragment shader
// TODO: compare performance when we set a bunch of GL state to the default here with OVERRIDE set so that there are fewer pointless state switches
} }
osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight) osg::Polytope MWShadowTechnique::computeLightViewFrustumPolytope(Frustum& frustum, LightData& positionedLight)

@ -288,7 +288,6 @@ namespace SceneUtil {
osg::ref_ptr<DebugHUD> _debugHud; osg::ref_ptr<DebugHUD> _debugHud;
osg::ref_ptr<osg::Program> _castingProgram; osg::ref_ptr<osg::Program> _castingProgram;
osg::ref_ptr<osg::Uniform> _shadowMapAlphaTestDisableUniform;
}; };
} }

@ -0,0 +1,166 @@
#include "shadowsbin.hpp"
#include <unordered_set>
#include <osg/StateSet>
#include <osg/Material>
#include <osgUtil/StateGraph>
using namespace osgUtil;
namespace
{
template <typename T>
inline void accumulateState(T& currentValue, T newValue, bool& isOverride, unsigned int overrideFlags)
{
if (isOverride && !(overrideFlags & osg::StateAttribute::PROTECTED)) return;
if (overrideFlags & osg::StateAttribute::OVERRIDE)
isOverride = true;
currentValue = newValue;
}
inline void accumulateModeState(const osg::StateSet* ss, bool& currentValue, bool& isOverride, int mode)
{
const osg::StateSet::ModeList& l = ss->getModeList();
osg::StateSet::ModeList::const_iterator mf = l.find(mode);
if (mf == l.end())
return;
int flags = mf->second;
bool newValue = flags & osg::StateAttribute::ON;
accumulateState(currentValue, newValue, isOverride, ss->getMode(mode));
}
inline bool materialNeedShadows(osg::Material* m)
{
// I'm pretty sure this needs to check the colour mode - vertex colours might override this value.
return m->getDiffuse(osg::Material::FRONT).a() > 0.5;
}
}
namespace SceneUtil
{
ShadowsBin::ShadowsBin()
{
mNoTestStateSet = new osg::StateSet;
mNoTestStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false));
mNoTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", false));
mShaderAlphaTestStateSet = new osg::StateSet;
mShaderAlphaTestStateSet->addUniform(new osg::Uniform("alphaTestShadows", true));
mShaderAlphaTestStateSet->setMode(GL_BLEND, osg::StateAttribute::OFF | osg::StateAttribute::OVERRIDE);
}
StateGraph* ShadowsBin::cullStateGraph(StateGraph* sg, StateGraph* root, std::unordered_set<StateGraph*>& uninterestingCache)
{
std::vector<StateGraph*> return_path;
State state;
StateGraph* sg_new = sg;
do
{
if (uninterestingCache.find(sg_new) != uninterestingCache.end())
break;
return_path.push_back(sg_new);
sg_new = sg_new->_parent;
} while (sg_new && sg_new != root);
for(auto itr=return_path.rbegin(); itr!=return_path.rend(); ++itr)
{
const osg::StateSet* ss = (*itr)->getStateSet();
if (!ss)
continue;
accumulateModeState(ss, state.mAlphaBlend, state.mAlphaBlendOverride, GL_BLEND);
accumulateModeState(ss, state.mAlphaTest, state.mAlphaTestOverride, GL_ALPHA_TEST);
const osg::StateSet::AttributeList& attributes = ss->getAttributeList();
osg::StateSet::AttributeList::const_iterator found = attributes.find(std::make_pair(osg::StateAttribute::MATERIAL, 0));
if (found != attributes.end())
{
const osg::StateSet::RefAttributePair& rap = found->second;
accumulateState(state.mMaterial, static_cast<osg::Material*>(rap.first.get()), state.mMaterialOverride, rap.second);
if (state.mMaterial && !materialNeedShadows(state.mMaterial))
state.mMaterial = nullptr;
}
// osg::FrontFace specifies triangle winding, not front-face culling. We can't safely reparent anything under it.
found = attributes.find(std::make_pair(osg::StateAttribute::FRONTFACE, 0));
if (found != attributes.end())
state.mImportantState = true;
if ((*itr) != sg && !state.interesting())
uninterestingCache.insert(*itr);
}
if (!state.needShadows())
return nullptr;
if (!state.needTexture() && !state.mImportantState)
{
for (RenderLeaf* leaf : sg->_leaves)
{
leaf->_parent = root;
root->_leaves.push_back(leaf);
}
return nullptr;
}
if (state.mAlphaBlend)
{
sg_new = sg->find_or_insert(mShaderAlphaTestStateSet);
for (RenderLeaf* leaf : sg->_leaves)
{
leaf->_parent = sg_new;
sg_new->_leaves.push_back(leaf);
}
return sg_new;
}
return sg;
}
bool ShadowsBin::State::needShadows() const
{
if (!mMaterial)
return true;
return materialNeedShadows(mMaterial);
}
void ShadowsBin::sortImplementation()
{
// The cull visitor contains a stategraph.
// When a stateset is pushed, it's added/found as a child of the current stategraph node, then that node becomes the new current stategraph node.
// When a drawable is added, the current stategraph node is added to the current renderbin (if it's not there already) and the drawable is added as a renderleaf to the stategraph
// This means our list only contains stategraph nodes with directly-attached renderleaves, but they might have parents with more state set that needs to be considered.
if (!_stateGraphList.size())
return;
StateGraph* root = _stateGraphList[0];
while (root->_parent)
{
root = root->_parent;
const osg::StateSet* ss = root->getStateSet();
if (ss->getMode(GL_NORMALIZE) & osg::StateAttribute::ON // that is root stategraph of renderingmanager cpp
|| ss->getAttribute(osg::StateAttribute::VIEWPORT)) // fallback to rendertargets sg just in case
break;
if (!root->_parent)
return;
}
StateGraph* noTestRoot = root->find_or_insert(mNoTestStateSet.get());
// root is now a stategraph with useDiffuseMapForShadowAlpha disabled but minimal other state
noTestRoot->_leaves.reserve(_stateGraphList.size());
StateGraphList newList;
std::unordered_set<StateGraph*> uninterestingCache;
for (StateGraph* graph : _stateGraphList)
{
// Render leaves which shouldn't use the diffuse map for shadow alpha but do cast shadows become children of root, so graph is now empty. Don't add to newList.
// Graphs containing just render leaves which don't cast shadows are discarded. Don't add to newList.
// Graphs containing other leaves need to be in newList.
StateGraph* graphToAdd = cullStateGraph(graph, noTestRoot, uninterestingCache);
if (graphToAdd)
newList.push_back(graphToAdd);
}
if (!noTestRoot->_leaves.empty())
newList.push_back(noTestRoot);
_stateGraphList = newList;
}
}

@ -0,0 +1,76 @@
#ifndef OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H
#define OPENMW_COMPONENTS_SCENEUTIL_SHADOWBIN_H
#include <unordered_set>
#include <osgUtil/RenderBin>
namespace osg
{
class Material;
}
namespace SceneUtil
{
/// renderbin which culls redundant state for shadow map rendering
class ShadowsBin : public osgUtil::RenderBin
{
private:
osg::ref_ptr<osg::StateSet> mNoTestStateSet;
osg::ref_ptr<osg::StateSet> mShaderAlphaTestStateSet;
public:
META_Object(SceneUtil, ShadowsBin)
ShadowsBin();
ShadowsBin(const ShadowsBin& rhs, const osg::CopyOp& copyop)
: osgUtil::RenderBin(rhs, copyop)
, mNoTestStateSet(rhs.mNoTestStateSet)
, mShaderAlphaTestStateSet(rhs.mShaderAlphaTestStateSet)
{}
void sortImplementation() override;
struct State
{
State()
: mAlphaBlend(false)
, mAlphaBlendOverride(false)
, mAlphaTest(false)
, mAlphaTestOverride(false)
, mMaterial(nullptr)
, mMaterialOverride(false)
, mImportantState(false)
{}
bool mAlphaBlend;
bool mAlphaBlendOverride;
bool mAlphaTest;
bool mAlphaTestOverride;
osg::Material* mMaterial;
bool mMaterialOverride;
bool mImportantState;
bool needTexture() const { return mAlphaBlend || mAlphaTest; }
bool needShadows() const;
// A state is interesting if there's anything about it that might affect whether we can optimise child state
bool interesting() const
{
return !needShadows() || needTexture() || mAlphaBlendOverride || mAlphaTestOverride || mMaterialOverride || mImportantState;
}
};
osgUtil::StateGraph* cullStateGraph(osgUtil::StateGraph* sg, osgUtil::StateGraph* root, std::unordered_set<osgUtil::StateGraph*>& uninteresting);
static void addPrototype(const std::string& name)
{
osg::ref_ptr<osgUtil::RenderBin> bin (new ShadowsBin);
osgUtil::RenderBin::addRenderBinPrototype(name, bin);
}
};
class ShadowsBinAdder
{
public:
ShadowsBinAdder(const std::string& name){ ShadowsBin::addPrototype(name); }
};
}
#endif

@ -384,14 +384,4 @@ namespace Shader
program.second->releaseGLObjects(state); program.second->releaseGLObjects(state);
} }
const osg::ref_ptr<osg::Uniform> ShaderManager::getShadowMapAlphaTestEnableUniform()
{
return mShadowMapAlphaTestEnableUniform;
}
const osg::ref_ptr<osg::Uniform> ShaderManager::getShadowMapAlphaTestDisableUniform()
{
return mShadowMapAlphaTestDisableUniform;
}
} }

@ -43,9 +43,6 @@ namespace Shader
void releaseGLObjects(osg::State* state); void releaseGLObjects(osg::State* state);
const osg::ref_ptr<osg::Uniform> getShadowMapAlphaTestEnableUniform();
const osg::ref_ptr<osg::Uniform> getShadowMapAlphaTestDisableUniform();
private: private:
std::string mPath; std::string mPath;
@ -63,9 +60,6 @@ namespace Shader
ProgramMap mPrograms; ProgramMap mPrograms;
std::mutex mMutex; std::mutex mMutex;
const osg::ref_ptr<osg::Uniform> mShadowMapAlphaTestEnableUniform = new osg::Uniform();
const osg::ref_ptr<osg::Uniform> mShadowMapAlphaTestDisableUniform = new osg::Uniform();
}; };
bool parseFors(std::string& source, const std::string& templateName); bool parseFors(std::string& source, const std::string& templateName);

@ -1,7 +1,5 @@
#include "shadervisitor.hpp" #include "shadervisitor.hpp"
#include <osg/AlphaFunc>
#include <osg/BlendFunc>
#include <osg/Geometry> #include <osg/Geometry>
#include <osg/Material> #include <osg/Material>
#include <osg/Texture> #include <osg/Texture>
@ -14,7 +12,6 @@
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
#include <components/sceneutil/riggeometry.hpp> #include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp> #include <components/sceneutil/morphgeometry.hpp>
#include <components/settings/settings.hpp>
#include "shadermanager.hpp" #include "shadermanager.hpp"
@ -25,7 +22,6 @@ namespace Shader
: mShaderRequired(false) : mShaderRequired(false)
, mColorMode(0) , mColorMode(0)
, mMaterialOverridden(false) , mMaterialOverridden(false)
, mBlendFuncOverridden(false)
, mNormalHeight(false) , mNormalHeight(false)
, mTexStageRequiringTangents(-1) , mTexStageRequiringTangents(-1)
, mNode(nullptr) , mNode(nullptr)
@ -43,6 +39,7 @@ namespace Shader
, mAllowedToModifyStateSets(true) , mAllowedToModifyStateSets(true)
, mAutoUseNormalMaps(false) , mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false) , mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mShaderManager(shaderManager) , mShaderManager(shaderManager)
, mImageManager(imageManager) , mImageManager(imageManager)
, mDefaultVsTemplate(defaultVsTemplate) , mDefaultVsTemplate(defaultVsTemplate)
@ -144,10 +141,8 @@ namespace Shader
// Bump maps are off by default as well // Bump maps are off by default as well
writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON); writableStateSet->setTextureMode(unit, GL_TEXTURE_2D, osg::StateAttribute::ON);
} }
else if (texName == "envMap") else if (texName == "envMap" && mApplyLightingToEnvMaps)
{ {
static const bool preLightEnv = Settings::Manager::getBool("apply lighting to environment maps", "Shaders");
if (preLightEnv)
mRequirements.back().mShaderRequired = true; mRequirements.back().mShaderRequired = true;
} }
} }
@ -233,14 +228,11 @@ namespace Shader
if (!writableStateSet) if (!writableStateSet)
writableStateSet = getWritableStateSet(node); writableStateSet = getWritableStateSet(node);
// We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out. // We probably shouldn't construct a new version of this each time as Uniforms use pointer comparison for early-out.
// Also it should probably belong to the shader manager // Also it should probably belong to the shader manager or be applied by the shadows bin
writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true)); writableStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true));
} }
} }
bool alphaSettingsChanged = false;
bool alphaTestShadows = false;
const osg::StateSet::AttributeList& attributes = stateset->getAttributeList(); const osg::StateSet::AttributeList& attributes = stateset->getAttributeList();
for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it) for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it)
{ {
@ -284,28 +276,8 @@ namespace Shader
mRequirements.back().mColorMode = colorMode; mRequirements.back().mColorMode = colorMode;
} }
} }
else if (it->first.first == osg::StateAttribute::BLENDFUNC)
{
if (!mRequirements.back().mBlendFuncOverridden || it->second.second & osg::StateAttribute::PROTECTED)
{
if (it->second.second & osg::StateAttribute::OVERRIDE)
mRequirements.back().mBlendFuncOverridden = true;
const osg::BlendFunc* blend = static_cast<const osg::BlendFunc*>(it->second.first.get());
if (blend->getSource() == osg::BlendFunc::SRC_ALPHA || blend->getSource() == osg::BlendFunc::SRC_COLOR)
alphaTestShadows = true;
alphaSettingsChanged = true;
}
}
// Eventually, move alpha testing to discard in shader adn remove deprecated state here // Eventually, move alpha testing to discard in shader adn remove deprecated state here
} }
// we don't need to check for glEnable/glDisable of blending as we always set it at the same time
if (alphaSettingsChanged)
{
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
writableStateSet->addUniform(alphaTestShadows ? mShaderManager.getShadowMapAlphaTestEnableUniform() : mShaderManager.getShadowMapAlphaTestDisableUniform());
}
} }
void ShaderVisitor::pushRequirements(osg::Node& node) void ShaderVisitor::pushRequirements(osg::Node& node)
@ -477,4 +449,9 @@ namespace Shader
mSpecularMapPattern = pattern; mSpecularMapPattern = pattern;
} }
void ShaderVisitor::setApplyLightingToEnvMaps(bool apply)
{
mApplyLightingToEnvMaps = apply;
}
} }

@ -38,6 +38,8 @@ namespace Shader
void setSpecularMapPattern(const std::string& pattern); void setSpecularMapPattern(const std::string& pattern);
void setApplyLightingToEnvMaps(bool apply);
void apply(osg::Node& node) override; void apply(osg::Node& node) override;
void apply(osg::Drawable& drawable) override; void apply(osg::Drawable& drawable) override;
@ -59,6 +61,8 @@ namespace Shader
bool mAutoUseSpecularMaps; bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern; std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps;
ShaderManager& mShaderManager; ShaderManager& mShaderManager;
Resource::ImageManager& mImageManager; Resource::ImageManager& mImageManager;
@ -75,7 +79,6 @@ namespace Shader
int mColorMode; int mColorMode;
bool mMaterialOverridden; bool mMaterialOverridden;
bool mBlendFuncOverridden;
bool mNormalHeight; // true if normal map has height info in alpha channel bool mNormalHeight; // true if normal map has height info in alpha channel

@ -58,3 +58,4 @@ The ranges included with each setting are the physically possible ranges, not re
windows windows
navigator navigator
physics physics
models

@ -0,0 +1,31 @@
Models Settings
###############
load unsupported nif files
--------------------------
:Type: boolean
:Range: True/False
:Default: False
Allow the engine to load arbitrary NIF files as long as they appear to be valid.
OpenMW has limited and **experimental** support for NIF files
that Morrowind itself cannot load, which normally goes unused.
If enabled, this setting allows the NIF loader to make use of that functionality.
.. warning::
You must keep in mind that since the mentioned support is experimental,
loading unsupported NIF files may fail, and the degree of this failure may vary.
In milder cases, OpenMW will reject the file anyway because
it lacks a definition for a certain record type that the file may use.
In more severe cases OpenMW's incomplete understanding of a record type
can lead to memory corruption, freezes or even crashes.
**Do not enable** this if you're not so sure that you know what you're doing.
To help debug possible issues OpenMW will log its progress in loading
every file that uses an unsupported NIF version.

@ -933,13 +933,20 @@ object shadows = false
enable indoor shadows = true enable indoor shadows = true
[Physics] [Physics]
# how much background thread to use in the physics solver. 0 to disable (i.e solver run in the main thread) # Set the number of background threads used for physics.
# If no background threads are used, physics calculations are processed in the main thread
# and the settings below have no effect.
async num threads = 0 async num threads = 0
# maintain a cache of lineofsight request in the bacground physics thread # Set the number of frames an inactive line-of-sight request will be kept
# determines for how much frames an inactive lineofsight request should be kept updated in the cache # refreshed in the background physics thread cache.
# -1 to disable (i.e the LOS will be calculated only on request) # If this is set to -1, line-of-sight requests are never cached.
lineofsight keep inactive cache = 0 lineofsight keep inactive cache = 0
# wether to defer aabb update till before collision detection # Defer bounding boxes update until collision detection.
defer aabb update = true defer aabb update = true
[Models]
# Attempt to load any valid NIF file regardless of its version and track the progress.
# Loading arbitrary meshes is not advised and may cause instability.
load unsupported nif files = false

Loading…
Cancel
Save