Merge remote-tracking branch 'upstream/master' into why_are_the_christmas_lights_still_up

pull/593/head
glassmancody.info 4 years ago
commit 7370acdf54

@ -131,6 +131,7 @@ Programmers
Martin Otto (MAtahualpa)
Mateusz Kołaczek (PL_kolek)
Mateusz Malisz (malice)
Max Henzerling (SaintMercury)
megaton
Michael Hogan (Xethik)
Michael Mc Donnell

@ -57,6 +57,7 @@
Bug #5424: Creatures do not headtrack player
Bug #5425: Poison effect only appears for one frame
Bug #5427: GetDistance unknown ID error is misleading
Bug #5431: Physics performance degradation after a specific number of actors on a scene
Bug #5435: Enemies can't hurt the player when collision is off
Bug #5441: Enemies can't push a player character when in critical strike stance
Bug #5451: Magic projectiles don't disappear with the caster
@ -87,6 +88,7 @@
Bug #5656: Sneaking characters block hits while standing
Bug #5661: Region sounds don't play at the right interval
Bug #5675: OpenMW-cs. FRMR subrecords are saved with the wrong MastIdx
Bug #5680: Bull Netches incorrectly aim over the player character's head and always miss
Bug #5681: Player character can clip or pass through bridges instead of colliding against them
Bug #5687: Bound items covering the same inventory slot expiring at the same time freezes the game
Bug #5688: Water shader broken indoors with enable indoor shadows = false
@ -109,6 +111,10 @@
Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment
Bug #5877: Effects appearing with empty icon
Bug #5899: Visible modal windows and dropdowns crashing game on exit
Bug #5902: NiZBufferProperty is unable to disable the depth test
Bug #5906: Sunglare doesn't work with Mesa drivers and AMD GPUs
Bug #5912: ImprovedBound mod doesn't work
Bug #5914: BM: The Swimmer can't reach destination
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu
@ -141,6 +147,7 @@
Feature #5730: Add graphic herbalism option to the launcher and documents
Feature #5771: ori command should report where a mesh is loaded from and whether the x version is used.
Feature #5813: Instanced groundcover support
Feature #5910: Fall back to delta time when physics can't keep up
Task #5480: Drop Qt4 support
Task #5520: Improve cell name autocompleter implementation
Task #5844: Update 'toggle sneak' documentation

@ -325,6 +325,16 @@ add_qt_platform_dlls() {
QT_PLATFORMS[$CONFIG]="${QT_PLATFORMS[$CONFIG]} $@"
}
declare -A QT_STYLES
QT_STYLES["Release"]=""
QT_STYLES["Debug"]=""
QT_STYLES["RelWithDebInfo"]=""
add_qt_style_dlls() {
local CONFIG=$1
shift
QT_STYLES[$CONFIG]="${QT_STYLES[$CONFIG]} $@"
}
if [ -z $PLATFORM ]; then
PLATFORM="$(uname -m)"
fi
@ -838,9 +848,11 @@ fi
wrappedExit 1
fi
if ! [ -e "aqt-venv/${VENV_BIN_DIR}/aqt" ]; then
# check version
aqt-venv/${VENV_BIN_DIR}/pip list | grep 'aqtinstall\s*1.1.3' || [ $? -ne 0 ]
if [ $? -eq 0 ]; then
echo " Installing aqt wheel into virtualenv..."
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==0.9.2
run_cmd "aqt-venv/${VENV_BIN_DIR}/pip" install aqtinstall==1.1.3
fi
popd > /dev/null
@ -868,6 +880,7 @@ fi
fi
add_runtime_dlls $CONFIGURATION "$(pwd)/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "$(pwd)/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "$(pwd)/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
done
echo Done.
else
@ -883,6 +896,7 @@ fi
DIR=$(windowsPathAsUnix "${QT_SDK}")
add_runtime_dlls $CONFIGURATION "${DIR}/bin/Qt5"{Core,Gui,Network,OpenGL,Widgets}${DLLSUFFIX}.dll
add_qt_platform_dlls $CONFIGURATION "${DIR}/plugins/platforms/qwindows${DLLSUFFIX}.dll"
add_qt_style_dlls $CONFIGURATION "${DIR}/plugins/styles/qwindowsvistastyle${DLLSUFFIX}.dll"
done
echo Done.
fi
@ -1060,6 +1074,13 @@ fi
cp "$DLL" "${DLL_PREFIX}platforms"
done
echo
echo "- Qt Style DLLs..."
mkdir -p ${DLL_PREFIX}styles
for DLL in ${QT_STYLES[$CONFIGURATION]}; do
echo " $(basename $DLL)"
cp "$DLL" "${DLL_PREFIX}styles"
done
echo
done
#fi
@ -1067,7 +1088,13 @@ if [ -n "$ACTIVATE_MSVC" ]; then
echo -n "- Activating MSVC in the current shell... "
command -v vswhere >/dev/null 2>&1 || { echo "Error: vswhere is not on the path."; wrappedExit 1; }
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath)
# There are so many arguments now that I'm going to document them:
# * products: allow Visual Studio or standalone build tools
# * version: obvious. Awk helps make a version range by adding one.
# * property installationPath: only give the installation path.
# * latest: return only one result if several candidates exist. Prefer the last installed/updated
# * requires: make sure it's got the MSVC compiler instead of, for example, just the .NET compiler. The .x86.x64 suffix means it's for either, not that it's the x64 on x86 cross compiler as you always get both
MSVC_INSTALLATION_PATH=$(vswhere -products '*' -version "[$MSVC_REAL_VER,$(awk "BEGIN { print $MSVC_REAL_VER + 1; exit }"))" -property installationPath -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64)
if [ -z "$MSVC_INSTALLATION_PATH" ]; then
echo "vswhere was unable to find MSVC $MSVC_DISPLAY_YEAR"
wrappedExit 1

@ -115,6 +115,12 @@ option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE)
option(QT_STATIC "Link static build of QT into the binaries" FALSE)
option(OPENMW_USE_SYSTEM_BULLET "Use system provided bullet physics library" ON)
if(OPENMW_USE_SYSTEM_BULLET)
set(_bullet_static_default OFF)
else()
set(_bullet_static_default ON)
endif()
option(BULLET_STATIC "Link static build of Bullet into the binaries" ${_bullet_static_default})
option(OPENMW_USE_SYSTEM_OSG "Use system provided OpenSceneGraph libraries" ON)
if(OPENMW_USE_SYSTEM_OSG)
@ -344,7 +350,9 @@ set(BOOST_COMPONENTS system filesystem program_options iostreams)
if(WIN32)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} locale)
if(MSVC)
set(BOOST_COMPONENTS ${BOOST_COMPONENTS} zlib)
# boost-zlib is not present (nor needed) in vcpkg version of boost.
# there, it is part of boost-iostreams instead.
set(BOOST_OPTIONAL_COMPONENTS zlib)
endif(MSVC)
endif(WIN32)
@ -354,7 +362,7 @@ endif()
set(Boost_NO_BOOST_CMAKE ON)
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS})
find_package(Boost 1.6.2 REQUIRED COMPONENTS ${BOOST_COMPONENTS} OPTIONAL_COMPONENTS ${BOOST_OPTIONAL_COMPONENTS})
if(OPENMW_USE_SYSTEM_MYGUI)
find_package(MyGUI 3.2.2 REQUIRED)
endif()

@ -34,7 +34,7 @@ namespace CSMPrefs
void storeValue(const QKeySequence& sequence);
void resetState();
static const int MaxKeys = 4;
static constexpr int MaxKeys = 4;
QPushButton* mButton;

@ -443,6 +443,8 @@ namespace MWBase
virtual void applyDeferredPreviewRotationToPlayer(float dt) = 0;
virtual void disableDeferredPreviewRotation() = 0;
virtual void saveLoaded() = 0;
virtual void setupPlayer() = 0;
virtual void renderPlayer() = 0;

@ -35,20 +35,20 @@ namespace MWGui
bool onDropItem(const MWWorld::Ptr &item, int count) override;
bool onTakeItem(const MWWorld::Ptr &item, int count) override;
static const int Category_Weapon = (1<<1);
static const int Category_Apparel = (1<<2);
static const int Category_Misc = (1<<3);
static const int Category_Magic = (1<<4);
static const int Category_All = 255;
static const int Filter_OnlyIngredients = (1<<0);
static const int Filter_OnlyEnchanted = (1<<1);
static const int Filter_OnlyEnchantable = (1<<2);
static const int Filter_OnlyChargedSoulstones = (1<<3);
static const int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
static const int Filter_OnlyRepairable = (1<<5);
static const int Filter_OnlyRechargable = (1<<6);
static const int Filter_OnlyRepairTools = (1<<7);
static constexpr int Category_Weapon = (1<<1);
static constexpr int Category_Apparel = (1<<2);
static constexpr int Category_Misc = (1<<3);
static constexpr int Category_Magic = (1<<4);
static constexpr int Category_All = 255;
static constexpr int Filter_OnlyIngredients = (1<<0);
static constexpr int Filter_OnlyEnchanted = (1<<1);
static constexpr int Filter_OnlyEnchantable = (1<<2);
static constexpr int Filter_OnlyChargedSoulstones = (1<<3);
static constexpr int Filter_OnlyUsableItems = (1<<4); // Only items with a Use action
static constexpr int Filter_OnlyRepairable = (1<<5);
static constexpr int Filter_OnlyRechargable = (1<<6);
static constexpr int Filter_OnlyRepairTools = (1<<7);
private:

@ -63,6 +63,8 @@ namespace MWGui
void watchActor(const MWWorld::Ptr& ptr);
MWWorld::Ptr getWatchedActor() const { return mWatched; }
void forceUpdate() { mWatchedStatsEmpty = true; }
};
}

@ -268,7 +268,7 @@ namespace MWGui
void initialiseOverride() override;
private:
static const int sIconOffset = 24;
static constexpr int sIconOffset = 24;
void updateWidgets();

@ -189,7 +189,7 @@ namespace MWGui
{
float uiScale = Settings::Manager::getFloat("scaling factor", "GUI");
mGuiPlatform = new osgMyGUI::Platform(viewer, guiRoot, resourceSystem->getImageManager(), uiScale);
mGuiPlatform->initialise(resourcePath, logpath);
mGuiPlatform->initialise(resourcePath, (boost::filesystem::path(logpath) / "MyGUI.log").generic_string());
mGui = new MyGUI::Gui;
mGui->initialise("");
@ -495,6 +495,8 @@ namespace MWGui
}
else
allow(GW_ALL);
mStatsWatcher->forceUpdate();
}
WindowManager::~WindowManager()

@ -5,6 +5,8 @@
#include "../mwmechanics/actorutil.hpp"
#include <components/misc/timer.hpp>
namespace MWRender
{
class Animation;
@ -41,12 +43,18 @@ namespace MWMechanics
bool isTurningToPlayer() const;
void setTurningToPlayer(bool turning);
Misc::TimerStatus updateEngageCombatTimer(float duration)
{
return mEngageCombat.update(duration);
}
private:
std::unique_ptr<CharacterController> mCharacterController;
int mGreetingTimer{0};
float mTargetAngleRadians{0.f};
GreetingState mGreetingState{Greet_None};
bool mIsTurningToPlayer{false};
Misc::DeviatingPeriodicTimer mEngageCombat{1.0f, 0.25f, Misc::Rng::deviate(0, 0.25f)};
};
}

@ -346,7 +346,11 @@ namespace MWMechanics
if (actor != MWMechanics::getPlayer())
return;
MWWorld::Ptr newItem = *store.getSlot(slot);
MWWorld::Ptr newItem;
auto it = store.getSlot(slot);
// Equip can fail because beast races cannot equip boots/helmets
if(it != store.end())
newItem = *it;
if (newItem.isEmpty() || boundPtr != newItem)
return;
@ -1901,14 +1905,11 @@ namespace MWMechanics
{
if(!paused)
{
static float timerUpdateAITargets = 0;
static float timerUpdateHeadTrack = 0;
static float timerUpdateEquippedLight = 0;
static float timerUpdateHello = 0;
const float updateEquippedLightInterval = 1.0f;
// target lists get updated once every 1.0 sec
if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0;
if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0;
if (timerUpdateHello >= 0.25f) timerUpdateHello = 0;
if (mTimerDisposeSummonsCorpses >= 0.2f) mTimerDisposeSummonsCorpses = 0;
@ -1961,6 +1962,8 @@ namespace MWMechanics
iter->first.getClass().getCreatureStats(iter->first).getActiveSpells().update(duration);
const Misc::TimerStatus engageCombatTimerStatus = iter->second->updateEngageCombatTimer(duration);
// For dead actors we need to update looping spell particles
if (iter->first.getClass().getCreatureStats(iter->first).isDead())
{
@ -1989,7 +1992,7 @@ namespace MWMechanics
}
if (aiActive && inProcessingRange)
{
if (timerUpdateAITargets == 0)
if (engageCombatTimerStatus == Misc::TimerStatus::Elapsed)
{
if (!isPlayer)
adjustCommandedActor(iter->first);
@ -2076,7 +2079,6 @@ namespace MWMechanics
if (avoidCollisions)
predictAndAvoidCollisions();
timerUpdateAITargets += duration;
timerUpdateHeadTrack += duration;
timerUpdateEquippedLight += duration;
timerUpdateHello += duration;

@ -1,5 +1,7 @@
#include "aicast.hpp"
#include <components/misc/constants.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/world.hpp"
@ -54,12 +56,12 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
if (target != actor && target.getClass().isActor())
{
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(target);
targetPos.z() += halfExtents.z() * 2 * 0.75f;
targetPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight;
}
osg::Vec3f actorPos = actor.getRefData().getPosition().asVec3();
osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
actorPos.z() += halfExtents.z() * 2 * 0.75f;
actorPos.z() += halfExtents.z() * 2 * Constants::TorsoHeight;
osg::Vec3f dir = targetPos - actorPos;

@ -146,19 +146,10 @@ namespace MWMechanics
}
storage.mActionCooldown -= duration;
float& timerReact = storage.mTimerReact;
if (timerReact < AI_REACTION_TIME)
{
timerReact += duration;
}
else
{
timerReact = 0;
if (attack(actor, target, storage, characterController))
return true;
}
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
return false;
return attack(actor, target, storage, characterController);
}
bool AiCombat::attack(const MWWorld::Ptr& actor, const MWWorld::Ptr& target, AiCombatStorage& storage, CharacterController& characterController)

@ -10,6 +10,7 @@
#include "pathfinding.hpp"
#include "movement.hpp"
#include "obstacle.hpp"
#include "aitimer.hpp"
namespace ESM
{
@ -27,7 +28,7 @@ namespace MWMechanics
struct AiCombatStorage : AiTemporaryBase
{
float mAttackCooldown;
float mTimerReact;
AiReactionTimer mReaction;
float mTimerCombatMove;
bool mReadyToAttack;
bool mAttack;
@ -60,7 +61,6 @@ namespace MWMechanics
AiCombatStorage():
mAttackCooldown(0.0f),
mTimerReact(AI_REACTION_TIME),
mTimerCombatMove(0.0f),
mReadyToAttack(false),
mAttack(false),

@ -73,23 +73,25 @@ namespace MWMechanics
const MWWorld::Ptr follower = MWBase::Environment::get().getWorld()->getPtr(mTargetActorRefId, false);
const osg::Vec3f leaderPos = actor.getRefData().getPosition().asVec3();
const osg::Vec3f followerPos = follower.getRefData().getPosition().asVec3();
const osg::Vec3f halfExtents = MWBase::Environment::get().getWorld()->getHalfExtents(actor);
const float maxHalfExtent = std::max(halfExtents.x(), std::max(halfExtents.y(), halfExtents.z()));
if ((leaderPos - followerPos).length2() <= mMaxDist * mMaxDist)
{
const osg::Vec3f dest(mX, mY, mZ);
if (pathTo(actor, dest, duration)) //Returns true on path complete
if (pathTo(actor, dest, duration, maxHalfExtent)) //Returns true on path complete
{
mRemainingDuration = mDuration;
return true;
}
mMaxDist = 450;
mMaxDist = maxHalfExtent + 450.0f;
}
else
{
// Stop moving if the player is too far away
MWBase::Environment::get().getMechanicsManager()->playAnimationGroup(actor, "idle3", 0, 1);
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
mMaxDist = 250;
mMaxDist = maxHalfExtent + 250.0f;
}
return false;

@ -28,7 +28,6 @@
MWMechanics::AiPackage::AiPackage(AiPackageTypeId typeId, const Options& options) :
mTypeId(typeId),
mOptions(options),
mTimer(AI_REACTION_TIME + 1.0f), // to force initial pathbuild
mTargetActorRefId(""),
mTargetActorId(-1),
mRotateOnTheRunChecks(0),
@ -64,7 +63,7 @@ MWWorld::Ptr MWMechanics::AiPackage::getTarget() const
void MWMechanics::AiPackage::reset()
{
// reset all members
mTimer = AI_REACTION_TIME + 1.0f;
mReaction.reset();
mIsShortcutting = false;
mShortcutProhibited = false;
mShortcutFailPos = osg::Vec3f();
@ -75,7 +74,7 @@ void MWMechanics::AiPackage::reset()
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance)
{
mTimer += duration; //Update timer
const Misc::TimerStatus timerStatus = mReaction.update(duration);
const osg::Vec3f position = actor.getRefData().getPosition().asVec3(); //position of the actor
MWBase::World* world = MWBase::Environment::get().getWorld();
@ -98,7 +97,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
const bool isDestReached = (distToTarget <= destTolerance);
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
if (!isDestReached && mTimer > AI_REACTION_TIME)
if (!isDestReached && timerStatus == Misc::TimerStatus::Elapsed)
{
if (actor.getClass().isBipedal(actor))
openDoors(actor);
@ -115,7 +114,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path
{
const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor);
mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
mPathFinder.buildLimitedPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
pathfindingHalfExtents, getNavigatorFlags(actor), getAreaCosts(actor));
mRotateOnTheRunChecks = 3;
@ -142,8 +141,6 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f&
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
}
}
mTimer = 0;
}
const float actorTolerance = 2 * actor.getClass().getMaxSpeed(actor) * duration

@ -10,6 +10,7 @@
#include "obstacle.hpp"
#include "aistate.hpp"
#include "aipackagetypeid.hpp"
#include "aitimer.hpp"
namespace MWWorld
{
@ -28,8 +29,6 @@ namespace ESM
namespace MWMechanics
{
const float AI_REACTION_TIME = 0.25f;
class CharacterController;
class PathgridGraph;
@ -158,7 +157,7 @@ namespace MWMechanics
PathFinder mPathFinder;
ObstacleCheck mObstacleCheck;
float mTimer;
AiReactionTimer mReaction;
std::string mTargetActorRefId;
mutable int mTargetActorId;

@ -0,0 +1,26 @@
#ifndef OPENMW_MECHANICS_AITIMER_H
#define OPENMW_MECHANICS_AITIMER_H
#include <components/misc/rng.hpp>
#include <components/misc/timer.hpp>
namespace MWMechanics
{
constexpr float AI_REACTION_TIME = 0.25f;
class AiReactionTimer
{
public:
static constexpr float sDeviation = 0.1f;
Misc::TimerStatus update(float duration) { return mImpl.update(duration); }
void reset() { mImpl.reset(Misc::Rng::deviate(0, sDeviation)); }
private:
Misc::DeviatingPeriodicTimer mImpl {AI_REACTION_TIME, sDeviation,
Misc::Rng::deviate(0, sDeviation)};
};
}
#endif

@ -223,15 +223,10 @@ namespace MWMechanics
doPerFrameActionsForState(actor, duration, storage);
float& lastReaction = storage.mReaction;
lastReaction += duration;
if (AI_REACTION_TIME <= lastReaction)
{
lastReaction = 0;
return reactionTimeActions(actor, storage, pos);
}
else
if (storage.mReaction.update(duration) == Misc::TimerStatus::Waiting)
return false;
return reactionTimeActions(actor, storage, pos);
}
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage, ESM::Position& pos)

@ -10,6 +10,7 @@
#include "pathfinding.hpp"
#include "obstacle.hpp"
#include "aistate.hpp"
#include "aitimer.hpp"
namespace ESM
{
@ -25,7 +26,7 @@ namespace MWMechanics
/// \brief This class holds the variables AiWander needs which are deleted if the package becomes inactive.
struct AiWanderStorage : AiTemporaryBase
{
float mReaction; // update some actions infrequently
AiReactionTimer mReaction;
// AiWander states
enum WanderState
@ -57,7 +58,6 @@ namespace MWMechanics
int mStuckCount;
AiWanderStorage():
mReaction(0),
mState(Wander_ChooseAction),
mIsWanderingManually(false),
mCanWanderAlongPathGrid(true),

@ -24,7 +24,7 @@ namespace MWMechanics
{
struct CorprusStats
{
static const int sWorseningPeriod = 24;
static constexpr int sWorseningPeriod = 24;
int mWorsenings[ESM::Attribute::Length];
MWWorld::TimeStamp mNextWorsening;

@ -12,7 +12,7 @@ namespace MWMechanics
{
struct Movement;
static const int NUM_EVADE_DIRECTIONS = 4;
static constexpr int NUM_EVADE_DIRECTIONS = 4;
/// tests actor's proximity to a closed door by default
bool proximityToDoor(const MWWorld::Ptr& actor, float minDist);

@ -442,4 +442,21 @@ namespace MWMechanics
std::copy(prePath.rbegin(), prePath.rend(), std::front_inserter(mPath));
}
void PathFinder::buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts)
{
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
const auto maxDistance = std::min(
navigator->getMaxNavmeshAreaRealRadius(),
static_cast<float>(Constants::CellSizeInUnits)
);
const auto startToEnd = endPoint - startPoint;
const auto distance = startToEnd.length();
if (distance <= maxDistance)
return buildPath(actor, startPoint, endPoint, cell, pathgridGraph, halfExtents, flags, areaCosts);
const auto end = startPoint + startToEnd * maxDistance / distance;
buildPath(actor, startPoint, end, cell, pathgridGraph, halfExtents, flags, areaCosts);
}
}

@ -101,6 +101,10 @@ namespace MWMechanics
void buildPathByNavMeshToNextPoint(const MWWorld::ConstPtr& actor, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
void buildLimitedPath(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph, const osg::Vec3f& halfExtents,
const DetourNavigator::Flags flags, const DetourNavigator::AreaCosts& areaCosts);
/// Remove front point if exist and within tolerance
void update(const osg::Vec3f& position, float pointTolerance, float destinationTolerance,
bool shortenIfAlmostStraight, bool canMoveByZ);

@ -24,6 +24,14 @@ namespace MWMechanics
{
}
Spells::Spells(const Spells& spells) : mSpellList(spells.mSpellList), mSpells(spells.mSpells),
mSelectedSpell(spells.mSelectedSpell), mUsedPowers(spells.mUsedPowers),
mSpellsChanged(spells.mSpellsChanged), mEffects(spells.mEffects), mSourcedEffects(spells.mSourcedEffects)
{
if(mSpellList)
mSpellList->addListener(this);
}
std::map<const ESM::Spell*, SpellParams>::const_iterator Spells::begin() const
{
return mSpells.begin();

@ -57,6 +57,10 @@ namespace MWMechanics
Spells();
Spells(const Spells&);
Spells(const Spells&&) = delete;
~Spells();
static bool hasCorprusEffect(const ESM::Spell *spell);

@ -145,6 +145,12 @@ namespace MWMechanics
for (std::map<ESM::SummonKey, int>::iterator it = creatureMap.begin(); it != creatureMap.end(); )
{
if(it->second == -1)
{
// Keep the spell effect active if we failed to spawn anything
it++;
continue;
}
MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second);
if (ptr.isEmpty() || (ptr.getClass().getCreatureStats(ptr).isDead() && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished()))
{

@ -22,6 +22,7 @@ namespace MWPhysics
Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler)
: mStandingOnPtr(nullptr), mCanWaterWalk(false), mWalkingOnWater(false)
, mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mHalfExtents(shape->mCollisionBox.extents)
, mStuckFrames(0), mLastStuckPosition{0, 0, 0}
, mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false)
, mInternalCollisionMode(true)
, mExternalCollisionMode(true)

@ -159,6 +159,24 @@ namespace MWPhysics
MWWorld::Ptr getStandingOnPtr() const;
void setStandingOnPtr(const MWWorld::Ptr& ptr);
unsigned int getStuckFrames() const
{
return mStuckFrames;
}
void setStuckFrames(unsigned int frames)
{
mStuckFrames = frames;
}
const osg::Vec3f &getLastStuckPosition() const
{
return mLastStuckPosition;
}
void setLastStuckPosition(osg::Vec3f position)
{
mLastStuckPosition = position;
}
private:
MWWorld::Ptr mStandingOnPtr;
/// Removes then re-adds the collision object to the dynamics world
@ -192,6 +210,9 @@ namespace MWPhysics
btTransform mLocalTransform;
mutable std::mutex mPositionMutex;
unsigned int mStuckFrames;
osg::Vec3f mLastStuckPosition;
osg::Vec3f mForce;
std::atomic<bool> mOnGround;
std::atomic<bool> mOnSlope;

@ -3,24 +3,24 @@
namespace MWPhysics
{
static const float sStepSizeUp = 34.0f;
static const float sStepSizeDown = 62.0f;
static constexpr float sStepSizeUp = 34.0f;
static constexpr float sStepSizeDown = 62.0f;
static const float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
static const float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes
static constexpr float sMinStep = 10.0f; // hack to skip over tiny unwalkable slopes
static constexpr float sMinStep2 = 20.0f; // hack to skip over shorter but longer/wider/further unwalkable slopes
// whether to do the above stairstepping logic hacks to work around bad morrowind assets - disabling causes problems but improves performance
static const bool sDoExtraStairHacks = true;
static constexpr bool sDoExtraStairHacks = true;
static const float sGroundOffset = 1.0f;
static const float sMaxSlope = 49.0f;
static constexpr float sGroundOffset = 1.0f;
static constexpr float sMaxSlope = 49.0f;
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
static const int sMaxIterations = 8;
static constexpr int sMaxIterations = 8;
// Allows for more precise movement solving without getting stuck or snagging too easily.
static const float sCollisionMargin = 0.1;
static constexpr float sCollisionMargin = 0.1;
// Allow for a small amount of penetration to prevent numerical precision issues from causing the "unstuck"ing code to run unnecessarily
// Currently set to 0 because having the "unstuck"ing code run whenever possible prevents some glitchy snagging issues
static const float sAllowedPenetration = 0.0;
static constexpr float sAllowedPenetration = 0.0;
}
#endif

@ -204,7 +204,7 @@ namespace MWPhysics
osg::Vec3f lastSlideNormalFallback(0,0,1);
bool forceGroundTest = false;
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.0001f; ++iterations)
{
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
@ -394,6 +394,12 @@ namespace MWPhysics
isOnGround = false;
}
}
// forcibly treat stuck actors as if they're on flat ground because buggy collisions when inside of things can/will break ground detection
if(physicActor->getStuckFrames() > 0)
{
isOnGround = true;
isOnSlope = false;
}
}
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || actor.mFlying)
@ -437,13 +443,23 @@ namespace MWPhysics
auto* collisionObject = physicActor->getCollisionObject();
auto tempPosition = actor.mPosition;
if(physicActor->getStuckFrames() >= 10)
{
if((physicActor->getLastStuckPosition() - actor.mPosition).length2() < 100)
return;
else
{
physicActor->setStuckFrames(0);
physicActor->setLastStuckPosition({0, 0, 0});
}
}
// use vanilla-accurate collision hull position hack (do same hitbox offset hack as movement solver)
// if vanilla compatibility didn't matter, the "correct" collision hull position would be physicActor->getScaledMeshTranslation()
const auto verticalHalfExtent = osg::Vec3f(0.0, 0.0, physicActor->getHalfExtents().z());
// use a 3d approximation of the movement vector to better judge player intent
const ESM::Position& refpos = ptr.getRefData().getPosition();
auto velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
auto velocity = (osg::Quat(actor.mRefpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(actor.mRefpos.rot[2], osg::Vec3f(0, 0, -1))) * actor.mMovement;
// try to pop outside of the world before doing anything else if we're inside of it
if (!physicActor->getOnGround() || physicActor->getOnSlope())
velocity += physicActor->getInertialForce();
@ -470,6 +486,8 @@ namespace MWPhysics
auto contactCallback = gatherContacts({0.0, 0.0, 0.0});
if(contactCallback.mDistance < -sAllowedPenetration)
{
physicActor->setStuckFrames(physicActor->getStuckFrames() + 1);
physicActor->setLastStuckPosition(actor.mPosition);
// we are; try moving it out of the world
auto positionDelta = contactCallback.mContactSum;
// limit rejection delta to the largest known individual rejections
@ -502,6 +520,11 @@ namespace MWPhysics
}
}
}
else
{
physicActor->setStuckFrames(0);
physicActor->setLastStuckPosition({0, 0, 0});
}
collisionObject->setWorldTransform(oldTransform);
actor.mPosition = tempPosition;

@ -9,6 +9,7 @@
#include "components/settings/settings.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwmechanics/movement.hpp"
#include "../mwrender/bulletdebugdraw.hpp"
#include "../mwworld/class.hpp"
#include "../mwworld/player.hpp"
@ -101,7 +102,7 @@ namespace
osg::Vec3f interpolateMovements(MWPhysics::ActorFrameData& actorData, float timeAccum, float physicsDt)
{
const float interpolationFactor = timeAccum / physicsDt;
const float interpolationFactor = std::clamp(timeAccum / physicsDt, 0.0f, 1.0f);
return actorData.mPosition * interpolationFactor + actorData.mActorRaw->getPreviousPosition() * (1.f - interpolationFactor);
}
@ -137,10 +138,12 @@ namespace
namespace MWPhysics
{
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld)
: mPhysicsDt(physicsDt)
PhysicsTaskScheduler::PhysicsTaskScheduler(float physicsDt, btCollisionWorld *collisionWorld, MWRender::DebugDrawer* debugDrawer)
: mDefaultPhysicsDt(physicsDt)
, mPhysicsDt(physicsDt)
, mTimeAccum(0.f)
, mCollisionWorld(std::move(collisionWorld))
, mCollisionWorld(collisionWorld)
, mDebugDrawer(debugDrawer)
, mNumJobs(0)
, mRemainingSteps(0)
, mLOSCacheExpiry(Settings::Manager::getInt("lineofsight keep inactive cache", "Physics"))
@ -152,6 +155,11 @@ namespace MWPhysics
, mNextLOS(0)
, mFrameNumber(0)
, mTimer(osg::Timer::instance())
, mPrevStepCount(1)
, mBudget(physicsDt)
, mAsyncBudget(0.0f)
, mBudgetCursor(0)
, mAsyncStartTime(0)
, mTimeBegin(0)
, mTimeEnd(0)
, mFrameStart(0)
@ -179,16 +187,18 @@ namespace MWPhysics
if (data.mActor.lock())
{
std::unique_lock lock(mCollisionWorldMutex);
MovementSolver::unstuck(data, mCollisionWorld.get());
MovementSolver::unstuck(data, mCollisionWorld);
}
});
mPostStepBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
{
if (mRemainingSteps)
{
--mRemainingSteps;
updateActorsPositions();
}
mNextJob.store(0, std::memory_order_release);
updateActorsPositions();
});
mPostSimBarrier = std::make_unique<Misc::Barrier>(mNumThreads, [&]()
@ -218,13 +228,61 @@ namespace MWPhysics
thread.join();
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
std::tuple<int, float> PhysicsTaskScheduler::calculateStepConfig(float timeAccum) const
{
int maxAllowedSteps = 2;
int numSteps = timeAccum / mDefaultPhysicsDt;
// adjust maximum step count based on whether we're likely physics bottlenecked or not
// if maxAllowedSteps ends up higher than numSteps, we will not invoke delta time
// if it ends up lower than numSteps, but greater than 1, we will run a number of true delta time physics steps that we expect to be within budget
// if it ends up lower than numSteps and also 1, we will run a single delta time physics step
// if we did not do this, and had a fixed step count limit,
// we would have an unnecessarily low render framerate if we were only physics bottlenecked,
// and we would be unnecessarily invoking true delta time if we were only render bottlenecked
// get physics timing stats
float budgetMeasurement = std::max(mBudget.get(), mAsyncBudget.get());
// time spent per step in terms of the intended physics framerate
budgetMeasurement /= mDefaultPhysicsDt;
// ensure sane minimum value
budgetMeasurement = std::max(0.00001f, budgetMeasurement);
// we're spending almost or more than realtime per physics frame; limit to a single step
if (budgetMeasurement > 0.95)
maxAllowedSteps = 1;
// physics is fairly cheap; limit based on expense
if (budgetMeasurement < 0.5)
maxAllowedSteps = std::ceil(1.0/budgetMeasurement);
// limit to a reasonable amount
maxAllowedSteps = std::min(10, maxAllowedSteps);
// fall back to delta time for this frame if fixed timestep physics would fall behind
float actualDelta = mDefaultPhysicsDt;
if (numSteps > maxAllowedSteps)
{
numSteps = maxAllowedSteps;
// ensure that we do not simulate a frame ahead when doing delta time; this reduces stutter and latency
// this causes interpolation to 100% use the most recent physics result when true delta time is happening
// and we deliberately simulate up to exactly the timestamp that we want to render
actualDelta = timeAccum/float(numSteps+1);
// actually: if this results in a per-step delta less than the target physics steptime, clamp it
// this might reintroduce some stutter, but only comes into play in obscure cases
// (because numSteps is originally based on mDefaultPhysicsDt, this won't cause us to overrun)
actualDelta = std::max(actualDelta, mDefaultPhysicsDt);
}
return std::make_tuple(numSteps, actualDelta);
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats)
{
// This function run in the main thread.
// While the mSimulationMutex is held, background physics threads can't run.
std::unique_lock lock(mSimulationMutex);
double timeStart = mTimer->tick();
mMovedActors.clear();
// start by finishing previous background computation
@ -249,14 +307,21 @@ namespace MWPhysics
mMovedActors.emplace_back(data.mActorRaw->getPtr());
}
}
if(mAdvanceSimulation)
mAsyncBudget.update(mTimer->delta_s(mAsyncStartTime, mTimeEnd), mPrevStepCount, mBudgetCursor);
updateStats(frameStart, frameNumber, stats);
}
auto [numSteps, newDelta] = calculateStepConfig(timeAccum);
timeAccum -= numSteps*newDelta;
// init
for (auto& data : actorsData)
data.updatePosition();
mPrevStepCount = numSteps;
mRemainingSteps = numSteps;
mTimeAccum = timeAccum;
mPhysicsDt = newDelta;
mActorsFrameData = std::move(actorsData);
mAdvanceSimulation = (mRemainingSteps != 0);
mNewFrame = true;
@ -267,20 +332,30 @@ namespace MWPhysics
if (mAdvanceSimulation)
mWorldFrameData = std::make_unique<WorldFrameData>();
if (mAdvanceSimulation)
mBudgetCursor += 1;
if (mNumThreads == 0)
{
syncComputation();
if(mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), numSteps, mBudgetCursor);
return mMovedActors;
}
mAsyncStartTime = mTimer->tick();
lock.unlock();
mHasJob.notify_all();
if (mAdvanceSimulation)
mBudget.update(mTimer->delta_s(timeStart, mTimer->tick()), 1, mBudgetCursor);
return mMovedActors;
}
const std::vector<MWWorld::Ptr>& PhysicsTaskScheduler::resetSimulation(const ActorMap& actors)
{
std::unique_lock lock(mSimulationMutex);
mBudget.reset(mDefaultPhysicsDt);
mAsyncBudget.reset(0.0f);
mMovedActors.clear();
mActorsFrameData.clear();
for (const auto& [_, actor] : actors)
@ -308,7 +383,7 @@ namespace MWPhysics
void PhysicsTaskScheduler::contactTest(btCollisionObject* colObj, btCollisionWorld::ContactResultCallback& resultCallback)
{
std::shared_lock lock(mCollisionWorldMutex);
ContactTestWrapper::contactTest(mCollisionWorld.get(), colObj, resultCallback);
ContactTestWrapper::contactTest(mCollisionWorld, colObj, resultCallback);
}
std::optional<btVector3> PhysicsTaskScheduler::getHitPoint(const btTransform& from, btCollisionObject* target)
@ -459,7 +534,7 @@ namespace MWPhysics
if(const auto actor = mActorsFrameData[job].mActor.lock())
{
MaybeSharedLock lockColWorld(mCollisionWorldMutex, mThreadSafeBullet);
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
MovementSolver::move(mActorsFrameData[job], mPhysicsDt, mCollisionWorld, *mWorldFrameData);
}
}
@ -521,8 +596,8 @@ namespace MWPhysics
{
for (auto& actorData : mActorsFrameData)
{
MovementSolver::unstuck(actorData, mCollisionWorld.get());
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld.get(), *mWorldFrameData);
MovementSolver::unstuck(actorData, mCollisionWorld);
MovementSolver::move(actorData, mPhysicsDt, mCollisionWorld, *mWorldFrameData);
}
updateActorsPositions();
@ -553,4 +628,10 @@ namespace MWPhysics
mTimeBegin = mTimer->tick();
mFrameNumber = frameNumber;
}
void PhysicsTaskScheduler::debugDraw()
{
std::shared_lock lock(mCollisionWorldMutex);
mDebugDrawer->step();
}
}

@ -13,18 +13,24 @@
#include "physicssystem.hpp"
#include "ptrholder.hpp"
#include "components/misc/budgetmeasurement.hpp"
namespace Misc
{
class Barrier;
}
namespace MWRender
{
class DebugDrawer;
}
namespace MWPhysics
{
class PhysicsTaskScheduler
{
public:
PhysicsTaskScheduler(float physicsDt, std::shared_ptr<btCollisionWorld> collisionWorld);
PhysicsTaskScheduler(float physicsDt, btCollisionWorld* collisionWorld, MWRender::DebugDrawer* debugDrawer);
~PhysicsTaskScheduler();
/// @brief move actors taking into account desired movements and collisions
@ -32,7 +38,7 @@ namespace MWPhysics
/// @param timeAccum accumulated time from previous run to interpolate movements
/// @param actorsData per actor data needed to compute new positions
/// @return new position of each actor
const std::vector<MWWorld::Ptr>& moveActors(int numSteps, float timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& moveActors(float & timeAccum, std::vector<ActorFrameData>&& actorsData, osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
const std::vector<MWWorld::Ptr>& resetSimulation(const ActorMap& actors);
@ -48,6 +54,7 @@ namespace MWPhysics
void removeCollisionObject(btCollisionObject* collisionObject);
void updateSingleAabb(std::weak_ptr<PtrHolder> ptr, bool immediate=false);
bool getLineOfSight(const std::weak_ptr<Actor>& actor1, const std::weak_ptr<Actor>& actor2);
void debugDraw();
private:
void syncComputation();
@ -58,13 +65,16 @@ namespace MWPhysics
void updateAabbs();
void updatePtrAabb(const std::weak_ptr<PtrHolder>& ptr);
void updateStats(osg::Timer_t frameStart, unsigned int frameNumber, osg::Stats& stats);
std::tuple<int, float> calculateStepConfig(float timeAccum) const;
std::unique_ptr<WorldFrameData> mWorldFrameData;
std::vector<ActorFrameData> mActorsFrameData;
std::vector<MWWorld::Ptr> mMovedActors;
const float mPhysicsDt;
float mDefaultPhysicsDt;
float mPhysicsDt;
float mTimeAccum;
std::shared_ptr<btCollisionWorld> mCollisionWorld;
btCollisionWorld* mCollisionWorld;
MWRender::DebugDrawer* mDebugDrawer;
std::vector<LOSRequest> mLOSCache;
std::set<std::weak_ptr<PtrHolder>, std::owner_less<std::weak_ptr<PtrHolder>>> mUpdateAabb;
@ -94,6 +104,12 @@ namespace MWPhysics
unsigned int mFrameNumber;
const osg::Timer* mTimer;
int mPrevStepCount;
Misc::BudgetMeasurement mBudget;
Misc::BudgetMeasurement mAsyncBudget;
unsigned int mBudgetCursor;
osg::Timer_t mAsyncStartTime;
osg::Timer_t mTimeBegin;
osg::Timer_t mTimeEnd;
osg::Timer_t mFrameStart;

@ -79,7 +79,7 @@ namespace MWPhysics
mDispatcher = std::make_unique<btCollisionDispatcher>(mCollisionConfiguration.get());
mBroadphase = std::make_unique<btDbvtBroadphase>();
mCollisionWorld = std::make_shared<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
mCollisionWorld = std::make_unique<btCollisionWorld>(mDispatcher.get(), mBroadphase.get(), mCollisionConfiguration.get());
// Don't update AABBs of all objects every frame. Most objects in MW are static, so we don't need this.
// Should a "static" object ever be moved, we have to update its AABB manually using DynamicsWorld::updateSingleAabb.
@ -97,8 +97,8 @@ namespace MWPhysics
}
}
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld);
mDebugDrawer = std::make_unique<MWRender::DebugDrawer>(mParentNode, mCollisionWorld.get(), mDebugDrawEnabled);
mTaskScheduler = std::make_unique<PhysicsTaskScheduler>(mPhysicsDt, mCollisionWorld.get(), mDebugDrawer.get());
}
PhysicsSystem::~PhysicsSystem()
@ -744,19 +744,14 @@ namespace MWPhysics
{
mTimeAccum += dt;
const int maxAllowedSteps = 20;
int numSteps = mTimeAccum / mPhysicsDt;
numSteps = std::min(numSteps, maxAllowedSteps);
mTimeAccum -= numSteps * mPhysicsDt;
if (skipSimulation)
return mTaskScheduler->resetSimulation(mActors);
return mTaskScheduler->moveActors(numSteps, mTimeAccum, prepareFrameData(numSteps), frameStart, frameNumber, stats);
// modifies mTimeAccum
return mTaskScheduler->moveActors(mTimeAccum, prepareFrameData(mTimeAccum >= mPhysicsDt), frameStart, frameNumber, stats);
}
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(int numSteps)
std::vector<ActorFrameData> PhysicsSystem::prepareFrameData(bool willSimulate)
{
std::vector<ActorFrameData> actorsFrameData;
actorsFrameData.reserve(mMovementQueue.size());
@ -796,7 +791,7 @@ namespace MWPhysics
// Ue current value only if we don't advance the simulation. Otherwise we might get a stale value.
MWWorld::Ptr standingOn;
if (numSteps == 0)
if (!willSimulate)
standingOn = physicActor->getStandingOnPtr();
actorsFrameData.emplace_back(std::move(physicActor), standingOn, moveToWaterSurface, movement, slowFall, waterlevel);
@ -832,7 +827,7 @@ namespace MWPhysics
void PhysicsSystem::debugDraw()
{
if (mDebugDrawEnabled)
mDebugDrawer->step();
mTaskScheduler->debugDraw();
}
bool PhysicsSystem::isActorStandingOn(const MWWorld::Ptr &actor, const MWWorld::ConstPtr &object) const

@ -252,14 +252,14 @@ namespace MWPhysics
void updateWater();
std::vector<ActorFrameData> prepareFrameData(int numSteps);
std::vector<ActorFrameData> prepareFrameData(bool willSimulate);
osg::ref_ptr<SceneUtil::UnrefQueue> mUnrefQueue;
std::unique_ptr<btBroadphaseInterface> mBroadphase;
std::unique_ptr<btDefaultCollisionConfiguration> mCollisionConfiguration;
std::unique_ptr<btCollisionDispatcher> mDispatcher;
std::shared_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<btCollisionWorld> mCollisionWorld;
std::unique_ptr<PhysicsTaskScheduler> mTaskScheduler;
std::unique_ptr<Resource::BulletShapeManager> mShapeManager;

@ -102,7 +102,7 @@ public:
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
};
/* This is the number of *discrete* blend masks. */
static const size_t sNumBlendMasks = 4;
static constexpr size_t sNumBlendMasks = 4;
/// Holds an animation priority value for each BoneGroup.
struct AnimPriority

@ -497,8 +497,6 @@ public:
// Disable writing to the color buffer. We are using this geometry for visibility tests only.
osg::ref_ptr<osg::ColorMask> colormask (new osg::ColorMask(0, 0, 0, 0));
stateset->setAttributeAndModes(colormask, osg::StateAttribute::ON);
osg::ref_ptr<osg::PolygonOffset> po (new osg::PolygonOffset( -1., -1. ));
stateset->setAttributeAndModes(po, osg::StateAttribute::ON);
mTransform->addChild(queryNode);
@ -595,7 +593,7 @@ private:
if (queryVisible)
{
osg::ref_ptr<osg::Depth> depth (new osg::Depth);
depth->setFunction(osg::Depth::LESS);
depth->setFunction(osg::Depth::LEQUAL);
// This is a trick to make fragments written by the query always use the maximum depth value,
// without having to retrieve the current far clipping distance.
// We want the sun glare to be "infinitely" far away.

@ -14,7 +14,7 @@ namespace MWScript
{
struct ExplicitRef
{
static const bool implicit = false;
static constexpr bool implicit = false;
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
bool activeOnly = false) const;
@ -22,7 +22,7 @@ namespace MWScript
struct ImplicitRef
{
static const bool implicit = true;
static constexpr bool implicit = true;
MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true,
bool activeOnly = false) const;

@ -69,15 +69,13 @@ namespace MWSound
FFmpeg_Decoder& operator=(const FFmpeg_Decoder &rhs);
FFmpeg_Decoder(const FFmpeg_Decoder &rhs);
FFmpeg_Decoder(const VFS::Manager* vfs);
public:
explicit FFmpeg_Decoder(const VFS::Manager* vfs);
virtual ~FFmpeg_Decoder();
friend class SoundManager;
};
#ifndef DEFAULT_DECODER
#define DEFAULT_DECODER (::MWSound::FFmpeg_Decoder)
#endif
}
#endif

@ -98,9 +98,6 @@ namespace MWSound
OpenAL_Output(SoundManager &mgr);
virtual ~OpenAL_Output();
};
#ifndef DEFAULT_OUTPUT
#define DEFAULT_OUTPUT(x) ::MWSound::OpenAL_Output((x))
#endif
}
#endif

@ -54,7 +54,7 @@ namespace MWSound
SoundManager::SoundManager(const VFS::Manager* vfs, bool useSound)
: mVFS(vfs)
, mOutput(new DEFAULT_OUTPUT(*this))
, mOutput(new OpenAL_Output(*this))
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
, mSoundBuffers(*vfs, *mOutput)
, mListenerUnderwater(false)
@ -109,7 +109,7 @@ namespace MWSound
SoundManager::~SoundManager()
{
clear();
SoundManager::clear();
mSoundBuffers.clear();
mOutput.reset();
}
@ -117,7 +117,7 @@ namespace MWSound
// Return a new decoder instance, used as needed by the output implementations
DecoderPtr SoundManager::getDecoder()
{
return DecoderPtr(new DEFAULT_DECODER (mVFS));
return std::make_shared<FFmpeg_Decoder>(mVFS);
}
DecoderPtr SoundManager::loadVoice(const std::string &voicefile)

@ -505,6 +505,7 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
character->getPath().filename().string());
MWBase::Environment::get().getWindowManager()->setNewGame(false);
MWBase::Environment::get().getWorld()->saveLoaded();
MWBase::Environment::get().getWorld()->setupPlayer();
MWBase::Environment::get().getWorld()->renderPlayer();
MWBase::Environment::get().getWindowManager()->updatePlayer();

@ -73,22 +73,22 @@ namespace MWWorld
{
public:
static const int Type_Potion = 0x0001;
static const int Type_Apparatus = 0x0002;
static const int Type_Armor = 0x0004;
static const int Type_Book = 0x0008;
static const int Type_Clothing = 0x0010;
static const int Type_Ingredient = 0x0020;
static const int Type_Light = 0x0040;
static const int Type_Lockpick = 0x0080;
static const int Type_Miscellaneous = 0x0100;
static const int Type_Probe = 0x0200;
static const int Type_Repair = 0x0400;
static const int Type_Weapon = 0x0800;
static const int Type_Last = Type_Weapon;
static const int Type_All = 0xffff;
static constexpr int Type_Potion = 0x0001;
static constexpr int Type_Apparatus = 0x0002;
static constexpr int Type_Armor = 0x0004;
static constexpr int Type_Book = 0x0008;
static constexpr int Type_Clothing = 0x0010;
static constexpr int Type_Ingredient = 0x0020;
static constexpr int Type_Light = 0x0040;
static constexpr int Type_Lockpick = 0x0080;
static constexpr int Type_Miscellaneous = 0x0100;
static constexpr int Type_Probe = 0x0200;
static constexpr int Type_Repair = 0x0400;
static constexpr int Type_Weapon = 0x0800;
static constexpr int Type_Last = Type_Weapon;
static constexpr int Type_All = 0xffff;
static const std::string sGoldId;
@ -265,13 +265,13 @@ namespace MWWorld
template<class From, class To, class Dummy>
struct IsConvertible
{
static const bool value = true;
static constexpr bool value = true;
};
template<class Dummy>
struct IsConvertible<ConstPtr, Ptr, Dummy>
{
static const bool value = false;
static constexpr bool value = false;
};
template<class T, class U>

@ -48,6 +48,57 @@ namespace
}
}
}
std::vector<ESM::NPC> getNPCsToReplace(const MWWorld::Store<ESM::Faction>& factions, const MWWorld::Store<ESM::Class>& classes, const std::map<std::string, ESM::NPC>& npcs)
{
// Cache first class from store - we will use it if current class is not found
std::string defaultCls;
auto it = classes.begin();
if (it != classes.end())
defaultCls = it->mId;
else
throw std::runtime_error("List of NPC classes is empty!");
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
std::vector<ESM::NPC> npcsToReplace;
for (auto it : npcs)
{
ESM::NPC npc = it.second;
bool changed = false;
const std::string npcFaction = npc.mFaction;
if (!npcFaction.empty())
{
const ESM::Faction *fact = factions.search(npcFaction);
if (!fact)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
npc.mFaction.clear();
npc.mNpdt.mRank = 0;
changed = true;
}
}
std::string npcClass = npc.mClass;
if (!npcClass.empty())
{
const ESM::Class *cls = classes.search(npcClass);
if (!cls)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
npc.mClass = defaultCls;
changed = true;
}
}
if (changed)
npcsToReplace.push_back(npc);
}
return npcsToReplace;
}
}
namespace MWWorld
@ -218,49 +269,7 @@ int ESMStore::getRefCount(const std::string& id) const
void ESMStore::validate()
{
// Cache first class from store - we will use it if current class is not found
std::string defaultCls = "";
Store<ESM::Class>::iterator it = mClasses.begin();
if (it != mClasses.end())
defaultCls = it->mId;
else
throw std::runtime_error("List of NPC classes is empty!");
// Validate NPCs for non-existing class and faction.
// We will replace invalid entries by fixed ones
std::vector<ESM::NPC> npcsToReplace;
for (ESM::NPC npc : mNpcs)
{
bool changed = false;
const std::string npcFaction = npc.mFaction;
if (!npcFaction.empty())
{
const ESM::Faction *fact = mFactions.search(npcFaction);
if (!fact)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent faction '" << npc.mFaction << "', ignoring it.";
npc.mFaction.clear();
npc.mNpdt.mRank = 0;
changed = true;
}
}
std::string npcClass = npc.mClass;
if (!npcClass.empty())
{
const ESM::Class *cls = mClasses.search(npcClass);
if (!cls)
{
Log(Debug::Verbose) << "NPC '" << npc.mId << "' (" << npc.mName << ") has nonexistent class '" << npc.mClass << "', using '" << defaultCls << "' class as replacement.";
npc.mClass = defaultCls;
changed = true;
}
}
if (changed)
npcsToReplace.push_back(npc);
}
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mStatic);
for (const ESM::NPC &npc : npcsToReplace)
{
@ -331,6 +340,14 @@ void ESMStore::validate()
}
}
void ESMStore::validateDynamic()
{
std::vector<ESM::NPC> npcsToReplace = getNPCsToReplace(mFactions, mClasses, mNpcs.mDynamic);
for (const ESM::NPC &npc : npcsToReplace)
mNpcs.insert(npc);
}
int ESMStore::countSavedGameRecords() const
{
return 1 // DYNA (dynamic name counter)
@ -384,12 +401,14 @@ void ESMStore::validate()
case ESM::REC_ENCH:
case ESM::REC_SPEL:
case ESM::REC_WEAP:
case ESM::REC_NPC_:
case ESM::REC_LEVI:
case ESM::REC_LEVC:
mStores[type]->read (reader);
return true;
case ESM::REC_NPC_:
case ESM::REC_CREA:
case ESM::REC_CONT:
mStores[type]->read (reader);
mStores[type]->read (reader, true);
return true;
case ESM::REC_DYNA:

@ -80,8 +80,6 @@ namespace MWWorld
std::map<int, StoreBase *> mStores;
ESM::NPC mPlayerTemplate;
unsigned int mDynamicCount;
mutable std::map<std::string, std::weak_ptr<MWMechanics::SpellList> > mSpellListCache;
@ -172,16 +170,18 @@ namespace MWWorld
for (std::map<int, StoreBase *>::iterator it = mStores.begin(); it != mStores.end(); ++it)
it->second->clearDynamic();
mNpcs.insert(mPlayerTemplate);
movePlayerRecord();
}
void movePlayerRecord ()
{
mPlayerTemplate = *mNpcs.find("player");
mNpcs.eraseStatic(mPlayerTemplate.mId);
mNpcs.insert(mPlayerTemplate);
auto player = mNpcs.find("player");
mNpcs.insert(*player);
}
/// Validate entries in store after loading a save
void validateDynamic();
void load(ESM::ESMReader &esm, Loading::Listener* listener);
template <class T>

@ -42,29 +42,29 @@ namespace MWWorld
{
public:
static const int Slot_Helmet = 0;
static const int Slot_Cuirass = 1;
static const int Slot_Greaves = 2;
static const int Slot_LeftPauldron = 3;
static const int Slot_RightPauldron = 4;
static const int Slot_LeftGauntlet = 5;
static const int Slot_RightGauntlet = 6;
static const int Slot_Boots = 7;
static const int Slot_Shirt = 8;
static const int Slot_Pants = 9;
static const int Slot_Skirt = 10;
static const int Slot_Robe = 11;
static const int Slot_LeftRing = 12;
static const int Slot_RightRing = 13;
static const int Slot_Amulet = 14;
static const int Slot_Belt = 15;
static const int Slot_CarriedRight = 16;
static const int Slot_CarriedLeft = 17;
static const int Slot_Ammunition = 18;
static const int Slots = 19;
static const int Slot_NoSlot = -1;
static constexpr int Slot_Helmet = 0;
static constexpr int Slot_Cuirass = 1;
static constexpr int Slot_Greaves = 2;
static constexpr int Slot_LeftPauldron = 3;
static constexpr int Slot_RightPauldron = 4;
static constexpr int Slot_LeftGauntlet = 5;
static constexpr int Slot_RightGauntlet = 6;
static constexpr int Slot_Boots = 7;
static constexpr int Slot_Shirt = 8;
static constexpr int Slot_Pants = 9;
static constexpr int Slot_Skirt = 10;
static constexpr int Slot_Robe = 11;
static constexpr int Slot_LeftRing = 12;
static constexpr int Slot_RightRing = 13;
static constexpr int Slot_Amulet = 14;
static constexpr int Slot_Belt = 15;
static constexpr int Slot_CarriedRight = 16;
static constexpr int Slot_CarriedLeft = 17;
static constexpr int Slot_Ammunition = 18;
static constexpr int Slots = 19;
static constexpr int Slot_NoSlot = -1;
private:

@ -265,9 +265,8 @@ namespace MWWorld
osg::Vec3f pos = caster.getRefData().getPosition().asVec3();
if (caster.getClass().isActor())
{
// Spawn at 0.75 * ActorHeight
// Note: we ignore the collision box offset, this is required to make some flying creatures work as intended.
pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * 0.75;
pos.z() += mPhysics->getRenderingHalfExtents(caster).z() * 2 * Constants::TorsoHeight;
}
if (MWBase::Environment::get().getWorld()->isUnderwater(caster.getCell(), pos)) // Underwater casting not possible

@ -250,9 +250,15 @@ namespace MWWorld
}
}
template<typename T>
T *Store<T>::insert(const T &item)
T *Store<T>::insert(const T &item, bool overrideOnly)
{
std::string id = Misc::StringUtils::lowerCase(item.mId);
if(overrideOnly)
{
auto it = mStatic.find(id);
if(it == mStatic.end())
return nullptr;
}
std::pair<typename Dynamic::iterator, bool> result =
mDynamic.insert(std::pair<std::string, T>(id, item));
T *ptr = &result.first->second;
@ -337,13 +343,13 @@ namespace MWWorld
}
}
template<typename T>
RecordId Store<T>::read(ESM::ESMReader& reader)
RecordId Store<T>::read(ESM::ESMReader& reader, bool overrideOnly)
{
T record;
bool isDeleted = false;
record.load (reader, isDeleted);
insert (record);
insert (record, overrideOnly);
return RecordId(record.mId, isDeleted);
}

@ -46,7 +46,7 @@ namespace MWWorld
virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const {}
virtual RecordId read (ESM::ESMReader& reader) { return RecordId(); }
virtual RecordId read (ESM::ESMReader& reader, bool overrideOnly = false) { return RecordId(); }
///< Read into dynamic storage
};
@ -192,7 +192,7 @@ namespace MWWorld
/// @note The record identifiers are listed in the order that the records were defined by the content files.
void listIdentifier(std::vector<std::string> &list) const override;
T *insert(const T &item);
T *insert(const T &item, bool overrideOnly = false);
T *insertStatic(const T &item);
bool eraseStatic(const std::string &id) override;
@ -201,7 +201,7 @@ namespace MWWorld
RecordId load(ESM::ESMReader &esm) override;
void write(ESM::ESMWriter& writer, Loading::Listener& progress) const override;
RecordId read(ESM::ESMReader& reader) override;
RecordId read(ESM::ESMReader& reader, bool overrideOnly = false) override;
};
template <>

@ -2473,6 +2473,11 @@ namespace MWWorld
mRendering->getCamera()->adjustCameraDistance(dist);
}
void World::saveLoaded()
{
mStore.validateDynamic();
}
void World::setupPlayer()
{
const ESM::NPC *player = mStore.get<ESM::NPC>().find("player");
@ -3867,11 +3872,14 @@ namespace MWWorld
return false;
}
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const MWWorld::ConstPtr& target)
osg::Vec3f World::aimToTarget(const ConstPtr &actor, const ConstPtr &target)
{
osg::Vec3f weaponPos = actor.getRefData().getPosition().asVec3();
weaponPos.z() += mPhysics->getHalfExtents(actor).z();
osg::Vec3f targetPos = mPhysics->getCollisionObjectPosition(target);
osg::Vec3f weaponHalfExtents = mPhysics->getHalfExtents(actor);
osg::Vec3f targetPos = target.getRefData().getPosition().asVec3();
osg::Vec3f targetHalfExtents = mPhysics->getHalfExtents(target);
weaponPos.z() += weaponHalfExtents.z() * 2 * Constants::TorsoHeight;
targetPos.z() += targetHalfExtents.z();
return (targetPos - weaponPos);
}

@ -549,6 +549,8 @@ namespace MWWorld
void applyDeferredPreviewRotationToPlayer(float dt) override;
void disableDeferredPreviewRotation() override;
void saveLoaded() override;
void setupPlayer() override;
void renderPlayer() override;

@ -28,12 +28,11 @@
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
using namespace std;
using namespace Bsa;
/// Error handling
void BSAFile::fail(const string &msg)
void BSAFile::fail(const std::string &msg)
{
throw std::runtime_error("BSA Error: " + msg + "\nArchive: " + mFilename);
}
@ -160,7 +159,7 @@ int BSAFile::getIndex(const char *str) const
}
/// Open an archive file.
void BSAFile::open(const string &file)
void BSAFile::open(const std::string &file)
{
mFilename = file;
readHeader();
@ -171,7 +170,7 @@ Files::IStreamPtr BSAFile::getFile(const char *file)
assert(file);
int i = getIndex(file);
if(i == -1)
fail("File not found: " + string(file));
fail("File not found: " + std::string(file));
const FileStruct &fs = mFiles[i];

@ -235,6 +235,8 @@ namespace DetourNavigator
const osg::Vec3f& end, const Flags includeFlags) const;
virtual RecastMeshTiles getRecastMeshTiles() = 0;
virtual float getMaxNavmeshAreaRealRadius() const = 0;
};
}

@ -213,4 +213,10 @@ namespace DetourNavigator
++it;
}
}
float NavigatorImpl::getMaxNavmeshAreaRealRadius() const
{
const auto& settings = getSettings();
return getRealTileSize(settings) * getMaxNavmeshAreaRadius(settings);
}
}

@ -60,6 +60,8 @@ namespace DetourNavigator
RecastMeshTiles getRecastMeshTiles() override;
float getMaxNavmeshAreaRealRadius() const override;
private:
Settings mSettings;
NavMeshManager mNavMeshManager;

@ -92,6 +92,11 @@ namespace DetourNavigator
return {};
}
float getMaxNavmeshAreaRealRadius() const override
{
return std::numeric_limits<float>::max();
}
private:
Settings mDefaultSettings {};
SharedNavMeshCacheItem mEmptyNavMeshCacheItem;

@ -1,4 +1,4 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SETTINGSUTILS_H
#include "settings.hpp"
@ -89,6 +89,16 @@ namespace DetourNavigator
transform.getOrigin() + btVector3(0, 0, getSwimLevel(settings, agentHalfExtentsZ) - agentHalfExtentsZ)
);
}
inline float getRealTileSize(const Settings& settings)
{
return settings.mTileSize * settings.mCellSize / settings.mRecastScaleFactor;
}
inline float getMaxNavmeshAreaRadius(const Settings& settings)
{
return std::floor(std::sqrt(settings.mMaxTilesNumber / osg::PI)) - 1;
}
}
#endif

@ -357,16 +357,14 @@ std::string ESMReader::getString(int size)
void ESMReader::fail(const std::string &msg)
{
using namespace std;
stringstream ss;
std::stringstream ss;
ss << "ESM Error: " << msg;
ss << "\n File: " << mCtx.filename;
ss << "\n Record: " << mCtx.recName.toString();
ss << "\n Subrecord: " << mCtx.subName.toString();
if (mEsm.get())
ss << "\n Offset: 0x" << hex << mEsm->tellg();
ss << "\n Offset: 0x" << std::hex << mEsm->tellg();
throw std::runtime_error(ss.str());
}

@ -0,0 +1,42 @@
#ifndef OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H
#define OPENMW_COMPONENTS_MISC_BUDGETMEASUREMENT_H
namespace Misc
{
class BudgetMeasurement
{
std::array<float, 4> mBudgetHistory;
std::array<unsigned int, 4> mBudgetStepCount;
public:
BudgetMeasurement(const float default_expense)
{
mBudgetHistory = {default_expense, default_expense, default_expense, default_expense};
mBudgetStepCount = {1, 1, 1, 1};
}
void reset(const float default_expense)
{
mBudgetHistory = {default_expense, default_expense, default_expense, default_expense};
mBudgetStepCount = {1, 1, 1, 1};
}
void update(double delta, unsigned int stepCount, size_t cursor)
{
mBudgetHistory[cursor%4] = delta;
mBudgetStepCount[cursor%4] = stepCount;
}
double get() const
{
float sum = (mBudgetHistory[0] + mBudgetHistory[1] + mBudgetHistory[2] + mBudgetHistory[3]);
unsigned int stepCountSum = (mBudgetStepCount[0] + mBudgetStepCount[1] + mBudgetStepCount[2] + mBudgetStepCount[3]);
return sum/float(stepCountSum);
}
};
}
#endif

@ -33,6 +33,9 @@ const std::string NightDayLabel = "NightDaySwitch";
// A label to mark visual switches for herbalism feature
const std::string HerbalismLabel = "HerbalismSwitch";
// Percentage height at which projectiles are spawned from an actor
const float TorsoHeight = 0.75f;
}
#endif

@ -47,4 +47,9 @@ namespace Misc
{
return static_cast<unsigned int>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
}
float Rng::deviate(float mean, float deviation, Seed& seed)
{
return std::uniform_real_distribution<float>(mean - deviation, mean + deviation)(seed.mGenerator);
}
}

@ -42,6 +42,8 @@ public:
/// returns default seed for RNG
static unsigned int generateDefaultSeed();
static float deviate(float mean, float deviation, Seed& seed = getSeed());
};
}

@ -0,0 +1,42 @@
#ifndef OPENMW_COMPONENTS_MISC_TIMER_H
#define OPENMW_COMPONENTS_MISC_TIMER_H
#include "rng.hpp"
namespace Misc
{
enum class TimerStatus
{
Waiting,
Elapsed,
};
class DeviatingPeriodicTimer
{
public:
explicit DeviatingPeriodicTimer(float period, float deviation, float timeLeft)
: mPeriod(period), mDeviation(deviation), mTimeLeft(timeLeft)
{}
TimerStatus update(float duration)
{
if (mTimeLeft > 0)
{
mTimeLeft -= duration;
return TimerStatus::Waiting;
}
mTimeLeft = Rng::deviate(mPeriod, mDeviation);
return TimerStatus::Elapsed;
}
void reset(float timeLeft) { mTimeLeft = timeLeft; }
private:
const float mPeriod;
const float mDeviation;
float mTimeLeft;
};
}
#endif

@ -2,11 +2,15 @@
#include <iomanip>
#include <components/debug/debuglog.hpp>
namespace osgMyGUI
{
void CustomLogListener::open()
{
mStream.open(boost::filesystem::path(mFileName), std::ios_base::out);
if (!mStream.is_open())
Log(Debug::Error) << "Unable to create MyGUI log with path " << mFileName;
}
void CustomLogListener::close()

@ -133,6 +133,8 @@ static std::map<std::string,RecordFactoryEntry> makeFactory()
factory["BSShaderTextureSet"] = {&construct <BSShaderTextureSet> , RC_BSShaderTextureSet };
factory["BSLODTriShape"] = {&construct <BSLODTriShape> , RC_BSLODTriShape };
factory["BSShaderProperty"] = {&construct <BSShaderProperty> , RC_BSShaderProperty };
factory["BSShaderPPLightingProperty"] = {&construct <BSShaderPPLightingProperty> , RC_BSShaderPPLightingProperty };
factory["BSShaderNoLightingProperty"] = {&construct <BSShaderNoLightingProperty> , RC_BSShaderNoLightingProperty };
return factory;
}

@ -77,7 +77,7 @@ struct KeyMapT {
mInterpolationType = nif->getUInt();
KeyT<T> key;
KeyType key = {};
NIFStream &nifReference = *nif;
if (mInterpolationType == InterpolationType_Linear

@ -118,6 +118,34 @@ void BSShaderLightingProperty::read(NIFStream *nif)
clamp = nif->getUInt();
}
void BSShaderPPLightingProperty::read(NIFStream *nif)
{
BSShaderLightingProperty::read(nif);
textureSet.read(nif);
if (nif->getBethVersion() <= 14)
return;
refraction.strength = nif->getFloat();
refraction.period = nif->getInt();
if (nif->getBethVersion() <= 24)
return;
parallax.passes = nif->getFloat();
parallax.scale = nif->getFloat();
}
void BSShaderPPLightingProperty::post(NIFFile *nif)
{
BSShaderLightingProperty::post(nif);
textureSet.post(nif);
}
void BSShaderNoLightingProperty::read(NIFStream *nif)
{
BSShaderLightingProperty::read(nif);
filename = nif->getSizedString();
if (nif->getBethVersion() >= 27)
falloffParams = nif->getVector4();
}
void NiFogProperty::read(NIFStream *nif)
{
Property::read(nif);
@ -137,8 +165,8 @@ void S_MaterialProperty::read(NIFStream *nif)
emissive = nif->getVector3();
glossiness = nif->getFloat();
alpha = nif->getFloat();
if (nif->getBethVersion() > 21)
emissive *= nif->getFloat();
if (nif->getBethVersion() >= 22)
emissiveMult = nif->getFloat();
}
void S_VertexColorProperty::read(NIFStream *nif)

@ -118,6 +118,18 @@ struct NiShadeProperty : public Property
struct BSShaderProperty : public NiShadeProperty
{
enum BSShaderType
{
SHADER_TALL_GRASS = 0,
SHADER_DEFAULT = 1,
SHADER_SKY = 10,
SHADER_SKIN = 14,
SHADER_WATER = 17,
SHADER_LIGHTING30 = 29,
SHADER_TILE = 32,
SHADER_NOLIGHTING = 33
};
unsigned int type{0u}, flags1{0u}, flags2{0u};
float envMapIntensity{0.f};
void read(NIFStream *nif) override;
@ -129,6 +141,34 @@ struct BSShaderLightingProperty : public BSShaderProperty
void read(NIFStream *nif) override;
};
struct BSShaderPPLightingProperty : public BSShaderLightingProperty
{
BSShaderTextureSetPtr textureSet;
struct RefractionSettings
{
float strength{0.f};
int period{0};
};
struct ParallaxSettings
{
float passes{0.f};
float scale{0.f};
};
RefractionSettings refraction;
ParallaxSettings parallax;
void read(NIFStream *nif) override;
void post(NIFFile *nif) override;
};
struct BSShaderNoLightingProperty : public BSShaderLightingProperty
{
std::string filename;
osg::Vec4f falloffParams;
void read(NIFStream *nif) override;
};
struct NiDitherProperty : public Property
{
unsigned short flags;
@ -193,7 +233,7 @@ struct S_MaterialProperty
// The vector components are R,G,B
osg::Vec3f ambient{1.f,1.f,1.f}, diffuse{1.f,1.f,1.f};
osg::Vec3f specular, emissive;
float glossiness{0.f}, alpha{0.f};
float glossiness{0.f}, alpha{0.f}, emissiveMult{1.f};
void read(NIFStream *nif);
};

@ -122,7 +122,9 @@ enum RecordType
RC_NiColorInterpolator,
RC_BSShaderTextureSet,
RC_BSLODTriShape,
RC_BSShaderProperty
RC_BSShaderProperty,
RC_BSShaderPPLightingProperty,
RC_BSShaderNoLightingProperty
};
/// Base class for all records

@ -1672,6 +1672,85 @@ namespace NifOsg
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
}
void handleTextureSet(const Nif::BSShaderTextureSet* textureSet, unsigned int clamp, const std::string& nodeName, osg::StateSet* stateset, Resource::ImageManager* imageManager, std::vector<unsigned int>& boundTextures)
{
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
const unsigned int uvSet = 0;
for (size_t i = 0; i < textureSet->textures.size(); ++i)
{
if (textureSet->textures[i].empty())
continue;
switch(i)
{
case Nif::BSShaderTextureSet::TextureType_Base:
case Nif::BSShaderTextureSet::TextureType_Normal:
case Nif::BSShaderTextureSet::TextureType_Glow:
break;
default:
{
Log(Debug::Info) << "Unhandled texture stage " << i << " on shape \"" << nodeName << "\" in " << mFilename;
continue;
}
}
std::string filename = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
if (image)
texture2d->setTextureSize(image->s(), image->t());
bool wrapT = clamp & 0x1;
bool wrapS = (clamp >> 1) & 0x1;
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
unsigned int texUnit = boundTextures.size();
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
// BSShaderTextureSet presence means there's no need for FFP support for the affected node
switch (i)
{
case Nif::BSShaderTextureSet::TextureType_Base:
texture2d->setName("diffuseMap");
break;
case Nif::BSShaderTextureSet::TextureType_Normal:
texture2d->setName("normalMap");
break;
case Nif::BSShaderTextureSet::TextureType_Glow:
texture2d->setName("emissiveMap");
break;
}
boundTextures.emplace_back(uvSet);
}
}
const std::string& getNVShaderPrefix(unsigned int type) const
{
static const std::map<unsigned int, std::string> mapping =
{
{Nif::BSShaderProperty::SHADER_TALL_GRASS, std::string()},
{Nif::BSShaderProperty::SHADER_DEFAULT, "nv_default"},
{Nif::BSShaderProperty::SHADER_SKY, std::string()},
{Nif::BSShaderProperty::SHADER_SKIN, std::string()},
{Nif::BSShaderProperty::SHADER_WATER, std::string()},
{Nif::BSShaderProperty::SHADER_LIGHTING30, std::string()},
{Nif::BSShaderProperty::SHADER_TILE, std::string()},
{Nif::BSShaderProperty::SHADER_NOLIGHTING, "nv_nolighting"},
};
auto prefix = mapping.find(type);
if (prefix == mapping.end())
Log(Debug::Warning) << "Unknown shader type " << type << " in " << mFilename;
else if (prefix->second.empty())
Log(Debug::Warning) << "Unhandled shader type " << type << " in " << mFilename;
else
return prefix->second;
return mapping.at(Nif::BSShaderProperty::SHADER_DEFAULT);
}
void handleProperty(const Nif::Property *property,
osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector<unsigned int>& boundTextures, int animflags)
{
@ -1725,11 +1804,16 @@ namespace NifOsg
case Nif::RC_NiZBufferProperty:
{
const Nif::NiZBufferProperty* zprop = static_cast<const Nif::NiZBufferProperty*>(property);
// VER_MW doesn't support a DepthFunction according to NifSkope
osg::StateSet* stateset = node->getOrCreateStateSet();
// Depth test flag
stateset->setMode(GL_DEPTH_TEST, zprop->flags&1 ? osg::StateAttribute::ON
: osg::StateAttribute::OFF);
osg::ref_ptr<osg::Depth> depth = new osg::Depth;
// Depth write flag
depth->setWriteMask((zprop->flags>>1)&1);
// Morrowind ignores depth test function
depth = shareAttribute(depth);
node->getOrCreateStateSet()->setAttributeAndModes(depth, osg::StateAttribute::ON);
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON);
break;
}
// OSG groups the material properties that NIFs have separate, so we have to parse them all again when one changed
@ -1752,6 +1836,63 @@ namespace NifOsg
handleTextureProperty(texprop, node->getName(), stateset, composite, imageManager, boundTextures, animflags);
break;
}
case Nif::RC_BSShaderPPLightingProperty:
{
auto texprop = static_cast<const Nif::BSShaderPPLightingProperty*>(property);
bool shaderRequired = true;
node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type));
node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet();
if (!texprop->textureSet.empty())
{
auto textureSet = texprop->textureSet.getPtr();
handleTextureSet(textureSet, texprop->clamp, node->getName(), stateset, imageManager, boundTextures);
}
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
break;
}
case Nif::RC_BSShaderNoLightingProperty:
{
auto texprop = static_cast<const Nif::BSShaderNoLightingProperty*>(property);
bool shaderRequired = true;
node->setUserValue("shaderPrefix", getNVShaderPrefix(texprop->type));
node->setUserValue("shaderRequired", shaderRequired);
osg::StateSet* stateset = node->getOrCreateStateSet();
if (!texprop->filename.empty())
{
if (!boundTextures.empty())
{
for (unsigned int i = 0; i < boundTextures.size(); ++i)
stateset->setTextureMode(i, GL_TEXTURE_2D, osg::StateAttribute::OFF);
boundTextures.clear();
}
std::string filename = Misc::ResourceHelpers::correctTexturePath(texprop->filename, imageManager->getVFS());
osg::ref_ptr<osg::Image> image = imageManager->getImage(filename);
osg::ref_ptr<osg::Texture2D> texture2d = new osg::Texture2D(image);
texture2d->setName("diffuseMap");
if (image)
texture2d->setTextureSize(image->s(), image->t());
bool wrapT = texprop->clamp & 0x1;
bool wrapS = (texprop->clamp >> 1) & 0x1;
texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE);
const unsigned int texUnit = 0;
const unsigned int uvSet = 0;
stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON);
boundTextures.push_back(uvSet);
}
if (mBethVersion >= 27)
{
stateset->addUniform(new osg::Uniform("useFalloff", true));
stateset->addUniform(new osg::Uniform("falloffParams", texprop->falloffParams));
}
else
{
stateset->addUniform(new osg::Uniform("useFalloff", false));
}
handleTextureControllers(texprop, composite, imageManager, stateset, animflags);
break;
}
// unused by mw
case Nif::RC_NiShadeProperty:
case Nif::RC_NiDitherProperty:
@ -1804,6 +1945,7 @@ namespace NifOsg
bool hasMatCtrl = false;
int lightmode = 1;
float emissiveMult = 1.f;
for (const Nif::Property* property : properties)
{
@ -1823,6 +1965,7 @@ namespace NifOsg
mat->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.diffuse, matprop->data.alpha));
mat->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.ambient, 1.f));
mat->setEmission(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.emissive, 1.f));
emissiveMult = matprop->data.emissiveMult;
mat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(matprop->data.specular, 1.f));
mat->setShininess(osg::Material::FRONT_AND_BACK, matprop->data.glossiness);
@ -1943,6 +2086,7 @@ namespace NifOsg
mat = shareAttribute(mat);
stateset->setAttributeAndModes(mat, osg::StateAttribute::ON);
stateset->addUniform(new osg::Uniform("emissiveMult", emissiveMult));
}
};

@ -785,7 +785,7 @@ namespace Resource
Shader::ShaderVisitor *SceneManager::createShaderVisitor(const std::string& shaderPrefix, bool translucentFramebuffer)
{
Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix+"_vertex.glsl", shaderPrefix+"_fragment.glsl");
Shader::ShaderVisitor* shaderVisitor = new Shader::ShaderVisitor(*mShaderManager.get(), *mImageManager, shaderPrefix);
shaderVisitor->setForceShaders(mForceShaders);
shaderVisitor->setAutoUseNormalMaps(mAutoUseNormalMaps);
shaderVisitor->setNormalMapPattern(mNormalMapPattern);

@ -6,6 +6,7 @@
#include <osg/Material>
#include <osg/Multisample>
#include <osg/Texture>
#include <osg/ValueObject>
#include <osgUtil/TangentSpaceGenerator>
@ -42,7 +43,7 @@ namespace Shader
}
ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultVsTemplate, const std::string &defaultFsTemplate)
ShaderVisitor::ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string &defaultShaderPrefix)
: osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
, mForceShaders(false)
, mAllowedToModifyStateSets(true)
@ -52,8 +53,7 @@ namespace Shader
, mTranslucentFramebuffer(false)
, mShaderManager(shaderManager)
, mImageManager(imageManager)
, mDefaultVsTemplate(defaultVsTemplate)
, mDefaultFsTemplate(defaultFsTemplate)
, mDefaultShaderPrefix(defaultShaderPrefix)
{
mRequirements.emplace_back();
}
@ -129,6 +129,10 @@ namespace Shader
if (mAllowedToModifyStateSets)
writableStateSet = node.getStateSet();
const osg::StateSet::TextureAttributeList& texAttributes = stateset->getTextureAttributeList();
bool shaderRequired = false;
if (node.getUserValue("shaderRequired", shaderRequired) && shaderRequired)
mRequirements.back().mShaderRequired = true;
if (!texAttributes.empty())
{
const osg::Texture* diffuseMap = nullptr;
@ -440,8 +444,12 @@ namespace Shader
defineMap["translucentFramebuffer"] = mTranslucentFramebuffer ? "1" : "0";
osg::ref_ptr<osg::Shader> vertexShader (mShaderManager.getShader(mDefaultVsTemplate, defineMap, osg::Shader::VERTEX));
osg::ref_ptr<osg::Shader> fragmentShader (mShaderManager.getShader(mDefaultFsTemplate, defineMap, osg::Shader::FRAGMENT));
std::string shaderPrefix;
if (!node.getUserValue("shaderPrefix", shaderPrefix))
shaderPrefix = mDefaultShaderPrefix;
osg::ref_ptr<osg::Shader> vertexShader (mShaderManager.getShader(shaderPrefix + "_vertex.glsl", defineMap, osg::Shader::VERTEX));
osg::ref_ptr<osg::Shader> fragmentShader (mShaderManager.getShader(shaderPrefix + "_fragment.glsl", defineMap, osg::Shader::FRAGMENT));
if (vertexShader && fragmentShader)
{

@ -17,7 +17,7 @@ namespace Shader
class ShaderVisitor : public osg::NodeVisitor
{
public:
ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultVsTemplate, const std::string& defaultFsTemplate);
ShaderVisitor(ShaderManager& shaderManager, Resource::ImageManager& imageManager, const std::string& defaultShaderPrefix);
/// By default, only bump mapped objects will have a shader added to them.
/// Setting force = true will cause all objects to render using shaders, regardless of having a bump map.
@ -104,8 +104,7 @@ namespace Shader
};
std::vector<ShaderRequirements> mRequirements;
std::string mDefaultVsTemplate;
std::string mDefaultFsTemplate;
std::string mDefaultShaderPrefix;
void createProgram(const ShaderRequirements& reqs);
void ensureFFP(osg::Node& node);

@ -1,20 +1,19 @@
// This program generates the file tables_gen.hpp
#include <iostream>
using namespace std;
#include <iconv.h>
#include <cassert>
void tab() { cout << " "; }
void tab() { std::cout << " "; }
// write one number with a space in front of it and a comma after it
void num(char i, bool last)
{
// Convert i to its integer value, i.e. -128 to 127. Printing it directly
// would result in non-printable characters in the source code, which is bad.
cout << " " << static_cast<int>(i);
if(!last) cout << ",";
std::cout << " " << static_cast<int>(i);
if(!last) std::cout << ",";
}
// Write one table entry (UTF8 value), 1-5 bytes
@ -27,9 +26,9 @@ void writeChar(char *value, int length, bool last, const std::string &comment=""
num(value[i], last && i==4);
if(comment != "")
cout << " // " << comment;
std::cout << " // " << comment;
cout << endl;
std::cout << std::endl;
}
// What to write on missing characters
@ -46,7 +45,7 @@ void writeMissing(bool last)
int write_table(const std::string &charset, const std::string &tableName)
{
// Write table header
cout << "static signed char " << tableName << "[] =\n{\n";
std::cout << "static signed char " << tableName << "[] =\n{\n";
// Open conversion system
iconv_t cd = iconv_open ("UTF-8", charset.c_str());
@ -74,7 +73,7 @@ int write_table(const std::string &charset, const std::string &tableName)
iconv_close (cd);
// Finish table
cout << "};\n";
std::cout << "};\n";
return 0;
}
@ -82,37 +81,37 @@ int write_table(const std::string &charset, const std::string &tableName)
int main()
{
// Write header guard
cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n";
std::cout << "#ifndef COMPONENTS_TOUTF8_TABLE_GEN_H\n#define COMPONENTS_TOUTF8_TABLE_GEN_H\n\n";
// Write namespace
cout << "namespace ToUTF8\n{\n\n";
std::cout << "namespace ToUTF8\n{\n\n";
// Central European and Eastern European languages that use Latin script, such as
// Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian.
cout << "\n/// Central European and Eastern European languages that use Latin script,"
"\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian,"
"\n/// Serbian (Latin script), Romanian and Albanian."
"\n";
std::cout << "\n/// Central European and Eastern European languages that use Latin script,"
"\n/// such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian,"
"\n/// Serbian (Latin script), Romanian and Albanian."
"\n";
write_table("WINDOWS-1250", "windows_1250");
// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages
cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic"
"\n/// and other languages"
"\n";
std::cout << "\n/// Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic"
"\n/// and other languages"
"\n";
write_table("WINDOWS-1251", "windows_1251");
// English
cout << "\n/// Latin alphabet used by English and some other Western languages"
"\n";
std::cout << "\n/// Latin alphabet used by English and some other Western languages"
"\n";
write_table("WINDOWS-1252", "windows_1252");
write_table("CP437", "cp437");
// Close namespace
cout << "\n}\n\n";
std::cout << "\n}\n\n";
// Close header guard
cout << "#endif\n\n";
std::cout << "#endif\n\n";
return 0;
}

@ -11,6 +11,7 @@ Install
#. Locate the plugin files, ``.esp`` or ``.omwaddon``, or possibly ``.esm``. The folder containing the plugin files we will call your *data folder*
#. Check that all resource folders (``Meshes``, ``Textures``, etc.) containing additional resource files (the actual meshes, textures, etc.) are in the *data folder*.
#. Note that not all mods have a plugin, and not all mods have resources, but they must at minimum have one or the other.
.. note::
There may be multiple levels of folders, but the location of the plugins must be the same as the resource folders.

@ -305,7 +305,7 @@ tile size
:Type: integer
:Range: > 0
:Default: 64
:Default: 128
The width and height of each tile.

@ -31,6 +31,15 @@ if(NOT OPENMW_USE_SYSTEM_BULLET)
set(USE_DOUBLE_PRECISION ${BULLET_USE_DOUBLES} CACHE BOOL "")
set(BULLET2_MULTITHREADING ON CACHE BOOL "")
if(BULLET_STATIC)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
else()
set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
if(MSVC)
set(USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE)
endif()
endif()
# master on 12 Mar 2021
include(FetchContent)
FetchContent_Declare(bullet
@ -61,15 +70,16 @@ if(NOT OPENMW_USE_SYSTEM_MYGUI)
set(MYGUI_DONT_USE_OBSOLETE OFF CACHE BOOL "")
if(MYGUI_STATIC)
set(BUILD_SHARED_LIBS OFF)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
else()
set(BUILD_SHARED_LIBS ON)
set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
endif()
# master on 13 Mar 2021
include(FetchContent)
FetchContent_Declare(mygui
URL https://github.com/MyGUI/mygui/archive/MyGUI3.4.1.zip
URL_HASH MD5=952d4033854612c99a5d9bf4b8550c26
URL https://github.com/MyGUI/mygui/archive/59c1388b942721887d18743ada15f1906ff11a1f.zip
URL_HASH MD5=0a64c9cccc8f96dc8c08172175e68e1c
SOURCE_DIR fetched/mygui
)
FetchContent_MakeAvailableExcludeFromAll(mygui)
@ -81,8 +91,6 @@ endif()
if(NOT OPENMW_USE_SYSTEM_OSG)
cmake_minimum_required(VERSION 3.11) # for FetchContent
set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "")
set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "")
set(BUILD_OSG_APPLICATIONS OFF CACHE BOOL "")
set(BUILD_OSG_DEPRECATED_SERIALIZERS OFF CACHE BOOL "")
set(OSG_FIND_3RD_PARTY_DEPS OFF CACHE BOOL "")
@ -104,9 +112,33 @@ if(NOT OPENMW_USE_SYSTEM_OSG)
set(OPENGL_PROFILE "GL2" CACHE STRING "")
if(OSG_STATIC)
set(BUILD_SHARED_LIBS OFF)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(DYNAMIC_OPENTHREADS OFF CACHE BOOL "" FORCE)
set(DYNAMIC_OPENSCENEGRAPH OFF CACHE BOOL "" FORCE)
else()
set(BUILD_SHARED_LIBS ON)
set(BUILD_SHARED_LIBS ON CACHE BOOL "" FORCE)
set(DYNAMIC_OPENTHREADS ON CACHE BOOL "" FORCE)
set(DYNAMIC_OPENSCENEGRAPH ON CACHE BOOL "" FORCE)
endif()
mark_as_advanced(DYNAMIC_OPENTHREADS DYNAMIC_OPENSCENEGRAPH)
if(WIN32)
# OSG here inherits C++17 language level because it doesn't specify its own.
#
# OSG's `using namespace std` interferes with Windows header files.
#
# See https://developercommunity.visualstudio.com/t/error-c2872-byte-ambiguous-symbol/93889
#
# An alternative way to work around this without changing the language level is:
#
# add_compile_definitions(_HAS_STD_BYTE=0)
#
# TODO: Put OSG into its own scope so that this does not leak into Recast below.
set(CMAKE_CXX_STANDARD 11)
if(MSVC)
set(OSG_MSVC_VERSIONED_DLL OFF CACHE BOOL "")
endif()
endif()
# branch OpenSceneGraph-3.6 on 23 Jan 2021.

@ -302,7 +302,6 @@
</Widget>
<Widget type="TextBox" skin="SandText" position="182 94 300 32" align="Left Top">
<Property key="MultiLine" value="true"/>
<Property key="Caption" value="Hint: press F3 to show \nthe current frame rate."/>
</Widget>

@ -193,7 +193,6 @@
<Property key="Shrink" value="true"/>
<Property key="WordWrap" value="true"/>
<Property key="TextAlign" value="Left Top"/>
<Property key="Spacing" value="28"/>
<UserString key="HStretch" value="true"/>
</Widget>
@ -248,7 +247,6 @@
<Property key="MultiLine" value="true"/>
<Property key="WordWrap" value="true"/>
<Property key="TextAlign" value="Left Top"/>
<Property key="Spacing" value="28"/>
</Widget>
</Widget>
@ -269,9 +267,6 @@
</Widget>
<Widget type="AutoSizedTextBox" skin="SandText" position="0 0 0 0" align="Left Top" name="LevelDetailText">
<Property key="AutoResize" value="true"/>
<Property key="MultiLine" value="true"/>
<Property key="Shrink" value="true"/>
<Property key="TextAlign" value="HCenter Top"/>
</Widget>
</Widget>

@ -26,6 +26,10 @@ set(SHADER_FILES
shadowcasting_vertex.glsl
shadowcasting_fragment.glsl
vertexcolors.glsl
nv_default_vertex.glsl
nv_default_fragment.glsl
nv_nolighting_vertex.glsl
nv_nolighting_fragment.glsl
)
copy_all_resource_files(${CMAKE_CURRENT_SOURCE_DIR} ${OPENMW_SHADERS_ROOT} ${DDIRRELATIVE} "${SHADER_FILES}")

@ -0,0 +1,106 @@
#version 120
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
#if @diffuseMap
uniform sampler2D diffuseMap;
varying vec2 diffuseMapUV;
#endif
#if @emissiveMap
uniform sampler2D emissiveMap;
varying vec2 emissiveMapUV;
#endif
#if @normalMap
uniform sampler2D normalMap;
varying vec2 normalMapUV;
varying vec4 passTangent;
#endif
uniform bool noAlpha;
varying float euclideanDepth;
varying float linearDepth;
#define PER_PIXEL_LIGHTING 1
varying vec3 passViewPos;
varying vec3 passNormal;
#include "vertexcolors.glsl"
#include "shadows_fragment.glsl"
#include "lighting.glsl"
#include "alpha.glsl"
uniform float emissiveMult;
void main()
{
#if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, adjustedDiffuseUV);
#else
gl_FragData[0] = vec4(1.0);
#endif
vec4 diffuseColor = getDiffuseColor();
gl_FragData[0].a *= diffuseColor.a;
alphaTest();
#if @normalMap
vec4 normalTex = texture2D(normalMap, normalMapUV);
// Must flip Y for DirectX format normal maps
normalTex.y = 1.0 - normalTex.y;
vec3 normalizedNormal = normalize(passNormal);
vec3 normalizedTangent = normalize(passTangent.xyz);
vec3 binormal = cross(normalizedTangent, normalizedNormal) * passTangent.w;
mat3 tbnTranspose = mat3(normalizedTangent, binormal, normalizedNormal);
vec3 viewNormal = gl_NormalMatrix * normalize(tbnTranspose * (normalTex.xyz * 2.0 - 1.0));
#else
vec3 viewNormal = gl_NormalMatrix * normalize(passNormal);
#endif
float shadowing = unshadowedLightRatio(linearDepth);
vec3 diffuseLight, ambientLight;
doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight);
vec3 emission = getEmissionColor().xyz * emissiveMult;
#if @emissiveMap
emission *= texture2D(emissiveMap, emissiveMapUV).xyz;
#endif
vec3 lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission;
#if @clamp
lighting = clamp(lighting, vec3(0.0), vec3(1.0));
#else
lighting = max(lighting, 0.0);
#endif
gl_FragData[0].xyz *= lighting;
float shininess = gl_FrontMaterial.shininess;
vec3 matSpec = getSpecularColor().xyz;
#if @normalMap
matSpec *= normalTex.a;
#endif
if (matSpec != vec3(0.0))
gl_FragData[0].xyz += getSpecular(normalize(viewNormal), normalize(passViewPos.xyz), shininess, matSpec) * shadowing;
#if @radialFog
float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#else
float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#endif
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
#if @translucentFramebuffer
if (noAlpha)
gl_FragData[0].a = 1.0;
#endif
applyShadowDebugOverlay();
}

@ -0,0 +1,58 @@
#version 120
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif
#if @emissiveMap
varying vec2 emissiveMapUV;
#endif
#if @normalMap
varying vec2 normalMapUV;
varying vec4 passTangent;
#endif
varying float euclideanDepth;
varying float linearDepth;
varying vec3 passViewPos;
varying vec3 passNormal;
#define PER_PIXEL_LIGHTING 1
#include "vertexcolors.glsl"
#include "shadows_vertex.glsl"
#include "lighting.glsl"
void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex);
gl_ClipVertex = viewPos;
euclideanDepth = length(viewPos.xyz);
linearDepth = gl_Position.z;
#if @diffuseMap
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
#endif
#if @emissiveMap
emissiveMapUV = (gl_TextureMatrix[@emissiveMapUV] * gl_MultiTexCoord@emissiveMapUV).xy;
#endif
#if @normalMap
normalMapUV = (gl_TextureMatrix[@normalMapUV] * gl_MultiTexCoord@normalMapUV).xy;
passTangent = gl_MultiTexCoord7.xyzw;
#endif
passColor = gl_Color;
passViewPos = viewPos.xyz;
passNormal = gl_Normal.xyz;
#if @shadows_enabled
vec3 viewNormal = normalize((gl_NormalMatrix * gl_Normal).xyz);
setupShadowCoords(viewPos, viewNormal);
#endif
}

@ -0,0 +1,55 @@
#version 120
#if @useGPUShader4
#extension GL_EXT_gpu_shader4: require
#endif
#if @diffuseMap
uniform sampler2D diffuseMap;
varying vec2 diffuseMapUV;
#endif
uniform bool noAlpha;
#if @radialFog
varying float euclideanDepth;
#else
varying float linearDepth;
#endif
uniform bool useFalloff;
varying float passFalloff;
#include "vertexcolors.glsl"
#include "alpha.glsl"
void main()
{
#if @diffuseMap
gl_FragData[0] = texture2D(diffuseMap, diffuseMapUV);
gl_FragData[0].a *= coveragePreservingAlphaScale(diffuseMap, diffuseMapUV);
#else
gl_FragData[0] = vec4(1.0);
#endif
gl_FragData[0] *= getDiffuseColor();
if (useFalloff)
gl_FragData[0].a *= passFalloff;
alphaTest();
#if @radialFog
float fogValue = clamp((euclideanDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#else
float fogValue = clamp((linearDepth - gl_Fog.start) * gl_Fog.scale, 0.0, 1.0);
#endif
#if @translucentFramebuffer
if (noAlpha)
gl_FragData[0].a = 1.0;
#endif
gl_FragData[0].xyz = mix(gl_FragData[0].xyz, gl_Fog.color.xyz, fogValue);
}

@ -0,0 +1,49 @@
#version 120
#if @diffuseMap
varying vec2 diffuseMapUV;
#endif
#if @radialFog
varying float euclideanDepth;
#else
varying float linearDepth;
#endif
uniform bool useFalloff;
uniform vec4 falloffParams;
varying vec3 passViewPos;
varying float passFalloff;
#include "vertexcolors.glsl"
void main(void)
{
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
vec4 viewPos = (gl_ModelViewMatrix * gl_Vertex);
gl_ClipVertex = viewPos;
#if @radialFog
euclideanDepth = length(viewPos.xyz);
#else
linearDepth = gl_Position.z;
#endif
#if @diffuseMap
diffuseMapUV = (gl_TextureMatrix[@diffuseMapUV] * gl_MultiTexCoord@diffuseMapUV).xy;
#endif
passColor = gl_Color;
if (useFalloff)
{
vec3 viewNormal = gl_NormalMatrix * normalize(gl_Normal.xyz);
vec3 viewDir = normalize(viewPos.xyz);
float viewAngle = abs(dot(viewNormal, viewDir));
passFalloff = smoothstep(falloffParams.y, falloffParams.x, viewAngle);
}
else
{
passFalloff = 1.0;
}
}

@ -68,6 +68,8 @@ varying float linearDepth;
#if !PER_PIXEL_LIGHTING
centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting;
#else
uniform float emissiveMult;
#endif
varying vec3 passViewPos;
varying vec3 passNormal;
@ -172,7 +174,8 @@ void main()
#else
vec3 diffuseLight, ambientLight;
doLighting(passViewPos, normalize(viewNormal), shadowing, diffuseLight, ambientLight);
lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz;
vec3 emission = getEmissionColor().xyz * emissiveMult;
lighting = diffuseColor.xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission;
#endif
#if @clamp

@ -53,6 +53,7 @@ varying float linearDepth;
#if !PER_PIXEL_LIGHTING
centroid varying vec3 passLighting;
centroid varying vec3 shadowDiffuseLighting;
uniform float emissiveMult;
#endif
varying vec3 passViewPos;
varying vec3 passNormal;
@ -121,7 +122,8 @@ void main(void)
#if !PER_PIXEL_LIGHTING
vec3 diffuseLight, ambientLight;
doLighting(viewPos.xyz, viewNormal, diffuseLight, ambientLight, shadowDiffuseLighting);
passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + getEmissionColor().xyz;
vec3 emission = getEmissionColor().xyz * emissiveMult;
passLighting = getDiffuseColor().xyz * diffuseLight + getAmbientColor().xyz * ambientLight + emission;
shadowDiffuseLighting *= getDiffuseColor().xyz;
#endif

@ -37,20 +37,21 @@ const float BUMP_RAIN = 2.5;
const float REFL_BUMP = 0.10; // reflection distortion amount
const float REFR_BUMP = 0.07; // refraction distortion amount
const float SCATTER_AMOUNT = 0.3; // amount of sunlight scattering
const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering
const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction
const float SPEC_HARDNESS = 256.0; // specular highlights hardness
const float BUMP_SUPPRESS_DEPTH = 300.0; // at what water depth bumpmap will be suppressed for reflections and refractions (prevents artifacts at shores)
const float BUMP_SUPPRESS_DEPTH_SS = 1000.0; // modifier using screenspace depth (helps prevent same artifacts but at higher distances)
const vec2 WIND_DIR = vec2(0.5f, -0.8f);
const float WIND_SPEED = 0.2f;
const vec3 WATER_COLOR = vec3(0.090195, 0.115685, 0.12745);
const float SCATTER_AMOUNT = 0.5; // amount of sunlight scattering
const vec3 SCATTER_COLOUR = WATER_COLOR * 8.0; // colour of sunlight scattering
// ---------------- rain ripples related stuff ---------------------
const float RAIN_RIPPLE_GAPS = 5.0;
@ -227,7 +228,10 @@ void main(void)
float depthSampleDistorted = linearizeDepth(texture2D(refractionDepthMap,screenCoords-screenCoordsOffset).x) * radialise;
float surfaceDepth = linearizeDepth(gl_FragCoord.z) * radialise;
float realWaterDepth = depthSample - surfaceDepth; // undistorted water depth in view direction, independent of frustum
screenCoordsOffset *= clamp(realWaterDepth / BUMP_SUPPRESS_DEPTH,0,1);
screenCoordsOffset *= clamp(
realWaterDepth / (BUMP_SUPPRESS_DEPTH
* max(1, depthSample / BUMP_SUPPRESS_DEPTH_SS)) // suppress more at distance
,0 ,1);
#endif
// reflection
vec3 reflection = texture2D(reflectionMap, screenCoords + screenCoordsOffset).rgb;
@ -251,7 +255,11 @@ void main(void)
if (cameraPos.z < 0.0)
refraction = clamp(refraction * 1.5, 0.0, 1.0);
else
refraction = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0));
{
vec3 refractionA = mix(refraction, waterColor, clamp(depthSampleDistorted/VISIBILITY, 0.0, 1.0));
vec3 refractionB = mix(refraction, waterColor, clamp(realWaterDepth/VISIBILITY, 0.0, 1.0));
refraction = mix(refractionA, refractionB, 0.8);
}
// sunlight scattering
// normal for sunlight scattering

Loading…
Cancel
Save