1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-03-01 08: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:
David Cernat 2020-04-01 19:24:11 +03:00
commit 7bc3298ed4
112 changed files with 2181 additions and 831 deletions

View file

@ -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"

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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);
@ -167,7 +175,16 @@ QStringList Launcher::DataFilesPage::selectedFilePaths()
QStringList filePaths;
for (const ContentSelectorModel::EsmFile *item : items)
{
filePaths.append(item->filePath());
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 &current)
{
setProfile(previous, current, true);

View file

@ -61,6 +61,7 @@ namespace Launcher
void slotProfileRenamed(const QString &previous, const QString &current);
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
{

View file

@ -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));

View file

@ -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

View file

@ -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" },

View file

@ -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,

View file

@ -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);

View file

@ -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));

View file

@ -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++;
}
}
}

View file

@ -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;
};
}

View file

@ -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)
{

View file

@ -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,

View file

@ -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");
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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

View file

@ -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);
}

View file

@ -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();

View file

@ -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())
return false;
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();

View file

@ -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
};
}

View file

@ -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;

View file

@ -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())
{

View file

@ -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;
}

View file

@ -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);
};
}

View file

@ -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)
{

View file

@ -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);
};
}

View 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);
}
}

View 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

View 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);
}
}

View 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

View 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

View 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;
}
}

View 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

View file

@ -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;
}
}

View file

@ -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

View 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;
}
}

View 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

View file

@ -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;

View file

@ -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:

View 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;
}
}

View 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

View file

@ -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

View file

@ -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;

View file

@ -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)

View 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;
}
}

View 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

View file

@ -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());
}
}

View file

@ -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;

View file

@ -13,6 +13,7 @@ namespace MWRender
Render_Scene,
Render_NavMesh,
Render_ActorsPaths,
Render_RecastMesh,
};
}

View file

@ -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

View file

@ -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);
}
}
}

View file

@ -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?

View file

@ -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())

View file

@ -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};

View file

@ -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))}
}));

View file

@ -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

View file

@ -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);
}
}

View file

@ -304,6 +304,7 @@ namespace Compiler
const int opcodeSetNavMeshNumberToRender = 0x200030a;
const int opcodeRepairedOnMe = 0x200030c;
const int opcodeRepairedOnMeExplicit = 0x200030d;
const int opcodeToggleRecastMesh = 0x2000310;
}
namespace Sky

View file

@ -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;

View file

@ -44,6 +44,9 @@ namespace ContentSelectorView
QWidget *uiWidget() const
{ return ui.contentGroupBox; }
QToolButton *refreshButton() const
{ return ui.refreshButton; }
private:

View file

@ -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,

View file

@ -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);

View file

@ -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;
};
}

View file

@ -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);

View file

@ -50,6 +50,8 @@ namespace DetourNavigator
void reportStats(unsigned int frameNumber, osg::Stats& stats) const override;
RecastMeshTiles getRecastMeshTiles() override;
private:
Settings mSettings;
NavMeshManager mNavMeshManager;

View file

@ -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;

View file

@ -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)
{

View file

@ -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;

View file

@ -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))

View file

@ -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;

View file

@ -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()

View file

@ -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();

View file

@ -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;
}
}

View file

@ -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;

View 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

View file

@ -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;
}
}

View file

@ -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,

View file

@ -153,13 +153,19 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory, bool
esm.writeHNT("XSCL", scale);
}
esm.writeHNOCString("ANAM", mOwner);
if (!inInventory)
esm.writeHNOCString("ANAM", mOwner);
esm.writeHNOCString("BNAM", mGlobalVariable);
esm.writeHNOCString("XSOL", mSoul);
esm.writeHNOCString("CNAM", mFaction);
if (mFactionRank != -2) {
esm.writeHNT("INDX", mFactionRank);
if (!inInventory)
{
esm.writeHNOCString("CNAM", mFaction);
if (mFactionRank != -2)
{
esm.writeHNT("INDX", mFactionRank);
}
}
if (mEnchantmentCharge != -1)

View file

@ -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;
}
};
}

View file

@ -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;
}
};
}

View file

@ -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;
}
};
}

View file

@ -144,7 +144,8 @@ void ESM::CreatureStats::save (ESMWriter &esm) const
if (mGoldPool)
esm.writeHNT ("GOLD", mGoldPool);
esm.writeHNT ("TIME", mTradeTime);
if (mTradeTime.mDay != 0 || mTradeTime.mHour != 0)
esm.writeHNT ("TIME", mTradeTime);
if (mDead)
esm.writeHNT ("DEAD", mDead);

View file

@ -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;
}
};
}

View file

@ -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;
}
};
}

View file

@ -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() {}

View file

@ -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();
};
}

View file

@ -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:

View file

@ -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()

View 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;
}
}

View 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

View file

@ -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,15 +141,22 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, con
continue;
}
// 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) {
Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] "
<< mit->first.second << " = " << settings.at(mit->first);
ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl;
mit->second = true;
changed = true;
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)
{
Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] "
<< mit->first.second << " = " << settings.at(mit->first);
ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl;
mit->second = true;
changed = true;
}
}
// Add an empty line after the last option in a category.
ostream << std::endl;
}
// Update the current category.

View file

@ -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