1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-03-14 01:19:59 +00:00

Merge branch 'master' into 'SHADER_HOT_RELOAD'

# Conflicts:
#   apps/openmw/mwrender/postprocessor.hpp
This commit is contained in:
florent teppe 2022-08-09 09:26:55 +00:00
commit baadc06e98
57 changed files with 652 additions and 773 deletions

View File

@ -30,6 +30,8 @@ variables:
- apt-cache/
- ccache/
stage: build
variables:
CMAKE_EXE_LINKER_FLAGS: -fuse-ld=mold
script:
- df -h
- export CCACHE_BASEDIR="`pwd`"
@ -61,28 +63,45 @@ variables:
- build/install/
Coverity:
extends: .Ubuntu_Image
tags:
- docker
- linux
image: ubuntu:20.04
stage: build
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
cache:
key: Coverity.ubuntu_20.04.v1
paths:
- apt-cache/
- ccache/
variables:
CCACHE_SIZE: 2G
CC: clang
CXX: clang++
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -O0
before_script:
- CI/install_debian_deps.sh clang openmw-deps openmw-deps-dynamic
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64 --form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
- CI/install_debian_deps.sh clang_ubuntu_20_04 openmw-deps openmw-deps-dynamic
- curl -o /tmp/cov-analysis-linux64.tgz https://scan.coverity.com/download/linux64
--form project=$COVERITY_SCAN_PROJECT_NAME --form token=$COVERITY_SCAN_TOKEN
- tar xfz /tmp/cov-analysis-linux64.tgz
script:
- export CCACHE_BASEDIR="$(pwd)"
- export CCACHE_DIR="$(pwd)/ccache"
- mkdir -pv "${CCACHE_DIR}"
- ccache -z -M "${CCACHE_SIZE}"
- CI/before_script.linux.sh
- cov-analysis-linux64-*/bin/cov-configure --template --comptype prefix --compiler ccache
# Remove the specific targets and build everything once we can do it under 3h
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc) openmw esmtool bsatool niftest openmw-wizard openmw-launcher openmw-iniimporter openmw-essimporter openmw-navmeshtool openmw-cs
- cov-analysis-linux64-*/bin/cov-build --dir cov-int cmake --build build -- -j $(nproc)
- ccache -s
after_script:
- tar cfz cov-int.tar.gz cov-int
- curl https://scan.coverity.com/builds?project=$COVERITY_SCAN_PROJECT_NAME
--form token=$COVERITY_SCAN_TOKEN --form email=$GITLAB_USER_EMAIL
--form file=@cov-int.tar.gz --form version="$CI_COMMIT_REF_NAME:$CI_COMMIT_SHORT_SHA"
--form description="CI_COMMIT_SHORT_SHA / $CI_COMMIT_TITLE / $CI_COMMIT_REF_NAME:$CI_PIPELINE_ID"
variables:
CC: clang
CXX: clang++
CXXFLAGS: -O0
artifacts:
paths:
- /builds/OpenMW/openmw/cov-int/build-log.txt
@ -154,7 +173,7 @@ Ubuntu_GCC_tests_asan:
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O1 -fno-omit-frame-pointer -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak
CMAKE_EXE_LINKER_FLAGS: -fsanitize=address -fsanitize=pointer-compare -fsanitize=pointer-subtract -fsanitize=leak -fuse-ld=mold
ASAN_OPTIONS: halt_on_error=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
artifacts:
paths: []
@ -189,7 +208,7 @@ Ubuntu_GCC_tests_ubsan:
BUILD_TESTS_ONLY: 1
CMAKE_BUILD_TYPE: Debug
CMAKE_CXX_FLAGS_DEBUG: -g -O2 -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=thread -fPIE
CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread
CMAKE_EXE_LINKER_FLAGS: -pthread -pie -fsanitize=thread -fuse-ld=mold
TSAN_OPTIONS: second_deadlock_stack=1:halt_on_error=1
artifacts:
paths: []

View File

@ -20,7 +20,7 @@
Bug #4376: Moved actors don't respawn in their original cells
Bug #4389: NPC's lips do not move if his head model has the NiBSAnimationNode root node
Bug #4602: Robert's Bodies: crash inside createInstance()
Bug #4700: Editor: Incorrect command implementation
Bug #4700: OpenMW-CS: Incorrect command implementation
Bug #4744: Invisible particles aren't always processed
Bug #4949: Incorrect particle lighting
Bug #5054: Non-biped creatures don't use spellcast equip/unequip animations
@ -87,7 +87,7 @@
Bug #6276: Deleted groundcover instances are not deleted in game
Bug #6282: Laura craft doesn't follow the player character
Bug #6283: Avis Dorsey follows you after her death
Bug #6285: Brush template drawing and terrain selection drawing performance is very bad
Bug #6285: OpenMW-CS: Brush template drawing and terrain selection drawing performance is very bad
Bug #6289: Keyword search in dialogues expected the text to be all ASCII characters
Bug #6291: Can't pickup the dead mage's journal from the mysterious hunter mod
Bug #6302: Teleporting disabled actor breaks its disabled state
@ -114,7 +114,6 @@
Bug #6417: OpenMW doesn't always use the right node to accumulate movement
Bug #6429: Wyrmhaven: Can't add AI packages to player
Bug #6433: Items bound to Quick Keys sometimes do not appear until the Quick Key menu is opened
Bug #6435: Add support for MSVC 2022
Bug #6451: Weapon summoned from Cast When Used item will have the name "None"
Bug #6473: Strings from NIF should be parsed only to first null terminator
Bug #6493: Unlocking owned but not locked or unlocked containers is considered a crime
@ -130,7 +129,6 @@
Bug #6655: Constant effect absorb attribute causes the game to break
Bug #6667: Pressing the Esc key while resting or waiting causes black screen.
Bug #6670: Dialogue order is incorrect
Bug #6672: Garbage object refs in groundcover plugins like Vurt's grass plugins
Bug #6680: object.cpp handles nodetree unsafely, memory access with dangling pointer
Bug #6682: HitOnMe doesn't fire as intended
Bug #6697: Shaders vertex lighting incorrectly clamped
@ -142,7 +140,6 @@
Bug #6753: Info records without a DATA subrecords are loaded incorrectly
Bug #6794: Light sources are attached to mesh bounds centers instead of mesh origins when AttachLight NiNode is missing
Bug #6799: Game crashes if an NPC has no Class attached
Bug #6818: ess-importer doesn't exactly reproduce camera
Bug #6849: ImageButton texture is not scaled properly
Bug #6869: Hits queue stagger during swing animation
Bug #6890: SDL_PeepEvents errors are not handled
@ -178,6 +175,7 @@
Feature #6017: Separate persistent and temporary cell references when saving
Feature #6019: Add antialias alpha test to the launcher or enable by default if possible
Feature #6032: Reverse-z depth buffer
Feature #6078: Do not clear depth buffer for first-person meshes
Feature #6128: Soft Particles
Feature #6171: In-game log viewer
Feature #6189: Navigation mesh disk cache
@ -196,23 +194,19 @@
Feature #6557: Add support for controller gyroscope
Feature #6592: Support for NiTriShape particle emitters
Feature #6600: Support NiSortAdjustNode
Feature #6631: Support FFMPEG 5
Feature #6684: Support NiFltAnimationNode
Feature #6699: Support Ignored flag
Feature #6700: Support windowed fullscreen
Feature #6706: Save the size of the Options window
Feature #6721: [OpenMW-CS] Add option to open records in new window
Feature #6721: OpenMW-CS: Add option to open records in new window
Feature #6867: Add a way to localize hardcoded strings in GUI
Feature #6888: Add switch for armor degradation fix.
Task #6078: First person should not clear depth buffer
Feature #6888: Add switch for armor degradation fix
Feature #6925: Allow to use a mouse wheel to rotate a head in the race selection menu
Task #6161: Refactor Sky to use shaders and be GLES/GL3 friendly
Task #6162: Refactor GUI to use shaders and to be GLES and GL3+ friendly
Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings
Task #6264: Remove the old classes in animation.cpp
Task #6553: Simplify interpreter instruction registration
Task #6435: Add support for MSVC 2022
Task #6564: Remove predefined data paths `data="?global?data"`, `data=./data`
Task #6631: Fix ffmpeg avio API usage causing hangs in ffmpeg version 5
Task #6709: Move KeyframeController transformation magic to NifOsg::MatrixTransform
Task #6763: gcc 12.1 multiple compiler warnings
0.47.0
------
@ -348,6 +342,7 @@
Bug #6047: Mouse bindings can be triggered during save loading
Bug #6136: Game freezes when NPCs try to open doors that are about to be closed
Bug #6294: Game crashes with empty pathgrid
Bug #6923: Dispose of corpse prevents respawning after load
Feature #390: 3rd person look "over the shoulder"
Feature #832: OpenMW-CS: Handle deleted references
Feature #1536: Show more information about level on menu

View File

@ -11,8 +11,6 @@ BUILD_UNITTESTS=OFF
BUILD_BENCHMARKS=OFF
if [[ "${BUILD_TESTS_ONLY}" ]]; then
export GOOGLETEST_DIR="${PWD}/googletest/build/install"
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
BUILD_UNITTESTS=ON
BUILD_BENCHMARKS=ON
fi
@ -28,9 +26,14 @@ declare -a CMAKE_CONF_OPTS=(
-DUSE_SYSTEM_TINYXML=ON
-DOPENMW_USE_SYSTEM_RECASTNAVIGATION=ON
-DOPENMW_CXX_FLAGS="-Werror -Werror=implicit-fallthrough" # flags specific to OpenMW project
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=mold"
)
if [[ "${CMAKE_EXE_LINKER_FLAGS}" ]]; then
CMAKE_CONF_OPTS+=(
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}"
)
fi
if [[ $CI_OPENMW_USE_STATIC_DEPS ]]; then
CMAKE_CONF_OPTS+=(
-DOPENMW_USE_SYSTEM_MYGUI=OFF
@ -99,8 +102,6 @@ if [[ "${BUILD_TESTS_ONLY}" ]]; then
-DBUILD_NIFTEST=OFF \
-DBUILD_UNITTESTS=${BUILD_UNITTESTS} \
-DBUILD_BENCHMARKS=${BUILD_BENCHMARKS} \
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
..
else
${ANALYZE} cmake \

View File

@ -642,16 +642,6 @@ if [ -z $SKIP_DOWNLOAD ]; then
"https://gitlab.com/OpenMW/openmw-deps/-/raw/main/windows/LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z" \
"LuaJIT-2.1.0-beta3-msvc${LUA_MSVC_YEAR}-win${BITS}.7z"
# Google test and mock
if [ -n "$TEST_FRAMEWORK" ]; then
echo "Google test 1.11.0..."
if [ -d googletest ]; then
printf " Google test exists, skipping."
else
git clone -b release-1.11.0 https://github.com/google/googletest.git
fi
fi
# ICU
download "ICU ${ICU_VER/_/.}"\
"https://github.com/unicode-org/icu/releases/download/release-${ICU_VER/_/-}/icu4c-${ICU_VER}-Win${BITS}-MSVC2019.zip" \
@ -1003,61 +993,6 @@ printf "LuaJIT 2.1.0-beta3... "
done
echo Done.
}
cd $DEPS
echo
# Google Test and Google Mock
if [ -n "$TEST_FRAMEWORK" ]; then
printf "Google test 1.11.0 ..."
cd googletest
mkdir -p build${MSVC_DISPLAY_YEAR}
cd build${MSVC_DISPLAY_YEAR}
GOOGLE_INSTALL_ROOT="${DEPS_INSTALL}/GoogleTest"
for CONFIGURATION in ${CONFIGURATIONS[@]}; do
# FindGMock.cmake mentions Release explicitly, but not RelWithDebInfo. Only one optimised library config can be used, so go for the safer one.
GTEST_CONFIG=$([ $CONFIGURATION == "RelWithDebInfo" ] && echo "Release" || echo "$CONFIGURATION" )
if [ $GTEST_CONFIG == "Debug" ]; then
DEBUG_SUFFIX="d"
else
DEBUG_SUFFIX=""
fi
if [ ! -f "$GOOGLE_INSTALL_ROOT/lib/gtest${DEBUG_SUFFIX}.lib" ]; then
# Always use MSBuild solution files as they don't need the environment activating
cmake .. -DCMAKE_USE_WIN32_THREADS_INIT=1 -G "Visual Studio $MSVC_REAL_VER $MSVC_DISPLAY_YEAR" "-A $([ $BITS -eq 64 ] && echo "x64" || echo "Win32")" -DBUILD_SHARED_LIBS=1
cmake --build . --config "${GTEST_CONFIG}"
cmake --install . --config "${GTEST_CONFIG}" --prefix "${GOOGLE_INSTALL_ROOT}"
fi
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest_main${DEBUG_SUFFIX}.dll"
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gtest${DEBUG_SUFFIX}.dll"
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock_main${DEBUG_SUFFIX}.dll"
add_runtime_dlls $CONFIGURATION "${GOOGLE_INSTALL_ROOT}\bin\gmock${DEBUG_SUFFIX}.dll"
done
add_cmake_opts -DBUILD_UNITTESTS=yes
# FindGTest and FindGMock do not work perfectly on Windows
# but we can help them by telling them everything we know about installation
add_cmake_opts -DGMOCK_ROOT="$GOOGLE_INSTALL_ROOT"
add_cmake_opts -DGTEST_ROOT="$GOOGLE_INSTALL_ROOT"
add_cmake_opts -DGTEST_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest.lib"
add_cmake_opts -DGTEST_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gtest_main.lib"
add_cmake_opts -DGMOCK_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock.lib"
add_cmake_opts -DGMOCK_MAIN_LIBRARY="$GOOGLE_INSTALL_ROOT/lib/gmock_main.lib"
add_cmake_opts -DGTEST_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtestd.lib"
add_cmake_opts -DGTEST_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gtest_maind.lib"
add_cmake_opts -DGMOCK_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmockd.lib"
add_cmake_opts -DGMOCK_MAIN_LIBRARY_DEBUG="$GOOGLE_INSTALL_ROOT/lib/gmock_maind.lib"
add_cmake_opts -DGTEST_LINKED_AS_SHARED_LIBRARY=True
add_cmake_opts -DGTEST_LIBRARY_TYPE=SHARED
add_cmake_opts -DGTEST_MAIN_LIBRARY_TYPE=SHARED
echo Done.
fi
cd $DEPS
echo
@ -1176,6 +1111,10 @@ if [ "${BUILD_BENCHMARKS}" ]; then
add_cmake_opts -DBUILD_BENCHMARKS=ON
fi
if [ -n "${TEST_FRAMEWORK}" ]; then
add_cmake_opts -DBUILD_UNITTESTS=ON
fi
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; }

View File

@ -1,17 +0,0 @@
#!/bin/sh -ex
git clone -b release-1.11.0 https://github.com/google/googletest.git
cd googletest
mkdir build
cd build
cmake \
-D CMAKE_C_COMPILER="${CC}" \
-D CMAKE_CXX_COMPILER="${CXX}" \
-D CMAKE_C_COMPILER_LAUNCHER=ccache \
-D CMAKE_CXX_COMPILER_LAUNCHER=ccache \
-D CMAKE_BUILD_TYPE="${CONFIGURATION}" \
-D CMAKE_INSTALL_PREFIX="${GOOGLETEST_DIR}" \
-G "${GENERATOR}" \
..
cmake --build . --config "${CONFIGURATION}" -- -j $(nproc)
cmake --install . --config "${CONFIGURATION}"

View File

@ -11,6 +11,7 @@ print_help() {
declare -rA GROUPED_DEPS=(
[gcc]="binutils gcc build-essential cmake ccache curl unzip git pkg-config mold"
[clang]="binutils clang make cmake ccache curl unzip git pkg-config mold"
[clang_ubuntu_20_04]="binutils clang make cmake ccache curl unzip git pkg-config"
# Common dependencies for building OpenMW.
[openmw-deps]="

View File

@ -163,6 +163,7 @@ option(RECASTNAVIGATION_STATIC "Build recastnavigation static libraries" ${_reca
option(OPENMW_USE_SYSTEM_SQLITE3 "Use system provided SQLite3 library" ON)
option(OPENMW_USE_SYSTEM_BENCHMARK "Use system Google Benchmark library." OFF)
option(OPENMW_USE_SYSTEM_GOOGLETEST "Use system Google Test library." OFF)
option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE)
option(OPENMW_LTO_BUILD "Build OpenMW with Link-Time Optimization (Needs ~2GB of RAM)" OFF)

View File

@ -112,7 +112,7 @@ namespace
World,
Gui,
Lua,
LuaSyncUpdate,
Number,
};
@ -152,6 +152,10 @@ namespace
template <>
const UserStats UserStatsValue<UserStatsType::Lua>::sValue {"Lua", "lua"};
template <>
const UserStats UserStatsValue<UserStatsType::LuaSyncUpdate>::sValue{ " -Sync", "luasyncupdate" };
template <UserStatsType type>
struct ForEachUserStatsValue
{
@ -336,10 +340,13 @@ bool OMW::Engine::frame(float frametime)
// Main menu opened? Then scripts are also paused.
bool paused = mWindowManager->containsMode(MWGui::GM_MainMenu);
// Should be called after input manager update and before any change to the game world.
// It applies to the game world queued changes from the previous frame.
mLuaManager->synchronizedUpdate();
{
ScopedProfile<UserStatsType::LuaSyncUpdate> profile(frameStart, frameNumber, *timer, *stats);
// Should be called after input manager update and before any change to the game world.
// It applies to the game world queued changes from the previous frame.
mLuaManager->synchronizedUpdate();
}
// update game state
{

View File

@ -796,13 +796,13 @@ namespace MWClass
return;
}
if (ptr.getRefData().getCount() <= 0)
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
if (ptr.getRefData().getCount() <= 0 && (!isFlagBitSet(ptr, ESM::Creature::Respawn) || !customData.mCreatureStats.isDead()))
{
state.mHasCustomState = false;
return;
}
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
ESM::CreatureState& creatureState = state.asCreatureState();
customData.mContainerStore->writeState (creatureState.mInventory);
customData.mCreatureStats.writeState (creatureState.mCreatureStats);

View File

@ -1349,13 +1349,13 @@ namespace MWClass
return;
}
if (ptr.getRefData().getCount() <= 0)
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
if (ptr.getRefData().getCount() <= 0 && (!(ptr.get<ESM::NPC>()->mBase->mFlags & ESM::NPC::Respawn) || !customData.mNpcStats.isDead()))
{
state.mHasCustomState = false;
return;
}
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
ESM::NpcState& npcState = state.asNpcState();
customData.mInventoryStore.writeState (npcState.mInventory);
customData.mNpcStats.writeState (npcState.mNpcStats);

View File

@ -17,28 +17,6 @@
#include "../mwbase/environment.hpp"
#include "../mwbase/windowmanager.hpp"
namespace
{
void saveChain()
{
auto* processor = MWBase::Environment::get().getWorld()->getPostProcessor();
std::vector<std::string> chain;
for (size_t i = 1; i < processor->getTechniques().size(); ++i)
{
auto technique = processor->getTechniques()[i];
if (!technique || technique->getDynamic())
continue;
chain.push_back(technique->getName());
}
Settings::Manager::setStringArray("chain", "Post Processing", chain);
}
}
namespace MWGui
{
void PostProcessorHud::ListWrapper::onKeyButtonPressed(MyGUI::KeyCode key, MyGUI::Char ch)
@ -145,7 +123,7 @@ namespace MWGui
processor->enableTechnique(technique);
else
processor->disableTechnique(technique);
saveChain();
processor->saveChain();
}
}
@ -178,7 +156,7 @@ namespace MWGui
return;
if (processor->enableTechnique(technique, index) != MWRender::PostProcessor::Status_Error)
saveChain();
processor->saveChain();
}
}
@ -377,7 +355,7 @@ namespace MWGui
{
Gui::AutoSizedTextBox* divider = mConfigArea->createWidget<Gui::AutoSizedTextBox>("MW_UniformGroup", {0,0,0,34}, MyGUI::Align::Default);
divider->setNeedMouseFocus(false);
divider->setCaption(uniform->mHeader);
divider->setCaptionWithReplacing(uniform->mHeader);
}
fx::Widgets::UniformBase* uwidget = mConfigArea->createWidget<fx::Widgets::UniformBase>("MW_UniformEdit", {0,0,0,22}, MyGUI::Align::Default);

View File

@ -56,6 +56,8 @@ namespace MWGui
setText("AppearanceT", MWBase::Environment::get().getWindowManager()->getGameSettingString("sRaceMenu1", "Appearance"));
getWidget(mPreviewImage, "PreviewImage");
mPreviewImage->eventMouseWheel += MyGUI::newDelegate(this, &RaceDialog::onPreviewScroll);
getWidget(mHeadRotate, "HeadRotate");
mHeadRotate->setScrollRange(1000);
@ -211,6 +213,19 @@ namespace MWGui
eventBack();
}
void RaceDialog::onPreviewScroll(MyGUI::Widget*, int _delta)
{
size_t oldPos = mHeadRotate->getScrollPosition();
size_t maxPos = mHeadRotate->getScrollRange() - 1;
size_t scrollPage = mHeadRotate->getScrollWheelPage();
if (_delta < 0)
mHeadRotate->setScrollPosition(oldPos + std::min(maxPos - oldPos, scrollPage));
else
mHeadRotate->setScrollPosition(oldPos - std::min(oldPos, scrollPage));
onHeadRotate(mHeadRotate, mHeadRotate->getScrollPosition());
}
void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position)
{
float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5f) * osg::PI * 2;

View File

@ -66,6 +66,7 @@ namespace MWGui
EventHandle_WindowBase eventDone;
protected:
void onPreviewScroll(MyGUI::Widget* _sender, int _delta);
void onHeadRotate(MyGUI::ScrollBar* _sender, size_t _position);
void onSelectPreviousGender(MyGUI::Widget* _sender);

View File

@ -56,7 +56,7 @@ namespace MWGui
if (effectId != -1)
{
const ESM::MagicEffect *magicEffect =
store.get<ESM::MagicEffect>().search(effectId);
store.get<ESM::MagicEffect>().find(effectId);
std::string effectIDStr = ESM::MagicEffect::effectIdToString(effectId);
std::string fullEffectName = wm->getGameSettingString(effectIDStr, "");

View File

@ -670,25 +670,26 @@ namespace MWMechanics
}
}
short unsigned AiWander::getRandomIdle()
int AiWander::getRandomIdle() const
{
unsigned short idleRoll = 0;
short unsigned selectedAnimation = 0;
MWBase::World* world = MWBase::Environment::get().getWorld();
static const float fIdleChanceMultiplier = world->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->mValue.getFloat();
if (Misc::Rng::rollClosedProbability(world->getPrng()) > fIdleChanceMultiplier)
return 0;
for(unsigned int counter = 0; counter < mIdle.size(); counter++)
int newIdle = 0;
float maxRoll = 0.f;
for (size_t i = 0; i < mIdle.size(); i++)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
static float fIdleChanceMultiplier = world->getStore().get<ESM::GameSetting>().find("fIdleChanceMultiplier")->mValue.getFloat();
unsigned short idleChance = static_cast<unsigned short>(fIdleChanceMultiplier * mIdle[counter]);
unsigned short randSelect = (int)(Misc::Rng::rollProbability(world->getPrng()) * int(100 / fIdleChanceMultiplier));
if(randSelect < idleChance && randSelect > idleRoll)
float roll = Misc::Rng::rollClosedProbability(world->getPrng()) * 100.f;
if (roll <= mIdle[i] && roll > maxRoll)
{
selectedAnimation = counter + GroupIndex_MinIdle;
idleRoll = randSelect;
newIdle = GroupIndex_MinIdle + i;
maxRoll = roll;
}
}
return selectedAnimation;
return newIdle;
}
void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state)

View File

@ -112,7 +112,7 @@ namespace MWMechanics
/// @return Success or error
bool playIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
bool checkIdle(const MWWorld::Ptr& actor, unsigned short idleSelect);
short unsigned getRandomIdle();
int getRandomIdle() const;
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);

View File

@ -307,7 +307,7 @@ void CharacterController::resetCurrentHitState()
void CharacterController::resetCurrentWeaponState()
{
clearStateAnimation(mCurrentWeapon);
mUpperBodyState = UpperCharState_Nothing;
mUpperBodyState = UpperBodyState::None;
}
void CharacterController::resetCurrentDeathState()
@ -401,15 +401,15 @@ void CharacterController::refreshHitRecoilAnims()
{
if (!mCurrentWeapon.empty())
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState > UpperCharState_WeapEquiped)
if (mUpperBodyState > UpperBodyState::WeaponEquipped)
{
mUpperBodyState = UpperCharState_WeapEquiped;
mUpperBodyState = UpperBodyState::WeaponEquipped;
if (mWeaponType > ESM::Weapon::None)
mAnimation->showWeapons(true);
}
else if (mUpperBodyState < UpperCharState_WeapEquiped)
else if (mUpperBodyState < UpperBodyState::WeaponEquipped)
{
mUpperBodyState = UpperCharState_Nothing;
mUpperBodyState = UpperBodyState::None;
}
}
@ -680,7 +680,7 @@ void CharacterController::refreshIdleAnims(CharacterState idle, bool force)
{
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
// the idle animation should be displayed
if (((mUpperBodyState != UpperCharState_Nothing && mUpperBodyState != UpperCharState_WeapEquiped)
if (((mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped)
|| mMovementState != CharState_None || mHitState != CharState_None) && !mPtr.getClass().isBipedal(mPtr))
{
resetCurrentIdleState();
@ -855,7 +855,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
getActiveWeapon(mPtr, &mWeaponType);
if (mWeaponType != ESM::Weapon::None)
{
mUpperBodyState = UpperCharState_WeapEquiped;
mUpperBodyState = UpperBodyState::WeaponEquipped;
mCurrentWeapon = getWeaponAnimation(mWeaponType);
}
@ -1071,7 +1071,7 @@ void CharacterController::updatePtr(const MWWorld::Ptr &ptr)
void CharacterController::updateIdleStormState(bool inwater) const
{
if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperCharState_Nothing || inwater)
if (!mAnimation->hasAnimation("idlestorm") || mUpperBodyState != UpperBodyState::None || inwater)
{
mAnimation->disable("idlestorm");
return;
@ -1113,7 +1113,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const
return mAnimation->updateCarriedLeftVisible(weaptype);
}
bool CharacterController::updateWeaponState(CharacterState idle)
bool CharacterController::updateWeaponState()
{
const auto world = MWBase::Environment::get().getWorld();
auto& prng = world->getPrng();
@ -1166,20 +1166,18 @@ bool CharacterController::updateWeaponState(CharacterState idle)
bool forcestateupdate = false;
// We should not play equipping animation and sound during weapon->weapon transition
const bool isStillWeapon = weaptype != ESM::Weapon::HandToHand && weaptype != ESM::Weapon::Spell && weaptype != ESM::Weapon::None &&
mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && mWeaponType != ESM::Weapon::None;
const bool isStillWeapon = isRealWeapon(mWeaponType) && isRealWeapon(weaptype);
// If the current weapon type was changed in the middle of attack (e.g. by Equip console command or when bound spell expires),
// we should force actor to the "weapon equipped" state, interrupt attack and update animations.
if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperCharState_WeapEquiped)
if (isStillWeapon && mWeaponType != weaptype && mUpperBodyState > UpperBodyState::WeaponEquipped)
{
forcestateupdate = true;
if (!mCurrentWeapon.empty())
mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
mUpperBodyState = UpperBodyState::WeaponEquipped;
setAttackingOrSpell(false);
mAnimation->showWeapons(true);
stats.setAttackingOrSpell(false);
}
if(!isKnockedOut() && !isKnockedDown() && !isRecovery())
@ -1187,7 +1185,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
std::string weapgroup;
if ((!isWerewolf || mWeaponType != ESM::Weapon::Spell)
&& weaptype != mWeaponType
&& mUpperBodyState != UpperCharState_UnEquipingWeap
&& mUpperBodyState != UpperBodyState::Unequipping
&& !isStillWeapon)
{
// We can not play un-equip animation if weapon changed since last update
@ -1209,7 +1207,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
mAnimation->play(weapgroup, priorityWeapon, unequipMask, false,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
mUpperBodyState = UpperBodyState::Unequipping;
mAnimation->detachArrow();
@ -1247,7 +1245,8 @@ bool CharacterController::updateWeaponState(CharacterState idle)
if (!isStillWeapon)
{
clearStateAnimation(mCurrentWeapon);
if (animPlaying)
mAnimation->disable(mCurrentWeapon);
if (weaptype != ESM::Weapon::None)
{
mAnimation->showWeapons(false);
@ -1262,15 +1261,18 @@ bool CharacterController::updateWeaponState(CharacterState idle)
mAnimation->play(weapgroup, priorityWeapon, equipMask, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
mUpperBodyState = UpperBodyState::Equipping;
// If we do not have the "equip attach" key, show weapon manually.
if (weaptype != ESM::Weapon::Spell)
if (weaptype != ESM::Weapon::Spell && mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
{
if (mAnimation->getTextKeyTime(weapgroup+": equip attach") < 0)
mAnimation->showWeapons(true);
mAnimation->showWeapons(true);
}
}
if (!upSoundId.empty())
{
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
}
}
if(isWerewolf)
@ -1286,18 +1288,13 @@ bool CharacterController::updateWeaponState(CharacterState idle)
mWeaponType = weaptype;
mCurrentWeapon = weapgroup;
if(!upSoundId.empty() && !isStillWeapon)
{
sndMgr->playSound3D(mPtr, upSoundId, 1.0f, 1.0f);
}
}
// Make sure that we disabled unequipping animation
if (mUpperBodyState == UpperCharState_UnEquipingWeap)
if (mUpperBodyState == UpperBodyState::Unequipping)
{
resetCurrentWeaponState();
mWeaponType = ESM::Weapon::None;
mCurrentWeapon = getWeaponAnimation(mWeaponType);
}
}
}
@ -1317,29 +1314,43 @@ bool CharacterController::updateWeaponState(CharacterState idle)
sndMgr->stopSound3D(mPtr, "WolfRun");
}
// Cancel attack if we no longer have ammunition
bool ammunition = true;
bool isWeapon = false;
float weapSpeed = 1.f;
if (cls.hasInventoryStore(mPtr))
{
MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator weapon = getActiveWeapon(mPtr, &weaptype);
isWeapon = (weapon != inv.end() && weapon->getType() == ESM::Weapon::sRecordId);
if (isWeapon)
if (stats.getDrawState() == DrawState::Weapon && !mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId)
{
weapSpeed = weapon->get<ESM::Weapon>()->mBase->mData.mSpeed;
weapSpeed = mWeapon.get<ESM::Weapon>()->mBase->mData.mSpeed;
MWWorld::ConstContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition);
int ammotype = getWeaponType(weapon->get<ESM::Weapon>()->mBase->mData.mType)->mAmmoType;
if (ammotype != ESM::Weapon::None && (ammo == inv.end() || ammo->get<ESM::Weapon>()->mBase->mData.mType != ammotype))
ammunition = false;
int ammotype = getWeaponType(mWeapon.get<ESM::Weapon>()->mBase->mData.mType)->mAmmoType;
if (ammotype != ESM::Weapon::None)
ammunition = ammo != inv.end() && ammo->get<ESM::Weapon>()->mBase->mData.mType == ammotype;
}
if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped)
// Cancel attack if we no longer have ammunition
if (!ammunition)
{
if (!mCurrentWeapon.empty())
if (mUpperBodyState == UpperBodyState::AttackPreWindUp || mUpperBodyState == UpperBodyState::AttackWindUp)
{
mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
mUpperBodyState = UpperBodyState::WeaponEquipped;
}
setAttackingOrSpell(false);
}
MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (torch != inv.end() && torch->getType() == ESM::Light::sRecordId && updateCarriedLeftVisible(mWeaponType))
{
if (mAnimation->isPlaying("shield"))
mAnimation->disable("shield");
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
false, 1.0f, "start", "stop", 0.0f, std::numeric_limits<size_t>::max(), true);
}
else if (mAnimation->isPlaying("torch"))
{
mAnimation->disable("torch");
}
}
@ -1352,15 +1363,13 @@ bool CharacterController::updateWeaponState(CharacterState idle)
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
if(getAttackingOrSpell())
{
bool resetIdle = ammunition;
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
bool resetIdle = true;
if (mUpperBodyState == UpperBodyState::WeaponEquipped && (mHitState == CharState_None || mHitState == CharState_Block))
{
mAttackStrength = 0;
// Randomize attacks for non-bipedal creatures
if (cls.getType() == ESM::Creature::sRecordId &&
!cls.isBipedal(mPtr) &&
(!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
if (!cls.isBipedal(mPtr) && (!mAnimation->hasAnimation(mCurrentWeapon) || isRandomAttackAnimation(mCurrentWeapon)))
{
mCurrentWeapon = chooseRandomAttackAnimation();
}
@ -1411,7 +1420,7 @@ bool CharacterController::updateWeaponState(CharacterState idle)
resetIdle = false;
// Spellcasting animation needs to "play" for at least one frame to reset the aiming factor
animPlaying = true;
mUpperBodyState = UpperCharState_CastingSpell;
mUpperBodyState = UpperBodyState::Casting;
}
// Play the spellcasting animation/VFX if the spellcasting was successful or failed due to insufficient magicka.
// Used up powers are exempt from this from some reason.
@ -1485,65 +1494,53 @@ bool CharacterController::updateWeaponState(CharacterState idle)
MWRender::Animation::BlendMask_All, true,
1, startKey, stopKey,
0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell;
mUpperBodyState = UpperBodyState::Casting;
}
else
{
resetIdle = false;
}
}
else if(mWeaponType == ESM::Weapon::PickProbe)
else
{
world->breakInvisibility(mPtr);
MWWorld::ContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
MWWorld::Ptr item = *weapon;
// TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
MWWorld::Ptr target = world->getFacedObject();
std::string resultMessage, resultSound;
std::string startKey = "start";
std::string stopKey = "stop";
bool autodisable = false;
if(!target.isEmpty())
if (mWeaponType == ESM::Weapon::PickProbe)
{
if(item.getType() == ESM::Lockpick::sRecordId)
Security(mPtr).pickLock(target, item, resultMessage, resultSound);
else if(item.getType() == ESM::Probe::sRecordId)
Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
}
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "start", "stop", 0.0, 0);
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
autodisable = true;
mUpperBodyState = UpperBodyState::AttackEnd;
if(!resultMessage.empty())
MWBase::Environment::get().getWindowManager()->messageBox(resultMessage);
if(!resultSound.empty())
sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f);
}
else if (ammunition)
{
std::string startKey;
std::string stopKey;
world->breakInvisibility(mPtr);
// TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes.
MWWorld::Ptr target = world->getFacedObject();
std::string resultMessage, resultSound;
if(weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
{
mAttackType = "shoot";
startKey = mAttackType+" start";
stopKey = mAttackType+" min attack";
if(!target.isEmpty())
{
if (mWeapon.getType() == ESM::Lockpick::sRecordId)
Security(mPtr).pickLock(target, mWeapon, resultMessage, resultSound);
else if (mWeapon.getType() == ESM::Probe::sRecordId)
Security(mPtr).probeTrap(target, mWeapon, resultMessage, resultSound);
}
if (!resultMessage.empty())
MWBase::Environment::get().getWindowManager()->messageBox(resultMessage);
if (!resultSound.empty())
sndMgr->playSound3D(target, resultSound, 1.0f, 1.0f);
}
else if (isRandomAttackAnimation(mCurrentWeapon))
else if (!isRandomAttackAnimation(mCurrentWeapon))
{
startKey = "start";
stopKey = "stop";
}
else
{
if(mPtr == getPlayer())
if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
mAttackType = "shoot";
else if (mPtr == getPlayer())
{
if (Settings::Manager::getBool("best attack", "Game"))
{
if (isWeapon)
if (!mWeapon.isEmpty() && mWeapon.getType() == ESM::Weapon::sRecordId)
{
MWWorld::ConstContainerStoreIterator weapon = cls.getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
mAttackType = getBestAttack(weapon->get<ESM::Weapon>()->mBase);
mAttackType = getBestAttack(mWeapon.get<ESM::Weapon>()->mBase);
}
else
{
@ -1562,12 +1559,12 @@ bool CharacterController::updateWeaponState(CharacterState idle)
}
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
MWRender::Animation::BlendMask_All, autodisable,
weapSpeed, startKey, stopKey,
0.0f, 0);
if(mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
if (mWeaponType != ESM::Weapon::PickProbe && mAnimation->getCurrentTime(mCurrentWeapon) != -1.f)
{
mUpperBodyState = UpperCharState_StartToMinAttack;
mUpperBodyState = UpperBodyState::AttackPreWindUp;
if (isRandomAttackAnimation(mCurrentWeapon))
{
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
@ -1578,25 +1575,34 @@ bool CharacterController::updateWeaponState(CharacterState idle)
}
// We should not break swim and sneak animations
if (resetIdle &&
idle != CharState_IdleSneak && idle != CharState_IdleSwim &&
mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
if (resetIdle && mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
{
resetCurrentIdleState();
}
if (!animPlaying)
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
mAttackStrength = complete;
}
else
{
if (!animPlaying)
animPlaying = mAnimation->getInfo(mCurrentWeapon, &complete);
if(mUpperBodyState == UpperCharState_MinAttackToMaxAttack && !isKnockedDown())
if (isKnockedDown())
{
if (mUpperBodyState > UpperBodyState::WeaponEquipped)
{
mUpperBodyState = UpperBodyState::WeaponEquipped;
if (mWeaponType > ESM::Weapon::None)
mAnimation->showWeapons(true);
}
if (!mCurrentWeapon.empty())
mAnimation->disable(mCurrentWeapon);
}
if (mUpperBodyState == UpperBodyState::AttackWindUp)
{
mAttackStrength = complete;
if (!getAttackingOrSpell())
{
world->breakInvisibility(mPtr);
float attackStrength = complete;
float minAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"min attack");
float maxAttackTime = mAnimation->getTextKeyTime(mCurrentWeapon+": "+mAttackType+" "+"max attack");
if (minAttackTime == maxAttackTime)
@ -1604,44 +1610,20 @@ bool CharacterController::updateWeaponState(CharacterState idle)
// most creatures don't actually have an attack wind-up animation, so use a uniform random value
// (even some creatures that can use weapons don't have a wind-up animation either, e.g. Rieklings)
// Note: vanilla MW uses a random value for *all* non-player actors, but we probably don't need to go that far.
attackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
mAttackStrength = std::min(1.f, 0.1f + Misc::Rng::rollClosedProbability(prng));
}
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
{
if(isWerewolf)
{
const MWWorld::ESMStore &store = world->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing", prng);
if(sound)
sndMgr->playSound3D(mPtr, sound->mId, 1.0f, 1.0f);
}
else
{
playSwishSound(attackStrength);
}
}
mAttackStrength = attackStrength;
playSwishSound(mAttackStrength);
mAnimation->disable(mCurrentWeapon);
if (animPlaying)
mAnimation->disable(mCurrentWeapon);
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
1.0f-complete, 0);
complete = 0.f;
mUpperBodyState = UpperCharState_MaxAttackToMinHit;
}
else if (isKnockedDown())
{
if (mUpperBodyState > UpperCharState_WeapEquiped)
{
mUpperBodyState = UpperCharState_WeapEquiped;
if (mWeaponType > ESM::Weapon::None)
mAnimation->showWeapons(true);
}
if (!mCurrentWeapon.empty())
mAnimation->disable(mCurrentWeapon);
mUpperBodyState = UpperBodyState::AttackRelease;
}
}
@ -1650,15 +1632,15 @@ bool CharacterController::updateWeaponState(CharacterState idle)
{
switch (mUpperBodyState)
{
case UpperCharState_StartToMinAttack:
case UpperBodyState::AttackPreWindUp:
mAnimation->setPitchFactor(complete);
break;
case UpperCharState_MinAttackToMaxAttack:
case UpperCharState_MaxAttackToMinHit:
case UpperCharState_MinHitToHit:
case UpperBodyState::AttackWindUp:
case UpperBodyState::AttackRelease:
case UpperBodyState::AttackHit:
mAnimation->setPitchFactor(1.f);
break;
case UpperCharState_FollowStartToFollowStop:
case UpperBodyState::AttackEnd:
if (animPlaying)
{
// technically we do not need a pitch for crossbow reload animation,
@ -1676,39 +1658,39 @@ bool CharacterController::updateWeaponState(CharacterState idle)
if(!animPlaying)
{
if(mUpperBodyState == UpperCharState_EquipingWeap ||
mUpperBodyState == UpperCharState_FollowStartToFollowStop ||
mUpperBodyState == UpperCharState_CastingSpell)
if (mUpperBodyState == UpperBodyState::Equipping ||
mUpperBodyState == UpperBodyState::AttackEnd ||
mUpperBodyState == UpperBodyState::Casting)
{
if (ammunition && mWeaponType == ESM::Weapon::MarksmanCrossbow)
mAnimation->attachArrow();
// Cancel stagger animation at the end of an attack to avoid abrupt transitions
// in favor of a different abrupt transition, like Morrowind
if (mUpperBodyState != UpperCharState_EquipingWeap && isRecovery())
if (mUpperBodyState != UpperBodyState::Equipping && isRecovery())
mAnimation->disable(mCurrentHit);
mUpperBodyState = UpperCharState_WeapEquiped;
mUpperBodyState = UpperBodyState::WeaponEquipped;
}
else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
mUpperBodyState = UpperCharState_Nothing;
else if (mUpperBodyState == UpperBodyState::Unequipping)
mUpperBodyState = UpperBodyState::None;
}
else if(complete >= 1.0f && !isRandomAttackAnimation(mCurrentWeapon))
{
std::string start, stop;
switch(mUpperBodyState)
{
case UpperCharState_MinAttackToMaxAttack:
case UpperBodyState::AttackWindUp:
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
if(!mAnimation->isPlaying(mCurrentWeapon))
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
break;
case UpperCharState_StartToMinAttack:
case UpperCharState_MaxAttackToMinHit:
case UpperBodyState::AttackPreWindUp:
case UpperBodyState::AttackRelease:
{
if (mUpperBodyState == UpperCharState_StartToMinAttack)
if (mUpperBodyState == UpperBodyState::AttackPreWindUp)
{
// If actor is already stopped preparing attack, do not play the "min attack -> max attack" part.
// Happens if the player did not hold the attack button.
@ -1719,13 +1701,12 @@ bool CharacterController::updateWeaponState(CharacterState idle)
{
start = mAttackType+" min attack";
stop = mAttackType+" max attack";
mUpperBodyState = UpperCharState_MinAttackToMaxAttack;
mUpperBodyState = UpperBodyState::AttackWindUp;
break;
}
world->breakInvisibility(mPtr);
if(weapclass != ESM::WeaponType::Ranged && weapclass != ESM::WeaponType::Thrown)
playSwishSound(0.0f);
playSwishSound(0.0f);
}
if(mAttackType == "shoot")
@ -1738,10 +1719,10 @@ bool CharacterController::updateWeaponState(CharacterState idle)
start = mAttackType+" min hit";
stop = mAttackType+" hit";
}
mUpperBodyState = UpperCharState_MinHitToHit;
mUpperBodyState = UpperBodyState::AttackHit;
break;
}
case UpperCharState_MinHitToHit:
case UpperBodyState::AttackHit:
if(mAttackType == "shoot")
{
start = mAttackType+" follow start";
@ -1757,57 +1738,26 @@ bool CharacterController::updateWeaponState(CharacterState idle)
: (str < 1.0f) ? " medium follow stop"
: " large follow stop");
}
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
mUpperBodyState = UpperBodyState::AttackEnd;
break;
default:
break;
}
// Note: apply crossbow reload animation only for upper body
// since blending with movement animations can give weird result.
if(!start.empty())
{
int mask = MWRender::Animation::BlendMask_All;
if (mWeaponType == ESM::Weapon::MarksmanCrossbow)
mask = MWRender::Animation::BlendMask_UpperBody;
bool autodisable = mUpperBodyState == UpperBodyState::AttackEnd;
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, priorityWeapon,
mask, true,
weapSpeed, start, stop, 0.0f, 0);
else
mAnimation->play(mCurrentWeapon, priorityWeapon,
mask, false,
weapSpeed, start, stop, 0.0f, 0);
mAnimation->play(mCurrentWeapon, priorityWeapon, MWRender::Animation::BlendMask_All, autodisable, weapSpeed, start, stop, 0.0f, 0);
}
}
else if(complete >= 1.0f && isRandomAttackAnimation(mCurrentWeapon))
{
clearStateAnimation(mCurrentWeapon);
mUpperBodyState = UpperCharState_WeapEquiped;
mUpperBodyState = UpperBodyState::WeaponEquipped;
}
if (cls.hasInventoryStore(mPtr))
{
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
MWWorld::ConstContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if(torch != inv.end() && torch->getType() == ESM::Light::sRecordId
&& updateCarriedLeftVisible(mWeaponType))
{
if (mAnimation->isPlaying("shield"))
mAnimation->disable("shield");
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
}
else if (mAnimation->isPlaying("torch"))
{
mAnimation->disable("torch");
}
}
mAnimation->setAccurateAiming(mUpperBodyState > UpperCharState_WeapEquiped);
mAnimation->setAccurateAiming(mUpperBodyState > UpperBodyState::WeaponEquipped);
return forcestateupdate;
}
@ -2270,7 +2220,7 @@ void CharacterController::update(float duration)
if (!mSkipAnim)
{
refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState(idlestate));
refreshCurrentAnims(idlestate, movestate, jumpstate, updateWeaponState());
updateIdleStormState(inwater);
}
@ -2567,8 +2517,8 @@ void CharacterController::forceStateUpdate()
mCanCast = false;
mCastingManualSpell = false;
setAttackingOrSpell(false);
if (mUpperBodyState != UpperCharState_Nothing)
mUpperBodyState = UpperCharState_WeapEquiped;
if (mUpperBodyState != UpperBodyState::None)
mUpperBodyState = UpperBodyState::WeaponEquipped;
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
@ -2687,13 +2637,13 @@ bool CharacterController::isRandomAttackAnimation(std::string_view group)
bool CharacterController::isAttackPreparing() const
{
return mUpperBodyState == UpperCharState_StartToMinAttack ||
mUpperBodyState == UpperCharState_MinAttackToMaxAttack;
return mUpperBodyState == UpperBodyState::AttackPreWindUp ||
mUpperBodyState == UpperBodyState::AttackWindUp;
}
bool CharacterController::isCastingSpell() const
{
return mCastingManualSpell || mUpperBodyState == UpperCharState_CastingSpell;
return mCastingManualSpell || mUpperBodyState == UpperBodyState::Casting;
}
bool CharacterController::isReadyToBlock() const
@ -2729,8 +2679,7 @@ bool CharacterController::isRecovery() const
bool CharacterController::isAttackingOrSpell() const
{
return mUpperBodyState != UpperCharState_Nothing &&
mUpperBodyState != UpperCharState_WeapEquiped;
return mUpperBodyState != UpperBodyState::None && mUpperBodyState != UpperBodyState::WeaponEquipped;
}
bool CharacterController::isSneaking() const
@ -2786,7 +2735,7 @@ std::string_view CharacterController::getRandomAttackType()
bool CharacterController::readyToPrepareAttack() const
{
return (mHitState == CharState_None || mHitState == CharState_Block)
&& mUpperBodyState <= UpperCharState_WeapEquiped;
&& mUpperBodyState <= UpperBodyState::WeaponEquipped;
}
bool CharacterController::readyToStartAttack() const
@ -2794,7 +2743,7 @@ bool CharacterController::readyToStartAttack() const
if (mHitState != CharState_None && mHitState != CharState_Block)
return false;
return mUpperBodyState == UpperCharState_WeapEquiped;
return mUpperBodyState == UpperBodyState::WeaponEquipped;
}
float CharacterController::getAttackStrength() const
@ -2819,15 +2768,33 @@ void CharacterController::setHeadTrackTarget(const MWWorld::ConstPtr &target)
void CharacterController::playSwishSound(float attackStrength) const
{
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
ESM::WeaponType::Class weapclass = getWeaponType(mWeaponType)->mWeaponClass;
if (weapclass == ESM::WeaponType::Ranged || weapclass == ESM::WeaponType::Thrown)
return;
std::string sound = "Weapon Swish";
if(attackStrength < 0.5f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 0.8f); //Weak attack
else if(attackStrength < 1.0f)
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.0f); //Medium attack
std::string soundId;
float pitch = 1.f;
const MWWorld::Class &cls = mPtr.getClass();
if (cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf())
{
MWBase::World* world = MWBase::Environment::get().getWorld();
const MWWorld::ESMStore &store = world->getStore();
const ESM::Sound *sound = store.get<ESM::Sound>().searchRandom("WolfSwing", world->getPrng());
if (sound)
soundId = sound->mId;
}
else
sndMgr->playSound3D(mPtr, sound, 1.0f, 1.2f); //Strong attack
{
soundId = "Weapon Swish";
if (attackStrength < 0.5f)
pitch = 0.8f; // Weak attack
else if (attackStrength >= 1.f)
pitch = 1.2f; // Strong attack
}
if (!soundId.empty())
MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, soundId, 1.0f, pitch);
}
void CharacterController::updateHeadTracking(float duration)

View File

@ -103,17 +103,18 @@ enum CharacterState {
CharState_Block
};
enum UpperBodyCharacterState {
UpperCharState_Nothing,
UpperCharState_EquipingWeap,
UpperCharState_UnEquipingWeap,
UpperCharState_WeapEquiped,
UpperCharState_StartToMinAttack,
UpperCharState_MinAttackToMaxAttack,
UpperCharState_MaxAttackToMinHit,
UpperCharState_MinHitToHit,
UpperCharState_FollowStartToFollowStop,
UpperCharState_CastingSpell
enum class UpperBodyState
{
None,
Equipping,
Unequipping,
WeaponEquipped,
AttackPreWindUp,
AttackWindUp,
AttackRelease,
AttackHit,
AttackEnd,
Casting
};
enum JumpingState {
@ -156,7 +157,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
CharacterState mHitState{CharState_None};
std::string mCurrentHit;
UpperBodyCharacterState mUpperBodyState{UpperCharState_Nothing};
UpperBodyState mUpperBodyState{UpperBodyState::None};
JumpingState mJumpState{JumpState_None};
std::string mCurrentJump;
@ -203,7 +204,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void clearAnimQueue(bool clearPersistAnims = false);
bool updateWeaponState(CharacterState idle);
bool updateWeaponState();
void updateIdleStormState(bool inwater) const;
std::string chooseRandomAttackAnimation() const;

View File

@ -158,33 +158,16 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const
{
const MWWorld::Class &cls = mPtr.getClass();
MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr);
if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell)
if (cls.hasInventoryStore(mPtr) && stats.getDrawState() == MWMechanics::DrawState::Nothing)
{
SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield");
mObjectRoot->accept(findVisitor);
if (findVisitor.mFoundNode || (mPtr == MWMechanics::getPlayer() && mPtr.isInCell() && MWBase::Environment::get().getWorld()->isFirstPerson()))
if (findVisitor.mFoundNode)
{
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty())
{
if(stats.getDrawState() != MWMechanics::DrawState::Weapon)
return false;
if (weapon != inv.end())
{
auto type = weapon->getType();
if(type == ESM::Weapon::sRecordId)
{
const MWWorld::LiveCellRef<ESM::Weapon> *ref = weapon->get<ESM::Weapon>();
ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType;
return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded);
}
else if (type == ESM::Lockpick::sRecordId || type == ESM::Probe::sRecordId)
return true;
}
}
return false;
}
}
}

View File

@ -295,13 +295,23 @@ void NpcAnimation::setViewMode(NpcAnimation::ViewMode viewMode)
assert(viewMode != VM_HeadOnly);
if(mViewMode == viewMode)
return;
// FIXME: sheathing state must be consistent if the third person skeleton doesn't have the necessary node, but
// third person skeleton is unavailable in first person view. This is a hack to avoid cosmetic issues.
bool viewChange = mViewMode == VM_FirstPerson || viewMode == VM_FirstPerson;
mViewMode = viewMode;
MWBase::Environment::get().getWorld()->scaleObject(mPtr, mPtr.getCellRef().getScale(), true); // apply race height after view change
mAmmunition.reset();
rebuild();
setRenderBin();
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
if (viewChange && shieldSheathing)
{
int weaptype = ESM::Weapon::None;
MWMechanics::getActiveWeapon(mPtr, &weaptype);
showCarriedLeft(updateCarriedLeftVisible(weaptype));
}
}
/// @brief A RenderBin callback to clear the depth buffer before rendering.
@ -983,6 +993,30 @@ void NpcAnimation::showWeapons(bool showWeapon)
updateQuiver();
}
bool NpcAnimation::updateCarriedLeftVisible(const int weaptype) const
{
static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game");
if (shieldSheathing)
{
const MWWorld::Class &cls = mPtr.getClass();
MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr);
if (stats.getDrawState() == MWMechanics::DrawState::Nothing)
{
SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield");
mObjectRoot->accept(findVisitor);
if (findVisitor.mFoundNode || mViewMode == VM_FirstPerson)
{
const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr);
const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if (shield != inv.end() && shield->getType() == ESM::Armor::sRecordId && !getSheathedShieldMesh(*shield).empty())
return false;
}
}
}
return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded);
}
void NpcAnimation::showCarriedLeft(bool show)
{
mShowCarriedLeft = show;

View File

@ -137,6 +137,7 @@ public:
void showWeapons(bool showWeapon) override;
bool updateCarriedLeftVisible(const int weaptype) const override;
bool getCarriedLeftShown() const override { return mShowCarriedLeft; }
void showCarriedLeft(bool show) override;

View File

@ -1,6 +1,7 @@
#include "objectpaging.hpp"
#include <unordered_map>
#include <vector>
#include <osg/LOD>
#include <osg/Switch>
@ -276,7 +277,7 @@ namespace MWRender
RefnumSet(){}
RefnumSet(const RefnumSet& copy, const osg::CopyOp&) : mRefnums(copy.mRefnums) {}
META_Object(MWRender, RefnumSet)
std::set<ESM::RefNum> mRefnums;
std::vector<ESM::RefNum> mRefnums;
};
class AnalyzeVisitor : public osg::NodeVisitor
@ -554,7 +555,7 @@ namespace MWRender
if (cnode->getNumChildrenRequiringUpdateTraversal() > 0 || SceneUtil::hasUserDescription(cnode, Constants::NightDayLabel) || SceneUtil::hasUserDescription(cnode, Constants::HerbalismLabel))
continue;
else
refnumSet->mRefnums.insert(pair.first);
refnumSet->mRefnums.push_back(pair.first);
}
{
@ -727,6 +728,8 @@ namespace MWRender
osg::UserDataContainer* udc = group->getOrCreateUserDataContainer();
if (activeGrid)
{
std::sort(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end());
refnumSet->mRefnums.erase(std::unique(refnumSet->mRefnums.begin(), refnumSet->mRefnums.end()), refnumSet->mRefnums.end());
udc->addUserObject(refnumSet);
group->addCullCallback(new SceneUtil::LightListCallback);
}
@ -837,7 +840,7 @@ namespace MWRender
struct GetRefnumsFunctor
{
GetRefnumsFunctor(std::set<ESM::RefNum>& output) : mOutput(output) {}
GetRefnumsFunctor(std::vector<ESM::RefNum>& output) : mOutput(output) {}
void operator()(MWRender::ChunkId chunkId, osg::Object* obj)
{
if (!std::get<2>(chunkId)) return;
@ -850,18 +853,20 @@ namespace MWRender
{
RefnumSet* refnums = dynamic_cast<RefnumSet*>(udc->getUserObject(0));
if (!refnums) return;
mOutput.insert(refnums->mRefnums.begin(), refnums->mRefnums.end());
mOutput.insert(mOutput.end(), refnums->mRefnums.begin(), refnums->mRefnums.end());
}
}
osg::Vec4i mActiveGrid;
std::set<ESM::RefNum>& mOutput;
std::vector<ESM::RefNum>& mOutput;
};
void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
void ObjectPaging::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out)
{
GetRefnumsFunctor grf(out);
grf.mActiveGrid = activeGrid;
mCache->call(grf);
std::sort(out.begin(), out.end());
out.erase(std::unique(out.begin(), out.end()), out.end());
}
void ObjectPaging::reportStats(unsigned int frameNumber, osg::Stats *stats) const

View File

@ -47,7 +47,7 @@ namespace MWRender
void reportStats(unsigned int frameNumber, osg::Stats* stats) const override;
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out);
private:
Resource::SceneManager* mSceneManager;

View File

@ -100,21 +100,10 @@ namespace MWRender
if (filtered.empty() || !bufferData.postprocessing)
{
if (bufferData.postprocessing)
{
if (!mLoggedLastError)
{
Log(Debug::Error) << "Critical error, postprocess shaders failed to compile. Using default shader.";
mLoggedLastError = true;
}
}
else
mLoggedLastError = false;
state.pushStateSet(mFallbackStateSet);
state.apply();
if (Stereo::getMultiview() && mMultiviewResolveProgram)
if (Stereo::getMultiview())
{
state.pushStateSet(mMultiviewResolveStateSet);
state.apply();
@ -126,7 +115,7 @@ namespace MWRender
drawGeometry(renderInfo);
state.popStateSet();
if (Stereo::getMultiview() && mMultiviewResolveProgram)
if (Stereo::getMultiview())
{
state.popStateSet();
}
@ -298,7 +287,7 @@ namespace MWRender
state.popStateSet();
}
if (Stereo::getMultiview() && mMultiviewResolveProgram)
if (Stereo::getMultiview())
{
ext->glBindFramebuffer(GL_DRAW_FRAMEBUFFER_EXT, 0);
lastApplied = 0;

View File

@ -83,8 +83,6 @@ namespace MWRender
mutable std::array<BufferData, 2> mBufferData;
mutable std::array<osg::ref_ptr<osg::FrameBufferObject>, 3> mFbos;
mutable osg::ref_ptr<osg::Viewport> mRenderViewport;
mutable bool mLoggedLastError = false;
};
}

View File

@ -126,7 +126,6 @@ namespace MWRender
, mNormalsSupported(false)
, mPassLights(false)
, mPrevPassLights(false)
, mMainTemplate(new osg::Texture2D)
{
mSoftParticles = Settings::Manager::getBool("soft particles", "Shaders");
mUsePostProcessing = Settings::Manager::getBool("enabled", "Post Processing");
@ -249,14 +248,6 @@ namespace MWRender
populateTechniqueFiles();
}
mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
mMainTemplate->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
mMainTemplate->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
mMainTemplate->setInternalFormat(GL_RGBA);
mMainTemplate->setSourceType(GL_UNSIGNED_BYTE);
mMainTemplate->setSourceFormat(GL_RGBA);
createTexturesAndCamera(frame() % 2);
removeChild(mHUDCamera);
@ -402,13 +393,8 @@ namespace MWRender
mReload = false;
if (!mTechniques.empty())
reloadMainPass(*mTechniques[0]);
reloadTechniques();
if (!mUsePostProcessing)
resize();
loadChain();
resize();
}
void PostProcessor::update(size_t frameId)
@ -486,9 +472,6 @@ namespace MWRender
auto fpDepthRb = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples);
fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::PACKED_DEPTH_STENCIL_BUFFER, osg::FrameBufferAttachment(fpDepthRb));
// When MSAA is enabled we must first render to a render buffer, then
// blit the result to the FBO which is either passed to the main frame
// buffer for display or used as the entry point for a post process chain.
if (mSamples > 1)
{
fbos[FBO_Multisample] = new osg::FrameBufferObject;
@ -497,7 +480,6 @@ namespace MWRender
{
auto normalRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Normal], mSamples);
fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB);
fbos[FBO_FirstPerson]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER1, normalRB);
}
auto depthRB = createFrameBufferAttachmentFromTemplate(Usage::RENDER_BUFFER, width, height, textures[Tex_Depth], mSamples);
fbos[FBO_Multisample]->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, colorRB);
@ -670,7 +652,7 @@ namespace MWRender
return Status_Error;
}
if (!technique || technique->getLocked() || (location.has_value() && location.value() <= 0))
if (!technique || technique->getLocked() || (location.has_value() && location.value() < 0))
return Status_Error;
disableTechnique(technique, false);
@ -737,17 +719,6 @@ namespace MWRender
textures[Tex_Normal]->setSourceFormat(GL_RGB);
textures[Tex_Normal]->setInternalFormat(GL_RGB);
if (mMainTemplate)
{
textures[Tex_Scene]->setSourceFormat(mMainTemplate->getSourceFormat());
textures[Tex_Scene]->setSourceType(mMainTemplate->getSourceType());
textures[Tex_Scene]->setInternalFormat(mMainTemplate->getInternalFormat());
textures[Tex_Scene]->setFilter(osg::Texture2D::MIN_FILTER, mMainTemplate->getFilter(osg::Texture2D::MIN_FILTER));
textures[Tex_Scene]->setFilter(osg::Texture2D::MAG_FILTER, mMainTemplate->getFilter(osg::Texture2D::MAG_FILTER));
textures[Tex_Scene]->setWrap(osg::Texture::WRAP_S, mMainTemplate->getWrap(osg::Texture2D::WRAP_S));
textures[Tex_Scene]->setWrap(osg::Texture::WRAP_T, mMainTemplate->getWrap(osg::Texture2D::WRAP_T));
}
auto setupDepth = [] (osg::Texture* tex) {
tex->setSourceFormat(GL_DEPTH_STENCIL_EXT);
tex->setSourceType(SceneUtil::AutoDepth::depthSourceType());
@ -819,14 +790,12 @@ namespace MWRender
return technique;
}
reloadMainPass(*technique);
mTemplates.push_back(std::move(technique));
return mTemplates.back();
}
void PostProcessor::reloadTechniques()
void PostProcessor::loadChain()
{
if (!isEnabled())
return;
@ -835,18 +804,9 @@ namespace MWRender
std::vector<std::string> techniqueStrings = Settings::Manager::getStringArray("chain", "Post Processing");
const std::string mainIdentifier = "main";
auto main = loadTechnique(mainIdentifier);
if (main)
main->setLocked(true);
mTechniques.push_back(std::move(main));
for (auto& techniqueName : techniqueStrings)
{
if (techniqueName.empty() || Misc::StringUtils::ciEqual(techniqueName, mainIdentifier))
if (techniqueName.empty())
continue;
mTechniques.push_back(loadTechnique(techniqueName));
@ -855,14 +815,17 @@ namespace MWRender
dirtyTechniques();
}
void PostProcessor::reloadMainPass(fx::Technique& technique)
void PostProcessor::saveChain()
{
if (!technique.getMainTemplate())
return;
std::vector<std::string> chain;
mMainTemplate = technique.getMainTemplate();
for (const auto& technique : mTechniques) {
if (!technique || technique->getDynamic())
continue;
chain.push_back(technique->getName());
}
resize();
Settings::Manager::setStringArray("chain", "Post Processing", chain);
}
void PostProcessor::toggleMode()

View File

@ -86,6 +86,13 @@ namespace MWRender
Unit_NextFree
};
enum Status
{
Status_Error,
Status_Toggled,
Status_Unchanged
};
PostProcessor(RenderingManager& rendering, osgViewer::Viewer* viewer, osg::Group* rootNode, const VFS::Manager* vfs);
~PostProcessor();
@ -110,13 +117,6 @@ namespace MWRender
void resize();
enum Status
{
Status_Error,
Status_Toggled,
Status_Unchanged
};
Status enableTechnique(std::shared_ptr<fx::Technique> technique, std::optional<int> location = std::nullopt);
Status disableTechnique(std::shared_ptr<fx::Technique> technique, bool dirty = true);
@ -184,6 +184,10 @@ namespace MWRender
bool mEnableLiveReload;
void loadChain();
void saveChain();
private:
void populateTechniqueFiles();
@ -194,8 +198,6 @@ namespace MWRender
void createTexturesAndCamera(size_t frameId);
void reloadTechniques();
void reloadMainPass(fx::Technique& technique);
void dirtyTechniques();
@ -250,8 +252,6 @@ namespace MWRender
bool mUBO;
int mGLSLVersion;
osg::ref_ptr<osg::Texture> mMainTemplate;
osg::ref_ptr<fx::StateUpdater> mStateUpdater;
osg::ref_ptr<PingPongCull> mPingPongCull;
osg::ref_ptr<PingPongCanvas> mPingPongCanvas;

View File

@ -637,7 +637,6 @@ namespace MWRender
updateProjectionMatrix();
mViewer->getCamera()->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
mViewer->getUpdateVisitor()->setTraversalMode(osg::NodeVisitor::TRAVERSE_ACTIVE_CHILDREN);
}
RenderingManager::~RenderingManager()
@ -1616,7 +1615,7 @@ namespace MWRender
}
return false;
}
void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out)
void RenderingManager::getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out)
{
if (mObjectPaging)
mObjectPaging->getPagedRefnums(activeGrid, out);

View File

@ -250,7 +250,7 @@ namespace MWRender
bool pagingEnableObject(int type, const MWWorld::ConstPtr& ptr, bool enabled);
void pagingBlacklistObject(int type, const MWWorld::ConstPtr &ptr);
bool pagingUnlockCache();
void getPagedRefnums(const osg::Vec4i &activeGrid, std::set<ESM::RefNum> &out);
void getPagedRefnums(const osg::Vec4i &activeGrid, std::vector<ESM::RefNum>& out);
void updateProjectionMatrix();

View File

@ -109,9 +109,6 @@ namespace MWRender
if (rl->_drawable->getNodeMask() == Mask_ParticleSystem || rl->_drawable->getNodeMask() == Mask_Effect)
continue;
if (ss->getAttribute(osg::StateAttribute::ALPHAFUNC))
continue;
if (ss->getAttribute(osg::StateAttribute::MATERIAL)) {
const osg::Material* mat = static_cast<const osg::Material*>(ss->getAttribute(osg::StateAttribute::MATERIAL));
if (mat->getDiffuse(osg::Material::FRONT).a() < 0.5)

View File

@ -99,8 +99,8 @@ namespace
return model;
}
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, MWPhysics::PhysicsSystem& physics,
MWRender::RenderingManager& rendering, std::set<ESM::RefNum>& pagedRefs)
void addObject(const MWWorld::Ptr& ptr, const MWWorld::World& world, const std::vector<ESM::RefNum>& pagedRefs,
MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering)
{
if (ptr.getRefData().getBaseNode() || physics.getActor(ptr))
{
@ -112,7 +112,7 @@ namespace
const auto rotation = makeDirectNodeRotation(ptr);
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
if (!refnum.hasContentFile() || pagedRefs.find(refnum) == pagedRefs.end())
if (!refnum.hasContentFile() || !std::binary_search(pagedRefs.begin(), pagedRefs.end(), refnum))
ptr.getClass().insertObjectRendering(ptr, model, rendering);
else
ptr.getRefData().setBaseNode(new SceneUtil::PositionAttitudeTransform); // FIXME remove this when physics code is fixed not to depend on basenode
@ -255,6 +255,15 @@ namespace
}
return false;
}
bool removeFromSorted(const ESM::RefNum& refNum, std::vector<ESM::RefNum>& pagedRefs)
{
const auto it = std::lower_bound(pagedRefs.begin(), pagedRefs.end(), refNum);
if (it == pagedRefs.end() || *it != refNum)
return false;
pagedRefs.erase(it);
return true;
}
}
@ -264,7 +273,7 @@ namespace MWWorld
void Scene::removeFromPagedRefs(const Ptr &ptr)
{
const ESM::RefNum& refnum = ptr.getCellRef().getRefNum();
if (refnum.hasContentFile() && mPagedRefs.erase(refnum))
if (refnum.hasContentFile() && removeFromSorted(refnum, mPagedRefs))
{
if (!ptr.getRefData().getBaseNode()) return;
ptr.getClass().insertObjectRendering(ptr, getModel(ptr, mRendering.getResourceSystem()->getVFS()), mRendering);
@ -900,7 +909,7 @@ namespace MWWorld
{
InsertVisitor insertVisitor(cell, loadingListener);
cell.forEach (insertVisitor);
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs); });
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering); });
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, mWorld, *mPhysics, mNavigator); });
}
@ -908,7 +917,7 @@ namespace MWWorld
{
try
{
addObject(ptr, mWorld, *mPhysics, mRendering, mPagedRefs);
addObject(ptr, mWorld, mPagedRefs, *mPhysics, mRendering);
addObject(ptr, mWorld, *mPhysics, mNavigator);
mWorld.scaleObject(ptr, ptr.getCellRef().getScale());
if (mCurrentCell != nullptr)

View File

@ -104,7 +104,7 @@ namespace MWWorld
osg::Vec3f mLastPlayerPos;
std::set<ESM::RefNum> mPagedRefs;
std::vector<ESM::RefNum> mPagedRefs;
std::vector<osg::ref_ptr<SceneUtil::WorkItem>> mWorkItems;

View File

@ -1,130 +1,130 @@
find_package(GTest 1.10 REQUIRED)
find_package(GMock 1.10 REQUIRED)
if (GTEST_FOUND AND GMOCK_FOUND)
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS})
file(GLOB UNITTEST_SRC_FILES
testing_util.hpp
../openmw/mwworld/store.cpp
../openmw/mwworld/esmstore.cpp
mwworld/test_store.cpp
mwdialogue/test_keywordsearch.cpp
mwscript/test_scripts.cpp
esm/test_fixed_string.cpp
esm/variant.cpp
lua/test_lua.cpp
lua/test_scriptscontainer.cpp
lua/test_utilpackage.cpp
lua/test_serialization.cpp
lua/test_configuration.cpp
lua/test_l10n.cpp
lua/test_storage.cpp
lua/test_ui_content.cpp
misc/test_stringops.cpp
misc/test_endianness.cpp
misc/test_resourcehelpers.cpp
misc/progressreporter.cpp
misc/compression.cpp
nifloader/testbulletnifloader.cpp
detournavigator/navigator.cpp
detournavigator/settingsutils.cpp
detournavigator/recastmeshbuilder.cpp
detournavigator/gettilespositions.cpp
detournavigator/recastmeshobject.cpp
detournavigator/navmeshtilescache.cpp
detournavigator/tilecachedrecastmeshmanager.cpp
detournavigator/navmeshdb.cpp
detournavigator/serialization.cpp
detournavigator/asyncnavmeshupdater.cpp
serialization/binaryreader.cpp
serialization/binarywriter.cpp
serialization/sizeaccumulator.cpp
serialization/integration.cpp
settings/parser.cpp
settings/shadermanager.cpp
shader/parsedefines.cpp
shader/parsefors.cpp
shader/parselinks.cpp
shader/shadermanager.cpp
../openmw/options.cpp
openmw/options.cpp
sqlite3/db.cpp
sqlite3/request.cpp
sqlite3/statement.cpp
sqlite3/transaction.cpp
esmloader/load.cpp
esmloader/esmdata.cpp
esmloader/record.cpp
files/hash.cpp
toutf8/toutf8.cpp
esm4/includes.cpp
fx/lexer.cpp
fx/technique.cpp
esm3/readerscache.cpp
)
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components)
# Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE)
target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT})
endif()
if (BUILD_WITH_CODE_COVERAGE)
add_definitions(--coverage)
target_link_libraries(openmw_test_suite gcov)
endif()
file(DOWNLOAD
https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame
${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame
EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6
)
target_compile_definitions(openmw_test_suite
PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data"
OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw_test_suite PRIVATE
<boost/program_options/options_description.hpp>
<gtest/gtest.h>
<sol/sol.hpp>
<algorithm>
<filesystem>
<fstream>
<functional>
<memory>
<string>
<vector>
)
endif()
if (OPENMW_USE_SYSTEM_GOOGLETEST)
find_package(GTest 1.10 REQUIRED)
find_package(GMock 1.10 REQUIRED)
endif()
include_directories(SYSTEM ${GTEST_INCLUDE_DIRS})
include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS})
file(GLOB UNITTEST_SRC_FILES
testing_util.hpp
../openmw/mwworld/store.cpp
../openmw/mwworld/esmstore.cpp
mwworld/test_store.cpp
mwdialogue/test_keywordsearch.cpp
mwscript/test_scripts.cpp
esm/test_fixed_string.cpp
esm/variant.cpp
lua/test_lua.cpp
lua/test_scriptscontainer.cpp
lua/test_utilpackage.cpp
lua/test_serialization.cpp
lua/test_configuration.cpp
lua/test_l10n.cpp
lua/test_storage.cpp
lua/test_ui_content.cpp
misc/test_stringops.cpp
misc/test_endianness.cpp
misc/test_resourcehelpers.cpp
misc/progressreporter.cpp
misc/compression.cpp
nifloader/testbulletnifloader.cpp
detournavigator/navigator.cpp
detournavigator/settingsutils.cpp
detournavigator/recastmeshbuilder.cpp
detournavigator/gettilespositions.cpp
detournavigator/recastmeshobject.cpp
detournavigator/navmeshtilescache.cpp
detournavigator/tilecachedrecastmeshmanager.cpp
detournavigator/navmeshdb.cpp
detournavigator/serialization.cpp
detournavigator/asyncnavmeshupdater.cpp
serialization/binaryreader.cpp
serialization/binarywriter.cpp
serialization/sizeaccumulator.cpp
serialization/integration.cpp
settings/parser.cpp
settings/shadermanager.cpp
shader/parsedefines.cpp
shader/parsefors.cpp
shader/parselinks.cpp
shader/shadermanager.cpp
../openmw/options.cpp
openmw/options.cpp
sqlite3/db.cpp
sqlite3/request.cpp
sqlite3/statement.cpp
sqlite3/transaction.cpp
esmloader/load.cpp
esmloader/esmdata.cpp
esmloader/record.cpp
files/hash.cpp
toutf8/toutf8.cpp
esm4/includes.cpp
fx/lexer.cpp
fx/technique.cpp
esm3/readerscache.cpp
)
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
target_link_libraries(openmw_test_suite GTest::GTest GMock::GMock components)
# Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE)
target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT})
endif()
if (BUILD_WITH_CODE_COVERAGE)
add_definitions(--coverage)
target_link_libraries(openmw_test_suite gcov)
endif()
file(DOWNLOAD
https://gitlab.com/OpenMW/example-suite/-/raw/8966dab24692555eec720c854fb0f73d108070cd/data/template.omwgame
${CMAKE_CURRENT_BINARY_DIR}/data/template.omwgame
EXPECTED_HASH SHA512=6e38642bcf013c5f496a9cb0bf3ec7c9553b6e86b836e7844824c5a05f556c9391167214469b6318401684b702d7569896bf743c85aee4198612b3315ba778d6
)
target_compile_definitions(openmw_test_suite
PRIVATE OPENMW_DATA_DIR="${CMAKE_CURRENT_BINARY_DIR}/data"
OPENMW_TEST_SUITE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16 AND MSVC)
target_precompile_headers(openmw_test_suite PRIVATE
<boost/program_options/options_description.hpp>
<gtest/gtest.h>
<sol/sol.hpp>
<algorithm>
<filesystem>
<fstream>
<functional>
<memory>
<string>
<vector>
)
endif()

View File

@ -87,20 +87,6 @@ TEST(EsmFixedString, empty_strings)
}
}
TEST(EsmFixedString, struct_size)
{
ASSERT_EQ(4, sizeof(ESM::NAME));
ASSERT_EQ(32, sizeof(ESM::NAME32));
ASSERT_EQ(64, sizeof(ESM::NAME64));
}
TEST(EsmFixedString, is_pod)
{
ASSERT_TRUE(std::is_pod<ESM::NAME>::value);
ASSERT_TRUE(std::is_pod<ESM::NAME32>::value);
ASSERT_TRUE(std::is_pod<ESM::NAME64>::value);
}
TEST(EsmFixedString, assign_should_zero_untouched_bytes_for_4)
{
ESM::NAME value;

View File

@ -23,7 +23,6 @@ namespace
TEST_F(LexerSingleTokenTest, single_token_shared) { test<Shared>(); }
TEST_F(LexerSingleTokenTest, single_token_technique) { test<Technique>(); }
TEST_F(LexerSingleTokenTest, single_token_main_pass) { test<Main_Pass>(); }
TEST_F(LexerSingleTokenTest, single_token_render_target) { test<Render_Target>(); }
TEST_F(LexerSingleTokenTest, single_token_vertex) { test<Vertex>(); }
TEST_F(LexerSingleTokenTest, single_token_fragment) { test<Fragment>(); }

View File

@ -8,6 +8,7 @@
#include <cstdint>
#include <cassert>
#include <limits>
#include <type_traits>
namespace ESM
{
@ -171,6 +172,14 @@ using NAME = FixedString<4>;
using NAME32 = FixedString<32>;
using NAME64 = FixedString<64>;
static_assert(std::is_standard_layout_v<NAME> && std::is_trivial_v<NAME>);
static_assert(std::is_standard_layout_v<NAME32> && std::is_trivial_v<NAME32>);
static_assert(std::is_standard_layout_v<NAME64> && std::is_trivial_v<NAME64>);
static_assert(sizeof(NAME) == 4);
static_assert(sizeof(NAME32) == 32);
static_assert(sizeof(NAME64) == 64);
/* This struct defines a file 'context' which can be saved and later
restored by an ESMReader instance. It will save the position within
a file, and when restored will let you read from that position as

View File

@ -230,7 +230,6 @@ namespace fx
if (value == "shared") return Shared{};
if (value == "technique") return Technique{};
if (value == "main_pass") return Main_Pass{};
if (value == "render_target") return Render_Target{};
if (value == "vertex") return Vertex{};
if (value == "fragment") return Fragment{};

View File

@ -18,7 +18,6 @@ namespace fx
struct Fragment { inline static constexpr std::string_view repr = "fragment"; };
struct Compute { inline static constexpr std::string_view repr = "compute"; };
struct Technique { inline static constexpr std::string_view repr = "technique"; };
struct Main_Pass { inline static constexpr std::string_view repr = "main_pass"; };
struct Render_Target { inline static constexpr std::string_view repr = "render_target"; };
struct Sampler_1D { inline static constexpr std::string_view repr = "sampler_1d"; };
struct Sampler_2D { inline static constexpr std::string_view repr = "sampler_2d"; };
@ -49,7 +48,7 @@ namespace fx
using Token = std::variant<Float, Integer, Boolean, String, Literal, Equal, Open_bracket, Close_bracket, Open_Parenthesis,
Close_Parenthesis, Quote, SemiColon, Comma, VBar, Colon, Shared, Technique, Render_Target, Vertex, Fragment,
Compute, Sampler_1D, Sampler_2D, Sampler_3D, Uniform_Bool, Uniform_Float, Uniform_Int, Uniform_Vec2, Uniform_Vec3, Uniform_Vec4,
True, False, Vec2, Vec3, Vec4, Main_Pass, Eof>;
True, False, Vec2, Vec3, Vec4, Eof>;
}
}

View File

@ -65,7 +65,6 @@ namespace fx
mPassKeys.clear();
mDefinedUniforms.clear();
mRenderTargets.clear();
mMainTemplate = nullptr;
mLastAppliedType = Pass::Type::None;
mFlags = 0;
mShared.clear();
@ -262,47 +261,6 @@ namespace fx
error("pass list in 'technique' block cannot be empty.");
}
template<>
void Technique::parseBlockImp<Lexer::Main_Pass>()
{
if (mMainTemplate)
error("duplicate 'main_pass' block");
if (mName != "main")
error("'main_pass' block can only be defined in the 'main.omwfx' technique file");
mMainTemplate = new osg::Texture2D;
mMainTemplate->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
mMainTemplate->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
while (!isNext<Lexer::Close_bracket>() && !isNext<Lexer::Eof>())
{
expect<Lexer::Literal>();
auto key = std::get<Lexer::Literal>(mToken).value;
expect<Lexer::Equal>();
if (key == "wrap_s")
mMainTemplate->setWrap(osg::Texture::WRAP_S, parseWrapMode());
else if (key == "wrap_t")
mMainTemplate->setWrap(osg::Texture::WRAP_T, parseWrapMode());
// Skip depth attachments for main scene, as some engine settings rely on specific depth formats.
// Allowing this to be overriden will cause confusion.
else if (key == "internal_format")
mMainTemplate->setInternalFormat(parseInternalFormat());
else if (key == "source_type")
mMainTemplate->setSourceType(parseSourceType());
else if (key == "source_format")
mMainTemplate->setSourceFormat(parseSourceFormat());
else
error(Misc::StringUtils::format("unexpected key '%s'", std::string(key)));
expect<Lexer::SemiColon>();
}
}
template<>
void Technique::parseBlockImp<Lexer::Render_Target>()
{
@ -738,8 +696,6 @@ namespace fx
parseBlock<Lexer::Shared>(false);
else if constexpr (std::is_same_v<Lexer::Technique, T>)
parseBlock<Lexer::Technique>(false);
else if constexpr (std::is_same_v<Lexer::Main_Pass, T>)
parseBlock<Lexer::Main_Pass>(false);
else if constexpr (std::is_same_v<Lexer::Render_Target, T>)
parseBlock<Lexer::Render_Target>();
else if constexpr (std::is_same_v<Lexer::Vertex, T>)

View File

@ -159,8 +159,6 @@ namespace fx
const std::unordered_set<std::string>& getGLSLExtensions() const { return mGLSLExtensions; }
osg::ref_ptr<osg::Texture2D> getMainTemplate() const { return mMainTemplate; }
FlagsType getFlags() const { return mFlags; }
bool getHidden() const { return mFlags & Flag_Hidden; }
@ -273,7 +271,6 @@ namespace fx
int mWidth;
int mHeight;
osg::ref_ptr<osg::Texture2D> mMainTemplate;
RenderTargetMap mRenderTargets;
TexList mTextures;
@ -301,7 +298,6 @@ namespace fx
template<> void Technique::parseBlockImp<Lexer::Shared>();
template<> void Technique::parseBlockImp<Lexer::Technique>();
template<> void Technique::parseBlockImp<Lexer::Main_Pass>();
template<> void Technique::parseBlockImp<Lexer::Render_Target>();
template<> void Technique::parseBlockImp<Lexer::Vertex>();
template<> void Technique::parseBlockImp<Lexer::Fragment>();

View File

@ -40,7 +40,7 @@ namespace Nif
horizontalDir = nif->getFloat();
horizontalAngle = nif->getFloat();
/*normal?*/ nif->getVector3();
/*color?*/ nif->getVector4();
color = nif->getVector4();
size = nif->getFloat();
startTime = nif->getFloat();
stopTime = nif->getFloat();

View File

@ -52,6 +52,7 @@ struct NiParticleSystemController : public Controller
float horizontalDir;
float horizontalAngle;
osg::Vec4f color;
float size;
float startTime;
float stopTime;

View File

@ -1050,9 +1050,8 @@ namespace NifOsg
const osg::Vec3f& position = particledata->vertices[particle.vertex];
created->setPosition(position);
osg::Vec4f partcolor (1.f,1.f,1.f,1.f);
if (particle.vertex < particledata->colors.size())
partcolor = particledata->colors[particle.vertex];
created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color));
created->setAlphaRange(osgParticle::rangef(1.f, 1.f));
float size = partctrl->size;
if (particle.vertex < particledata->sizes.size())
@ -1171,7 +1170,7 @@ namespace NifOsg
handleParticleInitialState(nifNode, partsys, partctrl);
partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size));
partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(osg::Vec4f(1.f,1.f,1.f,1.f), osg::Vec4f(1.f,1.f,1.f,1.f)));
partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color));
partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f));
if (!partctrl->emitter.empty())

View File

@ -476,16 +476,14 @@ namespace SceneUtil
{
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
osg::ref_ptr<osg::IntArray> indices = new osg::IntArray(mLightManager->getMaxLights());
osg::ref_ptr<osg::Uniform> indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", indices->size());
osg::ref_ptr<osg::Uniform> indicesUni = new osg::Uniform(osg::Uniform::Type::INT, "PointLightIndex", mLightManager->getMaxLights());
int pointCount = 0;
for (size_t i = 0; i < lightList.size(); ++i)
{
int bufIndex = mLightManager->getLightIndexMap(frameNum)[lightList[i]->mLightSource->getId()];
indices->at(pointCount++) = bufIndex;
indicesUni->setElement(pointCount++, bufIndex);
}
indicesUni->setArray(indices);
stateset->addUniform(indicesUni);
stateset->addUniform(new osg::Uniform("PointLightCount", pointCount));
@ -608,20 +606,32 @@ namespace SceneUtil
class LightManagerCullCallback : public SceneUtil::NodeCallback<LightManagerCullCallback, LightManager*, osgUtil::CullVisitor*>
{
public:
void operator()(LightManager* node, osgUtil::CullVisitor* cv)
LightManagerCullCallback(LightManager* lightManager)
{
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
if (!lightManager->getUBOManager())
return;
if (node->getLightingMethod() == LightingMethod::SingleUBO)
for (size_t i = 0; i < mStateSet.size(); ++i)
{
auto buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber());
auto& buffer = lightManager->getUBOManager()->getLightBuffer(i);
#if OSG_VERSION_GREATER_OR_EQUAL(3,5,7)
osg::ref_ptr<osg::UniformBufferBinding> ubb = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData(), 0, buffer->getData()->getTotalDataSize());
#else
osg::ref_ptr<osg::UniformBufferBinding> ubb = new osg::UniformBufferBinding(static_cast<int>(Resource::SceneManager::UBOBinding::LightBuffer), buffer->getData()->getBufferObject(), 0, buffer->getData()->getTotalDataSize());
#endif
stateset->setAttributeAndModes(ubb, osg::StateAttribute::ON);
mStateSet[i]->setAttributeAndModes(ubb, osg::StateAttribute::ON);
}
}
void operator()(LightManager* node, osgUtil::CullVisitor* cv)
{
const size_t frameId = cv->getTraversalNumber() % 2;
auto& stateset = mStateSet[frameId];
if (node->getLightingMethod() == LightingMethod::SingleUBO)
{
auto& buffer = node->getUBOManager()->getLightBuffer(cv->getTraversalNumber());
if (auto sun = node->getSunlight())
{
@ -652,6 +662,8 @@ namespace SceneUtil
if (node->getPPLightsBuffer() && cv->getCurrentCamera()->getName() == Constants::SceneCamera)
node->getPPLightsBuffer()->updateCount(cv->getTraversalNumber());
}
std::array<osg::ref_ptr<osg::StateSet>, 2> mStateSet = { new osg::StateSet, new osg::StateSet };
};
UBOManager::UBOManager(int lightCount)
@ -838,7 +850,7 @@ namespace SceneUtil
getOrCreateStateSet()->addUniform(new osg::Uniform("PointLightCount", 0));
addCullCallback(new LightManagerCullCallback);
addCullCallback(new LightManagerCullCallback(this));
}
LightManager::LightManager(const LightManager &copy, const osg::CopyOp &copyop)

18
extern/CMakeLists.txt vendored
View File

@ -301,3 +301,21 @@ if (NOT OPENMW_USE_SYSTEM_ICU)
endforeach()
set(ICU_LIBRARIES ICU::i18n ICU::uc ICU::data PARENT_SCOPE)
endif()
if (BUILD_UNITTESTS AND NOT OPENMW_USE_SYSTEM_GOOGLETEST)
cmake_minimum_required(VERSION 3.11)
include(FetchContent)
FetchContent_Declare(googletest
URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip
URL_HASH SHA512=1479ea2f3172c622c0ca305f5b2bc45a42941221ec0ac7865e6d6d020ec4d008d952fc64e01a4c5138d7bed4148cf75596f25bb9e9044a98bbbf5662053ea11c
SOURCE_DIR fetched/googletest
)
if (MSVC)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
endif()
FetchContent_MakeAvailableExcludeFromAll(googletest)
add_library(GTest::GTest ALIAS gtest)
add_library(GMock::GMock ALIAS gmock)
endif()

View File

@ -87,8 +87,8 @@ set(BUILTIN_DATA_FILES
scripts/omw/mwui/space.lua
scripts/omw/mwui/init.lua
shaders/main.omwfx
shaders/displaydepth.omwfx
shaders/adjustments.omwfx
shaders/debug.omwfx
mygui/core.skin
mygui/core.xml

View File

@ -1,3 +1,7 @@
DisplayDepthDescription: "Visualisiert den Tiefenpuffer."
DisplayDepthName: "Visualisiert den Tiefenpuffer."
DisplayDepthFactorDescription: "Bestimmt die Korrelation zwischen dem Pixeltiefenwert und seiner Ausgabefarbe. Hohe Werte führen zu einem helleren Bild."
DisplayDepthFactorName: "Farbfaktor"
ContrastLevelDescription: "Kontraststufe"
ContrastLevelName: "Kontrast"
GammaLevelDescription: "Gamma-Level"
GammaLevelName: "Gamma"

View File

@ -1,3 +1,12 @@
DisplayDepthDescription: "Visualizes the depth buffer."
DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output color. High values lead to brighter image."
DisplayDepthFactorName: "Color factor"
AdjustmentsDescription: "Colour adjustments."
DebugDescription: "Debug shader."
DebugHeaderDepth: "Depth Buffer"
DebugHeaderNormals: "Normals"
DisplayDepthFactorName: "Depth colour factor"
DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image."
DisplayDepthName: "Visualize depth buffer"
DisplayNormalsName: "Visualize pass normals"
ContrastLevelDescription: "Constrast level"
ContrastLevelName: "Constrast"
GammaLevelDescription: "Gamma level"
GammaLevelName: "Gamma"

View File

@ -1,3 +1,12 @@
DisplayDepthDescription: "Визуализирует буфер глубины."
AdjustmentsDescription: "Коррекция цвета."
DebugDescription: "Отладочный шейдер."
DebugHeaderDepth: "Буфер глубины"
DebugHeaderNormals: "Нормали"
DisplayDepthName: "Визуализация буфера глубины"
DisplayDepthFactorDescription: "Определяет соотношение между значением глубины пикселя и его цветом. Чем выше значение, тем ярче будет изображение."
DisplayDepthFactorName: "Соотношение цвета"
DisplayNormalsName: "Визуализация нормалей"
ContrastLevelDescription: "Контрастность изображения"
ContrastLevelName: "Контрастность"
GammaLevelDescription: "Яркость изображения"
GammaLevelName: "Яркость"

View File

@ -1,3 +1,7 @@
DisplayDepthDescription: "Visualiserar djupbufferten."
DisplayDepthName: "Visualiserar djupbufferten."
DisplayDepthFactorDescription: "Avgör korrelation mellan djupvärdet på pixeln och dess producerade färg. Högre värden ger ljusare bild."
DisplayDepthFactorName: "Färgfaktor"
ContrastLevelDescription: "Kontrastnivå"
ContrastLevelName: "Kontrast"
GammaLevelDescription: "Gammanivå"
# GammaLevelName: "Gamma" samma som en

View File

@ -1,11 +1,7 @@
Abovewater: "Überwasser"
Author: "Autor"
Configuration: "Konfiguration"
ContrastLevelDescription: "Kontraststufe"
ContrastLevelName: "Kontrast"
Description: "Beschreibung"
GammaLevelDescription: "Gamma-Level"
GammaLevelName: "Gamma"
InExteriors: "Außenbereich"
InInteriors: "Innenbereich"
KeyboardControls: |

View File

@ -1,11 +1,7 @@
Abovewater: "Abovewater"
Author: "Author"
Configuration: "Configuration"
ContrastLevelDescription: "Constrast level"
ContrastLevelName: "Constrast"
Description: "Description"
GammaLevelDescription: "Gamma level"
GammaLevelName: "Gamma"
InExteriors: "Exteriors"
InInteriors: "Interiors"
KeyboardControls: |
@ -15,7 +11,6 @@ KeyboardControls: |
Shift+Left-Arrow > Deactive shader
Shift+Up-Arrow > Move shader up
Shift+Down-Arrow > Move shader down
MainPassDescription: "Passes scene data to post processing shaders. Can not be toggled or moved."
PostProcessHUD: "Postprocess HUD"
ResetShader: "Reset shader to default state"
ShaderLocked: "Locked"

View File

@ -1,11 +1,7 @@
Abovewater: "Над водой"
Author: "Автор"
Configuration: "Настройки"
ContrastLevelDescription: "Контрастность изображения"
ContrastLevelName: "Контрастность"
Description: "Описание"
GammaLevelDescription: "Яркость изображения"
GammaLevelName: "Яркость"
InExteriors: "Вне помещений"
InInteriors: "В помещениях"
KeyboardControls: |
@ -15,7 +11,6 @@ KeyboardControls: |
Shift+Left-Arrow > Выключить шейдер
Shift+Up-Arrow > Передвинуть шейдер выше
Shift+Down-Arrow > Передвинуть шейдер ниже
MainPassDescription: "Передает данные сцены в шейдеры постобработки. Не может быть выключен или передвинут."
PostProcessHUD: "Настройки постобработки"
ResetShader: "Обнулить настройки этого шейдера"
ShaderLocked: "Заблокирован"

View File

@ -1,11 +1,7 @@
Abovewater: "Ovan vatten"
Author: "Skapare" # Author = Författare, but författare sounds very book-author-ish. Skapare, meaning "creator", sounds better in Swedish in this case. Ok?
Configuration: "Konfiguration"
ContrastLevelDescription: "Kontrastnivå"
ContrastLevelName: "Kontrast"
Description: "Beskrivning"
GammaLevelDescription: "Gammanivå"
# GammaLevelName: "Gamma" samma som en
InExteriors: "Exteriörer"
InInteriors: "Interiörer"
KeyboardControls: |
@ -15,7 +11,6 @@ KeyboardControls: |
Shift+Vänsterpil > Avaktivera shader
Shift+Pil upp > Flytta shader upp
Shift+Pil ner > Flytta shader ner
MainPassDescription: "Flyttar scendata till postprocess-shaders. Kan ej slås på/av eller flyttas."
PostProcessHUD: "Postprocess HUD"
ResetShader: "Återställ shader to ursprungligt läge"
ShaderLocked: "Låst"

View File

@ -1,18 +1,10 @@
main_pass {
wrap_s = clamp_to_edge;
wrap_t = clamp_to_edge;
internal_format = rgba;
source_type = unsigned_int;
source_format = rgba;
}
uniform_float uGamma {
default = 1.0;
step = 0.01;
min = 0.0;
max = 5.0;
display_name = "#{PostProcessing:GammaLevelName}";
description = "#{PostProcessing:GammaLevelDescription}";
display_name = "#{BuiltInShaders:GammaLevelName}";
description = "#{BuiltInShaders:GammaLevelDescription}";
}
uniform_float uContrast {
@ -20,8 +12,8 @@ uniform_float uContrast {
step = 0.01;
min = 0.0;
max = 5.0;
display_name = "#{PostProcessing:ContrastLevelName}";
description = "#{PostProcessing:ContrastLevelDescription}";
display_name = "#{BuiltInShaders:ContrastLevelName}";
description = "#{BuiltInShaders:ContrastLevelDescription}";
}
fragment main {
@ -39,9 +31,8 @@ fragment main {
}
technique {
description = "#{PostProcessing:MainPassDescription}";
description = "#{BuiltInShaders:AdjustmentsDescription}";
version = "1.0";
author = "OpenMW";
passes = main;
hdr = false;
}

View File

@ -0,0 +1,45 @@
uniform_bool uDisplayDepth {
header = "#{BuiltInShaders:DebugHeaderDepth}";
default = true;
display_name = "#{BuiltInShaders:DisplayDepthName}";
}
uniform_float uDepthFactor {
step = 0.1;
min = 0.01;
max = 20.0;
default = 1.0;
display_name = "#{BuiltInShaders:DisplayDepthFactorName}";
description = "#{BuiltInShaders:DisplayDepthFactorDescription}";
}
uniform_bool uDisplayNormals {
header = "#{BuiltInShaders:DebugHeaderNormals}";
default = true;
display_name = "#{BuiltInShaders:DisplayNormalsName}";
}
fragment main {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor = omw_GetLastShader(omw_TexCoord);
if (uDisplayDepth)
omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uDepthFactor), 1.0);
#if OMW_NORMALS
if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5))
omw_FragColor.rgb = omw_GetNormals(omw_TexCoord);
#endif
}
}
technique {
passes = main;
description = "#{BuiltInShaders:DebugDescription}";
author = "OpenMW";
version = "1.0";
pass_normals = true;
}

View File

@ -1,25 +0,0 @@
uniform_float uFactor {
step = 0.1;
min = 0.01;
max = 20.0;
default = 1.0;
display_name = "#{BuiltInShaders:DisplayDepthFactorName}";
description = "#{BuiltInShaders:DisplayDepthFactorDescription}";
}
fragment main {
omw_In vec2 omw_TexCoord;
void main()
{
omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uFactor), 1.0);
}
}
technique {
passes = main;
description = "#{BuiltInShaders:DisplayDepthDescription}";
author = "OpenMW";
version = "1.0";
}