mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-03-01 12:39:40 +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:rakhimov/boost'
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-xenial-7
|
||||
packages: [
|
||||
# Dev
|
||||
cmake, clang-7, clang-tools-7, gcc-8, g++-8, ccache,
|
||||
cmake, clang-tools, gcc-8, g++-8, ccache,
|
||||
# Boost
|
||||
libboost-filesystem-dev, libboost-iostreams-dev, libboost-program-options-dev, libboost-system-dev,
|
||||
# FFmpeg
|
||||
|
@ -38,7 +37,7 @@ addons:
|
|||
description: "<Your project description here>"
|
||||
branch_pattern: coverity_scan
|
||||
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"
|
||||
matrix:
|
||||
include:
|
||||
|
@ -48,17 +47,17 @@ matrix:
|
|||
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
||||
compiler: clang
|
||||
sudo: required
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
- os: linux
|
||||
env:
|
||||
- MATRIX_CC="CC=gcc-8 && CXX=g++-8"
|
||||
sudo: required
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
- os: linux
|
||||
env:
|
||||
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
||||
sudo: required
|
||||
dist: xenial
|
||||
dist: bionic
|
||||
allow_failures:
|
||||
- env:
|
||||
- MATRIX_CC="CC=clang-7 && CXX=clang++-7"
|
||||
|
|
|
@ -74,6 +74,7 @@ Programmers
|
|||
Fil Krynicki (filkry)
|
||||
Finbar Crago (finbar-crago)
|
||||
Florian Weber (Florianjw)
|
||||
Frédéric Chardon (fr3dz10)
|
||||
Gaëtan Dezeiraud (Brouilles)
|
||||
Gašper Sedej
|
||||
Gijsbert ter Horst (Ghostbird)
|
||||
|
@ -87,6 +88,7 @@ Programmers
|
|||
Jacob Essex (Yacoby)
|
||||
Jake Westrip (16bitint)
|
||||
James Carty (MrTopCat)
|
||||
James Moore (moore.work)
|
||||
James Stephens (james-h-stephens)
|
||||
Jan-Peter Nilsson (peppe)
|
||||
Jan Borsodi (am0s)
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
Bug #4594: Actors without AI packages don't use Hello dialogue
|
||||
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 #4601: Filtering referenceables by gender is broken
|
||||
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 #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 #5300: NPCs don't switch from torch to shield when starting combat
|
||||
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 #2229: Improve pathfinding AI
|
||||
Feature #3025: Analogue gamepad movement controls
|
||||
|
@ -225,6 +230,7 @@
|
|||
Feature #4544: Actors movement deceleration
|
||||
Feature #4673: Weapon sheathing
|
||||
Feature #4675: Support for NiRollController
|
||||
Feature #4708: Radial fog support
|
||||
Feature #4730: Native animated containers support
|
||||
Feature #4784: Launcher: Duplicate Content Lists
|
||||
Feature #4812: Support NiSwitchNode
|
||||
|
@ -251,6 +257,7 @@
|
|||
Feature #5091: Human-readable light source duration
|
||||
Feature #5094: Unix like console hotkeys
|
||||
Feature #5098: Allow user controller bindings
|
||||
Feature #5114: Refresh launcher mod list
|
||||
Feature #5121: Handle NiTriStrips and NiTriStripsData
|
||||
Feature #5122: Use magic glow for enchanted arrows
|
||||
Feature #5131: Custom skeleton bones
|
||||
|
@ -262,7 +269,9 @@
|
|||
Feature #5193: Weapon sheathing
|
||||
Feature #5219: Impelement TestCells console command
|
||||
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 #5314: Ingredient filter in the alchemy window
|
||||
Task #4686: Upgrade media decoder to a more current FFmpeg API
|
||||
Task #4695: Optimize Distant Terrain memory consumption
|
||||
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)
|
||||
- Land heightmap/shape editing and vertex selection (#5170)
|
||||
- Deleting instances with a keypress (#5172)
|
||||
- Dropping objects with keyboard shortcuts (#5274)
|
||||
|
||||
Bug Fixes:
|
||||
- The Mouse Wheel can now be used for key bindings (#2679)
|
||||
|
|
|
@ -421,16 +421,16 @@ if [ -z $SKIP_DOWNLOAD ]; then
|
|||
fi
|
||||
|
||||
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" \
|
||||
"https://www.lysator.liu.se/~ace/OpenMW/deps/qt-5-install.qs" \
|
||||
"qt-5-install.qs"
|
||||
fi
|
||||
|
||||
# SDL2
|
||||
download "SDL 2.0.7" \
|
||||
"https://www.libsdl.org/release/SDL2-devel-2.0.7-VC.zip" \
|
||||
"SDL2-2.0.7.zip"
|
||||
download "SDL 2.0.12" \
|
||||
"https://www.libsdl.org/release/SDL2-devel-2.0.12-VC.zip" \
|
||||
"SDL2-2.0.12.zip"
|
||||
|
||||
# Google test and mock
|
||||
if [ ! -z $TEST_FRAMEWORK ]; then
|
||||
|
@ -697,16 +697,16 @@ fi
|
|||
cd $DEPS
|
||||
echo
|
||||
# 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. "
|
||||
elif [ -z $SKIP_EXTRACT ]; then
|
||||
rm -rf SDL2-2.0.7
|
||||
eval 7z x -y SDL2-2.0.7.zip $STRIP
|
||||
rm -rf SDL2-2.0.12
|
||||
eval 7z x -y SDL2-2.0.12.zip $STRIP
|
||||
fi
|
||||
export SDL2DIR="$(real_pwd)/SDL2-2.0.7"
|
||||
add_runtime_dlls "$(pwd)/SDL2-2.0.7/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||
export SDL2DIR="$(real_pwd)/SDL2-2.0.12"
|
||||
add_runtime_dlls "$(pwd)/SDL2-2.0.12/lib/x${ARCHSUFFIX}/SDL2.dll"
|
||||
echo Done.
|
||||
}
|
||||
cd $DEPS
|
||||
|
|
|
@ -62,10 +62,13 @@ void Launcher::DataFilesPage::buildView()
|
|||
{
|
||||
ui.verticalLayout->insertWidget (0, mSelector->uiWidget());
|
||||
|
||||
QToolButton * refreshButton = mSelector->refreshButton();
|
||||
|
||||
//tool buttons
|
||||
ui.newProfileButton->setToolTip ("Create a new Content List");
|
||||
ui.cloneProfileButton->setToolTip ("Clone the current Content List");
|
||||
ui.deleteProfileButton->setToolTip ("Delete an existing Content List");
|
||||
refreshButton->setToolTip("Refresh Data Files");
|
||||
|
||||
//combo box
|
||||
ui.profilesComboBox->addItem(mDefaultContentListName);
|
||||
|
@ -76,6 +79,7 @@ void Launcher::DataFilesPage::buildView()
|
|||
ui.newProfileButton->setDefaultAction (ui.newProfileAction);
|
||||
ui.cloneProfileButton->setDefaultAction (ui.cloneProfileAction);
|
||||
ui.deleteProfileButton->setDefaultAction (ui.deleteProfileAction);
|
||||
refreshButton->setDefaultAction(ui.refreshDataFilesAction);
|
||||
|
||||
//establish connections
|
||||
connect (ui.profilesComboBox, SIGNAL (currentIndexChanged(int)),
|
||||
|
@ -86,6 +90,8 @@ void Launcher::DataFilesPage::buildView()
|
|||
|
||||
connect (ui.profilesComboBox, SIGNAL (signalProfileChanged(QString, QString)),
|
||||
this, SLOT (slotProfileChangedByUser(QString, QString)));
|
||||
|
||||
connect(ui.refreshDataFilesAction, SIGNAL(triggered()),this, SLOT(slotRefreshButtonClicked()));
|
||||
}
|
||||
|
||||
bool Launcher::DataFilesPage::loadSettings()
|
||||
|
@ -114,6 +120,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName)
|
|||
if (!mDataLocal.isEmpty())
|
||||
paths.insert(0, mDataLocal);
|
||||
|
||||
mSelector->clearFiles();
|
||||
|
||||
for (const QString &path : paths)
|
||||
mSelector->addFiles(path);
|
||||
|
||||
|
@ -166,9 +174,18 @@ QStringList Launcher::DataFilesPage::selectedFilePaths()
|
|||
ContentSelectorModel::ContentFileList items = mSelector->selectedFiles();
|
||||
QStringList filePaths;
|
||||
for (const ContentSelectorModel::EsmFile *item : items)
|
||||
{
|
||||
QFile file(item->filePath());
|
||||
|
||||
if(file.exists())
|
||||
{
|
||||
filePaths.append(item->filePath());
|
||||
}
|
||||
else
|
||||
{
|
||||
slotRefreshButtonClicked();
|
||||
}
|
||||
}
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
|
@ -221,6 +238,18 @@ void Launcher::DataFilesPage::slotProfileDeleted (const QString &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)
|
||||
{
|
||||
setProfile(previous, current, true);
|
||||
|
|
|
@ -61,6 +61,7 @@ namespace Launcher
|
|||
void slotProfileRenamed(const QString &previous, const QString ¤t);
|
||||
void slotProfileDeleted(const QString &item);
|
||||
void slotAddonDataChanged ();
|
||||
void slotRefreshButtonClicked ();
|
||||
|
||||
void updateNewProfileOkButton(const QString &text);
|
||||
void updateCloneProfileOkButton(const QString &text);
|
||||
|
@ -100,6 +101,7 @@ namespace Launcher
|
|||
void checkForDefaultProfile();
|
||||
void populateFileViews(const QString& contentModelName);
|
||||
void reloadCells(QStringList selectedFiles);
|
||||
void refreshDataFilesView ();
|
||||
|
||||
class PathIterator
|
||||
{
|
||||
|
|
|
@ -356,6 +356,10 @@ void CSMPrefs::State::declare()
|
|||
QKeySequence(Qt::ControlModifier | (int)Qt::MiddleButton));
|
||||
declareModifier ("scene-speed-modifier", "Speed Modifier", Qt::Key_Shift);
|
||||
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-eastcell", "Load East Cell", QKeySequence(Qt::KeypadModifier | Qt::Key_6));
|
||||
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>
|
||||
{
|
||||
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
|
||||
|
|
|
@ -288,7 +288,6 @@ namespace CSMWorld
|
|||
{ ColumnId_UChar, "Value [0..255]" },
|
||||
{ ColumnId_NpcMisc, "NPC Misc" },
|
||||
{ ColumnId_Level, "Level" },
|
||||
{ ColumnId_GenderNpc, "Gender"},
|
||||
{ ColumnId_Mana, "Mana" },
|
||||
{ ColumnId_Fatigue, "Fatigue" },
|
||||
{ ColumnId_NpcDisposition, "NPC Disposition" },
|
||||
|
|
|
@ -273,7 +273,7 @@ namespace CSMWorld
|
|||
ColumnId_UChar = 250,
|
||||
ColumnId_NpcMisc = 251,
|
||||
ColumnId_Level = 252,
|
||||
ColumnId_GenderNpc = 254,
|
||||
// unused
|
||||
ColumnId_Mana = 255,
|
||||
ColumnId_Fatigue = 256,
|
||||
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["clamp"] = "1"; // Clamp lighting
|
||||
defines["preLightEnv"] = "0"; // Apply environment maps after lighting like Morrowind
|
||||
defines["radialFog"] = "0";
|
||||
for (const auto& define : shadowDefines)
|
||||
defines[define.first] = define.second;
|
||||
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(defines);
|
||||
|
|
|
@ -486,7 +486,7 @@ CSMWorld::RefIdCollection::RefIdCollection()
|
|||
mColumns.push_back (RefIdColumn (Columns::ColumnId_Head, ColumnBase::Display_BodyPart));
|
||||
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.mFlags.insert (std::make_pair (essential, ESM::NPC::Essential));
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
|
||||
#include <QDragEnterEvent>
|
||||
#include <QPoint>
|
||||
#include <QString>
|
||||
|
||||
#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/idtree.hpp"
|
||||
#include "../../model/world/commands.hpp"
|
||||
|
@ -90,16 +96,26 @@ osg::Vec3f CSVRender::InstanceMode::getScreenCoords(const osg::Vec3f& pos)
|
|||
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",
|
||||
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&)),
|
||||
worldspaceWidget, SIGNAL(requestFocus(const std::string&)));
|
||||
|
||||
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget);
|
||||
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)
|
||||
|
@ -681,3 +697,187 @@ void CSVRender::InstanceMode::deleteSelectedInstances(bool active)
|
|||
|
||||
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
|
||||
#define CSV_RENDER_INSTANCEMODE_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <osg/ref_ptr>
|
||||
#include <osg/Group>
|
||||
#include <osg/Quat>
|
||||
#include <osg/Vec3f>
|
||||
|
||||
|
@ -16,6 +19,7 @@ namespace CSVRender
|
|||
{
|
||||
class TagBase;
|
||||
class InstanceSelectionMode;
|
||||
class Object;
|
||||
|
||||
class InstanceMode : public EditMode
|
||||
{
|
||||
|
@ -29,6 +33,14 @@ namespace CSVRender
|
|||
DragMode_Scale
|
||||
};
|
||||
|
||||
enum DropMode
|
||||
{
|
||||
Collision,
|
||||
Terrain,
|
||||
CollisionSep,
|
||||
TerrainSep
|
||||
};
|
||||
|
||||
CSVWidget::SceneToolMode *mSubMode;
|
||||
std::string mSubModeId;
|
||||
InstanceSelectionMode *mSelectionMode;
|
||||
|
@ -36,6 +48,7 @@ namespace CSVRender
|
|||
int mDragAxis;
|
||||
bool mLocked;
|
||||
float mUnitScaleDist;
|
||||
osg::ref_ptr<osg::Group> mParentNode;
|
||||
|
||||
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 getScreenCoords(const osg::Vec3f& pos);
|
||||
void dropInstance(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||
float getDropHeight(DropMode dropMode, CSVRender::Object* object, float objectHeight);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -93,6 +108,24 @@ namespace CSVRender
|
|||
|
||||
void subModeChanged (const std::string& id);
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
const QModelIndex& bottomRight)
|
||||
{
|
||||
|
|
|
@ -146,6 +146,12 @@ namespace CSVRender
|
|||
|
||||
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
|
||||
/// this object?
|
||||
bool referenceableDataChanged (const QModelIndex& topLeft,
|
||||
|
|
|
@ -370,7 +370,7 @@ void CSVRender::WorldspaceWidget::addVisibilitySelectorButtons (
|
|||
void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool)
|
||||
{
|
||||
/// \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");
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ add_openmw_dir (mwrender
|
|||
actors objects renderingmanager animation rotatecontroller sky npcanimation
|
||||
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation
|
||||
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
|
||||
renderbin actoranimation landmanager navmesh actorspaths
|
||||
renderbin actoranimation landmanager navmesh actorspaths recastmesh
|
||||
)
|
||||
|
||||
add_openmw_dir (mwinput
|
||||
|
@ -70,7 +70,9 @@ add_openmw_dir (mwworld
|
|||
)
|
||||
|
||||
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
|
||||
|
|
|
@ -381,7 +381,6 @@ namespace MWClass
|
|||
{
|
||||
if (!state.mHasCustomState)
|
||||
return;
|
||||
const ESM::ContainerState& state2 = dynamic_cast<const ESM::ContainerState&> (state);
|
||||
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
|
@ -390,21 +389,21 @@ namespace MWClass
|
|||
ptr.getRefData().setCustomData (data.release());
|
||||
}
|
||||
|
||||
dynamic_cast<ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
|
||||
readState (state2.mInventory);
|
||||
ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
|
||||
const ESM::ContainerState& containerState = state.asContainerState();
|
||||
customData.mContainerStore.readState (containerState.mInventory);
|
||||
}
|
||||
|
||||
void Container::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||
{
|
||||
ESM::ContainerState& state2 = dynamic_cast<ESM::ContainerState&> (state);
|
||||
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
state.mHasCustomState = false;
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic_cast<const ContainerCustomData&> (*ptr.getRefData().getCustomData()).mContainerStore.
|
||||
writeState (state2.mInventory);
|
||||
const ContainerCustomData& customData = ptr.getRefData().getCustomData()->asContainerCustomData();
|
||||
ESM::ContainerState& containerState = state.asContainerState();
|
||||
customData.mContainerStore.writeState (containerState.mInventory);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -964,8 +964,6 @@ namespace MWClass
|
|||
if (!state.mHasCustomState)
|
||||
return;
|
||||
|
||||
const ESM::CreatureState& state2 = dynamic_cast<const ESM::CreatureState&> (state);
|
||||
|
||||
if (state.mVersion > 0)
|
||||
{
|
||||
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.
|
||||
|
||||
CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||
|
||||
customData.mContainerStore->readState (state2.mInventory);
|
||||
customData.mCreatureStats.readState (state2.mCreatureStats);
|
||||
const ESM::CreatureState& creatureState = state.asCreatureState();
|
||||
customData.mContainerStore->readState (creatureState.mInventory);
|
||||
customData.mCreatureStats.readState (creatureState.mCreatureStats);
|
||||
}
|
||||
|
||||
void Creature::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
||||
const
|
||||
{
|
||||
ESM::CreatureState& state2 = dynamic_cast<ESM::CreatureState&> (state);
|
||||
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
state.mHasCustomState = false;
|
||||
|
@ -1002,9 +998,9 @@ namespace MWClass
|
|||
}
|
||||
|
||||
const CreatureCustomData& customData = ptr.getRefData().getCustomData()->asCreatureCustomData();
|
||||
|
||||
customData.mContainerStore->writeState (state2.mInventory);
|
||||
customData.mCreatureStats.writeState (state2.mCreatureStats);
|
||||
ESM::CreatureState& creatureState = state.asCreatureState();
|
||||
customData.mContainerStore->writeState (creatureState.mInventory);
|
||||
customData.mCreatureStats.writeState (creatureState.mCreatureStats);
|
||||
}
|
||||
|
||||
int Creature::getBaseGold(const MWWorld::ConstPtr& ptr) const
|
||||
|
|
|
@ -160,19 +160,16 @@ namespace MWClass
|
|||
if (!state.mHasCustomState)
|
||||
return;
|
||||
|
||||
const ESM::CreatureLevListState& state2 = dynamic_cast<const ESM::CreatureLevListState&> (state);
|
||||
|
||||
ensureCustomData(ptr);
|
||||
CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||
customData.mSpawnActorId = state2.mSpawnActorId;
|
||||
customData.mSpawn = state2.mSpawn;
|
||||
const ESM::CreatureLevListState& levListState = state.asCreatureLevListState();
|
||||
customData.mSpawnActorId = levListState.mSpawnActorId;
|
||||
customData.mSpawn = levListState.mSpawn;
|
||||
}
|
||||
|
||||
void CreatureLevList::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
||||
const
|
||||
{
|
||||
ESM::CreatureLevListState& state2 = dynamic_cast<ESM::CreatureLevListState&> (state);
|
||||
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
state.mHasCustomState = false;
|
||||
|
@ -180,7 +177,8 @@ namespace MWClass
|
|||
}
|
||||
|
||||
const CreatureLevListCustomData& customData = ptr.getRefData().getCustomData()->asCreatureLevListCustomData();
|
||||
state2.mSpawnActorId = customData.mSpawnActorId;
|
||||
state2.mSpawn = customData.mSpawn;
|
||||
ESM::CreatureLevListState& levListState = state.asCreatureLevListState();
|
||||
levListState.mSpawnActorId = customData.mSpawnActorId;
|
||||
levListState.mSpawn = customData.mSpawn;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -461,11 +461,11 @@ namespace MWClass
|
|||
{
|
||||
if (!state.mHasCustomState)
|
||||
return;
|
||||
|
||||
ensureCustomData(ptr);
|
||||
DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
||||
|
||||
const ESM::DoorState& state2 = dynamic_cast<const ESM::DoorState&>(state);
|
||||
customData.mDoorState = static_cast<MWWorld::DoorState>(state2.mDoorState);
|
||||
const ESM::DoorState& doorState = state.asDoorState();
|
||||
customData.mDoorState = MWWorld::DoorState(doorState.mDoorState);
|
||||
}
|
||||
|
||||
void Door::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state) const
|
||||
|
@ -475,10 +475,10 @@ namespace MWClass
|
|||
state.mHasCustomState = false;
|
||||
return;
|
||||
}
|
||||
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
||||
|
||||
ESM::DoorState& state2 = dynamic_cast<ESM::DoorState&>(state);
|
||||
state2.mDoorState = static_cast<int>(customData.mDoorState);
|
||||
const DoorCustomData& customData = ptr.getRefData().getCustomData()->asDoorCustomData();
|
||||
ESM::DoorState& doorState = state.asDoorState();
|
||||
doorState.mDoorState = int(customData.mDoorState);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1522,8 +1522,6 @@ namespace MWClass
|
|||
if (!state.mHasCustomState)
|
||||
return;
|
||||
|
||||
const ESM::NpcState& state2 = dynamic_cast<const ESM::NpcState&> (state);
|
||||
|
||||
if (state.mVersion > 0)
|
||||
{
|
||||
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.
|
||||
|
||||
NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||
|
||||
customData.mInventoryStore.readState (state2.mInventory);
|
||||
customData.mNpcStats.readState (state2.mNpcStats);
|
||||
static_cast<MWMechanics::CreatureStats&> (customData.mNpcStats).readState (state2.mCreatureStats);
|
||||
const ESM::NpcState& npcState = state.asNpcState();
|
||||
customData.mInventoryStore.readState (npcState.mInventory);
|
||||
customData.mNpcStats.readState (npcState.mNpcStats);
|
||||
customData.mNpcStats.readState (npcState.mCreatureStats);
|
||||
}
|
||||
|
||||
void Npc::writeAdditionalState (const MWWorld::ConstPtr& ptr, ESM::ObjectState& state)
|
||||
const
|
||||
{
|
||||
ESM::NpcState& state2 = dynamic_cast<ESM::NpcState&> (state);
|
||||
|
||||
if (!ptr.getRefData().getCustomData())
|
||||
{
|
||||
state.mHasCustomState = false;
|
||||
|
@ -1555,10 +1551,10 @@ namespace MWClass
|
|||
}
|
||||
|
||||
const NpcCustomData& customData = ptr.getRefData().getCustomData()->asNpcCustomData();
|
||||
|
||||
customData.mInventoryStore.writeState (state2.mInventory);
|
||||
customData.mNpcStats.writeState (state2.mNpcStats);
|
||||
static_cast<const MWMechanics::CreatureStats&> (customData.mNpcStats).writeState (state2.mCreatureStats);
|
||||
ESM::NpcState& npcState = state.asNpcState();
|
||||
customData.mInventoryStore.writeState (npcState.mInventory);
|
||||
customData.mNpcStats.writeState (npcState.mNpcStats);
|
||||
customData.mNpcStats.writeState (npcState.mCreatureStats);
|
||||
}
|
||||
|
||||
int Npc::getBaseGold(const MWWorld::ConstPtr& ptr) const
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <MyGUI_Gui.h>
|
||||
#include <MyGUI_Button.h>
|
||||
#include <MyGUI_EditBox.h>
|
||||
#include <MyGUI_ComboBox.h>
|
||||
#include <MyGUI_ControllerManager.h>
|
||||
#include <MyGUI_ControllerRepeatClick.h>
|
||||
|
||||
|
@ -29,6 +30,7 @@
|
|||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include <MyGUI_Macros.h>
|
||||
#include <components/esm/records.hpp>
|
||||
|
||||
#include "inventoryitemmodel.hpp"
|
||||
|
@ -41,6 +43,7 @@ namespace MWGui
|
|||
{
|
||||
AlchemyWindow::AlchemyWindow()
|
||||
: WindowBase("openmw_alchemy_window.layout")
|
||||
, mModel(nullptr)
|
||||
, mSortModel(nullptr)
|
||||
, mAlchemy(new MWMechanics::Alchemy())
|
||||
, mApparatus (4)
|
||||
|
@ -62,6 +65,8 @@ namespace MWGui
|
|||
getWidget(mDecreaseButton, "DecreaseButton");
|
||||
getWidget(mNameEdit, "NameEdit");
|
||||
getWidget(mItemView, "ItemView");
|
||||
getWidget(mFilterValue, "FilterValue");
|
||||
getWidget(mFilterType, "FilterType");
|
||||
|
||||
mBrewCountEdit->eventValueChanged += MyGUI::newDelegate(this, &AlchemyWindow::onCountValueChanged);
|
||||
mBrewCountEdit->eventEditSelectAccept += MyGUI::newDelegate(this, &AlchemyWindow::onAccept);
|
||||
|
@ -84,6 +89,9 @@ namespace MWGui
|
|||
mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &AlchemyWindow::onCancelButtonClicked);
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -186,16 +194,110 @@ namespace MWGui
|
|||
removeIngredient(mIngredients[i]);
|
||||
}
|
||||
|
||||
updateFilters();
|
||||
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()
|
||||
{
|
||||
mAlchemy->clear();
|
||||
mAlchemy->setAlchemist (MWMechanics::getPlayer());
|
||||
|
||||
InventoryItemModel* model = new InventoryItemModel(MWMechanics::getPlayer());
|
||||
mSortModel = new SortFilterItemModel(model);
|
||||
mModel = new InventoryItemModel(MWMechanics::getPlayer());
|
||||
mSortModel = new SortFilterItemModel(mModel);
|
||||
mSortModel->setFilter(SortFilterItemModel::Filter_OnlyIngredients);
|
||||
mItemView->setModel (mSortModel);
|
||||
mItemView->resetScrollBars();
|
||||
|
@ -217,6 +319,7 @@ namespace MWGui
|
|||
}
|
||||
|
||||
update();
|
||||
initFilter();
|
||||
|
||||
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mNameEdit);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
#include <vector>
|
||||
|
||||
#include <MyGUI_ControllerItem.h>
|
||||
#include <MyGUI_ComboBox.h>
|
||||
|
||||
#include <components/widgets/box.hpp>
|
||||
#include <components/widgets/numericeditbox.hpp>
|
||||
|
||||
#include "windowbase.hpp"
|
||||
|
@ -19,6 +21,7 @@ namespace MWGui
|
|||
{
|
||||
class ItemView;
|
||||
class ItemWidget;
|
||||
class InventoryItemModel;
|
||||
class SortFilterItemModel;
|
||||
|
||||
class AlchemyWindow : public WindowBase
|
||||
|
@ -36,8 +39,11 @@ namespace MWGui
|
|||
static const float sCountChangeInterval; // in seconds
|
||||
|
||||
std::string mSuggestedPotionName;
|
||||
enum class FilterType { ByName, ByEffect };
|
||||
FilterType mCurrentFilter;
|
||||
|
||||
ItemView* mItemView;
|
||||
InventoryItemModel* mModel;
|
||||
SortFilterItemModel* mSortModel;
|
||||
|
||||
MyGUI::Button* mCreateButton;
|
||||
|
@ -47,6 +53,8 @@ namespace MWGui
|
|||
|
||||
MyGUI::Button* mIncreaseButton;
|
||||
MyGUI::Button* mDecreaseButton;
|
||||
Gui::AutoSizedButton* mFilterType;
|
||||
MyGUI::ComboBox* mFilterValue;
|
||||
MyGUI::EditBox* mNameEdit;
|
||||
Gui::NumericEditBox* mBrewCountEdit;
|
||||
|
||||
|
@ -60,6 +68,13 @@ namespace MWGui
|
|||
void onCountValueChanged(int value);
|
||||
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 onIncreaseButtonTriggered();
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
#include "../mwworld/nullaction.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
|
||||
#include "../mwmechanics/alchemy.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool compareType(const std::string& type1, const std::string& type2)
|
||||
|
@ -151,6 +153,8 @@ namespace MWGui
|
|||
: mCategory(Category_All)
|
||||
, mFilter(0)
|
||||
, mSortByType(true)
|
||||
, mNameFilter("")
|
||||
, mEffectFilter("")
|
||||
{
|
||||
mSourceModel = sourceModel;
|
||||
}
|
||||
|
@ -199,8 +203,39 @@ namespace MWGui
|
|||
if (!(category & mCategory))
|
||||
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;
|
||||
|
||||
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))
|
||||
return false;
|
||||
if ((mFilter & Filter_OnlyChargedSoulstones) && (base.getTypeName() != typeid(ESM::Miscellaneous).name()
|
||||
|
@ -286,6 +321,11 @@ namespace MWGui
|
|||
mNameFilter = Misc::StringUtils::lowerCase(filter);
|
||||
}
|
||||
|
||||
void SortFilterItemModel::setEffectFilter (const std::string& filter)
|
||||
{
|
||||
mEffectFilter = Misc::StringUtils::lowerCase(filter);
|
||||
}
|
||||
|
||||
void SortFilterItemModel::update()
|
||||
{
|
||||
mSourceModel->update();
|
||||
|
|
|
@ -26,6 +26,7 @@ namespace MWGui
|
|||
void setCategory (int category);
|
||||
void setFilter (int filter);
|
||||
void setNameFilter (const std::string& filter);
|
||||
void setEffectFilter (const std::string& filter);
|
||||
|
||||
/// Use ItemStack::Type for sorting?
|
||||
void setSortByType(bool sort) { mSortByType = sort; }
|
||||
|
@ -60,6 +61,7 @@ namespace MWGui
|
|||
bool mSortByType;
|
||||
|
||||
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)
|
||||
{
|
||||
if (key == MyGUI::KeyCode::ArrowDown)
|
||||
if (key == MyGUI::KeyCode::ArrowUp)
|
||||
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));
|
||||
else
|
||||
return;
|
||||
|
|
|
@ -2044,6 +2044,8 @@ namespace MWMechanics
|
|||
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);
|
||||
|
||||
if (cls.isEssential(iter->first))
|
||||
|
@ -2072,7 +2074,10 @@ namespace MWMechanics
|
|||
// Make sure spell effects are removed
|
||||
purgeSpellEffects(stats.getActorId());
|
||||
|
||||
// Reset dynamic stats, attributes and skills
|
||||
calculateCreatureStatModifiers(iter->first, 0);
|
||||
if (iter->first.getClass().isNpc())
|
||||
calculateNpcStatModifiers(iter->first, 0);
|
||||
|
||||
if( iter->first == getPlayer())
|
||||
{
|
||||
|
|
|
@ -623,3 +623,37 @@ std::string MWMechanics::Alchemy::suggestPotionName()
|
|||
return MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>().find(
|
||||
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
|
||||
/// adjust the skills of the alchemist accordingly.
|
||||
/// \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;
|
||||
}
|
||||
|
||||
void MWMechanics::NpcStats::writeState (ESM::CreatureStats& state) const
|
||||
{
|
||||
CreatureStats::writeState(state);
|
||||
}
|
||||
|
||||
void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const
|
||||
{
|
||||
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;
|
||||
}
|
||||
void MWMechanics::NpcStats::readState (const ESM::CreatureStats& state)
|
||||
{
|
||||
CreatureStats::readState(state);
|
||||
}
|
||||
|
||||
void MWMechanics::NpcStats::readState (const ESM::NpcStats& state)
|
||||
{
|
||||
|
|
|
@ -171,8 +171,10 @@ namespace MWMechanics
|
|||
/// @param time value from [0,20]
|
||||
void setTimeToStartDrowning(float time);
|
||||
|
||||
void writeState (ESM::CreatureStats& state) const;
|
||||
void writeState (ESM::NpcStats& state) const;
|
||||
|
||||
void readState (const ESM::CreatureStats& 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 "../mwmechanics/creaturestats.hpp"
|
||||
#include "../mwmechanics/movement.hpp"
|
||||
#include "../mwmechanics/actorutil.hpp"
|
||||
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
#include "../mwworld/player.hpp"
|
||||
|
||||
#include "../mwrender/bulletdebugdraw.hpp"
|
||||
|
||||
|
@ -46,487 +44,14 @@
|
|||
#include "object.hpp"
|
||||
#include "heightfield.hpp"
|
||||
#include "hasspherecollisioncallback.hpp"
|
||||
#include "deepestnotmecontacttestresultcallback.hpp"
|
||||
#include "closestnotmerayresultcallback.hpp"
|
||||
#include "contacttestresultcallback.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "movementsolver.hpp"
|
||||
|
||||
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)
|
||||
: mShapeManager(new Resource::BulletShapeManager(resourceSystem->getVFS(), resourceSystem->getSceneManager(), resourceSystem->getNifFileManager()))
|
||||
, mResourceSystem(resourceSystem)
|
||||
|
@ -668,54 +193,6 @@ namespace MWPhysics
|
|||
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,
|
||||
const osg::Vec3f &origin,
|
||||
const osg::Quat &orient,
|
||||
|
@ -806,35 +283,6 @@ namespace MWPhysics
|
|||
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
|
||||
{
|
||||
btVector3 btFrom = Misc::Convert::toBullet(from);
|
||||
|
@ -979,32 +427,6 @@ namespace MWPhysics
|
|||
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
|
||||
{
|
||||
btCollisionObject* me = nullptr;
|
||||
|
|
|
@ -50,9 +50,6 @@ namespace MWPhysics
|
|||
class Object;
|
||||
class Actor;
|
||||
|
||||
static const float sMaxSlope = 49.0f;
|
||||
static const float sStepSizeUp = 34.0f;
|
||||
|
||||
class PhysicsSystem
|
||||
{
|
||||
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 "actor.hpp"
|
||||
#include "closestnotmeconvexresultcallback.hpp"
|
||||
|
||||
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)
|
||||
{
|
||||
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||
|
@ -59,22 +23,21 @@ void ActorTracer::doTrace(const btCollisionObject *actor, const osg::Vec3f& star
|
|||
from.setOrigin(btstart);
|
||||
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
|
||||
newTraceCallback.m_collisionFilterGroup = actor->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
newTraceCallback.m_collisionFilterMask = actor->getBroadphaseHandle()->m_collisionFilterMask;
|
||||
|
||||
const btCollisionShape *shape = actor->getCollisionShape();
|
||||
assert(shape->isConvex());
|
||||
world->convexSweepTest(static_cast<const btConvexShape*>(shape),
|
||||
from, to, newTraceCallback);
|
||||
world->convexSweepTest(static_cast<const btConvexShape*>(shape), from, to, newTraceCallback);
|
||||
|
||||
// Copy the hit data over to our trace results struct:
|
||||
if(newTraceCallback.hasHit())
|
||||
{
|
||||
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
|
||||
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;
|
||||
mHitPoint = Misc::Convert::toOsg(newTraceCallback.m_hitPointWorld);
|
||||
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)
|
||||
{
|
||||
const btVector3 btstart(start.x(), start.y(), start.z());
|
||||
const btVector3 btend(end.x(), end.y(), end.z());
|
||||
const btVector3 btstart = Misc::Convert::toBullet(start);
|
||||
const btVector3 btend = Misc::Convert::toBullet(end);
|
||||
|
||||
const btTransform &trans = actor->getCollisionObject()->getWorldTransform();
|
||||
btTransform from(trans.getBasis(), btstart);
|
||||
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
|
||||
newTraceCallback.m_collisionFilterGroup = actor->getCollisionObject()->getBroadphaseHandle()->m_collisionFilterGroup;
|
||||
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);
|
||||
if(newTraceCallback.hasHit())
|
||||
{
|
||||
const btVector3& tracehitnormal = newTraceCallback.m_hitNormalWorld;
|
||||
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;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace MWRender
|
|||
void NavMesh::update(const dtNavMesh& navMesh, const std::size_t id,
|
||||
const std::size_t generation, const std::size_t revision, const DetourNavigator::Settings& settings)
|
||||
{
|
||||
if (!mEnabled || (mGroup && mId == id && mGeneration >= generation && mRevision >= revision))
|
||||
if (!mEnabled || (mGroup && mId == id && mGeneration == generation && mRevision == revision))
|
||||
return;
|
||||
|
||||
mId = id;
|
||||
|
|
|
@ -269,6 +269,9 @@ void HeadAnimationTime::setBlinkStop(float value)
|
|||
NpcAnimation::NpcType NpcAnimation::getNpcType()
|
||||
{
|
||||
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;
|
||||
if (cls.getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0)
|
||||
curType = Type_Vampire;
|
||||
|
@ -587,7 +590,7 @@ void NpcAnimation::updateParts()
|
|||
int mBasePriority;
|
||||
} slotlist[] = {
|
||||
// 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_Helmet, 0 },
|
||||
{ MWWorld::InventoryStore::Slot_Cuirass, 0 },
|
||||
|
@ -641,7 +644,7 @@ void NpcAnimation::updateParts()
|
|||
ESM::PartReferenceType parts[] = {
|
||||
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_RForearm, ESM::PRT_LForearm
|
||||
ESM::PRT_RForearm, ESM::PRT_LForearm, ESM::PRT_Cuirass
|
||||
};
|
||||
size_t parts_size = sizeof(parts)/sizeof(parts[0]);
|
||||
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 "navmesh.hpp"
|
||||
#include "actorspaths.hpp"
|
||||
#include "recastmesh.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
@ -217,7 +218,9 @@ namespace MWRender
|
|||
{
|
||||
resourceSystem->getSceneManager()->setParticleSystemMask(SceneUtil::Mask_ParticleSystem);
|
||||
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
|
||||
resourceSystem->getSceneManager()->setClampLighting(Settings::Manager::getBool("clamp lighting", "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["clamp"] = Settings::Manager::getBool("clamp lighting", "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.
|
||||
mResourceSystem->getSceneManager()->getShaderManager().setGlobalDefines(globalDefines);
|
||||
|
||||
mNavMesh.reset(new NavMesh(mRootNode, Settings::Manager::getBool("enable nav mesh render", "Navigator")));
|
||||
mActorsPaths.reset(new ActorsPaths(mRootNode, Settings::Manager::getBool("enable agents paths render", "Navigator")));
|
||||
mRecastMesh.reset(new RecastMesh(mRootNode, Settings::Manager::getBool("enable recast mesh render", "Navigator")));
|
||||
mPathgrid.reset(new Pathgrid(mRootNode));
|
||||
|
||||
mObjects.reset(new Objects(mResourceSystem, sceneRoot, mUnrefQueue.get()));
|
||||
|
@ -585,6 +590,10 @@ namespace MWRender
|
|||
{
|
||||
return mActorsPaths->toggle();
|
||||
}
|
||||
else if (mode == Render_RecastMesh)
|
||||
{
|
||||
return mRecastMesh->toggle();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -651,6 +660,7 @@ namespace MWRender
|
|||
}
|
||||
|
||||
updateNavMesh();
|
||||
updateRecastMesh();
|
||||
|
||||
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 NavMesh;
|
||||
class ActorsPaths;
|
||||
class RecastMesh;
|
||||
|
||||
class RenderingManager : public MWRender::RenderingInterface
|
||||
{
|
||||
|
@ -246,6 +247,8 @@ namespace MWRender
|
|||
|
||||
void updateNavMesh();
|
||||
|
||||
void updateRecastMesh();
|
||||
|
||||
osg::ref_ptr<osgUtil::IntersectionVisitor> getIntersectionVisitor(osgUtil::Intersector* intersector, bool ignorePlayer, bool ignoreActors);
|
||||
|
||||
osg::ref_ptr<osgUtil::IntersectionVisitor> mIntersectionVisitor;
|
||||
|
@ -264,6 +267,7 @@ namespace MWRender
|
|||
std::unique_ptr<NavMesh> mNavMesh;
|
||||
std::size_t mNavMeshNumber = 0;
|
||||
std::unique_ptr<ActorsPaths> mActorsPaths;
|
||||
std::unique_ptr<RecastMesh> mRecastMesh;
|
||||
std::unique_ptr<Pathgrid> mPathgrid;
|
||||
std::unique_ptr<Objects> mObjects;
|
||||
std::unique_ptr<Water> mWater;
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace MWRender
|
|||
Render_Scene,
|
||||
Render_NavMesh,
|
||||
Render_ActorsPaths,
|
||||
Render_RecastMesh,
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -463,5 +463,6 @@ op 0x200030c: RepairedOnMe
|
|||
op 0x200030d: RepairedOnMe, explicit
|
||||
op 0x200030e: TestCells
|
||||
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)
|
||||
{
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox);
|
||||
|
@ -1664,6 +1678,7 @@ namespace MWScript
|
|||
interpreter.installSegment5 (Compiler::Misc::opcodeSetNavMeshNumberToRender, new OpSetNavMeshNumberToRender);
|
||||
interpreter.installSegment5 (Compiler::Misc::opcodeRepairedOnMe, new OpRepairedOnMe<ImplicitRef>);
|
||||
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().resetGlobalVariable();
|
||||
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
|
||||
// maybe we should do this in the copy constructor instead?
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
#include "../mwphysics/actor.hpp"
|
||||
#include "../mwphysics/collisiontype.hpp"
|
||||
#include "../mwphysics/object.hpp"
|
||||
#include "../mwphysics/constants.hpp"
|
||||
|
||||
#include "player.hpp"
|
||||
#include "manualref.hpp"
|
||||
|
@ -1627,7 +1628,9 @@ namespace MWWorld
|
|||
|
||||
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);
|
||||
if (traced.z() < pos.z())
|
||||
|
|
|
@ -25,12 +25,15 @@ namespace
|
|||
{
|
||||
const osg::Vec3f mAgentHalfExtents {1, 2, 3};
|
||||
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<float> mVertices {{0, 0, 0, 1, 0, 0, 1, 1, 0}};
|
||||
const std::vector<AreaType> mAreaTypes {1, AreaType_ground};
|
||||
const std::vector<RecastMesh::Water> mWater {};
|
||||
const std::size_t mTrianglesPerChunk {1};
|
||||
const RecastMesh mRecastMesh {mIndices, mVertices, mAreaTypes, mWater, mTrianglesPerChunk};
|
||||
const RecastMesh mRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, mWater, mTrianglesPerChunk};
|
||||
const std::vector<OffMeshConnection> mOffMeshConnections {};
|
||||
unsigned char* const mData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData mNavMeshData {mData, 1};
|
||||
|
@ -128,7 +131,8 @@ namespace
|
|||
const std::size_t maxSize = 1;
|
||||
NavMeshTilesCache cache(maxSize);
|
||||
const std::vector<RecastMesh::Water> water {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh unexistentRecastMesh {mIndices, mVertices, mAreaTypes, water, mTrianglesPerChunk};
|
||||
const RecastMesh unexistentRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, water, mTrianglesPerChunk};
|
||||
|
||||
cache.set(mAgentHalfExtents, mTilePosition, mRecastMesh, mOffMeshConnections, std::move(mNavMeshData));
|
||||
EXPECT_FALSE(cache.get(mAgentHalfExtents, mTilePosition, unexistentRecastMesh, mOffMeshConnections));
|
||||
|
@ -142,7 +146,8 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
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));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
|
@ -162,7 +167,8 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
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));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
|
@ -180,14 +186,14 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> leastRecentlySetWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh leastRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlySetWater,
|
||||
mTrianglesPerChunk};
|
||||
const RecastMesh leastRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, leastRecentlySetWater, mTrianglesPerChunk};
|
||||
const auto leastRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData leastRecentlySetNavMeshData {leastRecentlySetData, 1};
|
||||
|
||||
const std::vector<RecastMesh::Water> mostRecentlySetWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||
const RecastMesh mostRecentlySetRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlySetWater,
|
||||
mTrianglesPerChunk};
|
||||
const RecastMesh mostRecentlySetRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, mostRecentlySetWater, mTrianglesPerChunk};
|
||||
const auto mostRecentlySetData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData mostRecentlySetNavMeshData {mostRecentlySetData, 1};
|
||||
|
||||
|
@ -212,14 +218,14 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
const std::vector<RecastMesh::Water> leastRecentlyUsedWater {1, RecastMesh::Water {1, btTransform::getIdentity()}};
|
||||
const RecastMesh leastRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, leastRecentlyUsedWater,
|
||||
mTrianglesPerChunk};
|
||||
const RecastMesh leastRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, leastRecentlyUsedWater, mTrianglesPerChunk};
|
||||
const auto leastRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData leastRecentlyUsedNavMeshData {leastRecentlyUsedData, 1};
|
||||
|
||||
const std::vector<RecastMesh::Water> mostRecentlyUsedWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||
const RecastMesh mostRecentlyUsedRecastMesh {mIndices, mVertices, mAreaTypes, mostRecentlyUsedWater,
|
||||
mTrianglesPerChunk};
|
||||
const RecastMesh mostRecentlyUsedRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, mostRecentlyUsedWater, mTrianglesPerChunk};
|
||||
const auto mostRecentlyUsedData = reinterpret_cast<unsigned char*>(dtAlloc(1, DT_ALLOC_PERM));
|
||||
NavMeshData mostRecentlyUsedNavMeshData {mostRecentlyUsedData, 1};
|
||||
|
||||
|
@ -256,7 +262,7 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
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));
|
||||
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
||||
|
||||
|
@ -275,12 +281,13 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
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));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
const std::vector<RecastMesh::Water> tooLargeWater {1, RecastMesh::Water {2, btTransform::getIdentity()}};
|
||||
const RecastMesh tooLargeRecastMesh {mIndices, mVertices, mAreaTypes, tooLargeWater, mTrianglesPerChunk};
|
||||
const RecastMesh tooLargeRecastMesh {mGeneration, mRevision, mIndices, mVertices,
|
||||
mAreaTypes, tooLargeWater, mTrianglesPerChunk};
|
||||
const auto tooLargeData = reinterpret_cast<unsigned char*>(dtAlloc(2, DT_ALLOC_PERM));
|
||||
NavMeshData tooLargeNavMeshData {tooLargeData, 2};
|
||||
|
||||
|
@ -303,7 +310,8 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
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));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
|
@ -326,7 +334,7 @@ namespace
|
|||
NavMeshTilesCache cache(maxSize);
|
||||
|
||||
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));
|
||||
NavMeshData anotherNavMeshData {anotherData, 1};
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ namespace
|
|||
{
|
||||
Settings mSettings;
|
||||
TileBounds mBounds;
|
||||
const std::size_t mGeneration = 0;
|
||||
const std::size_t mRevision = 0;
|
||||
|
||||
DetourNavigatorRecastMeshBuilderTest()
|
||||
{
|
||||
|
@ -45,7 +47,7 @@ namespace
|
|||
TEST_F(DetourNavigatorRecastMeshBuilderTest, create_for_empty_should_return_empty)
|
||||
{
|
||||
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->getIndices(), std::vector<int>());
|
||||
EXPECT_EQ(recastMesh->getAreaTypes(), std::vector<AreaType>());
|
||||
|
@ -59,7 +61,7 @@ namespace
|
|||
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
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>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
|
@ -80,7 +82,7 @@ namespace
|
|||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
2, 3, 0,
|
||||
0, 3, 4,
|
||||
|
@ -96,7 +98,7 @@ namespace
|
|||
btHeightfieldTerrainShape shape(2, 2, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
-0.5, 0, -0.5,
|
||||
-0.5, 0, 0.5,
|
||||
|
@ -114,7 +116,7 @@ namespace
|
|||
btBoxShape shape(btVector3(1, 1, 2));
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
builder.addObject(static_cast<const btCollisionShape&>(shape), btTransform::getIdentity(), AreaType_ground);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 2, 1,
|
||||
-1, 2, 1,
|
||||
|
@ -161,7 +163,7 @@ namespace
|
|||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
|
@ -210,7 +212,7 @@ namespace
|
|||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
2, 3, 0,
|
||||
0, 3, 4,
|
||||
|
@ -234,7 +236,7 @@ namespace
|
|||
btTransform(btMatrix3x3::getIdentity().scaled(btVector3(1, 2, 3)), btVector3(1, 2, 3)),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
3, 12, 2,
|
||||
1, 12, 10,
|
||||
|
@ -256,7 +258,7 @@ namespace
|
|||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
|
@ -284,7 +286,7 @@ namespace
|
|||
btTransform::getIdentity(),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
-0.2f, 0, -0.3f,
|
||||
-0.3f, 0, -0.2f,
|
||||
|
@ -309,7 +311,7 @@ namespace
|
|||
static_cast<btScalar>(-osg::PI_4))),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
0, -0.70710659027099609375, -3.535533905029296875,
|
||||
0, 0.707107067108154296875, -3.535533905029296875,
|
||||
|
@ -334,7 +336,7 @@ namespace
|
|||
static_cast<btScalar>(osg::PI_4))),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
-3.535533905029296875, -0.70710659027099609375, 0,
|
||||
-3.535533905029296875, 0.707107067108154296875, 0,
|
||||
|
@ -359,7 +361,7 @@ namespace
|
|||
static_cast<btScalar>(osg::PI_4))),
|
||||
AreaType_ground
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1.41421353816986083984375, 0, 1.1920928955078125e-07,
|
||||
-1.41421353816986083984375, 0, -1.1920928955078125e-07,
|
||||
|
@ -388,7 +390,7 @@ namespace
|
|||
btTransform::getIdentity(),
|
||||
AreaType_null
|
||||
);
|
||||
const auto recastMesh = builder.create();
|
||||
const auto recastMesh = builder.create(mGeneration, mRevision);
|
||||
EXPECT_EQ(recastMesh->getVertices(), std::vector<float>({
|
||||
1, 0, -1,
|
||||
-1, 0, 1,
|
||||
|
@ -405,7 +407,7 @@ namespace
|
|||
{
|
||||
RecastMeshBuilder builder(mSettings, mBounds);
|
||||
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>({
|
||||
RecastMesh::Water {1000, btTransform(btMatrix3x3::getIdentity(), btVector3(100, 200, 300))}
|
||||
}));
|
||||
|
|
|
@ -52,7 +52,7 @@ add_component_dir (shader
|
|||
add_component_dir (sceneutil
|
||||
clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller
|
||||
lightmanager lightutil positionattitudetransform workqueue unrefqueue pathgridutil waterutil writescene serialize optimizer
|
||||
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique vismask
|
||||
actorutil detourdebugdraw navmesh agentpath shadow mwshadowtechnique vismask recastmesh
|
||||
)
|
||||
|
||||
add_component_dir (nif
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace Compiler
|
|||
{
|
||||
extensions.registerInstruction ("aiactivate", "c/l", opcodeAIActivate,
|
||||
opcodeAIActivateExplicit);
|
||||
extensions.registerInstruction ("aitravel", "fff/zx", opcodeAiTravel,
|
||||
extensions.registerInstruction ("aitravel", "fff/lx", opcodeAiTravel,
|
||||
opcodeAiTravelExplicit);
|
||||
extensions.registerInstruction ("aiescort", "cffff/l", opcodeAiEscort,
|
||||
opcodeAiEscortExplicit);
|
||||
|
@ -327,6 +327,7 @@ namespace Compiler
|
|||
extensions.registerInstruction ("toggleactorspaths", "", opcodeToggleActorsPaths);
|
||||
extensions.registerInstruction ("setnavmeshnumber", "l", opcodeSetNavMeshNumberToRender);
|
||||
extensions.registerFunction ("repairedonme", 'l', "S", opcodeRepairedOnMe, opcodeRepairedOnMeExplicit);
|
||||
extensions.registerInstruction ("togglerecastmesh", "", opcodeToggleRecastMesh);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -304,6 +304,7 @@ namespace Compiler
|
|||
const int opcodeSetNavMeshNumberToRender = 0x200030a;
|
||||
const int opcodeRepairedOnMe = 0x200030c;
|
||||
const int opcodeRepairedOnMeExplicit = 0x200030d;
|
||||
const int opcodeToggleRecastMesh = 0x2000310;
|
||||
}
|
||||
|
||||
namespace Sky
|
||||
|
|
|
@ -282,6 +282,8 @@ namespace Compiler
|
|||
|
||||
if (!scanName (name))
|
||||
return false;
|
||||
else if(name.empty())
|
||||
return true;
|
||||
|
||||
TokenLoc loc (mLoc);
|
||||
mLoc.mLiteral.clear();
|
||||
|
@ -368,6 +370,26 @@ namespace Compiler
|
|||
mErrorHandler.warning ("string contains newline character, make sure that it is intended", mLoc);
|
||||
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;
|
||||
mErrorHandler.error ("incomplete string or name", mLoc);
|
||||
break;
|
||||
|
|
|
@ -45,6 +45,9 @@ namespace ContentSelectorView
|
|||
QWidget *uiWidget() const
|
||||
{ return ui.contentGroupBox; }
|
||||
|
||||
QToolButton *refreshButton() const
|
||||
{ return ui.refreshButton; }
|
||||
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds)
|
||||
: mImpl(settings, bounds)
|
||||
CachedRecastMeshManager::CachedRecastMeshManager(const Settings& settings, const TileBounds& bounds,
|
||||
std::size_t generation)
|
||||
: mImpl(settings, bounds, generation)
|
||||
{}
|
||||
|
||||
bool CachedRecastMeshManager::addObject(const ObjectId id, const btCollisionShape& shape,
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace DetourNavigator
|
|||
class CachedRecastMeshManager
|
||||
{
|
||||
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,
|
||||
const AreaType areaType);
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
#include "settings.hpp"
|
||||
#include "objectid.hpp"
|
||||
#include "navmeshcacheitem.hpp"
|
||||
#include "recastmesh.hpp"
|
||||
#include "recastmeshtiles.hpp"
|
||||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
|
@ -204,6 +206,8 @@ namespace DetourNavigator
|
|||
*/
|
||||
boost::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
|
||||
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);
|
||||
}
|
||||
|
||||
RecastMeshTiles NavigatorImpl::getRecastMeshTiles()
|
||||
{
|
||||
return mNavMeshManager.getRecastMeshTiles();
|
||||
}
|
||||
|
||||
void NavigatorImpl::updateAvoidShapeId(const ObjectId id, const ObjectId avoidId)
|
||||
{
|
||||
updateId(id, avoidId, mWaterIds);
|
||||
|
|
|
@ -50,6 +50,8 @@ namespace DetourNavigator
|
|||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
|
||||
|
||||
RecastMeshTiles getRecastMeshTiles() override;
|
||||
|
||||
private:
|
||||
Settings mSettings;
|
||||
NavMeshManager mNavMeshManager;
|
||||
|
|
|
@ -81,6 +81,11 @@ namespace DetourNavigator
|
|||
|
||||
void reportStats(unsigned int /*frameNumber*/, osg::Stats& /*stats*/) const override {}
|
||||
|
||||
RecastMeshTiles getRecastMeshTiles() override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
Settings mDefaultSettings {};
|
||||
SharedNavMeshCacheItem mEmptyNavMeshCacheItem;
|
||||
|
|
|
@ -147,7 +147,7 @@ namespace DetourNavigator
|
|||
const auto playerTile = getTilePosition(mSettings, toNavMeshCoordinates(mSettings, playerPosition));
|
||||
auto& lastRevision = mLastRecastMeshManagerRevision[agentHalfExtents];
|
||||
auto lastPlayerTile = mPlayerTile.find(agentHalfExtents);
|
||||
if (lastRevision >= mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
|
||||
if (lastRevision == mRecastMeshManager.getRevision() && lastPlayerTile != mPlayerTile.end()
|
||||
&& lastPlayerTile->second == playerTile)
|
||||
return;
|
||||
lastRevision = mRecastMeshManager.getRevision();
|
||||
|
@ -220,6 +220,17 @@ namespace DetourNavigator
|
|||
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,
|
||||
const ChangeType changeType)
|
||||
{
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "cachedrecastmeshmanager.hpp"
|
||||
#include "offmeshconnectionsmanager.hpp"
|
||||
#include "sharednavmesh.hpp"
|
||||
#include "recastmeshtiles.hpp"
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||
|
||||
|
@ -52,6 +53,8 @@ namespace DetourNavigator
|
|||
|
||||
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
|
||||
|
||||
RecastMeshTiles getRecastMeshTiles();
|
||||
|
||||
private:
|
||||
const Settings& mSettings;
|
||||
TileCachedRecastMeshManager mRecastMeshManager;
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
RecastMesh::RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
|
||||
std::vector<Water> water, const std::size_t trianglesPerChunk)
|
||||
: mIndices(std::move(indices))
|
||||
RecastMesh::RecastMesh(std::size_t generation, std::size_t revision, std::vector<int> indices, std::vector<float> vertices,
|
||||
std::vector<AreaType> areaTypes, std::vector<Water> water, const std::size_t trianglesPerChunk)
|
||||
: mGeneration(generation)
|
||||
, mRevision(revision)
|
||||
, mIndices(std::move(indices))
|
||||
, mVertices(std::move(vertices))
|
||||
, mAreaTypes(std::move(areaTypes))
|
||||
, mWater(std::move(water))
|
||||
|
|
|
@ -24,8 +24,18 @@ namespace DetourNavigator
|
|||
btTransform mTransform;
|
||||
};
|
||||
|
||||
RecastMesh(std::vector<int> indices, std::vector<float> vertices, std::vector<AreaType> areaTypes,
|
||||
std::vector<Water> water, const std::size_t trianglesPerChunk);
|
||||
RecastMesh(std::size_t generation, std::size_t revision, std::vector<int> indices, std::vector<float> vertices,
|
||||
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
|
||||
{
|
||||
|
@ -68,6 +78,8 @@ namespace DetourNavigator
|
|||
}
|
||||
|
||||
private:
|
||||
std::size_t mGeneration;
|
||||
std::size_t mRevision;
|
||||
std::vector<int> mIndices;
|
||||
std::vector<float> mVertices;
|
||||
std::vector<AreaType> mAreaTypes;
|
||||
|
|
|
@ -112,9 +112,10 @@ namespace DetourNavigator
|
|||
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()
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace DetourNavigator
|
|||
|
||||
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();
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
|
||||
namespace DetourNavigator
|
||||
{
|
||||
RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds)
|
||||
: mShouldRebuild(false)
|
||||
RecastMeshManager::RecastMeshManager(const Settings& settings, const TileBounds& bounds, std::size_t generation)
|
||||
: mGeneration(generation)
|
||||
, mMeshBuilder(settings, bounds)
|
||||
{
|
||||
}
|
||||
|
@ -19,8 +19,8 @@ namespace DetourNavigator
|
|||
mObjectsOrder.erase(iterator);
|
||||
return false;
|
||||
}
|
||||
mShouldRebuild = true;
|
||||
return mShouldRebuild;
|
||||
++mRevision;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RecastMeshManager::updateObject(const ObjectId id, const btTransform& transform, const AreaType areaType)
|
||||
|
@ -30,8 +30,8 @@ namespace DetourNavigator
|
|||
return false;
|
||||
if (!object->second->update(transform, areaType))
|
||||
return false;
|
||||
mShouldRebuild = true;
|
||||
return mShouldRebuild;
|
||||
++mRevision;
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::optional<RemovedRecastMeshObject> RecastMeshManager::removeObject(const ObjectId id)
|
||||
|
@ -42,7 +42,7 @@ namespace DetourNavigator
|
|||
const RemovedRecastMeshObject result {object->second->getShape(), object->second->getTransform()};
|
||||
mObjectsOrder.erase(object->second);
|
||||
mObjects.erase(object);
|
||||
mShouldRebuild = true;
|
||||
++mRevision;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace DetourNavigator
|
|||
mWaterOrder.erase(iterator);
|
||||
return false;
|
||||
}
|
||||
mShouldRebuild = true;
|
||||
++mRevision;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ namespace DetourNavigator
|
|||
const auto water = mWater.find(cellPosition);
|
||||
if (water == mWater.end())
|
||||
return boost::none;
|
||||
mShouldRebuild = true;
|
||||
++mRevision;
|
||||
const auto result = *water->second;
|
||||
mWaterOrder.erase(water->second);
|
||||
mWater.erase(water);
|
||||
|
@ -74,7 +74,7 @@ namespace DetourNavigator
|
|||
std::shared_ptr<RecastMesh> RecastMeshManager::getMesh()
|
||||
{
|
||||
rebuild();
|
||||
return mMeshBuilder.create();
|
||||
return mMeshBuilder.create(mGeneration, mLastBuildRevision);
|
||||
}
|
||||
|
||||
bool RecastMeshManager::isEmpty() const
|
||||
|
@ -84,13 +84,13 @@ namespace DetourNavigator
|
|||
|
||||
void RecastMeshManager::rebuild()
|
||||
{
|
||||
if (!mShouldRebuild)
|
||||
if (mLastBuildRevision == mRevision)
|
||||
return;
|
||||
mMeshBuilder.reset();
|
||||
for (const auto& v : mWaterOrder)
|
||||
mMeshBuilder.addWater(v.mCellSize, v.mTransform);
|
||||
for (const auto& v : mObjectsOrder)
|
||||
mMeshBuilder.addObject(v.getShape(), v.getTransform(), v.getAreaType());
|
||||
mShouldRebuild = false;
|
||||
mLastBuildRevision = mRevision;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace DetourNavigator
|
|||
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,
|
||||
const AreaType areaType);
|
||||
|
@ -52,7 +52,9 @@ namespace DetourNavigator
|
|||
bool isEmpty() const;
|
||||
|
||||
private:
|
||||
bool mShouldRebuild;
|
||||
std::size_t mRevision = 0;
|
||||
std::size_t mLastBuildRevision = 0;
|
||||
std::size_t mGeneration;
|
||||
RecastMeshBuilder mMeshBuilder;
|
||||
std::list<RecastMeshObject> mObjectsOrder;
|
||||
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.mMax += osg::Vec2f(border, border);
|
||||
tile = tiles->insert(std::make_pair(tilePosition,
|
||||
CachedRecastMeshManager(mSettings, tileBounds))).first;
|
||||
CachedRecastMeshManager(mSettings, tileBounds, mTilesGeneration))).first;
|
||||
}
|
||||
if (tile->second.addWater(cellPosition, cellSize, transform))
|
||||
{
|
||||
|
@ -151,7 +151,10 @@ namespace DetourNavigator
|
|||
continue;
|
||||
const auto tileResult = tile->second.removeWater(cellPosition);
|
||||
if (tile->second.isEmpty())
|
||||
{
|
||||
tiles->erase(tile);
|
||||
++mTilesGeneration;
|
||||
}
|
||||
if (tileResult && !result)
|
||||
result = tileResult;
|
||||
}
|
||||
|
@ -189,7 +192,8 @@ namespace DetourNavigator
|
|||
auto tileBounds = makeTileBounds(mSettings, tilePosition);
|
||||
tileBounds.mMin -= 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);
|
||||
}
|
||||
|
@ -209,7 +213,10 @@ namespace DetourNavigator
|
|||
return boost::optional<RemovedRecastMeshObject>();
|
||||
const auto tileResult = tile->second.removeObject(id);
|
||||
if (tile->second.isEmpty())
|
||||
{
|
||||
tiles.erase(tile);
|
||||
++mTilesGeneration;
|
||||
}
|
||||
return tileResult;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ namespace DetourNavigator
|
|||
std::unordered_map<ObjectId, std::set<TilePosition>> mObjectsTilesPositions;
|
||||
std::map<osg::Vec2i, std::vector<TilePosition>> mWaterTilesPositions;
|
||||
std::size_t mRevision = 0;
|
||||
std::size_t mTilesGeneration = 0;
|
||||
|
||||
bool addTile(const ObjectId id, const btCollisionShape& shape, const btTransform& transform,
|
||||
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);
|
||||
}
|
||||
|
||||
if (!inInventory)
|
||||
esm.writeHNOCString("ANAM", mOwner);
|
||||
|
||||
esm.writeHNOCString("BNAM", mGlobalVariable);
|
||||
esm.writeHNOCString("XSOL", mSoul);
|
||||
|
||||
if (!inInventory)
|
||||
{
|
||||
esm.writeHNOCString("CNAM", mFaction);
|
||||
if (mFactionRank != -2) {
|
||||
if (mFactionRank != -2)
|
||||
{
|
||||
esm.writeHNT("INDX", mFactionRank);
|
||||
}
|
||||
}
|
||||
|
||||
if (mEnchantmentCharge != -1)
|
||||
esm.writeHNT("XCHG", mEnchantmentCharge);
|
||||
|
|
|
@ -14,6 +14,15 @@ namespace ESM
|
|||
|
||||
virtual void load (ESMReader &esm);
|
||||
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 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 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)
|
||||
esm.writeHNT ("GOLD", mGoldPool);
|
||||
|
||||
if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0)
|
||||
esm.writeHNT ("TIME", mTradeTime);
|
||||
|
||||
if (mDead)
|
||||
|
|
|
@ -13,6 +13,15 @@ namespace ESM
|
|||
|
||||
virtual void load (ESMReader &esm);
|
||||
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 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 <stdexcept>
|
||||
#include <sstream>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "esmreader.hpp"
|
||||
#include "esmwriter.hpp"
|
||||
|
||||
|
@ -84,4 +88,74 @@ void ESM::ObjectState::blank()
|
|||
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() {}
|
||||
|
|
|
@ -12,6 +12,11 @@ namespace ESM
|
|||
{
|
||||
class ESMReader;
|
||||
class ESMWriter;
|
||||
struct ContainerState;
|
||||
struct CreatureLevListState;
|
||||
struct CreatureState;
|
||||
struct DoorState;
|
||||
struct NpcState;
|
||||
|
||||
// format 0, saved games only
|
||||
|
||||
|
@ -48,6 +53,21 @@ namespace ESM
|
|||
void blank();
|
||||
|
||||
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.
|
||||
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;
|
||||
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
|
||||
|
@ -461,8 +461,6 @@ namespace NifOsg
|
|||
break;
|
||||
}
|
||||
case Nif::RC_NiSwitchNode:
|
||||
case Nif::RC_NiTriShape:
|
||||
case Nif::RC_NiTriStrips:
|
||||
case Nif::RC_NiAutoNormalParticles:
|
||||
case Nif::RC_NiRotatingParticles:
|
||||
// Leaf nodes in the NIF hierarchy, so won't be able to dynamically attach children.
|
||||
|
@ -1719,9 +1717,8 @@ namespace NifOsg
|
|||
|
||||
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)
|
||||
{
|
||||
case Nif::RC_NiSpecularProperty:
|
||||
|
|
|
@ -50,10 +50,8 @@ namespace SceneUtil
|
|||
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)
|
||||
|
@ -85,9 +83,10 @@ namespace SceneUtil
|
|||
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()
|
||||
|
|
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?
|
||||
bool existing = false;
|
||||
|
||||
// Is an entirely blank line queued to be added?
|
||||
bool emptyLineQueued = false;
|
||||
|
||||
// The category/section we're currently in.
|
||||
std::string currentCategory;
|
||||
|
||||
|
@ -100,12 +103,22 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
|||
// The current character position in the line.
|
||||
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;
|
||||
|
||||
// Copy entirely blank lines.
|
||||
if (!skipWhiteSpace(i, line)) {
|
||||
ostream << line << std::endl;
|
||||
// Queue entirely blank lines to add them if desired.
|
||||
if (!skipWhiteSpace(i, line))
|
||||
{
|
||||
emptyLineQueued = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -128,9 +141,13 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!currentCategory.empty())
|
||||
{
|
||||
// Ensure that all options in the current category have been written.
|
||||
for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) {
|
||||
if (mit->second == false && mit->first.first == currentCategory) {
|
||||
for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit)
|
||||
{
|
||||
if (mit->second == false && mit->first.first == currentCategory)
|
||||
{
|
||||
Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] "
|
||||
<< mit->first.second << " = " << settings.at(mit->first);
|
||||
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;
|
||||
}
|
||||
}
|
||||
// Add an empty line after the last option in a category.
|
||||
ostream << std::endl;
|
||||
}
|
||||
|
||||
// Update the current category.
|
||||
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.
|
||||
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
|
||||
***************
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue