mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-03 13:09:40 +00:00
Merge remote-tracking branch 'upstream/master' into why_are_the_christmas_lights_still_up
This commit is contained in:
commit
7370acdf54
88 changed files with 1222 additions and 293 deletions
|
@ -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 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 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 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;
|
||||
|
|
26
apps/openmw/mwmechanics/aitimer.hpp
Normal file
26
apps/openmw/mwmechanics/aitimer.hpp
Normal file
|
@ -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 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 const int Type_Last = Type_Weapon;
|
||||
static constexpr int Type_Last = Type_Weapon;
|
||||
|
||||
static const int Type_All = 0xffff;
|
||||
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 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 const int Slots = 19;
|
||||
static constexpr int Slots = 19;
|
||||
|
||||
static const int Slot_NoSlot = -1;
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
42
components/misc/budgetmeasurement.hpp
Normal file
42
components/misc/budgetmeasurement.hpp
Normal file
|
@ -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());
|
||||
};
|
||||
|
||||
}
|
||||
|
|
42
components/misc/timer.hpp
Normal file
42
components/misc/timer.hpp
Normal file
|
@ -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.
|
||||
|
||||
|
|
48
extern/CMakeLists.txt
vendored
48
extern/CMakeLists.txt
vendored
|
@ -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}")
|
||||
|
|
106
files/shaders/nv_default_fragment.glsl
Normal file
106
files/shaders/nv_default_fragment.glsl
Normal file
|
@ -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();
|
||||
}
|
58
files/shaders/nv_default_vertex.glsl
Normal file
58
files/shaders/nv_default_vertex.glsl
Normal file
|
@ -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
|
||||
}
|
55
files/shaders/nv_nolighting_fragment.glsl
Normal file
55
files/shaders/nv_nolighting_fragment.glsl
Normal file
|
@ -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);
|
||||
}
|
49
files/shaders/nv_nolighting_vertex.glsl
Normal file
49
files/shaders/nv_nolighting_vertex.glsl
Normal file
|
@ -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…
Reference in a new issue