mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-16 18:19:55 +00:00
Merge upstream/master
This commit is contained in:
commit
3785ba6aa0
190 changed files with 10010 additions and 1123 deletions
|
@ -15,11 +15,11 @@ Debian:
|
|||
- apt-get update -yq
|
||||
- apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev
|
||||
# - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||
- curl http://ftp.us.debian.org/debian/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
|
||||
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb
|
||||
- curl -L http://ftp.us.debian.org/debian/pool/main/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb
|
||||
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||
- curl -L https://http.kali.org/pool/main/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb
|
||||
- curl -L https://http.kali.org/pool/main/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb
|
||||
- dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb
|
||||
stage: build
|
||||
script:
|
||||
|
|
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,3 +1,10 @@
|
|||
0.46.0
|
||||
------
|
||||
|
||||
Feature #2229: Improve pathfinding AI
|
||||
Feature #3442: Default values for fallbacks from ini file
|
||||
|
||||
|
||||
0.45.0
|
||||
------
|
||||
|
||||
|
@ -8,6 +15,7 @@
|
|||
Bug #2256: Landing sound not playing when jumping immediately after landing
|
||||
Bug #2274: Thin platform clips through player character instead of lifting
|
||||
Bug #2326: After a bound item expires the last equipped item of that type is not automatically re-equipped
|
||||
Bug #2446: Restore Attribute/Skill should allow restoring drained attributes
|
||||
Bug #2455: Creatures attacks degrade armor
|
||||
Bug #2562: Forcing AI to activate a teleport door sometimes causes a crash
|
||||
Bug #2626: Resurrecting the player does not resume the game
|
||||
|
@ -33,6 +41,7 @@
|
|||
Bug #3788: GetPCInJail and GetPCTraveling do not work as in vanilla
|
||||
Bug #3836: Script fails to compile when command argument contains "\n"
|
||||
Bug #3876: Landscape texture painting is misaligned
|
||||
Bug #3890: Magic light source attenuation is inaccurate
|
||||
Bug #3897: Have Goodbye give all choices the effects of Goodbye
|
||||
Bug #3911: [macOS] Typing in the "Content List name" dialog box produces double characters
|
||||
Bug #3920: RemoveSpellEffects doesn't remove constant effects
|
||||
|
@ -45,6 +54,7 @@
|
|||
Bug #4110: Fixed undo / redo menu text losing the assigned shortcuts
|
||||
Bug #4125: OpenMW logo cropped on bugtracker
|
||||
Bug #4215: OpenMW shows book text after last EOL tag
|
||||
Bug #4217: Fixme implementation differs from Morrowind's
|
||||
Bug #4221: Characters get stuck in V-shaped terrain
|
||||
Bug #4230: AiTravel package issues break some Tribunal quests
|
||||
Bug #4231: Infected rats from the "Crimson Plague" quest rendered unconscious by change in Drain Fatigue functionality
|
||||
|
@ -54,6 +64,7 @@
|
|||
Bug #4274: Pre-0.43 death animations are not forward-compatible with 0.43+
|
||||
Bug #4286: Scripted animations can be interrupted
|
||||
Bug #4291: Non-persistent actors that started the game as dead do not play death animations
|
||||
Bug #4292: CenterOnCell implementation differs from vanilla
|
||||
Bug #4293: Faction members are not aware of faction ownerships in barter
|
||||
Bug #4304: "Follow" not working as a second AI package
|
||||
Bug #4307: World cleanup should remove dead bodies only if death animation is finished
|
||||
|
@ -64,7 +75,7 @@
|
|||
Bug #4368: Settings window ok button doesn't have key focus by default
|
||||
Bug #4378: On-self absorb spells restore stats
|
||||
Bug #4393: NPCs walk back to where they were after using ResetActors
|
||||
Bug #4416: Handle exception if we try to play non-music file
|
||||
Bug #4416: Non-music files crash the game when they are tried to be played
|
||||
Bug #4419: MRK NiStringExtraData is handled incorrectly
|
||||
Bug #4426: RotateWorld behavior is incorrect
|
||||
Bug #4429: [Windows] Error on build INSTALL.vcxproj project (debug) with cmake 3.7.2
|
||||
|
@ -80,6 +91,7 @@
|
|||
Bug #4459: NotCell dialogue condition doesn't support partial matches
|
||||
Bug #4460: Script function "Equip" doesn't bypass beast restrictions
|
||||
Bug #4461: "Open" spell from non-player caster isn't a crime
|
||||
Bug #4463: %g format doesn't return more digits
|
||||
Bug #4464: OpenMW keeps AiState cached storages even after we cancel AI packages
|
||||
Bug #4467: Content selector: cyrillic characters are decoded incorrectly in plugin descriptions
|
||||
Bug #4469: Abot Silt Striders – Model turn 90 degrees on horizontal
|
||||
|
@ -143,12 +155,17 @@
|
|||
Bug #4674: Journal can be opened when settings window is open
|
||||
Bug #4677: Crash in ESM reader when NPC record has DNAM record without DODT one
|
||||
Bug #4678: Crash in ESP parser when SCVR has no variable names
|
||||
Bug #4684: Spell Absorption is additive
|
||||
Bug #4685: Missing sound causes an exception inside Say command
|
||||
Bug #4689: Default creature soundgen entries are not used
|
||||
Bug #4691: Loading bar for cell should be moved up when text is still active at bottom of screen
|
||||
Feature #912: Editor: Add missing icons to UniversalId tables
|
||||
Feature #1221: Editor: Creature/NPC rendering
|
||||
Feature #1617: Editor: Enchantment effect record verifier
|
||||
Feature #1645: Casting effects from objects
|
||||
Feature #2606: Editor: Implemented (optional) case sensitive global search
|
||||
Feature #2787: Use the autogenerated collision box, if the creature mesh has no predefined one
|
||||
Feature #2845: Editor: add record view and preview default keybindings
|
||||
Feature #2847: Content selector: allow to copy the path to a file by using the context menu
|
||||
Feature #3083: Play animation when NPC is casting spell via script
|
||||
Feature #3103: Provide option for disposition to get increased by successful trade
|
||||
|
@ -178,7 +195,9 @@
|
|||
Feature #4632: AI priority: utilize vanilla AI GMSTs for priority rating
|
||||
Feature #4636: Use sTo GMST in spellmaking menu
|
||||
Feature #4642: Batching potion creation
|
||||
Feature #4647: Cull actors outside of AI processing range
|
||||
Feature #4682: Use the collision box from basic creature mesh if the X one have no collisions
|
||||
Feature #4697: Use the real thrown weapon damage in tooltips and AI
|
||||
Task #2490: Don't open command prompt window on Release-mode builds automatically
|
||||
Task #4545: Enable is_pod string test
|
||||
Task #4605: Optimize skinning
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
#!/bin/sh
|
||||
#!/bin/sh -e
|
||||
sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang
|
||||
sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/bin/sh -e
|
||||
|
||||
brew update
|
||||
|
||||
|
@ -6,5 +6,5 @@ brew outdated cmake || brew upgrade cmake
|
|||
brew outdated pkgconfig || brew upgrade pkgconfig
|
||||
brew install qt
|
||||
|
||||
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-100d2e0.zip -o ~/openmw-deps.zip
|
||||
curl -fSL -R -J https://downloads.openmw.org/osx/dependencies/openmw-deps-4eec887.zip -o ~/openmw-deps.zip
|
||||
unzip -o ~/openmw-deps.zip -d /private/tmp/openmw-deps > /dev/null
|
||||
|
|
|
@ -5,6 +5,9 @@ free -m
|
|||
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh
|
||||
GOOGLETEST_DIR="$(pwd)/googletest/build"
|
||||
|
||||
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_recastnavigation.sh
|
||||
RECASTNAVIGATION_DIR="$(pwd)/recastnavigation/build"
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
export CODE_COVERAGE=1
|
||||
|
@ -18,4 +21,5 @@ ${ANALYZE}cmake \
|
|||
-DUSE_SYSTEM_TINYXML=TRUE \
|
||||
-DGTEST_ROOT="${GOOGLETEST_DIR}" \
|
||||
-DGMOCK_ROOT="${GOOGLETEST_DIR}" \
|
||||
-DRecastNavigation_ROOT="${RECASTNAVIGATION_DIR}" \
|
||||
..
|
||||
|
|
|
@ -304,6 +304,10 @@ if ! [ -z $UNITY_BUILD ]; then
|
|||
add_cmake_opts "-DOPENMW_UNITY_BUILD=True"
|
||||
fi
|
||||
|
||||
if [ ${BITS} -eq 64 ]; then
|
||||
GENERATOR="${GENERATOR} Win64"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "==================================="
|
||||
echo "Starting prebuild on MSVC${MSVC_DISPLAY_YEAR} WIN${BITS}"
|
||||
|
@ -644,6 +648,13 @@ printf "SDL 2.0.7... "
|
|||
echo Done.
|
||||
}
|
||||
echo
|
||||
# recastnavigation
|
||||
printf 'recastnavigation...'
|
||||
{
|
||||
env GENERATOR="${GENERATOR}" CONFIGURATION="${CONFIGURATION}" ${DEPS_INSTALL}/../../CI/build_recastnavigation.sh
|
||||
add_cmake_opts -DRecastNavigation_ROOT="$(pwd)/recastnavigation/build"
|
||||
}
|
||||
echo
|
||||
cd $DEPS_INSTALL/..
|
||||
echo
|
||||
echo "Setting up OpenMW build..."
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
#!/bin/sh
|
||||
#!/bin/sh -e
|
||||
|
||||
export CXX=clang++
|
||||
export CC=clang
|
||||
|
||||
env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_recastnavigation.sh
|
||||
RECASTNAVIGATION_DIR="$(pwd)/recastnavigation/build"
|
||||
|
||||
DEPENDENCIES_ROOT="/private/tmp/openmw-deps/openmw-deps"
|
||||
QT_PATH=`brew --prefix qt`
|
||||
mkdir build
|
||||
|
@ -17,5 +20,6 @@ cmake \
|
|||
-D DESIRED_QT_VERSION=5 \
|
||||
-D BUILD_ESMTOOL=FALSE \
|
||||
-D BUILD_MYGUI_PLUGIN=FALSE \
|
||||
-D RecastNavigation_ROOT="${RECASTNAVIGATION_DIR}" \
|
||||
-G"Unix Makefiles" \
|
||||
..
|
||||
|
|
17
CI/build_recastnavigation.sh
Executable file
17
CI/build_recastnavigation.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
git clone https://github.com/recastnavigation/recastnavigation.git
|
||||
cd recastnavigation
|
||||
mkdir build
|
||||
cd build
|
||||
cmake \
|
||||
-DCMAKE_BUILD_TYPE="${CONFIGURATION}" \
|
||||
-DRECASTNAVIGATION_DEMO=OFF \
|
||||
-DRECASTNAVIGATION_TESTS=OFF \
|
||||
-DRECASTNAVIGATION_EXAMPLES=OFF \
|
||||
-DRECASTNAVIGATION_STATIC=ON \
|
||||
-DCMAKE_INSTALL_PREFIX=. \
|
||||
-G "${GENERATOR}" \
|
||||
..
|
||||
cmake --build . --config "${CONFIGURATION}"
|
||||
cmake --build . --target install --config "${CONFIGURATION}"
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/fallback/validate.hpp>
|
||||
#include <components/misc/rng.hpp>
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
|
||||
#include "model/doc/document.hpp"
|
||||
|
@ -355,6 +356,8 @@ int CS::Editor::run()
|
|||
if (mLocal.empty())
|
||||
return 1;
|
||||
|
||||
Misc::Rng::init();
|
||||
|
||||
mStartup.show();
|
||||
|
||||
QApplication::setQuitOnLastWindowClosed (true);
|
||||
|
|
|
@ -317,8 +317,8 @@ void CSMPrefs::State::declare()
|
|||
declareShortcut ("table-remove", "Remove Row/Record", QKeySequence(Qt::Key_Delete));
|
||||
declareShortcut ("table-moveup", "Move Record Up", QKeySequence());
|
||||
declareShortcut ("table-movedown", "Move Record Down", QKeySequence());
|
||||
declareShortcut ("table-view", "View Record", QKeySequence());
|
||||
declareShortcut ("table-preview", "Preview Record", QKeySequence());
|
||||
declareShortcut ("table-view", "View Record", QKeySequence(Qt::ShiftModifier | Qt::Key_C));
|
||||
declareShortcut ("table-preview", "Preview Record", QKeySequence(Qt::ShiftModifier | Qt::Key_V));
|
||||
declareShortcut ("table-extendeddelete", "Extended Record Deletion", QKeySequence());
|
||||
declareShortcut ("table-extendedrevert", "Extended Record Revertion", QKeySequence());
|
||||
|
||||
|
|
|
@ -292,6 +292,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
|
||||
mEditAction = new QAction (tr ("Edit Record"), this);
|
||||
connect (mEditAction, SIGNAL (triggered()), this, SLOT (editRecord()));
|
||||
mEditAction->setIcon(QIcon(":edit-edit"));
|
||||
addAction (mEditAction);
|
||||
CSMPrefs::Shortcut* editShortcut = new CSMPrefs::Shortcut("table-edit", this);
|
||||
editShortcut->associateAction(mEditAction);
|
||||
|
@ -300,12 +301,14 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
{
|
||||
mCreateAction = new QAction (tr ("Add Record"), this);
|
||||
connect (mCreateAction, SIGNAL (triggered()), this, SIGNAL (createRequest()));
|
||||
mCreateAction->setIcon(QIcon(":edit-add"));
|
||||
addAction (mCreateAction);
|
||||
CSMPrefs::Shortcut* createShortcut = new CSMPrefs::Shortcut("table-add", this);
|
||||
createShortcut->associateAction(mCreateAction);
|
||||
|
||||
mCloneAction = new QAction (tr ("Clone Record"), this);
|
||||
connect(mCloneAction, SIGNAL (triggered()), this, SLOT (cloneRecord()));
|
||||
mCloneAction->setIcon(QIcon(":edit-clone"));
|
||||
addAction(mCloneAction);
|
||||
CSMPrefs::Shortcut* cloneShortcut = new CSMPrefs::Shortcut("table-clone", this);
|
||||
cloneShortcut->associateAction(mCloneAction);
|
||||
|
@ -315,6 +318,7 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
{
|
||||
mTouchAction = new QAction(tr("Touch Record"), this);
|
||||
connect(mTouchAction, SIGNAL(triggered()), this, SLOT(touchRecord()));
|
||||
mTouchAction->setIcon(QIcon(":edit-touch"));
|
||||
addAction(mTouchAction);
|
||||
CSMPrefs::Shortcut* touchShortcut = new CSMPrefs::Shortcut("table-touch", this);
|
||||
touchShortcut->associateAction(mTouchAction);
|
||||
|
@ -322,49 +326,56 @@ CSVWorld::Table::Table (const CSMWorld::UniversalId& id,
|
|||
|
||||
mRevertAction = new QAction (tr ("Revert Record"), this);
|
||||
connect (mRevertAction, SIGNAL (triggered()), mDispatcher, SLOT (executeRevert()));
|
||||
mRevertAction->setIcon(QIcon(":edit-undo"));
|
||||
addAction (mRevertAction);
|
||||
CSMPrefs::Shortcut* revertShortcut = new CSMPrefs::Shortcut("table-revert", this);
|
||||
revertShortcut->associateAction(mRevertAction);
|
||||
|
||||
mDeleteAction = new QAction (tr ("Delete Record"), this);
|
||||
connect (mDeleteAction, SIGNAL (triggered()), mDispatcher, SLOT (executeDelete()));
|
||||
mDeleteAction->setIcon(QIcon(":edit-delete"));
|
||||
addAction (mDeleteAction);
|
||||
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("table-remove", this);
|
||||
deleteShortcut->associateAction(mDeleteAction);
|
||||
|
||||
|
||||
mMoveUpAction = new QAction (tr ("Move Up"), this);
|
||||
connect (mMoveUpAction, SIGNAL (triggered()), this, SLOT (moveUpRecord()));
|
||||
mMoveUpAction->setIcon(QIcon(":record-up"));
|
||||
addAction (mMoveUpAction);
|
||||
CSMPrefs::Shortcut* moveUpShortcut = new CSMPrefs::Shortcut("table-moveup", this);
|
||||
moveUpShortcut->associateAction(mMoveUpAction);
|
||||
|
||||
mMoveDownAction = new QAction (tr ("Move Down"), this);
|
||||
connect (mMoveDownAction, SIGNAL (triggered()), this, SLOT (moveDownRecord()));
|
||||
mMoveDownAction->setIcon(QIcon(":record-down"));
|
||||
addAction (mMoveDownAction);
|
||||
CSMPrefs::Shortcut* moveDownShortcut = new CSMPrefs::Shortcut("table-movedown", this);
|
||||
moveDownShortcut->associateAction(mMoveDownAction);
|
||||
|
||||
mViewAction = new QAction (tr ("View"), this);
|
||||
connect (mViewAction, SIGNAL (triggered()), this, SLOT (viewRecord()));
|
||||
mViewAction->setIcon(QIcon(":/cell.png"));
|
||||
addAction (mViewAction);
|
||||
CSMPrefs::Shortcut* viewShortcut = new CSMPrefs::Shortcut("table-view", this);
|
||||
viewShortcut->associateAction(mViewAction);
|
||||
|
||||
mPreviewAction = new QAction (tr ("Preview"), this);
|
||||
connect (mPreviewAction, SIGNAL (triggered()), this, SLOT (previewRecord()));
|
||||
mPreviewAction->setIcon(QIcon(":edit-preview"));
|
||||
addAction (mPreviewAction);
|
||||
CSMPrefs::Shortcut* previewShortcut = new CSMPrefs::Shortcut("table-preview", this);
|
||||
previewShortcut->associateAction(mPreviewAction);
|
||||
|
||||
mExtendedDeleteAction = new QAction (tr ("Extended Delete Record"), this);
|
||||
connect (mExtendedDeleteAction, SIGNAL (triggered()), this, SLOT (executeExtendedDelete()));
|
||||
mExtendedDeleteAction->setIcon(QIcon(":edit-delete"));
|
||||
addAction (mExtendedDeleteAction);
|
||||
CSMPrefs::Shortcut* extendedDeleteShortcut = new CSMPrefs::Shortcut("table-extendeddelete", this);
|
||||
extendedDeleteShortcut->associateAction(mExtendedDeleteAction);
|
||||
|
||||
mExtendedRevertAction = new QAction (tr ("Extended Revert Record"), this);
|
||||
connect (mExtendedRevertAction, SIGNAL (triggered()), this, SLOT (executeExtendedRevert()));
|
||||
mExtendedRevertAction->setIcon(QIcon(":edit-undo"));
|
||||
addAction (mExtendedRevertAction);
|
||||
CSMPrefs::Shortcut* extendedRevertShortcut = new CSMPrefs::Shortcut("table-extendedrevert", this);
|
||||
extendedRevertShortcut->associateAction(mExtendedRevertAction);
|
||||
|
|
|
@ -21,7 +21,7 @@ add_openmw_dir (mwrender
|
|||
actors objects renderingmanager animation rotatecontroller sky npcanimation vismask
|
||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
||||
renderbin actoranimation landmanager
|
||||
renderbin actoranimation landmanager navmesh actorspaths
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
@ -70,7 +70,7 @@ add_openmw_dir (mwworld
|
|||
)
|
||||
|
||||
add_openmw_dir (mwphysics
|
||||
physicssystem trace collisiontype actor convert
|
||||
physicssystem trace collisiontype actor convert object heightfield
|
||||
)
|
||||
|
||||
add_openmw_dir (mwclass
|
||||
|
@ -117,6 +117,10 @@ include_directories(
|
|||
${FFmpeg_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED)
|
||||
|
||||
include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(openmw
|
||||
${OSG_LIBRARIES}
|
||||
${OPENTHREADS_LIBRARIES}
|
||||
|
@ -134,6 +138,7 @@ target_link_libraries(openmw
|
|||
${FFmpeg_LIBRARIES}
|
||||
${MyGUI_LIBRARIES}
|
||||
${SDL2_LIBRARY}
|
||||
${RecastNavigation_LIBRARIES}
|
||||
"osg-ffmpeg-videoplayer"
|
||||
"oics"
|
||||
components
|
||||
|
|
|
@ -233,6 +233,10 @@ namespace MWBase
|
|||
|
||||
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell) = 0;
|
||||
|
||||
virtual void processChangedSettings (const std::set< std::pair<std::string, std::string> >& settings) = 0;
|
||||
|
||||
virtual float getActorsProcessingRange() const = 0;
|
||||
|
||||
/// Check if the target actor was detected by an observer
|
||||
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
|
||||
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer) = 0;
|
||||
|
|
|
@ -285,6 +285,8 @@ namespace MWBase
|
|||
|
||||
virtual void setEnemy (const MWWorld::Ptr& enemy) = 0;
|
||||
|
||||
virtual int getMessagesCount() const = 0;
|
||||
|
||||
virtual const Translation::Storage& getTranslationDataStorage() const = 0;
|
||||
|
||||
/// Warning: do not use MyGUI::InputManager::setKeyFocusWidget directly. Instead use this.
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
|
||||
#include <components/esm/cellid.hpp>
|
||||
|
||||
|
@ -54,6 +55,11 @@ namespace MWMechanics
|
|||
struct Movement;
|
||||
}
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class Navigator;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
|
@ -302,6 +308,9 @@ namespace MWBase
|
|||
|
||||
virtual bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) = 0;
|
||||
|
||||
virtual void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled) = 0;
|
||||
virtual bool isActorCollisionEnabled(const MWWorld::Ptr& ptr) = 0;
|
||||
|
||||
virtual bool toggleCollisionMode() = 0;
|
||||
///< Toggle collision mode for player. If disabled player object should ignore
|
||||
/// collisions and gravity.
|
||||
|
@ -592,6 +601,13 @@ namespace MWBase
|
|||
|
||||
/// Preload VFX associated with this effect list
|
||||
virtual void preloadEffects(const ESM::EffectList* effectList) = 0;
|
||||
|
||||
virtual DetourNavigator::Navigator* getNavigator() const = 0;
|
||||
|
||||
virtual void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const = 0;
|
||||
|
||||
virtual void setNavMeshNumberToRender(const std::size_t value) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ namespace MWClass
|
|||
|
||||
std::string Activator::getSoundIdFromSndGen(const MWWorld::Ptr &ptr, const std::string &name) const
|
||||
{
|
||||
std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
|
||||
const std::string model = getModel(ptr); // Assume it's not empty, since we wouldn't have gotten the soundgen otherwise
|
||||
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
|
||||
std::string creatureId;
|
||||
|
||||
|
@ -151,21 +151,35 @@ namespace MWClass
|
|||
}
|
||||
}
|
||||
|
||||
if (creatureId.empty())
|
||||
return std::string();
|
||||
|
||||
int type = getSndGenTypeFromName(name);
|
||||
std::vector<const ESM::SoundGenerator*> sounds;
|
||||
|
||||
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||
if (!creatureId.empty())
|
||||
{
|
||||
std::vector<const ESM::SoundGenerator*> sounds;
|
||||
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
|
||||
{
|
||||
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(creatureId, sound->mCreature)))
|
||||
sounds.push_back(&*sound);
|
||||
if (type == sound->mType && sound->mCreature.empty())
|
||||
fallbacksounds.push_back(&*sound);
|
||||
}
|
||||
|
||||
if (!sounds.empty())
|
||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||
if (!fallbacksounds.empty())
|
||||
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The activator doesn't have a corresponding creature ID, but we can try to use the defaults
|
||||
for (auto sound = store.get<ESM::SoundGenerator>().begin(); sound != store.get<ESM::SoundGenerator>().end(); ++sound)
|
||||
if (type == sound->mType && sound->mCreature.empty())
|
||||
fallbacksounds.push_back(&*sound);
|
||||
|
||||
if (type == ESM::SoundGenerator::Land)
|
||||
return "Body Fall Large";
|
||||
if (!fallbacksounds.empty())
|
||||
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||
}
|
||||
|
||||
return std::string();
|
||||
}
|
||||
|
|
|
@ -297,11 +297,11 @@ namespace MWClass
|
|||
{
|
||||
const MWWorld::InventoryStore& invStore = npc.getClass().getInventoryStore(npc);
|
||||
|
||||
if (ptr.getCellRef().getCharge() == 0)
|
||||
if (getItemHealth(ptr) == 0)
|
||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||
|
||||
// slots that this item can be equipped in
|
||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
||||
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
|
||||
|
||||
if (slots_.first.empty())
|
||||
return std::make_pair(0, "");
|
||||
|
|
|
@ -215,7 +215,7 @@ namespace MWClass
|
|||
std::pair<int, std::string> Clothing::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
||||
{
|
||||
// slots that this item can be equipped in
|
||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
||||
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
|
||||
|
||||
if (slots_.first.empty())
|
||||
return std::make_pair(0, "");
|
||||
|
|
|
@ -137,7 +137,7 @@ namespace MWClass
|
|||
|
||||
// Persistent actors with 0 health do not play death animation
|
||||
if (data->mCreatureStats.isDead())
|
||||
data->mCreatureStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
||||
data->mCreatureStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||
|
||||
// spells
|
||||
for (std::vector<std::string>::const_iterator iter (ref->mBase->mSpells.mList.begin());
|
||||
|
@ -194,9 +194,9 @@ namespace MWClass
|
|||
models.push_back(model);
|
||||
|
||||
// FIXME: use const version of InventoryStore functions once they are available
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
if (hasInventoryStore(ptr))
|
||||
{
|
||||
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
|
||||
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
|
||||
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||
{
|
||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||
|
@ -237,7 +237,7 @@ namespace MWClass
|
|||
|
||||
// Get the weapon used (if hand-to-hand, weapon = inv.end())
|
||||
MWWorld::Ptr weapon;
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
if (hasInventoryStore(ptr))
|
||||
{
|
||||
MWWorld::InventoryStore &inv = getInventoryStore(ptr);
|
||||
MWWorld::ContainerStoreIterator weaponslot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight);
|
||||
|
@ -253,8 +253,7 @@ namespace MWClass
|
|||
|
||||
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!ptr.isEmpty() && ptr.getClass().isActor())
|
||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||
stats.getAiSequence().getCombatTargets(targetActors);
|
||||
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(ptr, dist, targetActors);
|
||||
if (result.first.isEmpty())
|
||||
|
@ -332,7 +331,7 @@ namespace MWClass
|
|||
|
||||
void Creature::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
|
||||
// NOTE: 'object' and/or 'attacker' may be empty.
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !stats.getAiSequence().isInCombat(attacker))
|
||||
|
@ -346,7 +345,7 @@ namespace MWClass
|
|||
setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker);
|
||||
|
||||
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||
{
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
|
@ -522,7 +521,7 @@ namespace MWClass
|
|||
const MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
const MWMechanics::MagicEffects &mageffects = stats.getMagicEffects();
|
||||
|
||||
bool running = ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run);
|
||||
bool running = stats.getStance(MWMechanics::CreatureStats::Stance_Run);
|
||||
|
||||
// The Run speed difference for creatures comes from the animation speed difference (see runStateToWalkState in character.cpp)
|
||||
float runSpeed = walkSpeed;
|
||||
|
@ -632,6 +631,7 @@ namespace MWClass
|
|||
if(type >= 0)
|
||||
{
|
||||
std::vector<const ESM::SoundGenerator*> sounds;
|
||||
std::vector<const ESM::SoundGenerator*> fallbacksounds;
|
||||
|
||||
MWWorld::LiveCellRef<ESM::Creature>* ref = ptr.get<ESM::Creature>();
|
||||
|
||||
|
@ -642,15 +642,16 @@ namespace MWClass
|
|||
{
|
||||
if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature)))
|
||||
sounds.push_back(&*sound);
|
||||
if (type == sound->mType && sound->mCreature.empty())
|
||||
fallbacksounds.push_back(&*sound);
|
||||
++sound;
|
||||
}
|
||||
if (!sounds.empty())
|
||||
return sounds[Misc::Rng::rollDice(sounds.size())]->mSound;
|
||||
if (!fallbacksounds.empty())
|
||||
return fallbacksounds[Misc::Rng::rollDice(fallbacksounds.size())]->mSound;
|
||||
}
|
||||
|
||||
if (type == ESM::SoundGenerator::Land)
|
||||
return "Body Fall Large";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
@ -806,7 +807,7 @@ namespace MWClass
|
|||
|
||||
void Creature::respawn(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
||||
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
|
||||
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
||||
return;
|
||||
|
||||
|
|
|
@ -357,7 +357,7 @@ namespace MWClass
|
|||
|
||||
// Persistent actors with 0 health do not play death animation
|
||||
if (data->mNpcStats.isDead())
|
||||
data->mNpcStats.setDeathAnimationFinished(ptr.getClass().isPersistent(ptr));
|
||||
data->mNpcStats.setDeathAnimationFinished(isPersistent(ptr));
|
||||
|
||||
// race powers
|
||||
const ESM::Race *race = MWBase::Environment::get().getWorld()->getStore().get<ESM::Race>().find(ref->mBase->mRace);
|
||||
|
@ -469,9 +469,7 @@ namespace MWClass
|
|||
|
||||
// FIXME: use const version of InventoryStore functions once they are available
|
||||
// preload equipped items
|
||||
if (ptr.getClass().hasInventoryStore(ptr))
|
||||
{
|
||||
const MWWorld::InventoryStore& invStore = ptr.getClass().getInventoryStore(ptr);
|
||||
const MWWorld::InventoryStore& invStore = getInventoryStore(ptr);
|
||||
for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot)
|
||||
{
|
||||
MWWorld::ConstContainerStoreIterator equipped = invStore.getSlot(slot);
|
||||
|
@ -506,7 +504,6 @@ namespace MWClass
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// preload body parts
|
||||
if (race)
|
||||
|
@ -573,8 +570,8 @@ namespace MWClass
|
|||
|
||||
// For AI actors, get combat targets to use in the ray cast. Only those targets will return a positive hit result.
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
if (!ptr.isEmpty() && ptr.getClass().isActor() && ptr != MWMechanics::getPlayer())
|
||||
ptr.getClass().getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||
if (ptr != MWMechanics::getPlayer())
|
||||
getCreatureStats(ptr).getAiSequence().getCombatTargets(targetActors);
|
||||
|
||||
// TODO: Use second to work out the hit angle
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = world->getHitContact(ptr, dist, targetActors);
|
||||
|
@ -597,7 +594,7 @@ namespace MWClass
|
|||
if(!weapon.isEmpty())
|
||||
weapskill = weapon.getClass().getEquipmentSkill(weapon);
|
||||
|
||||
float hitchance = MWMechanics::getHitChance(ptr, victim, ptr.getClass().getSkill(ptr, weapskill));
|
||||
float hitchance = MWMechanics::getHitChance(ptr, victim, getSkill(ptr, weapskill));
|
||||
|
||||
if (Misc::Rng::roll0to99() >= hitchance)
|
||||
{
|
||||
|
@ -667,7 +664,7 @@ namespace MWClass
|
|||
void Npc::onHit(const MWWorld::Ptr &ptr, float damage, bool ishealth, const MWWorld::Ptr &object, const MWWorld::Ptr &attacker, const osg::Vec3f &hitPosition, bool successful) const
|
||||
{
|
||||
MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager();
|
||||
MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
bool wasDead = stats.isDead();
|
||||
|
||||
// Note OnPcHitMe is not set for friendly hits.
|
||||
|
@ -681,7 +678,7 @@ namespace MWClass
|
|||
}
|
||||
|
||||
// Attacker and target store each other as hitattemptactor if they have no one stored yet
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor() && !ptr.isEmpty() && ptr.getClass().isActor())
|
||||
if (!attacker.isEmpty() && attacker.getClass().isActor())
|
||||
{
|
||||
MWMechanics::CreatureStats& statsAttacker = attacker.getClass().getCreatureStats(attacker);
|
||||
// First handle the attacked actor
|
||||
|
@ -702,7 +699,7 @@ namespace MWClass
|
|||
|
||||
if (setOnPcHitMe && !attacker.isEmpty() && attacker == MWMechanics::getPlayer())
|
||||
{
|
||||
const std::string &script = ptr.getClass().getScript(ptr);
|
||||
const std::string &script = getScript(ptr);
|
||||
/* Set the OnPCHitMe script variable. The script is responsible for clearing it. */
|
||||
if(!script.empty())
|
||||
ptr.getRefData().getLocals().setVarByInt(script, "onpchitme", 1);
|
||||
|
@ -891,12 +888,11 @@ namespace MWClass
|
|||
if(stats.getAiSequence().isInCombat())
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::FailedAction(""));
|
||||
|
||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak)
|
||||
|| ptr.getClass().getCreatureStats(ptr).getKnockedDown())
|
||||
if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown())
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionOpen(ptr)); // stealing
|
||||
|
||||
// Can't talk to werewolfs
|
||||
if(ptr.getClass().isNpc() && ptr.getClass().getNpcStats(ptr).isWerewolf())
|
||||
if(getNpcStats(ptr).isWerewolf())
|
||||
return std::shared_ptr<MWWorld::Action> (new MWWorld::FailedAction(""));
|
||||
|
||||
return std::shared_ptr<MWWorld::Action>(new MWWorld::ActionTalk(ptr));
|
||||
|
@ -927,7 +923,7 @@ namespace MWClass
|
|||
|
||||
float Npc::getSpeed(const MWWorld::Ptr& ptr) const
|
||||
{
|
||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
||||
return 0.f;
|
||||
|
||||
|
@ -993,7 +989,7 @@ namespace MWClass
|
|||
if(getEncumbrance(ptr) > getCapacity(ptr))
|
||||
return 0.f;
|
||||
|
||||
const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr);
|
||||
const MWMechanics::CreatureStats& stats = getCreatureStats(ptr);
|
||||
if (stats.isParalyzed() || stats.getKnockedDown() || stats.isDead())
|
||||
return 0.f;
|
||||
|
||||
|
@ -1148,9 +1144,7 @@ namespace MWClass
|
|||
const bool hasHealth = it->getClass().hasItemHealth(*it);
|
||||
if (hasHealth)
|
||||
{
|
||||
int armorHealth = it->getClass().getItemHealth(*it);
|
||||
int armorMaxHealth = it->getClass().getItemMaxHealth(*it);
|
||||
ratings[i] *= (float(armorHealth) / armorMaxHealth);
|
||||
ratings[i] *= it->getClass().getItemNormalizedHealth(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1215,13 +1209,13 @@ namespace MWClass
|
|||
return (name == "left") ? "FootWaterLeft" : "FootWaterRight";
|
||||
if(world->isOnGround(ptr))
|
||||
{
|
||||
if (ptr.getClass().getNpcStats(ptr).isWerewolf()
|
||||
&& ptr.getClass().getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||
if (getNpcStats(ptr).isWerewolf()
|
||||
&& getCreatureStats(ptr).getStance(MWMechanics::CreatureStats::Stance_Run))
|
||||
{
|
||||
MWMechanics::WeaponType weaponType = MWMechanics::WeapType_None;
|
||||
MWMechanics::getActiveWeapon(ptr.getClass().getCreatureStats(ptr), ptr.getClass().getInventoryStore(ptr), &weaponType);
|
||||
MWMechanics::getActiveWeapon(getCreatureStats(ptr), getInventoryStore(ptr), &weaponType);
|
||||
if (weaponType == MWMechanics::WeapType_None)
|
||||
return "";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const MWWorld::InventoryStore &inv = Npc::getInventoryStore(ptr);
|
||||
|
@ -1239,12 +1233,12 @@ namespace MWClass
|
|||
return (name == "left") ? "FootHeavyLeft" : "FootHeavyRight";
|
||||
}
|
||||
}
|
||||
return "";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
// Morrowind ignores land soundgen for NPCs
|
||||
if(name == "land")
|
||||
return "";
|
||||
return std::string();
|
||||
if(name == "swimleft")
|
||||
return "Swim Left";
|
||||
if(name == "swimright")
|
||||
|
@ -1254,11 +1248,11 @@ namespace MWClass
|
|||
// only for biped creatures?
|
||||
|
||||
if(name == "moan")
|
||||
return "";
|
||||
return std::string();
|
||||
if(name == "roar")
|
||||
return "";
|
||||
return std::string();
|
||||
if(name == "scream")
|
||||
return "";
|
||||
return std::string();
|
||||
|
||||
throw std::runtime_error(std::string("Unexpected soundgen type: ")+name);
|
||||
}
|
||||
|
@ -1272,7 +1266,7 @@ namespace MWClass
|
|||
|
||||
int Npc::getSkill(const MWWorld::Ptr& ptr, int skill) const
|
||||
{
|
||||
return ptr.getClass().getNpcStats(ptr).getSkill(skill).getModified();
|
||||
return getNpcStats(ptr).getSkill(skill).getModified();
|
||||
}
|
||||
|
||||
int Npc::getBloodTexture(const MWWorld::ConstPtr &ptr) const
|
||||
|
@ -1354,7 +1348,7 @@ namespace MWClass
|
|||
|
||||
void Npc::respawn(const MWWorld::Ptr &ptr) const
|
||||
{
|
||||
const MWMechanics::CreatureStats& creatureStats = ptr.getClass().getCreatureStats(ptr);
|
||||
const MWMechanics::CreatureStats& creatureStats = getCreatureStats(ptr);
|
||||
if (ptr.getRefData().getCount() > 0 && !creatureStats.isDead())
|
||||
return;
|
||||
|
||||
|
|
|
@ -265,7 +265,7 @@ namespace MWClass
|
|||
std::string text;
|
||||
|
||||
// weapon type & damage
|
||||
if ((ref->mBase->mData.mType < 12 || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType < 14)
|
||||
if ((ref->mBase->mData.mType < ESM::Weapon::Arrow || Settings::Manager::getBool("show projectile damage", "Game")) && ref->mBase->mData.mType <= ESM::Weapon::Bolt)
|
||||
{
|
||||
text += "\n#{sType} ";
|
||||
|
||||
|
@ -295,7 +295,15 @@ namespace MWClass
|
|||
((oneOrTwoHanded != "") ? ", " + store.get<ESM::GameSetting>().find(oneOrTwoHanded)->mValue.getString() : "");
|
||||
|
||||
// weapon damage
|
||||
if (ref->mBase->mData.mType >= 9)
|
||||
if (ref->mBase->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
{
|
||||
// Thrown weapons have 2x real damage applied
|
||||
// as they're both the weapon and the ammo
|
||||
text += "\n#{sAttack}: "
|
||||
+ MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[0] * 2))
|
||||
+ " - " + MWGui::ToolTips::toString(static_cast<int>(ref->mBase->mData.mChop[1] * 2));
|
||||
}
|
||||
else if (ref->mBase->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
{
|
||||
// marksman
|
||||
text += "\n#{sAttack}: "
|
||||
|
@ -383,7 +391,7 @@ namespace MWClass
|
|||
|
||||
std::pair<int, std::string> Weapon::canBeEquipped(const MWWorld::ConstPtr &ptr, const MWWorld::Ptr &npc) const
|
||||
{
|
||||
if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0)
|
||||
if (hasItemHealth(ptr) && getItemHealth(ptr) == 0)
|
||||
return std::make_pair(0, "#{sInventoryMessage1}");
|
||||
|
||||
// Do not allow equip weapons from inventory during attack
|
||||
|
@ -391,7 +399,7 @@ namespace MWClass
|
|||
&& MWBase::Environment::get().getWindowManager()->isGuiMode())
|
||||
return std::make_pair(0, "#{sCantEquipWeapWarning}");
|
||||
|
||||
std::pair<std::vector<int>, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr);
|
||||
std::pair<std::vector<int>, bool> slots_ = getEquipmentSlots(ptr);
|
||||
|
||||
if (slots_.first.empty())
|
||||
return std::make_pair (0, "");
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace MWGui
|
|||
Log(Debug::Warning) << "Warning: no splash screens found!";
|
||||
}
|
||||
|
||||
void LoadingScreen::setLabel(const std::string &label, bool important)
|
||||
void LoadingScreen::setLabel(const std::string &label, bool important, bool center)
|
||||
{
|
||||
mImportantLabel = important;
|
||||
|
||||
|
@ -105,7 +105,11 @@ namespace MWGui
|
|||
MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight());
|
||||
size.width = std::max(300, size.width);
|
||||
mLoadingBox->setSize(size);
|
||||
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop());
|
||||
|
||||
if (center)
|
||||
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight()/2 - mLoadingBox->getHeight()/2);
|
||||
else
|
||||
mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mMainWidget->getHeight() - mLoadingBox->getHeight() - 8);
|
||||
}
|
||||
|
||||
void LoadingScreen::setVisible(bool visible)
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace MWGui
|
|||
virtual ~LoadingScreen();
|
||||
|
||||
/// Overridden from Loading::Listener, see the Loading::Listener documentation for usage details
|
||||
virtual void setLabel (const std::string& label, bool important);
|
||||
virtual void setLabel (const std::string& label, bool important, bool center);
|
||||
virtual void loadingOn(bool visible=true);
|
||||
virtual void loadingOff();
|
||||
virtual void setProgressRange (size_t range);
|
||||
|
|
|
@ -51,7 +51,7 @@ void MerchantRepair::setPtr(const MWWorld::Ptr &actor)
|
|||
{
|
||||
int maxDurability = iter->getClass().getItemMaxHealth(*iter);
|
||||
int durability = iter->getClass().getItemHealth(*iter);
|
||||
if (maxDurability == durability)
|
||||
if (maxDurability == durability || maxDurability == 0)
|
||||
continue;
|
||||
|
||||
int basePrice = iter->getClass().getValue(*iter);
|
||||
|
|
|
@ -35,6 +35,11 @@ namespace MWGui
|
|||
}
|
||||
}
|
||||
|
||||
int MessageBoxManager::getMessagesCount()
|
||||
{
|
||||
return mMessageBoxes.size();
|
||||
}
|
||||
|
||||
void MessageBoxManager::clear()
|
||||
{
|
||||
if (mInterMessageBoxe)
|
||||
|
|
|
@ -28,6 +28,8 @@ namespace MWGui
|
|||
bool createInteractiveMessageBox (const std::string& message, const std::vector<std::string>& buttons);
|
||||
bool isInteractiveMessageBox ();
|
||||
|
||||
int getMessagesCount();
|
||||
|
||||
const InteractiveMessageBox* getInteractiveMessageBox() const { return mInterMessageBoxe; }
|
||||
|
||||
/// Remove all message boxes
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/inputmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
||||
#include "confirmationdialog.hpp"
|
||||
|
@ -437,6 +438,7 @@ namespace MWGui
|
|||
MWBase::Environment::get().getSoundManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getWindowManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getInputManager()->processChangedSettings(changed);
|
||||
MWBase::Environment::get().getMechanicsManager()->processChangedSettings(changed);
|
||||
}
|
||||
|
||||
void SettingsWindow::onKeyboardSwitchClicked(MyGUI::Widget* _sender)
|
||||
|
|
|
@ -91,8 +91,7 @@ namespace
|
|||
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
||||
leftChargePercent = 101;
|
||||
else
|
||||
leftChargePercent = (left.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
|
||||
: static_cast<int>(left.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
||||
leftChargePercent = static_cast<int>(left.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,8 +103,7 @@ namespace
|
|||
if (ench->mData.mType == ESM::Enchantment::ConstantEffect)
|
||||
rightChargePercent = 101;
|
||||
else
|
||||
rightChargePercent = (right.mBase.getCellRef().getEnchantmentCharge() == -1) ? 100
|
||||
: static_cast<int>(right.mBase.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
||||
rightChargePercent = static_cast<int>(right.mBase.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,7 @@ namespace
|
|||
float price = static_cast<float>(item.getClass().getValue(item));
|
||||
if (item.getClass().hasItemHealth(item))
|
||||
{
|
||||
price *= item.getClass().getItemHealth(item);
|
||||
price /= item.getClass().getItemMaxHealth(item);
|
||||
price *= item.getClass().getItemNormalizedHealth(item);
|
||||
}
|
||||
return static_cast<int>(price * count);
|
||||
}
|
||||
|
|
|
@ -1369,8 +1369,7 @@ namespace MWGui
|
|||
const ESM::Enchantment* ench = mStore->get<ESM::Enchantment>()
|
||||
.find(item.getClass().getEnchantment(item));
|
||||
|
||||
int chargePercent = (item.getCellRef().getEnchantmentCharge() == -1) ? 100
|
||||
: static_cast<int>(item.getCellRef().getEnchantmentCharge() / static_cast<float>(ench->mData.mCharge) * 100);
|
||||
int chargePercent = static_cast<int>(item.getCellRef().getNormalizedEnchantmentCharge(ench->mData.mCharge) * 100);
|
||||
mHud->setSelectedEnchantItem(item, chargePercent);
|
||||
mSpellWindow->setTitle(item.getClass().getName(item));
|
||||
}
|
||||
|
@ -1386,7 +1385,7 @@ namespace MWGui
|
|||
int durabilityPercent = 100;
|
||||
if (item.getClass().hasItemHealth(item))
|
||||
{
|
||||
durabilityPercent = static_cast<int>(item.getClass().getItemHealth(item) / static_cast<float>(item.getClass().getItemMaxHealth(item)) * 100);
|
||||
durabilityPercent = static_cast<int>(item.getClass().getItemNormalizedHealth(item) * 100);
|
||||
}
|
||||
mHud->setSelectedWeapon(item, durabilityPercent);
|
||||
mInventoryWindow->setTitle(item.getClass().getName(item));
|
||||
|
@ -1671,6 +1670,15 @@ namespace MWGui
|
|||
mHud->setEnemy(enemy);
|
||||
}
|
||||
|
||||
int WindowManager::getMessagesCount() const
|
||||
{
|
||||
int count = 0;
|
||||
if (mMessageBoxManager)
|
||||
count = mMessageBoxManager->getMessagesCount();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
Loading::Listener* WindowManager::getLoadingScreen()
|
||||
{
|
||||
return mLoadingScreen;
|
||||
|
|
|
@ -316,6 +316,8 @@ namespace MWGui
|
|||
|
||||
virtual void setEnemy (const MWWorld::Ptr& enemy);
|
||||
|
||||
virtual int getMessagesCount() const;
|
||||
|
||||
virtual const Translation::Storage& getTranslationDataStorage() const;
|
||||
|
||||
void onSoulgemDialogButtonPressed (int button);
|
||||
|
|
|
@ -147,9 +147,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float
|
|||
|
||||
namespace MWMechanics
|
||||
{
|
||||
const float aiProcessingDistance = 7168;
|
||||
const float sqrAiProcessingDistance = aiProcessingDistance*aiProcessingDistance;
|
||||
|
||||
class SoulTrap : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
MWWorld::Ptr mCreature;
|
||||
|
@ -364,7 +361,8 @@ namespace MWMechanics
|
|||
const ESM::Position& actor1Pos = actor1.getRefData().getPosition();
|
||||
const ESM::Position& actor2Pos = actor2.getRefData().getPosition();
|
||||
float sqrDist = (actor1Pos.asVec3() - actor2Pos.asVec3()).length2();
|
||||
if (sqrDist > sqrAiProcessingDistance)
|
||||
|
||||
if (sqrDist > mActorsProcessingRange*mActorsProcessingRange)
|
||||
return;
|
||||
|
||||
// No combat for totally static creatures
|
||||
|
@ -1130,8 +1128,11 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
Actors::Actors() {
|
||||
Actors::Actors()
|
||||
{
|
||||
mTimerDisposeSummonsCorpses = 0.2f; // We should add a delay between summoned creature death and its corpse despawning
|
||||
|
||||
updateProcessingRange();
|
||||
}
|
||||
|
||||
Actors::~Actors()
|
||||
|
@ -1139,6 +1140,23 @@ namespace MWMechanics
|
|||
clear();
|
||||
}
|
||||
|
||||
float Actors::getProcessingRange() const
|
||||
{
|
||||
return mActorsProcessingRange;
|
||||
}
|
||||
|
||||
void Actors::updateProcessingRange()
|
||||
{
|
||||
// We have to cap it since using high values (larger than 7168) will make some quests harder or impossible to complete (bug #1876)
|
||||
static const float maxProcessingRange = 7168.f;
|
||||
static const float minProcessingRange = maxProcessingRange / 2.f;
|
||||
|
||||
float actorsProcessingRange = Settings::Manager::getFloat("actors processing range", "Game");
|
||||
actorsProcessingRange = std::min(actorsProcessingRange, maxProcessingRange);
|
||||
actorsProcessingRange = std::max(actorsProcessingRange, minProcessingRange);
|
||||
mActorsProcessingRange = actorsProcessingRange;
|
||||
}
|
||||
|
||||
void Actors::addActor (const MWWorld::Ptr& ptr, bool updateImmediately)
|
||||
{
|
||||
removeActor(ptr);
|
||||
|
@ -1184,7 +1202,7 @@ namespace MWMechanics
|
|||
// Otherwise check if any actor in AI processing range sees the target actor
|
||||
std::vector<MWWorld::Ptr> actors;
|
||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, actors);
|
||||
getObjectsInRange(position, mActorsProcessingRange, actors);
|
||||
for(std::vector<MWWorld::Ptr>::iterator it = actors.begin(); it != actors.end(); ++it)
|
||||
{
|
||||
if (*it == actor)
|
||||
|
@ -1242,7 +1260,7 @@ namespace MWMechanics
|
|||
{
|
||||
if (iter->first == player) continue;
|
||||
|
||||
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= sqrAiProcessingDistance;
|
||||
bool inProcessingRange = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2() <= mActorsProcessingRange*mActorsProcessingRange;
|
||||
if (inProcessingRange)
|
||||
{
|
||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||
|
@ -1288,7 +1306,8 @@ namespace MWMechanics
|
|||
if (timerUpdateEquippedLight >= updateEquippedLightInterval) timerUpdateEquippedLight = 0;
|
||||
|
||||
// show torches only when there are darkness and no precipitations
|
||||
bool showTorches = MWBase::Environment::get().getWorld()->useTorches();
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
bool showTorches = world->useTorches();
|
||||
|
||||
MWWorld::Ptr player = getPlayer();
|
||||
const osg::Vec3f playerPos = player.getRefData().getPosition().asVec3();
|
||||
|
@ -1301,7 +1320,7 @@ namespace MWMechanics
|
|||
int attackedByPlayerId = player.getClass().getCreatureStats(player).getHitAttemptActorId();
|
||||
if (attackedByPlayerId != -1)
|
||||
{
|
||||
const MWWorld::Ptr playerHitAttemptActor = MWBase::Environment::get().getWorld()->searchPtrViaActorId(attackedByPlayerId);
|
||||
const MWWorld::Ptr playerHitAttemptActor = world->searchPtrViaActorId(attackedByPlayerId);
|
||||
|
||||
if (!playerHitAttemptActor.isInCell())
|
||||
player.getClass().getCreatureStats(player).setHitAttemptActorId(-1);
|
||||
|
@ -1314,14 +1333,11 @@ namespace MWMechanics
|
|||
CharacterController* ctrl = iter->second->getCharacterController();
|
||||
|
||||
float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
|
||||
// AI processing is only done within distance of 7168 units to the player. Note the "AI distance" slider doesn't affect this
|
||||
// (it only does some throttling for targets beyond the "AI distance", so doesn't give any guarantees as to whether AI will be enabled or not)
|
||||
// This distance could be made configurable later, but the setting must be marked with a big warning:
|
||||
// using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876)
|
||||
bool inProcessingRange = distSqr <= sqrAiProcessingDistance;
|
||||
// AI processing is only done within given distance to the player.
|
||||
bool inProcessingRange = distSqr <= mActorsProcessingRange*mActorsProcessingRange;
|
||||
|
||||
if (isPlayer)
|
||||
ctrl->setAttackingOrSpell(MWBase::Environment::get().getWorld()->getPlayer().getAttackingOrSpell());
|
||||
ctrl->setAttackingOrSpell(world->getPlayer().getAttackingOrSpell());
|
||||
|
||||
// If dead or no longer in combat, no longer store any actors who attempted to hit us. Also remove for the player.
|
||||
if (iter->first != player && (iter->first.getClass().getCreatureStats(iter->first).isDead()
|
||||
|
@ -1335,10 +1351,10 @@ namespace MWMechanics
|
|||
|
||||
if (!iter->first.getClass().getCreatureStats(iter->first).isDead())
|
||||
{
|
||||
bool cellChanged = MWBase::Environment::get().getWorld()->hasCellChanged();
|
||||
bool cellChanged = world->hasCellChanged();
|
||||
MWWorld::Ptr actor = iter->first; // make a copy of the map key to avoid it being invalidated when the player teleports
|
||||
updateActor(actor, duration);
|
||||
if (!cellChanged && MWBase::Environment::get().getWorld()->hasCellChanged())
|
||||
if (!cellChanged && world->hasCellChanged())
|
||||
{
|
||||
return; // for now abort update of the old cell when cell changes by teleportation magic effect
|
||||
// a better solution might be to apply cell changes at the end of the frame
|
||||
|
@ -1363,7 +1379,7 @@ namespace MWMechanics
|
|||
MWWorld::Ptr headTrackTarget;
|
||||
|
||||
MWMechanics::CreatureStats& stats = iter->first.getClass().getCreatureStats(iter->first);
|
||||
bool firstPersonPlayer = isPlayer && MWBase::Environment::get().getWorld()->isFirstPerson();
|
||||
bool firstPersonPlayer = isPlayer && world->isFirstPerson();
|
||||
|
||||
// 1. Unconsious actor can not track target
|
||||
// 2. Actors in combat and pursue mode do not bother to headtrack
|
||||
|
@ -1423,27 +1439,25 @@ namespace MWMechanics
|
|||
CharacterController* playerCharacter = nullptr;
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
{
|
||||
const float animationDistance = aiProcessingDistance + 400; // Slightly larger than AI distance so there is time to switch back to the idle animation.
|
||||
const float distSqr = (playerPos - iter->first.getRefData().getPosition().asVec3()).length2();
|
||||
const float dist = (playerPos - iter->first.getRefData().getPosition().asVec3()).length();
|
||||
bool isPlayer = iter->first == player;
|
||||
bool inAnimationRange = isPlayer || (animationDistance == 0 || distSqr <= animationDistance*animationDistance);
|
||||
bool inRange = isPlayer || dist <= mActorsProcessingRange;
|
||||
int activeFlag = 1; // Can be changed back to '2' to keep updating bounding boxes off screen (more accurate, but slower)
|
||||
if (isPlayer)
|
||||
activeFlag = 2;
|
||||
int active = inAnimationRange ? activeFlag : 0;
|
||||
bool canFly = iter->first.getClass().canFly(iter->first);
|
||||
if (canFly)
|
||||
{
|
||||
// Keep animating flying creatures so they don't just hover in-air
|
||||
inAnimationRange = true;
|
||||
active = std::max(1, active);
|
||||
}
|
||||
int active = inRange ? activeFlag : 0;
|
||||
|
||||
CharacterController* ctrl = iter->second->getCharacterController();
|
||||
ctrl->setActive(active);
|
||||
|
||||
if (!inAnimationRange)
|
||||
if (!inRange)
|
||||
{
|
||||
iter->first.getRefData().getBaseNode()->setNodeMask(0);
|
||||
world->setActorCollisionMode(iter->first, false);
|
||||
continue;
|
||||
}
|
||||
else if (!isPlayer)
|
||||
iter->first.getRefData().getBaseNode()->setNodeMask(1<<3);
|
||||
|
||||
if (iter->first.getClass().getCreatureStats(iter->first).isParalyzed())
|
||||
ctrl->skipAnim();
|
||||
|
@ -1455,11 +1469,28 @@ namespace MWMechanics
|
|||
playerCharacter = ctrl;
|
||||
continue;
|
||||
}
|
||||
|
||||
world->setActorCollisionMode(iter->first, true);
|
||||
ctrl->update(duration);
|
||||
|
||||
// Fade away actors on large distance (>90% of actor's processing distance)
|
||||
float visibilityRatio = 1.0;
|
||||
float fadeStartDistance = mActorsProcessingRange*0.9f;
|
||||
float fadeEndDistance = mActorsProcessingRange;
|
||||
float fadeRatio = (dist - fadeStartDistance)/(fadeEndDistance - fadeStartDistance);
|
||||
if (fadeRatio > 0)
|
||||
visibilityRatio -= std::max(0.f, fadeRatio);
|
||||
|
||||
visibilityRatio = std::min(1.f, visibilityRatio);
|
||||
|
||||
ctrl->setVisibility(visibilityRatio);
|
||||
}
|
||||
|
||||
if (playerCharacter)
|
||||
{
|
||||
playerCharacter->update(duration);
|
||||
playerCharacter->setVisibility(1.f);
|
||||
}
|
||||
|
||||
for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter)
|
||||
{
|
||||
|
@ -1671,7 +1702,7 @@ namespace MWMechanics
|
|||
restoreDynamicStats(iter->first, sleep);
|
||||
|
||||
if ((!iter->first.getRefData().getBaseNode()) ||
|
||||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > sqrAiProcessingDistance)
|
||||
(playerPos - iter->first.getRefData().getPosition().asVec3()).length2() > mActorsProcessingRange*mActorsProcessingRange)
|
||||
continue;
|
||||
|
||||
adjustMagicEffects (iter->first);
|
||||
|
@ -1915,7 +1946,7 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> list;
|
||||
std::vector<MWWorld::Ptr> neighbors;
|
||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||
getObjectsInRange(position, mActorsProcessingRange, neighbors);
|
||||
for(auto neighbor = neighbors.begin(); neighbor != neighbors.end(); ++neighbor)
|
||||
{
|
||||
if (*neighbor == actor)
|
||||
|
@ -1936,7 +1967,7 @@ namespace MWMechanics
|
|||
std::list<MWWorld::Ptr> list;
|
||||
std::vector<MWWorld::Ptr> neighbors;
|
||||
osg::Vec3f position (actor.getRefData().getPosition().asVec3());
|
||||
getObjectsInRange(position, aiProcessingDistance, neighbors);
|
||||
getObjectsInRange(position, mActorsProcessingRange, neighbors);
|
||||
|
||||
std::set<MWWorld::Ptr> followers;
|
||||
getActorsFollowing(actor, followers);
|
||||
|
|
|
@ -65,6 +65,9 @@ namespace MWMechanics
|
|||
/// paused we may want to do it manually (after equipping permanent enchantment)
|
||||
void updateMagicEffects (const MWWorld::Ptr& ptr);
|
||||
|
||||
void updateProcessingRange();
|
||||
float getProcessingRange() const;
|
||||
|
||||
void addActor (const MWWorld::Ptr& ptr, bool updateImmediately=false);
|
||||
///< Register an actor for stats management
|
||||
///
|
||||
|
@ -168,6 +171,7 @@ namespace MWMechanics
|
|||
private:
|
||||
PtrActorMap mActors;
|
||||
float mTimerDisposeSummonsCorpses;
|
||||
float mActorsProcessingRange;
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace MWMechanics
|
|||
return true; //Target doesn't exist
|
||||
|
||||
//Set the target destination for the actor
|
||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
||||
const auto dest = target.getRefData().getPosition().asVec3();
|
||||
|
||||
if (pathTo(actor, dest, duration, MWBase::Environment::get().getWorld()->getMaxActivationDistance())) //Stop when you get in activation range
|
||||
{
|
||||
|
|
|
@ -37,7 +37,7 @@ bool MWMechanics::AiCast::execute(const MWWorld::Ptr& actor, MWMechanics::Charac
|
|||
if (!target)
|
||||
return true;
|
||||
|
||||
if (!mManual && !pathTo(actor, target.getRefData().getPosition().pos, duration, mDistance))
|
||||
if (!mManual && !pathTo(actor, target.getRefData().getPosition().asVec3(), duration, mDistance))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ namespace MWMechanics
|
|||
float targetReachedTolerance = 0.0f;
|
||||
if (storage.mLOS)
|
||||
targetReachedTolerance = storage.mAttackRange;
|
||||
bool is_target_reached = pathTo(actor, target.getRefData().getPosition().pos, duration, targetReachedTolerance);
|
||||
const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance);
|
||||
if (is_target_reached) storage.mReadyToAttack = true;
|
||||
}
|
||||
|
||||
|
@ -307,7 +307,7 @@ namespace MWMechanics
|
|||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||
coords.toLocal(localPos);
|
||||
|
||||
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos);
|
||||
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
||||
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
|
||||
{
|
||||
if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i))
|
||||
|
@ -359,7 +359,7 @@ namespace MWMechanics
|
|||
|
||||
float dist = (actor.getRefData().getPosition().asVec3() - target.getRefData().getPosition().asVec3()).length();
|
||||
if ((dist > fFleeDistance && !storage.mLOS)
|
||||
|| pathTo(actor, storage.mFleeDest, duration))
|
||||
|| pathTo(actor, PathFinder::makeOsgVec3(storage.mFleeDest), duration))
|
||||
{
|
||||
state = AiCombatStorage::FleeState_Idle;
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace MWMechanics
|
|||
point.mAutogenerated = 0;
|
||||
point.mConnectionNum = 0;
|
||||
point.mUnknown = 0;
|
||||
if (pathTo(actor,point,duration)) //Returns true on path complete
|
||||
if (pathTo(actor, osg::Vec3f(mX, mY, mZ), duration)) //Returns true on path complete
|
||||
{
|
||||
mRemainingDuration = mDuration;
|
||||
return true;
|
||||
|
|
|
@ -163,7 +163,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
}
|
||||
|
||||
//Set the target destination from the actor
|
||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
||||
const auto dest = target.getRefData().getPosition().asVec3();
|
||||
|
||||
short baseFollowDistance = followDistance;
|
||||
short threshold = 30; // to avoid constant switching between moving/stopping
|
||||
|
@ -196,7 +196,7 @@ bool AiFollow::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
if (storage.mMoving)
|
||||
{
|
||||
//Check if you're far away
|
||||
float dist = distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]);
|
||||
float dist = distance(dest, pos.asVec3());
|
||||
|
||||
if (dist > 450)
|
||||
actor.getClass().getCreatureStats(actor).setMovementFlag(MWMechanics::CreatureStats::Flag_Run, true); //Make NPC run
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include <components/esm/loadcell.hpp>
|
||||
#include <components/esm/loadland.hpp>
|
||||
#include <components/esm/loadmgef.hpp>
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
@ -90,70 +91,67 @@ void MWMechanics::AiPackage::reset()
|
|||
mTimer = AI_REACTION_TIME + 1.0f;
|
||||
mIsShortcutting = false;
|
||||
mShortcutProhibited = false;
|
||||
mShortcutFailPos = ESM::Pathgrid::Point();
|
||||
mShortcutFailPos = osg::Vec3f();
|
||||
|
||||
mPathFinder.clearPath();
|
||||
mObstacleCheck.clear();
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance)
|
||||
bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance)
|
||||
{
|
||||
mTimer += duration; //Update timer
|
||||
|
||||
ESM::Position pos = actor.getRefData().getPosition(); //position of the actor
|
||||
const auto position = actor.getRefData().getPosition().asVec3(); //position of the actor
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
|
||||
{
|
||||
const auto halfExtents = world->getHalfExtents(actor);
|
||||
world->updateActorPath(actor, mPathFinder.getPath(), halfExtents, position, dest);
|
||||
}
|
||||
|
||||
/// Stops the actor when it gets too close to a unloaded cell
|
||||
//... At current time, this test is unnecessary. AI shuts down when actor is more than 7168
|
||||
//... At current time, this test is unnecessary. AI shuts down when actor is more than "actors processing range" setting value
|
||||
//... units from player, and exterior cells are 8192 units long and wide.
|
||||
//... But AI processing distance may increase in the future.
|
||||
if (isNearInactiveCell(pos))
|
||||
if (isNearInactiveCell(position))
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// handle path building and shortcutting
|
||||
ESM::Pathgrid::Point start = pos.pos;
|
||||
|
||||
float distToTarget = distance(start, dest);
|
||||
bool isDestReached = (distToTarget <= destTolerance);
|
||||
const float distToTarget = distance(position, dest);
|
||||
const bool isDestReached = (distToTarget <= destTolerance);
|
||||
|
||||
if (!isDestReached && mTimer > AI_REACTION_TIME)
|
||||
{
|
||||
if (actor.getClass().isBipedal(actor))
|
||||
openDoors(actor);
|
||||
|
||||
bool wasShortcutting = mIsShortcutting;
|
||||
const bool wasShortcutting = mIsShortcutting;
|
||||
bool destInLOS = false;
|
||||
|
||||
const MWWorld::Class& actorClass = actor.getClass();
|
||||
MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||
|
||||
// check if actor can move along z-axis
|
||||
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|
||||
|| world->isFlying(actor);
|
||||
const bool actorCanMoveByZ = canActorMoveByZAxis(actor);
|
||||
|
||||
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
|
||||
if (actorCanMoveByZ || getTypeId() != TypeIdWander)
|
||||
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
|
||||
mIsShortcutting = actorCanMoveByZ
|
||||
&& shortcutPath(position, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
|
||||
|
||||
if (!mIsShortcutting)
|
||||
{
|
||||
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
|
||||
{
|
||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr());
|
||||
mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()),
|
||||
playerHalfExtents, getNavigatorFlags(actor));
|
||||
mRotateOnTheRunChecks = 3;
|
||||
|
||||
// give priority to go directly on target if there is minimal opportunity
|
||||
if (destInLOS && mPathFinder.getPath().size() > 1)
|
||||
{
|
||||
// get point just before dest
|
||||
std::list<ESM::Pathgrid::Point>::const_iterator pPointBeforeDest = mPathFinder.getPath().end();
|
||||
--pPointBeforeDest;
|
||||
--pPointBeforeDest;
|
||||
auto pPointBeforeDest = mPathFinder.getPath().rbegin() + 1;
|
||||
|
||||
// if start point is closer to the target then last point of path (excluding target itself) then go straight on the target
|
||||
if (distance(start, dest) <= distance(dest, *pPointBeforeDest))
|
||||
if (distance(position, dest) <= distance(dest, *pPointBeforeDest))
|
||||
{
|
||||
mPathFinder.clearPath();
|
||||
mPathFinder.addPointToPath(dest);
|
||||
|
@ -163,7 +161,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
|||
|
||||
if (!mPathFinder.getPath().empty()) //Path has points in it
|
||||
{
|
||||
ESM::Pathgrid::Point lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
|
||||
const auto& lastPos = mPathFinder.getPath().back(); //Get the end of the proposed path
|
||||
|
||||
if(distance(dest, lastPos) > 100) //End of the path is far from the destination
|
||||
mPathFinder.addPointToPath(dest); //Adds the final destination to the path, to try to get to where you want to go
|
||||
|
@ -173,15 +171,18 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
|||
mTimer = 0;
|
||||
}
|
||||
|
||||
if (isDestReached || mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1])) // if path is finished
|
||||
const auto pointTolerance = std::min(actor.getClass().getSpeed(actor), DEFAULT_TOLERANCE);
|
||||
|
||||
mPathFinder.update(position, pointTolerance, destTolerance);
|
||||
|
||||
if (isDestReached || mPathFinder.checkPathCompleted()) // if path is finished
|
||||
{
|
||||
// turn to destination point
|
||||
zTurn(actor, getZAngleToPoint(start, dest));
|
||||
smoothTurn(actor, getXAngleToPoint(start, dest), 0);
|
||||
zTurn(actor, getZAngleToPoint(position, dest));
|
||||
smoothTurn(actor, getXAngleToPoint(position, dest), 0);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if (mRotateOnTheRunChecks == 0
|
||||
|| isReachableRotatingOnTheRun(actor, *mPathFinder.getPath().begin())) // to prevent circling around a path point
|
||||
{
|
||||
|
@ -189,25 +190,22 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
|
|||
if (mRotateOnTheRunChecks > 0) mRotateOnTheRunChecks--;
|
||||
}
|
||||
|
||||
// handle obstacles on the way
|
||||
evadeObstacles(actor, duration, pos);
|
||||
}
|
||||
|
||||
// turn to next path point by X,Z axes
|
||||
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
||||
smoothTurn(actor, mPathFinder.getXAngleToNext(pos.pos[0], pos.pos[1], pos.pos[2]), 0);
|
||||
zTurn(actor, mPathFinder.getZAngleToNext(position.x(), position.y()));
|
||||
smoothTurn(actor, mPathFinder.getXAngleToNext(position.x(), position.y(), position.z()), 0);
|
||||
|
||||
mObstacleCheck.update(actor, duration);
|
||||
|
||||
// handle obstacles on the way
|
||||
evadeObstacles(actor);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos)
|
||||
void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor)
|
||||
{
|
||||
zTurn(actor, mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]));
|
||||
|
||||
MWMechanics::Movement& movement = actor.getClass().getMovementSettings(actor);
|
||||
|
||||
// check if stuck due to obstacles
|
||||
if (!mObstacleCheck.check(actor, duration)) return;
|
||||
if (!mObstacleCheck.isEvading()) return;
|
||||
|
||||
// first check if obstacle is a door
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||
|
@ -219,13 +217,14 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
|
|||
}
|
||||
else
|
||||
{
|
||||
mObstacleCheck.takeEvasiveAction(movement);
|
||||
mObstacleCheck.takeEvasiveAction(actor.getClass().getMovementSettings(actor));
|
||||
}
|
||||
}
|
||||
|
||||
void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
||||
{
|
||||
static float distance = MWBase::Environment::get().getWorld()->getMaxActivationDistance();
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
static float distance = world->getMaxActivationDistance();
|
||||
|
||||
const MWWorld::Ptr door = getNearbyDoor(actor, distance);
|
||||
if (door == MWWorld::Ptr())
|
||||
|
@ -236,7 +235,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
|||
{
|
||||
if ((door.getCellRef().getTrap().empty() && door.getCellRef().getLockLevel() <= 0 ))
|
||||
{
|
||||
MWBase::Environment::get().getWorld()->activate(door, actor);
|
||||
world->activate(door, actor);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -248,7 +247,7 @@ void MWMechanics::AiPackage::openDoors(const MWWorld::Ptr& actor)
|
|||
MWWorld::Ptr keyPtr = invStore.search(keyId);
|
||||
|
||||
if (!keyPtr.isEmpty())
|
||||
MWBase::Environment::get().getWorld()->activate(door, actor);
|
||||
world->activate(door, actor);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,14 +265,15 @@ const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const
|
|||
return *cache[id].get();
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
|
||||
bool MWMechanics::AiPackage::shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
|
||||
{
|
||||
if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
|
||||
if (!mShortcutProhibited || (mShortcutFailPos - startPoint).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
|
||||
{
|
||||
// check if target is clearly visible
|
||||
isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
||||
static_cast<float>(startPoint.mX), static_cast<float>(startPoint.mY), static_cast<float>(startPoint.mZ),
|
||||
static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), static_cast<float>(endPoint.mZ));
|
||||
startPoint.x(), startPoint.y(), startPoint.z(),
|
||||
endPoint.x(), endPoint.y(), endPoint.z());
|
||||
|
||||
if (destInLOS != nullptr) *destInLOS = isPathClear;
|
||||
|
||||
|
@ -294,21 +294,22 @@ bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor)
|
||||
bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor)
|
||||
{
|
||||
bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|
||||
|| MWBase::Environment::get().getWorld()->isFlying(actor);
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && world->isSwimming(actor))
|
||||
|| world->isFlying(actor);
|
||||
|
||||
if (actorCanMoveByZ)
|
||||
return true;
|
||||
|
||||
float actorSpeed = actor.getClass().getSpeed(actor);
|
||||
float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
||||
osg::Vec3f::value_type distToTarget = osg::Vec3f(static_cast<float>(endPoint.mX), static_cast<float>(endPoint.mY), 0).length();
|
||||
const float actorSpeed = actor.getClass().getSpeed(actor);
|
||||
const float maxAvoidDist = AI_REACTION_TIME * actorSpeed + actorSpeed / MAX_VEL_ANGULAR_RADIANS * 2; // *2 - for reliability
|
||||
const float distToTarget = osg::Vec2f(endPoint.x(), endPoint.y()).length();
|
||||
|
||||
float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
||||
const float offsetXY = distToTarget > maxAvoidDist*1.5? maxAvoidDist : maxAvoidDist/2;
|
||||
|
||||
bool isClear = checkWayIsClear(PathFinder::MakeOsgVec3(startPoint), PathFinder::MakeOsgVec3(endPoint), offsetXY);
|
||||
const bool isClear = checkWayIsClear(startPoint, endPoint, offsetXY);
|
||||
|
||||
// update shortcut prohibit state
|
||||
if (isClear)
|
||||
|
@ -316,12 +317,12 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point&
|
|||
if (mShortcutProhibited)
|
||||
{
|
||||
mShortcutProhibited = false;
|
||||
mShortcutFailPos = ESM::Pathgrid::Point();
|
||||
mShortcutFailPos = osg::Vec3f();
|
||||
}
|
||||
}
|
||||
if (!isClear)
|
||||
{
|
||||
if (mShortcutFailPos.mX == 0 && mShortcutFailPos.mY == 0 && mShortcutFailPos.mZ == 0)
|
||||
if (mShortcutFailPos == osg::Vec3f())
|
||||
{
|
||||
mShortcutProhibited = true;
|
||||
mShortcutFailPos = startPoint;
|
||||
|
@ -331,9 +332,11 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const ESM::Pathgrid::Point&
|
|||
return isClear;
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell)
|
||||
bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell)
|
||||
{
|
||||
return mPathFinder.getPath().empty() || (distance(mPathFinder.getPath().back(), newDest) > 10) || mPathFinder.getPathCell() != currentCell;
|
||||
return mPathFinder.getPath().empty()
|
||||
|| (distance(mPathFinder.getPath().back(), newDest) > 10)
|
||||
|| mPathFinder.getPathCell() != currentCell;
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|
||||
|
@ -343,23 +346,22 @@ bool MWMechanics::AiPackage::isTargetMagicallyHidden(const MWWorld::Ptr& target)
|
|||
|| (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75);
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
|
||||
bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position)
|
||||
{
|
||||
const ESM::Cell* playerCell(getPlayer().getCell()->getCell());
|
||||
if (playerCell->isExterior())
|
||||
{
|
||||
// get actor's distance from origin of center cell
|
||||
osg::Vec3f actorOffset(actorPos.asVec3());
|
||||
CoordinateConverter(playerCell).toLocal(actorOffset);
|
||||
CoordinateConverter(playerCell).toLocal(position);
|
||||
|
||||
// currently assumes 3 x 3 grid for exterior cells, with player at center cell.
|
||||
// ToDo: (Maybe) use "exterior cell load distance" setting to get count of actual active cells
|
||||
// While AI Process distance is 7168, AI shuts down actors before they reach edges of 3 x 3 grid.
|
||||
// AI shuts down actors before they reach edges of 3 x 3 grid.
|
||||
const float distanceFromEdge = 200.0;
|
||||
float minThreshold = (-1.0f * ESM::Land::REAL_SIZE) + distanceFromEdge;
|
||||
float maxThreshold = (2.0f * ESM::Land::REAL_SIZE) - distanceFromEdge;
|
||||
return (actorOffset[0] < minThreshold) || (maxThreshold < actorOffset[0])
|
||||
|| (actorOffset[1] < minThreshold) || (maxThreshold < actorOffset[1]);
|
||||
return (position.x() < minThreshold) || (maxThreshold < position.x())
|
||||
|| (position.y() < minThreshold) || (maxThreshold < position.y());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -367,7 +369,7 @@ bool MWMechanics::AiPackage::isNearInactiveCell(const ESM::Position& actorPos)
|
|||
}
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest)
|
||||
bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest)
|
||||
{
|
||||
// get actor's shortest radius for moving in circle
|
||||
float speed = actor.getClass().getSpeed(actor);
|
||||
|
@ -383,15 +385,38 @@ bool MWMechanics::AiPackage::isReachableRotatingOnTheRun(const MWWorld::Ptr& act
|
|||
radiusDir *= radius;
|
||||
|
||||
// pick up the nearest center candidate
|
||||
osg::Vec3f dest_ = PathFinder::MakeOsgVec3(dest);
|
||||
osg::Vec3f pos = actor.getRefData().getPosition().asVec3();
|
||||
osg::Vec3f center1 = pos - radiusDir;
|
||||
osg::Vec3f center2 = pos + radiusDir;
|
||||
osg::Vec3f center = (center1 - dest_).length2() < (center2 - dest_).length2() ? center1 : center2;
|
||||
osg::Vec3f center = (center1 - dest).length2() < (center2 - dest).length2() ? center1 : center2;
|
||||
|
||||
float distToDest = (center - dest_).length();
|
||||
float distToDest = (center - dest).length();
|
||||
|
||||
// if pathpoint is reachable for the actor rotating on the run:
|
||||
// no points of actor's circle should be farther from the center than destination point
|
||||
return (radius <= distToDest);
|
||||
}
|
||||
|
||||
DetourNavigator::Flags MWMechanics::AiPackage::getNavigatorFlags(const MWWorld::Ptr& actor) const
|
||||
{
|
||||
const auto& actorClass = actor.getClass();
|
||||
DetourNavigator::Flags result = DetourNavigator::Flag_none;
|
||||
|
||||
if (actorClass.isPureWaterCreature(actor) || (getTypeId() != TypeIdWander && actorClass.canSwim(actor)))
|
||||
result |= DetourNavigator::Flag_swim;
|
||||
|
||||
if (actorClass.canWalk(actor))
|
||||
result |= DetourNavigator::Flag_walk;
|
||||
|
||||
if (actorClass.isBipedal(actor) && getTypeId() != TypeIdWander)
|
||||
result |= DetourNavigator::Flag_openDoor;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MWMechanics::AiPackage::canActorMoveByZAxis(const MWWorld::Ptr& actor) const
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto& actorClass = actor.getClass();
|
||||
return (actorClass.canSwim(actor) && world->isSwimming(actor)) || world->isFlying(actor);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include "obstacle.hpp"
|
||||
#include "aistate.hpp"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class Ptr;
|
||||
|
@ -105,29 +107,35 @@ namespace MWMechanics
|
|||
bool isTargetMagicallyHidden(const MWWorld::Ptr& target);
|
||||
|
||||
/// Return if actor's rotation speed is sufficient to rotate to the destination pathpoint on the run. Otherwise actor should rotate while standing.
|
||||
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest);
|
||||
static bool isReachableRotatingOnTheRun(const MWWorld::Ptr& actor, const osg::Vec3f& dest);
|
||||
|
||||
protected:
|
||||
/// Handles path building and shortcutting with obstacles avoiding
|
||||
/** \return If the actor has arrived at his destination **/
|
||||
bool pathTo(const MWWorld::Ptr& actor, const ESM::Pathgrid::Point& dest, float duration, float destTolerance = 0.0f);
|
||||
bool pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& dest, float duration, float destTolerance = 0.0f);
|
||||
|
||||
/// Check if there aren't any obstacles along the path to make shortcut possible
|
||||
/// If a shortcut is possible then path will be cleared and filled with the destination point.
|
||||
/// \param destInLOS If not nullptr function will return ray cast check result
|
||||
/// \return If can shortcut the path
|
||||
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear);
|
||||
bool shortcutPath(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor,
|
||||
bool *destInLOS, bool isPathClear);
|
||||
|
||||
/// Check if the way to the destination is clear, taking into account actor speed
|
||||
bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor);
|
||||
bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor);
|
||||
|
||||
virtual bool doesPathNeedRecalc(const ESM::Pathgrid::Point& newDest, const MWWorld::CellStore* currentCell);
|
||||
bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell);
|
||||
|
||||
void evadeObstacles(const MWWorld::Ptr& actor);
|
||||
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos);
|
||||
void openDoors(const MWWorld::Ptr& actor);
|
||||
|
||||
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
|
||||
|
||||
DetourNavigator::Flags getNavigatorFlags(const MWWorld::Ptr& actor) const;
|
||||
|
||||
bool canActorMoveByZAxis(const MWWorld::Ptr& actor) const;
|
||||
|
||||
// TODO: all this does not belong here, move into temporary storage
|
||||
PathFinder mPathFinder;
|
||||
ObstacleCheck mObstacleCheck;
|
||||
|
@ -137,16 +145,14 @@ namespace MWMechanics
|
|||
std::string mTargetActorRefId;
|
||||
mutable int mTargetActorId;
|
||||
|
||||
osg::Vec3f mLastActorPos;
|
||||
|
||||
short mRotateOnTheRunChecks; // attempts to check rotation to the pathpoint on the run possibility
|
||||
|
||||
bool mIsShortcutting; // if shortcutting at the moment
|
||||
bool mShortcutProhibited; // shortcutting may be prohibited after unsuccessful attempt
|
||||
ESM::Pathgrid::Point mShortcutFailPos; // position of last shortcut fail
|
||||
osg::Vec3f mShortcutFailPos; // position of last shortcut fail
|
||||
|
||||
private:
|
||||
bool isNearInactiveCell(const ESM::Position& actorPos);
|
||||
bool isNearInactiveCell(osg::Vec3f position);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -49,13 +49,13 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, CharacterController& characte
|
|||
actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing);
|
||||
|
||||
//Set the target desition from the actor
|
||||
ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos;
|
||||
const auto dest = target.getRefData().getPosition().asVec3();
|
||||
ESM::Position aPos = actor.getRefData().getPosition();
|
||||
|
||||
float pathTolerance = 100.0;
|
||||
|
||||
if (pathTo(actor, dest, duration, pathTolerance) &&
|
||||
std::abs(dest.mZ - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction
|
||||
std::abs(dest.z() - aPos.pos[2]) < pathTolerance) // check the true distance in case the target is far away in Z-direction
|
||||
{
|
||||
target.getClass().activate(target,actor).get()->execute(actor); //Arrest player when reached
|
||||
return true;
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
#include <components/esm/aisequence.hpp>
|
||||
#include <components/esm/loadcell.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
@ -21,7 +22,8 @@ bool isWithinMaxRange(const osg::Vec3f& pos1, const osg::Vec3f& pos2)
|
|||
// Maximum travel distance for vanilla compatibility.
|
||||
// Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well.
|
||||
// We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways.
|
||||
return (pos1 - pos2).length2() <= 7168*7168;
|
||||
bool aiDistance = MWBase::Environment::get().getMechanicsManager()->getActorsProcessingRange();
|
||||
return (pos1 - pos2).length2() <= aiDistance*aiDistance;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -56,8 +58,7 @@ namespace MWMechanics
|
|||
// Unfortunately, with vanilla assets destination is sometimes blocked by other actor.
|
||||
// If we got close to target, check for actors nearby. If they are, finish AI package.
|
||||
int destinationTolerance = 64;
|
||||
ESM::Pathgrid::Point dest(static_cast<int>(mX), static_cast<int>(mY), static_cast<int>(mZ));
|
||||
if (distance(pos.pos, dest) <= destinationTolerance)
|
||||
if (distance(pos.asVec3(), osg::Vec3f(mX, mY, mZ)) <= destinationTolerance)
|
||||
{
|
||||
std::vector<MWWorld::Ptr> targetActors;
|
||||
std::pair<MWWorld::Ptr, osg::Vec3f> result = MWBase::Environment::get().getWorld()->getHitContact(actor, destinationTolerance, targetActors);
|
||||
|
@ -69,7 +70,7 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
if (pathTo(actor, dest, duration))
|
||||
if (pathTo(actor, osg::Vec3f(mX, mY, mZ), duration))
|
||||
{
|
||||
actor.getClass().getMovementSettings(actor).mPosition[1] = 0;
|
||||
return true;
|
||||
|
|
|
@ -50,7 +50,8 @@ namespace MWMechanics
|
|||
|
||||
AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector<unsigned char>& idle, bool repeat):
|
||||
mDistance(distance), mDuration(duration), mRemainingDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle),
|
||||
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)), mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0))
|
||||
mRepeat(repeat), mStoredInitialActorPosition(false), mInitialActorPosition(osg::Vec3f(0, 0, 0)),
|
||||
mHasDestination(false), mDestination(osg::Vec3f(0, 0, 0)), mUsePathgrid(false)
|
||||
{
|
||||
mIdle.resize(8, 0);
|
||||
init();
|
||||
|
@ -151,16 +152,24 @@ namespace MWMechanics
|
|||
// rebuild a path to it
|
||||
if (!mPathFinder.isPathConstructed() && mHasDestination)
|
||||
{
|
||||
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
|
||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
|
||||
|
||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
if (mUsePathgrid)
|
||||
{
|
||||
mPathFinder.buildPathByPathgrid(pos.asVec3(), mDestination, actor.getCell(),
|
||||
getPathGridGraph(actor.getCell()));
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();
|
||||
const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr());
|
||||
mPathFinder.buildPath(actor, pos.asVec3(), mDestination, actor.getCell(),
|
||||
getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor));
|
||||
}
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
storage.setState(AiWanderStorage::Wander_Walking);
|
||||
}
|
||||
|
||||
doPerFrameActionsForState(actor, duration, storage, pos);
|
||||
doPerFrameActionsForState(actor, duration, storage);
|
||||
|
||||
playIdleDialogueRandomly(actor);
|
||||
|
||||
|
@ -169,14 +178,14 @@ namespace MWMechanics
|
|||
if (AI_REACTION_TIME <= lastReaction)
|
||||
{
|
||||
lastReaction = 0;
|
||||
return reactionTimeActions(actor, storage, currentCell, cellChange, pos, duration);
|
||||
return reactionTimeActions(actor, storage, currentCell, cellChange, pos);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AiWander::reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
||||
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration)
|
||||
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos)
|
||||
{
|
||||
if (mDistance <= 0)
|
||||
storage.mCanWanderAlongPathGrid = false;
|
||||
|
@ -226,7 +235,7 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
// If Wandering manually and hit an obstacle, stop
|
||||
if (storage.mIsWanderingManually && mObstacleCheck.check(actor, duration, 2.0f)) {
|
||||
if (storage.mIsWanderingManually && mObstacleCheck.isEvading()) {
|
||||
completeManualWalking(actor, storage);
|
||||
}
|
||||
|
||||
|
@ -251,7 +260,9 @@ namespace MWMechanics
|
|||
setPathToAnAllowedNode(actor, storage, pos);
|
||||
}
|
||||
}
|
||||
} else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], DESTINATION_TOLERANCE)) {
|
||||
}
|
||||
else if (storage.mIsWanderingManually && mPathFinder.checkPathCompleted())
|
||||
{
|
||||
completeManualWalking(actor, storage);
|
||||
}
|
||||
|
||||
|
@ -292,11 +303,9 @@ namespace MWMechanics
|
|||
* Commands actor to walk to a random location near original spawn location.
|
||||
*/
|
||||
void AiWander::wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance) {
|
||||
const ESM::Pathgrid::Point currentPosition = actor.getRefData().getPosition().pos;
|
||||
const osg::Vec3f currentPositionVec3f = osg::Vec3f(currentPosition.mX, currentPosition.mY, currentPosition.mZ);
|
||||
const auto currentPosition = actor.getRefData().getPosition().asVec3();
|
||||
|
||||
std::size_t attempts = 10; // If a unit can't wander out of water, don't want to hang here
|
||||
ESM::Pathgrid::Point destinationPosition;
|
||||
bool isWaterCreature = actor.getClass().isPureWaterCreature(actor);
|
||||
do {
|
||||
// Determine a random location within radius of original position
|
||||
|
@ -305,19 +314,24 @@ namespace MWMechanics
|
|||
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
|
||||
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
|
||||
const float destinationZ = mInitialActorPosition.z();
|
||||
destinationPosition = ESM::Pathgrid::Point(destinationX, destinationY, destinationZ);
|
||||
const osg::Vec3f destinationPosition(destinationX, destinationY, destinationZ);
|
||||
mDestination = osg::Vec3f(destinationX, destinationY, destinationZ);
|
||||
|
||||
// Check if land creature will walk onto water or if water creature will swim onto land
|
||||
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
|
||||
(isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) {
|
||||
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
(isWaterCreature && !destinationThroughGround(currentPosition, mDestination)))
|
||||
{
|
||||
const auto world = MWBase::Environment::get().getWorld();;
|
||||
const auto playerHalfExtents = world->getHalfExtents(world->getPlayerPtr());
|
||||
mPathFinder.buildPath(actor, currentPosition, destinationPosition, actor.getCell(),
|
||||
getPathGridGraph(actor.getCell()), playerHalfExtents, getNavigatorFlags(actor));
|
||||
mPathFinder.addPointToPath(destinationPosition);
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
{
|
||||
storage.setState(AiWanderStorage::Wander_Walking, true);
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -348,7 +362,7 @@ namespace MWMechanics
|
|||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
|
||||
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos)
|
||||
void AiWander::doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
switch (storage.mState)
|
||||
{
|
||||
|
@ -357,7 +371,7 @@ namespace MWMechanics
|
|||
break;
|
||||
|
||||
case AiWanderStorage::Wander_Walking:
|
||||
onWalkingStatePerFrameActions(actor, duration, storage, pos);
|
||||
onWalkingStatePerFrameActions(actor, duration, storage);
|
||||
break;
|
||||
|
||||
case AiWanderStorage::Wander_ChooseAction:
|
||||
|
@ -413,11 +427,10 @@ namespace MWMechanics
|
|||
}
|
||||
}
|
||||
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor,
|
||||
float duration, AiWanderStorage& storage, ESM::Position& pos)
|
||||
void AiWander::onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage)
|
||||
{
|
||||
// Is there no destination or are we there yet?
|
||||
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, ESM::Pathgrid::Point(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|
||||
if ((!mPathFinder.isPathConstructed()) || pathTo(actor, osg::Vec3f(mPathFinder.getPath().back()), duration, DESTINATION_TOLERANCE))
|
||||
{
|
||||
stopWalking(actor, storage);
|
||||
storage.setState(AiWanderStorage::Wander_ChooseAction);
|
||||
|
@ -425,7 +438,7 @@ namespace MWMechanics
|
|||
else
|
||||
{
|
||||
// have not yet reached the destination
|
||||
evadeObstacles(actor, storage, duration, pos);
|
||||
evadeObstacles(actor, storage);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -456,7 +469,7 @@ namespace MWMechanics
|
|||
storage.setState(AiWanderStorage::Wander_IdleNow);
|
||||
}
|
||||
|
||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos)
|
||||
void AiWander::evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage)
|
||||
{
|
||||
if (mObstacleCheck.isEvading())
|
||||
{
|
||||
|
@ -592,15 +605,17 @@ namespace MWMechanics
|
|||
ToWorldCoordinates(dest, storage.mCell->getCell());
|
||||
|
||||
// actor position is already in world coordinates
|
||||
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
|
||||
const auto start = actorPos.asVec3();
|
||||
|
||||
// don't take shortcuts for wandering
|
||||
mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
const auto destVec3f = PathFinder::makeOsgVec3(dest);
|
||||
mPathFinder.buildPathByPathgrid(start, destVec3f, actor.getCell(), getPathGridGraph(actor.getCell()));
|
||||
|
||||
if (mPathFinder.isPathConstructed())
|
||||
{
|
||||
mDestination = osg::Vec3f(dest.mX, dest.mY, dest.mZ);
|
||||
mDestination = destVec3f;
|
||||
mHasDestination = true;
|
||||
mUsePathgrid = true;
|
||||
// Remove this node as an option and add back the previously used node (stops NPC from picking the same node):
|
||||
ESM::Pathgrid::Point temp = storage.mAllowedNodes[randNode];
|
||||
storage.mAllowedNodes.erase(storage.mAllowedNodes.begin() + randNode);
|
||||
|
@ -631,15 +646,15 @@ namespace MWMechanics
|
|||
// Every now and then check whether one of the doors is opened. (maybe
|
||||
// at the end of playing idle?) If the door is opened then re-calculate
|
||||
// allowed nodes starting from the spawn point.
|
||||
std::list<ESM::Pathgrid::Point> paths = pathfinder.getPath();
|
||||
auto paths = pathfinder.getPath();
|
||||
while(paths.size() >= 2)
|
||||
{
|
||||
ESM::Pathgrid::Point pt = paths.back();
|
||||
const auto pt = paths.back();
|
||||
for(unsigned int j = 0; j < nodes.size(); j++)
|
||||
{
|
||||
// FIXME: doesn't handle a door with the same X/Y
|
||||
// coordinates but with a different Z
|
||||
if(nodes[j].mX == pt.mX && nodes[j].mY == pt.mY)
|
||||
if (std::abs(nodes[j].mX - pt.x()) <= 0.5 && std::abs(nodes[j].mY - pt.y()) <= 0.5)
|
||||
{
|
||||
nodes.erase(nodes.begin() + j);
|
||||
break;
|
||||
|
@ -731,7 +746,7 @@ namespace MWMechanics
|
|||
ESM::Pathgrid::Point worldDest = dest;
|
||||
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
|
||||
|
||||
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
|
||||
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60);
|
||||
|
||||
// add offset only if the selected pathgrid is occupied by another actor
|
||||
if (isPathGridOccupied)
|
||||
|
@ -752,18 +767,18 @@ namespace MWMechanics
|
|||
ESM::Pathgrid::Point connDest = points[randomIndex];
|
||||
|
||||
// add an offset towards random neighboring node
|
||||
osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest);
|
||||
osg::Vec3f dir = PathFinder::makeOsgVec3(connDest) - PathFinder::makeOsgVec3(dest);
|
||||
float length = dir.length();
|
||||
dir.normalize();
|
||||
|
||||
for (int j = 1; j <= 3; j++)
|
||||
{
|
||||
// move for 5-15% towards random neighboring node
|
||||
dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
||||
dest = PathFinder::makePathgridPoint(PathFinder::makeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
|
||||
worldDest = dest;
|
||||
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
|
||||
|
||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
|
||||
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::makeOsgVec3(worldDest), 60);
|
||||
|
||||
if (!isOccupied)
|
||||
break;
|
||||
|
@ -799,7 +814,7 @@ namespace MWMechanics
|
|||
const ESM::Pathgrid *pathgrid =
|
||||
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
|
||||
|
||||
int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest));
|
||||
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||
|
||||
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
|
||||
}
|
||||
|
@ -832,7 +847,7 @@ namespace MWMechanics
|
|||
CoordinateConverter(cell).toLocal(npcPos);
|
||||
|
||||
// Find closest pathgrid point
|
||||
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, npcPos);
|
||||
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
||||
|
||||
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
||||
// and if the point is connected to the closest current point
|
||||
|
@ -840,7 +855,7 @@ namespace MWMechanics
|
|||
int pointIndex = 0;
|
||||
for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++)
|
||||
{
|
||||
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
|
||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(pathgrid->mPoints[counter]));
|
||||
if((npcPos - nodePos).length2() <= mDistance * mDistance &&
|
||||
getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
|
||||
{
|
||||
|
@ -867,7 +882,7 @@ namespace MWMechanics
|
|||
// 2. Partway along the path between the point and its connected points.
|
||||
void AiWander::AddNonPathGridAllowedPoints(osg::Vec3f npcPos, const ESM::Pathgrid * pathGrid, int pointIndex, AiWanderStorage& storage)
|
||||
{
|
||||
storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(npcPos));
|
||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(npcPos));
|
||||
for (std::vector<ESM::Pathgrid::Edge>::const_iterator it = pathGrid->mEdges.begin(); it != pathGrid->mEdges.end(); ++it)
|
||||
{
|
||||
if (it->mV0 == pointIndex)
|
||||
|
@ -879,8 +894,8 @@ namespace MWMechanics
|
|||
|
||||
void AiWander::AddPointBetweenPathGridPoints(const ESM::Pathgrid::Point& start, const ESM::Pathgrid::Point& end, AiWanderStorage& storage)
|
||||
{
|
||||
osg::Vec3f vectorStart = PathFinder::MakeOsgVec3(start);
|
||||
osg::Vec3f delta = PathFinder::MakeOsgVec3(end) - vectorStart;
|
||||
osg::Vec3f vectorStart = PathFinder::makeOsgVec3(start);
|
||||
osg::Vec3f delta = PathFinder::makeOsgVec3(end) - vectorStart;
|
||||
float length = delta.length();
|
||||
delta.normalize();
|
||||
|
||||
|
@ -889,7 +904,7 @@ namespace MWMechanics
|
|||
// must not travel longer than distance between waypoints or NPC goes past waypoint
|
||||
distance = std::min(distance, static_cast<int>(length));
|
||||
delta *= distance;
|
||||
storage.mAllowedNodes.push_back(PathFinder::MakePathgridPoint(vectorStart + delta));
|
||||
storage.mAllowedNodes.push_back(PathFinder::makePathgridPoint(vectorStart + delta));
|
||||
}
|
||||
|
||||
void AiWander::SetCurrentNodeToClosestAllowedNode(const osg::Vec3f& npcPos, AiWanderStorage& storage)
|
||||
|
@ -898,7 +913,7 @@ namespace MWMechanics
|
|||
unsigned int index = 0;
|
||||
for (unsigned int counterThree = 0; counterThree < storage.mAllowedNodes.size(); counterThree++)
|
||||
{
|
||||
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(storage.mAllowedNodes[counterThree]));
|
||||
osg::Vec3f nodePos(PathFinder::makeOsgVec3(storage.mAllowedNodes[counterThree]));
|
||||
float tempDist = (npcPos - nodePos).length2();
|
||||
if (tempDist < distanceToClosestNode)
|
||||
{
|
||||
|
|
|
@ -137,15 +137,15 @@ namespace MWMechanics
|
|||
short unsigned getRandomIdle();
|
||||
void setPathToAnAllowedNode(const MWWorld::Ptr& actor, AiWanderStorage& storage, const ESM::Position& actorPos);
|
||||
void playGreetingIfPlayerGetsTooClose(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage, float duration, ESM::Position& pos);
|
||||
void evadeObstacles(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void playIdleDialogueRandomly(const MWWorld::Ptr& actor);
|
||||
void turnActorToFacePlayer(const osg::Vec3f& actorPosition, const osg::Vec3f& playerPosition, AiWanderStorage& storage);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
|
||||
void doPerFrameActionsForState(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
void onIdleStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage, ESM::Position& pos);
|
||||
void onWalkingStatePerFrameActions(const MWWorld::Ptr& actor, float duration, AiWanderStorage& storage);
|
||||
void onChooseActionStatePerFrameActions(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
bool reactionTimeActions(const MWWorld::Ptr& actor, AiWanderStorage& storage,
|
||||
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos, float duration);
|
||||
const MWWorld::CellStore*& currentCell, bool cellChange, ESM::Position& pos);
|
||||
bool isPackageCompleted(const MWWorld::Ptr& actor, AiWanderStorage& storage);
|
||||
void wanderNearStart(const MWWorld::Ptr &actor, AiWanderStorage &storage, int wanderDistance);
|
||||
bool destinationIsAtWater(const MWWorld::Ptr &actor, const osg::Vec3f& destination);
|
||||
|
@ -164,6 +164,7 @@ namespace MWMechanics
|
|||
|
||||
bool mHasDestination;
|
||||
osg::Vec3f mDestination;
|
||||
bool mUsePathgrid;
|
||||
|
||||
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "../mwrender/animation.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
|
@ -1849,7 +1850,7 @@ void CharacterController::updateAnimQueue()
|
|||
mAnimation->setLoopingEnabled(mAnimQueue.front().mGroup, mAnimQueue.size() <= 1);
|
||||
}
|
||||
|
||||
void CharacterController::update(float duration)
|
||||
void CharacterController::update(float duration, bool animationOnly)
|
||||
{
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
const MWWorld::Class &cls = mPtr.getClass();
|
||||
|
@ -2235,10 +2236,10 @@ void CharacterController::update(float duration)
|
|||
world->rotateObject(mPtr, rot.x(), rot.y(), 0.0f, true);
|
||||
}
|
||||
|
||||
if (!mMovementAnimationControlled)
|
||||
if (!animationOnly && !mMovementAnimationControlled)
|
||||
world->queueMovement(mPtr, vec);
|
||||
}
|
||||
else
|
||||
else if (!animationOnly)
|
||||
// We must always queue movement, even if there is none, to apply gravity.
|
||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
|
||||
|
@ -2261,6 +2262,7 @@ void CharacterController::update(float duration)
|
|||
playDeath(1.f, mDeathState);
|
||||
}
|
||||
// We must always queue movement, even if there is none, to apply gravity.
|
||||
if (!animationOnly)
|
||||
world->queueMovement(mPtr, osg::Vec3f(0.f, 0.f, 0.f));
|
||||
}
|
||||
|
||||
|
@ -2295,7 +2297,7 @@ void CharacterController::update(float duration)
|
|||
moved.z() = 1.0;
|
||||
|
||||
// Update movement
|
||||
if(mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||
if(!animationOnly && mMovementAnimationControlled && mPtr.getClass().isActor())
|
||||
world->queueMovement(mPtr, moved);
|
||||
|
||||
mSkipAnim = false;
|
||||
|
@ -2521,6 +2523,7 @@ void CharacterController::resurrect()
|
|||
mAnimation->disable(mCurrentDeath);
|
||||
mCurrentDeath.clear();
|
||||
mDeathState = CharState_None;
|
||||
mWeaponType = WeapType_None;
|
||||
}
|
||||
|
||||
void CharacterController::updateContinuousVfx()
|
||||
|
@ -2544,6 +2547,19 @@ void CharacterController::updateMagicEffects()
|
|||
{
|
||||
if (!mPtr.getClass().isActor())
|
||||
return;
|
||||
|
||||
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
|
||||
mAnimation->setVampire(vampire);
|
||||
|
||||
float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude();
|
||||
mAnimation->setLightEffect(light);
|
||||
}
|
||||
|
||||
void CharacterController::setVisibility(float visibility)
|
||||
{
|
||||
// We should take actor's invisibility in account
|
||||
if (mPtr.getClass().isActor())
|
||||
{
|
||||
float alpha = 1.f;
|
||||
if (mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Invisibility).getModifier()) // Ignore base magnitude (see bug #3555).
|
||||
{
|
||||
|
@ -2557,13 +2573,12 @@ void CharacterController::updateMagicEffects()
|
|||
{
|
||||
alpha *= std::max(0.2f, (100.f - chameleon)/100.f);
|
||||
}
|
||||
mAnimation->setAlpha(alpha);
|
||||
|
||||
bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f;
|
||||
mAnimation->setVampire(vampire);
|
||||
visibility = std::min(visibility, alpha);
|
||||
}
|
||||
|
||||
float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude();
|
||||
mAnimation->setLightEffect(light);
|
||||
// TODO: implement a dithering shader rather than just change object transparency.
|
||||
mAnimation->setAlpha(visibility);
|
||||
}
|
||||
|
||||
void CharacterController::setAttackTypeBasedOnMovement()
|
||||
|
|
|
@ -257,7 +257,7 @@ public:
|
|||
|
||||
void updatePtr(const MWWorld::Ptr &ptr);
|
||||
|
||||
void update(float duration);
|
||||
void update(float duration, bool animationOnly=false);
|
||||
|
||||
void persistAnimationState();
|
||||
void unpersistAnimationState();
|
||||
|
@ -292,6 +292,7 @@ public:
|
|||
bool isTurning() const;
|
||||
bool isAttackingOrSpell() const;
|
||||
|
||||
void setVisibility(float visibility);
|
||||
void setAttackingOrSpell(bool attackingOrSpell);
|
||||
void castSpell(const std::string spellId, bool manualSpell=false);
|
||||
void setAIAttackType(const std::string& attackType);
|
||||
|
|
|
@ -373,9 +373,7 @@ namespace MWMechanics
|
|||
const bool weaphashealth = weapon.getClass().hasItemHealth(weapon);
|
||||
if (weaphashealth)
|
||||
{
|
||||
int weaphealth = weapon.getClass().getItemHealth(weapon);
|
||||
int weapmaxhealth = weapon.getClass().getItemMaxHealth(weapon);
|
||||
damage *= (float(weaphealth) / weapmaxhealth);
|
||||
damage *= weapon.getClass().getItemNormalizedHealth(weapon);
|
||||
}
|
||||
|
||||
static const float fDamageStrengthBase = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
||||
|
|
|
@ -33,11 +33,12 @@ namespace MWMechanics
|
|||
point.y() -= static_cast<float>(mCellY);
|
||||
}
|
||||
|
||||
osg::Vec3f CoordinateConverter::toLocalVec3(const ESM::Pathgrid::Point& point)
|
||||
osg::Vec3f CoordinateConverter::toLocalVec3(const osg::Vec3f& point)
|
||||
{
|
||||
return osg::Vec3f(
|
||||
static_cast<float>(point.mX - mCellX),
|
||||
static_cast<float>(point.mY - mCellY),
|
||||
static_cast<float>(point.mZ));
|
||||
point.x() - static_cast<float>(mCellX),
|
||||
point.y() - static_cast<float>(mCellY),
|
||||
point.z()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace MWMechanics
|
|||
/// in-place conversion from world to local
|
||||
void toLocal(osg::Vec3f& point);
|
||||
|
||||
osg::Vec3f toLocalVec3(const ESM::Pathgrid::Point& point);
|
||||
osg::Vec3f toLocalVec3(const osg::Vec3f& point);
|
||||
|
||||
private:
|
||||
int mCellX;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/statemanager.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/windowmanager.hpp"
|
||||
#include "../mwbase/dialoguemanager.hpp"
|
||||
|
@ -431,6 +432,29 @@ namespace MWMechanics
|
|||
mObjects.update(duration, paused);
|
||||
}
|
||||
|
||||
void MechanicsManager::processChangedSettings(const Settings::CategorySettingVector &changed)
|
||||
{
|
||||
for (Settings::CategorySettingVector::const_iterator it = changed.begin(); it != changed.end(); ++it)
|
||||
{
|
||||
if (it->first == "Game" && it->second == "actors processing range")
|
||||
{
|
||||
int state = MWBase::Environment::get().getStateManager()->getState();
|
||||
if (state != MWBase::StateManager::State_Running)
|
||||
continue;
|
||||
|
||||
mActors.updateProcessingRange();
|
||||
|
||||
// Update mechanics for new processing range immediately
|
||||
update(0.f, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float MechanicsManager::getActorsProcessingRange() const
|
||||
{
|
||||
return mActors.getProcessingRange();
|
||||
}
|
||||
|
||||
bool MechanicsManager::isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer)
|
||||
{
|
||||
return mActors.isActorDetected(actor, observer);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
||||
#define GAME_MWMECHANICS_MECHANICSMANAGERIMP_H
|
||||
|
||||
#include <components/settings/settings.hpp>
|
||||
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
@ -206,6 +208,10 @@ namespace MWMechanics
|
|||
|
||||
virtual void castSpell(const MWWorld::Ptr& ptr, const std::string spellId, bool manualSpell=false);
|
||||
|
||||
void processChangedSettings(const Settings::CategorySettingVector& settings) override;
|
||||
|
||||
virtual float getActorsProcessingRange() const;
|
||||
|
||||
/// Check if the target actor was detected by an observer
|
||||
/// If the observer is a non-NPC, check all actors in AI processing distance as observers
|
||||
virtual bool isActorDetected(const MWWorld::Ptr& actor, const MWWorld::Ptr& observer);
|
||||
|
|
|
@ -96,11 +96,6 @@ namespace MWMechanics
|
|||
mEvadeDuration = 0;
|
||||
}
|
||||
|
||||
bool ObstacleCheck::isNormalState() const
|
||||
{
|
||||
return mWalkState == State_Norm;
|
||||
}
|
||||
|
||||
bool ObstacleCheck::isEvading() const
|
||||
{
|
||||
return mWalkState == State_Evade;
|
||||
|
@ -128,7 +123,7 @@ namespace MWMechanics
|
|||
* u = how long to move sideways
|
||||
*
|
||||
*/
|
||||
bool ObstacleCheck::check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
|
||||
void ObstacleCheck::update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance)
|
||||
{
|
||||
const MWWorld::Class& cls = actor.getClass();
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
|
@ -180,9 +175,7 @@ namespace MWMechanics
|
|||
case State_Evade:
|
||||
{
|
||||
mEvadeDuration += duration;
|
||||
if(mEvadeDuration < DURATION_TO_EVADE)
|
||||
return true;
|
||||
else
|
||||
if(mEvadeDuration >= DURATION_TO_EVADE)
|
||||
{
|
||||
// tried to evade, assume all is ok and start again
|
||||
mWalkState = State_Norm;
|
||||
|
@ -191,10 +184,9 @@ namespace MWMechanics
|
|||
}
|
||||
/* NO DEFAULT CASE */
|
||||
}
|
||||
return false; // no obstacles to evade (yet)
|
||||
}
|
||||
|
||||
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement)
|
||||
void ObstacleCheck::takeEvasiveAction(MWMechanics::Movement& actorMovement) const
|
||||
{
|
||||
actorMovement.mPosition[0] = evadeDirections[mEvadeDirectionIndex][0];
|
||||
actorMovement.mPosition[1] = evadeDirections[mEvadeDirectionIndex][1];
|
||||
|
|
|
@ -27,15 +27,13 @@ namespace MWMechanics
|
|||
// Clear the timers and set the state machine to default
|
||||
void clear();
|
||||
|
||||
bool isNormalState() const;
|
||||
bool isEvading() const;
|
||||
|
||||
// Returns true if there is an obstacle and an evasive action
|
||||
// should be taken
|
||||
bool check(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
|
||||
// Updates internal state, call each frame for moving actor
|
||||
void update(const MWWorld::Ptr& actor, float duration, float scaleMinimumDistance = 1.0f);
|
||||
|
||||
// change direction to try to fix "stuck" actor
|
||||
void takeEvasiveAction(MWMechanics::Movement& actorMovement);
|
||||
void takeEvasiveAction(MWMechanics::Movement& actorMovement) const;
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
#include "pathfinding.hpp"
|
||||
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
#include <components/detournavigator/exceptions.hpp>
|
||||
#include <components/detournavigator/debug.hpp>
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwphysics/collisiontype.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
#include "pathgrid.hpp"
|
||||
#include "coordinateconverter.hpp"
|
||||
|
@ -29,7 +36,7 @@ namespace
|
|||
// points to a quadtree may help
|
||||
for(unsigned int counter = 0; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = MWMechanics::PathFinder::DistanceSquared(grid->mPoints[counter], pos);
|
||||
float potentialDistBetween = MWMechanics::PathFinder::distanceSquared(grid->mPoints[counter], pos);
|
||||
if (potentialDistBetween < closestDistanceReachable)
|
||||
{
|
||||
// found a closer one
|
||||
|
@ -57,56 +64,19 @@ namespace
|
|||
(closestReachableIndex, closestReachableIndex == closestIndex);
|
||||
}
|
||||
|
||||
float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs)
|
||||
{
|
||||
return (lhs - rhs).length2();
|
||||
}
|
||||
|
||||
float sqrDistanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
|
||||
{
|
||||
return sqrDistance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y()));
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
float sqrDistanceIgnoreZ(const ESM::Pathgrid::Point& point, float x, float y)
|
||||
{
|
||||
x -= point.mX;
|
||||
y -= point.mY;
|
||||
return (x * x + y * y);
|
||||
}
|
||||
|
||||
float distance(const ESM::Pathgrid::Point& point, float x, float y, float z)
|
||||
{
|
||||
x -= point.mX;
|
||||
y -= point.mY;
|
||||
z -= point.mZ;
|
||||
return sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
|
||||
{
|
||||
float x = static_cast<float>(a.mX - b.mX);
|
||||
float y = static_cast<float>(a.mY - b.mY);
|
||||
float z = static_cast<float>(a.mZ - b.mZ);
|
||||
return sqrt(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
float getZAngleToDir(const osg::Vec3f& dir)
|
||||
{
|
||||
return std::atan2(dir.x(), dir.y());
|
||||
}
|
||||
|
||||
float getXAngleToDir(const osg::Vec3f& dir)
|
||||
{
|
||||
float dirLen = dir.length();
|
||||
return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0;
|
||||
}
|
||||
|
||||
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
|
||||
{
|
||||
osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin);
|
||||
return getZAngleToDir(dir);
|
||||
}
|
||||
|
||||
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest)
|
||||
{
|
||||
osg::Vec3f dir = PathFinder::MakeOsgVec3(dest) - PathFinder::MakeOsgVec3(origin);
|
||||
return getXAngleToDir(dir);
|
||||
}
|
||||
|
||||
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY)
|
||||
{
|
||||
osg::Vec3f dir = to - from;
|
||||
|
@ -121,18 +91,6 @@ namespace MWMechanics
|
|||
return (std::abs(from.z() - h) <= PATHFIND_Z_REACH);
|
||||
}
|
||||
|
||||
PathFinder::PathFinder()
|
||||
: mPathgrid(nullptr)
|
||||
, mCell(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void PathFinder::clearPath()
|
||||
{
|
||||
if(!mPath.empty())
|
||||
mPath.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: This method may fail to find a path. The caller must check the
|
||||
* result before using it. If there is no path the AI routies need to
|
||||
|
@ -150,7 +108,7 @@ namespace MWMechanics
|
|||
* pathgrid point (e.g. wander) then it may be worth while to call
|
||||
* pop_back() to remove the redundant entry.
|
||||
*
|
||||
* NOTE: coordinates must be converted prior to calling GetClosestPoint()
|
||||
* NOTE: coordinates must be converted prior to calling getClosestPoint()
|
||||
*
|
||||
* |
|
||||
* | cell
|
||||
|
@ -169,52 +127,44 @@ namespace MWMechanics
|
|||
* j = @.x in local coordinates (i.e. within the cell)
|
||||
* k = @.x in world coordinates
|
||||
*/
|
||||
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
|
||||
const ESM::Pathgrid::Point &endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
void PathFinder::buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
{
|
||||
mPath.clear();
|
||||
|
||||
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
|
||||
if(mCell != cell || !mPathgrid)
|
||||
{
|
||||
mCell = cell;
|
||||
mPathgrid = pathgridGraph.getPathgrid();
|
||||
}
|
||||
const auto pathgrid = pathgridGraph.getPathgrid();
|
||||
|
||||
// Refer to AiWander reseach topic on openmw forums for some background.
|
||||
// Maybe there is no pathgrid for this cell. Just go to destination and let
|
||||
// physics take care of any blockages.
|
||||
if(!mPathgrid || mPathgrid->mPoints.empty())
|
||||
if(!pathgrid || pathgrid->mPoints.empty())
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
*out++ = endPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: GetClosestPoint expects local coordinates
|
||||
// NOTE: getClosestPoint expects local coordinates
|
||||
CoordinateConverter converter(mCell->getCell());
|
||||
|
||||
// NOTE: It is possible that GetClosestPoint returns a pathgrind point index
|
||||
// NOTE: It is possible that getClosestPoint returns a pathgrind point index
|
||||
// that is unreachable in some situations. e.g. actor is standing
|
||||
// outside an area enclosed by walls, but there is a pathgrid
|
||||
// point right behind the wall that is closer than any pathgrid
|
||||
// point outside the wall
|
||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
|
||||
int startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||
|
||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
|
||||
std::pair<int, bool> endNode = getClosestReachablePoint(pathgrid, &pathgridGraph,
|
||||
endPointInLocalCoords,
|
||||
startNode);
|
||||
|
||||
// if it's shorter for actor to travel from start to end, than to travel from either
|
||||
// start or end to nearest pathgrid point, just travel from start to end.
|
||||
float startToEndLength2 = (endPointInLocalCoords - startPointInLocalCoords).length2();
|
||||
float endTolastNodeLength2 = DistanceSquared(mPathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = DistanceSquared(mPathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
float endTolastNodeLength2 = distanceSquared(pathgrid->mPoints[endNode.first], endPointInLocalCoords);
|
||||
float startTo1stNodeLength2 = distanceSquared(pathgrid->mPoints[startNode], startPointInLocalCoords);
|
||||
if ((startToEndLength2 < startTo1stNodeLength2) || (startToEndLength2 < endTolastNodeLength2))
|
||||
{
|
||||
mPath.push_back(endPoint);
|
||||
*out++ = endPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -225,21 +175,21 @@ namespace MWMechanics
|
|||
// nodes are the same
|
||||
if(startNode == endNode.first)
|
||||
{
|
||||
ESM::Pathgrid::Point temp(mPathgrid->mPoints[startNode]);
|
||||
ESM::Pathgrid::Point temp(pathgrid->mPoints[startNode]);
|
||||
converter.toWorld(temp);
|
||||
mPath.push_back(temp);
|
||||
*out++ = makeOsgVec3(temp);
|
||||
}
|
||||
else
|
||||
{
|
||||
mPath = pathgridGraph.aStarSearch(startNode, endNode.first);
|
||||
auto path = pathgridGraph.aStarSearch(startNode, endNode.first);
|
||||
|
||||
// If nearest path node is in opposite direction from second, remove it from path.
|
||||
// Especially useful for wandering actors, if the nearest node is blocked for some reason.
|
||||
if (mPath.size() > 1)
|
||||
if (path.size() > 1)
|
||||
{
|
||||
ESM::Pathgrid::Point secondNode = *(++mPath.begin());
|
||||
osg::Vec3f firstNodeVec3f = MakeOsgVec3(mPathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = MakeOsgVec3(secondNode);
|
||||
ESM::Pathgrid::Point secondNode = *(++path.begin());
|
||||
osg::Vec3f firstNodeVec3f = makeOsgVec3(pathgrid->mPoints[startNode]);
|
||||
osg::Vec3f secondNodeVec3f = makeOsgVec3(secondNode);
|
||||
osg::Vec3f toSecondNodeVec3f = secondNodeVec3f - firstNodeVec3f;
|
||||
osg::Vec3f toStartPointVec3f = startPointInLocalCoords - firstNodeVec3f;
|
||||
if (toSecondNodeVec3f * toStartPointVec3f > 0)
|
||||
|
@ -248,19 +198,21 @@ namespace MWMechanics
|
|||
converter.toWorld(temp);
|
||||
// Add Z offset since path node can overlap with other objects.
|
||||
// Also ignore doors in raytesting.
|
||||
int mask = MWPhysics::CollisionType_World;
|
||||
const int mask = MWPhysics::CollisionType_World;
|
||||
bool isPathClear = !MWBase::Environment::get().getWorld()->castRay(
|
||||
startPoint.mX, startPoint.mY, startPoint.mZ+16, temp.mX, temp.mY, temp.mZ+16, mask);
|
||||
startPoint.x(), startPoint.y(), startPoint.z() + 16, temp.mX, temp.mY, temp.mZ + 16, mask);
|
||||
if (isPathClear)
|
||||
mPath.pop_front();
|
||||
path.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
// convert supplied path to world coordinates
|
||||
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
|
||||
std::transform(path.begin(), path.end(), out,
|
||||
[&] (ESM::Pathgrid::Point& point)
|
||||
{
|
||||
converter.toWorld(*iter);
|
||||
}
|
||||
converter.toWorld(point);
|
||||
return makeOsgVec3(point);
|
||||
});
|
||||
}
|
||||
|
||||
// If endNode found is NOT the closest PathGrid point to the endPoint,
|
||||
|
@ -276,7 +228,7 @@ namespace MWMechanics
|
|||
//
|
||||
// The AI routines will have to deal with such situations.
|
||||
if(endNode.second)
|
||||
mPath.push_back(endPoint);
|
||||
*out++ = endPoint;
|
||||
}
|
||||
|
||||
float PathFinder::getZAngleToNext(float x, float y) const
|
||||
|
@ -286,9 +238,9 @@ namespace MWMechanics
|
|||
if(mPath.empty())
|
||||
return 0.;
|
||||
|
||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
||||
float directionX = nextPoint.mX - x;
|
||||
float directionY = nextPoint.mY - y;
|
||||
const auto& nextPoint = mPath.front();
|
||||
const float directionX = nextPoint.x() - x;
|
||||
const float directionY = nextPoint.y() - y;
|
||||
|
||||
return std::atan2(directionX, directionY);
|
||||
}
|
||||
|
@ -300,62 +252,64 @@ namespace MWMechanics
|
|||
if(mPath.empty())
|
||||
return 0.;
|
||||
|
||||
const ESM::Pathgrid::Point &nextPoint = *mPath.begin();
|
||||
osg::Vec3f dir = MakeOsgVec3(nextPoint) - osg::Vec3f(x,y,z);
|
||||
const osg::Vec3f dir = mPath.front() - osg::Vec3f(x, y, z);
|
||||
|
||||
return getXAngleToDir(dir);
|
||||
}
|
||||
|
||||
bool PathFinder::checkPathCompleted(float x, float y, float tolerance)
|
||||
void PathFinder::update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance)
|
||||
{
|
||||
if (mPath.empty())
|
||||
return true;
|
||||
return;
|
||||
|
||||
const ESM::Pathgrid::Point& nextPoint = *mPath.begin();
|
||||
if (sqrDistanceIgnoreZ(nextPoint, x, y) < tolerance*tolerance)
|
||||
{
|
||||
const auto tolerance = mPath.size() > 1 ? pointTolerance : destinationTolerance;
|
||||
|
||||
if (sqrDistanceIgnoreZ(mPath.front(), position) < tolerance * tolerance)
|
||||
mPath.pop_front();
|
||||
}
|
||||
|
||||
void PathFinder::buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
|
||||
{
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
|
||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||
|
||||
mConstructed = true;
|
||||
}
|
||||
|
||||
void PathFinder::buildPath(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)
|
||||
{
|
||||
mPath.clear();
|
||||
mCell = cell;
|
||||
|
||||
buildPathByNavigatorImpl(actor, startPoint, endPoint, halfExtents, flags, std::back_inserter(mPath));
|
||||
|
||||
if (mPath.empty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
buildPathByPathgridImpl(startPoint, endPoint, pathgridGraph, std::back_inserter(mPath));
|
||||
|
||||
mConstructed = true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// see header for the rationale
|
||||
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
|
||||
const ESM::Pathgrid::Point &endPoint,
|
||||
const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
|
||||
void PathFinder::buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> out)
|
||||
{
|
||||
if (mPath.size() < 2)
|
||||
try
|
||||
{
|
||||
// if path has one point, then it's the destination.
|
||||
// don't need to worry about bad path for this case
|
||||
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
navigator->findPath(halfExtents, startPoint, endPoint, flags, out);
|
||||
}
|
||||
else
|
||||
catch (const DetourNavigator::NavigatorException& exception)
|
||||
{
|
||||
const ESM::Pathgrid::Point oldStart(*getPath().begin());
|
||||
buildPath(startPoint, endPoint, cell, pathgridGraph);
|
||||
if (mPath.size() >= 2)
|
||||
{
|
||||
// if 2nd waypoint of new path == 1st waypoint of old,
|
||||
// delete 1st waypoint of new path.
|
||||
std::list<ESM::Pathgrid::Point>::iterator iter = ++mPath.begin();
|
||||
if (iter->mX == oldStart.mX
|
||||
&& iter->mY == oldStart.mY
|
||||
&& iter->mZ == oldStart.mZ)
|
||||
{
|
||||
mPath.pop_front();
|
||||
DetourNavigator::log("PathFinder::buildPathByNavigator navigator exception: ", exception.what());
|
||||
Log(Debug::Verbose) << "Build path by navigator exception: \"" << exception.what()
|
||||
<< "\" for \"" << actor.getClass().getName(actor) << "\" (" << actor.getBase()
|
||||
<< ") from " << startPoint << " to " << endPoint << " with flags ("
|
||||
<< DetourNavigator::WriteFlags {flags} << ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MWWorld::CellStore* PathFinder::getPathCell() const
|
||||
{
|
||||
return mCell;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,57 @@
|
|||
#ifndef GAME_MWMECHANICS_PATHFINDING_H
|
||||
#define GAME_MWMECHANICS_PATHFINDING_H
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
#include <cassert>
|
||||
#include <iterator>
|
||||
|
||||
#include <components/detournavigator/flags.hpp>
|
||||
#include <components/esm/defs.hpp>
|
||||
#include <components/esm/loadpgrd.hpp>
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
class ConstPtr;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class PathgridGraph;
|
||||
|
||||
float distance(const ESM::Pathgrid::Point& point, float x, float y, float);
|
||||
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b);
|
||||
float getZAngleToDir(const osg::Vec3f& dir);
|
||||
float getXAngleToDir(const osg::Vec3f& dir);
|
||||
float getZAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
|
||||
float getXAngleToPoint(const ESM::Pathgrid::Point &origin, const ESM::Pathgrid::Point &dest);
|
||||
inline float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs)
|
||||
{
|
||||
return (lhs - rhs).length();
|
||||
}
|
||||
|
||||
inline float getZAngleToDir(const osg::Vec3f& dir)
|
||||
{
|
||||
return std::atan2(dir.x(), dir.y());
|
||||
}
|
||||
|
||||
inline float getXAngleToDir(const osg::Vec3f& dir)
|
||||
{
|
||||
float dirLen = dir.length();
|
||||
return (dirLen != 0) ? -std::asin(dir.z() / dirLen) : 0;
|
||||
}
|
||||
|
||||
inline float getZAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest)
|
||||
{
|
||||
return getZAngleToDir(dest - origin);
|
||||
}
|
||||
|
||||
inline float getXAngleToPoint(const osg::Vec3f& origin, const osg::Vec3f& dest)
|
||||
{
|
||||
return getXAngleToDir(dest - origin);
|
||||
}
|
||||
|
||||
const float PATHFIND_Z_REACH = 50.0f;
|
||||
//static const float sMaxSlope = 49.0f; // duplicate as in physicssystem
|
||||
// distance after which actor (failed previously to shortcut) will try again
|
||||
const float PATHFIND_SHORTCUT_RETRY_DIST = 300.0f;
|
||||
|
||||
const float DEFAULT_TOLERANCE = 32.0f;
|
||||
|
||||
// cast up-down ray with some offset from actor position to check for pits/obstacles on the way to target;
|
||||
// magnitude of pits/obstacles is defined by PATHFIND_Z_REACH
|
||||
bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY);
|
||||
|
@ -35,31 +59,33 @@ namespace MWMechanics
|
|||
class PathFinder
|
||||
{
|
||||
public:
|
||||
PathFinder();
|
||||
|
||||
static const int PathTolerance = 32;
|
||||
|
||||
static float sgn(float val)
|
||||
PathFinder()
|
||||
: mConstructed(false)
|
||||
, mCell(nullptr)
|
||||
{
|
||||
if(val > 0)
|
||||
return 1.0;
|
||||
return -1.0;
|
||||
}
|
||||
|
||||
static int sgn(int a)
|
||||
void clearPath()
|
||||
{
|
||||
if(a > 0)
|
||||
return 1;
|
||||
return -1;
|
||||
mConstructed = false;
|
||||
mPath.clear();
|
||||
mCell = nullptr;
|
||||
}
|
||||
|
||||
void clearPath();
|
||||
|
||||
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
|
||||
void buildPathByPathgrid(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
||||
|
||||
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
|
||||
///< \Returns true if we are within \a tolerance units of the last path point.
|
||||
void buildPath(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);
|
||||
|
||||
/// Remove front point if exist and within tolerance
|
||||
void update(const osg::Vec3f& position, const float pointTolerance, const float destinationTolerance);
|
||||
|
||||
bool checkPathCompleted() const
|
||||
{
|
||||
return mConstructed && mPath.empty();
|
||||
}
|
||||
|
||||
/// In radians
|
||||
float getZAngleToNext(float x, float y) const;
|
||||
|
@ -68,49 +94,43 @@ namespace MWMechanics
|
|||
|
||||
bool isPathConstructed() const
|
||||
{
|
||||
return !mPath.empty();
|
||||
return mConstructed && !mPath.empty();
|
||||
}
|
||||
|
||||
int getPathSize() const
|
||||
std::size_t getPathSize() const
|
||||
{
|
||||
return mPath.size();
|
||||
}
|
||||
|
||||
const std::list<ESM::Pathgrid::Point>& getPath() const
|
||||
const std::deque<osg::Vec3f>& getPath() const
|
||||
{
|
||||
return mPath;
|
||||
}
|
||||
|
||||
const MWWorld::CellStore* getPathCell() const;
|
||||
|
||||
/** Synchronize new path with old one to avoid visiting 1 waypoint 2 times
|
||||
@note
|
||||
BuildPath() takes closest PathGrid point to NPC as first point of path.
|
||||
This is undesirable if NPC has just passed a Pathgrid point, as this
|
||||
makes the 2nd point of the new path == the 1st point of old path.
|
||||
Which results in NPC "running in a circle" back to the just passed waypoint.
|
||||
*/
|
||||
void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
|
||||
const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
|
||||
|
||||
void addPointToPath(const ESM::Pathgrid::Point &point)
|
||||
const MWWorld::CellStore* getPathCell() const
|
||||
{
|
||||
return mCell;
|
||||
}
|
||||
|
||||
void addPointToPath(const osg::Vec3f& point)
|
||||
{
|
||||
mConstructed = true;
|
||||
mPath.push_back(point);
|
||||
}
|
||||
|
||||
/// utility function to convert a osg::Vec3f to a Pathgrid::Point
|
||||
static ESM::Pathgrid::Point MakePathgridPoint(const osg::Vec3f& v)
|
||||
static ESM::Pathgrid::Point makePathgridPoint(const osg::Vec3f& v)
|
||||
{
|
||||
return ESM::Pathgrid::Point(static_cast<int>(v[0]), static_cast<int>(v[1]), static_cast<int>(v[2]));
|
||||
}
|
||||
|
||||
/// utility function to convert an ESM::Position to a Pathgrid::Point
|
||||
static ESM::Pathgrid::Point MakePathgridPoint(const ESM::Position& p)
|
||||
static ESM::Pathgrid::Point makePathgridPoint(const ESM::Position& p)
|
||||
{
|
||||
return ESM::Pathgrid::Point(static_cast<int>(p.pos[0]), static_cast<int>(p.pos[1]), static_cast<int>(p.pos[2]));
|
||||
}
|
||||
|
||||
static osg::Vec3f MakeOsgVec3(const ESM::Pathgrid::Point& p)
|
||||
static osg::Vec3f makeOsgVec3(const ESM::Pathgrid::Point& p)
|
||||
{
|
||||
return osg::Vec3f(static_cast<float>(p.mX), static_cast<float>(p.mY), static_cast<float>(p.mZ));
|
||||
}
|
||||
|
@ -119,9 +139,9 @@ namespace MWMechanics
|
|||
// Caller needs to be careful for very short distances (i.e. less than 1)
|
||||
// or when accumuating the results i.e. (a + b)^2 != a^2 + b^2
|
||||
//
|
||||
static float DistanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos)
|
||||
static float distanceSquared(ESM::Pathgrid::Point point, const osg::Vec3f& pos)
|
||||
{
|
||||
return (MWMechanics::PathFinder::MakeOsgVec3(point) - pos).length2();
|
||||
return (MWMechanics::PathFinder::makeOsgVec3(point) - pos).length2();
|
||||
}
|
||||
|
||||
// Return the closest pathgrid point index from the specified position
|
||||
|
@ -130,18 +150,18 @@ namespace MWMechanics
|
|||
//
|
||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||
//
|
||||
static int GetClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||
static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||
{
|
||||
assert(grid && !grid->mPoints.empty());
|
||||
|
||||
float distanceBetween = DistanceSquared(grid->mPoints[0], pos);
|
||||
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
||||
int closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for(unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = DistanceSquared(grid->mPoints[counter], pos);
|
||||
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
||||
if(potentialDistBetween < distanceBetween)
|
||||
{
|
||||
distanceBetween = potentialDistBetween;
|
||||
|
@ -153,10 +173,17 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
private:
|
||||
std::list<ESM::Pathgrid::Point> mPath;
|
||||
bool mConstructed;
|
||||
std::deque<osg::Vec3f> mPath;
|
||||
|
||||
const ESM::Pathgrid *mPathgrid;
|
||||
const MWWorld::CellStore* mCell;
|
||||
|
||||
void buildPathByPathgridImpl(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint,
|
||||
const PathgridGraph& pathgridGraph, std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||
|
||||
void buildPathByNavigatorImpl(const MWWorld::ConstPtr& actor, const osg::Vec3f& startPoint,
|
||||
const osg::Vec3f& endPoint, const osg::Vec3f& halfExtents, const DetourNavigator::Flags flags,
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> out);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -257,10 +257,9 @@ namespace MWMechanics
|
|||
* pathgrid points form (currently they are converted to world
|
||||
* coordinates). Essentially trading speed w/ memory.
|
||||
*/
|
||||
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
|
||||
const int goal) const
|
||||
std::deque<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start, const int goal) const
|
||||
{
|
||||
std::list<ESM::Pathgrid::Point> path;
|
||||
std::deque<ESM::Pathgrid::Point> path;
|
||||
if(!isPointConnected(start, goal))
|
||||
{
|
||||
return path; // there is no path, return an empty path
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef GAME_MWMECHANICS_PATHGRID_H
|
||||
#define GAME_MWMECHANICS_PATHGRID_H
|
||||
|
||||
#include <list>
|
||||
#include <deque>
|
||||
|
||||
#include <components/esm/loadpgrd.hpp>
|
||||
|
||||
|
@ -38,8 +38,8 @@ namespace MWMechanics
|
|||
// cells) coordinates
|
||||
//
|
||||
// NOTE: if start equals end an empty path is returned
|
||||
std::list<ESM::Pathgrid::Point> aStarSearch(const int start,
|
||||
const int end) const;
|
||||
std::deque<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
|
||||
|
||||
private:
|
||||
|
||||
const ESM::Cell *mCell;
|
||||
|
|
|
@ -324,6 +324,34 @@ namespace MWMechanics
|
|||
return true;
|
||||
}
|
||||
|
||||
class GetAbsorptionProbability : public MWMechanics::EffectSourceVisitor
|
||||
{
|
||||
public:
|
||||
float mProbability;
|
||||
|
||||
GetAbsorptionProbability(const MWWorld::Ptr& actor)
|
||||
: mProbability(0.f){}
|
||||
|
||||
virtual void visit (MWMechanics::EffectKey key,
|
||||
const std::string& sourceName, const std::string& sourceId, int casterActorId,
|
||||
float magnitude, float remainingTime = -1, float totalTime = -1)
|
||||
{
|
||||
if (key.mId == ESM::MagicEffect::SpellAbsorption)
|
||||
{
|
||||
if (mProbability == 0.f)
|
||||
mProbability = magnitude / 100;
|
||||
else
|
||||
{
|
||||
// If there are different sources of SpellAbsorption effect, multiply failing probability for all effects.
|
||||
// Real absorption probability will be the (1 - total fail chance) in this case.
|
||||
float failProbability = 1.f - mProbability;
|
||||
failProbability *= 1.f - magnitude / 100;
|
||||
mProbability = 1.f - failProbability;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CastSpell::CastSpell(const MWWorld::Ptr &caster, const MWWorld::Ptr &target, const bool fromProjectile, const bool manualSpell)
|
||||
: mCaster(caster)
|
||||
, mTarget(target)
|
||||
|
@ -444,12 +472,20 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWindowManager()->setEnemy(target);
|
||||
|
||||
// Try absorbing if it's a spell
|
||||
// NOTE: Vanilla does this once per spell absorption effect source instead of adding the % from all sources together, not sure
|
||||
// if that is worth replicating.
|
||||
// Unlike Reflect, this is done once per spell absorption effect source
|
||||
bool absorbed = false;
|
||||
if (spell && caster != target && target.getClass().isActor())
|
||||
{
|
||||
float absorb = target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude();
|
||||
CreatureStats& stats = target.getClass().getCreatureStats(target);
|
||||
if (stats.getMagicEffects().get(ESM::MagicEffect::SpellAbsorption).getMagnitude() > 0.f)
|
||||
{
|
||||
GetAbsorptionProbability check(target);
|
||||
stats.getActiveSpells().visitEffectSources(check);
|
||||
stats.getSpells().visitEffectSources(check);
|
||||
if (target.getClass().hasInventoryStore(target))
|
||||
target.getClass().getInventoryStore(target).visitEffectSources(check);
|
||||
|
||||
int absorb = check.mProbability * 100;
|
||||
absorbed = (Misc::Rng::roll0to99() < absorb);
|
||||
if (absorbed)
|
||||
{
|
||||
|
@ -457,9 +493,10 @@ namespace MWMechanics
|
|||
MWBase::Environment::get().getWorld()->getAnimation(target)->addEffect(
|
||||
"meshes\\" + absorbStatic->mModel, ESM::MagicEffect::SpellAbsorption, false, "");
|
||||
// Magicka is increased by cost of spell
|
||||
DynamicStat<float> magicka = target.getClass().getCreatureStats(target).getMagicka();
|
||||
DynamicStat<float> magicka = stats.getMagicka();
|
||||
magicka.setCurrent(magicka.getCurrent() + spell->mData.mCost);
|
||||
target.getClass().getCreatureStats(target).setMagicka(magicka);
|
||||
stats.setMagicka(magicka);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,8 +58,16 @@ namespace MWMechanics
|
|||
}
|
||||
|
||||
const float chop = (weapon->mData.mChop[0] + weapon->mData.mChop[1]) / 2.f;
|
||||
if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
// We need to account for the fact that thrown weapons have 2x real damage applied to the target
|
||||
// as they're both the weapon and the ammo of the hit
|
||||
if (weapon->mData.mType == ESM::Weapon::MarksmanThrown)
|
||||
{
|
||||
rating = chop * 2;
|
||||
}
|
||||
else if (weapon->mData.mType >= ESM::Weapon::MarksmanBow)
|
||||
{
|
||||
rating = chop;
|
||||
}
|
||||
else
|
||||
{
|
||||
const float slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1]) / 2.f;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
|
||||
|
@ -28,6 +29,31 @@ Actor::Actor(const MWWorld::Ptr& ptr, osg::ref_ptr<const Resource::BulletShape>
|
|||
mHalfExtents = shape->mCollisionBoxHalfExtents;
|
||||
mMeshTranslation = shape->mCollisionBoxTranslate;
|
||||
|
||||
// We can not create actor without collisions - he will fall through the ground.
|
||||
// In this case we should autogenerate collision box based on mesh shape
|
||||
// (NPCs have bodyparts and use a different approach)
|
||||
if (!ptr.getClass().isNpc() && mHalfExtents.length2() == 0.f)
|
||||
{
|
||||
const Resource::BulletShape* collisionShape = shape.get();
|
||||
if (collisionShape && collisionShape->mCollisionShape)
|
||||
{
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
btVector3 min;
|
||||
btVector3 max;
|
||||
|
||||
collisionShape->mCollisionShape->getAabb(transform, min, max);
|
||||
mHalfExtents.x() = (max[0] - min[0])/2.f;
|
||||
mHalfExtents.y() = (max[1] - min[1])/2.f;
|
||||
mHalfExtents.z() = (max[2] - min[2])/2.f;
|
||||
|
||||
mMeshTranslation = osg::Vec3f(0.f, 0.f, mHalfExtents.z());
|
||||
}
|
||||
|
||||
if (mHalfExtents.length2() == 0.f)
|
||||
Log(Debug::Error) << "Error: Failed to calculate bounding box for actor \"" << ptr.getCellRef().getRefId() << "\".";
|
||||
}
|
||||
|
||||
// Use capsule shape only if base is square (nonuniform scaling apparently doesn't work on it)
|
||||
if (std::abs(mHalfExtents.x()-mHalfExtents.y())<mHalfExtents.x()*0.05 && mHalfExtents.z() >= mHalfExtents.x())
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <memory>
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
#include <osg/Vec3f>
|
||||
#include <osg/Quat>
|
||||
|
@ -22,30 +22,6 @@ namespace Resource
|
|||
namespace MWPhysics
|
||||
{
|
||||
|
||||
class PtrHolder
|
||||
{
|
||||
public:
|
||||
virtual ~PtrHolder() {}
|
||||
|
||||
void updatePtr(const MWWorld::Ptr& updated)
|
||||
{
|
||||
mPtr = updated;
|
||||
}
|
||||
|
||||
MWWorld::Ptr getPtr()
|
||||
{
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
MWWorld::ConstPtr getPtr() const
|
||||
{
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
protected:
|
||||
MWWorld::Ptr mPtr;
|
||||
};
|
||||
|
||||
class Actor : public PtrHolder
|
||||
{
|
||||
public:
|
||||
|
|
54
apps/openmw/mwphysics/heightfield.cpp
Normal file
54
apps/openmw/mwphysics/heightfield.cpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#include "heightfield.hpp"
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||
|
||||
#include <LinearMath/btTransform.h>
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
HeightField::HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
||||
{
|
||||
mShape = new btHeightfieldTerrainShape(
|
||||
sqrtVerts, sqrtVerts, heights, 1,
|
||||
minH, maxH, 2,
|
||||
PHY_FLOAT, false
|
||||
);
|
||||
mShape->setUseDiamondSubdivision(true);
|
||||
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
||||
|
||||
btTransform transform(btQuaternion::getIdentity(),
|
||||
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
||||
(y+0.5f) * triSize * (sqrtVerts-1),
|
||||
(maxH+minH)*0.5f));
|
||||
|
||||
mCollisionObject = new btCollisionObject;
|
||||
mCollisionObject->setCollisionShape(mShape);
|
||||
mCollisionObject->setWorldTransform(transform);
|
||||
|
||||
mHoldObject = holdObject;
|
||||
}
|
||||
|
||||
HeightField::~HeightField()
|
||||
{
|
||||
delete mCollisionObject;
|
||||
delete mShape;
|
||||
}
|
||||
|
||||
btCollisionObject* HeightField::getCollisionObject()
|
||||
{
|
||||
return mCollisionObject;
|
||||
}
|
||||
|
||||
const btCollisionObject* HeightField::getCollisionObject() const
|
||||
{
|
||||
return mCollisionObject;
|
||||
}
|
||||
|
||||
const btHeightfieldTerrainShape* HeightField::getShape() const
|
||||
{
|
||||
return mShape;
|
||||
}
|
||||
}
|
36
apps/openmw/mwphysics/heightfield.hpp
Normal file
36
apps/openmw/mwphysics/heightfield.hpp
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef OPENMW_MWPHYSICS_HEIGHTFIELD_H
|
||||
#define OPENMW_MWPHYSICS_HEIGHTFIELD_H
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
class btCollisionObject;
|
||||
class btHeightfieldTerrainShape;
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Object;
|
||||
}
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class HeightField
|
||||
{
|
||||
public:
|
||||
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject);
|
||||
~HeightField();
|
||||
|
||||
btCollisionObject* getCollisionObject();
|
||||
const btCollisionObject* getCollisionObject() const;
|
||||
const btHeightfieldTerrainShape* getShape() const;
|
||||
|
||||
private:
|
||||
btHeightfieldTerrainShape* mShape;
|
||||
btCollisionObject* mCollisionObject;
|
||||
osg::ref_ptr<const osg::Object> mHoldObject;
|
||||
|
||||
void operator=(const HeightField&);
|
||||
HeightField(const HeightField&);
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
130
apps/openmw/mwphysics/object.cpp
Normal file
130
apps/openmw/mwphysics/object.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
#include "object.hpp"
|
||||
#include "convert.hpp"
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/nifosg/particle.hpp>
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include <osg/Object>
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
#include <LinearMath/btTransform.h>
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
Object::Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
|
||||
: mShapeInstance(shapeInstance)
|
||||
, mSolid(true)
|
||||
{
|
||||
mPtr = ptr;
|
||||
|
||||
mCollisionObject.reset(new btCollisionObject);
|
||||
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
||||
|
||||
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
||||
|
||||
setScale(ptr.getCellRef().getScale());
|
||||
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||
const float* pos = ptr.getRefData().getPosition().pos;
|
||||
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
||||
}
|
||||
|
||||
const Resource::BulletShapeInstance* Object::getShapeInstance() const
|
||||
{
|
||||
return mShapeInstance.get();
|
||||
}
|
||||
|
||||
void Object::setScale(float scale)
|
||||
{
|
||||
mShapeInstance->setLocalScaling(btVector3(scale, scale, scale));
|
||||
}
|
||||
|
||||
void Object::setRotation(const btQuaternion& quat)
|
||||
{
|
||||
mCollisionObject->getWorldTransform().setRotation(quat);
|
||||
}
|
||||
|
||||
void Object::setOrigin(const btVector3& vec)
|
||||
{
|
||||
mCollisionObject->getWorldTransform().setOrigin(vec);
|
||||
}
|
||||
|
||||
btCollisionObject* Object::getCollisionObject()
|
||||
{
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
const btCollisionObject* Object::getCollisionObject() const
|
||||
{
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
bool Object::isSolid() const
|
||||
{
|
||||
return mSolid;
|
||||
}
|
||||
|
||||
void Object::setSolid(bool solid)
|
||||
{
|
||||
mSolid = solid;
|
||||
}
|
||||
|
||||
bool Object::isAnimated() const
|
||||
{
|
||||
return !mShapeInstance->mAnimatedShapes.empty();
|
||||
}
|
||||
|
||||
void Object::animateCollisionShapes(btCollisionWorld* collisionWorld)
|
||||
{
|
||||
if (mShapeInstance->mAnimatedShapes.empty())
|
||||
return;
|
||||
|
||||
assert (mShapeInstance->getCollisionShape()->isCompound());
|
||||
|
||||
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
|
||||
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
|
||||
{
|
||||
int recIndex = it->first;
|
||||
int shapeIndex = it->second;
|
||||
|
||||
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
|
||||
if (nodePathFound == mRecIndexToNodePath.end())
|
||||
{
|
||||
NifOsg::FindGroupByRecIndex visitor(recIndex);
|
||||
mPtr.getRefData().getBaseNode()->accept(visitor);
|
||||
if (!visitor.mFound)
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId();
|
||||
|
||||
// Remove nonexistent nodes from animated shapes map and early out
|
||||
mShapeInstance->mAnimatedShapes.erase(recIndex);
|
||||
return;
|
||||
}
|
||||
osg::NodePath nodePath = visitor.mFoundPath;
|
||||
nodePath.erase(nodePath.begin());
|
||||
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
|
||||
}
|
||||
|
||||
osg::NodePath& nodePath = nodePathFound->second;
|
||||
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
|
||||
matrix.orthoNormalize(matrix);
|
||||
|
||||
btTransform transform;
|
||||
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
|
||||
for (int i=0; i<3; ++i)
|
||||
for (int j=0; j<3; ++j)
|
||||
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
|
||||
|
||||
// Note: we can not apply scaling here for now since we treat scaled shapes
|
||||
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
|
||||
if (!(transform == compound->getChildTransform(shapeIndex)))
|
||||
compound->updateChildTransform(shapeIndex, transform);
|
||||
}
|
||||
|
||||
collisionWorld->updateSingleAabb(mCollisionObject.get());
|
||||
}
|
||||
}
|
48
apps/openmw/mwphysics/object.hpp
Normal file
48
apps/openmw/mwphysics/object.hpp
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef OPENMW_MWPHYSICS_OBJECT_H
|
||||
#define OPENMW_MWPHYSICS_OBJECT_H
|
||||
|
||||
#include "ptrholder.hpp"
|
||||
|
||||
#include <osg/Node>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace Resource
|
||||
{
|
||||
class BulletShapeInstance;
|
||||
}
|
||||
|
||||
class btCollisionObject;
|
||||
class btCollisionWorld;
|
||||
class btQuaternion;
|
||||
class btVector3;
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Object : public PtrHolder
|
||||
{
|
||||
public:
|
||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance);
|
||||
|
||||
const Resource::BulletShapeInstance* getShapeInstance() const;
|
||||
void setScale(float scale);
|
||||
void setRotation(const btQuaternion& quat);
|
||||
void setOrigin(const btVector3& vec);
|
||||
btCollisionObject* getCollisionObject();
|
||||
const btCollisionObject* getCollisionObject() const;
|
||||
/// Return solid flag. Not used by the object itself, true by default.
|
||||
bool isSolid() const;
|
||||
void setSolid(bool solid);
|
||||
bool isAnimated() const;
|
||||
void animateCollisionShapes(btCollisionWorld* collisionWorld);
|
||||
|
||||
private:
|
||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
||||
std::map<int, osg::NodePath> mRecIndexToNodePath;
|
||||
bool mSolid;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,6 +1,11 @@
|
|||
#include "physicssystem.hpp"
|
||||
#include "physicssystem.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <osg/Group>
|
||||
|
||||
|
@ -16,6 +21,12 @@
|
|||
|
||||
#include <LinearMath/btQuickprof.h>
|
||||
|
||||
#include <DetourCommon.h>
|
||||
#include <DetourNavMesh.h>
|
||||
#include <DetourNavMeshBuilder.h>
|
||||
#include <DetourNavMeshQuery.h>
|
||||
#include <Recast.h>
|
||||
|
||||
#include <components/nifbullet/bulletnifloader.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/bulletshapemanager.hpp>
|
||||
|
@ -48,12 +59,12 @@
|
|||
#include "actor.hpp"
|
||||
#include "convert.hpp"
|
||||
#include "trace.h"
|
||||
#include "object.hpp"
|
||||
#include "heightfield.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
|
||||
static const float sMaxSlope = 49.0f;
|
||||
static const float sStepSizeUp = 34.0f;
|
||||
static const float sStepSizeDown = 62.0f;
|
||||
static const float sMinStep = 10.f;
|
||||
static const float sGroundOffset = 1.0f;
|
||||
|
@ -507,175 +518,6 @@ namespace MWPhysics
|
|||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
class HeightField
|
||||
{
|
||||
public:
|
||||
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts, float minH, float maxH, const osg::Object* holdObject)
|
||||
{
|
||||
mShape = new btHeightfieldTerrainShape(
|
||||
sqrtVerts, sqrtVerts, heights, 1,
|
||||
minH, maxH, 2,
|
||||
PHY_FLOAT, false
|
||||
);
|
||||
mShape->setUseDiamondSubdivision(true);
|
||||
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
||||
|
||||
btTransform transform(btQuaternion::getIdentity(),
|
||||
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
||||
(y+0.5f) * triSize * (sqrtVerts-1),
|
||||
(maxH+minH)*0.5f));
|
||||
|
||||
mCollisionObject = new btCollisionObject;
|
||||
mCollisionObject->setCollisionShape(mShape);
|
||||
mCollisionObject->setWorldTransform(transform);
|
||||
|
||||
mHoldObject = holdObject;
|
||||
}
|
||||
~HeightField()
|
||||
{
|
||||
delete mCollisionObject;
|
||||
delete mShape;
|
||||
}
|
||||
btCollisionObject* getCollisionObject()
|
||||
{
|
||||
return mCollisionObject;
|
||||
}
|
||||
|
||||
private:
|
||||
btHeightfieldTerrainShape* mShape;
|
||||
btCollisionObject* mCollisionObject;
|
||||
osg::ref_ptr<const osg::Object> mHoldObject;
|
||||
|
||||
void operator=(const HeightField&);
|
||||
HeightField(const HeightField&);
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------
|
||||
|
||||
class Object : public PtrHolder
|
||||
{
|
||||
public:
|
||||
Object(const MWWorld::Ptr& ptr, osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance)
|
||||
: mShapeInstance(shapeInstance)
|
||||
, mSolid(true)
|
||||
{
|
||||
mPtr = ptr;
|
||||
|
||||
mCollisionObject.reset(new btCollisionObject);
|
||||
mCollisionObject->setCollisionShape(shapeInstance->getCollisionShape());
|
||||
|
||||
mCollisionObject->setUserPointer(static_cast<PtrHolder*>(this));
|
||||
|
||||
setScale(ptr.getCellRef().getScale());
|
||||
setRotation(toBullet(ptr.getRefData().getBaseNode()->getAttitude()));
|
||||
const float* pos = ptr.getRefData().getPosition().pos;
|
||||
setOrigin(btVector3(pos[0], pos[1], pos[2]));
|
||||
}
|
||||
|
||||
const Resource::BulletShapeInstance* getShapeInstance() const
|
||||
{
|
||||
return mShapeInstance.get();
|
||||
}
|
||||
|
||||
void setScale(float scale)
|
||||
{
|
||||
mShapeInstance->getCollisionShape()->setLocalScaling(btVector3(scale,scale,scale));
|
||||
}
|
||||
|
||||
void setRotation(const btQuaternion& quat)
|
||||
{
|
||||
mCollisionObject->getWorldTransform().setRotation(quat);
|
||||
}
|
||||
|
||||
void setOrigin(const btVector3& vec)
|
||||
{
|
||||
mCollisionObject->getWorldTransform().setOrigin(vec);
|
||||
}
|
||||
|
||||
btCollisionObject* getCollisionObject()
|
||||
{
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
const btCollisionObject* getCollisionObject() const
|
||||
{
|
||||
return mCollisionObject.get();
|
||||
}
|
||||
|
||||
/// Return solid flag. Not used by the object itself, true by default.
|
||||
bool isSolid() const
|
||||
{
|
||||
return mSolid;
|
||||
}
|
||||
|
||||
void setSolid(bool solid)
|
||||
{
|
||||
mSolid = solid;
|
||||
}
|
||||
|
||||
bool isAnimated() const
|
||||
{
|
||||
return !mShapeInstance->mAnimatedShapes.empty();
|
||||
}
|
||||
|
||||
void animateCollisionShapes(btCollisionWorld* collisionWorld)
|
||||
{
|
||||
if (mShapeInstance->mAnimatedShapes.empty())
|
||||
return;
|
||||
|
||||
assert (mShapeInstance->getCollisionShape()->isCompound());
|
||||
|
||||
btCompoundShape* compound = static_cast<btCompoundShape*>(mShapeInstance->getCollisionShape());
|
||||
for (std::map<int, int>::const_iterator it = mShapeInstance->mAnimatedShapes.begin(); it != mShapeInstance->mAnimatedShapes.end(); ++it)
|
||||
{
|
||||
int recIndex = it->first;
|
||||
int shapeIndex = it->second;
|
||||
|
||||
std::map<int, osg::NodePath>::iterator nodePathFound = mRecIndexToNodePath.find(recIndex);
|
||||
if (nodePathFound == mRecIndexToNodePath.end())
|
||||
{
|
||||
NifOsg::FindGroupByRecIndex visitor(recIndex);
|
||||
mPtr.getRefData().getBaseNode()->accept(visitor);
|
||||
if (!visitor.mFound)
|
||||
{
|
||||
Log(Debug::Warning) << "Warning: animateCollisionShapes can't find node " << recIndex << " for " << mPtr.getCellRef().getRefId();
|
||||
|
||||
// Remove nonexistent nodes from animated shapes map and early out
|
||||
mShapeInstance->mAnimatedShapes.erase(recIndex);
|
||||
return;
|
||||
}
|
||||
osg::NodePath nodePath = visitor.mFoundPath;
|
||||
nodePath.erase(nodePath.begin());
|
||||
nodePathFound = mRecIndexToNodePath.insert(std::make_pair(recIndex, nodePath)).first;
|
||||
}
|
||||
|
||||
osg::NodePath& nodePath = nodePathFound->second;
|
||||
osg::Matrixf matrix = osg::computeLocalToWorld(nodePath);
|
||||
matrix.orthoNormalize(matrix);
|
||||
|
||||
btTransform transform;
|
||||
transform.setOrigin(toBullet(matrix.getTrans()) * compound->getLocalScaling());
|
||||
for (int i=0; i<3; ++i)
|
||||
for (int j=0; j<3; ++j)
|
||||
transform.getBasis()[i][j] = matrix(j,i); // NB column/row major difference
|
||||
|
||||
// Note: we can not apply scaling here for now since we treat scaled shapes
|
||||
// as new shapes (btScaledBvhTriangleMeshShape) with 1.0 scale for now
|
||||
if (!(transform == compound->getChildTransform(shapeIndex)))
|
||||
compound->updateChildTransform(shapeIndex, transform);
|
||||
}
|
||||
|
||||
collisionWorld->updateSingleAabb(mCollisionObject.get());
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<btCollisionObject> mCollisionObject;
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> mShapeInstance;
|
||||
std::map<int, osg::NodePath> mRecIndexToNodePath;
|
||||
bool mSolid;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
||||
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
|
||||
|
@ -1171,6 +1013,14 @@ namespace MWPhysics
|
|||
}
|
||||
}
|
||||
|
||||
const HeightField* PhysicsSystem::getHeightField(int x, int y) const
|
||||
{
|
||||
const auto heightField = mHeightFields.find(std::make_pair(x, y));
|
||||
if (heightField == mHeightFields.end())
|
||||
return nullptr;
|
||||
return heightField->second;
|
||||
}
|
||||
|
||||
void PhysicsSystem::addObject (const MWWorld::Ptr& ptr, const std::string& mesh, int collisionType)
|
||||
{
|
||||
osg::ref_ptr<Resource::BulletShapeInstance> shapeInstance = mShapeManager->getInstance(mesh);
|
||||
|
@ -1366,6 +1216,33 @@ namespace MWPhysics
|
|||
return false;
|
||||
}
|
||||
|
||||
void PhysicsSystem::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled)
|
||||
{
|
||||
ActorMap::iterator found = mActors.find(ptr);
|
||||
if (found != mActors.end())
|
||||
{
|
||||
bool cmode = found->second->getCollisionMode();
|
||||
if (cmode == enabled)
|
||||
return;
|
||||
|
||||
cmode = enabled;
|
||||
found->second->enableCollisionMode(cmode);
|
||||
found->second->enableCollisionBody(cmode);
|
||||
}
|
||||
}
|
||||
|
||||
bool PhysicsSystem::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
ActorMap::iterator found = mActors.find(ptr);
|
||||
if (found != mActors.end())
|
||||
{
|
||||
bool cmode = found->second->getCollisionMode();
|
||||
return cmode;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PhysicsSystem::queueObjectMovement(const MWWorld::Ptr &ptr, const osg::Vec3f &movement)
|
||||
{
|
||||
PtrVelocityList::iterator iter = mMovementQueue.begin();
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <memory>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include <osg/Quat>
|
||||
#include <osg/ref_ptr>
|
||||
|
@ -49,6 +50,9 @@ namespace MWPhysics
|
|||
class Object;
|
||||
class Actor;
|
||||
|
||||
static const float sMaxSlope = 49.0f;
|
||||
static const float sStepSizeUp = 34.0f;
|
||||
|
||||
class PhysicsSystem
|
||||
{
|
||||
public:
|
||||
|
@ -85,7 +89,11 @@ namespace MWPhysics
|
|||
|
||||
void removeHeightField (int x, int y);
|
||||
|
||||
const HeightField* getHeightField(int x, int y) const;
|
||||
|
||||
bool toggleCollisionMode();
|
||||
bool isActorCollisionEnabled(const MWWorld::Ptr& ptr);
|
||||
void setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled);
|
||||
|
||||
void stepSimulation(float dt);
|
||||
void debugDraw();
|
||||
|
@ -170,6 +178,12 @@ namespace MWPhysics
|
|||
|
||||
bool isOnSolidGround (const MWWorld::Ptr& actor) const;
|
||||
|
||||
template <class Function>
|
||||
void forEachAnimatedObject(Function&& function) const
|
||||
{
|
||||
std::for_each(mAnimatedObjects.begin(), mAnimatedObjects.end(), function);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void updateWater();
|
||||
|
|
33
apps/openmw/mwphysics/ptrholder.hpp
Normal file
33
apps/openmw/mwphysics/ptrholder.hpp
Normal file
|
@ -0,0 +1,33 @@
|
|||
#ifndef OPENMW_MWPHYSICS_PTRHOLDER_H
|
||||
#define OPENMW_MWPHYSICS_PTRHOLDER_H
|
||||
|
||||
#include "../mwworld/ptr.hpp"
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class PtrHolder
|
||||
{
|
||||
public:
|
||||
virtual ~PtrHolder() {}
|
||||
|
||||
void updatePtr(const MWWorld::Ptr& updated)
|
||||
{
|
||||
mPtr = updated;
|
||||
}
|
||||
|
||||
MWWorld::Ptr getPtr()
|
||||
{
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
MWWorld::ConstPtr getPtr() const
|
||||
{
|
||||
return mPtr;
|
||||
}
|
||||
|
||||
protected:
|
||||
MWWorld::Ptr mPtr;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
99
apps/openmw/mwrender/actorspaths.cpp
Normal file
99
apps/openmw/mwrender/actorspaths.cpp
Normal file
|
@ -0,0 +1,99 @@
|
|||
#include "actorspaths.hpp"
|
||||
#include "vismask.hpp"
|
||||
|
||||
#include <components/sceneutil/agentpath.hpp>
|
||||
|
||||
#include <osg/PositionAttitudeTransform>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
ActorsPaths::ActorsPaths(const osg::ref_ptr<osg::Group>& root, bool enabled)
|
||||
: mRootNode(root)
|
||||
, mEnabled(enabled)
|
||||
{
|
||||
}
|
||||
|
||||
ActorsPaths::~ActorsPaths()
|
||||
{
|
||||
if (mEnabled)
|
||||
disable();
|
||||
}
|
||||
|
||||
bool ActorsPaths::toggle()
|
||||
{
|
||||
if (mEnabled)
|
||||
disable();
|
||||
else
|
||||
enable();
|
||||
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
void ActorsPaths::update(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||
const DetourNavigator::Settings& settings)
|
||||
{
|
||||
if (!mEnabled)
|
||||
return;
|
||||
|
||||
const auto group = mGroups.find(actor);
|
||||
if (group != mGroups.end())
|
||||
mRootNode->removeChild(group->second);
|
||||
|
||||
const auto newGroup = SceneUtil::createAgentPathGroup(path, halfExtents, start, end, settings);
|
||||
if (newGroup)
|
||||
{
|
||||
newGroup->setNodeMask(Mask_Debug);
|
||||
mRootNode->addChild(newGroup);
|
||||
mGroups[actor] = newGroup;
|
||||
}
|
||||
}
|
||||
|
||||
void ActorsPaths::remove(const MWWorld::ConstPtr& actor)
|
||||
{
|
||||
const auto group = mGroups.find(actor);
|
||||
if (group != mGroups.end())
|
||||
{
|
||||
mRootNode->removeChild(group->second);
|
||||
mGroups.erase(group);
|
||||
}
|
||||
}
|
||||
|
||||
void ActorsPaths::removeCell(const MWWorld::CellStore* const store)
|
||||
{
|
||||
for (auto it = mGroups.begin(); it != mGroups.end(); )
|
||||
{
|
||||
if (it->first.getCell() == store)
|
||||
{
|
||||
mRootNode->removeChild(it->second);
|
||||
it = mGroups.erase(it);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void ActorsPaths::updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated)
|
||||
{
|
||||
const auto it = mGroups.find(old);
|
||||
if (it == mGroups.end())
|
||||
return;
|
||||
auto group = std::move(it->second);
|
||||
mGroups.erase(it);
|
||||
mGroups.insert(std::make_pair(updated, std::move(group)));
|
||||
}
|
||||
|
||||
void ActorsPaths::enable()
|
||||
{
|
||||
std::for_each(mGroups.begin(), mGroups.end(),
|
||||
[&] (const Groups::value_type& v) { mRootNode->addChild(v.second); });
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
void ActorsPaths::disable()
|
||||
{
|
||||
std::for_each(mGroups.begin(), mGroups.end(),
|
||||
[&] (const Groups::value_type& v) { mRootNode->removeChild(v.second); });
|
||||
mEnabled = false;
|
||||
}
|
||||
}
|
51
apps/openmw/mwrender/actorspaths.hpp
Normal file
51
apps/openmw/mwrender/actorspaths.hpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
#ifndef OPENMW_MWRENDER_AGENTSPATHS_H
|
||||
#define OPENMW_MWRENDER_AGENTSPATHS_H
|
||||
|
||||
#include <apps/openmw/mwworld/ptr.hpp>
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
#include <unordered_map>
|
||||
#include <deque>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Group;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class ActorsPaths
|
||||
{
|
||||
public:
|
||||
ActorsPaths(const osg::ref_ptr<osg::Group>& root, bool enabled);
|
||||
~ActorsPaths();
|
||||
|
||||
bool toggle();
|
||||
|
||||
void update(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end,
|
||||
const DetourNavigator::Settings& settings);
|
||||
|
||||
void remove(const MWWorld::ConstPtr& actor);
|
||||
|
||||
void removeCell(const MWWorld::CellStore* const store);
|
||||
|
||||
void updatePtr(const MWWorld::ConstPtr& old, const MWWorld::ConstPtr& updated);
|
||||
|
||||
void enable();
|
||||
|
||||
void disable();
|
||||
|
||||
private:
|
||||
using Groups = std::map<MWWorld::ConstPtr, osg::ref_ptr<osg::Group>>;
|
||||
|
||||
osg::ref_ptr<osg::Group> mRootNode;
|
||||
Groups mGroups;
|
||||
bool mEnabled;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -15,13 +15,13 @@
|
|||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp>
|
||||
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/keyframemanager.hpp>
|
||||
#include <components/resource/imagemanager.hpp>
|
||||
|
||||
#include <components/misc/constants.hpp>
|
||||
|
||||
#include <components/nifosg/nifloader.hpp> // KeyframeHolder
|
||||
#include <components/nifosg/controller.hpp>
|
||||
|
||||
|
@ -190,12 +190,6 @@ namespace
|
|||
{
|
||||
}
|
||||
|
||||
RemoveFinishedCallbackVisitor(int effectId)
|
||||
: RemoveVisitor()
|
||||
, mHasMagicEffects(false)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void apply(osg::Node &node)
|
||||
{
|
||||
traverse(node);
|
||||
|
@ -228,9 +222,6 @@ namespace
|
|||
virtual void apply(osg::Geometry&)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
int mEffectId;
|
||||
};
|
||||
|
||||
class RemoveCallbackVisitor : public RemoveVisitor
|
||||
|
@ -1747,6 +1738,15 @@ namespace MWRender
|
|||
mAlpha = alpha;
|
||||
|
||||
if (alpha != 1.f)
|
||||
{
|
||||
// If we have an existing material for alpha transparency, just override alpha level
|
||||
osg::StateSet* stateset = mObjectRoot->getOrCreateStateSet();
|
||||
osg::Material* material = static_cast<osg::Material*>(stateset->getAttribute(osg::StateAttribute::MATERIAL));
|
||||
if (material)
|
||||
{
|
||||
material->setAlpha(osg::Material::FRONT_AND_BACK, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
osg::StateSet* stateset (new osg::StateSet);
|
||||
|
||||
|
@ -1754,7 +1754,7 @@ namespace MWRender
|
|||
stateset->setAttributeAndModes(blendfunc, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE);
|
||||
|
||||
// FIXME: overriding diffuse/ambient/emissive colors
|
||||
osg::Material* material (new osg::Material);
|
||||
material = new osg::Material;
|
||||
material->setColorMode(osg::Material::OFF);
|
||||
material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,alpha));
|
||||
material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4f(1,1,1,1));
|
||||
|
@ -1764,6 +1764,7 @@ namespace MWRender
|
|||
|
||||
mResourceSystem->getSceneManager()->recreateShaders(mObjectRoot);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mObjectRoot->setStateSet(nullptr);
|
||||
|
@ -1798,9 +1799,12 @@ namespace MWRender
|
|||
}
|
||||
else
|
||||
{
|
||||
effect += 3;
|
||||
float radius = effect * 66.f;
|
||||
float linearAttenuation = 0.5f / effect;
|
||||
// TODO: use global attenuation settings
|
||||
|
||||
// 1 pt of Light magnitude corresponds to 1 foot of radius
|
||||
float radius = effect * std::ceil(Constants::UnitsPerFoot);
|
||||
const float linearValue = 3.f; // Currently hardcoded: unmodified Morrowind attenuation settings
|
||||
float linearAttenuation = linearValue / radius;
|
||||
|
||||
if (!mGlowLight || linearAttenuation != mGlowLight->getLight(0)->getLinearAttenuation())
|
||||
{
|
||||
|
@ -1822,7 +1826,8 @@ namespace MWRender
|
|||
mGlowLight->setLight(light);
|
||||
}
|
||||
|
||||
mGlowLight->setRadius(radius);
|
||||
// Make the obvious cut-off a bit less obvious
|
||||
mGlowLight->setRadius(radius * 3);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
71
apps/openmw/mwrender/navmesh.cpp
Normal file
71
apps/openmw/mwrender/navmesh.cpp
Normal file
|
@ -0,0 +1,71 @@
|
|||
#include "navmesh.hpp"
|
||||
#include "vismask.hpp"
|
||||
|
||||
#include <components/sceneutil/navmesh.hpp>
|
||||
|
||||
#include <osg/PositionAttitudeTransform>
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
NavMesh::NavMesh(const osg::ref_ptr<osg::Group>& root, bool enabled)
|
||||
: mRootNode(root)
|
||||
, mEnabled(enabled)
|
||||
, mGeneration(0)
|
||||
, mRevision(0)
|
||||
{
|
||||
}
|
||||
|
||||
NavMesh::~NavMesh()
|
||||
{
|
||||
if (mEnabled)
|
||||
disable();
|
||||
}
|
||||
|
||||
bool NavMesh::toggle()
|
||||
{
|
||||
if (mEnabled)
|
||||
disable();
|
||||
else
|
||||
enable();
|
||||
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id,
|
||||
const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings)
|
||||
{
|
||||
if (!mEnabled || (mId == id && mGeneration >= generation && mRevision >= revision))
|
||||
return;
|
||||
|
||||
mId = id;
|
||||
mGeneration = generation;
|
||||
mRevision = revision;
|
||||
if (mGroup)
|
||||
mRootNode->removeChild(mGroup);
|
||||
mGroup = SceneUtil::createNavMeshGroup(navMesh, settings);
|
||||
if (mGroup)
|
||||
{
|
||||
mGroup->setNodeMask(Mask_Debug);
|
||||
mRootNode->addChild(mGroup);
|
||||
}
|
||||
}
|
||||
|
||||
void NavMesh::reset()
|
||||
{
|
||||
if (mGroup)
|
||||
mRootNode->removeChild(mGroup);
|
||||
}
|
||||
|
||||
void NavMesh::enable()
|
||||
{
|
||||
if (mGroup)
|
||||
mRootNode->addChild(mGroup);
|
||||
mEnabled = true;
|
||||
}
|
||||
|
||||
void NavMesh::disable()
|
||||
{
|
||||
reset();
|
||||
mEnabled = false;
|
||||
}
|
||||
}
|
43
apps/openmw/mwrender/navmesh.hpp
Normal file
43
apps/openmw/mwrender/navmesh.hpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
#ifndef OPENMW_MWRENDER_NAVMESH_H
|
||||
#define OPENMW_MWRENDER_NAVMESH_H
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Group;
|
||||
class Geometry;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class NavMesh
|
||||
{
|
||||
public:
|
||||
NavMesh(const osg::ref_ptr<osg::Group>& root, bool enabled);
|
||||
~NavMesh();
|
||||
|
||||
bool toggle();
|
||||
|
||||
void update(const dtNavMesh& navMesh, const std::size_t number, const std::size_t generation,
|
||||
const std::size_t revision, const DetourNavigator::Settings& settings);
|
||||
|
||||
void reset();
|
||||
|
||||
void enable();
|
||||
|
||||
void disable();
|
||||
|
||||
private:
|
||||
osg::ref_ptr<osg::Group> mRootNode;
|
||||
bool mEnabled;
|
||||
std::size_t mId = std::numeric_limits<std::size_t>::max();
|
||||
std::size_t mGeneration;
|
||||
std::size_t mRevision;
|
||||
osg::ref_ptr<osg::Group> mGroup;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
|
@ -47,6 +47,8 @@
|
|||
#include <components/esm/loadcell.hpp>
|
||||
#include <components/fallback/fallback.hpp>
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
@ -64,6 +66,8 @@
|
|||
#include "water.hpp"
|
||||
#include "terrainstorage.hpp"
|
||||
#include "util.hpp"
|
||||
#include "navmesh.hpp"
|
||||
#include "actorspaths.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -190,13 +194,16 @@ namespace MWRender
|
|||
Resource::ResourceSystem* mResourceSystem;
|
||||
};
|
||||
|
||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath)
|
||||
RenderingManager::RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath,
|
||||
DetourNavigator::Navigator& navigator)
|
||||
: mViewer(viewer)
|
||||
, mRootNode(rootNode)
|
||||
, mResourceSystem(resourceSystem)
|
||||
, mWorkQueue(workQueue)
|
||||
, mUnrefQueue(new SceneUtil::UnrefQueue)
|
||||
, mNavigator(navigator)
|
||||
, mLandFogStart(0.f)
|
||||
, mLandFogEnd(std::numeric_limits<float>::max())
|
||||
, mUnderwaterFogStart(0.f)
|
||||
|
@ -250,6 +257,8 @@ namespace MWRender
|
|||
// It is unnecessary to stop/start the viewer as no frames are being rendered yet.
|
||||
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines);
|
||||
|
||||
mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator")));
|
||||
mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")));
|
||||
mPathgrid.reset(new Pathgrid(mRootNode));
|
||||
|
||||
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
||||
|
@ -487,6 +496,7 @@ namespace MWRender
|
|||
void RenderingManager::removeCell(const MWWorld::CellStore *store)
|
||||
{
|
||||
mPathgrid->removeCell(store);
|
||||
mActorsPaths->removeCell(store);
|
||||
mObjects->removeCell(store);
|
||||
|
||||
if (store->getCell()->isExterior())
|
||||
|
@ -542,6 +552,14 @@ namespace MWRender
|
|||
mViewer->getCamera()->setCullMask(mask);
|
||||
return enabled;
|
||||
}
|
||||
else if (mode == Render_NavMesh)
|
||||
{
|
||||
return mNavMesh->toggle();
|
||||
}
|
||||
else if (mode == Render_ActorsPaths)
|
||||
{
|
||||
return mActorsPaths->toggle();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -607,6 +625,28 @@ namespace MWRender
|
|||
mWater->update(dt);
|
||||
}
|
||||
|
||||
const auto navMeshes = mNavigator.getNavMeshes();
|
||||
|
||||
auto it = navMeshes.begin();
|
||||
for (std::size_t i = 0; it != navMeshes.end() && i < mNavMeshNumber; ++i)
|
||||
++it;
|
||||
if (it == navMeshes.end())
|
||||
{
|
||||
mNavMesh->reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto locked = it->second.lockConst();
|
||||
mNavMesh->update(locked->getValue(), mNavMeshNumber, locked->getGeneration(),
|
||||
locked->getNavMeshRevision(), mNavigator.getSettings());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "NavMesh render update exception: " << e.what();
|
||||
}
|
||||
}
|
||||
mCamera->update(dt, paused);
|
||||
|
||||
osg::Vec3f focal, cameraPos;
|
||||
|
@ -668,6 +708,7 @@ namespace MWRender
|
|||
|
||||
void RenderingManager::removeObject(const MWWorld::Ptr &ptr)
|
||||
{
|
||||
mActorsPaths->remove(ptr);
|
||||
mObjects->removeObject(ptr);
|
||||
mWater->removeEmitter(ptr);
|
||||
}
|
||||
|
@ -1051,6 +1092,7 @@ namespace MWRender
|
|||
void RenderingManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated)
|
||||
{
|
||||
mObjects->updatePtr(old, updated);
|
||||
mActorsPaths->updatePtr(old, updated);
|
||||
}
|
||||
|
||||
void RenderingManager::spawnEffect(const std::string &model, const std::string &texture, const osg::Vec3f &worldPosition, float scale, bool isMagicVFX)
|
||||
|
@ -1371,5 +1413,19 @@ namespace MWRender
|
|||
return mTerrainStorage->getLandManager();
|
||||
}
|
||||
|
||||
|
||||
void RenderingManager::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const
|
||||
{
|
||||
mActorsPaths->update(actor, path, halfExtents, start, end, mNavigator.getSettings());
|
||||
}
|
||||
|
||||
void RenderingManager::removeActorPath(const MWWorld::ConstPtr& actor) const
|
||||
{
|
||||
mActorsPaths->remove(actor);
|
||||
}
|
||||
|
||||
void RenderingManager::setNavMeshNumber(const std::size_t value)
|
||||
{
|
||||
mNavMeshNumber = value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "renderinginterface.hpp"
|
||||
#include "rendermode.hpp"
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
class Group;
|
||||
|
@ -56,6 +58,12 @@ namespace SceneUtil
|
|||
class UnrefQueue;
|
||||
}
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class Navigator;
|
||||
struct Settings;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
|
||||
|
@ -69,12 +77,16 @@ namespace MWRender
|
|||
class Water;
|
||||
class TerrainStorage;
|
||||
class LandManager;
|
||||
class NavMesh;
|
||||
class ActorsPaths;
|
||||
|
||||
class RenderingManager : public MWRender::RenderingInterface
|
||||
{
|
||||
public:
|
||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode, Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath);
|
||||
RenderingManager(osgViewer::Viewer* viewer, osg::ref_ptr<osg::Group> rootNode,
|
||||
Resource::ResourceSystem* resourceSystem, SceneUtil::WorkQueue* workQueue,
|
||||
const Fallback::Map* fallback, const std::string& resourcePath,
|
||||
DetourNavigator::Navigator& navigator);
|
||||
~RenderingManager();
|
||||
|
||||
MWRender::Objects& getObjects();
|
||||
|
@ -212,6 +224,13 @@ namespace MWRender
|
|||
|
||||
bool toggleBorders();
|
||||
|
||||
void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const;
|
||||
|
||||
void removeActorPath(const MWWorld::ConstPtr& actor) const;
|
||||
|
||||
void setNavMeshNumber(const std::size_t value);
|
||||
|
||||
private:
|
||||
void updateProjectionMatrix();
|
||||
void updateTextureFiltering();
|
||||
|
@ -236,6 +255,10 @@ namespace MWRender
|
|||
|
||||
osg::ref_ptr<osg::Light> mSunLight;
|
||||
|
||||
DetourNavigator::Navigator& mNavigator;
|
||||
std::unique_ptr<NavMesh> mNavMesh;
|
||||
std::size_t mNavMeshNumber = 0;
|
||||
std::unique_ptr<ActorsPaths> mActorsPaths;
|
||||
std::unique_ptr<Pathgrid> mPathgrid;
|
||||
std::unique_ptr<Objects> mObjects;
|
||||
std::unique_ptr<Water> mWater;
|
||||
|
|
|
@ -10,7 +10,9 @@ namespace MWRender
|
|||
Render_Wireframe,
|
||||
Render_Pathgrid,
|
||||
Render_Water,
|
||||
Render_Scene
|
||||
Render_Scene,
|
||||
Render_NavMesh,
|
||||
Render_ActorsPaths,
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -455,5 +455,8 @@ op 0x2000304: Show
|
|||
op 0x2000305: Show, explicit
|
||||
op 0x2000306: OnActivate, explicit
|
||||
op 0x2000307: ToggleBorders, tb
|
||||
op 0x2000308: ToggleNavMesh
|
||||
op 0x2000309: ToggleActorsPaths
|
||||
op 0x200030a: SetNavMeshNumber
|
||||
|
||||
opcodes 0x2000308-0x3ffffff unused
|
||||
opcodes 0x200030b-0x3ffffff unused
|
||||
|
|
|
@ -1317,6 +1317,53 @@ namespace MWScript
|
|||
}
|
||||
};
|
||||
|
||||
class OpToggleNavMesh : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
bool enabled =
|
||||
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_NavMesh);
|
||||
|
||||
runtime.getContext().report (enabled ?
|
||||
"Navigation Mesh Rendering -> On" : "Navigation Mesh Rendering -> Off");
|
||||
}
|
||||
};
|
||||
|
||||
class OpToggleActorsPaths : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
bool enabled =
|
||||
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_ActorsPaths);
|
||||
|
||||
runtime.getContext().report (enabled ?
|
||||
"Agents Paths Rendering -> On" : "Agents Paths Rendering -> Off");
|
||||
}
|
||||
};
|
||||
|
||||
class OpSetNavMeshNumberToRender : public Interpreter::Opcode0
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void execute (Interpreter::Runtime& runtime)
|
||||
{
|
||||
const auto navMeshNumber = runtime[0].mInteger;
|
||||
runtime.pop();
|
||||
|
||||
if (navMeshNumber < 0)
|
||||
{
|
||||
runtime.getContext().report("Invalid navmesh number: use not less than zero values");
|
||||
return;
|
||||
}
|
||||
|
||||
MWBase::Environment::get().getWorld()->setNavMeshNumberToRender(static_cast<std::size_t>(navMeshNumber));
|
||||
}
|
||||
};
|
||||
|
||||
void installOpcodes (Interpreter::Interpreter& interpreter)
|
||||
{
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
|
||||
|
@ -1417,6 +1464,9 @@ namespace MWScript
|
|||
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraph, new OpShowSceneGraph<ImplicitRef>);
|
||||
interpreter.installSegment3 (Compiler::Misc::opcodeShowSceneGraphExplicit, new OpShowSceneGraph<ExplicitRef>);
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeToggleBorders, new OpToggleBorders);
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeToggleNavMesh, new OpToggleNavMesh);
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeToggleActorsPaths, new OpToggleActorsPaths);
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -265,9 +265,10 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot
|
|||
writer.save (stream);
|
||||
|
||||
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||
// Using only Cells for progress information, since they typically have the largest records by far
|
||||
listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells());
|
||||
listener.setLabel("#{sNotifyMessage4}", true);
|
||||
listener.setLabel("#{sNotifyMessage4}", true, messagesCount > 0);
|
||||
|
||||
Loading::ScopedLoad load(&listener);
|
||||
|
||||
|
@ -389,9 +390,10 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
|||
std::map<int, int> contentFileMap = buildContentFileIndexMap (reader);
|
||||
|
||||
Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||
|
||||
listener.setProgressRange(100);
|
||||
listener.setLabel("#{sLoadingMessage14}");
|
||||
listener.setLabel("#{sLoadingMessage14}", false, messagesCount > 0);
|
||||
|
||||
Loading::ScopedLoad load(&listener);
|
||||
|
||||
|
|
|
@ -70,6 +70,22 @@ namespace MWWorld
|
|||
return mCellRef.mEnchantmentCharge;
|
||||
}
|
||||
|
||||
float CellRef::getNormalizedEnchantmentCharge(int maxCharge) const
|
||||
{
|
||||
if (maxCharge == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (mCellRef.mEnchantmentCharge == -1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return mCellRef.mEnchantmentCharge / static_cast<float>(maxCharge);
|
||||
}
|
||||
}
|
||||
|
||||
void CellRef::setEnchantmentCharge(float charge)
|
||||
{
|
||||
if (charge != mCellRef.mEnchantmentCharge)
|
||||
|
|
|
@ -56,6 +56,9 @@ namespace MWWorld
|
|||
// Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full).
|
||||
float getEnchantmentCharge() const;
|
||||
|
||||
// Remaining enchantment charge rescaled to the supplied maximum charge (such as one of the enchantment).
|
||||
float getNormalizedEnchantmentCharge(int maxCharge) const;
|
||||
|
||||
void setEnchantmentCharge(float charge);
|
||||
|
||||
// For weapon or armor, this is the remaining item health.
|
||||
|
|
|
@ -432,7 +432,7 @@ namespace MWWorld
|
|||
mHasState = true;
|
||||
}
|
||||
|
||||
int CellStore::count() const
|
||||
std::size_t CellStore::count() const
|
||||
{
|
||||
return mMergedRefs.size();
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ namespace MWWorld
|
|||
|
||||
ESM::FogState* getFog () const;
|
||||
|
||||
int count() const;
|
||||
std::size_t count() const;
|
||||
///< Return total number of references, including deleted ones.
|
||||
|
||||
void load ();
|
||||
|
@ -283,7 +283,7 @@ namespace MWWorld
|
|||
/// \attention This function also lists deleted (count 0) objects!
|
||||
/// \return Iteration completed?
|
||||
template<class Visitor>
|
||||
bool forEachConst (Visitor& visitor) const
|
||||
bool forEachConst (Visitor&& visitor) const
|
||||
{
|
||||
if (mState != State_Loaded)
|
||||
return false;
|
||||
|
|
|
@ -83,6 +83,18 @@ namespace MWWorld
|
|||
return ptr.getCellRef().getCharge();
|
||||
}
|
||||
|
||||
float Class::getItemNormalizedHealth (const ConstPtr& ptr) const
|
||||
{
|
||||
if (getItemMaxHealth(ptr) == 0)
|
||||
{
|
||||
return 0.f;
|
||||
}
|
||||
else
|
||||
{
|
||||
return getItemHealth(ptr) / static_cast<float>(getItemMaxHealth(ptr));
|
||||
}
|
||||
}
|
||||
|
||||
int Class::getItemMaxHealth (const ConstPtr& ptr) const
|
||||
{
|
||||
throw std::runtime_error ("class does not have item health");
|
||||
|
|
|
@ -112,6 +112,9 @@ namespace MWWorld
|
|||
virtual int getItemHealth (const ConstPtr& ptr) const;
|
||||
///< Return current item health or throw an exception if class does not have item health
|
||||
|
||||
virtual float getItemNormalizedHealth (const ConstPtr& ptr) const;
|
||||
///< Return current item health re-scaled to maximum health
|
||||
|
||||
virtual int getItemMaxHealth (const ConstPtr& ptr) const;
|
||||
///< Return item max health or throw an exception, if class does not have item health
|
||||
/// (default implementation: throw an exception)
|
||||
|
|
|
@ -2,12 +2,19 @@
|
|||
|
||||
#include <limits>
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
#include <components/loadinglistener/loadinglistener.hpp>
|
||||
#include <components/misc/resourcehelpers.hpp>
|
||||
#include <components/settings/settings.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
#include <components/resource/scenemanager.hpp>
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
#include <components/detournavigator/debug.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/world.hpp"
|
||||
|
@ -19,6 +26,10 @@
|
|||
#include "../mwrender/landmanager.hpp"
|
||||
|
||||
#include "../mwphysics/physicssystem.hpp"
|
||||
#include "../mwphysics/actor.hpp"
|
||||
#include "../mwphysics/object.hpp"
|
||||
#include "../mwphysics/heightfield.hpp"
|
||||
#include "../mwphysics/convert.hpp"
|
||||
|
||||
#include "player.hpp"
|
||||
#include "localscripts.hpp"
|
||||
|
@ -30,25 +41,45 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
osg::Quat makeActorOsgQuat(const ESM::Position& position)
|
||||
{
|
||||
return osg::Quat(position.rot[2], osg::Vec3(0, 0, -1));
|
||||
}
|
||||
|
||||
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, bool inverseRotationOrder)
|
||||
osg::Quat makeInversedOrderObjectOsgQuat(const ESM::Position& position)
|
||||
{
|
||||
const float xr = position.rot[0];
|
||||
const float yr = position.rot[1];
|
||||
const float zr = position.rot[2];
|
||||
|
||||
return osg::Quat(xr, osg::Vec3(-1, 0, 0))
|
||||
* osg::Quat(yr, osg::Vec3(0, -1, 0))
|
||||
* osg::Quat(zr, osg::Vec3(0, 0, -1));
|
||||
}
|
||||
|
||||
osg::Quat makeObjectOsgQuat(const ESM::Position& position)
|
||||
{
|
||||
const float xr = position.rot[0];
|
||||
const float yr = position.rot[1];
|
||||
const float zr = position.rot[2];
|
||||
|
||||
return osg::Quat(zr, osg::Vec3(0, 0, -1))
|
||||
* osg::Quat(yr, osg::Vec3(0, -1, 0))
|
||||
* osg::Quat(xr, osg::Vec3(-1, 0, 0));
|
||||
}
|
||||
|
||||
void setNodeRotation(const MWWorld::Ptr& ptr, MWRender::RenderingManager& rendering, const bool inverseRotationOrder)
|
||||
{
|
||||
if (!ptr.getRefData().getBaseNode())
|
||||
return;
|
||||
|
||||
osg::Quat worldRotQuat(ptr.getRefData().getPosition().rot[2], osg::Vec3(0,0,-1));
|
||||
if (!ptr.getClass().isActor())
|
||||
{
|
||||
float xr = ptr.getRefData().getPosition().rot[0];
|
||||
float yr = ptr.getRefData().getPosition().rot[1];
|
||||
if (!inverseRotationOrder)
|
||||
worldRotQuat = worldRotQuat * osg::Quat(yr, osg::Vec3(0,-1,0)) *
|
||||
osg::Quat(xr, osg::Vec3(-1,0,0));
|
||||
else
|
||||
worldRotQuat = osg::Quat(xr, osg::Vec3(-1,0,0)) * osg::Quat(yr, osg::Vec3(0,-1,0)) * worldRotQuat;
|
||||
}
|
||||
|
||||
rendering.rotateObject(ptr, worldRotQuat);
|
||||
rendering.rotateObject(ptr,
|
||||
ptr.getClass().isActor()
|
||||
? makeActorOsgQuat(ptr.getRefData().getPosition())
|
||||
: (inverseRotationOrder
|
||||
? makeInversedOrderObjectOsgQuat(ptr.getRefData().getPosition())
|
||||
: makeObjectOsgQuat(ptr.getRefData().getPosition()))
|
||||
);
|
||||
}
|
||||
|
||||
void addObject(const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
||||
|
@ -84,6 +115,73 @@ namespace
|
|||
MWBase::Environment::get().getWorld()->applyLoopingParticles(ptr);
|
||||
}
|
||||
|
||||
void addObject(const MWWorld::Ptr& ptr, const MWPhysics::PhysicsSystem& physics, DetourNavigator::Navigator& navigator)
|
||||
{
|
||||
if (const auto object = physics.getObject(ptr))
|
||||
{
|
||||
if (ptr.getClass().isDoor() && !ptr.getCellRef().getTeleport())
|
||||
{
|
||||
const auto shape = object->getShapeInstance()->getCollisionShape();
|
||||
|
||||
btVector3 aabbMin;
|
||||
btVector3 aabbMax;
|
||||
shape->getAabb(btTransform::getIdentity(), aabbMin, aabbMax);
|
||||
|
||||
const auto center = (aabbMax + aabbMin) * 0.5f;
|
||||
|
||||
const auto distanceFromDoor = MWBase::Environment::get().getWorld()->getMaxActivationDistance() * 0.5f;
|
||||
const auto toPoint = aabbMax.x() - aabbMin.x() < aabbMax.y() - aabbMin.y()
|
||||
? btVector3(distanceFromDoor, 0, 0)
|
||||
: btVector3(0, distanceFromDoor, 0);
|
||||
|
||||
const auto& transform = object->getCollisionObject()->getWorldTransform();
|
||||
const btTransform closedDoorTransform(
|
||||
MWPhysics::toBullet(makeObjectOsgQuat(ptr.getCellRef().getPosition())),
|
||||
transform.getOrigin()
|
||||
);
|
||||
|
||||
const auto start = DetourNavigator::makeOsgVec3f(closedDoorTransform(center + toPoint));
|
||||
const auto startPoint = physics.castRay(start, start - osg::Vec3f(0, 0, 1000), ptr, {},
|
||||
MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water);
|
||||
const auto connectionStart = startPoint.mHit ? startPoint.mHitPos : start;
|
||||
|
||||
const auto end = DetourNavigator::makeOsgVec3f(closedDoorTransform(center - toPoint));
|
||||
const auto endPoint = physics.castRay(end, end - osg::Vec3f(0, 0, 1000), ptr, {},
|
||||
MWPhysics::CollisionType_World | MWPhysics::CollisionType_HeightMap | MWPhysics::CollisionType_Water);
|
||||
const auto connectionEnd = endPoint.mHit ? endPoint.mHitPos : end;
|
||||
|
||||
navigator.addObject(
|
||||
DetourNavigator::ObjectId(object),
|
||||
DetourNavigator::DoorShapes(
|
||||
*shape,
|
||||
object->getShapeInstance()->getAvoidCollisionShape(),
|
||||
connectionStart,
|
||||
connectionEnd
|
||||
),
|
||||
transform
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
navigator.addObject(
|
||||
DetourNavigator::ObjectId(object),
|
||||
DetourNavigator::ObjectShapes {
|
||||
*object->getShapeInstance()->getCollisionShape(),
|
||||
object->getShapeInstance()->getAvoidCollisionShape()
|
||||
},
|
||||
object->getCollisionObject()->getWorldTransform()
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (const auto actor = physics.getActor(ptr))
|
||||
{
|
||||
const auto halfExtents = ptr.getCell()->isExterior()
|
||||
? physics.getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
: actor->getHalfExtents();
|
||||
navigator.addAgent(halfExtents);
|
||||
}
|
||||
}
|
||||
|
||||
void updateObjectRotation (const MWWorld::Ptr& ptr, MWPhysics::PhysicsSystem& physics,
|
||||
MWRender::RenderingManager& rendering, bool inverseRotationOrder)
|
||||
{
|
||||
|
@ -110,24 +208,19 @@ namespace
|
|||
MWWorld::CellStore& mCell;
|
||||
bool mRescale;
|
||||
Loading::Listener& mLoadingListener;
|
||||
MWPhysics::PhysicsSystem& mPhysics;
|
||||
MWRender::RenderingManager& mRendering;
|
||||
|
||||
std::vector<MWWorld::Ptr> mToInsert;
|
||||
|
||||
InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener,
|
||||
MWPhysics::PhysicsSystem& physics, MWRender::RenderingManager& rendering);
|
||||
InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener);
|
||||
|
||||
bool operator() (const MWWorld::Ptr& ptr);
|
||||
void insert();
|
||||
|
||||
template <class AddObject>
|
||||
void insert(AddObject&& addObject);
|
||||
};
|
||||
|
||||
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale,
|
||||
Loading::Listener& loadingListener, MWPhysics::PhysicsSystem& physics,
|
||||
MWRender::RenderingManager& rendering)
|
||||
: mCell (cell), mRescale (rescale), mLoadingListener (loadingListener),
|
||||
mPhysics (physics),
|
||||
mRendering (rendering)
|
||||
InsertVisitor::InsertVisitor (MWWorld::CellStore& cell, bool rescale, Loading::Listener& loadingListener)
|
||||
: mCell (cell), mRescale (rescale), mLoadingListener (loadingListener)
|
||||
{}
|
||||
|
||||
bool InsertVisitor::operator() (const MWWorld::Ptr& ptr)
|
||||
|
@ -138,7 +231,8 @@ namespace
|
|||
return true;
|
||||
}
|
||||
|
||||
void InsertVisitor::insert()
|
||||
template <class AddObject>
|
||||
void InsertVisitor::insert(AddObject&& addObject)
|
||||
{
|
||||
for (std::vector<MWWorld::Ptr>::iterator it = mToInsert.begin(); it != mToInsert.end(); ++it)
|
||||
{
|
||||
|
@ -155,7 +249,7 @@ namespace
|
|||
{
|
||||
try
|
||||
{
|
||||
addObject(ptr, mPhysics, mRendering);
|
||||
addObject(ptr);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
|
@ -178,6 +272,11 @@ namespace
|
|||
}
|
||||
};
|
||||
|
||||
int getCellPositionDistanceToOrigin(const std::pair<int, int>& cellPosition)
|
||||
{
|
||||
return std::abs(cellPosition.first) + std::abs(cellPosition.second);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -233,14 +332,28 @@ namespace MWWorld
|
|||
void Scene::unloadCell (CellStoreCollection::iterator iter)
|
||||
{
|
||||
Log(Debug::Info) << "Unloading cell " << (*iter)->getCell()->getDescription();
|
||||
DetourNavigator::log("unload cell ", (*iter)->getCell()->getDescription());
|
||||
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
ListAndResetObjectsVisitor visitor;
|
||||
|
||||
(*iter)->forEach<ListAndResetObjectsVisitor>(visitor);
|
||||
for (std::vector<MWWorld::Ptr>::const_iterator iter2 (visitor.mObjects.begin());
|
||||
iter2!=visitor.mObjects.end(); ++iter2)
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
const auto playerHalfExtents = mPhysics->getHalfExtents(player);
|
||||
for (const auto& ptr : visitor.mObjects)
|
||||
{
|
||||
mPhysics->remove(*iter2);
|
||||
if (const auto object = mPhysics->getObject(ptr))
|
||||
navigator->removeObject(DetourNavigator::ObjectId(object));
|
||||
else if (const auto actor = mPhysics->getActor(ptr))
|
||||
{
|
||||
navigator->removeAgent(ptr.getCell()->isExterior() ? playerHalfExtents : actor->getHalfExtents());
|
||||
mRendering.removeActorPath(ptr);
|
||||
}
|
||||
mPhysics->remove(ptr);
|
||||
}
|
||||
|
||||
const auto cellX = (*iter)->getCell()->getGridX();
|
||||
const auto cellY = (*iter)->getCell()->getGridY();
|
||||
|
||||
if ((*iter)->getCell()->isExterior())
|
||||
{
|
||||
|
@ -250,8 +363,17 @@ namespace MWWorld
|
|||
(*iter)->getCell()->getGridY()
|
||||
);
|
||||
if (land && land->mDataTypes&ESM::Land::DATA_VHGT)
|
||||
mPhysics->removeHeightField ((*iter)->getCell()->getGridX(), (*iter)->getCell()->getGridY());
|
||||
{
|
||||
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
||||
navigator->removeObject(DetourNavigator::ObjectId(heightField));
|
||||
mPhysics->removeHeightField(cellX, cellY);
|
||||
}
|
||||
}
|
||||
|
||||
if ((*iter)->getCell()->hasWater())
|
||||
navigator->removeWater(osg::Vec2i(cellX, cellY));
|
||||
|
||||
navigator->update(player.getRefData().getPosition().asVec3());
|
||||
|
||||
MWBase::Environment::get().getMechanicsManager()->drop (*iter);
|
||||
|
||||
|
@ -271,20 +393,24 @@ namespace MWWorld
|
|||
if(result.second)
|
||||
{
|
||||
Log(Debug::Info) << "Loading cell " << cell->getCell()->getDescription();
|
||||
DetourNavigator::log("load cell ", cell->getCell()->getDescription());
|
||||
|
||||
float verts = ESM::Land::LAND_SIZE;
|
||||
float worldsize = ESM::Land::REAL_SIZE;
|
||||
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
|
||||
const int cellX = cell->getCell()->getGridX();
|
||||
const int cellY = cell->getCell()->getGridY();
|
||||
|
||||
// Load terrain physics first...
|
||||
if (cell->getCell()->isExterior())
|
||||
{
|
||||
int cellX = cell->getCell()->getGridX();
|
||||
int cellY = cell->getCell()->getGridY();
|
||||
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellX, cellY);
|
||||
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0;
|
||||
if (data)
|
||||
{
|
||||
mPhysics->addHeightField (data->mHeights, cellX, cell->getCell()->getGridY(), worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
|
||||
mPhysics->addHeightField (data->mHeights, cellX, cellY, worldsize / (verts-1), verts, data->mMinHeight, data->mMaxHeight, land.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -292,6 +418,10 @@ namespace MWWorld
|
|||
defaultHeight.resize(verts*verts, ESM::Land::DEFAULT_HEIGHT);
|
||||
mPhysics->addHeightField (&defaultHeight[0], cell->getCell()->getGridX(), cell->getCell()->getGridY(), worldsize / (verts-1), verts, ESM::Land::DEFAULT_HEIGHT, ESM::Land::DEFAULT_HEIGHT, land.get());
|
||||
}
|
||||
|
||||
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
||||
navigator->addObject(DetourNavigator::ObjectId(heightField), *heightField->getShape(),
|
||||
heightField->getCollisionObject()->getWorldTransform());
|
||||
}
|
||||
|
||||
// register local scripts
|
||||
|
@ -313,10 +443,25 @@ namespace MWWorld
|
|||
{
|
||||
mPhysics->enableWater(waterLevel);
|
||||
mRendering.setWaterHeight(waterLevel);
|
||||
|
||||
if (cell->getCell()->isExterior())
|
||||
{
|
||||
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
|
||||
navigator->addWater(osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE,
|
||||
cell->getWaterLevel(), heightField->getCollisionObject()->getWorldTransform());
|
||||
}
|
||||
else
|
||||
{
|
||||
navigator->addWater(osg::Vec2i(cellX, cellY), std::numeric_limits<int>::max(),
|
||||
cell->getWaterLevel(), btTransform::getIdentity());
|
||||
}
|
||||
}
|
||||
else
|
||||
mPhysics->disableWater();
|
||||
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
navigator->update(player.getRefData().getPosition().asVec3());
|
||||
|
||||
if (!cell->isExterior() && !(cell->getCell()->mData.mFlags & ESM::Cell::QuasiEx))
|
||||
mRendering.configureAmbient(cell->getCell());
|
||||
}
|
||||
|
@ -337,6 +482,10 @@ namespace MWWorld
|
|||
|
||||
void Scene::playerMoved(const osg::Vec3f &pos)
|
||||
{
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
navigator->update(player.getRefData().getPosition().asVec3());
|
||||
|
||||
if (!mCurrentCell || !mCurrentCell->isExterior())
|
||||
return;
|
||||
|
||||
|
@ -355,21 +504,22 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
void Scene::changeCellGrid (int X, int Y, bool changeEvent)
|
||||
void Scene::changeCellGrid (int playerCellX, int playerCellY, bool changeEvent)
|
||||
{
|
||||
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
Loading::ScopedLoad load(loadingListener);
|
||||
|
||||
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||
std::string loadingExteriorText = "#{sLoadingMessage3}";
|
||||
loadingListener->setLabel(loadingExteriorText);
|
||||
loadingListener->setLabel(loadingExteriorText, false, messagesCount > 0);
|
||||
|
||||
CellStoreCollection::iterator active = mActiveCells.begin();
|
||||
while (active!=mActiveCells.end())
|
||||
{
|
||||
if ((*active)->getCell()->isExterior())
|
||||
{
|
||||
if (std::abs (X-(*active)->getCell()->getGridX())<=mHalfGridSize &&
|
||||
std::abs (Y-(*active)->getCell()->getGridY())<=mHalfGridSize)
|
||||
if (std::abs (playerCellX-(*active)->getCell()->getGridX())<=mHalfGridSize &&
|
||||
std::abs (playerCellY-(*active)->getCell()->getGridY())<=mHalfGridSize)
|
||||
{
|
||||
// keep cells within the new grid
|
||||
++active;
|
||||
|
@ -379,11 +529,12 @@ namespace MWWorld
|
|||
unloadCell (active++);
|
||||
}
|
||||
|
||||
int refsToLoad = 0;
|
||||
std::size_t refsToLoad = 0;
|
||||
std::vector<std::pair<int, int>> cellsPositionsToLoad;
|
||||
// get the number of refs to load
|
||||
for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x)
|
||||
for (int x = playerCellX - mHalfGridSize; x <= playerCellX + mHalfGridSize; ++x)
|
||||
{
|
||||
for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y)
|
||||
for (int y = playerCellY - mHalfGridSize; y <= playerCellY + mHalfGridSize; ++y)
|
||||
{
|
||||
CellStoreCollection::iterator iter = mActiveCells.begin();
|
||||
|
||||
|
@ -399,17 +550,36 @@ namespace MWWorld
|
|||
}
|
||||
|
||||
if (iter==mActiveCells.end())
|
||||
{
|
||||
refsToLoad += MWBase::Environment::get().getWorld()->getExterior(x, y)->count();
|
||||
cellsPositionsToLoad.push_back(std::make_pair(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadingListener->setProgressRange(refsToLoad);
|
||||
|
||||
const auto getDistanceToPlayerCell = [&] (const std::pair<int, int>& cellPosition)
|
||||
{
|
||||
return std::abs(cellPosition.first - playerCellX) + std::abs(cellPosition.second - playerCellY);
|
||||
};
|
||||
|
||||
const auto getCellPositionPriority = [&] (const std::pair<int, int>& cellPosition)
|
||||
{
|
||||
return std::make_pair(getDistanceToPlayerCell(cellPosition), getCellPositionDistanceToOrigin(cellPosition));
|
||||
};
|
||||
|
||||
std::sort(cellsPositionsToLoad.begin(), cellsPositionsToLoad.end(),
|
||||
[&] (const std::pair<int, int>& lhs, const std::pair<int, int>& rhs) {
|
||||
return getCellPositionPriority(lhs) < getCellPositionPriority(rhs);
|
||||
});
|
||||
|
||||
// Load cells
|
||||
for (int x=X-mHalfGridSize; x<=X+mHalfGridSize; ++x)
|
||||
{
|
||||
for (int y=Y-mHalfGridSize; y<=Y+mHalfGridSize; ++y)
|
||||
for (const auto& cellPosition : cellsPositionsToLoad)
|
||||
{
|
||||
const auto x = cellPosition.first;
|
||||
const auto y = cellPosition.second;
|
||||
|
||||
CellStoreCollection::iterator iter = mActiveCells.begin();
|
||||
|
||||
while (iter != mActiveCells.end())
|
||||
|
@ -430,9 +600,8 @@ namespace MWWorld
|
|||
loadCell (cell, loadingListener, changeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(X,Y);
|
||||
CellStore* current = MWBase::Environment::get().getWorld()->getExterior(playerCellX, playerCellY);
|
||||
MWBase::Environment::get().getWindowManager()->changeCell(current);
|
||||
|
||||
if (changeEvent)
|
||||
|
@ -476,8 +645,9 @@ namespace MWWorld
|
|||
mLastPlayerPos = pos.asVec3();
|
||||
}
|
||||
|
||||
Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics)
|
||||
: mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering)
|
||||
Scene::Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics,
|
||||
DetourNavigator::Navigator& navigator)
|
||||
: mCurrentCell (0), mCellChanged (false), mPhysics(physics), mRendering(rendering), mNavigator(navigator)
|
||||
, mPreloadTimer(0.f)
|
||||
, mHalfGridSize(Settings::Manager::getInt("exterior cell load distance", "Cells"))
|
||||
, mCellLoadingThreshold(1024.f)
|
||||
|
@ -526,8 +696,9 @@ namespace MWWorld
|
|||
MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5);
|
||||
|
||||
Loading::Listener* loadingListener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
int messagesCount = MWBase::Environment::get().getWindowManager()->getMessagesCount();
|
||||
std::string loadingInteriorText = "#{sLoadingMessage2}";
|
||||
loadingListener->setLabel(loadingInteriorText);
|
||||
loadingListener->setLabel(loadingInteriorText, false, messagesCount > 0);
|
||||
Loading::ScopedLoad load(loadingListener);
|
||||
|
||||
if(!loadcell)
|
||||
|
@ -553,8 +724,7 @@ namespace MWWorld
|
|||
while (active!=mActiveCells.end())
|
||||
unloadCell (active++);
|
||||
|
||||
int refsToLoad = cell->count();
|
||||
loadingListener->setProgressRange(refsToLoad);
|
||||
loadingListener->setProgressRange(cell->count());
|
||||
|
||||
// Load cell.
|
||||
loadCell (cell, loadingListener, changeEvent);
|
||||
|
@ -606,9 +776,10 @@ namespace MWWorld
|
|||
|
||||
void Scene::insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener)
|
||||
{
|
||||
InsertVisitor insertVisitor (cell, rescale, *loadingListener, *mPhysics, mRendering);
|
||||
InsertVisitor insertVisitor (cell, rescale, *loadingListener);
|
||||
cell.forEach (insertVisitor);
|
||||
insertVisitor.insert();
|
||||
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mRendering); });
|
||||
insertVisitor.insert([&] (const MWWorld::Ptr& ptr) { addObject(ptr, *mPhysics, mNavigator); });
|
||||
|
||||
// do adjustPosition (snapping actors to ground) after objects are loaded, so we don't depend on the loading order
|
||||
AdjustPositionVisitor adjustPosVisitor;
|
||||
|
@ -620,7 +791,11 @@ namespace MWWorld
|
|||
try
|
||||
{
|
||||
addObject(ptr, *mPhysics, mRendering);
|
||||
addObject(ptr, *mPhysics, mNavigator);
|
||||
MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale());
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
navigator->update(player.getRefData().getPosition().asVec3());
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -632,6 +807,20 @@ namespace MWWorld
|
|||
{
|
||||
MWBase::Environment::get().getMechanicsManager()->remove (ptr);
|
||||
MWBase::Environment::get().getSoundManager()->stopSound3D (ptr);
|
||||
const auto navigator = MWBase::Environment::get().getWorld()->getNavigator();
|
||||
if (const auto object = mPhysics->getObject(ptr))
|
||||
{
|
||||
navigator->removeObject(DetourNavigator::ObjectId(object));
|
||||
const auto player = MWBase::Environment::get().getWorld()->getPlayerPtr();
|
||||
navigator->update(player.getRefData().getPosition().asVec3());
|
||||
}
|
||||
else if (const auto actor = mPhysics->getActor(ptr))
|
||||
{
|
||||
const auto& halfExtents = ptr.getCell()->isExterior()
|
||||
? mPhysics->getHalfExtents(MWBase::Environment::get().getWorld()->getPlayerPtr())
|
||||
: actor->getHalfExtents();
|
||||
navigator->removeAgent(halfExtents);
|
||||
}
|
||||
mPhysics->remove(ptr);
|
||||
mRendering.removeObject (ptr);
|
||||
if (ptr.getClass().isActor())
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace osg
|
||||
{
|
||||
|
@ -27,6 +28,12 @@ namespace Loading
|
|||
class Listener;
|
||||
}
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
class Navigator;
|
||||
class Water;
|
||||
}
|
||||
|
||||
namespace MWRender
|
||||
{
|
||||
class SkyManager;
|
||||
|
@ -57,6 +64,7 @@ namespace MWWorld
|
|||
bool mCellChanged;
|
||||
MWPhysics::PhysicsSystem *mPhysics;
|
||||
MWRender::RenderingManager& mRendering;
|
||||
DetourNavigator::Navigator& mNavigator;
|
||||
std::unique_ptr<CellPreloader> mPreloader;
|
||||
float mPreloadTimer;
|
||||
int mHalfGridSize;
|
||||
|
@ -74,7 +82,7 @@ namespace MWWorld
|
|||
void insertCell (CellStore &cell, bool rescale, Loading::Listener* loadingListener);
|
||||
|
||||
// Load and unload cells as necessary to create a cell grid with "X" and "Y" in the center
|
||||
void changeCellGrid (int X, int Y, bool changeEvent = true);
|
||||
void changeCellGrid (int playerCellX, int playerCellY, bool changeEvent = true);
|
||||
|
||||
void getGridCenter(int& cellX, int& cellY);
|
||||
|
||||
|
@ -85,7 +93,8 @@ namespace MWWorld
|
|||
|
||||
public:
|
||||
|
||||
Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics);
|
||||
Scene (MWRender::RenderingManager& rendering, MWPhysics::PhysicsSystem *physics,
|
||||
DetourNavigator::Navigator& navigator);
|
||||
|
||||
~Scene();
|
||||
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
#include <osg/Group>
|
||||
#include <osg/ComputeBoundsVisitor>
|
||||
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
|
@ -15,10 +18,15 @@
|
|||
|
||||
#include <components/files/collections.hpp>
|
||||
|
||||
#include <components/resource/bulletshape.hpp>
|
||||
#include <components/resource/resourcesystem.hpp>
|
||||
|
||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||
|
||||
#include <components/detournavigator/debug.hpp>
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
#include <components/detournavigator/debug.hpp>
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/soundmanager.hpp"
|
||||
#include "../mwbase/mechanicsmanager.hpp"
|
||||
|
@ -47,6 +55,7 @@
|
|||
#include "../mwphysics/physicssystem.hpp"
|
||||
#include "../mwphysics/actor.hpp"
|
||||
#include "../mwphysics/collisiontype.hpp"
|
||||
#include "../mwphysics/object.hpp"
|
||||
|
||||
#include "player.hpp"
|
||||
#include "manualref.hpp"
|
||||
|
@ -159,12 +168,6 @@ namespace MWWorld
|
|||
mLevitationEnabled(true), mGoToJail(false), mDaysInPrison(0),
|
||||
mPlayerTraveling(false), mPlayerInJail(false), mSpellPreloadTimer(0.f)
|
||||
{
|
||||
mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode));
|
||||
mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath));
|
||||
mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get()));
|
||||
|
||||
mRendering->preloadCommonAssets();
|
||||
|
||||
mEsm.resize(contentFiles.size());
|
||||
Loading::Listener* listener = MWBase::Environment::get().getWindowManager()->getLoadingScreen();
|
||||
listener->loadingOn();
|
||||
|
@ -193,9 +196,49 @@ namespace MWWorld
|
|||
|
||||
mSwimHeightScale = mStore.get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||
|
||||
mPhysics.reset(new MWPhysics::PhysicsSystem(resourceSystem, rootNode));
|
||||
|
||||
DetourNavigator::Settings navigatorSettings;
|
||||
navigatorSettings.mBorderSize = Settings::Manager::getInt("border size", "Navigator");
|
||||
navigatorSettings.mCellHeight = Settings::Manager::getFloat("cell height", "Navigator");
|
||||
navigatorSettings.mCellSize = Settings::Manager::getFloat("cell size", "Navigator");
|
||||
navigatorSettings.mDetailSampleDist = Settings::Manager::getFloat("detail sample dist", "Navigator");
|
||||
navigatorSettings.mDetailSampleMaxError = Settings::Manager::getFloat("detail sample max error", "Navigator");
|
||||
navigatorSettings.mMaxClimb = MWPhysics::sStepSizeUp;
|
||||
navigatorSettings.mMaxSimplificationError = Settings::Manager::getFloat("max simplification error", "Navigator");
|
||||
navigatorSettings.mMaxSlope = MWPhysics::sMaxSlope;
|
||||
navigatorSettings.mRecastScaleFactor = Settings::Manager::getFloat("recast scale factor", "Navigator");
|
||||
navigatorSettings.mSwimHeightScale = mSwimHeightScale;
|
||||
navigatorSettings.mMaxEdgeLen = Settings::Manager::getInt("max edge len", "Navigator");
|
||||
navigatorSettings.mMaxNavMeshQueryNodes = Settings::Manager::getInt("max nav mesh query nodes", "Navigator");
|
||||
navigatorSettings.mMaxPolys = Settings::Manager::getInt("max polygons per tile", "Navigator");
|
||||
navigatorSettings.mMaxVertsPerPoly = Settings::Manager::getInt("max verts per poly", "Navigator");
|
||||
navigatorSettings.mRegionMergeSize = Settings::Manager::getInt("region merge size", "Navigator");
|
||||
navigatorSettings.mRegionMinSize = Settings::Manager::getInt("region min size", "Navigator");
|
||||
navigatorSettings.mTileSize = Settings::Manager::getInt("tile size", "Navigator");
|
||||
navigatorSettings.mAsyncNavMeshUpdaterThreads = static_cast<std::size_t>(Settings::Manager::getInt("async nav mesh updater threads", "Navigator"));
|
||||
navigatorSettings.mMaxNavMeshTilesCacheSize = static_cast<std::size_t>(Settings::Manager::getInt("max nav mesh tiles cache size", "Navigator"));
|
||||
navigatorSettings.mMaxPolygonPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max polygon path size", "Navigator"));
|
||||
navigatorSettings.mMaxSmoothPathSize = static_cast<std::size_t>(Settings::Manager::getInt("max smooth path size", "Navigator"));
|
||||
navigatorSettings.mTrianglesPerChunk = static_cast<std::size_t>(Settings::Manager::getInt("triangles per chunk", "Navigator"));
|
||||
navigatorSettings.mEnableWriteRecastMeshToFile = Settings::Manager::getBool("enable write recast mesh to file", "Navigator");
|
||||
navigatorSettings.mEnableWriteNavMeshToFile = Settings::Manager::getBool("enable write nav mesh to file", "Navigator");
|
||||
navigatorSettings.mRecastMeshPathPrefix = Settings::Manager::getString("recast mesh path prefix", "Navigator");
|
||||
navigatorSettings.mNavMeshPathPrefix = Settings::Manager::getString("nav mesh path prefix", "Navigator");
|
||||
navigatorSettings.mEnableRecastMeshFileNameRevision = Settings::Manager::getBool("enable recast mesh file name revision", "Navigator");
|
||||
navigatorSettings.mEnableNavMeshFileNameRevision = Settings::Manager::getBool("enable nav mesh file name revision", "Navigator");
|
||||
if (Settings::Manager::getBool("enable log", "Navigator"))
|
||||
DetourNavigator::Log::instance().setSink(std::unique_ptr<DetourNavigator::FileSink>(
|
||||
new DetourNavigator::FileSink(Settings::Manager::getString("log path", "Navigator"))));
|
||||
mNavigator.reset(new DetourNavigator::Navigator(navigatorSettings));
|
||||
|
||||
mRendering.reset(new MWRender::RenderingManager(viewer, rootNode, resourceSystem, workQueue, &mFallback, resourcePath, *mNavigator));
|
||||
mProjectileManager.reset(new ProjectileManager(mRendering->getLightRoot(), resourceSystem, mRendering.get(), mPhysics.get()));
|
||||
mRendering->preloadCommonAssets();
|
||||
|
||||
mWeatherManager.reset(new MWWorld::WeatherManager(*mRendering, mFallback, mStore));
|
||||
|
||||
mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get()));
|
||||
mWorldScene.reset(new Scene(*mRendering.get(), mPhysics.get(), *mNavigator));
|
||||
}
|
||||
|
||||
void World::fillGlobalVariables()
|
||||
|
@ -1506,6 +1549,32 @@ namespace MWWorld
|
|||
moveObjectImp(player->first, player->second.x(), player->second.y(), player->second.z(), false);
|
||||
}
|
||||
|
||||
void World::updateNavigator()
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
mPhysics->forEachAnimatedObject([&] (const MWPhysics::Object* object)
|
||||
{
|
||||
updated = updateNavigatorObject(object) || updated;
|
||||
});
|
||||
|
||||
for (const auto& door : mDoorStates)
|
||||
if (const auto object = mPhysics->getObject(door.first))
|
||||
updated = updateNavigatorObject(object) || updated;
|
||||
|
||||
if (updated)
|
||||
mNavigator->update(getPlayerPtr().getRefData().getPosition().asVec3());
|
||||
}
|
||||
|
||||
bool World::updateNavigatorObject(const MWPhysics::Object* object)
|
||||
{
|
||||
const DetourNavigator::ObjectShapes shapes {
|
||||
*object->getShapeInstance()->getCollisionShape(),
|
||||
object->getShapeInstance()->getAvoidCollisionShape()
|
||||
};
|
||||
return mNavigator->updateObject(DetourNavigator::ObjectId(object), shapes, object->getCollisionObject()->getWorldTransform());
|
||||
}
|
||||
|
||||
bool World::castRay (float x1, float y1, float z1, float x2, float y2, float z2)
|
||||
{
|
||||
int mask = MWPhysics::CollisionType_World | MWPhysics::CollisionType_Door;
|
||||
|
@ -1583,6 +1652,16 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
void World::setActorCollisionMode(const MWWorld::Ptr& ptr, bool enabled)
|
||||
{
|
||||
mPhysics->setActorCollisionMode(ptr, enabled);
|
||||
}
|
||||
|
||||
bool World::isActorCollisionEnabled(const MWWorld::Ptr& ptr)
|
||||
{
|
||||
return mPhysics->isActorCollisionEnabled(ptr);
|
||||
}
|
||||
|
||||
bool World::toggleCollisionMode()
|
||||
{
|
||||
if (mPhysics->toggleCollisionMode())
|
||||
|
@ -1698,7 +1777,10 @@ namespace MWWorld
|
|||
updateWeather(duration, paused);
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
doPhysics (duration);
|
||||
updateNavigator();
|
||||
}
|
||||
|
||||
updatePlayer();
|
||||
|
||||
|
@ -2295,6 +2377,7 @@ namespace MWWorld
|
|||
{
|
||||
// Remove the old CharacterController
|
||||
MWBase::Environment::get().getMechanicsManager()->remove(getPlayerPtr());
|
||||
mNavigator->removeAgent(mPhysics->getHalfExtents(getPlayerPtr()));
|
||||
mPhysics->remove(getPlayerPtr());
|
||||
mRendering->removePlayer(getPlayerPtr());
|
||||
|
||||
|
@ -2329,6 +2412,8 @@ namespace MWWorld
|
|||
mPhysics->addActor(getPlayerPtr(), model);
|
||||
|
||||
applyLoopingParticles(player);
|
||||
|
||||
mNavigator->addAgent(mPhysics->getHalfExtents(getPlayerPtr()));
|
||||
}
|
||||
|
||||
World::RestPermitted World::canRest () const
|
||||
|
@ -3560,8 +3645,14 @@ namespace MWWorld
|
|||
MWBase::Environment::get().getMechanicsManager()->getObjectsInRange(
|
||||
origin, feetToGameUnits(static_cast<float>(effectIt->mArea)), objects);
|
||||
for (std::vector<MWWorld::Ptr>::iterator affected = objects.begin(); affected != objects.end(); ++affected)
|
||||
{
|
||||
// Ignore actors without collisions here, otherwise it will be possible to hit actors outside processing range.
|
||||
if (affected->getClass().isActor() && !isActorCollisionEnabled(*affected))
|
||||
continue;
|
||||
|
||||
toApply[*affected].push_back(*effectIt);
|
||||
}
|
||||
}
|
||||
|
||||
// Now apply the appropriate effects to each actor in range
|
||||
for (std::map<MWWorld::Ptr, std::vector<ESM::ENAMstruct> >::iterator apply = toApply.begin(); apply != toApply.end(); ++apply)
|
||||
|
@ -3688,4 +3779,20 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
DetourNavigator::Navigator* World::getNavigator() const
|
||||
{
|
||||
return mNavigator.get();
|
||||
}
|
||||
|
||||
void World::updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const
|
||||
{
|
||||
mRendering->updateActorPath(actor, path, halfExtents, start, end);
|
||||
}
|
||||
|
||||
void World::setNavMeshNumberToRender(const std::size_t value)
|
||||
{
|
||||
mRendering->setNavMeshNumber(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -61,6 +61,11 @@ namespace ToUTF8
|
|||
|
||||
struct ContentLoader;
|
||||
|
||||
namespace MWPhysics
|
||||
{
|
||||
class Object;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class WeatherManager;
|
||||
|
@ -94,6 +99,7 @@ namespace MWWorld
|
|||
|
||||
std::unique_ptr<MWWorld::Player> mPlayer;
|
||||
std::unique_ptr<MWPhysics::PhysicsSystem> mPhysics;
|
||||
std::unique_ptr<DetourNavigator::Navigator> mNavigator;
|
||||
std::unique_ptr<MWRender::RenderingManager> mRendering;
|
||||
std::unique_ptr<MWWorld::Scene> mWorldScene;
|
||||
std::unique_ptr<MWWorld::WeatherManager> mWeatherManager;
|
||||
|
@ -147,6 +153,10 @@ namespace MWWorld
|
|||
void doPhysics(float duration);
|
||||
///< Run physics simulation and modify \a world accordingly.
|
||||
|
||||
void updateNavigator();
|
||||
|
||||
bool updateNavigatorObject(const MWPhysics::Object* object);
|
||||
|
||||
void ensureNeededRecords();
|
||||
|
||||
void fillGlobalVariables();
|
||||
|
@ -407,6 +417,9 @@ namespace MWWorld
|
|||
|
||||
bool castRay (float x1, float y1, float z1, float x2, float y2, float z2) override;
|
||||
|
||||
void setActorCollisionMode(const Ptr& ptr, bool enabled) override;
|
||||
bool isActorCollisionEnabled(const Ptr& ptr) override;
|
||||
|
||||
bool toggleCollisionMode() override;
|
||||
///< Toggle collision mode for player. If disabled player object should ignore
|
||||
/// collisions and gravity.
|
||||
|
@ -690,6 +703,13 @@ namespace MWWorld
|
|||
|
||||
/// Preload VFX associated with this effect list
|
||||
void preloadEffects(const ESM::EffectList* effectList) override;
|
||||
|
||||
DetourNavigator::Navigator* getNavigator() const override;
|
||||
|
||||
void updateActorPath(const MWWorld::ConstPtr& actor, const std::deque<osg::Vec3f>& path,
|
||||
const osg::Vec3f& halfExtents, const osg::Vec3f& start, const osg::Vec3f& end) const override;
|
||||
|
||||
void setNavMeshNumberToRender(const std::size_t value) override;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,10 +17,21 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
|||
misc/test_stringops.cpp
|
||||
|
||||
nifloader/testbulletnifloader.cpp
|
||||
|
||||
detournavigator/navigator.cpp
|
||||
detournavigator/settingsutils.cpp
|
||||
detournavigator/recastmeshbuilder.cpp
|
||||
detournavigator/gettilespositions.cpp
|
||||
detournavigator/recastmeshobject.cpp
|
||||
detournavigator/navmeshtilescache.cpp
|
||||
)
|
||||
|
||||
source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
||||
find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED)
|
||||
|
||||
include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS})
|
||||
|
||||
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
|
||||
|
||||
target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components)
|
||||
|
|
104
apps/openmw_test_suite/detournavigator/gettilespositions.cpp
Normal file
104
apps/openmw_test_suite/detournavigator/gettilespositions.cpp
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include <components/detournavigator/gettilespositions.hpp>
|
||||
#include <components/detournavigator/debug.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
|
||||
struct CollectTilesPositions
|
||||
{
|
||||
std::vector<TilePosition>& mTilesPositions;
|
||||
|
||||
void operator ()(const TilePosition& value)
|
||||
{
|
||||
mTilesPositions.push_back(value);
|
||||
}
|
||||
};
|
||||
|
||||
struct DetourNavigatorGetTilesPositionsTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
std::vector<TilePosition> mTilesPositions;
|
||||
CollectTilesPositions mCollect {mTilesPositions};
|
||||
|
||||
DetourNavigatorGetTilesPositionsTest()
|
||||
{
|
||||
mSettings.mBorderSize = 0;
|
||||
mSettings.mCellSize = 0.5;
|
||||
mSettings.mRecastScaleFactor = 1;
|
||||
mSettings.mTileSize = 64;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_in_single_tile_should_return_one_tile)
|
||||
{
|
||||
getTilesPositions(osg::Vec3f(2, 2, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_x_bounds_in_two_tiles_should_return_two_tiles)
|
||||
{
|
||||
getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 31, 1), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(1, 0)));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, for_object_with_y_bounds_in_two_tiles_should_return_two_tiles)
|
||||
{
|
||||
getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 32, 1), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0), TilePosition(0, 1)));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_works_only_for_x_and_y_coordinates)
|
||||
{
|
||||
getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31, 31, 32), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, tiling_should_work_with_negative_coordinates)
|
||||
{
|
||||
getTilesPositions(osg::Vec3f(-31, -31, 0), osg::Vec3f(31, 31, 1), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(
|
||||
TilePosition(-1, -1),
|
||||
TilePosition(-1, 0),
|
||||
TilePosition(0, -1),
|
||||
TilePosition(0, 0)
|
||||
));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, border_size_should_extend_tile_bounds)
|
||||
{
|
||||
mSettings.mBorderSize = 1;
|
||||
|
||||
getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(31.5, 31.5, 1), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(
|
||||
TilePosition(-1, -1),
|
||||
TilePosition(-1, 0),
|
||||
TilePosition(-1, 1),
|
||||
TilePosition(0, -1),
|
||||
TilePosition(0, 0),
|
||||
TilePosition(0, 1),
|
||||
TilePosition(1, -1),
|
||||
TilePosition(1, 0),
|
||||
TilePosition(1, 1)
|
||||
));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilesPositionsTest, should_apply_recast_scale_factor)
|
||||
{
|
||||
mSettings.mRecastScaleFactor = 0.5;
|
||||
|
||||
getTilesPositions(osg::Vec3f(0, 0, 0), osg::Vec3f(32, 32, 1), mSettings, mCollect);
|
||||
|
||||
EXPECT_THAT(mTilesPositions, ElementsAre(TilePosition(0, 0)));
|
||||
}
|
||||
}
|
661
apps/openmw_test_suite/detournavigator/navigator.cpp
Normal file
661
apps/openmw_test_suite/detournavigator/navigator.cpp
Normal file
|
@ -0,0 +1,661 @@
|
|||
#include "operators.hpp"
|
||||
|
||||
#include <components/detournavigator/navigator.hpp>
|
||||
#include <components/detournavigator/exceptions.hpp>
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <iterator>
|
||||
#include <deque>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
|
||||
struct DetourNavigatorNavigatorTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
std::unique_ptr<Navigator> mNavigator;
|
||||
osg::Vec3f mPlayerPosition;
|
||||
osg::Vec3f mAgentHalfExtents;
|
||||
osg::Vec3f mStart;
|
||||
osg::Vec3f mEnd;
|
||||
std::deque<osg::Vec3f> mPath;
|
||||
std::back_insert_iterator<std::deque<osg::Vec3f>> mOut;
|
||||
|
||||
DetourNavigatorNavigatorTest()
|
||||
: mPlayerPosition(0, 0, 0)
|
||||
, mAgentHalfExtents(29, 29, 66)
|
||||
, mStart(-215, 215, 1)
|
||||
, mEnd(215, -215, 1)
|
||||
, mOut(mPath)
|
||||
{
|
||||
mSettings.mEnableWriteRecastMeshToFile = false;
|
||||
mSettings.mEnableWriteNavMeshToFile = false;
|
||||
mSettings.mEnableRecastMeshFileNameRevision = false;
|
||||
mSettings.mEnableNavMeshFileNameRevision = false;
|
||||
mSettings.mBorderSize = 16;
|
||||
mSettings.mCellHeight = 0.2f;
|
||||
mSettings.mCellSize = 0.2f;
|
||||
mSettings.mDetailSampleDist = 6;
|
||||
mSettings.mDetailSampleMaxError = 1;
|
||||
mSettings.mMaxClimb = 34;
|
||||
mSettings.mMaxSimplificationError = 1.3f;
|
||||
mSettings.mMaxSlope = 49;
|
||||
mSettings.mRecastScaleFactor = 0.017647058823529415f;
|
||||
mSettings.mSwimHeightScale = 0.89999997615814208984375f;
|
||||
mSettings.mMaxEdgeLen = 12;
|
||||
mSettings.mMaxNavMeshQueryNodes = 2048;
|
||||
mSettings.mMaxVertsPerPoly = 6;
|
||||
mSettings.mRegionMergeSize = 20;
|
||||
mSettings.mRegionMinSize = 8;
|
||||
mSettings.mTileSize = 64;
|
||||
mSettings.mAsyncNavMeshUpdaterThreads = 1;
|
||||
mSettings.mMaxNavMeshTilesCacheSize = 1024 * 1024;
|
||||
mSettings.mMaxPolygonPathSize = 1024;
|
||||
mSettings.mMaxSmoothPathSize = 1024;
|
||||
mSettings.mTrianglesPerChunk = 256;
|
||||
mSettings.mMaxPolys = 4096;
|
||||
mNavigator.reset(new Navigator(mSettings));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_throw_exception)
|
||||
{
|
||||
EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_existing_agent_with_no_navmesh_should_throw_exception)
|
||||
{
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_removed_agent_should_throw_exception)
|
||||
{
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->removeAgent(mAgentHalfExtents);
|
||||
EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), InvalidArgument);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, add_agent_should_count_each_agent)
|
||||
{
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->removeAgent(mAgentHalfExtents);
|
||||
EXPECT_THROW(mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut), NavigatorException);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, update_then_find_path_should_return_path)
|
||||
{
|
||||
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.85963428020477294921875),
|
||||
osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125),
|
||||
osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125),
|
||||
osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375),
|
||||
osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625),
|
||||
osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875),
|
||||
osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375),
|
||||
osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375),
|
||||
osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625),
|
||||
osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375),
|
||||
osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875),
|
||||
osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125),
|
||||
osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625),
|
||||
osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875),
|
||||
osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375),
|
||||
osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125),
|
||||
osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125),
|
||||
osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625),
|
||||
osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625),
|
||||
osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875),
|
||||
osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625),
|
||||
osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625),
|
||||
osg::Vec3f(215, -215, 1.877177715301513671875),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, add_object_should_change_navmesh)
|
||||
{
|
||||
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
btBoxShape boxShape(btVector3(20, 20, 100));
|
||||
btCompoundShape compoundShape;
|
||||
compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape);
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath));
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.85963428020477294921875),
|
||||
osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125),
|
||||
osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125),
|
||||
osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375),
|
||||
osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625),
|
||||
osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875),
|
||||
osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375),
|
||||
osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375),
|
||||
osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625),
|
||||
osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375),
|
||||
osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875),
|
||||
osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125),
|
||||
osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625),
|
||||
osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875),
|
||||
osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375),
|
||||
osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125),
|
||||
osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125),
|
||||
osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625),
|
||||
osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625),
|
||||
osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875),
|
||||
osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625),
|
||||
osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625),
|
||||
osg::Vec3f(215, -215, 1.877177715301513671875),
|
||||
})) << mPath;
|
||||
|
||||
mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mPath.clear();
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath));
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.87827122211456298828125),
|
||||
osg::Vec3f(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375),
|
||||
osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847270965576171875),
|
||||
osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.40817737579345703125),
|
||||
osg::Vec3f(-154.1872711181640625, 119.36397552490234375, -19.837890625),
|
||||
osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675952911376953125),
|
||||
osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6972980499267578125),
|
||||
osg::Vec3f(-108.57772064208984375, 47.636936187744140625, -36.12701416015625),
|
||||
osg::Vec3f(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125),
|
||||
osg::Vec3f(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625),
|
||||
osg::Vec3f(-62.968158721923828125, -24.0900936126708984375, -33.50289154052734375),
|
||||
osg::Vec3f(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625),
|
||||
osg::Vec3f(-23.852447509765625, -63.196765899658203125, -33.97112274169921875),
|
||||
osg::Vec3f(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875),
|
||||
osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.7740936279296875),
|
||||
osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.051288604736328125),
|
||||
osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.62355804443359375),
|
||||
osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958160400390625),
|
||||
osg::Vec3f(119.6226959228515625, -154.382720947265625, -19.7680912017822265625),
|
||||
osg::Vec3f(143.53521728515625, -169.58038330078125, -14.3403491973876953125),
|
||||
osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.91261768341064453125),
|
||||
osg::Vec3f(191.360260009765625, -199.9757080078125, -3.484879016876220703125),
|
||||
osg::Vec3f(215, -215, 1.87827455997467041015625),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, update_changed_object_should_change_navmesh)
|
||||
{
|
||||
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
btBoxShape boxShape(btVector3(20, 20, 100));
|
||||
btCompoundShape compoundShape;
|
||||
compoundShape.addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(0, 0, 0)), &boxShape);
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&heightfieldShape), heightfieldShape, btTransform::getIdentity());
|
||||
mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, std::back_inserter(mPath));
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.87827122211456298828125),
|
||||
osg::Vec3f(-199.7968292236328125, 191.09100341796875, -3.54876613616943359375),
|
||||
osg::Vec3f(-184.5936431884765625, 167.1819915771484375, -8.97847270965576171875),
|
||||
osg::Vec3f(-169.3904571533203125, 143.2729949951171875, -14.40817737579345703125),
|
||||
osg::Vec3f(-154.1872711181640625, 119.36397552490234375, -19.837890625),
|
||||
osg::Vec3f(-138.9840850830078125, 95.45496368408203125, -25.2675952911376953125),
|
||||
osg::Vec3f(-123.78090667724609375, 71.54595184326171875, -30.6972980499267578125),
|
||||
osg::Vec3f(-108.57772064208984375, 47.636936187744140625, -36.12701416015625),
|
||||
osg::Vec3f(-93.3745269775390625, 23.7279262542724609375, -40.754688262939453125),
|
||||
osg::Vec3f(-78.17134857177734375, -0.18108306825160980224609375, -37.128787994384765625),
|
||||
osg::Vec3f(-62.968158721923828125, -24.0900936126708984375, -33.50289154052734375),
|
||||
osg::Vec3f(-47.764972686767578125, -47.999103546142578125, -30.797946929931640625),
|
||||
osg::Vec3f(-23.852447509765625, -63.196765899658203125, -33.97112274169921875),
|
||||
osg::Vec3f(0.0600789971649646759033203125, -78.39443206787109375, -37.14543914794921875),
|
||||
osg::Vec3f(23.97260284423828125, -93.5920867919921875, -40.7740936279296875),
|
||||
osg::Vec3f(47.885128021240234375, -108.78974151611328125, -36.051288604736328125),
|
||||
osg::Vec3f(71.7976531982421875, -123.98740386962890625, -30.62355804443359375),
|
||||
osg::Vec3f(95.71018218994140625, -139.18505859375, -25.1958160400390625),
|
||||
osg::Vec3f(119.6226959228515625, -154.382720947265625, -19.7680912017822265625),
|
||||
osg::Vec3f(143.53521728515625, -169.58038330078125, -14.3403491973876953125),
|
||||
osg::Vec3f(167.4477386474609375, -184.778045654296875, -8.91261768341064453125),
|
||||
osg::Vec3f(191.360260009765625, -199.9757080078125, -3.484879016876220703125),
|
||||
osg::Vec3f(215, -215, 1.87827455997467041015625),
|
||||
})) << mPath;
|
||||
|
||||
compoundShape.updateChildTransform(0, btTransform(btMatrix3x3::getIdentity(), btVector3(1000, 0, 0)));
|
||||
|
||||
mNavigator->updateObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mPath.clear();
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.85963428020477294921875),
|
||||
osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125),
|
||||
osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125),
|
||||
osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375),
|
||||
osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625),
|
||||
osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875),
|
||||
osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375),
|
||||
osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375),
|
||||
osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625),
|
||||
osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375),
|
||||
osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875),
|
||||
osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125),
|
||||
osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625),
|
||||
osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875),
|
||||
osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375),
|
||||
osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125),
|
||||
osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125),
|
||||
osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625),
|
||||
osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625),
|
||||
osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875),
|
||||
osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625),
|
||||
osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625),
|
||||
osg::Vec3f(215, -215, 1.877177715301513671875),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, for_overlapping_heightfields_should_use_higher)
|
||||
{
|
||||
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
const std::array<btScalar, 5 * 5> heightfieldData2 {{
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape2(5, 5, heightfieldData2.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape2.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->addObject(ObjectId(&shape2), shape2, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.96328866481781005859375),
|
||||
osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -0.2422157227993011474609375),
|
||||
osg::Vec3f(-174.930633544921875, 174.930633544921875, -2.44772052764892578125),
|
||||
osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -4.653223514556884765625),
|
||||
osg::Vec3f(-134.86126708984375, 134.86126708984375, -6.858728885650634765625),
|
||||
osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -9.0642337799072265625),
|
||||
osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -11.26973724365234375),
|
||||
osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -13.26497173309326171875),
|
||||
osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -15.24860286712646484375),
|
||||
osg::Vec3f(-34.68780517578125, 34.68780517578125, -17.2322368621826171875),
|
||||
osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -19.2158660888671875),
|
||||
osg::Vec3f(5.3815765380859375, -5.3815765380859375, -20.1338443756103515625),
|
||||
osg::Vec3f(25.41626739501953125, -25.41626739501953125, -18.150211334228515625),
|
||||
osg::Vec3f(45.450958251953125, -45.450958251953125, -16.1665802001953125),
|
||||
osg::Vec3f(65.48564910888671875, -65.48564910888671875, -14.18294811248779296875),
|
||||
osg::Vec3f(85.5203399658203125, -85.5203399658203125, -12.19931507110595703125),
|
||||
osg::Vec3f(105.55503082275390625, -105.55503082275390625, -10.08488559722900390625),
|
||||
osg::Vec3f(125.5897216796875, -125.5897216796875, -7.879383563995361328125),
|
||||
osg::Vec3f(145.6244049072265625, -145.6244049072265625, -5.673877239227294921875),
|
||||
osg::Vec3f(165.659088134765625, -165.659088134765625, -3.4683735370635986328125),
|
||||
osg::Vec3f(185.6937713623046875, -185.6937713623046875, -1.2628715038299560546875),
|
||||
osg::Vec3f(205.7284698486328125, -205.7284698486328125, 0.9426348209381103515625),
|
||||
osg::Vec3f(215, -215, 1.96328866481781005859375),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, path_should_be_around_avoid_shape)
|
||||
{
|
||||
std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
std::array<btScalar, 5 * 5> heightfieldDataAvoid {{
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
-25, -25, -25, -25, -25,
|
||||
}};
|
||||
btHeightfieldTerrainShape shapeAvoid(5, 5, heightfieldDataAvoid.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shapeAvoid.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&shape), ObjectShapes {shape, &shapeAvoid}, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.9393787384033203125),
|
||||
osg::Vec3f(-200.8159637451171875, 190.47265625, -0.639537751674652099609375),
|
||||
osg::Vec3f(-186.6319427490234375, 165.9453125, -3.2184507846832275390625),
|
||||
osg::Vec3f(-172.447906494140625, 141.41796875, -5.797363758087158203125),
|
||||
osg::Vec3f(-158.263885498046875, 116.8906097412109375, -8.37627887725830078125),
|
||||
osg::Vec3f(-144.079864501953125, 92.3632659912109375, -10.95519161224365234375),
|
||||
osg::Vec3f(-129.89581298828125, 67.83591461181640625, -13.534107208251953125),
|
||||
osg::Vec3f(-115.7117919921875, 43.308563232421875, -16.1130199432373046875),
|
||||
osg::Vec3f(-101.5277557373046875, 18.7812137603759765625, -18.6919345855712890625),
|
||||
osg::Vec3f(-87.34372711181640625, -5.7461376190185546875, -20.4680538177490234375),
|
||||
osg::Vec3f(-67.02922821044921875, -25.4970550537109375, -20.514247894287109375),
|
||||
osg::Vec3f(-46.714717864990234375, -45.2479705810546875, -20.5604457855224609375),
|
||||
osg::Vec3f(-26.40021514892578125, -64.99889373779296875, -20.6066417694091796875),
|
||||
osg::Vec3f(-6.085712432861328125, -84.74980926513671875, -20.652835845947265625),
|
||||
osg::Vec3f(14.22879505157470703125, -104.50072479248046875, -18.151393890380859375),
|
||||
osg::Vec3f(39.05098724365234375, -118.16222381591796875, -15.6674861907958984375),
|
||||
osg::Vec3f(63.87317657470703125, -131.82373046875, -13.18357944488525390625),
|
||||
osg::Vec3f(88.69537353515625, -145.4852142333984375, -10.69967365264892578125),
|
||||
osg::Vec3f(113.51757049560546875, -159.146697998046875, -8.21576690673828125),
|
||||
osg::Vec3f(138.3397674560546875, -172.808197021484375, -5.731858730316162109375),
|
||||
osg::Vec3f(163.1619720458984375, -186.469696044921875, -3.2479503154754638671875),
|
||||
osg::Vec3f(187.984161376953125, -200.1311798095703125, -0.764044582843780517578125),
|
||||
osg::Vec3f(212.8063507080078125, -213.7926788330078125, 1.7198636531829833984375),
|
||||
osg::Vec3f(215, -215, 1.93937528133392333984375),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_ground_lower_than_water_with_only_swim_flag)
|
||||
{
|
||||
std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
-50, -50, -50, -50, 0,
|
||||
-50, -100, -150, -100, -50,
|
||||
-50, -150, -200, -150, -100,
|
||||
-50, -100, -150, -100, -100,
|
||||
0, -50, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, 300, btTransform::getIdentity());
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mStart.x() = 0;
|
||||
mStart.z() = 300;
|
||||
mEnd.x() = 0;
|
||||
mEnd.z() = 300;
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(0, 215, 185.33331298828125),
|
||||
osg::Vec3f(0, 186.6666717529296875, 185.33331298828125),
|
||||
osg::Vec3f(0, 158.333343505859375, 185.33331298828125),
|
||||
osg::Vec3f(0, 130.0000152587890625, 185.33331298828125),
|
||||
osg::Vec3f(0, 101.66667938232421875, 185.33331298828125),
|
||||
osg::Vec3f(0, 73.333343505859375, 185.33331298828125),
|
||||
osg::Vec3f(0, 45.0000152587890625, 185.33331298828125),
|
||||
osg::Vec3f(0, 16.6666812896728515625, 185.33331298828125),
|
||||
osg::Vec3f(0, -11.66664981842041015625, 185.33331298828125),
|
||||
osg::Vec3f(0, -39.999980926513671875, 185.33331298828125),
|
||||
osg::Vec3f(0, -68.33331298828125, 185.33331298828125),
|
||||
osg::Vec3f(0, -96.66664886474609375, 185.33331298828125),
|
||||
osg::Vec3f(0, -124.99997711181640625, 185.33331298828125),
|
||||
osg::Vec3f(0, -153.33331298828125, 185.33331298828125),
|
||||
osg::Vec3f(0, -181.6666412353515625, 185.33331298828125),
|
||||
osg::Vec3f(0, -209.999969482421875, 185.33331298828125),
|
||||
osg::Vec3f(0, -215, 185.33331298828125),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_swim_and_walk_flags)
|
||||
{
|
||||
std::array<btScalar, 7 * 7> heightfieldData {{
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
0, -100, -100, -100, -100, -100, 0,
|
||||
0, -100, -150, -150, -150, -100, 0,
|
||||
0, -100, -150, -200, -150, -100, 0,
|
||||
0, -100, -150, -150, -150, -100, 0,
|
||||
0, -100, -100, -100, -100, -100, 0,
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity());
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mStart.x() = 0;
|
||||
mEnd.x() = 0;
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim | Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(0, 215, -94.75363922119140625),
|
||||
osg::Vec3f(0, 186.6666717529296875, -106.0000152587890625),
|
||||
osg::Vec3f(0, 158.333343505859375, -115.85507965087890625),
|
||||
osg::Vec3f(0, 130.0000152587890625, -125.71016693115234375),
|
||||
osg::Vec3f(0, 101.66667938232421875, -135.5652313232421875),
|
||||
osg::Vec3f(0, 73.333343505859375, -143.3333587646484375),
|
||||
osg::Vec3f(0, 45.0000152587890625, -143.3333587646484375),
|
||||
osg::Vec3f(0, 16.6666812896728515625, -143.3333587646484375),
|
||||
osg::Vec3f(0, -11.66664981842041015625, -143.3333587646484375),
|
||||
osg::Vec3f(0, -39.999980926513671875, -143.3333587646484375),
|
||||
osg::Vec3f(0, -68.33331298828125, -143.3333587646484375),
|
||||
osg::Vec3f(0, -96.66664886474609375, -137.3043670654296875),
|
||||
osg::Vec3f(0, -124.99997711181640625, -127.44930267333984375),
|
||||
osg::Vec3f(0, -153.33331298828125, -117.5942230224609375),
|
||||
osg::Vec3f(0, -181.6666412353515625, -107.7391510009765625),
|
||||
osg::Vec3f(0, -209.999969482421875, -97.79712677001953125),
|
||||
osg::Vec3f(0, -215, -94.753631591796875),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_water_when_ground_cross_water_with_max_int_cells_size_and_swim_and_walk_flags)
|
||||
{
|
||||
std::array<btScalar, 7 * 7> heightfieldData {{
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
0, -100, -100, -100, -100, -100, 0,
|
||||
0, -100, -150, -150, -150, -100, 0,
|
||||
0, -100, -150, -200, -150, -100, 0,
|
||||
0, -100, -150, -150, -150, -100, 0,
|
||||
0, -100, -100, -100, -100, -100, 0,
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->addWater(osg::Vec2i(0, 0), std::numeric_limits<int>::max(), -25, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mStart.x() = 0;
|
||||
mEnd.x() = 0;
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_swim | Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(0, 215, -94.75363922119140625),
|
||||
osg::Vec3f(0, 186.6666717529296875, -106.0000152587890625),
|
||||
osg::Vec3f(0, 158.333343505859375, -115.85507965087890625),
|
||||
osg::Vec3f(0, 130.0000152587890625, -125.71016693115234375),
|
||||
osg::Vec3f(0, 101.66667938232421875, -135.5652313232421875),
|
||||
osg::Vec3f(0, 73.333343505859375, -143.3333587646484375),
|
||||
osg::Vec3f(0, 45.0000152587890625, -143.3333587646484375),
|
||||
osg::Vec3f(0, 16.6666812896728515625, -143.3333587646484375),
|
||||
osg::Vec3f(0, -11.66664981842041015625, -143.3333587646484375),
|
||||
osg::Vec3f(0, -39.999980926513671875, -143.3333587646484375),
|
||||
osg::Vec3f(0, -68.33331298828125, -143.3333587646484375),
|
||||
osg::Vec3f(0, -96.66664886474609375, -137.3043670654296875),
|
||||
osg::Vec3f(0, -124.99997711181640625, -127.44930267333984375),
|
||||
osg::Vec3f(0, -153.33331298828125, -117.5942230224609375),
|
||||
osg::Vec3f(0, -181.6666412353515625, -107.7391510009765625),
|
||||
osg::Vec3f(0, -209.999969482421875, -97.79712677001953125),
|
||||
osg::Vec3f(0, -215, -94.753631591796875),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, path_should_be_over_ground_when_ground_cross_water_with_only_walk_flag)
|
||||
{
|
||||
std::array<btScalar, 7 * 7> heightfieldData {{
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
0, -100, -100, -100, -100, -100, 0,
|
||||
0, -100, -150, -150, -150, -100, 0,
|
||||
0, -100, -150, -200, -150, -100, 0,
|
||||
0, -100, -150, -150, -150, -100, 0,
|
||||
0, -100, -100, -100, -100, -100, 0,
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addWater(osg::Vec2i(0, 0), 128 * 4, -25, btTransform::getIdentity());
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mStart.x() = 0;
|
||||
mEnd.x() = 0;
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(0, 215, -94.75363922119140625),
|
||||
osg::Vec3f(9.8083515167236328125, 188.4185333251953125, -105.19994354248046875),
|
||||
osg::Vec3f(19.6167049407958984375, 161.837066650390625, -114.25496673583984375),
|
||||
osg::Vec3f(29.42505645751953125, 135.255615234375, -123.309967041015625),
|
||||
osg::Vec3f(39.23340606689453125, 108.674163818359375, -132.3649749755859375),
|
||||
osg::Vec3f(49.04175567626953125, 82.09270477294921875, -137.2874755859375),
|
||||
osg::Vec3f(58.8501129150390625, 55.5112457275390625, -139.2451171875),
|
||||
osg::Vec3f(68.6584625244140625, 28.9297885894775390625, -141.2027740478515625),
|
||||
osg::Vec3f(78.4668121337890625, 2.3483295440673828125, -143.1604156494140625),
|
||||
osg::Vec3f(88.27516937255859375, -24.233127593994140625, -141.3894805908203125),
|
||||
osg::Vec3f(83.73651885986328125, -52.2005767822265625, -142.3761444091796875),
|
||||
osg::Vec3f(79.19786834716796875, -80.16802978515625, -143.114837646484375),
|
||||
osg::Vec3f(64.8477935791015625, -104.598602294921875, -137.840911865234375),
|
||||
osg::Vec3f(50.497714996337890625, -129.0291748046875, -131.45831298828125),
|
||||
osg::Vec3f(36.147632598876953125, -153.459747314453125, -121.42321014404296875),
|
||||
osg::Vec3f(21.7975559234619140625, -177.8903350830078125, -111.38809967041015625),
|
||||
osg::Vec3f(7.44747829437255859375, -202.3209075927734375, -101.1938323974609375),
|
||||
osg::Vec3f(0, -215, -94.753631591796875),
|
||||
})) << mPath;
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavigatorTest, update_remove_and_update_then_find_path_should_return_path)
|
||||
{
|
||||
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||
0, 0, 0, 0, 0,
|
||||
0, -25, -25, -25, -25,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
0, -25, -100, -100, -100,
|
||||
}};
|
||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||
|
||||
mNavigator->addAgent(mAgentHalfExtents);
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->removeObject(ObjectId(&shape));
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||
mNavigator->update(mPlayerPosition);
|
||||
mNavigator->wait();
|
||||
|
||||
mNavigator->findPath(mAgentHalfExtents, mStart, mEnd, Flag_walk, mOut);
|
||||
|
||||
EXPECT_EQ(mPath, std::deque<osg::Vec3f>({
|
||||
osg::Vec3f(-215, 215, 1.85963428020477294921875),
|
||||
osg::Vec3f(-194.9653167724609375, 194.9653167724609375, -6.5760211944580078125),
|
||||
osg::Vec3f(-174.930633544921875, 174.930633544921875, -15.01167774200439453125),
|
||||
osg::Vec3f(-154.8959503173828125, 154.8959503173828125, -23.4473323822021484375),
|
||||
osg::Vec3f(-134.86126708984375, 134.86126708984375, -31.8829898834228515625),
|
||||
osg::Vec3f(-114.82657623291015625, 114.82657623291015625, -40.3186492919921875),
|
||||
osg::Vec3f(-94.7918853759765625, 94.7918853759765625, -47.39907073974609375),
|
||||
osg::Vec3f(-74.75719451904296875, 74.75719451904296875, -53.7258148193359375),
|
||||
osg::Vec3f(-54.722499847412109375, 54.722499847412109375, -60.052555084228515625),
|
||||
osg::Vec3f(-34.68780517578125, 34.68780517578125, -66.37929534912109375),
|
||||
osg::Vec3f(-14.6531162261962890625, 14.6531162261962890625, -72.70604705810546875),
|
||||
osg::Vec3f(5.3815765380859375, -5.3815765380859375, -75.35065460205078125),
|
||||
osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.96945953369140625),
|
||||
osg::Vec3f(45.450958251953125, -45.450958251953125, -60.58824920654296875),
|
||||
osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375),
|
||||
osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.825855255126953125),
|
||||
osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125),
|
||||
osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625),
|
||||
osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625),
|
||||
osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875),
|
||||
osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625),
|
||||
osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.53864824771881103515625),
|
||||
osg::Vec3f(215, -215, 1.877177715301513671875),
|
||||
})) << mPath;
|
||||
}
|
||||
}
|
312
apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp
Normal file
312
apps/openmw_test_suite/detournavigator/navmeshtilescache.cpp
Normal file
|
@ -0,0 +1,312 @@
|
|||
#include "operators.hpp"
|
||||
|
||||
#include <components/detournavigator/navmeshtilescache.hpp>
|
||||
#include <components/detournavigator/exceptions.hpp>
|
||||
#include <components/detournavigator/recastmesh.hpp>
|
||||
|
||||
#include <LinearMath/btTransform.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
static inline bool operator ==(const NavMeshDataRef& lhs, const NavMeshDataRef& rhs)
|
||||
{
|
||||
return std::make_pair(lhs.mValue, lhs.mSize) == std::make_pair(rhs.mValue, rhs.mSize);
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
|
||||
struct DetourNavigatorNavMeshTilesCacheTest : Test
|
||||
{
|
||||
const osg::Vec3f mAgentHalfExtents {1, 2, 3};
|
||||
const TilePosition mTilePosition {0, 0};
|
||||
const std::vector<int> mIndices {{0, 1, 2}};
|
||||
const std::vector<float> mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}};
|
||||
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
|
||||
const std::vector<RecastMesh::Water> mWater {};
|
||||
const std::size_t mTrianglesPerChunk {1};
|
||||
const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk};
|
||||
const std::vector<OffMeshConnection> mOffMeshConnections {};
|
||||
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData mNavMeshData {mData, 1};
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_empty_cache_should_return_empty_value)
|
||||
{
|
||||
const std::size_t maxSize = 0;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_for_not_enought_cache_size_should_return_empty_value)
|
||||
{
|
||||
const std::size_t maxSize = 0;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
|
||||
std::move(mNavMeshData)));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_return_cached_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
|
||||
std::move(mNavMeshData));
|
||||
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_existing_element_should_throw_exception)
|
||||
{
|
||||
const std::size_t maxSize = 2;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
EXPECT_THROW(
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(anotherNavMeshData)),
|
||||
InvalidArgument
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_should_return_cached_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
const auto result = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_agent_half_extents_should_return_empty_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
const osg::Vec3f unexsistentAgentHalfExtents {1, 1, 1};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
EXPECT_FALSE(cache.get(unexsistentAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_tile_position_should_return_empty_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
const TilePosition unexistentTilePosition {1, 1};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, unexistentTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, get_for_cache_miss_by_recast_mesh_should_return_empty_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
const auto result = cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
|
||||
std::move(anotherNavMeshData));
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_EQ(result.get(), (NavMeshDataRef {anotherData, 1}));
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_used_value)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
|
||||
std::move(mNavMeshData));
|
||||
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
|
||||
std::move(anotherNavMeshData)));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_set_value)
|
||||
{
|
||||
const std::size_t maxSize = 2;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater,
|
||||
mTrianglesPerChunk};
|
||||
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
|
||||
|
||||
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||
const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater,
|
||||
mTrianglesPerChunk};
|
||||
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
|
||||
|
||||
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections,
|
||||
std::move(leastRecentlySetNavMeshData)));
|
||||
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections,
|
||||
std::move(mostRecentlySetNavMeshData)));
|
||||
|
||||
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
|
||||
std::move(mNavMeshData));
|
||||
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
|
||||
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlySetRecastMesh, mOffMeshConnections));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlySetRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_replace_unused_least_recently_used_value)
|
||||
{
|
||||
const std::size_t maxSize = 2;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater,
|
||||
mTrianglesPerChunk};
|
||||
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
|
||||
|
||||
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||
const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater,
|
||||
mTrianglesPerChunk};
|
||||
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections,
|
||||
std::move(leastRecentlyUsedNavMeshData));
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections,
|
||||
std::move(mostRecentlyUsedNavMeshData));
|
||||
|
||||
{
|
||||
const auto value = cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections);
|
||||
ASSERT_TRUE(value);
|
||||
ASSERT_EQ(value.get(), (NavMeshDataRef {leastRecentlyUsedData, 1}));
|
||||
}
|
||||
|
||||
{
|
||||
const auto value = cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections);
|
||||
ASSERT_TRUE(value);
|
||||
ASSERT_EQ(value.get(), (NavMeshDataRef {mostRecentlyUsedData, 1}));
|
||||
}
|
||||
|
||||
const auto result = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
|
||||
std::move(mNavMeshData));
|
||||
EXPECT_EQ(result.get(), (NavMeshDataRef {mData, 1}));
|
||||
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, leastRecentlyUsedRecastMesh, mOffMeshConnections));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mostRecentlyUsedRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_cache_max_size)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
||||
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections,
|
||||
std::move(tooLargeNavMeshData)));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, set_should_not_replace_unused_least_recently_used_value_when_item_does_not_not_fit_size_of_unused_items)
|
||||
{
|
||||
const std::size_t maxSize = 2;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk};
|
||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk};
|
||||
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
||||
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
||||
|
||||
const auto value = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections,
|
||||
std::move(mNavMeshData));
|
||||
ASSERT_TRUE(value);
|
||||
ASSERT_TRUE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
|
||||
std::move(anotherNavMeshData)));
|
||||
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, tooLargeRecastMesh, mOffMeshConnections,
|
||||
std::move(tooLargeNavMeshData)));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_used_after_set_then_used_by_get_item_should_left_this_item_available)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
const auto firstCopy = cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
ASSERT_TRUE(firstCopy);
|
||||
{
|
||||
const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
|
||||
ASSERT_TRUE(secondCopy);
|
||||
}
|
||||
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
|
||||
std::move(anotherNavMeshData)));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorNavMeshTilesCacheTest, release_twice_used_item_should_left_this_item_available)
|
||||
{
|
||||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
const auto firstCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
|
||||
ASSERT_TRUE(firstCopy);
|
||||
{
|
||||
const auto secondCopy = cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections);
|
||||
ASSERT_TRUE(secondCopy);
|
||||
}
|
||||
EXPECT_FALSE(cache.set(mAgentHalfExtents, mTilePosition, anotherRecastMesh, mOffMeshConnections,
|
||||
std::move(anotherNavMeshData)));
|
||||
EXPECT_TRUE(cache.get(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections));
|
||||
}
|
||||
}
|
40
apps/openmw_test_suite/detournavigator/operators.hpp
Normal file
40
apps/openmw_test_suite/detournavigator/operators.hpp
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H
|
||||
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_OPERATORS_H
|
||||
|
||||
#include <components/bullethelpers/operators.hpp>
|
||||
#include <components/detournavigator/debug.hpp>
|
||||
#include <components/osghelpers/operators.hpp>
|
||||
|
||||
#include <deque>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
static inline bool operator ==(const TileBounds& lhs, const TileBounds& rhs)
|
||||
{
|
||||
return lhs.mMin == rhs.mMin && lhs.mMax == rhs.mMax;
|
||||
}
|
||||
}
|
||||
|
||||
namespace testing
|
||||
{
|
||||
template <>
|
||||
inline testing::Message& Message::operator <<(const std::deque<osg::Vec3f>& value)
|
||||
{
|
||||
(*this) << "{\n";
|
||||
for (const auto& v : value)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << v;
|
||||
(*this) << stream.str() << ",\n";
|
||||
}
|
||||
return (*this) << "}";
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
413
apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp
Normal file
413
apps/openmw_test_suite/detournavigator/recastmeshbuilder.cpp
Normal file
|
@ -0,0 +1,413 @@
|
|||
#include "operators.hpp"
|
||||
|
||||
#include <components/detournavigator/recastmeshbuilder.hpp>
|
||||
#include <components/detournavigator/settings.hpp>
|
||||
#include <components/detournavigator/recastmesh.hpp>
|
||||
#include <components/detournavigator/exceptions.hpp>
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btTriangleMesh.h>
|
||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
static inline bool operator ==(const RecastMesh::Water& lhs, const RecastMesh::Water& rhs)
|
||||
{
|
||||
return lhs.mCellSize == rhs.mCellSize && lhs.mTransform == rhs.mTransform;
|
||||
}
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
|
||||
struct DetourNavigatorRecastMeshBuilderTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
TileBounds mBounds;
|
||||
|
||||
DetourNavigatorRecastMeshBuilderTest()
|
||||
{
|
||||
mSettings.mRecastScaleFactor = 1.0f;
|
||||
mSettings.mTrianglesPerChunk = 256;
|
||||
mBounds.mMin = osg::Vec2f(-std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon(),
|
||||
-std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon());
|
||||
mBounds.mMax = osg::Vec2f(std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon(),
|
||||
std::numeric_limits<float>::max() * std::numeric_limits<float>::epsilon());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty)
|
||||
{
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>());
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>());
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>());
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_bhv_triangle_mesh_shape)
|
||||
{
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
-1, 0, -1,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_bhv_triangle_mesh_shape)
|
||||
{
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
2, 3, 0,
|
||||
0, 3, 4,
|
||||
0, 3, 0,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_heightfield_terrian_shape)
|
||||
{
|
||||
const std::array<btScalar, 4> heightfieldData {{0, 0, 0, 0}};
|
||||
btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
-0.5, 0, -0.5,
|
||||
-0.5, 0, 0.5,
|
||||
0.5, 0, -0.5,
|
||||
0.5, 0, -0.5,
|
||||
-0.5, 0, 0.5,
|
||||
0.5, 0, 0.5,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2, 3, 4, 5}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground, AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_box_shape_should_produce_12_triangles)
|
||||
{
|
||||
btBoxShape shape(btVector3(1, 1, 2));
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 2, 1,
|
||||
-1, 2, 1,
|
||||
1, 2, -1,
|
||||
-1, 2, -1,
|
||||
1, -2, 1,
|
||||
-1, -2, 1,
|
||||
1, -2, -1,
|
||||
-1, -2, -1,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({
|
||||
0, 2, 3,
|
||||
3, 1, 0,
|
||||
0, 4, 6,
|
||||
6, 2, 0,
|
||||
0, 1, 5,
|
||||
5, 4, 0,
|
||||
7, 5, 1,
|
||||
1, 3, 7,
|
||||
7, 3, 2,
|
||||
2, 6, 7,
|
||||
7, 6, 4,
|
||||
4, 5, 7,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>(12, AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_compound_shape)
|
||||
{
|
||||
btTriangleMesh mesh1;
|
||||
mesh1.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape triangle1(&mesh1, true);
|
||||
btBoxShape box(btVector3(1, 1, 2));
|
||||
btTriangleMesh mesh2;
|
||||
mesh2.addTriangle(btVector3(1, 1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape triangle2(&mesh2, true);
|
||||
btCompoundShape shape;
|
||||
shape.addChildShape(btTransform::getIdentity(), &triangle1);
|
||||
shape.addChildShape(btTransform::getIdentity(), &box);
|
||||
shape.addChildShape(btTransform::getIdentity(), &triangle2);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
-1, 0, -1,
|
||||
1, 2, 1,
|
||||
-1, 2, 1,
|
||||
1, 2, -1,
|
||||
-1, 2, -1,
|
||||
1, -2, 1,
|
||||
-1, -2, 1,
|
||||
1, -2, -1,
|
||||
-1, -2, -1,
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
1, 0, 1,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({
|
||||
0, 1, 2,
|
||||
3, 5, 6,
|
||||
6, 4, 3,
|
||||
3, 7, 9,
|
||||
9, 5, 3,
|
||||
3, 4, 8,
|
||||
8, 7, 3,
|
||||
10, 8, 4,
|
||||
4, 6, 10,
|
||||
10, 6, 5,
|
||||
5, 9, 10,
|
||||
10, 9, 7,
|
||||
7, 8, 10,
|
||||
11, 12, 13,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>(14, AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape)
|
||||
{
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape triangle(&mesh, true);
|
||||
btCompoundShape shape;
|
||||
shape.addChildShape(btTransform::getIdentity(), &triangle);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
2, 3, 0,
|
||||
0, 3, 4,
|
||||
0, 3, 0,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_transformed_compound_shape_with_transformed_bhv_triangle_shape)
|
||||
{
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape triangle(&mesh, true);
|
||||
btCompoundShape shape;
|
||||
shape.addChildShape(btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
&triangle);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
3, 12, 2,
|
||||
1, 12, 10,
|
||||
1, 12, 2,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, without_bounds_add_bhv_triangle_shape_should_not_filter_by_bounds)
|
||||
{
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
-1, 0, -1,
|
||||
-2, 0, -3,
|
||||
-3, 0, -2,
|
||||
-3, 0, -3,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2, 3, 4, 5}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>(2, AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_bhv_triangle_shape_should_filter_by_bounds)
|
||||
{
|
||||
mSettings.mRecastScaleFactor = 0.1f;
|
||||
mBounds.mMin = osg::Vec2f(-3, -3) * mSettings.mRecastScaleFactor;
|
||||
mBounds.mMax = osg::Vec2f(-2, -2) * mSettings.mRecastScaleFactor;
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
-0.2f, 0, -0.3f,
|
||||
-0.3f, 0, -0.2f,
|
||||
-0.3f, 0, -0.3f,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_x_bhv_triangle_shape_should_filter_by_bounds)
|
||||
{
|
||||
mBounds.mMin = osg::Vec2f(-5, -5);
|
||||
mBounds.mMax = osg::Vec2f(5, -2);
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(0, -1, -1), btVector3(0, -1, -1), btVector3(0, 1, -1));
|
||||
mesh.addTriangle(btVector3(0, -3, -3), btVector3(0, -3, -2), btVector3(0, -2, -3));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform(btQuaternion(btVector3(1, 0, 0),
|
||||
static_cast<btScalar>(-osg::PI_4))),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
0, -0.70710659027099609375, -3.535533905029296875,
|
||||
0, 0.707107067108154296875, -3.535533905029296875,
|
||||
0, 2.384185791015625e-07, -4.24264049530029296875,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_y_bhv_triangle_shape_should_filter_by_bounds)
|
||||
{
|
||||
mBounds.mMin = osg::Vec2f(-5, -5);
|
||||
mBounds.mMax = osg::Vec2f(-3, 5);
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, 0, -1), btVector3(-1, 0, 1), btVector3(1, 0, -1));
|
||||
mesh.addTriangle(btVector3(-3, 0, -3), btVector3(-3, 0, -2), btVector3(-2, 0, -3));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform(btQuaternion(btVector3(0, 1, 0),
|
||||
static_cast<btScalar>(osg::PI_4))),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
-3.535533905029296875, -0.70710659027099609375, 0,
|
||||
-3.535533905029296875, 0.707107067108154296875, 0,
|
||||
-4.24264049530029296875, 2.384185791015625e-07, 0,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, with_bounds_add_rotated_by_z_bhv_triangle_shape_should_filter_by_bounds)
|
||||
{
|
||||
mBounds.mMin = osg::Vec2f(-5, -5);
|
||||
mBounds.mMax = osg::Vec2f(-1, -1);
|
||||
btTriangleMesh mesh;
|
||||
mesh.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
mesh.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0));
|
||||
btBvhTriangleMeshShape shape(&mesh, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape),
|
||||
btTransform(btQuaternion(btVector3(0, 0, 1),
|
||||
static_cast<btScalar>(osg::PI_4))),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
0.707107067108154296875, 0, -3.535533905029296875,
|
||||
-0.70710659027099609375, 0, -3.535533905029296875,
|
||||
2.384185791015625e-07, 0, -4.24264049530029296875,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, flags_values_should_be_corresponding_to_added_objects)
|
||||
{
|
||||
btTriangleMesh mesh1;
|
||||
mesh1.addTriangle(btVector3(-1, -1, 0), btVector3(-1, 1, 0), btVector3(1, -1, 0));
|
||||
btBvhTriangleMeshShape shape1(&mesh1, true);
|
||||
btTriangleMesh mesh2;
|
||||
mesh2.addTriangle(btVector3(-3, -3, 0), btVector3(-3, -2, 0), btVector3(-2, -3, 0));
|
||||
btBvhTriangleMeshShape shape2(&mesh2, true);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape1),
|
||||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
builder.addObject(
|
||||
static_cast<const btCollisionShape&>(shape2),
|
||||
btTransform::getIdentity(),
|
||||
AreaType_null
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
-1, 0, -1,
|
||||
-2, 0, -3,
|
||||
-3, 0, -2,
|
||||
-3, 0, -3,
|
||||
}));
|
||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>({0, 1, 2, 3, 4, 5}));
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>({AreaType_ground, AreaType_null}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, add_water_then_get_water_should_return_it)
|
||||
{
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300)));
|
||||
const auto recastMesh = builder.create();
|
||||
EXPECT_EQ(recastMesh->getWater(), std::vector<RecastMesh::Water>({
|
||||
RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))}
|
||||
}));
|
||||
}
|
||||
}
|
72
apps/openmw_test_suite/detournavigator/recastmeshobject.cpp
Normal file
72
apps/openmw_test_suite/detournavigator/recastmeshobject.cpp
Normal file
|
@ -0,0 +1,72 @@
|
|||
#include "operators.hpp"
|
||||
|
||||
#include <components/detournavigator/recastmeshobject.hpp>
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
|
||||
struct DetourNavigatorRecastMeshObjectTest : Test
|
||||
{
|
||||
btBoxShape mBoxShape {btVector3(1, 2, 3)};
|
||||
btCompoundShape mCompoundShape {btVector3(1, 2, 3)};
|
||||
btTransform mTransform {btQuaternion(btVector3(1, 2, 3), 1), btVector3(1, 2, 3)};
|
||||
|
||||
DetourNavigatorRecastMeshObjectTest()
|
||||
{
|
||||
mCompoundShape.addChildShape(mTransform, std::addressof(mBoxShape));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, constructed_object_should_have_shape_and_transform)
|
||||
{
|
||||
const RecastMeshObject object(mBoxShape, mTransform, AreaType_ground);
|
||||
EXPECT_EQ(std::addressof(object.getShape()), std::addressof(mBoxShape));
|
||||
EXPECT_EQ(object.getTransform(), mTransform);
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_same_transform_for_not_compound_shape_should_return_false)
|
||||
{
|
||||
RecastMeshObject object(mBoxShape, mTransform, AreaType_ground);
|
||||
EXPECT_FALSE(object.update(mTransform, AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_transform_should_return_true)
|
||||
{
|
||||
RecastMeshObject object(mBoxShape, mTransform, AreaType_ground);
|
||||
EXPECT_TRUE(object.update(btTransform::getIdentity(), AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, update_with_different_flags_should_return_true)
|
||||
{
|
||||
RecastMeshObject object(mBoxShape, mTransform, AreaType_ground);
|
||||
EXPECT_TRUE(object.update(mTransform, AreaType_null));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_not_changed_child_transform_should_return_false)
|
||||
{
|
||||
RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground);
|
||||
EXPECT_FALSE(object.update(mTransform, AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, update_for_compound_shape_with_same_transform_and_changed_child_transform_should_return_true)
|
||||
{
|
||||
RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground);
|
||||
mCompoundShape.updateChildTransform(0, btTransform::getIdentity());
|
||||
EXPECT_TRUE(object.update(mTransform, AreaType_ground));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorRecastMeshObjectTest, repeated_update_for_compound_shape_without_changes_should_return_false)
|
||||
{
|
||||
RecastMeshObject object(mCompoundShape, mTransform, AreaType_ground);
|
||||
mCompoundShape.updateChildTransform(0, btTransform::getIdentity());
|
||||
object.update(mTransform, AreaType_ground);
|
||||
EXPECT_FALSE(object.update(mTransform, AreaType_ground));
|
||||
}
|
||||
}
|
68
apps/openmw_test_suite/detournavigator/settingsutils.cpp
Normal file
68
apps/openmw_test_suite/detournavigator/settingsutils.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "operators.hpp"
|
||||
|
||||
#include <components/detournavigator/settingsutils.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
using namespace DetourNavigator;
|
||||
|
||||
struct DetourNavigatorGetTilePositionTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
|
||||
DetourNavigatorGetTilePositionTest()
|
||||
{
|
||||
mSettings.mCellSize = 0.5;
|
||||
mSettings.mTileSize = 64;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorGetTilePositionTest, for_zero_coordinates_should_return_zero_tile_position)
|
||||
{
|
||||
EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(0, 0, 0)), TilePosition(0, 0));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilePositionTest, tile_size_should_be_multiplied_by_cell_size)
|
||||
{
|
||||
EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(32, 0, 0)), TilePosition(1, 0));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilePositionTest, tile_position_calculates_by_floor)
|
||||
{
|
||||
EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(31, 0, 0)), TilePosition(0, 0));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilePositionTest, tile_position_depends_on_x_and_z_coordinates)
|
||||
{
|
||||
EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(32, 64, 128)), TilePosition(1, 4));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorGetTilePositionTest, tile_position_works_for_negative_coordinates)
|
||||
{
|
||||
EXPECT_EQ(getTilePosition(mSettings, osg::Vec3f(-31, 0, -32)), TilePosition(-1, -1));
|
||||
}
|
||||
|
||||
struct DetourNavigatorMakeTileBoundsTest : Test
|
||||
{
|
||||
Settings mSettings;
|
||||
|
||||
DetourNavigatorMakeTileBoundsTest()
|
||||
{
|
||||
mSettings.mCellSize = 0.5;
|
||||
mSettings.mTileSize = 64;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DetourNavigatorMakeTileBoundsTest, tile_bounds_depend_on_tile_size_and_cell_size)
|
||||
{
|
||||
EXPECT_EQ(makeTileBounds(mSettings, TilePosition(0, 0)), (TileBounds {osg::Vec2f(0, 0), osg::Vec2f(32, 32)}));
|
||||
}
|
||||
|
||||
TEST_F(DetourNavigatorMakeTileBoundsTest, tile_bounds_are_multiplied_by_tile_position)
|
||||
{
|
||||
EXPECT_EQ(makeTileBounds(mSettings, TilePosition(1, 2)), (TileBounds {osg::Vec2f(32, 64), osg::Vec2f(64, 96)}));
|
||||
}
|
||||
}
|
|
@ -142,6 +142,7 @@ namespace Resource
|
|||
static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs)
|
||||
{
|
||||
return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape)
|
||||
&& compareObjects(lhs.mAvoidCollisionShape, rhs.mAvoidCollisionShape)
|
||||
&& lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents
|
||||
&& lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate
|
||||
&& lhs.mAnimatedShapes == rhs.mAnimatedShapes;
|
||||
|
@ -151,6 +152,7 @@ namespace Resource
|
|||
{
|
||||
return stream << "Resource::BulletShape {"
|
||||
<< value.mCollisionShape << ", "
|
||||
<< value.mAvoidCollisionShape << ", "
|
||||
<< value.mCollisionBoxHalfExtents << ", "
|
||||
<< value.mAnimatedShapes
|
||||
<< "}";
|
||||
|
@ -266,7 +268,8 @@ namespace
|
|||
value.target = Nif::ControlledPtr(nullptr);
|
||||
}
|
||||
|
||||
void copy(const btTransform& src, Nif::Transformation& dst) {
|
||||
void copy(const btTransform& src, Nif::Transformation& dst)
|
||||
{
|
||||
dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z());
|
||||
for (int row = 0; row < 3; ++row)
|
||||
for (int column = 0; column < 3; ++column)
|
||||
|
@ -837,7 +840,10 @@ namespace
|
|||
EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif"));
|
||||
const auto result = mLoader.load(mNifFile);
|
||||
|
||||
std::unique_ptr<btTriangleMesh> triangles(new btTriangleMesh(false));
|
||||
triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0));
|
||||
Resource::BulletShape expected;
|
||||
expected.mAvoidCollisionShape = new Resource::TriangleMeshShape(triangles.release(), false);
|
||||
|
||||
EXPECT_EQ(*result, expected);
|
||||
}
|
||||
|
@ -929,10 +935,8 @@ namespace
|
|||
mNiStringExtraData.string = "MRK";
|
||||
mNiStringExtraData.recType = Nif::RC_NiStringExtraData;
|
||||
mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData);
|
||||
mNiNode3.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
|
||||
mNiNode3.recType = Nif::RC_RootCollisionNode;
|
||||
mNiNode2.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(nullptr), Nif::NodePtr(&mNiNode3)}));
|
||||
mNiNode2.recType = Nif::RC_NiNode;
|
||||
mNiNode2.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiTriShape)}));
|
||||
mNiNode2.recType = Nif::RC_RootCollisionNode;
|
||||
mNiNode.children = Nif::NodeList(std::vector<Nif::NodePtr>({Nif::NodePtr(&mNiNode2)}));
|
||||
mNiNode.recType = Nif::RC_NiNode;
|
||||
|
||||
|
|
40
cmake/FindRecastNavigation.cmake
Normal file
40
cmake/FindRecastNavigation.cmake
Normal file
|
@ -0,0 +1,40 @@
|
|||
find_path(RecastNavigation_INCLUDE_DIR
|
||||
NAMES Recast.h
|
||||
HINTS $ENV{RecastNavigation_ROOT}
|
||||
${RecastNavigation_ROOT}
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
mark_as_advanced(RecastNavigation_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
set(RecastNavigation_LIBRARIES "")
|
||||
|
||||
foreach(COMPONENT ${RecastNavigation_FIND_COMPONENTS})
|
||||
if(NOT RecastNavigation_${COMPONENT}_FOUND)
|
||||
find_library(RecastNavigation_${COMPONENT}_LIBRARY
|
||||
HINTS $ENV{RecastNavigation_ROOT}
|
||||
${RecastNavigation_ROOT}
|
||||
NAMES ${COMPONENT}
|
||||
PATH_SUFFIXES lib
|
||||
)
|
||||
find_package_handle_standard_args(RecastNavigation_${COMPONENT} DEFAULT_MSG
|
||||
RecastNavigation_${COMPONENT}_LIBRARY
|
||||
RecastNavigation_INCLUDE_DIR
|
||||
)
|
||||
mark_as_advanced(RecastNavigation_${COMPONENT}_LIBRARY)
|
||||
if(RecastNavigation_${COMPONENT}_FOUND)
|
||||
list(APPEND RecastNavigation_LIBRARIES ${RecastNavigation_${COMPONENT}_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
mark_as_advanced(RecastNavigation_LIBRARIES)
|
||||
|
||||
find_package_handle_standard_args(RecastNavigation DEFAULT_MSG
|
||||
RecastNavigation_LIBRARIES
|
||||
RecastNavigation_INCLUDE_DIR
|
||||
)
|
||||
|
||||
if(RecastNavigation_FOUND)
|
||||
set(RecastNavigation_INCLUDE_DIRS ${RecastNavigation_INCLUDE_DIR})
|
||||
endif()
|
|
@ -51,7 +51,7 @@ add_component_dir (shader
|
|||
add_component_dir (sceneutil
|
||||
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
||||
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
||||
actorutil shadow mwshadowtechnique
|
||||
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique
|
||||
)
|
||||
|
||||
add_component_dir (nif
|
||||
|
@ -156,6 +156,23 @@ if(NOT WIN32 AND NOT ANDROID)
|
|||
)
|
||||
endif()
|
||||
|
||||
add_component_dir(detournavigator
|
||||
debug
|
||||
makenavmesh
|
||||
findsmoothpath
|
||||
recastmeshbuilder
|
||||
recastmeshmanager
|
||||
cachedrecastmeshmanager
|
||||
navmeshmanager
|
||||
navigator
|
||||
asyncnavmeshupdater
|
||||
chunkytrimesh
|
||||
recastmesh
|
||||
tilecachedrecastmeshmanager
|
||||
recastmeshobject
|
||||
navmeshtilescache
|
||||
)
|
||||
|
||||
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
||||
)
|
||||
|
||||
|
@ -196,6 +213,10 @@ include_directories(${Bullet_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR})
|
|||
|
||||
add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR})
|
||||
|
||||
find_package(RecastNavigation COMPONENTS DebugUtils Detour Recast REQUIRED)
|
||||
|
||||
include_directories(SYSTEM ${RecastNavigation_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(components
|
||||
${Boost_SYSTEM_LIBRARY}
|
||||
${Boost_FILESYSTEM_LIBRARY}
|
||||
|
@ -215,6 +236,7 @@ target_link_libraries(components
|
|||
${SDL2_LIBRARIES}
|
||||
${OPENGL_gl_LIBRARY}
|
||||
${MyGUI_LIBRARIES}
|
||||
${RecastNavigation_LIBRARIES}
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue