mirror of
https://github.com/TES3MP/openmw-tes3mp.git
synced 2025-01-30 16:45:33 +00:00
Merge remote-tracking branch 'upstream/master' into why_are_the_christmas_lights_still_up
This commit is contained in:
commit
c5ea966f24
18 changed files with 240 additions and 47 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -84,13 +84,3 @@ moc_*.cxx
|
||||||
*.[ao]
|
*.[ao]
|
||||||
*.so
|
*.so
|
||||||
venv/
|
venv/
|
||||||
|
|
||||||
## recastnavigation unused files
|
|
||||||
extern/recastnavigation/.travis.yml
|
|
||||||
extern/recastnavigation/CONTRIBUTING.md
|
|
||||||
extern/recastnavigation/Docs/
|
|
||||||
extern/recastnavigation/Doxyfile
|
|
||||||
extern/recastnavigation/README.md
|
|
||||||
extern/recastnavigation/RecastDemo/
|
|
||||||
extern/recastnavigation/Tests/
|
|
||||||
extern/recastnavigation/appveyor.yml
|
|
||||||
|
|
|
@ -103,6 +103,7 @@
|
||||||
Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod.
|
Bug #5838: Local map and other menus become blank in some locations while playing Wizards' Islands mod.
|
||||||
Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior )
|
Bug #5840: GetSoundPlaying "Health Damage" doesn't play when NPC hits target with shield effect ( vanilla engine behavior )
|
||||||
Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0
|
Bug #5841: Can't Cast Zero Cost Spells When Magicka is < 0
|
||||||
|
Bug #5871: The console appears if you type the Russian letter "Ё" in the name of the enchantment
|
||||||
Feature #390: 3rd person look "over the shoulder"
|
Feature #390: 3rd person look "over the shoulder"
|
||||||
Feature #1536: Show more information about level on menu
|
Feature #1536: Show more information about level on menu
|
||||||
Feature #2386: Distant Statics in the form of Object Paging
|
Feature #2386: Distant Statics in the form of Object Paging
|
||||||
|
|
|
@ -233,7 +233,11 @@ if(OSG_STATIC)
|
||||||
add_library(openmw_cs_osg_plugins INTERFACE)
|
add_library(openmw_cs_osg_plugins INTERFACE)
|
||||||
foreach(_plugin ${USED_OSG_PLUGINS})
|
foreach(_plugin ${USED_OSG_PLUGINS})
|
||||||
string(TOUPPER ${_plugin} _plugin_uc)
|
string(TOUPPER ${_plugin} _plugin_uc)
|
||||||
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
|
if (${_plugin_uc}_LIBRARY MATCHES "[/.]")
|
||||||
|
list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY})
|
||||||
|
else()
|
||||||
|
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
|
||||||
|
endif()
|
||||||
target_link_libraries(openmw_cs_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY})
|
target_link_libraries(openmw_cs_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY})
|
||||||
endforeach()
|
endforeach()
|
||||||
# We use --whole-archive because OSG plugins use registration.
|
# We use --whole-archive because OSG plugins use registration.
|
||||||
|
|
|
@ -148,7 +148,11 @@ if(OSG_STATIC)
|
||||||
add_library(openmw_osg_plugins INTERFACE)
|
add_library(openmw_osg_plugins INTERFACE)
|
||||||
foreach(_plugin ${USED_OSG_PLUGINS})
|
foreach(_plugin ${USED_OSG_PLUGINS})
|
||||||
string(TOUPPER ${_plugin} _plugin_uc)
|
string(TOUPPER ${_plugin} _plugin_uc)
|
||||||
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
|
if (${_plugin_uc}_LIBRARY MATCHES "[/.]")
|
||||||
|
list(APPEND _osg_plugins_static_files ${${_plugin_uc}_LIBRARY})
|
||||||
|
else()
|
||||||
|
list(APPEND _osg_plugins_static_files $<TARGET_FILE:${${_plugin_uc}_LIBRARY}>)
|
||||||
|
endif()
|
||||||
target_link_libraries(openmw_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY})
|
target_link_libraries(openmw_osg_plugins INTERFACE ${${_plugin_uc}_LIBRARY})
|
||||||
endforeach()
|
endforeach()
|
||||||
# We use --whole-archive because OSG plugins use registration.
|
# We use --whole-archive because OSG plugins use registration.
|
||||||
|
|
|
@ -249,7 +249,7 @@ namespace MWGui
|
||||||
size_t length = mCommandLine->getTextCursor() - max;
|
size_t length = mCommandLine->getTextCursor() - max;
|
||||||
if(length > 0)
|
if(length > 0)
|
||||||
{
|
{
|
||||||
std::string text = caption;
|
auto text = caption;
|
||||||
text.erase(max, length);
|
text.erase(max, length);
|
||||||
mCommandLine->setCaption(text);
|
mCommandLine->setCaption(text);
|
||||||
mCommandLine->setTextCursor(max);
|
mCommandLine->setTextCursor(max);
|
||||||
|
@ -259,7 +259,7 @@ namespace MWGui
|
||||||
{
|
{
|
||||||
if(mCommandLine->getTextCursor() > 0)
|
if(mCommandLine->getTextCursor() > 0)
|
||||||
{
|
{
|
||||||
std::string text = mCommandLine->getCaption();
|
auto text = mCommandLine->getCaption();
|
||||||
text.erase(0, mCommandLine->getTextCursor());
|
text.erase(0, mCommandLine->getTextCursor());
|
||||||
mCommandLine->setCaption(text);
|
mCommandLine->setCaption(text);
|
||||||
mCommandLine->setTextCursor(0);
|
mCommandLine->setTextCursor(0);
|
||||||
|
|
|
@ -39,12 +39,14 @@ namespace MWInput
|
||||||
&& MWBase::Environment::get().getWindowManager()->isConsoleMode())
|
&& MWBase::Environment::get().getWindowManager()->isConsoleMode())
|
||||||
SDL_StopTextInput();
|
SDL_StopTextInput();
|
||||||
|
|
||||||
bool consumed = false;
|
bool consumed = SDL_IsTextInputActive() && // Little trick to check if key is printable
|
||||||
|
(!(SDLK_SCANCODE_MASK & arg.keysym.sym) &&
|
||||||
|
(std::isprint(arg.keysym.sym) ||
|
||||||
|
// Don't trust isprint for symbols outside the extended ASCII range
|
||||||
|
(kc == MyGUI::KeyCode::None && arg.keysym.sym > 0xff)));
|
||||||
if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState())
|
if (kc != MyGUI::KeyCode::None && !mBindingsManager->isDetectingBindingState())
|
||||||
{
|
{
|
||||||
consumed = MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat);
|
if (MWBase::Environment::get().getWindowManager()->injectKeyPress(kc, 0, arg.repeat))
|
||||||
if (SDL_IsTextInputActive() && // Little trick to check if key is printable
|
|
||||||
(!(SDLK_SCANCODE_MASK & arg.keysym.sym) && std::isprint(arg.keysym.sym)))
|
|
||||||
consumed = true;
|
consumed = true;
|
||||||
mBindingsManager->setPlayerControlsEnabled(!consumed);
|
mBindingsManager->setPlayerControlsEnabled(!consumed);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <components/misc/mathutil.hpp>
|
#include <components/misc/mathutil.hpp>
|
||||||
|
|
||||||
#include <components/sceneutil/positionattitudetransform.hpp>
|
#include <components/sceneutil/positionattitudetransform.hpp>
|
||||||
|
#include <components/detournavigator/navigator.hpp>
|
||||||
|
|
||||||
#include "../mwphysics/collisiontype.hpp"
|
#include "../mwphysics/collisiontype.hpp"
|
||||||
|
|
||||||
|
@ -127,10 +128,11 @@ namespace MWMechanics
|
||||||
{
|
{
|
||||||
//Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
|
//Update every frame. UpdateLOS uses a timer, so the LOS check does not happen every frame.
|
||||||
updateLOS(actor, target, duration, storage);
|
updateLOS(actor, target, duration, storage);
|
||||||
float targetReachedTolerance = 0.0f;
|
const float targetReachedTolerance = storage.mLOS && !storage.mUseCustomDestination
|
||||||
if (storage.mLOS)
|
? storage.mAttackRange : 0.0f;
|
||||||
targetReachedTolerance = storage.mAttackRange;
|
const osg::Vec3f destination = storage.mUseCustomDestination
|
||||||
const bool is_target_reached = pathTo(actor, target.getRefData().getPosition().asVec3(), duration, targetReachedTolerance);
|
? storage.mCustomDestination : target.getRefData().getPosition().asVec3();
|
||||||
|
const bool is_target_reached = pathTo(actor, destination, duration, targetReachedTolerance);
|
||||||
if (is_target_reached) storage.mReadyToAttack = true;
|
if (is_target_reached) storage.mReadyToAttack = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,8 +234,8 @@ namespace MWMechanics
|
||||||
const ESM::Weapon* weapon = currentAction->getWeapon();
|
const ESM::Weapon* weapon = currentAction->getWeapon();
|
||||||
|
|
||||||
ESM::Position pos = actor.getRefData().getPosition();
|
ESM::Position pos = actor.getRefData().getPosition();
|
||||||
osg::Vec3f vActorPos(pos.asVec3());
|
const osg::Vec3f vActorPos(pos.asVec3());
|
||||||
osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
const osg::Vec3f vTargetPos(target.getRefData().getPosition().asVec3());
|
||||||
|
|
||||||
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
osg::Vec3f vAimDir = MWBase::Environment::get().getWorld()->aimToTarget(actor, target);
|
||||||
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
float distToTarget = MWBase::Environment::get().getWorld()->getHitDistance(actor, target);
|
||||||
|
@ -243,9 +245,7 @@ namespace MWMechanics
|
||||||
if (isRangedCombat)
|
if (isRangedCombat)
|
||||||
{
|
{
|
||||||
// rotate actor taking into account target movement direction and projectile speed
|
// rotate actor taking into account target movement direction and projectile speed
|
||||||
osg::Vec3f& lastTargetPos = storage.mLastTargetPos;
|
vAimDir = AimDirToMovingTarget(actor, target, storage.mLastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
||||||
vAimDir = AimDirToMovingTarget(actor, target, lastTargetPos, AI_REACTION_TIME, (weapon ? weapon->mData.mType : 0), storage.mStrength);
|
|
||||||
lastTargetPos = vTargetPos;
|
|
||||||
|
|
||||||
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
storage.mMovement.mRotation[0] = getXAngleToDir(vAimDir);
|
||||||
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
storage.mMovement.mRotation[2] = getZAngleToDir(vAimDir);
|
||||||
|
@ -256,12 +256,69 @@ namespace MWMechanics
|
||||||
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
storage.mMovement.mRotation[2] = getZAngleToDir((vTargetPos-vActorPos)); // using vAimDir results in spastic movements since the head is animated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage.mLastTargetPos = vTargetPos;
|
||||||
|
|
||||||
if (storage.mReadyToAttack)
|
if (storage.mReadyToAttack)
|
||||||
{
|
{
|
||||||
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
storage.startCombatMove(isRangedCombat, distToTarget, rangeAttack, actor, target);
|
||||||
// start new attack
|
// start new attack
|
||||||
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
storage.startAttackIfReady(actor, characterController, weapon, isRangedCombat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If actor uses custom destination it has to try to rebuild path because environment can change
|
||||||
|
// (door is opened between actor and target) or target position has changed and current custom destination
|
||||||
|
// is not good enough to attack target.
|
||||||
|
if (storage.mCurrentAction->isAttackingOrSpell()
|
||||||
|
&& ((!storage.mReadyToAttack && !mPathFinder.isPathConstructed())
|
||||||
|
|| (storage.mUseCustomDestination && (storage.mCustomDestination - vTargetPos).length() > rangeAttack)))
|
||||||
|
{
|
||||||
|
// Try to build path to the target.
|
||||||
|
const auto halfExtents = MWBase::Environment::get().getWorld()->getPathfindingHalfExtents(actor);
|
||||||
|
const auto navigatorFlags = getNavigatorFlags(actor);
|
||||||
|
const auto areaCosts = getAreaCosts(actor);
|
||||||
|
const auto pathGridGraph = getPathGridGraph(actor.getCell());
|
||||||
|
mPathFinder.buildPath(actor, vActorPos, vTargetPos, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts);
|
||||||
|
|
||||||
|
if (!mPathFinder.isPathConstructed())
|
||||||
|
{
|
||||||
|
// If there is no path, try to find a point on a line from the actor position to target projected
|
||||||
|
// on navmesh to attack the target from there.
|
||||||
|
const MWBase::World* world = MWBase::Environment::get().getWorld();
|
||||||
|
const auto halfExtents = world->getPathfindingHalfExtents(actor);
|
||||||
|
const auto navigator = world->getNavigator();
|
||||||
|
const auto navigatorFlags = getNavigatorFlags(actor);
|
||||||
|
const auto areaCosts = getAreaCosts(actor);
|
||||||
|
const auto hit = navigator->raycast(halfExtents, vActorPos, vTargetPos, navigatorFlags);
|
||||||
|
|
||||||
|
if (hit.has_value() && (*hit - vTargetPos).length() <= rangeAttack)
|
||||||
|
{
|
||||||
|
// If the point is close enough, try to find a path to that point.
|
||||||
|
mPathFinder.buildPath(actor, vActorPos, *hit, actor.getCell(), pathGridGraph, halfExtents, navigatorFlags, areaCosts);
|
||||||
|
if (mPathFinder.isPathConstructed())
|
||||||
|
{
|
||||||
|
// If path to that point is found use it as custom destination.
|
||||||
|
storage.mCustomDestination = *hit;
|
||||||
|
storage.mUseCustomDestination = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mPathFinder.isPathConstructed())
|
||||||
|
{
|
||||||
|
storage.mUseCustomDestination = false;
|
||||||
|
storage.stopAttack();
|
||||||
|
characterController.setAttackingOrSpell(false);
|
||||||
|
currentAction.reset(new ActionFlee());
|
||||||
|
actionCooldown = currentAction->getActionCooldown();
|
||||||
|
storage.startFleeing();
|
||||||
|
MWBase::Environment::get().getDialogueManager()->say(actor, "flee");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
storage.mUseCustomDestination = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,9 @@ namespace MWMechanics
|
||||||
float mFleeBlindRunTimer;
|
float mFleeBlindRunTimer;
|
||||||
ESM::Pathgrid::Point mFleeDest;
|
ESM::Pathgrid::Point mFleeDest;
|
||||||
|
|
||||||
|
bool mUseCustomDestination;
|
||||||
|
osg::Vec3f mCustomDestination;
|
||||||
|
|
||||||
AiCombatStorage():
|
AiCombatStorage():
|
||||||
mAttackCooldown(0.0f),
|
mAttackCooldown(0.0f),
|
||||||
mTimerReact(AI_REACTION_TIME),
|
mTimerReact(AI_REACTION_TIME),
|
||||||
|
@ -74,7 +77,9 @@ namespace MWMechanics
|
||||||
mFleeState(FleeState_None),
|
mFleeState(FleeState_None),
|
||||||
mLOS(false),
|
mLOS(false),
|
||||||
mUpdateLOSTimer(0.0f),
|
mUpdateLOSTimer(0.0f),
|
||||||
mFleeBlindRunTimer(0.0f)
|
mFleeBlindRunTimer(0.0f),
|
||||||
|
mUseCustomDestination(false),
|
||||||
|
mCustomDestination()
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
void startCombatMove(bool isDistantCombat, float distToTarget, float rangeAttack, const MWWorld::Ptr& actor, const MWWorld::Ptr& target);
|
||||||
|
|
|
@ -58,6 +58,15 @@ namespace MWPhysics
|
||||||
mShape->setUseDiamondSubdivision(true);
|
mShape->setUseDiamondSubdivision(true);
|
||||||
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
mShape->setLocalScaling(btVector3(triSize, triSize, 1));
|
||||||
|
|
||||||
|
#if BT_BULLET_VERSION >= 289
|
||||||
|
// Accelerates some collision tests.
|
||||||
|
//
|
||||||
|
// Note: The accelerator data structure in Bullet is only used
|
||||||
|
// in some operations. This could be improved, see:
|
||||||
|
// https://github.com/bulletphysics/bullet3/issues/3276
|
||||||
|
mShape->buildAccelerator();
|
||||||
|
#endif
|
||||||
|
|
||||||
btTransform transform(btQuaternion::getIdentity(),
|
btTransform transform(btQuaternion::getIdentity(),
|
||||||
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
btVector3((x+0.5f) * triSize * (sqrtVerts-1),
|
||||||
(y+0.5f) * triSize * (sqrtVerts-1),
|
(y+0.5f) * triSize * (sqrtVerts-1),
|
||||||
|
|
|
@ -182,7 +182,7 @@ namespace MWRender
|
||||||
noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE);
|
noBlendAlphaEnv->setCombine_RGB(osg::TexEnvCombine::REPLACE);
|
||||||
noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
|
noBlendAlphaEnv->setSource0_RGB(osg::TexEnvCombine::PREVIOUS);
|
||||||
osg::ref_ptr<osg::Texture2D> dummyTexture = new osg::Texture2D();
|
osg::ref_ptr<osg::Texture2D> dummyTexture = new osg::Texture2D();
|
||||||
dummyTexture->setInternalFormat(GL_RED);
|
dummyTexture->setInternalFormat(GL_DEPTH_COMPONENT);
|
||||||
dummyTexture->setTextureSize(1, 1);
|
dummyTexture->setTextureSize(1, 1);
|
||||||
// This might clash with a shadow map, so make sure it doesn't cast shadows
|
// This might clash with a shadow map, so make sure it doesn't cast shadows
|
||||||
dummyTexture->setShadowComparison(true);
|
dummyTexture->setShadowComparison(true);
|
||||||
|
|
|
@ -76,6 +76,17 @@ namespace
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <std::size_t size>
|
||||||
|
btHeightfieldTerrainShape makeSquareHeightfieldTerrainShape(const std::array<btScalar, size>& values,
|
||||||
|
btScalar heightScale = 1, int upAxis = 2, PHY_ScalarType heightDataType = PHY_FLOAT, bool flipQuadEdges = false)
|
||||||
|
{
|
||||||
|
const int width = static_cast<int>(std::sqrt(size));
|
||||||
|
const btScalar min = *std::min_element(values.begin(), values.end());
|
||||||
|
const btScalar max = *std::max_element(values.begin(), values.end());
|
||||||
|
const btScalar greater = std::max(std::abs(min), std::abs(max));
|
||||||
|
return btHeightfieldTerrainShape(width, width, values.data(), heightScale, -greater, greater, upAxis, heightDataType, flipQuadEdges);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
|
TEST_F(DetourNavigatorNavigatorTest, find_path_for_empty_should_return_empty)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut),
|
EXPECT_EQ(mNavigator->findPath(mAgentHalfExtents, mStepSize, mStart, mEnd, Flag_walk, mAreaCosts, mOut),
|
||||||
|
@ -108,7 +119,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -154,7 +165,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
btBoxShape boxShape(btVector3(20, 20, 100));
|
btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
|
@ -238,7 +249,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
btBoxShape boxShape(btVector3(20, 20, 100));
|
btBoxShape boxShape(btVector3(20, 20, 100));
|
||||||
|
@ -325,7 +336,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
const std::array<btScalar, 5 * 5> heightfieldData2 {{
|
const std::array<btScalar, 5 * 5> heightfieldData2 {{
|
||||||
|
@ -335,7 +346,7 @@ namespace
|
||||||
-25, -25, -25, -25, -25,
|
-25, -25, -25, -25, -25,
|
||||||
-25, -25, -25, -25, -25,
|
-25, -25, -25, -25, -25,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape2(5, 5, heightfieldData2.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape2 = makeSquareHeightfieldTerrainShape(heightfieldData2);
|
||||||
shape2.setLocalScaling(btVector3(128, 128, 1));
|
shape2.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -382,7 +393,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
std::array<btScalar, 5 * 5> heightfieldDataAvoid {{
|
std::array<btScalar, 5 * 5> heightfieldDataAvoid {{
|
||||||
|
@ -392,7 +403,7 @@ namespace
|
||||||
-25, -25, -25, -25, -25,
|
-25, -25, -25, -25, -25,
|
||||||
-25, -25, -25, -25, -25,
|
-25, -25, -25, -25, -25,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shapeAvoid(5, 5, heightfieldDataAvoid.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shapeAvoid = makeSquareHeightfieldTerrainShape(heightfieldDataAvoid);
|
||||||
shapeAvoid.setLocalScaling(btVector3(128, 128, 1));
|
shapeAvoid.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -439,7 +450,7 @@ namespace
|
||||||
-50, -100, -150, -100, -100,
|
-50, -100, -150, -100, -100,
|
||||||
0, -50, -100, -100, -100,
|
0, -50, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -487,7 +498,7 @@ namespace
|
||||||
0, -100, -100, -100, -100, -100, 0,
|
0, -100, -100, -100, -100, -100, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -534,7 +545,7 @@ namespace
|
||||||
0, -100, -100, -100, -100, -100, 0,
|
0, -100, -100, -100, -100, -100, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -581,7 +592,7 @@ namespace
|
||||||
0, -100, -100, -100, -100, -100, 0,
|
0, -100, -100, -100, -100, -100, 0,
|
||||||
0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(7, 7, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -626,7 +637,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -680,7 +691,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape shape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
shape.setLocalScaling(btVector3(128, 128, 1));
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
mNavigator->addAgent(mAgentHalfExtents);
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
@ -711,7 +722,7 @@ namespace
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
0, -25, -100, -100, -100,
|
0, -25, -100, -100, -100,
|
||||||
}};
|
}};
|
||||||
btHeightfieldTerrainShape heightfieldShape(5, 5, heightfieldData.data(), 1, 0, 0, 2, PHY_FLOAT, false);
|
btHeightfieldTerrainShape heightfieldShape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
heightfieldShape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
const std::vector<btBoxShape> boxShapes(100, btVector3(20, 20, 100));
|
const std::vector<btBoxShape> boxShapes(100, btVector3(20, 20, 100));
|
||||||
|
@ -802,4 +813,26 @@ namespace
|
||||||
EXPECT_GT(duration, mSettings.mMinUpdateInterval)
|
EXPECT_GT(duration, mSettings.mMinUpdateInterval)
|
||||||
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(duration).count() << " ms";
|
<< std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(duration).count() << " ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DetourNavigatorNavigatorTest, update_then_raycast_should_return_position)
|
||||||
|
{
|
||||||
|
const std::array<btScalar, 5 * 5> heightfieldData {{
|
||||||
|
0, 0, 0, 0, 0,
|
||||||
|
0, -25, -25, -25, -25,
|
||||||
|
0, -25, -100, -100, -100,
|
||||||
|
0, -25, -100, -100, -100,
|
||||||
|
0, -25, -100, -100, -100,
|
||||||
|
}};
|
||||||
|
btHeightfieldTerrainShape shape = makeSquareHeightfieldTerrainShape(heightfieldData);
|
||||||
|
shape.setLocalScaling(btVector3(128, 128, 1));
|
||||||
|
|
||||||
|
mNavigator->addAgent(mAgentHalfExtents);
|
||||||
|
mNavigator->addObject(ObjectId(&shape), shape, btTransform::getIdentity());
|
||||||
|
mNavigator->update(mPlayerPosition);
|
||||||
|
mNavigator->wait();
|
||||||
|
|
||||||
|
const auto result = mNavigator->raycast(mAgentHalfExtents, mStart, mEnd, Flag_walk);
|
||||||
|
|
||||||
|
ASSERT_THAT(result, Optional(Vec3fEq(mEnd.x(), mEnd.y(), 1.87719)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
#include <components/detournavigator/settingsutils.hpp>
|
#include <components/detournavigator/settingsutils.hpp>
|
||||||
|
|
||||||
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
#include <BulletCollision/CollisionShapes/btBoxShape.h>
|
||||||
#include <BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h>
|
|
||||||
#include <BulletCollision/CollisionShapes/btTriangleMesh.h>
|
|
||||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
|
||||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <gmock/gmock.h>
|
#include <gmock/gmock.h>
|
||||||
|
|
|
@ -181,6 +181,7 @@ add_component_dir(detournavigator
|
||||||
settings
|
settings
|
||||||
navigator
|
navigator
|
||||||
findrandompointaroundcircle
|
findrandompointaroundcircle
|
||||||
|
raycast
|
||||||
)
|
)
|
||||||
|
|
||||||
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
set (ESM_UI ${CMAKE_SOURCE_DIR}/files/ui/contentselector.ui
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
#include <BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h>
|
||||||
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
#include <BulletCollision/CollisionShapes/btCompoundShape.h>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "findrandompointaroundcircle.hpp"
|
#include "findrandompointaroundcircle.hpp"
|
||||||
#include "navigator.hpp"
|
#include "navigator.hpp"
|
||||||
|
#include "raycast.hpp"
|
||||||
|
|
||||||
namespace DetourNavigator
|
namespace DetourNavigator
|
||||||
{
|
{
|
||||||
|
@ -17,4 +18,19 @@ namespace DetourNavigator
|
||||||
return std::optional<osg::Vec3f>();
|
return std::optional<osg::Vec3f>();
|
||||||
return std::optional<osg::Vec3f>(fromNavMeshCoordinates(settings, *result));
|
return std::optional<osg::Vec3f>(fromNavMeshCoordinates(settings, *result));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<osg::Vec3f> Navigator::raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start,
|
||||||
|
const osg::Vec3f& end, const Flags includeFlags) const
|
||||||
|
{
|
||||||
|
const auto navMesh = getNavMesh(agentHalfExtents);
|
||||||
|
if (navMesh == nullptr)
|
||||||
|
return {};
|
||||||
|
const auto settings = getSettings();
|
||||||
|
const auto result = DetourNavigator::raycast(navMesh->lockConst()->getImpl(),
|
||||||
|
toNavMeshCoordinates(settings, agentHalfExtents), toNavMeshCoordinates(settings, start),
|
||||||
|
toNavMeshCoordinates(settings, end), includeFlags, settings);
|
||||||
|
if (!result)
|
||||||
|
return {};
|
||||||
|
return fromNavMeshCoordinates(settings, *result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,6 +223,17 @@ namespace DetourNavigator
|
||||||
std::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
|
std::optional<osg::Vec3f> findRandomPointAroundCircle(const osg::Vec3f& agentHalfExtents,
|
||||||
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const;
|
const osg::Vec3f& start, const float maxRadius, const Flags includeFlags) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief raycast finds farest navmesh point from start on a line from start to end that has path from start.
|
||||||
|
* @param agentHalfExtents allows to find navmesh for given actor.
|
||||||
|
* @param start of the line
|
||||||
|
* @param end of the line
|
||||||
|
* @param includeFlags setup allowed surfaces for actor to walk.
|
||||||
|
* @return not empty optional with position if point is found and empty optional if point is not found.
|
||||||
|
*/
|
||||||
|
std::optional<osg::Vec3f> raycast(const osg::Vec3f& agentHalfExtents, const osg::Vec3f& start,
|
||||||
|
const osg::Vec3f& end, const Flags includeFlags) const;
|
||||||
|
|
||||||
virtual RecastMeshTiles getRecastMeshTiles() = 0;
|
virtual RecastMeshTiles getRecastMeshTiles() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
44
components/detournavigator/raycast.cpp
Normal file
44
components/detournavigator/raycast.cpp
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
#include "raycast.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
#include "findsmoothpath.hpp"
|
||||||
|
|
||||||
|
#include <DetourCommon.h>
|
||||||
|
#include <DetourNavMesh.h>
|
||||||
|
#include <DetourNavMeshQuery.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace DetourNavigator
|
||||||
|
{
|
||||||
|
std::optional<osg::Vec3f> raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
|
||||||
|
const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings)
|
||||||
|
{
|
||||||
|
dtNavMeshQuery navMeshQuery;
|
||||||
|
if (!initNavMeshQuery(navMeshQuery, navMesh, settings.mMaxNavMeshQueryNodes))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
dtQueryFilter queryFilter;
|
||||||
|
queryFilter.setIncludeFlags(includeFlags);
|
||||||
|
|
||||||
|
dtPolyRef ref = 0;
|
||||||
|
if (dtStatus status = navMeshQuery.findNearestPoly(start.ptr(), halfExtents.ptr(), &queryFilter, &ref, nullptr);
|
||||||
|
dtStatusFailed(status) || ref == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const unsigned options = 0;
|
||||||
|
std::array<dtPolyRef, 16> path;
|
||||||
|
dtRaycastHit hit;
|
||||||
|
hit.path = path.data();
|
||||||
|
hit.maxPath = path.size();
|
||||||
|
if (dtStatus status = navMeshQuery.raycast(ref, start.ptr(), end.ptr(), &queryFilter, options, &hit);
|
||||||
|
dtStatusFailed(status) || hit.pathCount == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
osg::Vec3f hitPosition;
|
||||||
|
if (dtStatus status = navMeshQuery.closestPointOnPoly(path[hit.pathCount - 1], end.ptr(), hitPosition.ptr(), nullptr);
|
||||||
|
dtStatusFailed(status))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return hitPosition;
|
||||||
|
}
|
||||||
|
}
|
19
components/detournavigator/raycast.hpp
Normal file
19
components/detournavigator/raycast.hpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H
|
||||||
|
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_RAYCAST_H
|
||||||
|
|
||||||
|
#include "flags.hpp"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <osg/Vec3f>
|
||||||
|
|
||||||
|
class dtNavMesh;
|
||||||
|
|
||||||
|
namespace DetourNavigator
|
||||||
|
{
|
||||||
|
struct Settings;
|
||||||
|
|
||||||
|
std::optional<osg::Vec3f> raycast(const dtNavMesh& navMesh, const osg::Vec3f& halfExtents,
|
||||||
|
const osg::Vec3f& start, const osg::Vec3f& end, const Flags includeFlags, const Settings& settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in a new issue