Merge pull request #350 from TES3MP/master

Add master commits up to 29 Nov 2017
new-script-api
David Cernat 7 years ago committed by GitHub
commit eff3504b05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -191,6 +191,7 @@ void CSMPrefs::State::declare()
setTooltip ("Acceleration factor during drag operations while holding down shift"). setTooltip ("Acceleration factor during drag operations while holding down shift").
setRange (0.001, 100.0); setRange (0.001, 100.0);
declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1); declareDouble ("rotate-factor", "Free rotation factor", 0.007).setPrecision(4).setRange(0.0001, 0.1);
declareDouble ("object-marker-alpha", "Object Marker Transparency", 0.5).setPrecision(2).setRange(0,1);
declareCategory ("Tooltips"); declareCategory ("Tooltips");
declareBool ("scene", "Show Tooltips in 3D scenes", true); declareBool ("scene", "Show Tooltips in 3D scenes", true);

@ -3,6 +3,7 @@
#include <stdexcept> #include <stdexcept>
#include <iostream> #include <iostream>
#include <osg/Depth>
#include <osg/Group> #include <osg/Group>
#include <osg/PositionAttitudeTransform> #include <osg/PositionAttitudeTransform>
@ -21,6 +22,7 @@
#include "../../model/world/universalid.hpp" #include "../../model/world/universalid.hpp"
#include "../../model/world/commandmacro.hpp" #include "../../model/world/commandmacro.hpp"
#include "../../model/world/cellcoordinates.hpp" #include "../../model/world/cellcoordinates.hpp"
#include "../../model/prefs/state.hpp"
#include <components/resource/scenemanager.hpp> #include <components/resource/scenemanager.hpp>
#include <components/sceneutil/lightutil.hpp> #include <components/sceneutil/lightutil.hpp>
@ -220,7 +222,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker (int axis)
for (int i=0; i<2; ++i) for (int i=0; i<2; ++i)
{ {
float length = i ? shaftLength : 0; float length = i ? shaftLength : MarkerShaftWidth;
vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis)); vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, -MarkerShaftWidth/2, length, axis));
vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis)); vertices->push_back (getMarkerPosition (-MarkerShaftWidth/2, MarkerShaftWidth/2, length, axis));
@ -285,15 +287,15 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeMoveOrScaleMarker (int axis)
for (int i=0; i<8; ++i) for (int i=0; i<8; ++i)
colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f,
axis==2 ? 1.0f : 0.2f, 1.0f)); axis==2 ? 1.0f : 0.2f, mMarkerTransparency));
for (int i=8; i<8+4+1; ++i) for (int i=8; i<8+4+1; ++i)
colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f, colours->push_back (osg::Vec4f (axis==0 ? 1.0f : 0.0f, axis==1 ? 1.0f : 0.0f,
axis==2 ? 1.0f : 0.0f, 1.0f)); axis==2 ? 1.0f : 0.0f, mMarkerTransparency));
geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX); geometry->setColorArray (colours, osg::Array::BIND_PER_VERTEX);
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Geode> geode (new osg::Geode); osg::ref_ptr<osg::Geode> geode (new osg::Geode);
geode->addDrawable (geometry); geode->addDrawable (geometry);
@ -305,11 +307,11 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
{ {
const float Pi = 3.14159265f; const float Pi = 3.14159265f;
const float InnerRadius = mBaseNode->getBound().radius(); const float InnerRadius = std::max(MarkerShaftBaseLength, mBaseNode->getBound().radius());
const float OuterRadius = InnerRadius + MarkerShaftWidth; const float OuterRadius = InnerRadius + MarkerShaftWidth;
const float SegmentDistance = 100.f; const float SegmentDistance = 100.f;
const size_t SegmentCount = std::min(64, std::max(8, (int)(OuterRadius * 2 * Pi / SegmentDistance))); const size_t SegmentCount = std::min(64, std::max(24, (int)(OuterRadius * 2 * Pi / SegmentDistance)));
const size_t VerticesPerSegment = 4; const size_t VerticesPerSegment = 4;
const size_t IndicesPerSegment = 24; const size_t IndicesPerSegment = 24;
@ -334,6 +336,9 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES, osg::ref_ptr<osg::DrawElementsUShort> primitives = new osg::DrawElementsUShort(osg::PrimitiveSet::TRIANGLES,
IndexCount); IndexCount);
// prevent some depth collision issues from overlaps
osg::Vec3f offset = getMarkerPosition(0, MarkerShaftWidth/4, 0, axis);
for (size_t i = 0; i < SegmentCount; ++i) for (size_t i = 0; i < SegmentCount; ++i)
{ {
size_t index = i * VerticesPerSegment; size_t index = i * VerticesPerSegment;
@ -344,13 +349,17 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
float outerX = OuterRadius * std::cos(i * Angle); float outerX = OuterRadius * std::cos(i * Angle);
float outerY = OuterRadius * std::sin(i * Angle); float outerY = OuterRadius * std::sin(i * Angle);
vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis); vertices->at(index++) = getMarkerPosition(innerX, innerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis); vertices->at(index++) = getMarkerPosition(innerX, innerY, -MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis); vertices->at(index++) = getMarkerPosition(outerX, outerY, MarkerShaftWidth / 2, axis) + offset;
vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis); vertices->at(index++) = getMarkerPosition(outerX, outerY, -MarkerShaftWidth / 2, axis) + offset;
} }
colors->at(0) = osg::Vec4f (axis==0 ? 1.0f : 0.2f, axis==1 ? 1.0f : 0.2f, axis==2 ? 1.0f : 0.2f, 1.0f); colors->at(0) = osg::Vec4f (
axis==0 ? 1.0f : 0.2f,
axis==1 ? 1.0f : 0.2f,
axis==2 ? 1.0f : 0.2f,
mMarkerTransparency);
for (size_t i = 0; i < SegmentCount; ++i) for (size_t i = 0; i < SegmentCount; ++i)
{ {
@ -374,7 +383,7 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
geometry->setColorArray(colors, osg::Array::BIND_OVERALL); geometry->setColorArray(colors, osg::Array::BIND_OVERALL);
geometry->addPrimitiveSet(primitives); geometry->addPrimitiveSet(primitives);
geometry->getOrCreateStateSet()->setMode (GL_LIGHTING, osg::StateAttribute::OFF); setupCommonMarkerState(geometry);
osg::ref_ptr<osg::Geode> geode = new osg::Geode(); osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable (geometry); geode->addDrawable (geometry);
@ -382,6 +391,15 @@ osg::ref_ptr<osg::Node> CSVRender::Object::makeRotateMarker (int axis)
return geode; return geode;
} }
void CSVRender::Object::setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry)
{
osg::ref_ptr<osg::StateSet> state = geometry->getOrCreateStateSet();
state->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
state->setMode(GL_BLEND, osg::StateAttribute::ON);
state->setRenderingHint(osg::StateSet::TRANSPARENT_BIN);
}
osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis) osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int axis)
{ {
switch (axis) switch (axis)
@ -399,7 +417,7 @@ osg::Vec3f CSVRender::Object::getMarkerPosition (float x, float y, float z, int
CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode, CSVRender::Object::Object (CSMWorld::Data& data, osg::Group* parentNode,
const std::string& id, bool referenceable, bool forceBaseToZero) const std::string& id, bool referenceable, bool forceBaseToZero)
: mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero), : mData (data), mBaseNode(0), mSelected(false), mParentNode(parentNode), mResourceSystem(data.getResourceSystem().get()), mForceBaseToZero (forceBaseToZero),
mScaleOverride (1), mOverrideFlags (0), mSubMode (-1) mScaleOverride (1), mOverrideFlags (0), mSubMode (-1), mMarkerTransparency(0.5f)
{ {
mRootNode = new osg::PositionAttitudeTransform; mRootNode = new osg::PositionAttitudeTransform;
@ -453,6 +471,7 @@ void CSVRender::Object::setSelected(bool selected)
else else
mRootNode->addChild(mBaseNode); mRootNode->addChild(mBaseNode);
mMarkerTransparency = CSMPrefs::get()["3D Scene Input"]["object-marker-alpha"].toDouble();
updateMarker(); updateMarker();
} }
@ -629,6 +648,12 @@ void CSVRender::Object::setScale (float scale)
adjustTransform(); adjustTransform();
} }
void CSVRender::Object::setMarkerTransparency(float value)
{
mMarkerTransparency = value;
updateMarker();
}
void CSVRender::Object::apply (CSMWorld::CommandMacro& commands) void CSVRender::Object::apply (CSMWorld::CommandMacro& commands)
{ {
const CSMWorld::RefCollection& collection = mData.getReferences(); const CSMWorld::RefCollection& collection = mData.getReferences();

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <osg/Geometry>
#include <osg/Referenced> #include <osg/Referenced>
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
@ -96,6 +97,7 @@ namespace CSVRender
int mOverrideFlags; int mOverrideFlags;
osg::ref_ptr<osg::Node> mMarker[3]; osg::ref_ptr<osg::Node> mMarker[3];
int mSubMode; int mSubMode;
float mMarkerTransparency;
/// Not implemented /// Not implemented
Object (const Object&); Object (const Object&);
@ -121,6 +123,9 @@ namespace CSVRender
osg::ref_ptr<osg::Node> makeMoveOrScaleMarker (int axis); osg::ref_ptr<osg::Node> makeMoveOrScaleMarker (int axis);
osg::ref_ptr<osg::Node> makeRotateMarker (int axis); osg::ref_ptr<osg::Node> makeRotateMarker (int axis);
/// Sets up a stateset with properties common to all marker types.
void setupCommonMarkerState(osg::ref_ptr<osg::Geometry> geometry);
osg::Vec3f getMarkerPosition (float x, float y, float z, int axis); osg::Vec3f getMarkerPosition (float x, float y, float z, int axis);
public: public:
@ -179,6 +184,8 @@ namespace CSVRender
/// Set override scale /// Set override scale
void setScale (float scale); void setScale (float scale);
void setMarkerTransparency(float value);
/// Apply override changes via command and end edit mode /// Apply override changes via command and end edit mode
void apply (CSMWorld::CommandMacro& commands); void apply (CSMWorld::CommandMacro& commands);

@ -51,6 +51,7 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
, mToolTipPos (-1, -1) , mToolTipPos (-1, -1)
, mShowToolTips(false) , mShowToolTips(false)
, mToolTipDelay(0) , mToolTipDelay(0)
, mInConstructor(true)
{ {
setAcceptDrops(true); setAcceptDrops(true);
@ -114,6 +115,8 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg
CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this);
connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag())); connect(abortShortcut, SIGNAL(activated()), this, SLOT(abortDrag()));
mInConstructor = false;
} }
CSVRender::WorldspaceWidget::~WorldspaceWidget () CSVRender::WorldspaceWidget::~WorldspaceWidget ()
@ -128,6 +131,17 @@ void CSVRender::WorldspaceWidget::settingChanged (const CSMPrefs::Setting *setti
mDragWheelFactor = setting->toDouble(); mDragWheelFactor = setting->toDouble();
else if (*setting=="3D Scene Input/drag-shift-factor") else if (*setting=="3D Scene Input/drag-shift-factor")
mDragShiftFactor = setting->toDouble(); mDragShiftFactor = setting->toDouble();
else if (*setting=="3D Scene Input/object-marker-alpha" && !mInConstructor)
{
float alpha = setting->toDouble();
// getSelection is virtual, thus this can not be called from the constructor
auto selection = getSelection(Mask_Reference);
for (osg::ref_ptr<TagBase> tag : selection)
{
if (auto objTag = dynamic_cast<ObjectTag*>(tag.get()))
objTag->mObject->setMarkerTransparency(alpha);
}
}
else if (*setting=="Tooltips/scene-delay") else if (*setting=="Tooltips/scene-delay")
mToolTipDelay = setting->toInt(); mToolTipDelay = setting->toInt();
else if (*setting=="Tooltips/scene") else if (*setting=="Tooltips/scene")

@ -65,6 +65,7 @@ namespace CSVRender
QPoint mToolTipPos; QPoint mToolTipPos;
bool mShowToolTips; bool mShowToolTips;
int mToolTipDelay; int mToolTipDelay;
bool mInConstructor;
public: public:

@ -201,6 +201,9 @@ namespace MWBase
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0; virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects) = 0;
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects) = 0; virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects) = 0;
/// Check if there are actors in selected range
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius) = 0;
///Returns the list of actors which are siding with the given actor in fights ///Returns the list of actors which are siding with the given actor in fights
/**ie AiFollow or AiEscort is active and the target is the actor **/ /**ie AiFollow or AiEscort is active and the target is the actor **/
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0; virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor) = 0;

@ -8,7 +8,9 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
#include "widgets.hpp" #include "widgets.hpp"
@ -70,8 +72,14 @@ namespace MWGui
updateBirths(); updateBirths();
updateSpells(); updateSpells();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mBirthList);
}
// Show the current birthsign by default
const std::string &signId =
MWBase::Environment::get().getWorld()->getPlayer().getBirthSign();
if (!signId.empty())
setBirthId(signId);
}
void BirthDialog::setBirthId(const std::string &birthId) void BirthDialog::setBirthId(const std::string &birthId)
{ {

@ -7,6 +7,9 @@
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwmechanics/actorutil.hpp"
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "tooltips.hpp" #include "tooltips.hpp"
@ -131,8 +134,16 @@ namespace MWGui
updateClasses(); updateClasses();
updateStats(); updateStats();
MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList); MWBase::Environment::get().getWindowManager()->setKeyFocusWidget(mClassList);
}
// Show the current class by default
MWWorld::Ptr player = MWMechanics::getPlayer();
const std::string &classId =
player.get<ESM::NPC>()->mBase->mClass;
if (!classId.empty())
setClassId(classId);
}
void PickClassDialog::setClassId(const std::string &classId) void PickClassDialog::setClassId(const std::string &classId)
{ {

@ -244,7 +244,10 @@ namespace MWInput
if (mControlSwitch["playercontrols"]) if (mControlSwitch["playercontrols"])
{ {
if (action == A_Use) if (action == A_Use)
mPlayer->setAttackingOrSpell(currentValue != 0); {
MWMechanics::DrawState_ state = MWBase::Environment::get().getWorld()->getPlayer().getDrawState();
mPlayer->setAttackingOrSpell(currentValue != 0 && state != MWMechanics::DrawState_Nothing);
}
else if (action == A_Jump) else if (action == A_Jump)
mAttemptJump = (currentValue == 1.0 && previousValue == 0.0); mAttemptJump = (currentValue == 1.0 && previousValue == 0.0);
} }

@ -1847,6 +1847,17 @@ namespace MWMechanics
} }
} }
bool Actors::isAnyObjectInRange(const osg::Vec3f& position, float radius)
{
for (PtrActorMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter)
{
if ((iter->first.getRefData().getPosition().asVec3() - position).length2() <= radius*radius)
return true;
}
return false;
}
std::list<MWWorld::Ptr> Actors::getActorsSidingWith(const MWWorld::Ptr& actor) std::list<MWWorld::Ptr> Actors::getActorsSidingWith(const MWWorld::Ptr& actor)
{ {
std::list<MWWorld::Ptr> list; std::list<MWWorld::Ptr> list;

@ -125,15 +125,17 @@ namespace MWMechanics
bool isRunning(const MWWorld::Ptr& ptr); bool isRunning(const MWWorld::Ptr& ptr);
bool isSneaking(const MWWorld::Ptr& ptr); bool isSneaking(const MWWorld::Ptr& ptr);
void forceStateUpdate(const MWWorld::Ptr &ptr); void forceStateUpdate(const MWWorld::Ptr &ptr);
bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false); bool playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number, bool persist=false);
void skipAnimation(const MWWorld::Ptr& ptr); void skipAnimation(const MWWorld::Ptr& ptr);
bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName); bool checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName);
void persistAnimationStates(); void persistAnimationStates();
void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out); void getObjectsInRange(const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& out);
bool isAnyObjectInRange(const osg::Vec3f& position, float radius);
void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId); void cleanupSummonedCreature (CreatureStats& casterStats, int creatureActorId);
///Returns the list of actors which are siding with the given actor in fights ///Returns the list of actors which are siding with the given actor in fights

@ -26,6 +26,7 @@
#include "../mwrender/animation.hpp" #include "../mwrender/animation.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "steering.hpp" #include "steering.hpp"
#include "movement.hpp" #include "movement.hpp"
@ -440,7 +441,7 @@ namespace MWMechanics
int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos); int closestPointIndex = PathFinder::GetClosestPoint(pathgrid, localPos);
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++) for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
{ {
if (i != closestPointIndex && storage.mCell->isPointConnected(closestPointIndex, i)) if (i != closestPointIndex && getPathGridGraph(storage.mCell).isPointConnected(closestPointIndex, i))
{ {
points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]); points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]);
} }

@ -14,6 +14,7 @@
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "../mwworld/inventorystore.hpp" #include "../mwworld/inventorystore.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "movement.hpp" #include "movement.hpp"
#include "steering.hpp" #include "steering.hpp"
@ -100,14 +101,23 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const ESM::Pathgr
{ {
bool wasShortcutting = mIsShortcutting; bool wasShortcutting = mIsShortcutting;
bool destInLOS = false; bool destInLOS = false;
if (getTypeId() != TypeIdWander) // prohibit shortcuts for AiWander
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS); // try to shortcut first const MWWorld::Class& actorClass = actor.getClass();
MWBase::World* world = MWBase::Environment::get().getWorld();
// check if actor can move along z-axis
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| world->isFlying(actor);
// Prohibit shortcuts for AiWander, if the actor can not move in 3 dimensions.
if (actorCanMoveByZ || getTypeId() != TypeIdWander)
mIsShortcutting = shortcutPath(start, dest, actor, &destInLOS, actorCanMoveByZ); // try to shortcut first
if (!mIsShortcutting) if (!mIsShortcutting)
{ {
if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path
{ {
mPathFinder.buildSyncedPath(start, dest, actor.getCell()); mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
mRotateOnTheRunChecks = 3; mRotateOnTheRunChecks = 3;
// give priority to go directly on target if there is minimal opportunity // give priority to go directly on target if there is minimal opportunity
@ -220,20 +230,23 @@ void MWMechanics::AiPackage::evadeObstacles(const MWWorld::Ptr& actor, float dur
} }
} }
bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS) const MWMechanics::PathgridGraph& MWMechanics::AiPackage::getPathGridGraph(const MWWorld::CellStore *cell)
{ {
const MWWorld::Class& actorClass = actor.getClass(); const ESM::CellId& id = cell->getCell()->getCellId();
MWBase::World* world = MWBase::Environment::get().getWorld(); // static cache is OK for now, pathgrids can never change during runtime
typedef std::map<ESM::CellId, std::unique_ptr<MWMechanics::PathgridGraph> > CacheMap;
// check if actor can move along z-axis static CacheMap cache;
bool actorCanMoveByZ = (actorClass.canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor)) CacheMap::iterator found = cache.find(id);
|| world->isFlying(actor); if (found == cache.end())
{
// don't use pathgrid when actor can move in 3 dimensions cache.insert(std::make_pair(id, std::unique_ptr<MWMechanics::PathgridGraph>(new MWMechanics::PathgridGraph(cell))));
bool isPathClear = actorCanMoveByZ; }
return *cache[id].get();
}
if (!isPathClear bool MWMechanics::AiPackage::shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear)
&& (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)) {
if (!mShortcutProhibited || (PathFinder::MakeOsgVec3(mShortcutFailPos) - PathFinder::MakeOsgVec3(startPoint)).length() >= PATHFIND_SHORTCUT_RETRY_DIST)
{ {
// check if target is clearly visible // check if target is clearly visible
isPathClear = !MWBase::Environment::get().getWorld()->castRay( isPathClear = !MWBase::Environment::get().getWorld()->castRay(

@ -27,6 +27,7 @@ namespace MWMechanics
const float AI_REACTION_TIME = 0.25f; const float AI_REACTION_TIME = 0.25f;
class CharacterController; class CharacterController;
class PathgridGraph;
/// \brief Base class for AI packages /// \brief Base class for AI packages
class AiPackage class AiPackage
@ -110,7 +111,7 @@ namespace MWMechanics
/// If a shortcut is possible then path will be cleared and filled with the destination point. /// If a shortcut is possible then path will be cleared and filled with the destination point.
/// \param destInLOS If not NULL function will return ray cast check result /// \param destInLOS If not NULL function will return ray cast check result
/// \return If can shortcut the path /// \return If can shortcut the path
bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS); bool shortcutPath(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor, bool *destInLOS, bool isPathClear);
/// Check if the way to the destination is clear, taking into account actor speed /// Check if the way to the destination is clear, taking into account actor speed
bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor); bool checkWayIsClearForActor(const ESM::Pathgrid::Point& startPoint, const ESM::Pathgrid::Point& endPoint, const MWWorld::Ptr& actor);
@ -119,6 +120,8 @@ namespace MWMechanics
void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos); void evadeObstacles(const MWWorld::Ptr& actor, float duration, const ESM::Position& pos);
const PathgridGraph& getPathGridGraph(const MWWorld::CellStore* cell);
// TODO: all this does not belong here, move into temporary storage // TODO: all this does not belong here, move into temporary storage
PathFinder mPathFinder; PathFinder mPathFinder;
ObstacleCheck mObstacleCheck; ObstacleCheck mObstacleCheck;

@ -17,6 +17,7 @@
#include "../mwworld/esmstore.hpp" #include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "pathgrid.hpp"
#include "creaturestats.hpp" #include "creaturestats.hpp"
#include "steering.hpp" #include "steering.hpp"
#include "movement.hpp" #include "movement.hpp"
@ -217,7 +218,7 @@ namespace MWMechanics
ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination)); ESM::Pathgrid::Point dest(PathFinder::MakePathgridPoint(mDestination));
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
mPathFinder.buildSyncedPath(start, dest, actor.getCell()); mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
storage.setState(Wander_Walking); storage.setState(Wander_Walking);
@ -264,9 +265,20 @@ namespace MWMechanics
getAllowedNodes(actor, currentCell->getCell(), storage); getAllowedNodes(actor, currentCell->getCell(), storage);
} }
bool actorCanMoveByZ = (actor.getClass().canSwim(actor) && MWBase::Environment::get().getWorld()->isSwimming(actor))
|| MWBase::Environment::get().getWorld()->isFlying(actor);
if(actorCanMoveByZ && mDistance > 0) {
// Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 92 && storage.mState != Wander_Walking) {
wanderNearStart(actor, storage, mDistance);
}
storage.mCanWanderAlongPathGrid = false;
}
// If the package has a wander distance but no pathgrid is available, // If the package has a wander distance but no pathgrid is available,
// randomly idle or wander near spawn point // randomly idle or wander near spawn point
if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) { else if(storage.mAllowedNodes.empty() && mDistance > 0 && !storage.mIsWanderingManually) {
// Typically want to idle for a short time before the next wander // Typically want to idle for a short time before the next wander
if (Misc::Rng::rollDice(100) >= 96) { if (Misc::Rng::rollDice(100) >= 96) {
wanderNearStart(actor, storage, mDistance); wanderNearStart(actor, storage, mDistance);
@ -349,7 +361,7 @@ namespace MWMechanics
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(pos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell()); mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
{ {
@ -372,7 +384,7 @@ namespace MWMechanics
do { do {
// Determine a random location within radius of original position // Determine a random location within radius of original position
const float pi = 3.14159265359f; const float pi = 3.14159265359f;
const float wanderRadius = Misc::Rng::rollClosedProbability() * wanderDistance; const float wanderRadius = (0.2f + Misc::Rng::rollClosedProbability() * 0.8f) * wanderDistance;
const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi; const float randomDirection = Misc::Rng::rollClosedProbability() * 2.0f * pi;
const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection); const float destinationX = mInitialActorPosition.x() + wanderRadius * std::cos(randomDirection);
const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection); const float destinationY = mInitialActorPosition.y() + wanderRadius * std::sin(randomDirection);
@ -383,7 +395,7 @@ namespace MWMechanics
// Check if land creature will walk onto water or if water creature will swim onto land // Check if land creature will walk onto water or if water creature will swim onto land
if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) || if ((!isWaterCreature && !destinationIsAtWater(actor, mDestination)) ||
(isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) { (isWaterCreature && !destinationThroughGround(currentPositionVec3f, mDestination))) {
mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell()); mPathFinder.buildSyncedPath(currentPosition, destinationPosition, actor.getCell(), getPathGridGraph(actor.getCell()));
mPathFinder.addPointToPath(destinationPosition); mPathFinder.addPointToPath(destinationPosition);
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
@ -660,13 +672,14 @@ namespace MWMechanics
{ {
unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size()); unsigned int randNode = Misc::Rng::rollDice(storage.mAllowedNodes.size());
ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]); ESM::Pathgrid::Point dest(storage.mAllowedNodes[randNode]);
ToWorldCoordinates(dest, storage.mCell->getCell()); ToWorldCoordinates(dest, storage.mCell->getCell());
// actor position is already in world coordinates // actor position is already in world coordinates
ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos)); ESM::Pathgrid::Point start(PathFinder::MakePathgridPoint(actorPos));
// don't take shortcuts for wandering // don't take shortcuts for wandering
mPathFinder.buildSyncedPath(start, dest, actor.getCell()); mPathFinder.buildSyncedPath(start, dest, actor.getCell(), getPathGridGraph(actor.getCell()));
if (mPathFinder.isPathConstructed()) if (mPathFinder.isPathConstructed())
{ {
@ -799,20 +812,80 @@ namespace MWMechanics
int index = Misc::Rng::rollDice(storage.mAllowedNodes.size()); int index = Misc::Rng::rollDice(storage.mAllowedNodes.size());
ESM::Pathgrid::Point dest = storage.mAllowedNodes[index]; ESM::Pathgrid::Point dest = storage.mAllowedNodes[index];
state.moveIn(new AiWanderStorage()); ESM::Pathgrid::Point worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
bool isPathGridOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
// add offset only if the selected pathgrid is occupied by another actor
if (isPathGridOccupied)
{
ESM::Pathgrid::PointList points;
getNeighbouringNodes(dest, actor.getCell(), points);
// there are no neighbouring nodes, nowhere to move
if (points.empty())
return;
int initialSize = points.size();
bool isOccupied = false;
// AI will try to move the NPC towards every neighboring node until suitable place will be found
for (int i = 0; i < initialSize; i++)
{
int randomIndex = Misc::Rng::rollDice(points.size());
ESM::Pathgrid::Point connDest = points[randomIndex];
// add an offset towards random neighboring node
osg::Vec3f dir = PathFinder::MakeOsgVec3(connDest) - PathFinder::MakeOsgVec3(dest);
float length = dir.length();
dir.normalize();
for (int j = 1; j <= 3; j++)
{
// move for 5-15% towards random neighboring node
dest = PathFinder::MakePathgridPoint(PathFinder::MakeOsgVec3(dest) + dir * (j * 5 * length / 100.f));
worldDest = dest;
ToWorldCoordinates(worldDest, actor.getCell()->getCell());
isOccupied = MWBase::Environment::get().getMechanicsManager()->isAnyActorInRange(PathFinder::MakeOsgVec3(worldDest), 60);
if (!isOccupied)
break;
}
if (!isOccupied)
break;
// Will try an another neighboring node
points.erase(points.begin()+randomIndex);
}
// there is no free space, nowhere to move
if (isOccupied)
return;
}
// place above to prevent moving inside objects, e.g. stairs, because a vector between pathgrids can be underground.
// Adding 20 in adjustPosition() is not enough.
dest.mZ += 60;
dest.mX += OffsetToPreventOvercrowding();
dest.mY += OffsetToPreventOvercrowding();
ToWorldCoordinates(dest, actor.getCell()->getCell()); ToWorldCoordinates(dest, actor.getCell()->getCell());
state.moveIn(new AiWanderStorage());
MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX), MWBase::Environment::get().getWorld()->moveObject(actor, static_cast<float>(dest.mX),
static_cast<float>(dest.mY), static_cast<float>(dest.mZ)); static_cast<float>(dest.mY), static_cast<float>(dest.mZ));
actor.getClass().adjustPosition(actor, false); actor.getClass().adjustPosition(actor, false);
} }
int AiWander::OffsetToPreventOvercrowding() void AiWander::getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points)
{ {
return static_cast<int>(20 * (Misc::Rng::rollProbability() * 2.0f - 1.0f)); const ESM::Pathgrid *pathgrid =
MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*currentCell->getCell());
int index = PathFinder::GetClosestPoint(pathgrid, PathFinder::MakeOsgVec3(dest));
getPathGridGraph(currentCell).getNeighbouringPoints(index, points);
} }
void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage) void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage)
@ -853,7 +926,7 @@ namespace MWMechanics
{ {
osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter])); osg::Vec3f nodePos(PathFinder::MakeOsgVec3(pathgrid->mPoints[counter]));
if((npcPos - nodePos).length2() <= mDistance * mDistance && if((npcPos - nodePos).length2() <= mDistance * mDistance &&
cellStore->isPointConnected(closestPointIndex, counter)) getPathGridGraph(cellStore).isPointConnected(closestPointIndex, counter))
{ {
storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]); storage.mAllowedNodes.push_back(pathgrid->mPoints[counter]);
pointIndex = counter; pointIndex = counter;

@ -104,6 +104,8 @@ namespace MWMechanics
bool mHasDestination; bool mHasDestination;
osg::Vec3f mDestination; osg::Vec3f mDestination;
void getNeighbouringNodes(ESM::Pathgrid::Point dest, const MWWorld::CellStore* currentCell, ESM::Pathgrid::PointList& points);
void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage); void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell, AiWanderStorage& storage);
void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder); void trimAllowedNodes(std::vector<ESM::Pathgrid::Point>& nodes, const PathFinder& pathfinder);

@ -1338,7 +1338,16 @@ bool CharacterController::updateWeaponState()
bool animPlaying; bool animPlaying;
if(mAttackingOrSpell) if(mAttackingOrSpell)
{ {
mIdleState = CharState_None; MWWorld::Ptr player = getPlayer();
// We should reset player's idle animation in the first-person mode.
if (mPtr == player && MWBase::Environment::get().getWorld()->isFirstPerson())
mIdleState = CharState_None;
// In other cases we should not break swim and sneak animations
if (mIdleState != CharState_IdleSneak && mIdleState != CharState_IdleSwim)
mIdleState = CharState_None;
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block)) if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{ {
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr); MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
@ -1348,7 +1357,7 @@ bool CharacterController::updateWeaponState()
// Unset casting flag, otherwise pressing the mouse button down would // Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation // continue casting every frame if there is no animation
mAttackingOrSpell = false; mAttackingOrSpell = false;
if (mPtr == getPlayer()) if (mPtr == player)
{ {
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false); MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
} }
@ -1358,7 +1367,7 @@ bool CharacterController::updateWeaponState()
// For the player, set the spell we want to cast // For the player, set the spell we want to cast
// This has to be done at the start of the casting animation, // This has to be done at the start of the casting animation,
// *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation) // *not* when selecting a spell in the GUI (otherwise you could change the spell mid-animation)
if (mPtr == getPlayer()) if (mPtr == player)
{ {
std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell(); std::string selectedSpell = MWBase::Environment::get().getWindowManager()->getSelectedSpell();
stats.getSpells().setSelectedSpell(selectedSpell); stats.getSpells().setSelectedSpell(selectedSpell);
@ -1389,7 +1398,7 @@ bool CharacterController::updateWeaponState()
MWMechanics::CastSpell cast(mPtr, NULL); MWMechanics::CastSpell cast(mPtr, NULL);
cast.playSpellCastingEffects(spellid); cast.playSpellCastingEffects(spellid);
const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid); const ESM::Spell *spell = store.get<ESM::Spell>().find(spellid);
const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back(); const ESM::ENAMstruct &lastEffect = spell->mEffects.mList.back();
const ESM::MagicEffect *effect; const ESM::MagicEffect *effect;
@ -1728,7 +1737,7 @@ void CharacterController::update(float duration)
float speed = 0.f; float speed = 0.f;
updateMagicEffects(); updateMagicEffects();
if (isKnockedOut()) if (isKnockedOut())
mTimeUntilWake -= duration; mTimeUntilWake -= duration;

@ -1635,6 +1635,11 @@ namespace MWMechanics
mActors.getObjectsInRange(position, radius, objects); mActors.getObjectsInRange(position, radius, objects);
} }
bool MechanicsManager::isAnyActorInRange(const osg::Vec3f &position, float radius)
{
return mActors.isAnyObjectInRange(position, radius);
}
std::list<MWWorld::Ptr> MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor) std::list<MWWorld::Ptr> MechanicsManager::getActorsSidingWith(const MWWorld::Ptr& actor)
{ {
return mActors.getActorsSidingWith(actor); return mActors.getActorsSidingWith(actor);

@ -168,6 +168,9 @@ namespace MWMechanics
virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects); virtual void getObjectsInRange (const osg::Vec3f& position, float radius, std::vector<MWWorld::Ptr>& objects);
virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects); virtual void getActorsInRange(const osg::Vec3f &position, float radius, std::vector<MWWorld::Ptr> &objects);
/// Check if there are actors in selected range
virtual bool isAnyActorInRange(const osg::Vec3f &position, float radius);
virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor); virtual std::list<MWWorld::Ptr> getActorsSidingWith(const MWWorld::Ptr& actor);
virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor); virtual std::list<MWWorld::Ptr> getActorsFollowing(const MWWorld::Ptr& actor);
virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor); virtual std::list<int> getActorsFollowingIndices(const MWWorld::Ptr& actor);

@ -5,16 +5,16 @@
#include "../mwbase/world.hpp" #include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp" #include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/cellstore.hpp" #include "../mwworld/cellstore.hpp"
#include "pathgrid.hpp"
#include "coordinateconverter.hpp" #include "coordinateconverter.hpp"
namespace namespace
{ {
// Chooses a reachable end pathgrid point. start is assumed reachable. // Chooses a reachable end pathgrid point. start is assumed reachable.
std::pair<int, bool> getClosestReachablePoint(const ESM::Pathgrid* grid, std::pair<int, bool> getClosestReachablePoint(const ESM::Pathgrid* grid,
const MWWorld::CellStore *cell, const MWMechanics::PathgridGraph *graph,
const osg::Vec3f& pos, int start) const osg::Vec3f& pos, int start)
{ {
assert(grid && !grid->mPoints.empty()); assert(grid && !grid->mPoints.empty());
@ -31,7 +31,7 @@ namespace
if (potentialDistBetween < closestDistanceReachable) if (potentialDistBetween < closestDistanceReachable)
{ {
// found a closer one // found a closer one
if (cell->isPointConnected(start, counter)) if (graph->isPointConnected(start, counter))
{ {
closestDistanceReachable = potentialDistBetween; closestDistanceReachable = potentialDistBetween;
closestReachableIndex = counter; closestReachableIndex = counter;
@ -45,7 +45,7 @@ namespace
} }
// post-condition: start and endpoint must be connected // post-condition: start and endpoint must be connected
assert(cell->isPointConnected(start, closestReachableIndex)); assert(graph->isPointConnected(start, closestReachableIndex));
// AiWander has logic that depends on whether a path was created, deleting // AiWander has logic that depends on whether a path was created, deleting
// allowed nodes if not. Hence a path needs to be created even if the start // allowed nodes if not. Hence a path needs to be created even if the start
@ -120,8 +120,8 @@ namespace MWMechanics
} }
PathFinder::PathFinder() PathFinder::PathFinder()
: mPathgrid(NULL), : mPathgrid(NULL)
mCell(NULL) , mCell(NULL)
{ {
} }
@ -169,14 +169,15 @@ namespace MWMechanics
*/ */
void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint, void PathFinder::buildPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell) const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph)
{ {
mPath.clear(); mPath.clear();
// TODO: consider removing mCell / mPathgrid in favor of mPathgridGraph
if(mCell != cell || !mPathgrid) if(mCell != cell || !mPathgrid)
{ {
mCell = cell; mCell = cell;
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*mCell->getCell()); mPathgrid = pathgridGraph.getPathgrid();
} }
// Refer to AiWander reseach topic on openmw forums for some background. // Refer to AiWander reseach topic on openmw forums for some background.
@ -200,7 +201,7 @@ namespace MWMechanics
int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords); int startNode = GetClosestPoint(mPathgrid, startPointInLocalCoords);
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint)); osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, cell, std::pair<int, bool> endNode = getClosestReachablePoint(mPathgrid, &pathgridGraph,
endPointInLocalCoords, endPointInLocalCoords,
startNode); startNode);
@ -228,7 +229,7 @@ namespace MWMechanics
} }
else else
{ {
mPath = mCell->aStarSearch(startNode, endNode.first); mPath = pathgridGraph.aStarSearch(startNode, endNode.first);
// convert supplied path to world coordinates // convert supplied path to world coordinates
for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter) for (std::list<ESM::Pathgrid::Point>::iterator iter(mPath.begin()); iter != mPath.end(); ++iter)
@ -301,18 +302,18 @@ namespace MWMechanics
// see header for the rationale // see header for the rationale
void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint, void PathFinder::buildSyncedPath(const ESM::Pathgrid::Point &startPoint,
const ESM::Pathgrid::Point &endPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell) const MWWorld::CellStore* cell, const MWMechanics::PathgridGraph& pathgridGraph)
{ {
if (mPath.size() < 2) if (mPath.size() < 2)
{ {
// if path has one point, then it's the destination. // if path has one point, then it's the destination.
// don't need to worry about bad path for this case // don't need to worry about bad path for this case
buildPath(startPoint, endPoint, cell); buildPath(startPoint, endPoint, cell, pathgridGraph);
} }
else else
{ {
const ESM::Pathgrid::Point oldStart(*getPath().begin()); const ESM::Pathgrid::Point oldStart(*getPath().begin());
buildPath(startPoint, endPoint, cell); buildPath(startPoint, endPoint, cell, pathgridGraph);
if (mPath.size() >= 2) if (mPath.size() >= 2)
{ {
// if 2nd waypoint of new path == 1st waypoint of old, // if 2nd waypoint of new path == 1st waypoint of old,

@ -14,6 +14,8 @@ namespace MWWorld
namespace MWMechanics namespace MWMechanics
{ {
class PathgridGraph;
float distance(const ESM::Pathgrid::Point& point, float x, float y, float); float distance(const ESM::Pathgrid::Point& point, float x, float y, float);
float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b); float distance(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b);
float getZAngleToDir(const osg::Vec3f& dir); float getZAngleToDir(const osg::Vec3f& dir);
@ -54,7 +56,7 @@ namespace MWMechanics
void clearPath(); void clearPath();
void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell); const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
bool checkPathCompleted(float x, float y, float tolerance = PathTolerance); bool checkPathCompleted(float x, float y, float tolerance = PathTolerance);
///< \Returns true if we are within \a tolerance units of the last path point. ///< \Returns true if we are within \a tolerance units of the last path point.
@ -89,7 +91,7 @@ namespace MWMechanics
Which results in NPC "running in a circle" back to the just passed waypoint. Which results in NPC "running in a circle" back to the just passed waypoint.
*/ */
void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, void buildSyncedPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint,
const MWWorld::CellStore* cell); const MWWorld::CellStore* cell, const PathgridGraph& pathgridGraph);
void addPointToPath(const ESM::Pathgrid::Point &point) void addPointToPath(const ESM::Pathgrid::Point &point)
{ {

@ -49,7 +49,7 @@ namespace
namespace MWMechanics namespace MWMechanics
{ {
PathgridGraph::PathgridGraph() PathgridGraph::PathgridGraph(const MWWorld::CellStore *cell)
: mCell(NULL) : mCell(NULL)
, mPathgrid(NULL) , mPathgrid(NULL)
, mIsExterior(0) , mIsExterior(0)
@ -58,6 +58,7 @@ namespace MWMechanics
, mSCCId(0) , mSCCId(0)
, mSCCIndex(0) , mSCCIndex(0)
{ {
load(cell);
} }
/* /*
@ -130,6 +131,11 @@ namespace MWMechanics
return true; return true;
} }
const ESM::Pathgrid *PathgridGraph::getPathgrid() const
{
return mPathgrid;
}
// v is the pathgrid point index (some call them vertices) // v is the pathgrid point index (some call them vertices)
void PathgridGraph::recursiveStrongConnect(int v) void PathgridGraph::recursiveStrongConnect(int v)
{ {
@ -214,6 +220,16 @@ namespace MWMechanics
return (mGraph[start].componentId == mGraph[end].componentId); return (mGraph[start].componentId == mGraph[end].componentId);
} }
void PathgridGraph::getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const
{
for(int i = 0; i < static_cast<int> (mGraph[index].edges.size()); i++)
{
int neighbourIndex = mGraph[index].edges[i].index;
if (neighbourIndex != index)
nodes.push_back(mPathgrid->mPoints[neighbourIndex]);
}
}
/* /*
* NOTE: Based on buildPath2(), please check git history if interested * NOTE: Based on buildPath2(), please check git history if interested
* Should consider using a 3rd party library version (e.g. boost) * Should consider using a 3rd party library version (e.g. boost)

@ -20,14 +20,19 @@ namespace MWMechanics
class PathgridGraph class PathgridGraph
{ {
public: public:
PathgridGraph(); PathgridGraph(const MWWorld::CellStore* cell);
bool load(const MWWorld::CellStore *cell); bool load(const MWWorld::CellStore *cell);
const ESM::Pathgrid* getPathgrid() const;
// returns true if end point is strongly connected (i.e. reachable // returns true if end point is strongly connected (i.e. reachable
// from start point) both start and end are pathgrid point indexes // from start point) both start and end are pathgrid point indexes
bool isPointConnected(const int start, const int end) const; bool isPointConnected(const int start, const int end) const;
// get neighbouring nodes for index node and put them to "nodes" vector
void getNeighbouringPoints(const int index, ESM::Pathgrid::PointList &nodes) const;
// the input parameters are pathgrid point indexes // the input parameters are pathgrid point indexes
// the output list is in local (internal cells) or world (external // the output list is in local (internal cells) or world (external
// cells) coordinates // cells) coordinates

@ -633,10 +633,6 @@ namespace MWWorld
loadRefs (); loadRefs ();
mState = State_Loaded; mState = State_Loaded;
// TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else.
// In a simple test, loading the graph for all cells in MW + expansions took 200 ms
mPathgridGraph.load(this);
} }
} }
@ -1125,16 +1121,6 @@ namespace MWWorld
return !(left==right); return !(left==right);
} }
bool CellStore::isPointConnected(const int start, const int end) const
{
return mPathgridGraph.isPointConnected(start, end);
}
std::list<ESM::Pathgrid::Point> CellStore::aStarSearch(const int start, const int end) const
{
return mPathgridGraph.aStarSearch(start, end);
}
void CellStore::setFog(ESM::FogState *fog) void CellStore::setFog(ESM::FogState *fog)
{ {
mFogState.reset(fog); mFogState.reset(fog);

@ -32,13 +32,12 @@
#include <components/esm/loadmisc.hpp> #include <components/esm/loadmisc.hpp>
#include <components/esm/loadbody.hpp> #include <components/esm/loadbody.hpp>
#include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld
#include "timestamp.hpp" #include "timestamp.hpp"
#include "ptr.hpp" #include "ptr.hpp"
namespace ESM namespace ESM
{ {
struct Cell;
struct CellState; struct CellState;
struct FogState; struct FogState;
struct CellId; struct CellId;
@ -436,10 +435,6 @@ namespace MWWorld
void respawn (); void respawn ();
///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded. ///< Check mLastRespawn and respawn references if necessary. This is a no-op if the cell is not loaded.
bool isPointConnected(const int start, const int end) const;
std::list<ESM::Pathgrid::Point> aStarSearch(const int start, const int end) const;
private: private:
/// Run through references and store IDs /// Run through references and store IDs
@ -451,8 +446,6 @@ namespace MWWorld
///< Make case-adjustments to \a ref and insert it into the respective container. ///< Make case-adjustments to \a ref and insert it into the respective container.
/// ///
/// Invalid \a ref objects are silently dropped. /// Invalid \a ref objects are silently dropped.
MWMechanics::PathgridGraph mPathgridGraph;
}; };
template<> template<>

Loading…
Cancel
Save