mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-01 15:09:41 +00:00
Add OpenMW commits up to 1 Apr 2020
# Conflicts: # .travis.yml # CI/before_install.linux.sh # apps/openmw/mwphysics/physicssystem.cpp
This commit is contained in:
commit
7bc3298ed4
112 changed files with 2181 additions and 831 deletions
11
.travis.yml
11
.travis.yml
|
@ -17,10 +17,9 @@ addons:
|
||||||
- sourceline: 'ppa:openmw/openmw'
|
- sourceline: 'ppa:openmw/openmw'
|
||||||
- sourceline: 'ppa:rakhimov/boost'
|
- sourceline: 'ppa:rakhimov/boost'
|
||||||
- ubuntu-toolchain-r-test
|
- ubuntu-toolchain-r-test
|
||||||
- llvm-toolchain-xenial-7
|
|
||||||
packages: [
|
packages: [
|
||||||
# Dev
|
# Dev
|
||||||
cmake, clang-7, clang-tools-7, gcc-8, g++-8, ccache,
|
cmake, clang-tools, gcc-8, g++-8, ccache,
|
||||||
# Boost
|
# Boost
|
||||||
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
||||||
# FFmpeg
|
# FFmpeg
|
||||||
|
@ -38,7 +37,7 @@ addons:
|
||||||
description: "<Your project description here>"
|
description: "<Your project description here>"
|
||||||
branch_pattern: coverity_scan
|
branch_pattern: coverity_scan
|
||||||
notification_email: koncord@tes3mp.com
|
notification_email: koncord@tes3mp.com
|
||||||
build_command_prepend: "cov-configure --comptype gcc --compiler gcc-5 --template; cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
|
build_command_prepend: "cov-configure --comptype gcc --compiler gcc-8 --template; cmake . -DBUILD_UNITTESTS=FALSE -DBUILD_OPENCS=FALSE -DBUILD_BSATOOL=FALSE -DBUILD_ESMTOOL=FALSE -DBUILD_MWINIIMPORTER=FALSE -DBUILD_LAUNCHER=FALSE"
|
||||||
build_command: "make VERBOSE=1 -j3"
|
build_command: "make VERBOSE=1 -j3"
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
@ -48,17 +47,17 @@ matrix:
|
||||||
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
||||||
compiler: clang
|
compiler: clang
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: xenial
|
dist: bionic
|
||||||
- os: linux
|
- os: linux
|
||||||
env:
|
env:
|
||||||
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
|
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: xenial
|
dist: bionic
|
||||||
- os: linux
|
- os: linux
|
||||||
env:
|
env:
|
||||||
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: xenial
|
dist: bionic
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env:
|
- env:
|
||||||
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
||||||
|
|
|
@ -74,6 +74,7 @@ Programmers
|
||||||
Fil Krynicki (filkry)
|
Fil Krynicki (filkry)
|
||||||
Finbar Crago (finbar-crago)
|
Finbar Crago (finbar-crago)
|
||||||
Florian Weber (Florianjw)
|
Florian Weber (Florianjw)
|
||||||
|
Frédéric Chardon (fr3dz10)
|
||||||
Gaëtan Dezeiraud (Brouilles)
|
Gaëtan Dezeiraud (Brouilles)
|
||||||
Gašper Sedej
|
Gašper Sedej
|
||||||
Gijsbert ter Horst (Ghostbird)
|
Gijsbert ter Horst (Ghostbird)
|
||||||
|
@ -87,6 +88,7 @@ Programmers
|
||||||
Jacob Essex (Yacoby)
|
Jacob Essex (Yacoby)
|
||||||
Jake Westrip (16bitint)
|
Jake Westrip (16bitint)
|
||||||
James Carty (MrTopCat)
|
James Carty (MrTopCat)
|
||||||
|
James Moore (moore.work)
|
||||||
James Stephens (james-h-stephens)
|
James Stephens (james-h-stephens)
|
||||||
Jan-Peter Nilsson (peppe)
|
Jan-Peter Nilsson (peppe)
|
||||||
Jan Borsodi (am0s)
|
Jan Borsodi (am0s)
|
||||||
|
|
|
@ -43,6 +43,7 @@
|
||||||
Bug #4594: Actors without AI packages don't use Hello dialogue
|
Bug #4594: Actors without AI packages don't use Hello dialogue
|
||||||
Bug #4598: Script parser does not support non-ASCII characters
|
Bug #4598: Script parser does not support non-ASCII characters
|
||||||
Bug #4600: Crash when no sound output is available or --no-sound is used.
|
Bug #4600: Crash when no sound output is available or --no-sound is used.
|
||||||
|
Bug #4601: Filtering referenceables by gender is broken
|
||||||
Bug #4639: Black screen after completing first mages guild mission + training
|
Bug #4639: Black screen after completing first mages guild mission + training
|
||||||
Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog
|
Bug #4650: Focus is lost after pressing ESC in confirmation dialog inside savegame dialog
|
||||||
Bug #4680: Heap corruption on faulty esp
|
Bug #4680: Heap corruption on faulty esp
|
||||||
|
@ -205,6 +206,10 @@
|
||||||
Bug #5278: Console command Show doesn't fall back to global variable after local var not found
|
Bug #5278: Console command Show doesn't fall back to global variable after local var not found
|
||||||
Bug #5300: NPCs don't switch from torch to shield when starting combat
|
Bug #5300: NPCs don't switch from torch to shield when starting combat
|
||||||
Bug #5308: World map copying makes save loading much slower
|
Bug #5308: World map copying makes save loading much slower
|
||||||
|
Bug #5313: Node properties of identical type are not applied in the correct order
|
||||||
|
Bug #5326: Formatting issues in the settings.cfg
|
||||||
|
Bug #5328: Skills aren't properly reset for dead actors
|
||||||
|
Bug #5345: Dopey Necromancy does not work due to a missing quote
|
||||||
Feature #1774: Handle AvoidNode
|
Feature #1774: Handle AvoidNode
|
||||||
Feature #2229: Improve pathfinding AI
|
Feature #2229: Improve pathfinding AI
|
||||||
Feature #3025: Analogue gamepad movement controls
|
Feature #3025: Analogue gamepad movement controls
|
||||||
|
@ -225,6 +230,7 @@
|
||||||
Feature #4544: Actors movement deceleration
|
Feature #4544: Actors movement deceleration
|
||||||
Feature #4673: Weapon sheathing
|
Feature #4673: Weapon sheathing
|
||||||
Feature #4675: Support for NiRollController
|
Feature #4675: Support for NiRollController
|
||||||
|
Feature #4708: Radial fog support
|
||||||
Feature #4730: Native animated containers support
|
Feature #4730: Native animated containers support
|
||||||
Feature #4784: Launcher: Duplicate Content Lists
|
Feature #4784: Launcher: Duplicate Content Lists
|
||||||
Feature #4812: Support NiSwitchNode
|
Feature #4812: Support NiSwitchNode
|
||||||
|
@ -251,6 +257,7 @@
|
||||||
Feature #5091: Human-readable light source duration
|
Feature #5091: Human-readable light source duration
|
||||||
Feature #5094: Unix like console hotkeys
|
Feature #5094: Unix like console hotkeys
|
||||||
Feature #5098: Allow user controller bindings
|
Feature #5098: Allow user controller bindings
|
||||||
|
Feature #5114: Refresh launcher mod list
|
||||||
Feature #5121: Handle NiTriStrips and NiTriStripsData
|
Feature #5121: Handle NiTriStrips and NiTriStripsData
|
||||||
Feature #5122: Use magic glow for enchanted arrows
|
Feature #5122: Use magic glow for enchanted arrows
|
||||||
Feature #5131: Custom skeleton bones
|
Feature #5131: Custom skeleton bones
|
||||||
|
@ -262,7 +269,9 @@
|
||||||
Feature #5193: Weapon sheathing
|
Feature #5193: Weapon sheathing
|
||||||
Feature #5219: Impelement TestCells console command
|
Feature #5219: Impelement TestCells console command
|
||||||
Feature #5224: Handle NiKeyframeController for NiTriShape
|
Feature #5224: Handle NiKeyframeController for NiTriShape
|
||||||
|
Feature #5274: Editor: Keyboard shortcut to drop objects to ground/obstacle in scene view
|
||||||
Feature #5304: Morrowind-style bump-mapping
|
Feature #5304: Morrowind-style bump-mapping
|
||||||
|
Feature #5314: Ingredient filter in the alchemy window
|
||||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||||
Task #4695: Optimize Distant Terrain memory consumption
|
Task #4695: Optimize Distant Terrain memory consumption
|
||||||
Task #4789: Optimize cell transitions
|
Task #4789: Optimize cell transitions
|
||||||
|
|
|
@ -43,6 +43,7 @@ New Editor Features:
|
||||||
- Changes to height editing can be cancelled without changes to data (press esc to cancel) (#4840)
|
- Changes to height editing can be cancelled without changes to data (press esc to cancel) (#4840)
|
||||||
- Land heightmap/shape editing and vertex selection (#5170)
|
- Land heightmap/shape editing and vertex selection (#5170)
|
||||||
- Deleting instances with a keypress (#5172)
|
- Deleting instances with a keypress (#5172)
|
||||||
|
- Dropping objects with keyboard shortcuts (#5274)
|
||||||
|
|
||||||
Bug Fixes:
|
Bug Fixes:
|
||||||
- The Mouse Wheel can now be used for key bindings (#2679)
|
- The Mouse Wheel can now be used for key bindings (#2679)
|
||||||
|
|
|
@ -421,16 +421,16 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
download "Qt 5.7.0" \
|
download "Qt 5.7.0" \
|
||||||
"https://download.qt.io/archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \
|
"https://download.qt.io/new_archive/qt/5.7/5.7.0/qt-opensource-windows-x86-msvc${MSVC_YEAR}${QT_SUFFIX}-5.7.0.exe" \
|
||||||
"qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \
|
"qt-5.7.0-msvc${MSVC_YEAR}-win${BITS}.exe" \
|
||||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
|
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
|
||||||
"qt-5-install.qs"
|
"qt-5-install.qs"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# SDL2
|
# SDL2
|
||||||
download "SDL 2.0.7" \
|
download "SDL 2.0.12" \
|
||||||
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \
|
"https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \
|
||||||
"SDL2-2.0.7.zip"
|
"SDL2-2.0.12.zip"
|
||||||
|
|
||||||
# Google test and mock
|
# Google test and mock
|
||||||
if [ ! -z $TEST_FRAMEWORK ]; then
|
if [ ! -z $TEST_FRAMEWORK ]; then
|
||||||
|
@ -697,16 +697,16 @@ fi
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
echo
|
echo
|
||||||
# SDL2
|
# SDL2
|
||||||
printf "SDL 2.0.7... "
|
printf "SDL 2.0.12... "
|
||||||
{
|
{
|
||||||
if [ -d SDL2-2.0.7 ]; then
|
if [ -d SDL2-2.0.12 ]; then
|
||||||
printf "Exists. "
|
printf "Exists. "
|
||||||
elif [ -z $SKIP_EXTRACT ]; then
|
elif [ -z $SKIP_EXTRACT ]; then
|
||||||
rm -rf SDL2-2.0.7
|
rm -rf SDL2-2.0.12
|
||||||
eval 7z x -y SDL2-2.0.7.zip $STRIP
|
eval 7z x -y SDL2-2.0.12.zip $STRIP
|
||||||
fi
|
fi
|
||||||
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
|
export SDL2DIR="$(real_pwd)/SDL2-2.0.12"
|
||||||
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
|
add_runtime_dlls "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||||
echo Done.
|
echo Done.
|
||||||
}
|
}
|
||||||
cd $DEPS
|
cd $DEPS
|
||||||
|
|
|
@ -62,10 +62,13 @@ void Launcher::DataFilesPage::buildView()
|
||||||
{
|
{
|
||||||
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
|
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
|
||||||
|
|
||||||
|
QToolButton * refreshButton = mSelector->refreshButton();
|
||||||
|
|
||||||
//tool buttons
|
//tool buttons
|
||||||
ui.newProfileButton->setToolTip ("Create a new Content List");
|
ui.newProfileButton->setToolTip ("Create a new Content List");
|
||||||
ui.cloneProfileButton->setToolTip ("Clone the current Content List");
|
ui.cloneProfileButton->setToolTip ("Clone the current Content List");
|
||||||
ui.deleteProfileButton->setToolTip ("Delete an existing Content List");
|
ui.deleteProfileButton->setToolTip ("Delete an existing Content List");
|
||||||
|
refreshButton->setToolTip("Refresh Data Files");
|
||||||
|
|
||||||
//combo box
|
//combo box
|
||||||
ui.profilesComboBox->addItem(mDefaultContentListName);
|
ui.profilesComboBox->addItem(mDefaultContentListName);
|
||||||
|
@ -76,6 +79,7 @@ void Launcher::DataFilesPage::buildView()
|
||||||
ui.newProfileButton->setDefaultAction (ui.newProfileAction);
|
ui.newProfileButton->setDefaultAction (ui.newProfileAction);
|
||||||
ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction);
|
ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction);
|
||||||
ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction);
|
ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction);
|
||||||
|
refreshButton->setDefaultAction(ui.refreshDataFilesAction);
|
||||||
|
|
||||||
//establish connections
|
//establish connections
|
||||||
connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)),
|
connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)),
|
||||||
|
@ -86,6 +90,8 @@ void Launcher::DataFilesPage::buildView()
|
||||||
|
|
||||||
connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)),
|
connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)),
|
||||||
this, SLOT (slotProfileChangedByUser(QString, QString)));
|
this, SLOT (slotProfileChangedByUser(QString, QString)));
|
||||||
|
|
||||||
|
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Launcher::DataFilesPage::loadSettings()
|
bool Launcher::DataFilesPage::loadSettings()
|
||||||
|
@ -114,6 +120,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
||||||
if (!mDataLocal.isEmpty())
|
if (!mDataLocal.isEmpty())
|
||||||
paths.insert(0, mDataLocal);
|
paths.insert(0, mDataLocal);
|
||||||
|
|
||||||
|
mSelector->clearFiles();
|
||||||
|
|
||||||
for (const QString &path : paths)
|
for (const QString &path : paths)
|
||||||
mSelector->addFiles(path);
|
mSelector->addFiles(path);
|
||||||
|
|
||||||
|
@ -166,9 +174,18 @@ QStringList Launcher::DataFilesPage::selectedFilePaths()
|
||||||
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
||||||
QStringList filePaths;
|
QStringList filePaths;
|
||||||
for (const ContentSelectorModel::EsmFile *item : items)
|
for (const ContentSelectorModel::EsmFile *item : items)
|
||||||
|
{
|
||||||
|
QFile file(item->filePath());
|
||||||
|
|
||||||
|
if(file.exists())
|
||||||
{
|
{
|
||||||
filePaths.append(item->filePath());
|
filePaths.append(item->filePath());
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
slotRefreshButtonClicked();
|
||||||
|
}
|
||||||
|
}
|
||||||
return filePaths;
|
return filePaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,6 +238,18 @@ void Launcher::DataFilesPage::slotProfileDeleted (const QString &item)
|
||||||
removeProfile (item);
|
removeProfile (item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Launcher::DataFilesPage:: refreshDataFilesView ()
|
||||||
|
{
|
||||||
|
QString currentProfile = ui.profilesComboBox->currentText();
|
||||||
|
saveSettings(currentProfile);
|
||||||
|
populateFileViews(currentProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Launcher::DataFilesPage::slotRefreshButtonClicked ()
|
||||||
|
{
|
||||||
|
refreshDataFilesView();
|
||||||
|
}
|
||||||
|
|
||||||
void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t)
|
void Launcher::DataFilesPage::slotProfileChangedByUser(const QString &previous, const QString ¤t)
|
||||||
{
|
{
|
||||||
setProfile(previous, current, true);
|
setProfile(previous, current, true);
|
||||||
|
|
|
@ -61,6 +61,7 @@ namespace Launcher
|
||||||
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
||||||
void slotProfileDeleted(const QString &item);
|
void slotProfileDeleted(const QString &item);
|
||||||
void slotAddonDataChanged ();
|
void slotAddonDataChanged ();
|
||||||
|
void slotRefreshButtonClicked ();
|
||||||
|
|
||||||
void updateNewProfileOkButton(const QString &text);
|
void updateNewProfileOkButton(const QString &text);
|
||||||
void updateCloneProfileOkButton(const QString &text);
|
void updateCloneProfileOkButton(const QString &text);
|
||||||
|
@ -100,6 +101,7 @@ namespace Launcher
|
||||||
void checkForDefaultProfile();
|
void checkForDefaultProfile();
|
||||||
void populateFileViews(const QString& contentModelName);
|
void populateFileViews(const QString& contentModelName);
|
||||||
void reloadCells(QStringList selectedFiles);
|
void reloadCells(QStringList selectedFiles);
|
||||||
|
void refreshDataFilesView ();
|
||||||
|
|
||||||
class PathIterator
|
class PathIterator
|
||||||
{
|
{
|
||||||
|
|
|
@ -356,6 +356,10 @@ void CSMPrefs::State::declare()
|
||||||
QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton));
|
QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton));
|
||||||
declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift);
|
declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift);
|
||||||
declareShortcut ("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete));
|
declareShortcut ("scene-delete", "Delete Instance", QKeySequence(Qt::Key_Delete));
|
||||||
|
declareShortcut ("scene-instance-drop-terrain", "Drop to terrain level", QKeySequence(Qt::Key_G));
|
||||||
|
declareShortcut ("scene-instance-drop-collision", "Drop to collision", QKeySequence(Qt::Key_H));
|
||||||
|
declareShortcut ("scene-instance-drop-terrain-separately", "Drop to terrain level separately", QKeySequence());
|
||||||
|
declareShortcut ("scene-instance-drop-collision-separately", "Drop to collision separately", QKeySequence());
|
||||||
declareShortcut ("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5));
|
declareShortcut ("scene-load-cam-cell", "Load Camera Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_5));
|
||||||
declareShortcut ("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6));
|
declareShortcut ("scene-load-cam-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6));
|
||||||
declareShortcut ("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8));
|
declareShortcut ("scene-load-cam-northcell", "Load North Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_8));
|
||||||
|
|
|
@ -1770,7 +1770,7 @@ namespace CSMWorld
|
||||||
struct GenderNpcColumn : public Column<ESXRecordT>
|
struct GenderNpcColumn : public Column<ESXRecordT>
|
||||||
{
|
{
|
||||||
GenderNpcColumn()
|
GenderNpcColumn()
|
||||||
: Column<ESXRecordT>(Columns::ColumnId_GenderNpc, ColumnBase::Display_GenderNpc)
|
: Column<ESXRecordT>(Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
virtual QVariant get(const Record<ESXRecordT>& record) const
|
virtual QVariant get(const Record<ESXRecordT>& record) const
|
||||||
|
|
|
@ -288,7 +288,6 @@ namespace CSMWorld
|
||||||
{ ColumnId_UChar, "Value [0..255]" },
|
{ ColumnId_UChar, "Value [0..255]" },
|
||||||
{ ColumnId_NpcMisc, "NPC Misc" },
|
{ ColumnId_NpcMisc, "NPC Misc" },
|
||||||
{ ColumnId_Level, "Level" },
|
{ ColumnId_Level, "Level" },
|
||||||
{ ColumnId_GenderNpc, "Gender"},
|
|
||||||
{ ColumnId_Mana, "Mana" },
|
{ ColumnId_Mana, "Mana" },
|
||||||
{ ColumnId_Fatigue, "Fatigue" },
|
{ ColumnId_Fatigue, "Fatigue" },
|
||||||
{ ColumnId_NpcDisposition, "NPC Disposition" },
|
{ ColumnId_NpcDisposition, "NPC Disposition" },
|
||||||
|
|
|
@ -273,7 +273,7 @@ namespace CSMWorld
|
||||||
ColumnId_UChar = 250,
|
ColumnId_UChar = 250,
|
||||||
ColumnId_NpcMisc = 251,
|
ColumnId_NpcMisc = 251,
|
||||||
ColumnId_Level = 252,
|
ColumnId_Level = 252,
|
||||||
ColumnId_GenderNpc = 254,
|
// unused
|
||||||
ColumnId_Mana = 255,
|
ColumnId_Mana = 255,
|
||||||
ColumnId_Fatigue = 256,
|
ColumnId_Fatigue = 256,
|
||||||
ColumnId_NpcDisposition = 257,
|
ColumnId_NpcDisposition = 257,
|
||||||
|
|
|
@ -82,6 +82,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, bool fsStrict, const Files::Pat
|
||||||
defines["forcePPL"] = "0"; // Don't force per-pixel lighting
|
defines["forcePPL"] = "0"; // Don't force per-pixel lighting
|
||||||
defines["clamp"] = "1"; // Clamp lighting
|
defines["clamp"] = "1"; // Clamp lighting
|
||||||
defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind
|
defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind
|
||||||
|
defines["radialFog"] = "0";
|
||||||
for (const auto& define : shadowDefines)
|
for (const auto& define : shadowDefines)
|
||||||
defines[define.first] = define.second;
|
defines[define.first] = define.second;
|
||||||
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
|
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
|
||||||
|
|
|
@ -486,7 +486,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
||||||
mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_BodyPart));
|
mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_BodyPart));
|
||||||
npcColumns.mHead = &mColumns.back();
|
npcColumns.mHead = &mColumns.back();
|
||||||
|
|
||||||
mColumns.push_back (RefIdColumn (Columns::ColumnId_GenderNpc, ColumnBase::Display_GenderNpc));
|
mColumns.push_back (RefIdColumn (Columns::ColumnId_Gender, ColumnBase::Display_GenderNpc));
|
||||||
npcColumns.mGender = &mColumns.back();
|
npcColumns.mGender = &mColumns.back();
|
||||||
|
|
||||||
npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential));
|
npcColumns.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential));
|
||||||
|
|
|
@ -3,9 +3,15 @@
|
||||||
|
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include "../../model/prefs/state.hpp"
|
#include "../../model/prefs/state.hpp"
|
||||||
|
|
||||||
|
#include <osg/ComputeBoundsVisitor>
|
||||||
|
#include <osg/Group>
|
||||||
|
#include <osg/Vec3d>
|
||||||
|
#include <osgUtil/LineSegmentIntersector>
|
||||||
|
|
||||||
#include "../../model/world/idtable.hpp"
|
#include "../../model/world/idtable.hpp"
|
||||||
#include "../../model/world/idtree.hpp"
|
#include "../../model/world/idtree.hpp"
|
||||||
#include "../../model/world/commands.hpp"
|
#include "../../model/world/commands.hpp"
|
||||||
|
@ -90,16 +96,26 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos)
|
||||||
return pos * combined;
|
return pos * combined;
|
||||||
}
|
}
|
||||||
|
|
||||||
CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent)
|
CSVRender::InstanceMode::InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget *parent)
|
||||||
: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), SceneUtil::Mask_EditorReference | SceneUtil::Mask_Terrain, "Instance editing",
|
: EditMode (worldspaceWidget, QIcon (":scenetoolbar/editing-instance"), SceneUtil::Mask_EditorReference | SceneUtil::Mask_Terrain, "Instance editing",
|
||||||
parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None),
|
parent), mSubMode (0), mSubModeId ("move"), mSelectionMode (0), mDragMode (DragMode_None),
|
||||||
mDragAxis (-1), mLocked (false), mUnitScaleDist(1)
|
mDragAxis (-1), mLocked (false), mUnitScaleDist(1), mParentNode (parentNode)
|
||||||
{
|
{
|
||||||
connect(this, SIGNAL(requestFocus(const std::string&)),
|
connect(this, SIGNAL(requestFocus(const std::string&)),
|
||||||
worldspaceWidget, SIGNAL(requestFocus(const std::string&)));
|
worldspaceWidget, SIGNAL(requestFocus(const std::string&)));
|
||||||
|
|
||||||
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget);
|
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget);
|
||||||
connect(deleteShortcut, SIGNAL(activated(bool)), this, SLOT(deleteSelectedInstances(bool)));
|
connect(deleteShortcut, SIGNAL(activated(bool)), this, SLOT(deleteSelectedInstances(bool)));
|
||||||
|
|
||||||
|
// Following classes could be simplified by using QSignalMapper, which is obsolete in Qt5.10, but not in Qt4.8 and Qt5.14
|
||||||
|
CSMPrefs::Shortcut* dropToCollisionShortcut = new CSMPrefs::Shortcut("scene-instance-drop-collision", worldspaceWidget);
|
||||||
|
connect(dropToCollisionShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollision()));
|
||||||
|
CSMPrefs::Shortcut* dropToTerrainLevelShortcut = new CSMPrefs::Shortcut("scene-instance-drop-terrain", worldspaceWidget);
|
||||||
|
connect(dropToTerrainLevelShortcut, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrain()));
|
||||||
|
CSMPrefs::Shortcut* dropToCollisionShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-collision-separately", worldspaceWidget);
|
||||||
|
connect(dropToCollisionShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToCollisionSeparately()));
|
||||||
|
CSMPrefs::Shortcut* dropToTerrainLevelShortcut2 = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget);
|
||||||
|
connect(dropToTerrainLevelShortcut2, SIGNAL(activated()), this, SLOT(dropSelectedInstancesToTerrainSeparately()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
|
void CSVRender::InstanceMode::activate (CSVWidget::SceneToolbar *toolbar)
|
||||||
|
@ -681,3 +697,187 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active)
|
||||||
|
|
||||||
getWorldspaceWidget().clearSelection (SceneUtil::Mask_EditorReference);
|
getWorldspaceWidget().clearSelection (SceneUtil::Mask_EditorReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
||||||
|
{
|
||||||
|
osg::Vec3d point = object->getPosition().asVec3();
|
||||||
|
|
||||||
|
osg::Vec3d start = point;
|
||||||
|
start.z() += objectHeight;
|
||||||
|
osg::Vec3d end = point;
|
||||||
|
end.z() = std::numeric_limits<float>::lowest();
|
||||||
|
|
||||||
|
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector (new osgUtil::LineSegmentIntersector(
|
||||||
|
osgUtil::Intersector::MODEL, start, end) );
|
||||||
|
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
|
||||||
|
osgUtil::IntersectionVisitor visitor(intersector);
|
||||||
|
|
||||||
|
if (dropMode == TerrainSep)
|
||||||
|
visitor.setTraversalMask(SceneUtil::Mask_Terrain);
|
||||||
|
if (dropMode == CollisionSep)
|
||||||
|
visitor.setTraversalMask(SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference);
|
||||||
|
|
||||||
|
mParentNode->accept(visitor);
|
||||||
|
|
||||||
|
for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
|
||||||
|
it != intersector->getIntersections().end(); ++it)
|
||||||
|
{
|
||||||
|
osgUtil::LineSegmentIntersector::Intersection intersection = *it;
|
||||||
|
ESM::Position position = object->getPosition();
|
||||||
|
object->setEdited (Object::Override_Position);
|
||||||
|
position.pos[2] = intersection.getWorldIntersectPoint().z() + objectHeight;
|
||||||
|
object->setPosition(position.pos);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float CSVRender::InstanceMode::getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight)
|
||||||
|
{
|
||||||
|
osg::Vec3d point = object->getPosition().asVec3();
|
||||||
|
|
||||||
|
osg::Vec3d start = point;
|
||||||
|
start.z() += objectHeight;
|
||||||
|
osg::Vec3d end = point;
|
||||||
|
end.z() = std::numeric_limits<float>::lowest();
|
||||||
|
|
||||||
|
osg::ref_ptr<osgUtil::LineSegmentIntersector> intersector (new osgUtil::LineSegmentIntersector(
|
||||||
|
osgUtil::Intersector::MODEL, start, end) );
|
||||||
|
intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT);
|
||||||
|
osgUtil::IntersectionVisitor visitor(intersector);
|
||||||
|
|
||||||
|
if (dropMode == Terrain)
|
||||||
|
visitor.setTraversalMask(SceneUtil::Mask_Terrain);
|
||||||
|
if (dropMode == Collision)
|
||||||
|
visitor.setTraversalMask(SceneUtil::Mask_Terrain | SceneUtil::Mask_EditorReference);
|
||||||
|
|
||||||
|
mParentNode->accept(visitor);
|
||||||
|
|
||||||
|
for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin();
|
||||||
|
it != intersector->getIntersections().end(); ++it)
|
||||||
|
{
|
||||||
|
osgUtil::LineSegmentIntersector::Intersection intersection = *it;
|
||||||
|
float collisionLevel = intersection.getWorldIntersectPoint().z();
|
||||||
|
return point.z() - collisionLevel + objectHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::dropSelectedInstancesToCollision()
|
||||||
|
{
|
||||||
|
handleDropMethod(Collision, "Drop instances to next collision");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::dropSelectedInstancesToTerrain()
|
||||||
|
{
|
||||||
|
handleDropMethod(Terrain, "Drop instances to terrain level");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::dropSelectedInstancesToCollisionSeparately()
|
||||||
|
{
|
||||||
|
handleDropMethod(TerrainSep, "Drop instances to next collision level separately");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::dropSelectedInstancesToTerrainSeparately()
|
||||||
|
{
|
||||||
|
handleDropMethod(CollisionSep, "Drop instances to terrain level separately");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSVRender::InstanceMode::handleDropMethod(DropMode dropMode, QString commandMsg)
|
||||||
|
{
|
||||||
|
std::vector<osg::ref_ptr<TagBase> > selection = getWorldspaceWidget().getSelection (SceneUtil::Mask_EditorReference);
|
||||||
|
if (selection.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
||||||
|
QUndoStack& undoStack = document.getUndoStack();
|
||||||
|
|
||||||
|
CSMWorld::CommandMacro macro (undoStack, commandMsg);
|
||||||
|
|
||||||
|
DropObjectDataHandler dropObjectDataHandler(&getWorldspaceWidget());
|
||||||
|
|
||||||
|
switch (dropMode)
|
||||||
|
{
|
||||||
|
case Terrain:
|
||||||
|
case Collision:
|
||||||
|
{
|
||||||
|
float smallestDropHeight = std::numeric_limits<float>::max();
|
||||||
|
int counter = 0;
|
||||||
|
for(osg::ref_ptr<TagBase> tag: selection)
|
||||||
|
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||||
|
{
|
||||||
|
float thisDrop = getDropHeight(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
||||||
|
if (thisDrop < smallestDropHeight)
|
||||||
|
smallestDropHeight = thisDrop;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
for(osg::ref_ptr<TagBase> tag: selection)
|
||||||
|
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||||
|
{
|
||||||
|
objectTag->mObject->setEdited (Object::Override_Position);
|
||||||
|
ESM::Position position = objectTag->mObject->getPosition();
|
||||||
|
position.pos[2] -= smallestDropHeight;
|
||||||
|
objectTag->mObject->setPosition(position.pos);
|
||||||
|
objectTag->mObject->apply (macro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TerrainSep:
|
||||||
|
case CollisionSep:
|
||||||
|
{
|
||||||
|
int counter = 0;
|
||||||
|
for(osg::ref_ptr<TagBase> tag: selection)
|
||||||
|
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||||
|
{
|
||||||
|
dropInstance(dropMode, objectTag->mObject, dropObjectDataHandler.mObjectHeights[counter]);
|
||||||
|
objectTag->mObject->apply (macro);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSVRender::DropObjectDataHandler::DropObjectDataHandler(WorldspaceWidget* worldspacewidget)
|
||||||
|
: mWorldspaceWidget(worldspacewidget)
|
||||||
|
{
|
||||||
|
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (SceneUtil::Mask_EditorReference);
|
||||||
|
for(osg::ref_ptr<TagBase> tag: selection)
|
||||||
|
{
|
||||||
|
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> objectNodeWithGUI = objectTag->mObject->getRootNode();
|
||||||
|
osg::ref_ptr<osg::Group> objectNodeWithoutGUI = objectTag->mObject->getBaseNode();
|
||||||
|
|
||||||
|
osg::ComputeBoundsVisitor computeBounds;
|
||||||
|
computeBounds.setTraversalMask(SceneUtil::Mask_EditorReference);
|
||||||
|
objectNodeWithoutGUI->accept(computeBounds);
|
||||||
|
osg::BoundingBox bounds = computeBounds.getBoundingBox();
|
||||||
|
float boundingBoxOffset = 0.0f;
|
||||||
|
if (bounds.valid())
|
||||||
|
boundingBoxOffset = bounds.zMin();
|
||||||
|
|
||||||
|
mObjectHeights.emplace_back(boundingBoxOffset);
|
||||||
|
mOldMasks.emplace_back(objectNodeWithGUI->getNodeMask());
|
||||||
|
|
||||||
|
objectNodeWithGUI->setNodeMask(SceneUtil::Mask_Disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CSVRender::DropObjectDataHandler::~DropObjectDataHandler()
|
||||||
|
{
|
||||||
|
std::vector<osg::ref_ptr<TagBase> > selection = mWorldspaceWidget->getSelection (SceneUtil::Mask_EditorReference);
|
||||||
|
int counter = 0;
|
||||||
|
for(osg::ref_ptr<TagBase> tag: selection)
|
||||||
|
{
|
||||||
|
if (CSVRender::ObjectTag *objectTag = dynamic_cast<CSVRender::ObjectTag *> (tag.get()))
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> objectNodeWithGUI = objectTag->mObject->getRootNode();
|
||||||
|
objectNodeWithGUI->setNodeMask(mOldMasks[counter]);
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
#ifndef CSV_RENDER_INSTANCEMODE_H
|
#ifndef CSV_RENDER_INSTANCEMODE_H
|
||||||
#define CSV_RENDER_INSTANCEMODE_H
|
#define CSV_RENDER_INSTANCEMODE_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
#include <osg/ref_ptr>
|
#include <osg/ref_ptr>
|
||||||
|
#include <osg/Group>
|
||||||
#include <osg/Quat>
|
#include <osg/Quat>
|
||||||
#include <osg/Vec3f>
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
@ -16,6 +19,7 @@ namespace CSVRender
|
||||||
{
|
{
|
||||||
class TagBase;
|
class TagBase;
|
||||||
class InstanceSelectionMode;
|
class InstanceSelectionMode;
|
||||||
|
class Object;
|
||||||
|
|
||||||
class InstanceMode : public EditMode
|
class InstanceMode : public EditMode
|
||||||
{
|
{
|
||||||
|
@ -29,6 +33,14 @@ namespace CSVRender
|
||||||
DragMode_Scale
|
DragMode_Scale
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum DropMode
|
||||||
|
{
|
||||||
|
Collision,
|
||||||
|
Terrain,
|
||||||
|
CollisionSep,
|
||||||
|
TerrainSep
|
||||||
|
};
|
||||||
|
|
||||||
CSVWidget::SceneToolMode *mSubMode;
|
CSVWidget::SceneToolMode *mSubMode;
|
||||||
std::string mSubModeId;
|
std::string mSubModeId;
|
||||||
InstanceSelectionMode *mSelectionMode;
|
InstanceSelectionMode *mSelectionMode;
|
||||||
|
@ -36,6 +48,7 @@ namespace CSVRender
|
||||||
int mDragAxis;
|
int mDragAxis;
|
||||||
bool mLocked;
|
bool mLocked;
|
||||||
float mUnitScaleDist;
|
float mUnitScaleDist;
|
||||||
|
osg::ref_ptr<osg::Group> mParentNode;
|
||||||
|
|
||||||
int getSubModeFromId (const std::string& id) const;
|
int getSubModeFromId (const std::string& id) const;
|
||||||
|
|
||||||
|
@ -44,10 +57,12 @@ namespace CSVRender
|
||||||
|
|
||||||
osg::Vec3f getSelectionCenter(const std::vector<osg::ref_ptr<TagBase> >& selection) const;
|
osg::Vec3f getSelectionCenter(const std::vector<osg::ref_ptr<TagBase> >& selection) const;
|
||||||
osg::Vec3f getScreenCoords(const osg::Vec3f& pos);
|
osg::Vec3f getScreenCoords(const osg::Vec3f& pos);
|
||||||
|
void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||||
|
float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
InstanceMode (WorldspaceWidget *worldspaceWidget, QWidget *parent = 0);
|
InstanceMode (WorldspaceWidget *worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget *parent = 0);
|
||||||
|
|
||||||
virtual void activate (CSVWidget::SceneToolbar *toolbar);
|
virtual void activate (CSVWidget::SceneToolbar *toolbar);
|
||||||
|
|
||||||
|
@ -93,6 +108,24 @@ namespace CSVRender
|
||||||
|
|
||||||
void subModeChanged (const std::string& id);
|
void subModeChanged (const std::string& id);
|
||||||
void deleteSelectedInstances(bool active);
|
void deleteSelectedInstances(bool active);
|
||||||
|
void dropSelectedInstancesToCollision();
|
||||||
|
void dropSelectedInstancesToTerrain();
|
||||||
|
void dropSelectedInstancesToCollisionSeparately();
|
||||||
|
void dropSelectedInstancesToTerrainSeparately();
|
||||||
|
void handleDropMethod(DropMode dropMode, QString commandMsg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// \brief Helper class to handle object mask data in safe way
|
||||||
|
class DropObjectDataHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DropObjectDataHandler(WorldspaceWidget* worldspacewidget);
|
||||||
|
~DropObjectDataHandler();
|
||||||
|
std::vector<float> mObjectHeights;
|
||||||
|
|
||||||
|
private:
|
||||||
|
WorldspaceWidget* mWorldspaceWidget;
|
||||||
|
std::vector<osg::Node::NodeMask> mOldMasks;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -477,6 +477,16 @@ bool CSVRender::Object::getSelected() const
|
||||||
return mSelected;
|
return mSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Group> CSVRender::Object::getRootNode()
|
||||||
|
{
|
||||||
|
return mRootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Group> CSVRender::Object::getBaseNode()
|
||||||
|
{
|
||||||
|
return mBaseNode;
|
||||||
|
}
|
||||||
|
|
||||||
bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft,
|
bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft,
|
||||||
const QModelIndex& bottomRight)
|
const QModelIndex& bottomRight)
|
||||||
{
|
{
|
||||||
|
|
|
@ -146,6 +146,12 @@ namespace CSVRender
|
||||||
|
|
||||||
bool getSelected() const;
|
bool getSelected() const;
|
||||||
|
|
||||||
|
/// Get object node with GUI graphics
|
||||||
|
osg::ref_ptr<osg::Group> getRootNode();
|
||||||
|
|
||||||
|
/// Get object node without GUI graphics
|
||||||
|
osg::ref_ptr<osg::Group> getBaseNode();
|
||||||
|
|
||||||
/// \return Did this call result in a modification of the visual representation of
|
/// \return Did this call result in a modification of the visual representation of
|
||||||
/// this object?
|
/// this object?
|
||||||
bool referenceableDataChanged (const QModelIndex& topLeft,
|
bool referenceableDataChanged (const QModelIndex& topLeft,
|
||||||
|
|
|
@ -370,7 +370,7 @@ void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons (
|
||||||
void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool)
|
void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool)
|
||||||
{
|
{
|
||||||
/// \todo replace EditMode with suitable subclasses
|
/// \todo replace EditMode with suitable subclasses
|
||||||
tool->addButton (new InstanceMode (this, tool), "object");
|
tool->addButton (new InstanceMode (this, mRootNode, tool), "object");
|
||||||
tool->addButton (new PathgridMode (this, tool), "pathgrid");
|
tool->addButton (new PathgridMode (this, tool), "pathgrid");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ add_openmw_dir (mwrender
|
||||||
actors objects renderingmanager animation rotatecontroller sky npcanimation
|
actors objects renderingmanager animation rotatecontroller sky npcanimation
|
||||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
||||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
||||||
renderbin actoranimation landmanager navmesh actorspaths
|
renderbin actoranimation landmanager navmesh actorspaths recastmesh
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwinput
|
add_openmw_dir (mwinput
|
||||||
|
@ -70,7 +70,9 @@ add_openmw_dir (mwworld
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwphysics
|
add_openmw_dir (mwphysics
|
||||||
physicssystem trace collisiontype actor convert object heightfield
|
physicssystem trace collisiontype actor convert object heightfield closestnotmerayresultcallback
|
||||||
|
contacttestresultcallback deepestnotmecontacttestresultcallback stepper movementsolver
|
||||||
|
closestnotmeconvexresultcallback
|
||||||
)
|
)
|
||||||
|
|
||||||
add_openmw_dir (mwclass
|
add_openmw_dir (mwclass
|
||||||
|
|
|
@ -381,7 +381,6 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
return;
|
return;
|
||||||
const ESM::ContainerState& state2 = dynamic_cast<const ESM::ContainerState&> (state);
|
|
||||||
|
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
|
@ -390,21 +389,21 @@ namespace MWClass
|
||||||
ptr.getRefData().setCustomData (data.release());
|
ptr.getRefData().setCustomData (data.release());
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic_cast<ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
|
ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
|
||||||
readState (state2.mInventory);
|
const ESM::ContainerState& containerState = state.asContainerState();
|
||||||
|
customData.mContainerStore.readState (containerState.mInventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||||
{
|
{
|
||||||
ESM::ContainerState& state2 = dynamic_cast<ESM::ContainerState&> (state);
|
|
||||||
|
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
state.mHasCustomState = false;
|
state.mHasCustomState = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dynamic_cast<const ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
|
const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
|
||||||
writeState (state2.mInventory);
|
ESM::ContainerState& containerState = state.asContainerState();
|
||||||
|
customData.mContainerStore.writeState (containerState.mInventory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -964,8 +964,6 @@ namespace MWClass
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::CreatureState& state2 = dynamic_cast<const ESM::CreatureState&> (state);
|
|
||||||
|
|
||||||
if (state.mVersion > 0)
|
if (state.mVersion > 0)
|
||||||
{
|
{
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
|
@ -985,16 +983,14 @@ namespace MWClass
|
||||||
ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
|
ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
|
||||||
|
|
||||||
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||||
|
const ESM::CreatureState& creatureState = state.asCreatureState();
|
||||||
customData.mContainerStore->readState (state2.mInventory);
|
customData.mContainerStore->readState (creatureState.mInventory);
|
||||||
customData.mCreatureStats.readState (state2.mCreatureStats);
|
customData.mCreatureStats.readState (creatureState.mCreatureStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
||||||
const
|
const
|
||||||
{
|
{
|
||||||
ESM::CreatureState& state2 = dynamic_cast<ESM::CreatureState&> (state);
|
|
||||||
|
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
state.mHasCustomState = false;
|
state.mHasCustomState = false;
|
||||||
|
@ -1002,9 +998,9 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||||
|
ESM::CreatureState& creatureState = state.asCreatureState();
|
||||||
customData.mContainerStore->writeState (state2.mInventory);
|
customData.mContainerStore->writeState (creatureState.mInventory);
|
||||||
customData.mCreatureStats.writeState (state2.mCreatureStats);
|
customData.mCreatureStats.writeState (creatureState.mCreatureStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const
|
int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const
|
||||||
|
|
|
@ -160,19 +160,16 @@ namespace MWClass
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::CreatureLevListState& state2 = dynamic_cast<const ESM::CreatureLevListState&> (state);
|
|
||||||
|
|
||||||
ensureCustomData(ptr);
|
ensureCustomData(ptr);
|
||||||
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||||
customData.mSpawnActorId = state2.mSpawnActorId;
|
const ESM::CreatureLevListState& levListState = state.asCreatureLevListState();
|
||||||
customData.mSpawn = state2.mSpawn;
|
customData.mSpawnActorId = levListState.mSpawnActorId;
|
||||||
|
customData.mSpawn = levListState.mSpawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
||||||
const
|
const
|
||||||
{
|
{
|
||||||
ESM::CreatureLevListState& state2 = dynamic_cast<ESM::CreatureLevListState&> (state);
|
|
||||||
|
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
state.mHasCustomState = false;
|
state.mHasCustomState = false;
|
||||||
|
@ -180,7 +177,8 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||||
state2.mSpawnActorId = customData.mSpawnActorId;
|
ESM::CreatureLevListState& levListState = state.asCreatureLevListState();
|
||||||
state2.mSpawn = customData.mSpawn;
|
levListState.mSpawnActorId = customData.mSpawnActorId;
|
||||||
|
levListState.mSpawn = customData.mSpawn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -461,11 +461,11 @@ namespace MWClass
|
||||||
{
|
{
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ensureCustomData(ptr);
|
ensureCustomData(ptr);
|
||||||
DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
||||||
|
const ESM::DoorState& doorState = state.asDoorState();
|
||||||
const ESM::DoorState& state2 = dynamic_cast<const ESM::DoorState&>(state);
|
customData.mDoorState = MWWorld::DoorState(doorState.mDoorState);
|
||||||
customData.mDoorState = static_cast<MWWorld::DoorState>(state2.mDoorState);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||||
|
@ -475,10 +475,10 @@ namespace MWClass
|
||||||
state.mHasCustomState = false;
|
state.mHasCustomState = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
|
||||||
|
|
||||||
ESM::DoorState& state2 = dynamic_cast<ESM::DoorState&>(state);
|
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
||||||
state2.mDoorState = static_cast<int>(customData.mDoorState);
|
ESM::DoorState& doorState = state.asDoorState();
|
||||||
|
doorState.mDoorState = int(customData.mDoorState);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1522,8 +1522,6 @@ namespace MWClass
|
||||||
if (!state.mHasCustomState)
|
if (!state.mHasCustomState)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const ESM::NpcState& state2 = dynamic_cast<const ESM::NpcState&> (state);
|
|
||||||
|
|
||||||
if (state.mVersion > 0)
|
if (state.mVersion > 0)
|
||||||
{
|
{
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
|
@ -1537,17 +1535,15 @@ namespace MWClass
|
||||||
ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
|
ensureCustomData(ptr); // in openmw 0.30 savegames not all state was saved yet, so need to load it regardless.
|
||||||
|
|
||||||
NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||||
|
const ESM::NpcState& npcState = state.asNpcState();
|
||||||
customData.mInventoryStore.readState (state2.mInventory);
|
customData.mInventoryStore.readState (npcState.mInventory);
|
||||||
customData.mNpcStats.readState (state2.mNpcStats);
|
customData.mNpcStats.readState (npcState.mNpcStats);
|
||||||
static_cast<MWMechanics::CreatureStats&> (customData.mNpcStats).readState (state2.mCreatureStats);
|
customData.mNpcStats.readState (npcState.mCreatureStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
||||||
const
|
const
|
||||||
{
|
{
|
||||||
ESM::NpcState& state2 = dynamic_cast<ESM::NpcState&> (state);
|
|
||||||
|
|
||||||
if (!ptr.getRefData().getCustomData())
|
if (!ptr.getRefData().getCustomData())
|
||||||
{
|
{
|
||||||
state.mHasCustomState = false;
|
state.mHasCustomState = false;
|
||||||
|
@ -1555,10 +1551,10 @@ namespace MWClass
|
||||||
}
|
}
|
||||||
|
|
||||||
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||||
|
ESM::NpcState& npcState = state.asNpcState();
|
||||||
customData.mInventoryStore.writeState (state2.mInventory);
|
customData.mInventoryStore.writeState (npcState.mInventory);
|
||||||
customData.mNpcStats.writeState (state2.mNpcStats);
|
customData.mNpcStats.writeState (npcState.mNpcStats);
|
||||||
static_cast<const MWMechanics::CreatureStats&> (customData.mNpcStats).writeState (state2.mCreatureStats);
|
customData.mNpcStats.writeState (npcState.mCreatureStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const
|
int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <MyGUI_Gui.h>
|
#include <MyGUI_Gui.h>
|
||||||
#include <MyGUI_Button.h>
|
#include <MyGUI_Button.h>
|
||||||
#include <MyGUI_EditBox.h>
|
#include <MyGUI_EditBox.h>
|
||||||
|
#include <MyGUI_ComboBox.h>
|
||||||
#include <MyGUI_ControllerManager.h>
|
#include <MyGUI_ControllerManager.h>
|
||||||
#include <MyGUI_ControllerRepeatClick.h>
|
#include <MyGUI_ControllerRepeatClick.h>
|
||||||
|
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
#include "../mwworld/class.hpp"
|
#include "../mwworld/class.hpp"
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#include <MyGUI_Macros.h>
|
||||||
#include <components/esm/records.hpp>
|
#include <components/esm/records.hpp>
|
||||||
|
|
||||||
#include "inventoryitemmodel.hpp"
|
#include "inventoryitemmodel.hpp"
|
||||||
|
@ -41,6 +43,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
AlchemyWindow::AlchemyWindow()
|
AlchemyWindow::AlchemyWindow()
|
||||||
: WindowBase("openmw_alchemy_window.layout")
|
: WindowBase("openmw_alchemy_window.layout")
|
||||||
|
, mModel(nullptr)
|
||||||
, mSortModel(nullptr)
|
, mSortModel(nullptr)
|
||||||
, mAlchemy(new MWMechanics::Alchemy())
|
, mAlchemy(new MWMechanics::Alchemy())
|
||||||
, mApparatus (4)
|
, mApparatus (4)
|
||||||
|
@ -62,6 +65,8 @@ namespace MWGui
|
||||||
getWidget(mDecreaseButton, "DecreaseButton");
|
getWidget(mDecreaseButton, "DecreaseButton");
|
||||||
getWidget(mNameEdit, "NameEdit");
|
getWidget(mNameEdit, "NameEdit");
|
||||||
getWidget(mItemView, "ItemView");
|
getWidget(mItemView, "ItemView");
|
||||||
|
getWidget(mFilterValue, "FilterValue");
|
||||||
|
getWidget(mFilterType, "FilterType");
|
||||||
|
|
||||||
mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged);
|
mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged);
|
||||||
mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
||||||
|
@ -84,6 +89,9 @@ namespace MWGui
|
||||||
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
|
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
|
||||||
|
|
||||||
mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
mNameEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
||||||
|
mFilterValue->eventComboChangePosition += MyGUI::newDelegate(this, &AlchemyWindow::onFilterChanged);
|
||||||
|
mFilterValue->eventEditTextChange += MyGUI::newDelegate(this, &AlchemyWindow::onFilterEdited);
|
||||||
|
mFilterType->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::switchFilterType);
|
||||||
|
|
||||||
center();
|
center();
|
||||||
}
|
}
|
||||||
|
@ -186,16 +194,110 @@ namespace MWGui
|
||||||
removeIngredient(mIngredients[i]);
|
removeIngredient(mIngredients[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFilters();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AlchemyWindow::initFilter()
|
||||||
|
{
|
||||||
|
auto const& wm = MWBase::Environment::get().getWindowManager();
|
||||||
|
auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients");
|
||||||
|
auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects");
|
||||||
|
|
||||||
|
if (mFilterType->getCaption() == ingredient)
|
||||||
|
mCurrentFilter = FilterType::ByName;
|
||||||
|
else
|
||||||
|
mCurrentFilter = FilterType::ByEffect;
|
||||||
|
updateFilters();
|
||||||
|
mFilterValue->clearIndexSelected();
|
||||||
|
updateFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlchemyWindow::switchFilterType(MyGUI::Widget* _sender)
|
||||||
|
{
|
||||||
|
auto const& wm = MWBase::Environment::get().getWindowManager();
|
||||||
|
auto const ingredient = wm->getGameSettingString("sIngredients", "Ingredients");
|
||||||
|
auto const effect = wm->getGameSettingString("sMagicEffects", "Magic Effects");
|
||||||
|
auto *button = _sender->castType<MyGUI::Button>();
|
||||||
|
|
||||||
|
if (button->getCaption() == ingredient)
|
||||||
|
{
|
||||||
|
button->setCaption(effect);
|
||||||
|
mCurrentFilter = FilterType::ByEffect;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
button->setCaption(ingredient);
|
||||||
|
mCurrentFilter = FilterType::ByName;
|
||||||
|
}
|
||||||
|
mSortModel->setNameFilter({});
|
||||||
|
mSortModel->setEffectFilter({});
|
||||||
|
mFilterValue->clearIndexSelected();
|
||||||
|
updateFilters();
|
||||||
|
mItemView->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlchemyWindow::updateFilters()
|
||||||
|
{
|
||||||
|
std::set<std::string> itemNames, itemEffects;
|
||||||
|
for (size_t i = 0; i < mModel->getItemCount(); ++i)
|
||||||
|
{
|
||||||
|
auto const& base = mModel->getItem(i).mBase;
|
||||||
|
if (base.getTypeName() != typeid(ESM::Ingredient).name())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
itemNames.insert(base.getClass().getName(base));
|
||||||
|
|
||||||
|
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
|
||||||
|
auto const alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
|
||||||
|
|
||||||
|
auto const effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill);
|
||||||
|
itemEffects.insert(effects.begin(), effects.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
mFilterValue->removeAllItems();
|
||||||
|
auto const addItems = [&](auto const& container)
|
||||||
|
{
|
||||||
|
for (auto const& item : container)
|
||||||
|
mFilterValue->addItem(item);
|
||||||
|
};
|
||||||
|
switch (mCurrentFilter)
|
||||||
|
{
|
||||||
|
case FilterType::ByName: addItems(itemNames); break;
|
||||||
|
case FilterType::ByEffect: addItems(itemEffects); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlchemyWindow::applyFilter(const std::string& filter)
|
||||||
|
{
|
||||||
|
switch (mCurrentFilter)
|
||||||
|
{
|
||||||
|
case FilterType::ByName: mSortModel->setNameFilter(filter); break;
|
||||||
|
case FilterType::ByEffect: mSortModel->setEffectFilter(filter); break;
|
||||||
|
}
|
||||||
|
mItemView->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlchemyWindow::onFilterChanged(MyGUI::ComboBox* _sender, size_t _index)
|
||||||
|
{
|
||||||
|
// ignore spurious event fired when one edit the content after selection.
|
||||||
|
// onFilterEdited will handle it.
|
||||||
|
if (_index != MyGUI::ITEM_NONE)
|
||||||
|
applyFilter(_sender->getItemNameAt(_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AlchemyWindow::onFilterEdited(MyGUI::EditBox* _sender)
|
||||||
|
{
|
||||||
|
applyFilter(_sender->getCaption());
|
||||||
|
}
|
||||||
|
|
||||||
void AlchemyWindow::onOpen()
|
void AlchemyWindow::onOpen()
|
||||||
{
|
{
|
||||||
mAlchemy->clear();
|
mAlchemy->clear();
|
||||||
mAlchemy->setAlchemist (MWMechanics::getPlayer());
|
mAlchemy->setAlchemist (MWMechanics::getPlayer());
|
||||||
|
|
||||||
InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer());
|
mModel = new InventoryItemModel(MWMechanics::getPlayer());
|
||||||
mSortModel = new SortFilterItemModel(model);
|
mSortModel = new SortFilterItemModel(mModel);
|
||||||
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
|
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
|
||||||
mItemView->setModel (mSortModel);
|
mItemView->setModel (mSortModel);
|
||||||
mItemView->resetScrollBars();
|
mItemView->resetScrollBars();
|
||||||
|
@ -217,6 +319,7 @@ namespace MWGui
|
||||||
}
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
initFilter();
|
||||||
|
|
||||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
|
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <MyGUI_ControllerItem.h>
|
#include <MyGUI_ControllerItem.h>
|
||||||
|
#include <MyGUI_ComboBox.h>
|
||||||
|
|
||||||
|
#include <components/widgets/box.hpp>
|
||||||
#include <components/widgets/numericeditbox.hpp>
|
#include <components/widgets/numericeditbox.hpp>
|
||||||
|
|
||||||
#include "windowbase.hpp"
|
#include "windowbase.hpp"
|
||||||
|
@ -19,6 +21,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
class ItemView;
|
class ItemView;
|
||||||
class ItemWidget;
|
class ItemWidget;
|
||||||
|
class InventoryItemModel;
|
||||||
class SortFilterItemModel;
|
class SortFilterItemModel;
|
||||||
|
|
||||||
class AlchemyWindow : public WindowBase
|
class AlchemyWindow : public WindowBase
|
||||||
|
@ -36,8 +39,11 @@ namespace MWGui
|
||||||
static const float sCountChangeInterval; // in seconds
|
static const float sCountChangeInterval; // in seconds
|
||||||
|
|
||||||
std::string mSuggestedPotionName;
|
std::string mSuggestedPotionName;
|
||||||
|
enum class FilterType { ByName, ByEffect };
|
||||||
|
FilterType mCurrentFilter;
|
||||||
|
|
||||||
ItemView* mItemView;
|
ItemView* mItemView;
|
||||||
|
InventoryItemModel* mModel;
|
||||||
SortFilterItemModel* mSortModel;
|
SortFilterItemModel* mSortModel;
|
||||||
|
|
||||||
MyGUI::Button* mCreateButton;
|
MyGUI::Button* mCreateButton;
|
||||||
|
@ -47,6 +53,8 @@ namespace MWGui
|
||||||
|
|
||||||
MyGUI::Button* mIncreaseButton;
|
MyGUI::Button* mIncreaseButton;
|
||||||
MyGUI::Button* mDecreaseButton;
|
MyGUI::Button* mDecreaseButton;
|
||||||
|
Gui::AutoSizedButton* mFilterType;
|
||||||
|
MyGUI::ComboBox* mFilterValue;
|
||||||
MyGUI::EditBox* mNameEdit;
|
MyGUI::EditBox* mNameEdit;
|
||||||
Gui::NumericEditBox* mBrewCountEdit;
|
Gui::NumericEditBox* mBrewCountEdit;
|
||||||
|
|
||||||
|
@ -60,6 +68,13 @@ namespace MWGui
|
||||||
void onCountValueChanged(int value);
|
void onCountValueChanged(int value);
|
||||||
void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller);
|
void onRepeatClick(MyGUI::Widget* widget, MyGUI::ControllerItem* controller);
|
||||||
|
|
||||||
|
void applyFilter(const std::string& filter);
|
||||||
|
void initFilter();
|
||||||
|
void onFilterChanged(MyGUI::ComboBox* _sender, size_t _index);
|
||||||
|
void onFilterEdited(MyGUI::EditBox* _sender);
|
||||||
|
void switchFilterType(MyGUI::Widget* _sender);
|
||||||
|
void updateFilters();
|
||||||
|
|
||||||
void addRepeatController(MyGUI::Widget* widget);
|
void addRepeatController(MyGUI::Widget* widget);
|
||||||
|
|
||||||
void onIncreaseButtonTriggered();
|
void onIncreaseButtonTriggered();
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
#include "../mwworld/nullaction.hpp"
|
#include "../mwworld/nullaction.hpp"
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
|
||||||
|
#include "../mwmechanics/alchemy.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
bool compareType(const std::string& type1, const std::string& type2)
|
bool compareType(const std::string& type1, const std::string& type2)
|
||||||
|
@ -151,6 +153,8 @@ namespace MWGui
|
||||||
: mCategory(Category_All)
|
: mCategory(Category_All)
|
||||||
, mFilter(0)
|
, mFilter(0)
|
||||||
, mSortByType(true)
|
, mSortByType(true)
|
||||||
|
, mNameFilter("")
|
||||||
|
, mEffectFilter("")
|
||||||
{
|
{
|
||||||
mSourceModel = sourceModel;
|
mSourceModel = sourceModel;
|
||||||
}
|
}
|
||||||
|
@ -199,8 +203,39 @@ namespace MWGui
|
||||||
if (!(category & mCategory))
|
if (!(category & mCategory))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if ((mFilter & Filter_OnlyIngredients) && base.getTypeName() != typeid(ESM::Ingredient).name())
|
if (mFilter & Filter_OnlyIngredients)
|
||||||
|
{
|
||||||
|
if (base.getTypeName() != typeid(ESM::Ingredient).name())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (!mNameFilter.empty() && !mEffectFilter.empty())
|
||||||
|
throw std::logic_error("name and magic effect filter are mutually exclusive");
|
||||||
|
|
||||||
|
if (!mNameFilter.empty())
|
||||||
|
{
|
||||||
|
const auto itemName = Misc::StringUtils::lowerCase(base.getClass().getName(base));
|
||||||
|
return itemName.find(mNameFilter) != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mEffectFilter.empty())
|
||||||
|
{
|
||||||
|
MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr();
|
||||||
|
const auto alchemySkill = player.getClass().getSkill(player, ESM::Skill::Alchemy);
|
||||||
|
|
||||||
|
const auto effects = MWMechanics::Alchemy::effectsDescription(base, alchemySkill);
|
||||||
|
|
||||||
|
for (const auto& effect : effects)
|
||||||
|
{
|
||||||
|
const auto ciEffect = Misc::StringUtils::lowerCase(effect);
|
||||||
|
|
||||||
|
if (ciEffect.find(mEffectFilter) != std::string::npos)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted))
|
if ((mFilter & Filter_OnlyEnchanted) && !(item.mFlags & ItemStack::Flag_Enchanted))
|
||||||
return false;
|
return false;
|
||||||
if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
|
if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
|
||||||
|
@ -286,6 +321,11 @@ namespace MWGui
|
||||||
mNameFilter = Misc::StringUtils::lowerCase(filter);
|
mNameFilter = Misc::StringUtils::lowerCase(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SortFilterItemModel::setEffectFilter (const std::string& filter)
|
||||||
|
{
|
||||||
|
mEffectFilter = Misc::StringUtils::lowerCase(filter);
|
||||||
|
}
|
||||||
|
|
||||||
void SortFilterItemModel::update()
|
void SortFilterItemModel::update()
|
||||||
{
|
{
|
||||||
mSourceModel->update();
|
mSourceModel->update();
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace MWGui
|
||||||
void setCategory (int category);
|
void setCategory (int category);
|
||||||
void setFilter (int filter);
|
void setFilter (int filter);
|
||||||
void setNameFilter (const std::string& filter);
|
void setNameFilter (const std::string& filter);
|
||||||
|
void setEffectFilter (const std::string& filter);
|
||||||
|
|
||||||
/// Use ItemStack::Type for sorting?
|
/// Use ItemStack::Type for sorting?
|
||||||
void setSortByType(bool sort) { mSortByType = sort; }
|
void setSortByType(bool sort) { mSortByType = sort; }
|
||||||
|
@ -60,6 +61,7 @@ namespace MWGui
|
||||||
bool mSortByType;
|
bool mSortByType;
|
||||||
|
|
||||||
std::string mNameFilter; // filter by item name
|
std::string mNameFilter; // filter by item name
|
||||||
|
std::string mEffectFilter; // filter by magic effect
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -267,9 +267,9 @@ namespace MWGui
|
||||||
|
|
||||||
void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character)
|
void WaitDialog::onKeyButtonPressed(MyGUI::Widget *sender, MyGUI::KeyCode key, MyGUI::Char character)
|
||||||
{
|
{
|
||||||
if (key == MyGUI::KeyCode::ArrowDown)
|
if (key == MyGUI::KeyCode::ArrowUp)
|
||||||
mHourSlider->setScrollPosition(std::min(mHourSlider->getScrollPosition()+1, mHourSlider->getScrollRange()-1));
|
mHourSlider->setScrollPosition(std::min(mHourSlider->getScrollPosition()+1, mHourSlider->getScrollRange()-1));
|
||||||
else if (key == MyGUI::KeyCode::ArrowUp)
|
else if (key == MyGUI::KeyCode::ArrowDown)
|
||||||
mHourSlider->setScrollPosition(std::max(static_cast<int>(mHourSlider->getScrollPosition())-1, 0));
|
mHourSlider->setScrollPosition(std::max(static_cast<int>(mHourSlider->getScrollPosition())-1, 0));
|
||||||
else
|
else
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -2044,6 +2044,8 @@ namespace MWMechanics
|
||||||
stats.getActiveSpells().visitEffectSources(soulTrap);
|
stats.getActiveSpells().visitEffectSources(soulTrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Magic effects will be reset later, and the magic effect that could kill the actor
|
||||||
|
// needs to be determined now
|
||||||
calculateCreatureStatModifiers(iter->first, 0);
|
calculateCreatureStatModifiers(iter->first, 0);
|
||||||
|
|
||||||
if (cls.isEssential(iter->first))
|
if (cls.isEssential(iter->first))
|
||||||
|
@ -2072,7 +2074,10 @@ namespace MWMechanics
|
||||||
// Make sure spell effects are removed
|
// Make sure spell effects are removed
|
||||||
purgeSpellEffects(stats.getActorId());
|
purgeSpellEffects(stats.getActorId());
|
||||||
|
|
||||||
|
// Reset dynamic stats, attributes and skills
|
||||||
calculateCreatureStatModifiers(iter->first, 0);
|
calculateCreatureStatModifiers(iter->first, 0);
|
||||||
|
if (iter->first.getClass().isNpc())
|
||||||
|
calculateNpcStatModifiers(iter->first, 0);
|
||||||
|
|
||||||
if( iter->first == getPlayer())
|
if( iter->first == getPlayer())
|
||||||
{
|
{
|
||||||
|
|
|
@ -623,3 +623,37 @@ std::string MWMechanics::Alchemy::suggestPotionName()
|
||||||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||||
ESM::MagicEffect::effectIdToString(effectId))->mValue.getString();
|
ESM::MagicEffect::effectIdToString(effectId))->mValue.getString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> MWMechanics::Alchemy::effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySkill)
|
||||||
|
{
|
||||||
|
std::vector<std::string> effects;
|
||||||
|
|
||||||
|
const auto& item = ptr.get<ESM::Ingredient>()->mBase;
|
||||||
|
const auto& gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
|
const static auto fWortChanceValue = gmst.find("fWortChanceValue")->mValue.getFloat();
|
||||||
|
const auto& data = item->mData;
|
||||||
|
|
||||||
|
for (auto i = 0; i < 4; ++i)
|
||||||
|
{
|
||||||
|
const auto effectID = data.mEffectID[i];
|
||||||
|
const auto skillID = data.mSkills[i];
|
||||||
|
const auto attributeID = data.mAttributes[i];
|
||||||
|
|
||||||
|
if (alchemySkill < fWortChanceValue * (i + 1))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (effectID != -1)
|
||||||
|
{
|
||||||
|
std::string effect = gmst.find(ESM::MagicEffect::effectIdToString(effectID))->mValue.getString();
|
||||||
|
|
||||||
|
if (skillID != -1)
|
||||||
|
effect += " " + gmst.find(ESM::Skill::sSkillNameIds[skillID])->mValue.getString();
|
||||||
|
else if (attributeID != -1)
|
||||||
|
effect += " " + gmst.find(ESM::Attribute::sGmstAttributeIds[attributeID])->mValue.getString();
|
||||||
|
|
||||||
|
effects.push_back(effect);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return effects;
|
||||||
|
}
|
||||||
|
|
|
@ -152,6 +152,8 @@ namespace MWMechanics
|
||||||
///< Try to create potions from the ingredients, place them in the inventory of the alchemist and
|
///< Try to create potions from the ingredients, place them in the inventory of the alchemist and
|
||||||
/// adjust the skills of the alchemist accordingly.
|
/// adjust the skills of the alchemist accordingly.
|
||||||
/// \param name must not be an empty string, or Result_NoName is returned
|
/// \param name must not be an empty string, or Result_NoName is returned
|
||||||
|
|
||||||
|
static std::vector<std::string> effectsDescription (const MWWorld::ConstPtr &ptr, const int alchemySKill);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -527,6 +527,11 @@ void MWMechanics::NpcStats::setTimeToStartDrowning(float time)
|
||||||
mTimeToStartDrowning=time;
|
mTimeToStartDrowning=time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MWMechanics::NpcStats::writeState (ESM::CreatureStats& state) const
|
||||||
|
{
|
||||||
|
CreatureStats::writeState(state);
|
||||||
|
}
|
||||||
|
|
||||||
void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
|
void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
|
||||||
{
|
{
|
||||||
for (std::map<std::string, int>::const_iterator iter (mFactionRank.begin());
|
for (std::map<std::string, int>::const_iterator iter (mFactionRank.begin());
|
||||||
|
@ -566,6 +571,10 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
|
||||||
|
|
||||||
state.mTimeToStartDrowning = mTimeToStartDrowning;
|
state.mTimeToStartDrowning = mTimeToStartDrowning;
|
||||||
}
|
}
|
||||||
|
void MWMechanics::NpcStats::readState (const ESM::CreatureStats& state)
|
||||||
|
{
|
||||||
|
CreatureStats::readState(state);
|
||||||
|
}
|
||||||
|
|
||||||
void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
|
void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
|
||||||
{
|
{
|
||||||
|
|
|
@ -171,8 +171,10 @@ namespace MWMechanics
|
||||||
/// @param time value from [0,20]
|
/// @param time value from [0,20]
|
||||||
void setTimeToStartDrowning(float time);
|
void setTimeToStartDrowning(float time);
|
||||||
|
|
||||||
|
void writeState (ESM::CreatureStats& state) const;
|
||||||
void writeState (ESM::NpcStats& state) const;
|
void writeState (ESM::NpcStats& state) const;
|
||||||
|
|
||||||
|
void readState (const ESM::CreatureStats& state);
|
||||||
void readState (const ESM::NpcStats& state);
|
void readState (const ESM::NpcStats& state);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
34
apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp
Normal file
34
apps/openmw/mwphysics/closestnotmeconvexresultcallback.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "closestnotmeconvexresultcallback.hpp"
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
ClosestNotMeConvexResultCallback::ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot)
|
||||||
|
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
|
||||||
|
mMe(me), mMotion(motion), mMinCollisionDot(minCollisionDot)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar ClosestNotMeConvexResultCallback::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
|
||||||
|
{
|
||||||
|
if (convexResult.m_hitCollisionObject == mMe)
|
||||||
|
return btScalar(1);
|
||||||
|
|
||||||
|
btVector3 hitNormalWorld;
|
||||||
|
if (normalInWorldSpace)
|
||||||
|
hitNormalWorld = convexResult.m_hitNormalLocal;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
///need to transform normal into worldspace
|
||||||
|
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dot product of the motion vector against the collision contact normal
|
||||||
|
btScalar dotCollision = mMotion.dot(hitNormalWorld);
|
||||||
|
if (dotCollision <= mMinCollisionDot)
|
||||||
|
return btScalar(1);
|
||||||
|
|
||||||
|
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
||||||
|
}
|
||||||
|
}
|
24
apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp
Normal file
24
apps/openmw/mwphysics/closestnotmeconvexresultcallback.hpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
|
||||||
|
#define OPENMW_MWPHYSICS_CLOSESTNOTMECONVEXRESULTCALLBACK_H
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &motion, btScalar minCollisionDot);
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const btCollisionObject *mMe;
|
||||||
|
const btVector3 mMotion;
|
||||||
|
const btScalar mMinCollisionDot;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
34
apps/openmw/mwphysics/closestnotmerayresultcallback.cpp
Normal file
34
apps/openmw/mwphysics/closestnotmerayresultcallback.cpp
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
#include "closestnotmerayresultcallback.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
ClosestNotMeRayResultCallback::ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to)
|
||||||
|
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
||||||
|
, mMe(me), mTargets(targets)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar ClosestNotMeRayResultCallback::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
|
||||||
|
{
|
||||||
|
if (rayResult.m_collisionObject == mMe)
|
||||||
|
return 1.f;
|
||||||
|
if (!mTargets.empty())
|
||||||
|
{
|
||||||
|
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
||||||
|
{
|
||||||
|
PtrHolder* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
|
||||||
|
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
|
||||||
|
return 1.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||||
|
}
|
||||||
|
}
|
24
apps/openmw/mwphysics/closestnotmerayresultcallback.hpp
Normal file
24
apps/openmw/mwphysics/closestnotmerayresultcallback.hpp
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H
|
||||||
|
#define OPENMW_MWPHYSICS_CLOSESTNOTMERAYRESULTCALLBACK_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to);
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace);
|
||||||
|
private:
|
||||||
|
const btCollisionObject* mMe;
|
||||||
|
const std::vector<const btCollisionObject*> mTargets;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
16
apps/openmw/mwphysics/constants.hpp
Normal file
16
apps/openmw/mwphysics/constants.hpp
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_CONSTANTS_H
|
||||||
|
#define OPENMW_MWPHYSICS_CONSTANTS_H
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
static const float sStepSizeUp = 34.0f;
|
||||||
|
static const float sStepSizeDown = 62.0f;
|
||||||
|
static const float sMinStep = 10.f;
|
||||||
|
static const float sGroundOffset = 1.0f;
|
||||||
|
static const float sMaxSlope = 49.0f;
|
||||||
|
|
||||||
|
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
||||||
|
static const int sMaxIterations = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
27
apps/openmw/mwphysics/contacttestresultcallback.cpp
Normal file
27
apps/openmw/mwphysics/contacttestresultcallback.cpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#include "contacttestresultcallback.hpp"
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
ContactTestResultCallback::ContactTestResultCallback(const btCollisionObject* testedAgainst)
|
||||||
|
: mTestedAgainst(testedAgainst)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar ContactTestResultCallback::addSingleResult(btManifoldPoint& cp,
|
||||||
|
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
||||||
|
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
|
||||||
|
{
|
||||||
|
const btCollisionObject* collisionObject = col0Wrap->m_collisionObject;
|
||||||
|
if (collisionObject == mTestedAgainst)
|
||||||
|
collisionObject = col1Wrap->m_collisionObject;
|
||||||
|
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
|
||||||
|
if (holder)
|
||||||
|
mResult.push_back(holder->getPtr());
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
apps/openmw/mwphysics/contacttestresultcallback.hpp
Normal file
30
apps/openmw/mwphysics/contacttestresultcallback.hpp
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_CONTACTTESTRESULTCALLBACK_H
|
||||||
|
#define OPENMW_MWPHYSICS_CONTACTTESTRESULTCALLBACK_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
struct btCollisionObjectWrapper;
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback
|
||||||
|
{
|
||||||
|
const btCollisionObject* mTestedAgainst;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ContactTestResultCallback(const btCollisionObject* testedAgainst);
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btManifoldPoint& cp,
|
||||||
|
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
||||||
|
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1);
|
||||||
|
|
||||||
|
std::vector<MWWorld::Ptr> mResult;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include "deepestnotmecontacttestresultcallback.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
|
||||||
|
#include "ptrholder.hpp"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
|
||||||
|
DeepestNotMeContactTestResultCallback::DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3 &origin)
|
||||||
|
: mMe(me), mTargets(targets), mOrigin(origin), mLeastDistSqr(std::numeric_limits<float>::max())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar DeepestNotMeContactTestResultCallback::addSingleResult(btManifoldPoint& cp,
|
||||||
|
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
||||||
|
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
|
||||||
|
{
|
||||||
|
const btCollisionObject* collisionObject = col1Wrap->m_collisionObject;
|
||||||
|
if (collisionObject != mMe)
|
||||||
|
{
|
||||||
|
if (!mTargets.empty())
|
||||||
|
{
|
||||||
|
if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end()))
|
||||||
|
{
|
||||||
|
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
|
||||||
|
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA());
|
||||||
|
if(!mObject || distsqr < mLeastDistSqr)
|
||||||
|
{
|
||||||
|
mObject = collisionObject;
|
||||||
|
mLeastDistSqr = distsqr;
|
||||||
|
mContactPoint = cp.getPositionWorldOnA();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.f;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H
|
||||||
|
#define OPENMW_MWPHYSICS_DEEPESTNOTMECONTACTTESTRESULTCALLBACK_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback
|
||||||
|
{
|
||||||
|
const btCollisionObject* mMe;
|
||||||
|
const std::vector<const btCollisionObject*> mTargets;
|
||||||
|
|
||||||
|
// Store the real origin, since the shape's origin is its center
|
||||||
|
btVector3 mOrigin;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const btCollisionObject *mObject{nullptr};
|
||||||
|
btVector3 mContactPoint{0,0,0};
|
||||||
|
btScalar mLeastDistSqr;
|
||||||
|
|
||||||
|
DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3 &origin);
|
||||||
|
|
||||||
|
virtual btScalar addSingleResult(btManifoldPoint& cp,
|
||||||
|
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
||||||
|
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
324
apps/openmw/mwphysics/movementsolver.cpp
Normal file
324
apps/openmw/mwphysics/movementsolver.cpp
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
#include "movementsolver.hpp"
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
#include <BulletCollision/CollisionShapes/btCollisionShape.h>
|
||||||
|
|
||||||
|
#include <components/esm/loadgmst.hpp>
|
||||||
|
#include <components/misc/convert.hpp>
|
||||||
|
|
||||||
|
#include "../mwbase/world.hpp"
|
||||||
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
|
#include "../mwmechanics/movement.hpp"
|
||||||
|
|
||||||
|
#include "../mwworld/class.hpp"
|
||||||
|
#include "../mwworld/esmstore.hpp"
|
||||||
|
#include "../mwworld/player.hpp"
|
||||||
|
#include "../mwworld/refdata.hpp"
|
||||||
|
|
||||||
|
#include "actor.hpp"
|
||||||
|
#include "collisiontype.hpp"
|
||||||
|
#include "constants.hpp"
|
||||||
|
#include "stepper.hpp"
|
||||||
|
#include "trace.h"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
static bool isActor(const btCollisionObject *obj)
|
||||||
|
{
|
||||||
|
assert(obj);
|
||||||
|
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class Vec3>
|
||||||
|
static bool isWalkableSlope(const Vec3 &normal)
|
||||||
|
{
|
||||||
|
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
||||||
|
return (normal.z() > sMaxSlopeCos);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Vec3f MovementSolver::traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
|
||||||
|
{
|
||||||
|
osg::Vec3f offset = actor->getCollisionObjectPosition() - ptr.getRefData().getPosition().asVec3();
|
||||||
|
|
||||||
|
ActorTracer tracer;
|
||||||
|
tracer.findGround(actor, position + offset, position + offset - osg::Vec3f(0,0,maxHeight), collisionWorld);
|
||||||
|
if (tracer.mFraction >= 1.0f)
|
||||||
|
{
|
||||||
|
actor->setOnGround(false);
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
actor->setOnGround(true);
|
||||||
|
|
||||||
|
// Check if we actually found a valid spawn point (use an infinitely thin ray this time).
|
||||||
|
// Required for some broken door destinations in Morrowind.esm, where the spawn point
|
||||||
|
// intersects with other geometry if the actor's base is taken into account
|
||||||
|
btVector3 from = Misc::Convert::toBullet(position);
|
||||||
|
btVector3 to = from - btVector3(0,0,maxHeight);
|
||||||
|
|
||||||
|
btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to);
|
||||||
|
resultCallback1.m_collisionFilterGroup = 0xff;
|
||||||
|
resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap;
|
||||||
|
|
||||||
|
collisionWorld->rayTest(from, to, resultCallback1);
|
||||||
|
|
||||||
|
if (resultCallback1.hasHit() && ((Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) - tracer.mEndPos + offset).length2() > 35*35
|
||||||
|
|| !isWalkableSlope(tracer.mPlaneNormal)))
|
||||||
|
{
|
||||||
|
actor->setOnSlope(!isWalkableSlope(resultCallback1.m_hitNormalWorld));
|
||||||
|
return Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, sGroundOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
actor->setOnSlope(!isWalkableSlope(tracer.mPlaneNormal));
|
||||||
|
|
||||||
|
return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
osg::Vec3f MovementSolver::move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
|
||||||
|
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
|
||||||
|
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
|
||||||
|
{
|
||||||
|
const ESM::Position& refpos = ptr.getRefData().getPosition();
|
||||||
|
// Early-out for totally static creatures
|
||||||
|
// (Not sure if gravity should still apply?)
|
||||||
|
if (!ptr.getClass().isMobile(ptr))
|
||||||
|
return position;
|
||||||
|
|
||||||
|
// Reset per-frame data
|
||||||
|
physicActor->setWalkingOnWater(false);
|
||||||
|
// Anything to collide with?
|
||||||
|
if(!physicActor->getCollisionMode())
|
||||||
|
{
|
||||||
|
return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
|
||||||
|
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
|
||||||
|
) * movement * time;
|
||||||
|
}
|
||||||
|
|
||||||
|
const btCollisionObject *colobj = physicActor->getCollisionObject();
|
||||||
|
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
||||||
|
|
||||||
|
// NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos).
|
||||||
|
// That means the collision shape used for moving this actor is in a different spot than the collision shape
|
||||||
|
// other actors are using to collide against this actor.
|
||||||
|
// While this is strictly speaking wrong, it's needed for MW compatibility.
|
||||||
|
position.z() += halfExtents.z();
|
||||||
|
|
||||||
|
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fSwimHeightScale")->mValue.getFloat();
|
||||||
|
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
||||||
|
|
||||||
|
ActorTracer tracer;
|
||||||
|
|
||||||
|
osg::Vec3f inertia = physicActor->getInertialForce();
|
||||||
|
osg::Vec3f velocity;
|
||||||
|
|
||||||
|
if (position.z() < swimlevel || isFlying)
|
||||||
|
{
|
||||||
|
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) * osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
||||||
|
|
||||||
|
if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope())
|
||||||
|
|| (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope()))
|
||||||
|
inertia = velocity;
|
||||||
|
else if (!physicActor->getOnGround() || physicActor->getOnSlope())
|
||||||
|
velocity = velocity + inertia;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
|
||||||
|
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
|
||||||
|
velocity = osg::Vec3f(0,0,1) * 25;
|
||||||
|
|
||||||
|
if (ptr.getClass().getMovementSettings(ptr).mPosition[2])
|
||||||
|
{
|
||||||
|
const bool isPlayer = (ptr == MWMechanics::getPlayer());
|
||||||
|
// Advance acrobatics and set flag for GetPCJumping
|
||||||
|
if (isPlayer)
|
||||||
|
{
|
||||||
|
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
|
||||||
|
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease fatigue
|
||||||
|
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
|
||||||
|
{
|
||||||
|
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||||
|
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
|
||||||
|
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
|
||||||
|
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
|
||||||
|
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
|
||||||
|
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
|
||||||
|
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
||||||
|
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
|
||||||
|
}
|
||||||
|
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that we have the effective movement vector, apply wind forces to it
|
||||||
|
if (MWBase::Environment::get().getWorld()->isInStorm())
|
||||||
|
{
|
||||||
|
osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
|
||||||
|
float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length())));
|
||||||
|
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find("fStromWalkMult")->mValue.getFloat();
|
||||||
|
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
|
||||||
|
}
|
||||||
|
|
||||||
|
Stepper stepper(collisionWorld, colobj);
|
||||||
|
osg::Vec3f origVelocity = velocity;
|
||||||
|
osg::Vec3f newPosition = position;
|
||||||
|
/*
|
||||||
|
* A loop to find newPosition using tracer, if successful different from the starting position.
|
||||||
|
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
|
||||||
|
* The initial velocity was set earlier (see above).
|
||||||
|
*/
|
||||||
|
float remainingTime = time;
|
||||||
|
for (int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
|
||||||
|
{
|
||||||
|
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
||||||
|
|
||||||
|
// If not able to fly, don't allow to swim up into the air
|
||||||
|
if(!isFlying && nextpos.z() > swimlevel && newPosition.z() < swimlevel)
|
||||||
|
{
|
||||||
|
const osg::Vec3f down(0,0,-1);
|
||||||
|
velocity = slide(velocity, down);
|
||||||
|
// NOTE: remainingTime is unchanged before the loop continues
|
||||||
|
continue; // velocity updated, calculate nextpos again
|
||||||
|
}
|
||||||
|
|
||||||
|
if((newPosition - nextpos).length2() > 0.0001)
|
||||||
|
{
|
||||||
|
// trace to where character would go if there were no obstructions
|
||||||
|
tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
|
||||||
|
|
||||||
|
// check for obstructions
|
||||||
|
if(tracer.mFraction >= 1.0f)
|
||||||
|
{
|
||||||
|
newPosition = tracer.mEndPos; // ok to move, so set newPosition
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The current position and next position are nearly the same, so just exit.
|
||||||
|
// Note: Bullet can trigger an assert in debug modes if the positions
|
||||||
|
// are the same, since that causes it to attempt to normalize a zero
|
||||||
|
// length vector (which can also happen with nearly identical vectors, since
|
||||||
|
// precision can be lost due to any math Bullet does internally). Since we
|
||||||
|
// aren't performing any collision detection, we want to reject the next
|
||||||
|
// position, so that we don't slowly move inside another object.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are touching something.
|
||||||
|
if (tracer.mFraction < 1E-9f)
|
||||||
|
{
|
||||||
|
// Try to separate by backing off slighly to unstuck the solver
|
||||||
|
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
|
||||||
|
newPosition += backOff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We hit something. Check if we can step up.
|
||||||
|
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
|
||||||
|
osg::Vec3f oldPosition = newPosition;
|
||||||
|
bool result = false;
|
||||||
|
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
|
||||||
|
{
|
||||||
|
// Try to step up onto it.
|
||||||
|
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
||||||
|
result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
|
||||||
|
}
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
// don't let pure water creatures move out of water after stepMove
|
||||||
|
if (ptr.getClass().isPureWaterCreature(ptr) && newPosition.z() + halfExtents.z() > waterlevel)
|
||||||
|
newPosition = oldPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Can't move this way, try to find another spot along the plane
|
||||||
|
osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
|
||||||
|
|
||||||
|
// Do not allow sliding upward if there is gravity.
|
||||||
|
// Stepping will have taken care of that.
|
||||||
|
if(!(newPosition.z() < swimlevel || isFlying))
|
||||||
|
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
|
||||||
|
|
||||||
|
if ((newVelocity-velocity).length2() < 0.01)
|
||||||
|
break;
|
||||||
|
if ((newVelocity * origVelocity) <= 0.f)
|
||||||
|
break; // ^ dot product
|
||||||
|
|
||||||
|
velocity = newVelocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isOnGround = false;
|
||||||
|
bool isOnSlope = false;
|
||||||
|
if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
|
||||||
|
{
|
||||||
|
osg::Vec3f from = newPosition;
|
||||||
|
osg::Vec3f to = newPosition - (physicActor->getOnGround() ? osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset));
|
||||||
|
tracer.doTrace(colobj, from, to, collisionWorld);
|
||||||
|
if(tracer.mFraction < 1.0f && !isActor(tracer.mHitObject))
|
||||||
|
{
|
||||||
|
const btCollisionObject* standingOn = tracer.mHitObject;
|
||||||
|
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
||||||
|
if (ptrHolder)
|
||||||
|
standingCollisionTracker[ptr] = ptrHolder->getPtr();
|
||||||
|
|
||||||
|
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
||||||
|
physicActor->setWalkingOnWater(true);
|
||||||
|
if (!isFlying)
|
||||||
|
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
||||||
|
|
||||||
|
isOnGround = true;
|
||||||
|
|
||||||
|
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// standing on actors is not allowed (see above).
|
||||||
|
// in addition to that, apply a sliding effect away from the center of the actor,
|
||||||
|
// so that we do not stay suspended in air indefinitely.
|
||||||
|
if (tracer.mFraction < 1.0f && isActor(tracer.mHitObject))
|
||||||
|
{
|
||||||
|
if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
|
||||||
|
{
|
||||||
|
btVector3 aabbMin, aabbMax;
|
||||||
|
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
|
||||||
|
btVector3 center = (aabbMin + aabbMax) / 2.f;
|
||||||
|
inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0);
|
||||||
|
inertia.normalize();
|
||||||
|
inertia *= 100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnGround = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || isFlying)
|
||||||
|
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
|
||||||
|
if (inertia.z() < 0)
|
||||||
|
inertia.z() *= slowFall;
|
||||||
|
if (slowFall < 1.f) {
|
||||||
|
inertia.x() *= slowFall;
|
||||||
|
inertia.y() *= slowFall;
|
||||||
|
}
|
||||||
|
physicActor->setInertialForce(inertia);
|
||||||
|
}
|
||||||
|
physicActor->setOnGround(isOnGround);
|
||||||
|
physicActor->setOnSlope(isOnSlope);
|
||||||
|
|
||||||
|
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
}
|
40
apps/openmw/mwphysics/movementsolver.hpp
Normal file
40
apps/openmw/mwphysics/movementsolver.hpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_MOVEMENTSOLVER_H
|
||||||
|
#define OPENMW_MWPHYSICS_MOVEMENTSOLVER_H
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
#include "../mwworld/ptr.hpp"
|
||||||
|
|
||||||
|
class btCollisionWorld;
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class Actor;
|
||||||
|
|
||||||
|
class MovementSolver
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
///Project a vector u on another vector v
|
||||||
|
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
|
||||||
|
{
|
||||||
|
return v * (u * v);
|
||||||
|
// ^ dot product
|
||||||
|
}
|
||||||
|
|
||||||
|
///Helper for computing the character sliding
|
||||||
|
static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
|
||||||
|
{
|
||||||
|
return direction - project(direction, planeNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight);
|
||||||
|
static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
|
||||||
|
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
|
||||||
|
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -29,12 +29,10 @@
|
||||||
#include "../mwbase/environment.hpp"
|
#include "../mwbase/environment.hpp"
|
||||||
|
|
||||||
#include "../mwmechanics/creaturestats.hpp"
|
#include "../mwmechanics/creaturestats.hpp"
|
||||||
#include "../mwmechanics/movement.hpp"
|
|
||||||
#include "../mwmechanics/actorutil.hpp"
|
#include "../mwmechanics/actorutil.hpp"
|
||||||
|
|
||||||
#include "../mwworld/esmstore.hpp"
|
#include "../mwworld/esmstore.hpp"
|
||||||
#include "../mwworld/cellstore.hpp"
|
#include "../mwworld/cellstore.hpp"
|
||||||
#include "../mwworld/player.hpp"
|
|
||||||
|
|
||||||
#include "../mwrender/bulletdebugdraw.hpp"
|
#include "../mwrender/bulletdebugdraw.hpp"
|
||||||
|
|
||||||
|
@ -46,487 +44,14 @@
|
||||||
#include "object.hpp"
|
#include "object.hpp"
|
||||||
#include "heightfield.hpp"
|
#include "heightfield.hpp"
|
||||||
#include "hasspherecollisioncallback.hpp"
|
#include "hasspherecollisioncallback.hpp"
|
||||||
|
#include "deepestnotmecontacttestresultcallback.hpp"
|
||||||
|
#include "closestnotmerayresultcallback.hpp"
|
||||||
|
#include "contacttestresultcallback.hpp"
|
||||||
|
#include "constants.hpp"
|
||||||
|
#include "movementsolver.hpp"
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
|
||||||
static const float sStepSizeDown = 62.0f;
|
|
||||||
static const float sMinStep = 10.f;
|
|
||||||
static const float sGroundOffset = 1.0f;
|
|
||||||
|
|
||||||
// Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared.
|
|
||||||
static const int sMaxIterations = 8;
|
|
||||||
|
|
||||||
static bool isActor(const btCollisionObject *obj)
|
|
||||||
{
|
|
||||||
assert(obj);
|
|
||||||
return obj->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class Vec3>
|
|
||||||
static bool isWalkableSlope(const Vec3 &normal)
|
|
||||||
{
|
|
||||||
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
|
||||||
return (normal.z() > sMaxSlopeCos);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool canStepDown(const ActorTracer &stepper)
|
|
||||||
{
|
|
||||||
return stepper.mHitObject && isWalkableSlope(stepper.mPlaneNormal) && !isActor(stepper.mHitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Stepper
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
const btCollisionWorld *mColWorld;
|
|
||||||
const btCollisionObject *mColObj;
|
|
||||||
|
|
||||||
ActorTracer mTracer, mUpStepper, mDownStepper;
|
|
||||||
bool mHaveMoved;
|
|
||||||
|
|
||||||
public:
|
|
||||||
Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj)
|
|
||||||
: mColWorld(colWorld)
|
|
||||||
, mColObj(colObj)
|
|
||||||
, mHaveMoved(true)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* Slide up an incline or set of stairs. Should be called only after a
|
|
||||||
* collision detection otherwise unnecessary tracing will be performed.
|
|
||||||
*
|
|
||||||
* NOTE: with a small change this method can be used to step over an obstacle
|
|
||||||
* of height sStepSize.
|
|
||||||
*
|
|
||||||
* If successful return 'true' and update 'position' to the new possible
|
|
||||||
* location and adjust 'remainingTime'.
|
|
||||||
*
|
|
||||||
* If not successful return 'false'. May fail for these reasons:
|
|
||||||
* - can't move directly up from current position
|
|
||||||
* - having moved up by between epsilon() and sStepSize, can't move forward
|
|
||||||
* - having moved forward by between epsilon() and toMove,
|
|
||||||
* = moved down between 0 and just under sStepSize but slope was too steep, or
|
|
||||||
* = moved the full sStepSize down (FIXME: this could be a bug)
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* Starting position. Obstacle or stairs with height upto sStepSize in front.
|
|
||||||
*
|
|
||||||
* +--+ +--+ |XX
|
|
||||||
* | | -------> toMove | | +--+XX
|
|
||||||
* | | | | |XXXXX
|
|
||||||
* | | +--+ | | +--+XXXXX
|
|
||||||
* | | |XX| | | |XXXXXXXX
|
|
||||||
* +--+ +--+ +--+ +--------
|
|
||||||
* ==============================================
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Try moving up sStepSize using stepper.
|
|
||||||
* FIXME: does not work in case there is no front obstacle but there is one above
|
|
||||||
*
|
|
||||||
* +--+ +--+
|
|
||||||
* | | | |
|
|
||||||
* | | | | |XX
|
|
||||||
* | | | | +--+XX
|
|
||||||
* | | | | |XXXXX
|
|
||||||
* +--+ +--+ +--+ +--+XXXXX
|
|
||||||
* |XX| |XXXXXXXX
|
|
||||||
* +--+ +--------
|
|
||||||
* ==============================================
|
|
||||||
*/
|
|
||||||
if (mHaveMoved)
|
|
||||||
{
|
|
||||||
mHaveMoved = false;
|
|
||||||
mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld);
|
|
||||||
if(mUpStepper.mFraction < std::numeric_limits<float>::epsilon())
|
|
||||||
return false; // didn't even move the smallest representable amount
|
|
||||||
// (TODO: shouldn't this be larger? Why bother with such a small amount?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Try moving from the elevated position using tracer.
|
|
||||||
*
|
|
||||||
* +--+ +--+
|
|
||||||
* | | |YY| FIXME: collision with object YY
|
|
||||||
* | | +--+
|
|
||||||
* | |
|
|
||||||
* <------------------->| |
|
|
||||||
* +--+ +--+
|
|
||||||
* |XX| the moved amount is toMove*tracer.mFraction
|
|
||||||
* +--+
|
|
||||||
* ==============================================
|
|
||||||
*/
|
|
||||||
osg::Vec3f tracerPos = mUpStepper.mEndPos;
|
|
||||||
mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld);
|
|
||||||
if(mTracer.mFraction < std::numeric_limits<float>::epsilon())
|
|
||||||
return false; // didn't even move the smallest representable amount
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Try moving back down sStepSizeDown using stepper.
|
|
||||||
* NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up".
|
|
||||||
* Below diagram is the case where we "stepped over" an obstacle in front.
|
|
||||||
*
|
|
||||||
* +--+
|
|
||||||
* |YY|
|
|
||||||
* +--+ +--+
|
|
||||||
* | |
|
|
||||||
* | |
|
|
||||||
* +--+ | |
|
|
||||||
* |XX| | |
|
|
||||||
* +--+ +--+
|
|
||||||
* ==============================================
|
|
||||||
*/
|
|
||||||
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
|
|
||||||
if (!canStepDown(mDownStepper))
|
|
||||||
{
|
|
||||||
// Try again with increased step length
|
|
||||||
if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
osg::Vec3f direction = toMove;
|
|
||||||
direction.normalize();
|
|
||||||
mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld);
|
|
||||||
if (mTracer.mFraction < 0.001f)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
|
|
||||||
if (!canStepDown(mDownStepper))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (mDownStepper.mFraction < 1.0f)
|
|
||||||
{
|
|
||||||
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
|
|
||||||
// TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing
|
|
||||||
// NOTE: caller's variables 'position' & 'remainingTime' are modified here
|
|
||||||
position = mDownStepper.mEndPos;
|
|
||||||
remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance
|
|
||||||
mHaveMoved = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MovementSolver
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
///Project a vector u on another vector v
|
|
||||||
static inline osg::Vec3f project(const osg::Vec3f& u, const osg::Vec3f &v)
|
|
||||||
{
|
|
||||||
return v * (u * v);
|
|
||||||
// ^ dot product
|
|
||||||
}
|
|
||||||
|
|
||||||
///Helper for computing the character sliding
|
|
||||||
static inline osg::Vec3f slide(const osg::Vec3f& direction, const osg::Vec3f &planeNormal)
|
|
||||||
{
|
|
||||||
return direction - project(direction, planeNormal);
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
static osg::Vec3f traceDown(const MWWorld::Ptr &ptr, const osg::Vec3f& position, Actor* actor, btCollisionWorld* collisionWorld, float maxHeight)
|
|
||||||
{
|
|
||||||
osg::Vec3f offset = actor->getCollisionObjectPosition() - ptr.getRefData().getPosition().asVec3();
|
|
||||||
|
|
||||||
ActorTracer tracer;
|
|
||||||
tracer.findGround(actor, position + offset, position + offset - osg::Vec3f(0,0,maxHeight), collisionWorld);
|
|
||||||
if(tracer.mFraction >= 1.0f)
|
|
||||||
{
|
|
||||||
actor->setOnGround(false);
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
actor->setOnGround(true);
|
|
||||||
|
|
||||||
// Check if we actually found a valid spawn point (use an infinitely thin ray this time).
|
|
||||||
// Required for some broken door destinations in Morrowind.esm, where the spawn point
|
|
||||||
// intersects with other geometry if the actor's base is taken into account
|
|
||||||
btVector3 from = Misc::Convert::toBullet(position);
|
|
||||||
btVector3 to = from - btVector3(0,0,maxHeight);
|
|
||||||
|
|
||||||
btCollisionWorld::ClosestRayResultCallback resultCallback1(from, to);
|
|
||||||
resultCallback1.m_collisionFilterGroup = 0xff;
|
|
||||||
resultCallback1.m_collisionFilterMask = CollisionType_World|CollisionType_HeightMap;
|
|
||||||
|
|
||||||
collisionWorld->rayTest(from, to, resultCallback1);
|
|
||||||
|
|
||||||
if (resultCallback1.hasHit() &&
|
|
||||||
( (Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) - (tracer.mEndPos-offset)).length2() > 35*35
|
|
||||||
|| !isWalkableSlope(tracer.mPlaneNormal)))
|
|
||||||
{
|
|
||||||
actor->setOnSlope(!isWalkableSlope(resultCallback1.m_hitNormalWorld));
|
|
||||||
return Misc::Convert::toOsg(resultCallback1.m_hitPointWorld) + osg::Vec3f(0.f, 0.f, sGroundOffset);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
actor->setOnSlope(!isWalkableSlope(tracer.mPlaneNormal));
|
|
||||||
}
|
|
||||||
|
|
||||||
return tracer.mEndPos-offset + osg::Vec3f(0.f, 0.f, sGroundOffset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static osg::Vec3f move(osg::Vec3f position, const MWWorld::Ptr &ptr, Actor* physicActor, const osg::Vec3f &movement, float time,
|
|
||||||
bool isFlying, float waterlevel, float slowFall, const btCollisionWorld* collisionWorld,
|
|
||||||
std::map<MWWorld::Ptr, MWWorld::Ptr>& standingCollisionTracker)
|
|
||||||
{
|
|
||||||
const ESM::Position& refpos = ptr.getRefData().getPosition();
|
|
||||||
// Early-out for totally static creatures
|
|
||||||
// (Not sure if gravity should still apply?)
|
|
||||||
if (!ptr.getClass().isMobile(ptr))
|
|
||||||
return position;
|
|
||||||
|
|
||||||
// Reset per-frame data
|
|
||||||
physicActor->setWalkingOnWater(false);
|
|
||||||
// Anything to collide with?
|
|
||||||
if(!physicActor->getCollisionMode())
|
|
||||||
{
|
|
||||||
return position + (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
|
|
||||||
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))
|
|
||||||
) * movement * time;
|
|
||||||
}
|
|
||||||
|
|
||||||
const btCollisionObject *colobj = physicActor->getCollisionObject();
|
|
||||||
osg::Vec3f halfExtents = physicActor->getHalfExtents();
|
|
||||||
|
|
||||||
// NOTE: here we don't account for the collision box translation (i.e. physicActor->getPosition() - refpos.pos).
|
|
||||||
// That means the collision shape used for moving this actor is in a different spot than the collision shape
|
|
||||||
// other actors are using to collide against this actor.
|
|
||||||
// While this is strictly speaking wrong, it's needed for MW compatibility.
|
|
||||||
position.z() += halfExtents.z();
|
|
||||||
|
|
||||||
static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
|
||||||
.find("fSwimHeightScale")->mValue.getFloat();
|
|
||||||
float swimlevel = waterlevel + halfExtents.z() - (physicActor->getRenderingHalfExtents().z() * 2 * fSwimHeightScale);
|
|
||||||
|
|
||||||
ActorTracer tracer;
|
|
||||||
|
|
||||||
osg::Vec3f inertia = physicActor->getInertialForce();
|
|
||||||
osg::Vec3f velocity;
|
|
||||||
|
|
||||||
if(position.z() < swimlevel || isFlying)
|
|
||||||
{
|
|
||||||
velocity = (osg::Quat(refpos.rot[0], osg::Vec3f(-1, 0, 0)) *
|
|
||||||
osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
velocity = (osg::Quat(refpos.rot[2], osg::Vec3f(0, 0, -1))) * movement;
|
|
||||||
|
|
||||||
if ((velocity.z() > 0.f && physicActor->getOnGround() && !physicActor->getOnSlope())
|
|
||||||
|| (velocity.z() > 0.f && velocity.z() + inertia.z() <= -velocity.z() && physicActor->getOnSlope()))
|
|
||||||
inertia = velocity;
|
|
||||||
else if (!physicActor->getOnGround() || physicActor->getOnSlope())
|
|
||||||
velocity = velocity + inertia;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dead actors underwater will float to the surface, if the CharacterController tells us to do so
|
|
||||||
if (movement.z() > 0 && ptr.getClass().getCreatureStats(ptr).isDead() && position.z() < swimlevel)
|
|
||||||
velocity = osg::Vec3f(0,0,1) * 25;
|
|
||||||
|
|
||||||
if (ptr.getClass().getMovementSettings(ptr).mPosition[2])
|
|
||||||
{
|
|
||||||
const bool isPlayer = (ptr == MWMechanics::getPlayer());
|
|
||||||
// Advance acrobatics and set flag for GetPCJumping
|
|
||||||
if (isPlayer)
|
|
||||||
{
|
|
||||||
ptr.getClass().skillUsageSucceeded(ptr, ESM::Skill::Acrobatics, 0);
|
|
||||||
MWBase::Environment::get().getWorld()->getPlayer().setJumping(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrease fatigue
|
|
||||||
if (!isPlayer || !MWBase::Environment::get().getWorld()->getGodModeState())
|
|
||||||
{
|
|
||||||
const MWWorld::Store<ESM::GameSetting> &gmst = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
|
||||||
const float fFatigueJumpBase = gmst.find("fFatigueJumpBase")->mValue.getFloat();
|
|
||||||
const float fFatigueJumpMult = gmst.find("fFatigueJumpMult")->mValue.getFloat();
|
|
||||||
const float normalizedEncumbrance = std::min(1.f, ptr.getClass().getNormalizedEncumbrance(ptr));
|
|
||||||
const float fatigueDecrease = fFatigueJumpBase + normalizedEncumbrance * fFatigueJumpMult;
|
|
||||||
MWMechanics::DynamicStat<float> fatigue = ptr.getClass().getCreatureStats(ptr).getFatigue();
|
|
||||||
fatigue.setCurrent(fatigue.getCurrent() - fatigueDecrease);
|
|
||||||
ptr.getClass().getCreatureStats(ptr).setFatigue(fatigue);
|
|
||||||
}
|
|
||||||
ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have the effective movement vector, apply wind forces to it
|
|
||||||
if (MWBase::Environment::get().getWorld()->isInStorm())
|
|
||||||
{
|
|
||||||
osg::Vec3f stormDirection = MWBase::Environment::get().getWorld()->getStormDirection();
|
|
||||||
float angleDegrees = osg::RadiansToDegrees(std::acos(stormDirection * velocity / (stormDirection.length() * velocity.length())));
|
|
||||||
static const float fStromWalkMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
|
|
||||||
.find("fStromWalkMult")->mValue.getFloat();
|
|
||||||
velocity *= 1.f-(fStromWalkMult * (angleDegrees/180.f));
|
|
||||||
}
|
|
||||||
|
|
||||||
Stepper stepper(collisionWorld, colobj);
|
|
||||||
osg::Vec3f origVelocity = velocity;
|
|
||||||
osg::Vec3f newPosition = position;
|
|
||||||
/*
|
|
||||||
* A loop to find newPosition using tracer, if successful different from the starting position.
|
|
||||||
* nextpos is the local variable used to find potential newPosition, using velocity and remainingTime
|
|
||||||
* The initial velocity was set earlier (see above).
|
|
||||||
*/
|
|
||||||
float remainingTime = time;
|
|
||||||
for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations)
|
|
||||||
{
|
|
||||||
osg::Vec3f nextpos = newPosition + velocity * remainingTime;
|
|
||||||
|
|
||||||
// If not able to fly, don't allow to swim up into the air
|
|
||||||
if(!isFlying && // can't fly
|
|
||||||
nextpos.z() > swimlevel && // but about to go above water
|
|
||||||
newPosition.z() < swimlevel)
|
|
||||||
{
|
|
||||||
const osg::Vec3f down(0,0,-1);
|
|
||||||
velocity = slide(velocity, down);
|
|
||||||
// NOTE: remainingTime is unchanged before the loop continues
|
|
||||||
continue; // velocity updated, calculate nextpos again
|
|
||||||
}
|
|
||||||
|
|
||||||
if((newPosition - nextpos).length2() > 0.0001)
|
|
||||||
{
|
|
||||||
// trace to where character would go if there were no obstructions
|
|
||||||
tracer.doTrace(colobj, newPosition, nextpos, collisionWorld);
|
|
||||||
|
|
||||||
// check for obstructions
|
|
||||||
if(tracer.mFraction >= 1.0f)
|
|
||||||
{
|
|
||||||
newPosition = tracer.mEndPos; // ok to move, so set newPosition
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The current position and next position are nearly the same, so just exit.
|
|
||||||
// Note: Bullet can trigger an assert in debug modes if the positions
|
|
||||||
// are the same, since that causes it to attempt to normalize a zero
|
|
||||||
// length vector (which can also happen with nearly identical vectors, since
|
|
||||||
// precision can be lost due to any math Bullet does internally). Since we
|
|
||||||
// aren't performing any collision detection, we want to reject the next
|
|
||||||
// position, so that we don't slowly move inside another object.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are touching something.
|
|
||||||
if (tracer.mFraction < 1E-9f)
|
|
||||||
{
|
|
||||||
// Try to separate by backing off slighly to unstuck the solver
|
|
||||||
osg::Vec3f backOff = (newPosition - tracer.mHitPoint) * 1E-2f;
|
|
||||||
newPosition += backOff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We hit something. Check if we can step up.
|
|
||||||
float hitHeight = tracer.mHitPoint.z() - tracer.mEndPos.z() + halfExtents.z();
|
|
||||||
osg::Vec3f oldPosition = newPosition;
|
|
||||||
bool result = false;
|
|
||||||
if (hitHeight < sStepSizeUp && !isActor(tracer.mHitObject))
|
|
||||||
{
|
|
||||||
// Try to step up onto it.
|
|
||||||
// NOTE: stepMove does not allow stepping over, modifies newPosition if successful
|
|
||||||
result = stepper.step(newPosition, velocity*remainingTime, remainingTime);
|
|
||||||
}
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
// don't let pure water creatures move out of water after stepMove
|
|
||||||
if (ptr.getClass().isPureWaterCreature(ptr)
|
|
||||||
&& newPosition.z() + halfExtents.z() > waterlevel)
|
|
||||||
newPosition = oldPosition;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Can't move this way, try to find another spot along the plane
|
|
||||||
osg::Vec3f newVelocity = slide(velocity, tracer.mPlaneNormal);
|
|
||||||
|
|
||||||
// Do not allow sliding upward if there is gravity.
|
|
||||||
// Stepping will have taken care of that.
|
|
||||||
if(!(newPosition.z() < swimlevel || isFlying))
|
|
||||||
newVelocity.z() = std::min(newVelocity.z(), 0.0f);
|
|
||||||
|
|
||||||
if ((newVelocity-velocity).length2() < 0.01)
|
|
||||||
break;
|
|
||||||
if ((newVelocity * origVelocity) <= 0.f)
|
|
||||||
break; // ^ dot product
|
|
||||||
|
|
||||||
velocity = newVelocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isOnGround = false;
|
|
||||||
bool isOnSlope = false;
|
|
||||||
if (!(inertia.z() > 0.f) && !(newPosition.z() < swimlevel))
|
|
||||||
{
|
|
||||||
osg::Vec3f from = newPosition;
|
|
||||||
osg::Vec3f to = newPosition - (physicActor->getOnGround() ?
|
|
||||||
osg::Vec3f(0,0,sStepSizeDown + 2*sGroundOffset) : osg::Vec3f(0,0,2*sGroundOffset));
|
|
||||||
tracer.doTrace(colobj, from, to, collisionWorld);
|
|
||||||
if(tracer.mFraction < 1.0f
|
|
||||||
&& tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor)
|
|
||||||
{
|
|
||||||
const btCollisionObject* standingOn = tracer.mHitObject;
|
|
||||||
PtrHolder* ptrHolder = static_cast<PtrHolder*>(standingOn->getUserPointer());
|
|
||||||
if (ptrHolder)
|
|
||||||
standingCollisionTracker[ptr] = ptrHolder->getPtr();
|
|
||||||
|
|
||||||
if (standingOn->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Water)
|
|
||||||
physicActor->setWalkingOnWater(true);
|
|
||||||
if (!isFlying)
|
|
||||||
newPosition.z() = tracer.mEndPos.z() + sGroundOffset;
|
|
||||||
|
|
||||||
isOnGround = true;
|
|
||||||
|
|
||||||
isOnSlope = !isWalkableSlope(tracer.mPlaneNormal);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// standing on actors is not allowed (see above).
|
|
||||||
// in addition to that, apply a sliding effect away from the center of the actor,
|
|
||||||
// so that we do not stay suspended in air indefinitely.
|
|
||||||
if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor)
|
|
||||||
{
|
|
||||||
if (osg::Vec3f(velocity.x(), velocity.y(), 0).length2() < 100.f*100.f)
|
|
||||||
{
|
|
||||||
btVector3 aabbMin, aabbMax;
|
|
||||||
tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax);
|
|
||||||
btVector3 center = (aabbMin + aabbMax) / 2.f;
|
|
||||||
inertia = osg::Vec3f(position.x() - center.x(), position.y() - center.y(), 0);
|
|
||||||
inertia.normalize();
|
|
||||||
inertia *= 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isOnGround = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if((isOnGround && !isOnSlope) || newPosition.z() < swimlevel || isFlying)
|
|
||||||
physicActor->setInertialForce(osg::Vec3f(0.f, 0.f, 0.f));
|
|
||||||
else
|
|
||||||
{
|
|
||||||
inertia.z() -= time * Constants::GravityConst * Constants::UnitsPerMeter;
|
|
||||||
if (inertia.z() < 0)
|
|
||||||
inertia.z() *= slowFall;
|
|
||||||
if (slowFall < 1.f) {
|
|
||||||
inertia.x() *= slowFall;
|
|
||||||
inertia.y() *= slowFall;
|
|
||||||
}
|
|
||||||
physicActor->setInertialForce(inertia);
|
|
||||||
}
|
|
||||||
physicActor->setOnGround(isOnGround);
|
|
||||||
physicActor->setOnSlope(isOnSlope);
|
|
||||||
|
|
||||||
newPosition.z() -= halfExtents.z(); // remove what was added at the beginning
|
|
||||||
return newPosition;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
PhysicsSystem::PhysicsSystem(Resource::ResourceSystem* resourceSystem, osg::ref_ptr<osg::Group> parentNode)
|
||||||
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
|
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
|
||||||
, mResourceSystem(resourceSystem)
|
, mResourceSystem(resourceSystem)
|
||||||
|
@ -668,54 +193,6 @@ namespace MWPhysics
|
||||||
End of tes3mp addition
|
End of tes3mp addition
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class DeepestNotMeContactTestResultCallback : public btCollisionWorld::ContactResultCallback
|
|
||||||
{
|
|
||||||
const btCollisionObject* mMe;
|
|
||||||
const std::vector<const btCollisionObject*> mTargets;
|
|
||||||
|
|
||||||
// Store the real origin, since the shape's origin is its center
|
|
||||||
btVector3 mOrigin;
|
|
||||||
|
|
||||||
public:
|
|
||||||
const btCollisionObject *mObject;
|
|
||||||
btVector3 mContactPoint;
|
|
||||||
btScalar mLeastDistSqr;
|
|
||||||
|
|
||||||
DeepestNotMeContactTestResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3 &origin)
|
|
||||||
: mMe(me), mTargets(targets), mOrigin(origin), mObject(nullptr), mContactPoint(0,0,0),
|
|
||||||
mLeastDistSqr(std::numeric_limits<float>::max())
|
|
||||||
{ }
|
|
||||||
|
|
||||||
virtual btScalar addSingleResult(btManifoldPoint& cp,
|
|
||||||
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
|
||||||
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
|
|
||||||
{
|
|
||||||
const btCollisionObject* collisionObject = col1Wrap->m_collisionObject;
|
|
||||||
if (collisionObject != mMe)
|
|
||||||
{
|
|
||||||
if (!mTargets.empty())
|
|
||||||
{
|
|
||||||
if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end()))
|
|
||||||
{
|
|
||||||
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
|
|
||||||
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
|
|
||||||
return 0.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA());
|
|
||||||
if(!mObject || distsqr < mLeastDistSqr)
|
|
||||||
{
|
|
||||||
mObject = collisionObject;
|
|
||||||
mLeastDistSqr = distsqr;
|
|
||||||
mContactPoint = cp.getPositionWorldOnA();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0.f;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
|
std::pair<MWWorld::Ptr, osg::Vec3f> PhysicsSystem::getHitContact(const MWWorld::ConstPtr& actor,
|
||||||
const osg::Vec3f &origin,
|
const osg::Vec3f &origin,
|
||||||
const osg::Quat &orient,
|
const osg::Quat &orient,
|
||||||
|
@ -806,35 +283,6 @@ namespace MWPhysics
|
||||||
return (point - Misc::Convert::toOsg(cb.m_hitPointWorld)).length();
|
return (point - Misc::Convert::toOsg(cb.m_hitPointWorld)).length();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ClosestNotMeRayResultCallback(const btCollisionObject* me, const std::vector<const btCollisionObject*>& targets, const btVector3& from, const btVector3& to)
|
|
||||||
: btCollisionWorld::ClosestRayResultCallback(from, to)
|
|
||||||
, mMe(me), mTargets(targets)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
|
|
||||||
{
|
|
||||||
if (rayResult.m_collisionObject == mMe)
|
|
||||||
return 1.f;
|
|
||||||
if (!mTargets.empty())
|
|
||||||
{
|
|
||||||
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end()))
|
|
||||||
{
|
|
||||||
PtrHolder* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
|
|
||||||
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
|
|
||||||
return 1.f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
const btCollisionObject* mMe;
|
|
||||||
const std::vector<const btCollisionObject*> mTargets;
|
|
||||||
};
|
|
||||||
|
|
||||||
PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
|
PhysicsSystem::RayResult PhysicsSystem::castRay(const osg::Vec3f &from, const osg::Vec3f &to, const MWWorld::ConstPtr& ignore, std::vector<MWWorld::Ptr> targets, int mask, int group) const
|
||||||
{
|
{
|
||||||
btVector3 btFrom = Misc::Convert::toBullet(from);
|
btVector3 btFrom = Misc::Convert::toBullet(from);
|
||||||
|
@ -979,32 +427,6 @@ namespace MWPhysics
|
||||||
return osg::Vec3f();
|
return osg::Vec3f();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactTestResultCallback : public btCollisionWorld::ContactResultCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ContactTestResultCallback(const btCollisionObject* testedAgainst)
|
|
||||||
: mTestedAgainst(testedAgainst)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
const btCollisionObject* mTestedAgainst;
|
|
||||||
|
|
||||||
std::vector<MWWorld::Ptr> mResult;
|
|
||||||
|
|
||||||
virtual btScalar addSingleResult(btManifoldPoint& cp,
|
|
||||||
const btCollisionObjectWrapper* col0Wrap,int partId0,int index0,
|
|
||||||
const btCollisionObjectWrapper* col1Wrap,int partId1,int index1)
|
|
||||||
{
|
|
||||||
const btCollisionObject* collisionObject = col0Wrap->m_collisionObject;
|
|
||||||
if (collisionObject == mTestedAgainst)
|
|
||||||
collisionObject = col1Wrap->m_collisionObject;
|
|
||||||
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
|
|
||||||
if (holder)
|
|
||||||
mResult.push_back(holder->getPtr());
|
|
||||||
return 0.f;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
|
std::vector<MWWorld::Ptr> PhysicsSystem::getCollisions(const MWWorld::ConstPtr &ptr, int collisionGroup, int collisionMask) const
|
||||||
{
|
{
|
||||||
btCollisionObject* me = nullptr;
|
btCollisionObject* me = nullptr;
|
||||||
|
|
|
@ -50,9 +50,6 @@ namespace MWPhysics
|
||||||
class Object;
|
class Object;
|
||||||
class Actor;
|
class Actor;
|
||||||
|
|
||||||
static const float sMaxSlope = 49.0f;
|
|
||||||
static const float sStepSizeUp = 34.0f;
|
|
||||||
|
|
||||||
class PhysicsSystem
|
class PhysicsSystem
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
148
apps/openmw/mwphysics/stepper.cpp
Normal file
148
apps/openmw/mwphysics/stepper.cpp
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
#include "stepper.hpp"
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
|
||||||
|
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||||
|
|
||||||
|
#include "collisiontype.hpp"
|
||||||
|
#include "constants.hpp"
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
static bool canStepDown(const ActorTracer &stepper)
|
||||||
|
{
|
||||||
|
if (!stepper.mHitObject)
|
||||||
|
return false;
|
||||||
|
static const float sMaxSlopeCos = std::cos(osg::DegreesToRadians(sMaxSlope));
|
||||||
|
if (stepper.mPlaneNormal.z() <= sMaxSlopeCos)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != CollisionType_Actor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Stepper::Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj)
|
||||||
|
: mColWorld(colWorld)
|
||||||
|
, mColObj(colObj)
|
||||||
|
, mHaveMoved(true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Stepper::step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Slide up an incline or set of stairs. Should be called only after a
|
||||||
|
* collision detection otherwise unnecessary tracing will be performed.
|
||||||
|
*
|
||||||
|
* NOTE: with a small change this method can be used to step over an obstacle
|
||||||
|
* of height sStepSize.
|
||||||
|
*
|
||||||
|
* If successful return 'true' and update 'position' to the new possible
|
||||||
|
* location and adjust 'remainingTime'.
|
||||||
|
*
|
||||||
|
* If not successful return 'false'. May fail for these reasons:
|
||||||
|
* - can't move directly up from current position
|
||||||
|
* - having moved up by between epsilon() and sStepSize, can't move forward
|
||||||
|
* - having moved forward by between epsilon() and toMove,
|
||||||
|
* = moved down between 0 and just under sStepSize but slope was too steep, or
|
||||||
|
* = moved the full sStepSize down (FIXME: this could be a bug)
|
||||||
|
*
|
||||||
|
* Starting position. Obstacle or stairs with height upto sStepSize in front.
|
||||||
|
*
|
||||||
|
* +--+ +--+ |XX
|
||||||
|
* | | -------> toMove | | +--+XX
|
||||||
|
* | | | | |XXXXX
|
||||||
|
* | | +--+ | | +--+XXXXX
|
||||||
|
* | | |XX| | | |XXXXXXXX
|
||||||
|
* +--+ +--+ +--+ +--------
|
||||||
|
* ==============================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try moving up sStepSize using stepper.
|
||||||
|
* FIXME: does not work in case there is no front obstacle but there is one above
|
||||||
|
*
|
||||||
|
* +--+ +--+
|
||||||
|
* | | | |
|
||||||
|
* | | | | |XX
|
||||||
|
* | | | | +--+XX
|
||||||
|
* | | | | |XXXXX
|
||||||
|
* +--+ +--+ +--+ +--+XXXXX
|
||||||
|
* |XX| |XXXXXXXX
|
||||||
|
* +--+ +--------
|
||||||
|
* ==============================================
|
||||||
|
*/
|
||||||
|
if (mHaveMoved)
|
||||||
|
{
|
||||||
|
mHaveMoved = false;
|
||||||
|
|
||||||
|
mUpStepper.doTrace(mColObj, position, position+osg::Vec3f(0.0f,0.0f,sStepSizeUp), mColWorld);
|
||||||
|
if (mUpStepper.mFraction < std::numeric_limits<float>::epsilon())
|
||||||
|
return false; // didn't even move the smallest representable amount
|
||||||
|
// (TODO: shouldn't this be larger? Why bother with such a small amount?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try moving from the elevated position using tracer.
|
||||||
|
*
|
||||||
|
* +--+ +--+
|
||||||
|
* | | |YY| FIXME: collision with object YY
|
||||||
|
* | | +--+
|
||||||
|
* | |
|
||||||
|
* <------------------->| |
|
||||||
|
* +--+ +--+
|
||||||
|
* |XX| the moved amount is toMove*tracer.mFraction
|
||||||
|
* +--+
|
||||||
|
* ==============================================
|
||||||
|
*/
|
||||||
|
osg::Vec3f tracerPos = mUpStepper.mEndPos;
|
||||||
|
mTracer.doTrace(mColObj, tracerPos, tracerPos + toMove, mColWorld);
|
||||||
|
if (mTracer.mFraction < std::numeric_limits<float>::epsilon())
|
||||||
|
return false; // didn't even move the smallest representable amount
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Try moving back down sStepSizeDown using stepper.
|
||||||
|
* NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up".
|
||||||
|
* Below diagram is the case where we "stepped over" an obstacle in front.
|
||||||
|
*
|
||||||
|
* +--+
|
||||||
|
* |YY|
|
||||||
|
* +--+ +--+
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* +--+ | |
|
||||||
|
* |XX| | |
|
||||||
|
* +--+ +--+
|
||||||
|
* ==============================================
|
||||||
|
*/
|
||||||
|
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
|
||||||
|
if (!canStepDown(mDownStepper))
|
||||||
|
{
|
||||||
|
// Try again with increased step length
|
||||||
|
if (mTracer.mFraction < 1.0f || toMove.length2() > sMinStep*sMinStep)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
osg::Vec3f direction = toMove;
|
||||||
|
direction.normalize();
|
||||||
|
mTracer.doTrace(mColObj, tracerPos, tracerPos + direction*sMinStep, mColWorld);
|
||||||
|
if (mTracer.mFraction < 0.001f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
mDownStepper.doTrace(mColObj, mTracer.mEndPos, mTracer.mEndPos-osg::Vec3f(0.0f,0.0f,sStepSizeDown), mColWorld);
|
||||||
|
if (!canStepDown(mDownStepper))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDownStepper.mFraction < 1.0f)
|
||||||
|
{
|
||||||
|
// only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall.
|
||||||
|
// TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing
|
||||||
|
// NOTE: caller's variables 'position' & 'remainingTime' are modified here
|
||||||
|
position = mDownStepper.mEndPos;
|
||||||
|
remainingTime *= (1.0f-mTracer.mFraction); // remaining time is proportional to remaining distance
|
||||||
|
mHaveMoved = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
32
apps/openmw/mwphysics/stepper.hpp
Normal file
32
apps/openmw/mwphysics/stepper.hpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef OPENMW_MWPHYSICS_STEPPER_H
|
||||||
|
#define OPENMW_MWPHYSICS_STEPPER_H
|
||||||
|
|
||||||
|
#include "trace.h"
|
||||||
|
|
||||||
|
class btCollisionObject;
|
||||||
|
class btCollisionWorld;
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Vec3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWPhysics
|
||||||
|
{
|
||||||
|
class Stepper
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
const btCollisionWorld *mColWorld;
|
||||||
|
const btCollisionObject *mColObj;
|
||||||
|
|
||||||
|
ActorTracer mTracer, mUpStepper, mDownStepper;
|
||||||
|
bool mHaveMoved;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Stepper(const btCollisionWorld *colWorld, const btCollisionObject *colObj);
|
||||||
|
|
||||||
|
bool step(osg::Vec3f &position, const osg::Vec3f &toMove, float &remainingTime);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -7,47 +7,11 @@
|
||||||
|
|
||||||
#include "collisiontype.hpp"
|
#include "collisiontype.hpp"
|
||||||
#include "actor.hpp"
|
#include "actor.hpp"
|
||||||
|
#include "closestnotmeconvexresultcallback.hpp"
|
||||||
|
|
||||||
namespace MWPhysics
|
namespace MWPhysics
|
||||||
{
|
{
|
||||||
|
|
||||||
class ClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ClosestNotMeConvexResultCallback(const btCollisionObject *me, const btVector3 &up, btScalar minSlopeDot)
|
|
||||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)),
|
|
||||||
mMe(me), mUp(up), mMinSlopeDot(minSlopeDot)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult,bool normalInWorldSpace)
|
|
||||||
{
|
|
||||||
if(convexResult.m_hitCollisionObject == mMe)
|
|
||||||
return btScalar( 1 );
|
|
||||||
|
|
||||||
btVector3 hitNormalWorld;
|
|
||||||
if(normalInWorldSpace)
|
|
||||||
hitNormalWorld = convexResult.m_hitNormalLocal;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
///need to transform normal into worldspace
|
|
||||||
hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis()*convexResult.m_hitNormalLocal;
|
|
||||||
}
|
|
||||||
|
|
||||||
btScalar dotUp = mUp.dot(hitNormalWorld);
|
|
||||||
if(dotUp < mMinSlopeDot)
|
|
||||||
return btScalar(1);
|
|
||||||
|
|
||||||
return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
const btCollisionObject *mMe;
|
|
||||||
const btVector3 mUp;
|
|
||||||
const btScalar mMinSlopeDot;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
|
void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
|
||||||
{
|
{
|
||||||
const btVector3 btstart = Misc::Convert::toBullet(start);
|
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||||
|
@ -59,22 +23,21 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
|
||||||
from.setOrigin(btstart);
|
from.setOrigin(btstart);
|
||||||
to.setOrigin(btend);
|
to.setOrigin(btend);
|
||||||
|
|
||||||
ClosestNotMeConvexResultCallback newTraceCallback(actor, btstart-btend, btScalar(0.0));
|
const btVector3 motion = btstart-btend;
|
||||||
|
ClosestNotMeConvexResultCallback newTraceCallback(actor, motion, btScalar(0.0));
|
||||||
// Inherit the actor's collision group and mask
|
// Inherit the actor's collision group and mask
|
||||||
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
|
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
|
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
|
||||||
const btCollisionShape *shape = actor->getCollisionShape();
|
const btCollisionShape *shape = actor->getCollisionShape();
|
||||||
assert(shape->isConvex());
|
assert(shape->isConvex());
|
||||||
world->convexSweepTest(static_cast<const btConvexShape*>(shape),
|
world->convexSweepTest(static_cast<const btConvexShape*>(shape), from, to, newTraceCallback);
|
||||||
from, to, newTraceCallback);
|
|
||||||
|
|
||||||
// Copy the hit data over to our trace results struct:
|
// Copy the hit data over to our trace results struct:
|
||||||
if(newTraceCallback.hasHit())
|
if(newTraceCallback.hasHit())
|
||||||
{
|
{
|
||||||
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
|
|
||||||
mFraction = newTraceCallback.m_closestHitFraction;
|
mFraction = newTraceCallback.m_closestHitFraction;
|
||||||
mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
|
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
||||||
mEndPos = (end-start)*mFraction + start;
|
mEndPos = (end-start)*mFraction + start;
|
||||||
mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld);
|
mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld);
|
||||||
mHitObject = newTraceCallback.m_hitCollisionObject;
|
mHitObject = newTraceCallback.m_hitCollisionObject;
|
||||||
|
@ -91,14 +54,15 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
|
||||||
|
|
||||||
void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
|
void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const osg::Vec3f& end, const btCollisionWorld* world)
|
||||||
{
|
{
|
||||||
const btVector3 btstart(start.x(), start.y(), start.z());
|
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||||
const btVector3 btend(end.x(), end.y(), end.z());
|
const btVector3 btend = Misc::Convert::toBullet(end);
|
||||||
|
|
||||||
const btTransform &trans = actor->getCollisionObject()->getWorldTransform();
|
const btTransform &trans = actor->getCollisionObject()->getWorldTransform();
|
||||||
btTransform from(trans.getBasis(), btstart);
|
btTransform from(trans.getBasis(), btstart);
|
||||||
btTransform to(trans.getBasis(), btend);
|
btTransform to(trans.getBasis(), btend);
|
||||||
|
|
||||||
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), btstart-btend, btScalar(0.0));
|
const btVector3 motion = btstart-btend;
|
||||||
|
ClosestNotMeConvexResultCallback newTraceCallback(actor->getCollisionObject(), motion, btScalar(0.0));
|
||||||
// Inherit the actor's collision group and mask
|
// Inherit the actor's collision group and mask
|
||||||
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||||
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
newTraceCallback.m_collisionFilterMask = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterMask;
|
||||||
|
@ -107,9 +71,8 @@ void ActorTracer::findGround(const Actor* actor, const osg::Vec3f& start, const
|
||||||
world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback);
|
world->convexSweepTest(actor->getConvexShape(), from, to, newTraceCallback);
|
||||||
if(newTraceCallback.hasHit())
|
if(newTraceCallback.hasHit())
|
||||||
{
|
{
|
||||||
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
|
|
||||||
mFraction = newTraceCallback.m_closestHitFraction;
|
mFraction = newTraceCallback.m_closestHitFraction;
|
||||||
mPlaneNormal = osg::Vec3f(tracehitnormal.x(), tracehitnormal.y(), tracehitnormal.z());
|
mPlaneNormal = Misc::Convert::toOsg(newTraceCallback.m_hitNormalWorld);
|
||||||
mEndPos = (end-start)*mFraction + start;
|
mEndPos = (end-start)*mFraction + start;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace MWRender
|
||||||
void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id,
|
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)
|
const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings)
|
||||||
{
|
{
|
||||||
if (!mEnabled || (mGroup && mId == id && mGeneration >= generation && mRevision >= revision))
|
if (!mEnabled || (mGroup && mId == id && mGeneration == generation && mRevision == revision))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mId = id;
|
mId = id;
|
||||||
|
|
|
@ -269,6 +269,9 @@ void HeadAnimationTime::setBlinkStop(float value)
|
||||||
NpcAnimation::NpcType NpcAnimation::getNpcType()
|
NpcAnimation::NpcType NpcAnimation::getNpcType()
|
||||||
{
|
{
|
||||||
const MWWorld::Class &cls = mPtr.getClass();
|
const MWWorld::Class &cls = mPtr.getClass();
|
||||||
|
// Dead vampires should typically stay vampires.
|
||||||
|
if (mNpcType == Type_Vampire && cls.getNpcStats(mPtr).isDead() && !cls.getNpcStats(mPtr).isWerewolf())
|
||||||
|
return mNpcType;
|
||||||
NpcAnimation::NpcType curType = Type_Normal;
|
NpcAnimation::NpcType curType = Type_Normal;
|
||||||
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
|
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
|
||||||
curType = Type_Vampire;
|
curType = Type_Vampire;
|
||||||
|
@ -587,7 +590,7 @@ void NpcAnimation::updateParts()
|
||||||
int mBasePriority;
|
int mBasePriority;
|
||||||
} slotlist[] = {
|
} slotlist[] = {
|
||||||
// FIXME: Priority is based on the number of reserved slots. There should be a better way.
|
// FIXME: Priority is based on the number of reserved slots. There should be a better way.
|
||||||
{ MWWorld::InventoryStore::Slot_Robe, 12 },
|
{ MWWorld::InventoryStore::Slot_Robe, 11 },
|
||||||
{ MWWorld::InventoryStore::Slot_Skirt, 3 },
|
{ MWWorld::InventoryStore::Slot_Skirt, 3 },
|
||||||
{ MWWorld::InventoryStore::Slot_Helmet, 0 },
|
{ MWWorld::InventoryStore::Slot_Helmet, 0 },
|
||||||
{ MWWorld::InventoryStore::Slot_Cuirass, 0 },
|
{ MWWorld::InventoryStore::Slot_Cuirass, 0 },
|
||||||
|
@ -641,7 +644,7 @@ void NpcAnimation::updateParts()
|
||||||
ESM::PartReferenceType parts[] = {
|
ESM::PartReferenceType parts[] = {
|
||||||
ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg,
|
ESM::PRT_Groin, ESM::PRT_Skirt, ESM::PRT_RLeg, ESM::PRT_LLeg,
|
||||||
ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee,
|
ESM::PRT_RUpperarm, ESM::PRT_LUpperarm, ESM::PRT_RKnee, ESM::PRT_LKnee,
|
||||||
ESM::PRT_RForearm, ESM::PRT_LForearm
|
ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass
|
||||||
};
|
};
|
||||||
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
|
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
|
||||||
for(size_t p = 0;p < parts_size;++p)
|
for(size_t p = 0;p < parts_size;++p)
|
||||||
|
|
92
apps/openmw/mwrender/recastmesh.cpp
Normal file
92
apps/openmw/mwrender/recastmesh.cpp
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#include "recastmesh.hpp"
|
||||||
|
|
||||||
|
#include <components/sceneutil/vismask.hpp>
|
||||||
|
#include <components/sceneutil/recastmesh.hpp>
|
||||||
|
#include <components/debug/debuglog.hpp>
|
||||||
|
|
||||||
|
#include <osg/PositionAttitudeTransform>
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
RecastMesh::RecastMesh(const osg::ref_ptr<osg::Group>& root, bool enabled)
|
||||||
|
: mRootNode(root)
|
||||||
|
, mEnabled(enabled)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RecastMesh::~RecastMesh()
|
||||||
|
{
|
||||||
|
if (mEnabled)
|
||||||
|
disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RecastMesh::toggle()
|
||||||
|
{
|
||||||
|
if (mEnabled)
|
||||||
|
disable();
|
||||||
|
else
|
||||||
|
enable();
|
||||||
|
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecastMesh::update(const DetourNavigator::RecastMeshTiles& tiles, const DetourNavigator::Settings& settings)
|
||||||
|
{
|
||||||
|
if (!mEnabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto it = mGroups.begin(); it != mGroups.end();)
|
||||||
|
{
|
||||||
|
const auto tile = tiles.find(it->first);
|
||||||
|
if (tile == tiles.end())
|
||||||
|
{
|
||||||
|
mRootNode->removeChild(it->second.mValue);
|
||||||
|
it = mGroups.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it->second.mGeneration != tile->second->getGeneration()
|
||||||
|
|| it->second.mRevision != tile->second->getRevision())
|
||||||
|
{
|
||||||
|
const auto group = SceneUtil::createRecastMeshGroup(*tile->second, settings);
|
||||||
|
group->setNodeMask(SceneUtil::Mask_Debug);
|
||||||
|
mRootNode->removeChild(it->second.mValue);
|
||||||
|
mRootNode->addChild(group);
|
||||||
|
it->second.mValue = group;
|
||||||
|
it->second.mGeneration = tile->second->getGeneration();
|
||||||
|
it->second.mRevision = tile->second->getRevision();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& tile : tiles)
|
||||||
|
{
|
||||||
|
if (mGroups.count(tile.first))
|
||||||
|
continue;
|
||||||
|
const auto group = SceneUtil::createRecastMeshGroup(*tile.second, settings);
|
||||||
|
group->setNodeMask(SceneUtil::Mask_Debug);
|
||||||
|
mGroups.emplace(tile.first, Group {tile.second->getGeneration(), tile.second->getRevision(), group});
|
||||||
|
mRootNode->addChild(group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecastMesh::reset()
|
||||||
|
{
|
||||||
|
std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); });
|
||||||
|
mGroups.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecastMesh::enable()
|
||||||
|
{
|
||||||
|
std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->addChild(v.second.mValue); });
|
||||||
|
mEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RecastMesh::disable()
|
||||||
|
{
|
||||||
|
std::for_each(mGroups.begin(), mGroups.end(), [&] (const auto& v) { mRootNode->removeChild(v.second.mValue); });
|
||||||
|
mEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
53
apps/openmw/mwrender/recastmesh.hpp
Normal file
53
apps/openmw/mwrender/recastmesh.hpp
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
#ifndef OPENMW_MWRENDER_RECASTMESH_H
|
||||||
|
#define OPENMW_MWRENDER_RECASTMESH_H
|
||||||
|
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Group;
|
||||||
|
class Geometry;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace MWRender
|
||||||
|
{
|
||||||
|
class RecastMesh
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RecastMesh(const osg::ref_ptr<osg::Group>& root, bool enabled);
|
||||||
|
~RecastMesh();
|
||||||
|
|
||||||
|
bool toggle();
|
||||||
|
|
||||||
|
void update(const DetourNavigator::RecastMeshTiles& recastMeshTiles, const DetourNavigator::Settings& settings);
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void enable();
|
||||||
|
|
||||||
|
void disable();
|
||||||
|
|
||||||
|
bool isEnabled() const
|
||||||
|
{
|
||||||
|
return mEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Group
|
||||||
|
{
|
||||||
|
std::size_t mGeneration;
|
||||||
|
std::size_t mRevision;
|
||||||
|
osg::ref_ptr<osg::Group> mValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
osg::ref_ptr<osg::Group> mRootNode;
|
||||||
|
bool mEnabled;
|
||||||
|
std::map<DetourNavigator::TilePosition, Group> mGroups;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -66,6 +66,7 @@
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
#include "navmesh.hpp"
|
#include "navmesh.hpp"
|
||||||
#include "actorspaths.hpp"
|
#include "actorspaths.hpp"
|
||||||
|
#include "recastmesh.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
@ -217,7 +218,9 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
resourceSystem->getSceneManager()->setParticleSystemMask(SceneUtil::Mask_ParticleSystem);
|
resourceSystem->getSceneManager()->setParticleSystemMask(SceneUtil::Mask_ParticleSystem);
|
||||||
resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders");
|
resourceSystem->getSceneManager()->setShaderPath(resourcePath + "/shaders");
|
||||||
resourceSystem->getSceneManager()->setForceShaders(Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows")); // Shadows have problems with fixed-function mode
|
// Shadows and radial fog have problems with fixed-function mode
|
||||||
|
bool forceShaders = Settings::Manager::getBool("radial fog", "Shaders") || Settings::Manager::getBool("force shaders", "Shaders") || Settings::Manager::getBool("enable shadows", "Shadows");
|
||||||
|
resourceSystem->getSceneManager()->setForceShaders(forceShaders);
|
||||||
// FIXME: calling dummy method because terrain needs to know whether lighting is clamped
|
// FIXME: calling dummy method because terrain needs to know whether lighting is clamped
|
||||||
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
|
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "Shaders"));
|
||||||
resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders"));
|
resourceSystem->getSceneManager()->setAutoUseNormalMaps(Settings::Manager::getBool("auto use object normal maps", "Shaders"));
|
||||||
|
@ -254,12 +257,14 @@ namespace MWRender
|
||||||
globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0";
|
globalDefines["forcePPL"] = Settings::Manager::getBool("force per pixel lighting", "Shaders") ? "1" : "0";
|
||||||
globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0";
|
globalDefines["clamp"] = Settings::Manager::getBool("clamp lighting", "Shaders") ? "1" : "0";
|
||||||
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0";
|
globalDefines["preLightEnv"] = Settings::Manager::getBool("apply lighting to environment maps", "Shaders") ? "1" : "0";
|
||||||
|
globalDefines["radialFog"] = Settings::Manager::getBool("radial fog", "Shaders") ? "1" : "0";
|
||||||
|
|
||||||
// It is unnecessary to stop/start the viewer as no frames are being rendered yet.
|
// It is unnecessary to stop/start the viewer as no frames are being rendered yet.
|
||||||
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines);
|
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines);
|
||||||
|
|
||||||
mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator")));
|
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")));
|
mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")));
|
||||||
|
mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator")));
|
||||||
mPathgrid.reset(new Pathgrid(mRootNode));
|
mPathgrid.reset(new Pathgrid(mRootNode));
|
||||||
|
|
||||||
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
||||||
|
@ -585,6 +590,10 @@ namespace MWRender
|
||||||
{
|
{
|
||||||
return mActorsPaths->toggle();
|
return mActorsPaths->toggle();
|
||||||
}
|
}
|
||||||
|
else if (mode == Render_RecastMesh)
|
||||||
|
{
|
||||||
|
return mRecastMesh->toggle();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,6 +660,7 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNavMesh();
|
updateNavMesh();
|
||||||
|
updateRecastMesh();
|
||||||
|
|
||||||
mCamera->update(dt, paused);
|
mCamera->update(dt, paused);
|
||||||
|
|
||||||
|
@ -1463,4 +1473,12 @@ namespace MWRender
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderingManager::updateRecastMesh()
|
||||||
|
{
|
||||||
|
if (!mRecastMesh->isEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
mRecastMesh->update(mNavigator.getRecastMeshTiles(), mNavigator.getSettings());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,7 @@ namespace MWRender
|
||||||
class LandManager;
|
class LandManager;
|
||||||
class NavMesh;
|
class NavMesh;
|
||||||
class ActorsPaths;
|
class ActorsPaths;
|
||||||
|
class RecastMesh;
|
||||||
|
|
||||||
class RenderingManager : public MWRender::RenderingInterface
|
class RenderingManager : public MWRender::RenderingInterface
|
||||||
{
|
{
|
||||||
|
@ -246,6 +247,8 @@ namespace MWRender
|
||||||
|
|
||||||
void updateNavMesh();
|
void updateNavMesh();
|
||||||
|
|
||||||
|
void updateRecastMesh();
|
||||||
|
|
||||||
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
|
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
|
||||||
|
|
||||||
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;
|
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;
|
||||||
|
@ -264,6 +267,7 @@ namespace MWRender
|
||||||
std::unique_ptr<NavMesh> mNavMesh;
|
std::unique_ptr<NavMesh> mNavMesh;
|
||||||
std::size_t mNavMeshNumber = 0;
|
std::size_t mNavMeshNumber = 0;
|
||||||
std::unique_ptr<ActorsPaths> mActorsPaths;
|
std::unique_ptr<ActorsPaths> mActorsPaths;
|
||||||
|
std::unique_ptr<RecastMesh> mRecastMesh;
|
||||||
std::unique_ptr<Pathgrid> mPathgrid;
|
std::unique_ptr<Pathgrid> mPathgrid;
|
||||||
std::unique_ptr<Objects> mObjects;
|
std::unique_ptr<Objects> mObjects;
|
||||||
std::unique_ptr<Water> mWater;
|
std::unique_ptr<Water> mWater;
|
||||||
|
|
|
@ -13,6 +13,7 @@ namespace MWRender
|
||||||
Render_Scene,
|
Render_Scene,
|
||||||
Render_NavMesh,
|
Render_NavMesh,
|
||||||
Render_ActorsPaths,
|
Render_ActorsPaths,
|
||||||
|
Render_RecastMesh,
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -463,5 +463,6 @@ op 0x200030c: RepairedOnMe
|
||||||
op 0x200030d: RepairedOnMe, explicit
|
op 0x200030d: RepairedOnMe, explicit
|
||||||
op 0x200030e: TestCells
|
op 0x200030e: TestCells
|
||||||
op 0x200030f: TestInteriorCells
|
op 0x200030f: TestInteriorCells
|
||||||
|
op 0x2000310: ToggleRecastMesh
|
||||||
|
|
||||||
opcodes 0x2000310-0x3ffffff unused
|
opcodes 0x2000311-0x3ffffff unused
|
||||||
|
|
|
@ -1559,6 +1559,20 @@ namespace MWScript
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class OpToggleRecastMesh : public Interpreter::Opcode0
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual void execute (Interpreter::Runtime& runtime)
|
||||||
|
{
|
||||||
|
bool enabled =
|
||||||
|
MWBase::Environment::get().getWorld()->toggleRenderMode (MWRender::Render_RecastMesh);
|
||||||
|
|
||||||
|
runtime.getContext().report (enabled ?
|
||||||
|
"Recast Mesh Rendering -> On" : "Recast Mesh Rendering -> Off");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void installOpcodes (Interpreter::Interpreter& interpreter)
|
void installOpcodes (Interpreter::Interpreter& interpreter)
|
||||||
{
|
{
|
||||||
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
|
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
|
||||||
|
@ -1664,6 +1678,7 @@ namespace MWScript
|
||||||
interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender);
|
interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender);
|
||||||
interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe<ImplicitRef>);
|
interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe<ImplicitRef>);
|
||||||
interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe<ExplicitRef>);
|
interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMeExplicit, new OpRepairedOnMe<ExplicitRef>);
|
||||||
|
interpreter.installSegment5 (Compiler::Misc::opcodeToggleRecastMesh, new OpToggleRecastMesh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -346,7 +346,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr
|
||||||
item.getCellRef().setOwner("");
|
item.getCellRef().setOwner("");
|
||||||
item.getCellRef().resetGlobalVariable();
|
item.getCellRef().resetGlobalVariable();
|
||||||
item.getCellRef().setFaction("");
|
item.getCellRef().setFaction("");
|
||||||
item.getCellRef().setFactionRank(-1);
|
item.getCellRef().setFactionRank(-2);
|
||||||
|
|
||||||
// must reset the RefNum on the copied item, so that the RefNum on the original item stays unique
|
// must reset the RefNum on the copied item, so that the RefNum on the original item stays unique
|
||||||
// maybe we should do this in the copy constructor instead?
|
// maybe we should do this in the copy constructor instead?
|
||||||
|
|
|
@ -78,6 +78,7 @@
|
||||||
#include "../mwphysics/actor.hpp"
|
#include "../mwphysics/actor.hpp"
|
||||||
#include "../mwphysics/collisiontype.hpp"
|
#include "../mwphysics/collisiontype.hpp"
|
||||||
#include "../mwphysics/object.hpp"
|
#include "../mwphysics/object.hpp"
|
||||||
|
#include "../mwphysics/constants.hpp"
|
||||||
|
|
||||||
#include "player.hpp"
|
#include "player.hpp"
|
||||||
#include "manualref.hpp"
|
#include "manualref.hpp"
|
||||||
|
@ -1627,7 +1628,9 @@ namespace MWWorld
|
||||||
|
|
||||||
pos.z() += 20; // place slightly above. will snap down to ground with code below
|
pos.z() += 20; // place slightly above. will snap down to ground with code below
|
||||||
|
|
||||||
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !isSwimming(ptr) && isActorCollisionEnabled(ptr)))
|
// We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
|
||||||
|
bool swims = ptr.getClass().isActor() && isSwimming(ptr) && !(ptr.getClass().isPersistent(ptr) && ptr.getClass().getCreatureStats(ptr).isDeathAnimationFinished());
|
||||||
|
if (force || !ptr.getClass().isActor() || (!isFlying(ptr) && !swims && isActorCollisionEnabled(ptr)))
|
||||||
{
|
{
|
||||||
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
|
osg::Vec3f traced = mPhysics->traceDown(ptr, pos, Constants::CellSizeInUnits);
|
||||||
if (traced.z() < pos.z())
|
if (traced.z() < pos.z())
|
||||||
|
|
|
@ -25,12 +25,15 @@ namespace
|
||||||
{
|
{
|
||||||
const osg::Vec3f mAgentHalfExtents {1, 2, 3};
|
const osg::Vec3f mAgentHalfExtents {1, 2, 3};
|
||||||
const TilePosition mTilePosition {0, 0};
|
const TilePosition mTilePosition {0, 0};
|
||||||
|
const std::size_t mGeneration = 0;
|
||||||
|
const std::size_t mRevision = 0;
|
||||||
const std::vector<int> mIndices {{0, 1, 2}};
|
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<float> mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}};
|
||||||
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
|
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
|
||||||
const std::vector<RecastMesh::Water> mWater {};
|
const std::vector<RecastMesh::Water> mWater {};
|
||||||
const std::size_t mTrianglesPerChunk {1};
|
const std::size_t mTrianglesPerChunk {1};
|
||||||
const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk};
|
const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
|
mAreaTypes, mWater, mTrianglesPerChunk};
|
||||||
const std::vector<OffMeshConnection> mOffMeshConnections {};
|
const std::vector<OffMeshConnection> mOffMeshConnections {};
|
||||||
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData mNavMeshData {mData, 1};
|
NavMeshData mNavMeshData {mData, 1};
|
||||||
|
@ -128,7 +131,8 @@ namespace
|
||||||
const std::size_t maxSize = 1;
|
const std::size_t maxSize = 1;
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
|
mAreaTypes, water, mTrianglesPerChunk};
|
||||||
|
|
||||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections));
|
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections));
|
||||||
|
@ -142,7 +146,8 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
|
mAreaTypes, water, mTrianglesPerChunk};
|
||||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||||
|
|
||||||
|
@ -162,7 +167,8 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
|
mAreaTypes, water, mTrianglesPerChunk};
|
||||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||||
|
|
||||||
|
@ -180,14 +186,14 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater,
|
const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
mTrianglesPerChunk};
|
mAreaTypes, leastRecentlySetWater, mTrianglesPerChunk};
|
||||||
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
|
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||||
const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater,
|
const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
mTrianglesPerChunk};
|
mAreaTypes, mostRecentlySetWater, mTrianglesPerChunk};
|
||||||
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
|
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
|
||||||
|
|
||||||
|
@ -212,14 +218,14 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater,
|
const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
mTrianglesPerChunk};
|
mAreaTypes, leastRecentlyUsedWater, mTrianglesPerChunk};
|
||||||
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
|
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||||
const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater,
|
const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
mTrianglesPerChunk};
|
mAreaTypes, mostRecentlyUsedWater, mTrianglesPerChunk};
|
||||||
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1};
|
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1};
|
||||||
|
|
||||||
|
@ -256,7 +262,7 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||||
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
||||||
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
||||||
|
|
||||||
|
@ -275,12 +281,13 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> anotherWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk};
|
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, anotherWater, mTrianglesPerChunk};
|
||||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||||
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk};
|
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
|
mAreaTypes, tooLargeWater, mTrianglesPerChunk};
|
||||||
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
||||||
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
||||||
|
|
||||||
|
@ -303,7 +310,8 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||||
|
mAreaTypes, water, mTrianglesPerChunk};
|
||||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||||
|
|
||||||
|
@ -326,7 +334,7 @@ namespace
|
||||||
NavMeshTilesCache cache(maxSize);
|
NavMeshTilesCache cache(maxSize);
|
||||||
|
|
||||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||||
const RecastMesh anotherRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
const RecastMesh anotherRecastMesh {mGeneration, mRevision, mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||||
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
const auto anotherData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,8 @@ namespace
|
||||||
{
|
{
|
||||||
Settings mSettings;
|
Settings mSettings;
|
||||||
TileBounds mBounds;
|
TileBounds mBounds;
|
||||||
|
const std::size_t mGeneration = 0;
|
||||||
|
const std::size_t mRevision = 0;
|
||||||
|
|
||||||
DetourNavigatorRecastMeshBuilderTest()
|
DetourNavigatorRecastMeshBuilderTest()
|
||||||
{
|
{
|
||||||
|
@ -45,7 +47,7 @@ namespace
|
||||||
TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty)
|
TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty)
|
||||||
{
|
{
|
||||||
RecastMeshBuilder builder(mSettings, mBounds);
|
RecastMeshBuilder builder(mSettings, mBounds);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>());
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>());
|
||||||
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>());
|
EXPECT_EQ(recastMesh->getIndices(), std::vector<int>());
|
||||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>());
|
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>());
|
||||||
|
@ -59,7 +61,7 @@ namespace
|
||||||
|
|
||||||
RecastMeshBuilder builder(mSettings, mBounds);
|
RecastMeshBuilder builder(mSettings, mBounds);
|
||||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
1, 0, -1,
|
1, 0, -1,
|
||||||
-1, 0, 1,
|
-1, 0, 1,
|
||||||
|
@ -80,7 +82,7 @@ namespace
|
||||||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
2, 3, 0,
|
2, 3, 0,
|
||||||
0, 3, 4,
|
0, 3, 4,
|
||||||
|
@ -96,7 +98,7 @@ namespace
|
||||||
btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||||
RecastMeshBuilder builder(mSettings, mBounds);
|
RecastMeshBuilder builder(mSettings, mBounds);
|
||||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
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,
|
||||||
|
@ -114,7 +116,7 @@ namespace
|
||||||
btBoxShape shape(btVector3(1, 1, 2));
|
btBoxShape shape(btVector3(1, 1, 2));
|
||||||
RecastMeshBuilder builder(mSettings, mBounds);
|
RecastMeshBuilder builder(mSettings, mBounds);
|
||||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
1, 2, 1,
|
1, 2, 1,
|
||||||
-1, 2, 1,
|
-1, 2, 1,
|
||||||
|
@ -161,7 +163,7 @@ namespace
|
||||||
btTransform::getIdentity(),
|
btTransform::getIdentity(),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
1, 0, -1,
|
1, 0, -1,
|
||||||
-1, 0, 1,
|
-1, 0, 1,
|
||||||
|
@ -210,7 +212,7 @@ namespace
|
||||||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
2, 3, 0,
|
2, 3, 0,
|
||||||
0, 3, 4,
|
0, 3, 4,
|
||||||
|
@ -234,7 +236,7 @@ namespace
|
||||||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
3, 12, 2,
|
3, 12, 2,
|
||||||
1, 12, 10,
|
1, 12, 10,
|
||||||
|
@ -256,7 +258,7 @@ namespace
|
||||||
btTransform::getIdentity(),
|
btTransform::getIdentity(),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
1, 0, -1,
|
1, 0, -1,
|
||||||
-1, 0, 1,
|
-1, 0, 1,
|
||||||
|
@ -284,7 +286,7 @@ namespace
|
||||||
btTransform::getIdentity(),
|
btTransform::getIdentity(),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
-0.2f, 0, -0.3f,
|
-0.2f, 0, -0.3f,
|
||||||
-0.3f, 0, -0.2f,
|
-0.3f, 0, -0.2f,
|
||||||
|
@ -309,7 +311,7 @@ namespace
|
||||||
static_cast<btScalar>(-osg::PI_4))),
|
static_cast<btScalar>(-osg::PI_4))),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
0, -0.70710659027099609375, -3.535533905029296875,
|
0, -0.70710659027099609375, -3.535533905029296875,
|
||||||
0, 0.707107067108154296875, -3.535533905029296875,
|
0, 0.707107067108154296875, -3.535533905029296875,
|
||||||
|
@ -334,7 +336,7 @@ namespace
|
||||||
static_cast<btScalar>(osg::PI_4))),
|
static_cast<btScalar>(osg::PI_4))),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
-3.535533905029296875, -0.70710659027099609375, 0,
|
-3.535533905029296875, -0.70710659027099609375, 0,
|
||||||
-3.535533905029296875, 0.707107067108154296875, 0,
|
-3.535533905029296875, 0.707107067108154296875, 0,
|
||||||
|
@ -359,7 +361,7 @@ namespace
|
||||||
static_cast<btScalar>(osg::PI_4))),
|
static_cast<btScalar>(osg::PI_4))),
|
||||||
AreaType_ground
|
AreaType_ground
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
1.41421353816986083984375, 0, 1.1920928955078125e-07,
|
1.41421353816986083984375, 0, 1.1920928955078125e-07,
|
||||||
-1.41421353816986083984375, 0, -1.1920928955078125e-07,
|
-1.41421353816986083984375, 0, -1.1920928955078125e-07,
|
||||||
|
@ -388,7 +390,7 @@ namespace
|
||||||
btTransform::getIdentity(),
|
btTransform::getIdentity(),
|
||||||
AreaType_null
|
AreaType_null
|
||||||
);
|
);
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||||
1, 0, -1,
|
1, 0, -1,
|
||||||
-1, 0, 1,
|
-1, 0, 1,
|
||||||
|
@ -405,7 +407,7 @@ namespace
|
||||||
{
|
{
|
||||||
RecastMeshBuilder builder(mSettings, mBounds);
|
RecastMeshBuilder builder(mSettings, mBounds);
|
||||||
builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300)));
|
builder.addWater(1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300)));
|
||||||
const auto recastMesh = builder.create();
|
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||||
EXPECT_EQ(recastMesh->getWater(), std::vector<RecastMesh::Water>({
|
EXPECT_EQ(recastMesh->getWater(), std::vector<RecastMesh::Water>({
|
||||||
RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))}
|
RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))}
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -52,7 +52,7 @@ add_component_dir (shader
|
||||||
add_component_dir (sceneutil
|
add_component_dir (sceneutil
|
||||||
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
||||||
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
||||||
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique vismask
|
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique vismask recastmesh
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (nif
|
add_component_dir (nif
|
||||||
|
|
|
@ -33,7 +33,7 @@ namespace Compiler
|
||||||
{
|
{
|
||||||
extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate,
|
extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate,
|
||||||
opcodeAIActivateExplicit);
|
opcodeAIActivateExplicit);
|
||||||
extensions.registerInstruction ("aitravel", "fff/zx", opcodeAiTravel,
|
extensions.registerInstruction ("aitravel", "fff/lx", opcodeAiTravel,
|
||||||
opcodeAiTravelExplicit);
|
opcodeAiTravelExplicit);
|
||||||
extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort,
|
extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort,
|
||||||
opcodeAiEscortExplicit);
|
opcodeAiEscortExplicit);
|
||||||
|
@ -327,6 +327,7 @@ namespace Compiler
|
||||||
extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths);
|
extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths);
|
||||||
extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender);
|
extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender);
|
||||||
extensions.registerFunction ("repairedonme", 'l', "S", opcodeRepairedOnMe, opcodeRepairedOnMeExplicit);
|
extensions.registerFunction ("repairedonme", 'l', "S", opcodeRepairedOnMe, opcodeRepairedOnMeExplicit);
|
||||||
|
extensions.registerInstruction ("togglerecastmesh", "", opcodeToggleRecastMesh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -304,6 +304,7 @@ namespace Compiler
|
||||||
const int opcodeSetNavMeshNumberToRender = 0x200030a;
|
const int opcodeSetNavMeshNumberToRender = 0x200030a;
|
||||||
const int opcodeRepairedOnMe = 0x200030c;
|
const int opcodeRepairedOnMe = 0x200030c;
|
||||||
const int opcodeRepairedOnMeExplicit = 0x200030d;
|
const int opcodeRepairedOnMeExplicit = 0x200030d;
|
||||||
|
const int opcodeToggleRecastMesh = 0x2000310;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Sky
|
namespace Sky
|
||||||
|
|
|
@ -282,6 +282,8 @@ namespace Compiler
|
||||||
|
|
||||||
if (!scanName (name))
|
if (!scanName (name))
|
||||||
return false;
|
return false;
|
||||||
|
else if(name.empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
TokenLoc loc (mLoc);
|
TokenLoc loc (mLoc);
|
||||||
mLoc.mLiteral.clear();
|
mLoc.mLiteral.clear();
|
||||||
|
@ -368,6 +370,26 @@ namespace Compiler
|
||||||
mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc);
|
mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
bool allWhitespace = true;
|
||||||
|
for (size_t i = 1; i < name.size(); i++)
|
||||||
|
{
|
||||||
|
//ignore comments
|
||||||
|
if (name[i] == ';')
|
||||||
|
break;
|
||||||
|
else if (name[i] != '\t' && name[i] != ' ' && name[i] != '\r')
|
||||||
|
{
|
||||||
|
allWhitespace = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (allWhitespace)
|
||||||
|
{
|
||||||
|
name.clear();
|
||||||
|
mLoc.mLiteral.clear();
|
||||||
|
mErrorHandler.warning ("unterminated empty string", mLoc);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
error = true;
|
error = true;
|
||||||
mErrorHandler.error ("incomplete string or name", mLoc);
|
mErrorHandler.error ("incomplete string or name", mLoc);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -45,6 +45,9 @@ namespace ContentSelectorView
|
||||||
QWidget *uiWidget() const
|
QWidget *uiWidget() const
|
||||||
{ return ui.contentGroupBox; }
|
{ return ui.contentGroupBox; }
|
||||||
|
|
||||||
|
QToolButton *refreshButton() const
|
||||||
|
{ return ui.refreshButton; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds)
|
CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds,
|
||||||
: mImpl(settings, bounds)
|
std::size_t generation)
|
||||||
|
: mImpl(settings, bounds, generation)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape,
|
bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape,
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace DetourNavigator
|
||||||
class CachedRecastMeshManager
|
class CachedRecastMeshManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds);
|
CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation);
|
||||||
|
|
||||||
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||||
const AreaType areaType);
|
const AreaType areaType);
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
#include "objectid.hpp"
|
#include "objectid.hpp"
|
||||||
#include "navmeshcacheitem.hpp"
|
#include "navmeshcacheitem.hpp"
|
||||||
|
#include "recastmesh.hpp"
|
||||||
|
#include "recastmeshtiles.hpp"
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
|
@ -204,6 +206,8 @@ namespace DetourNavigator
|
||||||
*/
|
*/
|
||||||
boost::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
|
boost::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
|
||||||
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const;
|
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const;
|
||||||
|
|
||||||
|
virtual RecastMeshTiles getRecastMeshTiles() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,11 @@ namespace DetourNavigator
|
||||||
mNavMeshManager.reportStats(frameNumber, stats);
|
mNavMeshManager.reportStats(frameNumber, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecastMeshTiles NavigatorImpl::getRecastMeshTiles()
|
||||||
|
{
|
||||||
|
return mNavMeshManager.getRecastMeshTiles();
|
||||||
|
}
|
||||||
|
|
||||||
void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId)
|
void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId)
|
||||||
{
|
{
|
||||||
updateId(id, avoidId, mWaterIds);
|
updateId(id, avoidId, mWaterIds);
|
||||||
|
|
|
@ -50,6 +50,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
||||||
|
|
||||||
|
RecastMeshTiles getRecastMeshTiles() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Settings mSettings;
|
Settings mSettings;
|
||||||
NavMeshManager mNavMeshManager;
|
NavMeshManager mNavMeshManager;
|
||||||
|
|
|
@ -81,6 +81,11 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {}
|
void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {}
|
||||||
|
|
||||||
|
RecastMeshTiles getRecastMeshTiles() override
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Settings mDefaultSettings {};
|
Settings mDefaultSettings {};
|
||||||
SharedNavMeshCacheItem mEmptyNavMeshCacheItem;
|
SharedNavMeshCacheItem mEmptyNavMeshCacheItem;
|
||||||
|
|
|
@ -147,7 +147,7 @@ namespace DetourNavigator
|
||||||
const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition));
|
const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition));
|
||||||
auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
|
auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
|
||||||
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
|
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
|
||||||
if (lastRevision >= mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
|
if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
|
||||||
&& lastPlayerTile->second == playerTile)
|
&& lastPlayerTile->second == playerTile)
|
||||||
return;
|
return;
|
||||||
lastRevision = mRecastMeshManager.getRevision();
|
lastRevision = mRecastMeshManager.getRevision();
|
||||||
|
@ -220,6 +220,17 @@ namespace DetourNavigator
|
||||||
mAsyncNavMeshUpdater.reportStats(frameNumber, stats);
|
mAsyncNavMeshUpdater.reportStats(frameNumber, stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecastMeshTiles NavMeshManager::getRecastMeshTiles()
|
||||||
|
{
|
||||||
|
std::vector<TilePosition> tiles;
|
||||||
|
mRecastMeshManager.forEachTilePosition(
|
||||||
|
[&tiles] (const TilePosition& tile) { tiles.push_back(tile); });
|
||||||
|
RecastMeshTiles result;
|
||||||
|
std::transform(tiles.begin(), tiles.end(), std::inserter(result, result.end()),
|
||||||
|
[this] (const TilePosition& tile) { return std::make_pair(tile, mRecastMeshManager.getMesh(tile)); });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
|
void NavMeshManager::addChangedTiles(const btCollisionShape& shape, const btTransform& transform,
|
||||||
const ChangeType changeType)
|
const ChangeType changeType)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "cachedrecastmeshmanager.hpp"
|
#include "cachedrecastmeshmanager.hpp"
|
||||||
#include "offmeshconnectionsmanager.hpp"
|
#include "offmeshconnectionsmanager.hpp"
|
||||||
#include "sharednavmesh.hpp"
|
#include "sharednavmesh.hpp"
|
||||||
|
#include "recastmeshtiles.hpp"
|
||||||
|
|
||||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||||
|
|
||||||
|
@ -52,6 +53,8 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||||
|
|
||||||
|
RecastMeshTiles getRecastMeshTiles();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const Settings& mSettings;
|
const Settings& mSettings;
|
||||||
TileCachedRecastMeshManager mRecastMeshManager;
|
TileCachedRecastMeshManager mRecastMeshManager;
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
|
RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, std::vector<int> indices, std::vector<float> vertices,
|
||||||
std::vector<Water> water, const std::size_t trianglesPerChunk)
|
std::vector<AreaType> areaTypes, std::vector<Water> water, const std::size_t trianglesPerChunk)
|
||||||
: mIndices(std::move(indices))
|
: mGeneration(generation)
|
||||||
|
, mRevision(revision)
|
||||||
|
, mIndices(std::move(indices))
|
||||||
, mVertices(std::move(vertices))
|
, mVertices(std::move(vertices))
|
||||||
, mAreaTypes(std::move(areaTypes))
|
, mAreaTypes(std::move(areaTypes))
|
||||||
, mWater(std::move(water))
|
, mWater(std::move(water))
|
||||||
|
|
|
@ -24,8 +24,18 @@ namespace DetourNavigator
|
||||||
btTransform mTransform;
|
btTransform mTransform;
|
||||||
};
|
};
|
||||||
|
|
||||||
RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
|
RecastMesh(std::size_t generation, std::size_t revision, std::vector<int> indices, std::vector<float> vertices,
|
||||||
std::vector<Water> water, const std::size_t trianglesPerChunk);
|
std::vector<AreaType> areaTypes, std::vector<Water> water, const std::size_t trianglesPerChunk);
|
||||||
|
|
||||||
|
std::size_t getGeneration() const
|
||||||
|
{
|
||||||
|
return mGeneration;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t getRevision() const
|
||||||
|
{
|
||||||
|
return mRevision;
|
||||||
|
}
|
||||||
|
|
||||||
const std::vector<int>& getIndices() const
|
const std::vector<int>& getIndices() const
|
||||||
{
|
{
|
||||||
|
@ -68,6 +78,8 @@ namespace DetourNavigator
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::size_t mGeneration;
|
||||||
|
std::size_t mRevision;
|
||||||
std::vector<int> mIndices;
|
std::vector<int> mIndices;
|
||||||
std::vector<float> mVertices;
|
std::vector<float> mVertices;
|
||||||
std::vector<AreaType> mAreaTypes;
|
std::vector<AreaType> mAreaTypes;
|
||||||
|
|
|
@ -112,9 +112,10 @@ namespace DetourNavigator
|
||||||
mWater.push_back(RecastMesh::Water {cellSize, transform});
|
mWater.push_back(RecastMesh::Water {cellSize, transform});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<RecastMesh> RecastMeshBuilder::create() const
|
std::shared_ptr<RecastMesh> RecastMeshBuilder::create(std::size_t generation, std::size_t revision) const
|
||||||
{
|
{
|
||||||
return std::make_shared<RecastMesh>(mIndices, mVertices, mAreaTypes, mWater, mSettings.get().mTrianglesPerChunk);
|
return std::make_shared<RecastMesh>(generation, revision, mIndices, mVertices, mAreaTypes,
|
||||||
|
mWater, mSettings.get().mTrianglesPerChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RecastMeshBuilder::reset()
|
void RecastMeshBuilder::reset()
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void addWater(const int mCellSize, const btTransform& transform);
|
void addWater(const int mCellSize, const btTransform& transform);
|
||||||
|
|
||||||
std::shared_ptr<RecastMesh> create() const;
|
std::shared_ptr<RecastMesh> create(std::size_t generation, std::size_t revision) const;
|
||||||
|
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds)
|
RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation)
|
||||||
: mShouldRebuild(false)
|
: mGeneration(generation)
|
||||||
, mMeshBuilder(settings, bounds)
|
, mMeshBuilder(settings, bounds)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ namespace DetourNavigator
|
||||||
mObjectsOrder.erase(iterator);
|
mObjectsOrder.erase(iterator);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mShouldRebuild = true;
|
++mRevision;
|
||||||
return mShouldRebuild;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
|
bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
|
||||||
|
@ -30,8 +30,8 @@ namespace DetourNavigator
|
||||||
return false;
|
return false;
|
||||||
if (!object->second->update(transform, areaType))
|
if (!object->second->update(transform, areaType))
|
||||||
return false;
|
return false;
|
||||||
mShouldRebuild = true;
|
++mRevision;
|
||||||
return mShouldRebuild;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::optional<RemovedRecastMeshObject> RecastMeshManager::removeObject(const ObjectId id)
|
boost::optional<RemovedRecastMeshObject> RecastMeshManager::removeObject(const ObjectId id)
|
||||||
|
@ -42,7 +42,7 @@ namespace DetourNavigator
|
||||||
const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()};
|
const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()};
|
||||||
mObjectsOrder.erase(object->second);
|
mObjectsOrder.erase(object->second);
|
||||||
mObjects.erase(object);
|
mObjects.erase(object);
|
||||||
mShouldRebuild = true;
|
++mRevision;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ namespace DetourNavigator
|
||||||
mWaterOrder.erase(iterator);
|
mWaterOrder.erase(iterator);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
mShouldRebuild = true;
|
++mRevision;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ namespace DetourNavigator
|
||||||
const auto water = mWater.find(cellPosition);
|
const auto water = mWater.find(cellPosition);
|
||||||
if (water == mWater.end())
|
if (water == mWater.end())
|
||||||
return boost::none;
|
return boost::none;
|
||||||
mShouldRebuild = true;
|
++mRevision;
|
||||||
const auto result = *water->second;
|
const auto result = *water->second;
|
||||||
mWaterOrder.erase(water->second);
|
mWaterOrder.erase(water->second);
|
||||||
mWater.erase(water);
|
mWater.erase(water);
|
||||||
|
@ -74,7 +74,7 @@ namespace DetourNavigator
|
||||||
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh()
|
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh()
|
||||||
{
|
{
|
||||||
rebuild();
|
rebuild();
|
||||||
return mMeshBuilder.create();
|
return mMeshBuilder.create(mGeneration, mLastBuildRevision);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RecastMeshManager::isEmpty() const
|
bool RecastMeshManager::isEmpty() const
|
||||||
|
@ -84,13 +84,13 @@ namespace DetourNavigator
|
||||||
|
|
||||||
void RecastMeshManager::rebuild()
|
void RecastMeshManager::rebuild()
|
||||||
{
|
{
|
||||||
if (!mShouldRebuild)
|
if (mLastBuildRevision == mRevision)
|
||||||
return;
|
return;
|
||||||
mMeshBuilder.reset();
|
mMeshBuilder.reset();
|
||||||
for (const auto& v : mWaterOrder)
|
for (const auto& v : mWaterOrder)
|
||||||
mMeshBuilder.addWater(v.mCellSize, v.mTransform);
|
mMeshBuilder.addWater(v.mCellSize, v.mTransform);
|
||||||
for (const auto& v : mObjectsOrder)
|
for (const auto& v : mObjectsOrder)
|
||||||
mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType());
|
mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType());
|
||||||
mShouldRebuild = false;
|
mLastBuildRevision = mRevision;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace DetourNavigator
|
||||||
btTransform mTransform;
|
btTransform mTransform;
|
||||||
};
|
};
|
||||||
|
|
||||||
RecastMeshManager(const Settings& settings, const TileBounds& bounds);
|
RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation);
|
||||||
|
|
||||||
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
bool addObject(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||||
const AreaType areaType);
|
const AreaType areaType);
|
||||||
|
@ -52,7 +52,9 @@ namespace DetourNavigator
|
||||||
bool isEmpty() const;
|
bool isEmpty() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool mShouldRebuild;
|
std::size_t mRevision = 0;
|
||||||
|
std::size_t mLastBuildRevision = 0;
|
||||||
|
std::size_t mGeneration;
|
||||||
RecastMeshBuilder mMeshBuilder;
|
RecastMeshBuilder mMeshBuilder;
|
||||||
std::list<RecastMeshObject> mObjectsOrder;
|
std::list<RecastMeshObject> mObjectsOrder;
|
||||||
std::unordered_map<ObjectId, std::list<RecastMeshObject>::iterator> mObjects;
|
std::unordered_map<ObjectId, std::list<RecastMeshObject>::iterator> mObjects;
|
||||||
|
|
15
components/detournavigator/recastmeshtiles.hpp
Normal file
15
components/detournavigator/recastmeshtiles.hpp
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H
|
||||||
|
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RECASTMESHTILE_H
|
||||||
|
|
||||||
|
#include "tileposition.hpp"
|
||||||
|
#include "recastmesh.hpp"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace DetourNavigator
|
||||||
|
{
|
||||||
|
using RecastMeshTiles = std::map<TilePosition, std::shared_ptr<RecastMesh>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -121,7 +121,7 @@ namespace DetourNavigator
|
||||||
tileBounds.mMin -= osg::Vec2f(border, border);
|
tileBounds.mMin -= osg::Vec2f(border, border);
|
||||||
tileBounds.mMax += osg::Vec2f(border, border);
|
tileBounds.mMax += osg::Vec2f(border, border);
|
||||||
tile = tiles->insert(std::make_pair(tilePosition,
|
tile = tiles->insert(std::make_pair(tilePosition,
|
||||||
CachedRecastMeshManager(mSettings, tileBounds))).first;
|
CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first;
|
||||||
}
|
}
|
||||||
if (tile->second.addWater(cellPosition, cellSize, transform))
|
if (tile->second.addWater(cellPosition, cellSize, transform))
|
||||||
{
|
{
|
||||||
|
@ -151,7 +151,10 @@ namespace DetourNavigator
|
||||||
continue;
|
continue;
|
||||||
const auto tileResult = tile->second.removeWater(cellPosition);
|
const auto tileResult = tile->second.removeWater(cellPosition);
|
||||||
if (tile->second.isEmpty())
|
if (tile->second.isEmpty())
|
||||||
|
{
|
||||||
tiles->erase(tile);
|
tiles->erase(tile);
|
||||||
|
++mTilesGeneration;
|
||||||
|
}
|
||||||
if (tileResult && !result)
|
if (tileResult && !result)
|
||||||
result = tileResult;
|
result = tileResult;
|
||||||
}
|
}
|
||||||
|
@ -189,7 +192,8 @@ namespace DetourNavigator
|
||||||
auto tileBounds = makeTileBounds(mSettings, tilePosition);
|
auto tileBounds = makeTileBounds(mSettings, tilePosition);
|
||||||
tileBounds.mMin -= osg::Vec2f(border, border);
|
tileBounds.mMin -= osg::Vec2f(border, border);
|
||||||
tileBounds.mMax += osg::Vec2f(border, border);
|
tileBounds.mMax += osg::Vec2f(border, border);
|
||||||
tile = tiles.insert(std::make_pair(tilePosition, CachedRecastMeshManager(mSettings, tileBounds))).first;
|
tile = tiles.insert(std::make_pair(
|
||||||
|
tilePosition, CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first;
|
||||||
}
|
}
|
||||||
return tile->second.addObject(id, shape, transform, areaType);
|
return tile->second.addObject(id, shape, transform, areaType);
|
||||||
}
|
}
|
||||||
|
@ -209,7 +213,10 @@ namespace DetourNavigator
|
||||||
return boost::optional<RemovedRecastMeshObject>();
|
return boost::optional<RemovedRecastMeshObject>();
|
||||||
const auto tileResult = tile->second.removeObject(id);
|
const auto tileResult = tile->second.removeObject(id);
|
||||||
if (tile->second.isEmpty())
|
if (tile->second.isEmpty())
|
||||||
|
{
|
||||||
tiles.erase(tile);
|
tiles.erase(tile);
|
||||||
|
++mTilesGeneration;
|
||||||
|
}
|
||||||
return tileResult;
|
return tileResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,7 @@ namespace DetourNavigator
|
||||||
std::unordered_map<ObjectId, std::set<TilePosition>> mObjectsTilesPositions;
|
std::unordered_map<ObjectId, std::set<TilePosition>> mObjectsTilesPositions;
|
||||||
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
|
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
|
||||||
std::size_t mRevision = 0;
|
std::size_t mRevision = 0;
|
||||||
|
std::size_t mTilesGeneration = 0;
|
||||||
|
|
||||||
bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||||
const AreaType areaType, const TilePosition& tilePosition, float border,
|
const AreaType areaType, const TilePosition& tilePosition, float border,
|
||||||
|
|
|
@ -153,14 +153,20 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool
|
||||||
esm.writeHNT("XSCL", scale);
|
esm.writeHNT("XSCL", scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!inInventory)
|
||||||
esm.writeHNOCString("ANAM", mOwner);
|
esm.writeHNOCString("ANAM", mOwner);
|
||||||
|
|
||||||
esm.writeHNOCString("BNAM", mGlobalVariable);
|
esm.writeHNOCString("BNAM", mGlobalVariable);
|
||||||
esm.writeHNOCString("XSOL", mSoul);
|
esm.writeHNOCString("XSOL", mSoul);
|
||||||
|
|
||||||
|
if (!inInventory)
|
||||||
|
{
|
||||||
esm.writeHNOCString("CNAM", mFaction);
|
esm.writeHNOCString("CNAM", mFaction);
|
||||||
if (mFactionRank != -2) {
|
if (mFactionRank != -2)
|
||||||
|
{
|
||||||
esm.writeHNT("INDX", mFactionRank);
|
esm.writeHNT("INDX", mFactionRank);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mEnchantmentCharge != -1)
|
if (mEnchantmentCharge != -1)
|
||||||
esm.writeHNT("XCHG", mEnchantmentCharge);
|
esm.writeHNT("XCHG", mEnchantmentCharge);
|
||||||
|
|
|
@ -14,6 +14,15 @@ namespace ESM
|
||||||
|
|
||||||
virtual void load (ESMReader &esm);
|
virtual void load (ESMReader &esm);
|
||||||
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
||||||
|
|
||||||
|
virtual ContainerState& asContainerState()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
virtual const ContainerState& asContainerState() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,15 @@ namespace ESM
|
||||||
|
|
||||||
virtual void load (ESMReader &esm);
|
virtual void load (ESMReader &esm);
|
||||||
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
||||||
|
|
||||||
|
virtual CreatureLevListState& asCreatureLevListState()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
virtual const CreatureLevListState& asCreatureLevListState() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,15 @@ namespace ESM
|
||||||
|
|
||||||
virtual void load (ESMReader &esm);
|
virtual void load (ESMReader &esm);
|
||||||
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
||||||
|
|
||||||
|
virtual CreatureState& asCreatureState()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
virtual const CreatureState& asCreatureState() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,7 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
|
||||||
if (mGoldPool)
|
if (mGoldPool)
|
||||||
esm.writeHNT ("GOLD", mGoldPool);
|
esm.writeHNT ("GOLD", mGoldPool);
|
||||||
|
|
||||||
|
if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0)
|
||||||
esm.writeHNT ("TIME", mTradeTime);
|
esm.writeHNT ("TIME", mTradeTime);
|
||||||
|
|
||||||
if (mDead)
|
if (mDead)
|
||||||
|
|
|
@ -13,6 +13,15 @@ namespace ESM
|
||||||
|
|
||||||
virtual void load (ESMReader &esm);
|
virtual void load (ESMReader &esm);
|
||||||
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
||||||
|
|
||||||
|
virtual DoorState& asDoorState()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
virtual const DoorState& asDoorState() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,15 @@ namespace ESM
|
||||||
|
|
||||||
virtual void load (ESMReader &esm);
|
virtual void load (ESMReader &esm);
|
||||||
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
virtual void save (ESMWriter &esm, bool inInventory = false) const;
|
||||||
|
|
||||||
|
virtual NpcState& asNpcState()
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
virtual const NpcState& asNpcState() const
|
||||||
|
{
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include "objectstate.hpp"
|
#include "objectstate.hpp"
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <sstream>
|
||||||
|
#include <typeinfo>
|
||||||
|
|
||||||
#include "esmreader.hpp"
|
#include "esmreader.hpp"
|
||||||
#include "esmwriter.hpp"
|
#include "esmwriter.hpp"
|
||||||
|
|
||||||
|
@ -84,4 +88,74 @@ void ESM::ObjectState::blank()
|
||||||
mHasCustomState = true;
|
mHasCustomState = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ESM::NpcState& ESM::ObjectState::asNpcState() const
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to NpcState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::NpcState& ESM::ObjectState::asNpcState()
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to NpcState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const ESM::CreatureState& ESM::ObjectState::asCreatureState() const
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to CreatureState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::CreatureState& ESM::ObjectState::asCreatureState()
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to CreatureState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const ESM::ContainerState& ESM::ObjectState::asContainerState() const
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to ContainerState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::ContainerState& ESM::ObjectState::asContainerState()
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to ContainerState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const ESM::DoorState& ESM::ObjectState::asDoorState() const
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to DoorState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::DoorState& ESM::ObjectState::asDoorState()
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to DoorState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState() const
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to CreatureLevListState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
ESM::CreatureLevListState& ESM::ObjectState::asCreatureLevListState()
|
||||||
|
{
|
||||||
|
std::stringstream error;
|
||||||
|
error << "bad cast " << typeid(this).name() << " to CreatureLevListState";
|
||||||
|
throw std::logic_error(error.str());
|
||||||
|
}
|
||||||
|
|
||||||
ESM::ObjectState::~ObjectState() {}
|
ESM::ObjectState::~ObjectState() {}
|
||||||
|
|
|
@ -12,6 +12,11 @@ namespace ESM
|
||||||
{
|
{
|
||||||
class ESMReader;
|
class ESMReader;
|
||||||
class ESMWriter;
|
class ESMWriter;
|
||||||
|
struct ContainerState;
|
||||||
|
struct CreatureLevListState;
|
||||||
|
struct CreatureState;
|
||||||
|
struct DoorState;
|
||||||
|
struct NpcState;
|
||||||
|
|
||||||
// format 0, saved games only
|
// format 0, saved games only
|
||||||
|
|
||||||
|
@ -48,6 +53,21 @@ namespace ESM
|
||||||
void blank();
|
void blank();
|
||||||
|
|
||||||
virtual ~ObjectState();
|
virtual ~ObjectState();
|
||||||
|
|
||||||
|
virtual const NpcState& asNpcState() const;
|
||||||
|
virtual NpcState& asNpcState();
|
||||||
|
|
||||||
|
virtual const CreatureState& asCreatureState() const;
|
||||||
|
virtual CreatureState& asCreatureState();
|
||||||
|
|
||||||
|
virtual const ContainerState& asContainerState() const;
|
||||||
|
virtual ContainerState& asContainerState();
|
||||||
|
|
||||||
|
virtual const DoorState& asDoorState() const;
|
||||||
|
virtual DoorState& asDoorState();
|
||||||
|
|
||||||
|
virtual const CreatureLevListState& asCreatureLevListState() const;
|
||||||
|
virtual CreatureLevListState& asCreatureLevListState();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,8 @@ namespace
|
||||||
// Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it.
|
// Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it.
|
||||||
void collectDrawableProperties(const Nif::Node* nifNode, std::vector<const Nif::Property*>& out)
|
void collectDrawableProperties(const Nif::Node* nifNode, std::vector<const Nif::Property*>& out)
|
||||||
{
|
{
|
||||||
|
if (nifNode->parent)
|
||||||
|
collectDrawableProperties(nifNode->parent, out);
|
||||||
const Nif::PropertyList& props = nifNode->props;
|
const Nif::PropertyList& props = nifNode->props;
|
||||||
for (size_t i = 0; i <props.length();++i)
|
for (size_t i = 0; i <props.length();++i)
|
||||||
{
|
{
|
||||||
|
@ -81,8 +83,6 @@ namespace
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nifNode->parent)
|
|
||||||
collectDrawableProperties(nifNode->parent, out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale
|
// NodeCallback used to have a node always oriented towards the camera. The node can have translation and scale
|
||||||
|
@ -461,8 +461,6 @@ namespace NifOsg
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Nif::RC_NiSwitchNode:
|
case Nif::RC_NiSwitchNode:
|
||||||
case Nif::RC_NiTriShape:
|
|
||||||
case Nif::RC_NiTriStrips:
|
|
||||||
case Nif::RC_NiAutoNormalParticles:
|
case Nif::RC_NiAutoNormalParticles:
|
||||||
case Nif::RC_NiRotatingParticles:
|
case Nif::RC_NiRotatingParticles:
|
||||||
// Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
|
// Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
|
||||||
|
@ -1719,9 +1717,8 @@ namespace NifOsg
|
||||||
|
|
||||||
int lightmode = 1;
|
int lightmode = 1;
|
||||||
|
|
||||||
for (std::vector<const Nif::Property*>::const_reverse_iterator it = properties.rbegin(); it != properties.rend(); ++it)
|
for (const Nif::Property* property : properties)
|
||||||
{
|
{
|
||||||
const Nif::Property* property = *it;
|
|
||||||
switch (property->recType)
|
switch (property->recType)
|
||||||
{
|
{
|
||||||
case Nif::RC_NiSpecularProperty:
|
case Nif::RC_NiSpecularProperty:
|
||||||
|
|
|
@ -50,10 +50,8 @@ namespace SceneUtil
|
||||||
mDepthMask = state;
|
mDepthMask = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugDraw::texture(bool state)
|
void DebugDraw::texture(bool)
|
||||||
{
|
{
|
||||||
if (state)
|
|
||||||
throw std::logic_error("DebugDraw does not support textures (at " __FILE__ ":" OPENMW_LINE_STRING ")");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugDraw::begin(osg::PrimitiveSet::Mode mode, float size)
|
void DebugDraw::begin(osg::PrimitiveSet::Mode mode, float size)
|
||||||
|
@ -85,9 +83,10 @@ namespace SceneUtil
|
||||||
vertex(pos[0], pos[1], pos[2], color, uv[0], uv[1]);
|
vertex(pos[0], pos[1], pos[2], color, uv[0], uv[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugDraw::vertex(const float, const float, const float, unsigned, const float, const float)
|
void DebugDraw::vertex(const float x, const float y, const float z, unsigned color, const float, const float)
|
||||||
{
|
{
|
||||||
throw std::logic_error("Not implemented (at " __FILE__ ":" OPENMW_LINE_STRING ")");
|
addVertex(osg::Vec3f(x, y, z));
|
||||||
|
addColor(SceneUtil::colourFromRGBA(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugDraw::end()
|
void DebugDraw::end()
|
||||||
|
|
48
components/sceneutil/recastmesh.cpp
Normal file
48
components/sceneutil/recastmesh.cpp
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#include "navmesh.hpp"
|
||||||
|
#include "detourdebugdraw.hpp"
|
||||||
|
|
||||||
|
#include <components/detournavigator/settings.hpp>
|
||||||
|
#include <components/detournavigator/recastmesh.hpp>
|
||||||
|
|
||||||
|
#include <RecastDebugDraw.h>
|
||||||
|
|
||||||
|
#include <osg/Group>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
std::vector<float> calculateNormals(const std::vector<float>& vertices, const std::vector<int>& indices)
|
||||||
|
{
|
||||||
|
std::vector<float> result(indices.size());
|
||||||
|
for (std::size_t i = 0, n = indices.size(); i < n; i += 3)
|
||||||
|
{
|
||||||
|
const float* v0_ptr = &vertices[indices[i] * 3];
|
||||||
|
const float* v1_ptr = &vertices[indices[i + 1] * 3];
|
||||||
|
const float* v2_ptr = &vertices[indices[i + 2] * 3];
|
||||||
|
const osg::Vec3f v0(v0_ptr[0], v0_ptr[1], v0_ptr[2]);
|
||||||
|
const osg::Vec3f v1(v1_ptr[0], v1_ptr[1], v1_ptr[2]);
|
||||||
|
const osg::Vec3f v2(v2_ptr[0], v2_ptr[1], v2_ptr[2]);
|
||||||
|
const osg::Vec3f e0 = v1 - v0;
|
||||||
|
const osg::Vec3f e1 = v2 - v0;
|
||||||
|
osg::Vec3f normal = e0 ^ e1;
|
||||||
|
normal.normalize();
|
||||||
|
for (std::size_t j = 0; j < 3; ++j)
|
||||||
|
result[i + j] = normal[j];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh,
|
||||||
|
const DetourNavigator::Settings& settings)
|
||||||
|
{
|
||||||
|
const osg::ref_ptr<osg::Group> group(new osg::Group);
|
||||||
|
DebugDraw debugDraw(*group, osg::Vec3f(0, 0, 0), 1.0f / settings.mRecastScaleFactor);
|
||||||
|
const auto normals = calculateNormals(recastMesh.getVertices(), recastMesh.getIndices());
|
||||||
|
const auto texScale = 1.0f / (settings.mCellSize * 10.0f);
|
||||||
|
duDebugDrawTriMesh(&debugDraw, recastMesh.getVertices().data(), recastMesh.getVerticesCount(),
|
||||||
|
recastMesh.getIndices().data(), normals.data(), recastMesh.getTrianglesCount(), nullptr, texScale);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
23
components/sceneutil/recastmesh.hpp
Normal file
23
components/sceneutil/recastmesh.hpp
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H
|
||||||
|
#define OPENMW_COMPONENTS_SCENEUTIL_RECASTMESH_H
|
||||||
|
|
||||||
|
#include <osg/ref_ptr>
|
||||||
|
|
||||||
|
namespace osg
|
||||||
|
{
|
||||||
|
class Group;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace DetourNavigator
|
||||||
|
{
|
||||||
|
class RecastMesh;
|
||||||
|
struct Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace SceneUtil
|
||||||
|
{
|
||||||
|
osg::ref_ptr<osg::Group> createRecastMeshGroup(const DetourNavigator::RecastMesh& recastMesh,
|
||||||
|
const DetourNavigator::Settings& settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -77,6 +77,9 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
||||||
// Were there any lines at all in the file?
|
// Were there any lines at all in the file?
|
||||||
bool existing = false;
|
bool existing = false;
|
||||||
|
|
||||||
|
// Is an entirely blank line queued to be added?
|
||||||
|
bool emptyLineQueued = false;
|
||||||
|
|
||||||
// The category/section we're currently in.
|
// The category/section we're currently in.
|
||||||
std::string currentCategory;
|
std::string currentCategory;
|
||||||
|
|
||||||
|
@ -100,12 +103,22 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
||||||
// The current character position in the line.
|
// The current character position in the line.
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
// Don't add additional newlines at the end of the file.
|
// An empty line was queued.
|
||||||
|
if (emptyLineQueued)
|
||||||
|
{
|
||||||
|
emptyLineQueued = false;
|
||||||
|
// We're still going through the current category, so we should copy it.
|
||||||
|
if (currentCategory.empty() || istream.eof() || line[i] != '[')
|
||||||
|
ostream << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't add additional newlines at the end of the file otherwise.
|
||||||
if (istream.eof()) continue;
|
if (istream.eof()) continue;
|
||||||
|
|
||||||
// Copy entirely blank lines.
|
// Queue entirely blank lines to add them if desired.
|
||||||
if (!skipWhiteSpace(i, line)) {
|
if (!skipWhiteSpace(i, line))
|
||||||
ostream << line << std::endl;
|
{
|
||||||
|
emptyLineQueued = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,9 +141,13 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!currentCategory.empty())
|
||||||
|
{
|
||||||
// Ensure that all options in the current category have been written.
|
// Ensure that all options in the current category have been written.
|
||||||
for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) {
|
for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit)
|
||||||
if (mit->second == false && mit->first.first == currentCategory) {
|
{
|
||||||
|
if (mit->second == false && mit->first.first == currentCategory)
|
||||||
|
{
|
||||||
Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] "
|
Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] "
|
||||||
<< mit->first.second << " = " << settings.at(mit->first);
|
<< mit->first.second << " = " << settings.at(mit->first);
|
||||||
ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl;
|
ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl;
|
||||||
|
@ -138,6 +155,9 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Add an empty line after the last option in a category.
|
||||||
|
ostream << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
// Update the current category.
|
// Update the current category.
|
||||||
currentCategory = line.substr(i+1, end - (i+1));
|
currentCategory = line.substr(i+1, end - (i+1));
|
||||||
|
|
|
@ -166,6 +166,20 @@ Make visible all NPC's and creaure's plans where they are going.
|
||||||
Works even if Navigator is disabled.
|
Works even if Navigator is disabled.
|
||||||
Potentially decreases performance.
|
Potentially decreases performance.
|
||||||
|
|
||||||
|
enable recast mesh render
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
:Type: boolean
|
||||||
|
:Range: True/False
|
||||||
|
:Default: False
|
||||||
|
|
||||||
|
Render recast mesh that is built as set of culled tiles from physical mesh.
|
||||||
|
Should show similar mesh to physical one.
|
||||||
|
Little difference can be a result of floating point error.
|
||||||
|
Absent pieces usually mean a bug in recast mesh tiles building.
|
||||||
|
Allows to do in-game debug.
|
||||||
|
Potentially decreases performance.
|
||||||
|
|
||||||
Expert settings
|
Expert settings
|
||||||
***************
|
***************
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue