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
libavcodec-dev, libavformat-dev, libavutil-dev, libswresample-dev, libswscale-dev,
# 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
libbullet-dev, libopenscenegraph-3.4-dev, libmygui-dev,
# tes3mp stuff
@ -71,11 +71,11 @@ before_script:
- ./CI/before_script.${TRAVIS_OS_NAME}.sh
script:
- cd ./build
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ${ANALYZE} make -j3; fi
- if [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${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 [ "${COVERITY_SCAN_BRANCH}" != 1 ] && [ "${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
- ${ANALYZE} make -j3; fi
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then make package; fi
- if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ../CI/check_package.osx.sh; fi
- if [ "${TRAVIS_OS_NAME}" = "linux" ] && [ "${BUILD_TESTS_ONLY}" ]; then ./openmw_test_suite; fi
- if [ "${TRAVIS_OS_NAME}" = "linux" ]; then cd .. && ./CI/check_tabs.sh; fi
- cd "${TRAVIS_BUILD_DIR}"
- ccache -s
#deploy:

@ -10,10 +10,12 @@
Bug #2976 [reopened]: Issues combining settings from the command line and both config files
Bug #3676: NiParticleColorModifier isn't applied properly
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 #3929: Leveled list merchant containers respawn on barter
Bug #4021: Attributes and skills are not stored as floats
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 #4631: Setting MSAA level too high doesn't fall back to highest supported level
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 qmake >/dev/null 2>&1 || brew install qt
brew link --overwrite lz4 # overwrite system lz4; use brew
brew reinstall lz4
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-ef2462c.zip -o ~/openmw-deps.zip
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-f8918dd.zip -o ~/openmw-deps.zip
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null

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

@ -132,6 +132,7 @@ int main(int argc, char **argv)
if(!parseOptions (argc, argv, files))
return 1;
Nif::NIFFile::setLoadUnsupportedFiles(true);
// std::cout << "Reading Files" << std::endl;
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)
{
CreatureStats &creatureStats = ptr.getClass().getCreatureStats(ptr);
const MagicEffects &effects = creatureStats.getMagicEffects();
bool godmode = ptr == getPlayer() && MWBase::Environment::get().getWorld()->getGodModeState();
applyCureEffects(ptr);
bool wasDead = creatureStats.isDead();
if (duration > 0)
@ -1810,6 +1852,9 @@ namespace MWMechanics
void Actors::predictAndAvoidCollisions()
{
if (!MWBase::Environment::get().getMechanicsManager()->isAIActive())
return;
const float minGap = 10.f;
const float maxDistForPartialAvoiding = 200.f;
const float maxDistForStrictAvoiding = 100.f;

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

@ -217,40 +217,6 @@ namespace MWMechanics
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:
return false;
}

@ -12,6 +12,7 @@ namespace MWMechanics
struct EffectKey;
/// 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?
bool effectTick(CreatureStats& creatureStats, const MWWorld::Ptr& actor, const EffectKey& effectKey, float magnitude);
}

@ -2,6 +2,8 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "components/misc/convert.hpp"
#include "ptrholder.hpp"
namespace MWPhysics
@ -20,7 +22,7 @@ namespace MWPhysics
collisionObject = col1Wrap->m_collisionObject;
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
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;
}

@ -7,6 +7,8 @@
#include "../mwworld/ptr.hpp"
#include "physicssystem.hpp"
class btCollisionObject;
struct btCollisionObjectWrapper;
@ -23,7 +25,7 @@ namespace MWPhysics
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
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();
}
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;
ObjectMap::const_iterator found = mObjects.find(ptr);
auto found = mObjects.find(ptr);
if (found != mObjects.end())
me = found->second->getCollisionObject();
else
return std::vector<MWWorld::Ptr>();
return {};
ContactTestResultCallback resultCallback (me);
resultCallback.m_collisionFilterGroup = collisionGroup;
@ -444,6 +444,14 @@ namespace MWPhysics
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)
{
ActorMap::iterator found = mActors.find(ptr);

@ -57,6 +57,13 @@ namespace MWPhysics
class Actor;
class PhysicsTaskScheduler;
struct ContactPoint
{
MWWorld::Ptr mObject;
osg::Vec3f mPoint;
osg::Vec3f mNormal;
};
struct LOSRequest
{
LOSRequest(const std::weak_ptr<Actor>& a1, const std::weak_ptr<Actor>& a2);
@ -145,6 +152,7 @@ namespace MWPhysics
void debugDraw();
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);
std::pair<MWWorld::Ptr, osg::Vec3f> getHitContact(const MWWorld::ConstPtr& actor,

@ -16,7 +16,6 @@
#include <components/resource/scenemanager.hpp>
#include <components/resource/keyframemanager.hpp>
#include <components/resource/resourcesystem.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/resourcehelpers.hpp>
@ -37,8 +36,6 @@
#include <components/settings/settings.hpp>
#include <components/shader/shadermanager.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
@ -493,9 +490,8 @@ namespace MWRender
class TransparencyUpdater : public SceneUtil::StateSetUpdater
{
public:
TransparencyUpdater(const float alpha, osg::ref_ptr<osg::Uniform> shadowUniform)
TransparencyUpdater(const float alpha)
: mAlpha(alpha)
, mShadowUniform(shadowUniform)
{
}
@ -509,9 +505,6 @@ namespace MWRender
{
osg::BlendFunc* blendfunc (new osg::BlendFunc);
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->setRenderBinMode(osg::StateSet::OVERRIDE_RENDERBIN_DETAILS);
@ -533,7 +526,6 @@ namespace MWRender
private:
float mAlpha;
osg::ref_ptr<osg::Uniform> mShadowUniform;
};
struct Animation::AnimSource
@ -1773,7 +1765,7 @@ namespace MWRender
{
if (mTransparencyUpdater == nullptr)
{
mTransparencyUpdater = new TransparencyUpdater(alpha, mResourceSystem->getSceneManager()->getShaderManager().getShadowMapAlphaTestEnableUniform());
mTransparencyUpdater = new TransparencyUpdater(alpha);
mObjectRoot->addCullCallback(mTransparencyUpdater);
}
else

@ -216,6 +216,7 @@ namespace MWRender
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()->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;
sceneRoot->setLightingMask(Mask_Lighting);
@ -361,6 +362,7 @@ namespace MWRender
mViewer->getCamera()->setCullMask(~(Mask_UpdateVisitor|Mask_SimpleWater));
NifOsg::Loader::setHiddenNodeMask(Mask_UpdateVisitor);
NifOsg::Loader::setIntersectionDisabledNodeMask(Mask_Effect);
Nif::NIFFile::setLoadUnsupportedFiles(Settings::Manager::getBool("load unsupported nif files", "Models"));
mNearClip = Settings::Manager::getFloat("near clip", "Camera");
mViewDistance = Settings::Manager::getFloat("viewing distance", "Camera");

@ -1771,19 +1771,26 @@ namespace MWWorld
float minRot = door.getCellRef().getPosition().rot[2];
float maxRot = minRot + osg::DegreesToRadians(90.f);
float diff = duration * osg::DegreesToRadians(90.f);
float targetRot = std::min(std::max(minRot, oldRot + diff * (state == MWWorld::DoorState::Opening ? 1 : -1)), maxRot);
float diff = duration * osg::DegreesToRadians(90.f) * (state == MWWorld::DoorState::Opening ? 1 : -1);
float targetRot = std::min(std::max(minRot, oldRot + diff), maxRot);
rotateObject(door, objPos.rot[0], objPos.rot[1], targetRot, MWBase::RotationFlag_none);
bool reached = (targetRot == maxRot && state != MWWorld::DoorState::Idle) || targetRot == minRot;
/// \todo should use convexSweepTest here
bool collisionWithActor = false;
std::vector<MWWorld::Ptr> collisions = mPhysics->getCollisions(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor);
for (MWWorld::Ptr& ptr : collisions)
for (auto& [ptr, point, normal] : mPhysics->getCollisionsPoints(door, MWPhysics::CollisionType_Door, MWPhysics::CollisionType_Actor))
{
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;
// 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)
{
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
@ -422,11 +423,13 @@ namespace
{
mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3);
mNode.boundPos = osg::Vec3f(-1, -2, -3);
mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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, getRoot(0)).WillOnce(Return(&mNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
@ -444,12 +447,14 @@ namespace
{
mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3);
mNode.boundPos = osg::Vec3f(-1, -2, -3);
mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
@ -467,16 +472,19 @@ namespace
{
mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3);
mNode.boundPos = osg::Vec3f(-1, -2, -3);
mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNiNode.hasBounds = true;
mNiNode.boundXYZ = osg::Vec3f(4, 5, 6);
mNiNode.boundPos = osg::Vec3f(-4, -5, -6);
mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
Resource::BulletShape expected;
@ -494,20 +502,24 @@ namespace
{
mNode.hasBounds = true;
mNode.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode.boundXYZ = osg::Vec3f(1, 2, 3);
mNode.boundPos = osg::Vec3f(-1, -2, -3);
mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNode2.hasBounds = true;
mNode2.boundXYZ = osg::Vec3f(4, 5, 6);
mNode2.boundPos = osg::Vec3f(-4, -5, -6);
mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6);
mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6);
mNiNode.hasBounds = true;
mNiNode.boundXYZ = osg::Vec3f(7, 8, 9);
mNiNode.boundPos = osg::Vec3f(-7, -8, -9);
mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
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)
{
mNode.hasBounds = true;
mNode.boundXYZ = osg::Vec3f(1, 2, 3);
mNode.boundPos = osg::Vec3f(-1, -2, -3);
mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode.bounds.box.extents = osg::Vec3f(1, 2, 3);
mNode.bounds.box.center = osg::Vec3f(-1, -2, -3);
mNode2.hasBounds = true;
mNode2.flags |= Nif::NiNode::Flag_BBoxCollision;
mNode2.boundXYZ = osg::Vec3f(4, 5, 6);
mNode2.boundPos = osg::Vec3f(-4, -5, -6);
mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6);
mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6);
mNiNode.hasBounds = true;
mNiNode.boundXYZ = osg::Vec3f(7, 8, 9);
mNiNode.boundPos = osg::Vec3f(-7, -8, -9);
mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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)}));
EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1));
EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode));
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
const auto result = mLoader.load(mNifFile);
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)
{
mNode.hasBounds = true;
mNode.boundXYZ = osg::Vec3f(1, 2, 3);
mNode.boundPos = osg::Vec3f(-1, -2, -3);
mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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, 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)
{
mNiTriShape.hasBounds = true;
mNiTriShape.boundXYZ = osg::Vec3f(1, 2, 3);
mNiTriShape.boundPos = osg::Vec3f(-1, -2, -3);
mNiTriShape.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV;
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, getRoot(0)).WillOnce(Return(&mNiTriShape));

@ -52,7 +52,7 @@ add_component_dir (shader
add_component_dir (sceneutil
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
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

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

@ -92,6 +92,8 @@ namespace Nif
void NiMaterialColorController::read(NIFStream *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.
// 00: Ambient
// 01: Diffuse
@ -101,12 +103,14 @@ namespace Nif
targetColor = nif->getUShort() & 3;
else
targetColor = (flags >> 4) & 3;
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
data.read(nif);
}
void NiMaterialColorController::post(NIFFile *nif)
{
Controller::post(nif);
interpolator.post(nif);
data.post(nif);
}
@ -161,25 +165,33 @@ namespace Nif
void NiKeyframeController::read(NIFStream *nif)
{
Controller::read(nif);
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
data.read(nif);
else
interpolator.read(nif);
}
void NiKeyframeController::post(NIFFile *nif)
{
Controller::post(nif);
data.post(nif);
interpolator.post(nif);
}
void NiFloatInterpController::read(NIFStream *nif)
{
Controller::read(nif);
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
data.read(nif);
else
interpolator.read(nif);
}
void NiFloatInterpController::post(NIFFile *nif)
{
Controller::post(nif);
data.post(nif);
interpolator.post(nif);
}
void NiGeomMorpherController::read(NIFStream *nif)
@ -189,13 +201,34 @@ namespace Nif
/*bool updateNormals = !!*/nif->getUShort();
data.read(nif);
if (nif->getVersion() >= NIFFile::NIFVersion::VER_MW)
{
/*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)
{
Controller::post(nif);
data.post(nif);
interpolators.post(nif);
}
void NiVisController::read(NIFStream *nif)
@ -213,6 +246,8 @@ namespace Nif
void NiFlipController::read(NIFStream *nif)
{
Controller::read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,2,0,0))
mInterpolator.read(nif);
mTexSlot = nif->getUInt();
if (nif->getVersion() <= NIFStream::generateVersion(10,1,0,103))
{
@ -225,7 +260,69 @@ namespace Nif
void NiFlipController::post(NIFFile *nif)
{
Controller::post(nif);
mInterpolator.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
{
public:
NiPoint3InterpolatorPtr interpolator;
NiPosDataPtr data;
unsigned int targetColor;
@ -138,6 +139,7 @@ class NiKeyframeController : public Controller
{
public:
NiKeyframeDataPtr data;
NiTransformInterpolatorPtr interpolator;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
@ -146,6 +148,7 @@ public:
struct NiFloatInterpController : public Controller
{
NiFloatDataPtr data;
NiFloatInterpolatorPtr interpolator;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
@ -158,6 +161,7 @@ class NiGeomMorpherController : public Controller
{
public:
NiMorphDataPtr data;
NiFloatInterpolatorList interpolators;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
@ -175,6 +179,7 @@ public:
class NiFlipController : public Controller
{
public:
NiFloatInterpolatorPtr mInterpolator;
int mTexSlot; // NiTexturingProperty::TextureType
float mDelta; // Time between two flips. delta = (start_time - stop_time) / num_sources
NiSourceTextureList mSources;
@ -183,5 +188,47 @@ public:
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
#endif

@ -6,6 +6,8 @@ namespace Nif
void NiSkinInstance::read(NIFStream *nif)
{
data.read(nif);
if (nif->getVersion() >= NIFStream::generateVersion(10,1,0,101))
partitions.read(nif);
root.read(nif);
bones.read(nif);
}
@ -13,6 +15,7 @@ void NiSkinInstance::read(NIFStream *nif)
void NiSkinInstance::post(NIFFile *nif)
{
data.post(nif);
partitions.post(nif);
root.post(nif);
bones.post(nif);
@ -320,7 +323,7 @@ void NiSkinData::read(NIFStream *nif)
int boneNum = nif->getInt();
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
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)
{
int morphCount = nif->getInt();
@ -355,7 +421,7 @@ void NiMorphData::read(NIFStream *nif)
for(int i = 0;i < morphCount;i++)
{
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);
}
}
@ -392,4 +458,17 @@ void NiPalette::read(NIFStream *nif)
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

@ -174,6 +174,7 @@ class NiSkinInstance : public Record
{
public:
NiSkinDataPtr data;
NiSkinPartitionPtr partitions;
NodePtr root;
NodeList bones;
@ -200,6 +201,25 @@ public:
Transformation trafo;
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;
};
@ -240,5 +260,17 @@ public:
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
#endif

@ -80,5 +80,11 @@ void NiFloatsExtraData::read(NIFStream *nif)
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;
};
struct BSBound : public Extra
{
osg::Vec3f center, halfExtents;
void read(NIFStream *nif) override;
};
} // Namespace
#endif

@ -1,6 +1,7 @@
#include "niffile.hpp"
#include "effect.hpp"
#include <array>
#include <map>
#include <sstream>
@ -113,6 +114,19 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
factory["NiColorExtraData"] = {&construct <NiVectorExtraData> , RC_NiColorExtraData };
factory["NiFloatExtraData"] = {&construct <NiFloatExtraData> , RC_NiFloatExtraData };
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;
}
@ -137,15 +151,45 @@ void NIFFile::parse(Files::IStreamPtr stream)
// Check the header string
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);
supported = false;
// Get BCD version
ver = nif.getUInt();
// 4.0.0.0 is an older, practically identical version of the format.
// 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));
}
// NIF data endianness
if (ver >= NIFStream::generateVersion(20,0,0,4))
@ -245,6 +289,9 @@ void NIFFile::parse(Files::IStreamPtr stream)
else
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->recType != RC_MISSING);
r->recName = rec;
@ -286,4 +333,11 @@ bool NIFFile::getUseSkinning() const
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;
static bool sLoadUnsupportedFiles;
/// Parse the file
void parse(Files::IStreamPtr stream);
@ -149,6 +151,8 @@ public:
/// Get the Bethesda version of the NIF format used
unsigned int getBethVersion() const override { return bethVer; }
static void setLoadUnsupportedFiles(bool load);
};
using NIFFilePtr = std::shared_ptr<const Nif::NIFFile>;

@ -52,16 +52,27 @@ struct KeyMapT {
MapType mKeys;
//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);
mInterpolationType = InterpolationType_Unknown;
if (morph && nif->getVersion() >= NIFStream::generateVersion(10,1,0,106))
nif->getString(); // Frame name
size_t count = nif->getUInt();
if(count == 0 && !force)
if (count == 0 && !force && !morph)
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();
mInterpolationType = nif->getUInt();

@ -16,6 +16,117 @@ namespace Nif
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
parent node (unless it's the root), and transformation (location
and rotation) relative to it's parent.
@ -31,9 +142,7 @@ public:
// Bounding box info
bool hasBounds{false};
osg::Vec3f boundPos;
Matrix3 boundRot;
osg::Vec3f boundXYZ; // Box size
NiBoundingVolume bounds;
void read(NIFStream *nif) override
{
@ -49,12 +158,7 @@ public:
if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0))
hasBounds = nif->getBoolean();
if (hasBounds)
{
nif->getInt(); // always 1
boundPos = nif->getVector3();
boundRot = nif->getMatrix3();
boundXYZ = nif->getVector3();
}
bounds.read(nif);
// Reference to the collision object in Gamebryo files.
if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0))
nif->skip(4);

@ -109,7 +109,17 @@ enum RecordType
RC_NiVectorExtraData,
RC_NiColorExtraData,
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

@ -143,6 +143,11 @@ class NiAutoNormalParticlesData;
class NiPalette;
struct NiParticleModifier;
struct NiLinesData;
struct NiBoolData;
struct NiSkinPartition;
struct NiFloatInterpolator;
struct NiPoint3Interpolator;
struct NiTransformInterpolator;
using NodePtr = RecordPtrT<Node>;
using ExtraPtr = RecordPtrT<Extra>;
@ -166,11 +171,17 @@ using NiRotatingParticlesDataPtr = RecordPtrT<NiRotatingParticlesData>;
using NiAutoNormalParticlesDataPtr = RecordPtrT<NiAutoNormalParticlesData>;
using NiPalettePtr = RecordPtrT<NiPalette>;
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 PropertyList = RecordListT<Property>;
using ExtraList = RecordListT<Extra>;
using NiSourceTextureList = RecordListT<NiSourceTexture>;
using NiFloatInterpolatorList = RecordListT<NiFloatInterpolator>;
} // Namespace
#endif

@ -8,6 +8,7 @@
#include <components/debug/debuglog.hpp>
#include <components/misc/convert.hpp>
#include <components/misc/stringops.hpp>
#include <components/nif/node.hpp>
@ -24,11 +25,6 @@ osg::Matrixf getWorldTransform(const Nif::Node *node)
return node->trafo.toMatrix();
}
btVector3 getbtVector(const osg::Vec3f &v)
{
return btVector3(v.x(), v.y(), v.z());
}
bool pathFileNameStartsWithX(const std::string& path)
{
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');
}
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.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)
{
mesh.addTriangle(
getbtVector(vertices[triangles[i + 0]] * transform),
getbtVector(vertices[triangles[i + 1]] * transform),
getbtVector(vertices[triangles[i + 2]] * transform)
Misc::Convert::toBullet(vertices[triangles[i + 0]] * transform),
Misc::Convert::toBullet(vertices[triangles[i + 1]] * 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<std::vector<unsigned short>> &strips = data.strips;
if (vertices.empty() || strips.empty())
return;
mesh.preallocateVertices(static_cast<int>(data.vertices.size()));
mesh.preallocateVertices(static_cast<int>(vertices.size()));
int numTriangles = 0;
for (const std::vector<unsigned short>& strip : strips)
{
@ -88,17 +84,17 @@ void fillTriangleMeshWithTransform(btTriangleMesh& mesh, const Nif::NiTriStripsD
if (i%2==0)
{
mesh.addTriangle(
getbtVector(vertices[a] * transform),
getbtVector(vertices[b] * transform),
getbtVector(vertices[c] * transform)
Misc::Convert::toBullet(vertices[a] * transform),
Misc::Convert::toBullet(vertices[b] * transform),
Misc::Convert::toBullet(vertices[c] * transform)
);
}
else
{
mesh.addTriangle(
getbtVector(vertices[a] * transform),
getbtVector(vertices[c] * transform),
getbtVector(vertices[b] * transform)
Misc::Convert::toBullet(vertices[a] * transform),
Misc::Convert::toBullet(vertices[c] * 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)
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
else // if (nifNode->recType == Nif::RC_NiTriStrips)
fillTriangleMeshWithTransform(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform);
}
void fillTriangleMesh(btTriangleMesh& mesh, const Nif::Node* node)
{
fillTriangleMeshWithTransform(mesh, node, osg::Matrixf());
fillTriangleMesh(mesh, static_cast<const Nif::NiTriShape*>(nifNode)->data.get(), transform);
else if (nifNode->recType == Nif::RC_NiTriStrips)
fillTriangleMesh(mesh, static_cast<const Nif::NiTriStrips*>(nifNode)->data.get(), transform);
}
}
@ -141,18 +132,21 @@ osg::ref_ptr<Resource::BulletShape> BulletNifLoader::load(const Nif::File& nif)
if ((node = dynamic_cast<Nif::Node*>(r)))
break;
}
const std::string filename = nif.getFilename();
if (!node)
{
warn("Found no root nodes in NIF.");
warn("Found no root nodes in NIF file " + filename);
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<btBoxShape> boxShape(new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents)));
std::unique_ptr<btBoxShape> boxShape(new btBoxShape(halfExtents));
btTransform transform = btTransform::getIdentity();
transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate));
transform.setOrigin(origin);
compound->addChildShape(transform, boxShape.get());
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).
// assume all nodes in the file will be animated
const auto filename = nif.getFilename();
const bool isAnimated = pathFileNameStartsWithX(filename);
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.
// 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)
{
mShape->mCollisionBoxHalfExtents = node->boundXYZ;
mShape->mCollisionBoxTranslate = node->boundPos;
unsigned int type = node->bounds.type;
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)
{
@ -222,7 +228,7 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node)
{
if(!list[i].empty())
{
bool found = findBoundingBox (list[i].getPtr());
bool found = findBoundingBox (list[i].getPtr(), filename);
if (found)
return true;
}
@ -383,7 +389,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
if (!mAvoidStaticMesh)
mAvoidStaticMesh.reset(new btTriangleMesh(false));
fillTriangleMeshWithTransform(*mAvoidStaticMesh, nifNode, transform);
fillTriangleMesh(*mAvoidStaticMesh, nifNode, transform);
}
else
{
@ -391,7 +397,7 @@ void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, cons
mStaticMesh.reset(new btTriangleMesh(false));
// 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);
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,
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
{
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)
mKeyFrames.emplace_back(data->mMorphs[i].mKeyFrames);
if (ctrl->data.empty())
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)
@ -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)
: osg::NodeCallback(copy, copyop)
, 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)
: mData(data->mKeyList, 1.f)
, 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)
: mData(data->mKeyList, osg::Vec3f(1,1,1))
, 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)
, 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)
@ -463,14 +518,19 @@ FlipController::FlipController(const FlipController &copy, const osg::CopyOp &co
, mTexSlot(copy.mTexSlot)
, mDelta(copy.mDelta)
, mTextures(copy.mTextures)
, mData(copy.mData)
{
}
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]);
}
}

@ -10,6 +10,7 @@
#include <components/sceneutil/statesetupdater.hpp>
#include <set>
#include <type_traits>
#include <osg/Texture2D>
@ -59,6 +60,31 @@ namespace NifOsg
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())
: mKeys(keys)
, mDefaultVal(defaultVal)
@ -188,7 +214,7 @@ namespace NifOsg
class GeomMorpherController : public osg::Drawable::UpdateCallback, public SceneUtil::Controller
{
public:
GeomMorpherController(const Nif::NiMorphData* data);
GeomMorpherController(const Nif::NiGeomMorpherController* ctrl);
GeomMorpherController();
GeomMorpherController(const GeomMorpherController& copy, const osg::CopyOp& copyop);
@ -203,7 +229,14 @@ namespace NifOsg
class KeyframeController : public osg::NodeCallback, public SceneUtil::Controller
{
public:
// This is used if there's no interpolator but there is data (Morrowind meshes).
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(const KeyframeController& copy, const osg::CopyOp& copyop);
@ -272,6 +305,7 @@ namespace NifOsg
public:
RollController(const Nif::NiFloatData *data);
RollController(const Nif::NiFloatInterpolator* interpolator);
RollController() = default;
RollController(const RollController& copy, const osg::CopyOp& copyop);
@ -287,6 +321,7 @@ namespace NifOsg
osg::ref_ptr<const osg::Material> mBaseMaterial;
public:
AlphaController(const Nif::NiFloatData *data, const osg::Material* baseMaterial);
AlphaController(const Nif::NiFloatInterpolator* interpolator, const osg::Material* baseMaterial);
AlphaController();
AlphaController(const AlphaController& copy, const osg::CopyOp& copyop);
@ -308,6 +343,7 @@ namespace NifOsg
Emissive = 3
};
MaterialColorController(const Nif::NiPosData *data, TargetColor color, const osg::Material* baseMaterial);
MaterialColorController(const Nif::NiPoint3Interpolator* interpolator, TargetColor color, const osg::Material* baseMaterial);
MaterialColorController();
MaterialColorController(const MaterialColorController& copy, const osg::CopyOp& copyop);
@ -329,6 +365,7 @@ namespace NifOsg
int mTexSlot{0};
float mDelta{0.f};
std::vector<osg::ref_ptr<osg::Texture2D> > mTextures;
FloatInterpolator mData;
public:
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.
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::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
if(key->data.empty())
if (key->data.empty() && key->interpolator.empty())
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)));
if (!target.mKeyframeControllers.emplace(strdata->string, callback).second)
@ -528,7 +540,19 @@ namespace NifOsg
// - finding a random child NiNode in NiBspArrayController
node->setUserValue("recIndex", nifNode->recIndex);
std::vector<Nif::ExtraPtr> extraCollection;
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)
{
@ -584,7 +608,7 @@ namespace NifOsg
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)
{
@ -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)
{
for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next)
@ -710,9 +752,9 @@ namespace NifOsg
if (ctrl->recType == Nif::RC_NiKeyframeController)
{
const Nif::NiKeyframeController *key = static_cast<const Nif::NiKeyframeController*>(ctrl.getPtr());
if (key->data.empty())
if (key->data.empty() && key->interpolator.empty())
continue;
osg::ref_ptr<KeyframeController> callback(new KeyframeController(key->data.getPtr()));
osg::ref_ptr<KeyframeController> callback(handleKeyframeController(key));
setupController(key, callback, animflags);
node->addUpdateCallback(callback);
isAnimated = true;
@ -739,9 +781,13 @@ namespace NifOsg
else if (ctrl->recType == Nif::RC_NiRollController)
{
const Nif::NiRollController *rollctrl = static_cast<const Nif::NiRollController*>(ctrl.getPtr());
if (rollctrl->data.empty())
if (rollctrl->data.empty() && rollctrl->interpolator.empty())
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);
node->addUpdateCallback(callback);
isAnimated = true;
@ -767,19 +813,27 @@ namespace NifOsg
if (ctrl->recType == Nif::RC_NiAlphaController)
{
const Nif::NiAlphaController* alphactrl = static_cast<const Nif::NiAlphaController*>(ctrl.getPtr());
if (alphactrl->data.empty())
if (alphactrl->data.empty() && alphactrl->interpolator.empty())
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);
composite->addController(osgctrl);
}
else if (ctrl->recType == Nif::RC_NiMaterialColorController)
{
const Nif::NiMaterialColorController* matctrl = static_cast<const Nif::NiMaterialColorController*>(ctrl.getPtr());
if (matctrl->data.empty())
if (matctrl->data.empty() && matctrl->interpolator.empty())
continue;
osg::ref_ptr<MaterialColorController> osgctrl;
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);
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)
{
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::Geometry> geom (new osg::Geometry);
handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags);
@ -1190,7 +1244,7 @@ namespace NifOsg
continue;
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);
drawable->setUpdateCallback(morphctrl);
break;
@ -1220,7 +1274,7 @@ namespace NifOsg
void handleSkinnedGeometry(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::Geometry> geometry (new osg::Geometry);
handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags);
osg::ref_ptr<SceneUtil::RigGeometry> rig(new SceneUtil::RigGeometry);

@ -220,6 +220,7 @@ namespace Resource
, mClampLighting(true)
, mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mInstanceCache(new MultiObjectCache)
, mSharedStateManager(new SharedStateManager)
, mImageManager(imageManager)
@ -284,6 +285,11 @@ namespace Resource
mSpecularMapPattern = pattern;
}
void SceneManager::setApplyLightingToEnvMaps(bool apply)
{
mApplyLightingToEnvMaps = apply;
}
SceneManager::~SceneManager()
{
// 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->setAutoUseSpecularMaps(mAutoUseSpecularMaps);
shaderVisitor->setSpecularMapPattern(mSpecularMapPattern);
shaderVisitor->setApplyLightingToEnvMaps(mApplyLightingToEnvMaps);
return shaderVisitor;
}

@ -73,6 +73,8 @@ namespace Resource
void setSpecularMapPattern(const std::string& pattern);
void setApplyLightingToEnvMaps(bool apply);
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
@ -156,6 +158,7 @@ namespace Resource
std::string mNormalHeightMapPattern;
bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps;
osg::ref_ptr<MultiObjectCache> mInstanceCache;

@ -25,6 +25,7 @@
#include <osg/Depth>
#include <sstream>
#include "shadowsbin.hpp"
namespace {
@ -273,10 +274,20 @@ void VDSMCameraCullCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
cv->pushCullingSet();
}
#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())
{
_vdsm->getShadowedScene()->osg::Group::traverse(*nv);
}
cv->popStateSet();
#if 1
if (!_polytope.empty())
{
@ -555,6 +566,7 @@ MWShadowTechnique::ShadowData::ShadowData(MWShadowTechnique::ViewDependentData*
_camera = new osg::Camera;
_camera->setName("ShadowCamera");
_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(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_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*/)
@ -1583,17 +1586,14 @@ void MWShadowTechnique::createShaders()
_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
_shadowCastingStateSet->setTextureAttributeAndModes(0, _fallbackBaseTexture.get(), osg::StateAttribute::ON);
_shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", false));
_shadowCastingStateSet->addUniform(_shadowMapAlphaTestDisableUniform);
_shadowCastingStateSet->addUniform(new osg::Uniform("useDiffuseMapForShadowAlpha", true));
_shadowCastingStateSet->addUniform(new osg::Uniform("alphaTestShadows", false));
osg::ref_ptr<osg::Depth> depth = new osg::Depth;
depth->setWriteMask(true);
_shadowCastingStateSet->setAttribute(depth, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
_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 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)

@ -288,7 +288,6 @@ namespace SceneUtil {
osg::ref_ptr<DebugHUD> _debugHud;
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);
}
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);
const osg::ref_ptr<osg::Uniform> getShadowMapAlphaTestEnableUniform();
const osg::ref_ptr<osg::Uniform> getShadowMapAlphaTestDisableUniform();
private:
std::string mPath;
@ -63,9 +60,6 @@ namespace Shader
ProgramMap mPrograms;
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);

@ -1,7 +1,5 @@
#include "shadervisitor.hpp"
#include <osg/AlphaFunc>
#include <osg/BlendFunc>
#include <osg/Geometry>
#include <osg/Material>
#include <osg/Texture>
@ -14,7 +12,6 @@
#include <components/vfs/manager.hpp>
#include <components/sceneutil/riggeometry.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/settings/settings.hpp>
#include "shadermanager.hpp"
@ -25,7 +22,6 @@ namespace Shader
: mShaderRequired(false)
, mColorMode(0)
, mMaterialOverridden(false)
, mBlendFuncOverridden(false)
, mNormalHeight(false)
, mTexStageRequiringTangents(-1)
, mNode(nullptr)
@ -43,6 +39,7 @@ namespace Shader
, mAllowedToModifyStateSets(true)
, mAutoUseNormalMaps(false)
, mAutoUseSpecularMaps(false)
, mApplyLightingToEnvMaps(false)
, mShaderManager(shaderManager)
, mImageManager(imageManager)
, mDefaultVsTemplate(defaultVsTemplate)
@ -144,10 +141,8 @@ namespace Shader
// Bump maps are off by default as well
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;
}
}
@ -233,14 +228,11 @@ namespace Shader
if (!writableStateSet)
writableStateSet = getWritableStateSet(node);
// 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));
}
}
bool alphaSettingsChanged = false;
bool alphaTestShadows = false;
const osg::StateSet::AttributeList& attributes = stateset->getAttributeList();
for (osg::StateSet::AttributeList::const_iterator it = attributes.begin(); it != attributes.end(); ++it)
{
@ -284,28 +276,8 @@ namespace Shader
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
}
// 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)
@ -477,4 +449,9 @@ namespace Shader
mSpecularMapPattern = pattern;
}
void ShaderVisitor::setApplyLightingToEnvMaps(bool apply)
{
mApplyLightingToEnvMaps = apply;
}
}

@ -38,6 +38,8 @@ namespace Shader
void setSpecularMapPattern(const std::string& pattern);
void setApplyLightingToEnvMaps(bool apply);
void apply(osg::Node& node) override;
void apply(osg::Drawable& drawable) override;
@ -59,6 +61,8 @@ namespace Shader
bool mAutoUseSpecularMaps;
std::string mSpecularMapPattern;
bool mApplyLightingToEnvMaps;
ShaderManager& mShaderManager;
Resource::ImageManager& mImageManager;
@ -75,7 +79,6 @@ namespace Shader
int mColorMode;
bool mMaterialOverridden;
bool mBlendFuncOverridden;
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
navigator
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
[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
# maintain a cache of lineofsight request in the bacground physics thread
# determines for how much frames an inactive lineofsight request should be kept updated in the cache
# -1 to disable (i.e the LOS will be calculated only on request)
# Set the number of frames an inactive line-of-sight request will be kept
# refreshed in the background physics thread cache.
# If this is set to -1, line-of-sight requests are never cached.
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
[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