diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index 161a60dcf..d798585c4 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -168,22 +168,20 @@ bool AIController::getAIMove(Move* movePtr) { obj = getAIInfo()->mObj; } - bool adjusted = false; - if (getNav()->avoidObstacles()) - { - adjusted = true; - } - else if (mRandI(0, 100) < mControllerData->mFlocking.mChance && getNav()->flock()) - { - adjusted = true; - } + + Point3F start = obj->getPosition(); + Point3F end = start; + start.z = obj->getBoxCenter().z; + end.z -= mControllerData->mHeightTolerance; + obj->disableCollision(); // Only repath if not already adjusted and on risky ground RayInfo info; - if (!adjusted && obj->getContainer()->castRay(obj->getPosition(), obj->getPosition() - Point3F(0, 0, mControllerData->mHeightTolerance), StaticShapeObjectType, &info)) + if (obj->getContainer()->castRay(start, end, StaticShapeObjectType, &info)) { getNav()->repath(); } + obj->enableCollision(); getGoal()->mInRange = false; } if (getGoal()->getDist() < mControllerData->mFollowTolerance ) @@ -541,7 +539,7 @@ AIControllerData::AIControllerData() mAttackRadius = 2.0f; mMoveStuckTolerance = 0.01f; mMoveStuckTestDelay = 30; - mHeightTolerance = 0.001f; + mHeightTolerance = 0.1f; mFollowTolerance = 1.0f; #ifdef TORQUE_NAVIGATION_ENABLED diff --git a/Engine/source/T3D/AI/AINavigation.cpp b/Engine/source/T3D/AI/AINavigation.cpp index 86a400f8c..3ff12eda4 100644 --- a/Engine/source/T3D/AI/AINavigation.cpp +++ b/Engine/source/T3D/AI/AINavigation.cpp @@ -23,7 +23,7 @@ #include "AIController.h" #include "T3D/shapeBase.h" -static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType | AIObjectType; +static U32 sAILoSMask = TerrainObjectType | StaticShapeObjectType | StaticObjectType; AINavigation::AINavigation(AIController* controller) { @@ -339,11 +339,11 @@ void AINavigation::repath() if (mPathData.path.isNull() || !mPathData.owned) return; - if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock()) + if (avoidObstacles()) { mPathData.path->mTo = mMoveDestination; } - else if (avoidObstacles()) + else if (mRandI(0, 100) < getCtrl()->mControllerData->mFlocking.mChance && flock()) { mPathData.path->mTo = mMoveDestination; } @@ -401,7 +401,7 @@ bool AINavigation::avoidObstacles() leftDir.normalizeSafe(); rightDir.normalizeSafe(); - F32 rayLength = getCtrl()->mMovement.getMoveSpeed(); + F32 rayLength = obj->getVelocity().lenSquared() * TickSec * 2 + getCtrl()->getAIInfo()->mRadius; Point3F directions[3] = { forward, leftDir, @@ -445,9 +445,10 @@ bool AINavigation::flock() obj->disableCollision(); Point3F pos = obj->getBoxCenter(); - Point3F searchArea = Point3F(flockingData.mMin / 2, flockingData.mMax / 2, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2); F32 maxFlocksq = flockingData.mMax * flockingData.mMax; + Point3F searchArea = Point3F(maxFlocksq, maxFlocksq, getCtrl()->getAIInfo()->mObj->getObjBox().maxExtents.z / 2); + bool flocking = false; U32 found = 0; if (getCtrl()->getGoal()) @@ -471,41 +472,35 @@ bool AINavigation::flock() sql.mList.remove(obj); Point3F avoidanceOffset = Point3F::Zero; + F32 avoidanceAmtSq = 0; - //avoid objects in the way RayInfo info; - if (obj->getContainer()->castRay(pos, dest + Point3F(0, 0, obj->getObjBox().len_z() / 2), sAILoSMask, &info)) - { - Point3F blockerOffset = (info.point - dest); - blockerOffset.z = 0; - avoidanceOffset += blockerOffset; - } - //avoid bots that are too close for (U32 i = 0; i < sql.mList.size(); i++) { ShapeBase* other = dynamic_cast(sql.mList[i]); Point3F objectCenter = other->getBoxCenter(); - F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; + F32 sumMinRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius; - sumRad += separation; + separation += sumMinRad; Point3F offset = (pos - objectCenter); F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares - if ((flockingData.mMin > 0) && (offsetLensq < (sumRad * sumRad))) + if ((flockingData.mMin > 0) && (offsetLensq < (sumMinRad * sumMinRad))) { other->disableCollision(); - if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info)) + if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info)) { found++; - offset.normalizeSafe(); - offset *= sumRad + separation; + offset *= separation; avoidanceOffset += offset; //accumulate total group, move away from that + avoidanceAmtSq += offsetLensq; } other->enableCollision(); } } + //if we don't have to worry about bumping into one another (nothing found lower than minFLock), see about grouping up if (found == 0) { @@ -514,20 +509,20 @@ bool AINavigation::flock() ShapeBase* other = static_cast(sql.mList[i]); Point3F objectCenter = other->getBoxCenter(); - F32 sumRad = flockingData.mMin + other->getAIController()->mControllerData->mFlocking.mMin; + F32 sumMaxRad = flockingData.mMax + other->getAIController()->mControllerData->mFlocking.mMax; F32 separation = getCtrl()->getAIInfo()->mRadius + other->getAIController()->getAIInfo()->mRadius; - sumRad += separation; + separation += sumMaxRad; Point3F offset = (pos - objectCenter); - if ((flockingData.mMin > 0) && ((sumRad * sumRad) < (maxFlocksq))) + F32 offsetLensq = offset.lenSquared(); //square roots are expensive, so use squared val compares + if ((flockingData.mMax > 0) && (offsetLensq < (sumMaxRad * sumMaxRad))) { other->disableCollision(); - if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask, &info)) + if (!obj->getContainer()->castRay(pos, other->getBoxCenter(), sAILoSMask | AIObjectType, &info)) { found++; - offset.normalizeSafe(); - offset *= sumRad + separation; avoidanceOffset -= offset; // subtract total group, move toward it + avoidanceAmtSq -= offsetLensq; } other->enableCollision(); } @@ -535,27 +530,36 @@ bool AINavigation::flock() } if (found > 0) { + //ephasize the *side* portion of sidestep to better avoid clumps + if (avoidanceOffset.x < avoidanceOffset.y) + avoidanceOffset.x *= 2.0; + else + avoidanceOffset.y *= 2.0; + + //add fuzz to sidestepping avoidanceOffset.z = 0; avoidanceOffset.x = (mRandF() * avoidanceOffset.x) * 0.5 + avoidanceOffset.x * 0.75; avoidanceOffset.y = (mRandF() * avoidanceOffset.y) * 0.5 + avoidanceOffset.y * 0.75; - if (avoidanceOffset.lenSquared() < (maxFlocksq)) + + avoidanceOffset.normalizeSafe(); + avoidanceOffset *= avoidanceAmtSq; + + if ((avoidanceAmtSq) > flockingData.mMin * flockingData.mMin) { - dest += avoidanceOffset; + dest = obj->getPosition()+avoidanceOffset; } //if we're not jumping... if (mJump == None) { dest.z = obj->getPosition().z; + //make sure we don't run off a cliff Point3F zlen(0, 0, getCtrl()->mControllerData->mHeightTolerance); if (obj->getContainer()->castRay(dest + zlen, dest - zlen, TerrainObjectType | StaticShapeObjectType | StaticObjectType, &info)) { - if ((mMoveDestination - dest).len() > getCtrl()->mControllerData->mMoveTolerance) - { - mMoveDestination = dest; - flocking = true; - } + mMoveDestination = dest; + flocking = true; } } } diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp index 1c254277f..e5d8e2875 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.cpp @@ -112,6 +112,9 @@ NavMeshTestTool::NavMeshTestTool() mPlayer = NULL; mCurPlayer = NULL; + mFollowObject = NULL; + mCurFollowObject = NULL; + mPathStart = Point3F::Max; mPathEnd = Point3F::Max; @@ -120,10 +123,12 @@ NavMeshTestTool::NavMeshTestTool() mLinkTypes = LinkData(AllFlags); mFilter.setIncludeFlags(mLinkTypes.getFlags()); mFilter.setExcludeFlags(0); + mSelectFollow = false; } void NavMeshTestTool::onActivated(const Gui3DMouseEvent& evt) { + mSelectFollow = false; Con::executef(this, "onActivated"); } @@ -140,6 +145,8 @@ void NavMeshTestTool::onDeactivated() Con::executef(this, "onPlayerDeselected"); } + mSelectFollow = false; + Con::executef(this, "onDeactivated"); } @@ -175,8 +182,17 @@ void NavMeshTestTool::on3DMouseDown(const Gui3DMouseEvent& evt) { if (!ri.object) return; - - mPlayer = ri.object; + if (mSelectFollow) + { + mFollowObject = ri.object; + Con::executef(this, "onFollowSelected"); + mSelectFollow = false; + return; + } + else + { + mPlayer = ri.object; + } #ifdef TORQUE_NAVIGATION_ENABLED AIPlayer* asAIPlayer = dynamic_cast(mPlayer.getPointer()); @@ -277,10 +293,17 @@ void NavMeshTestTool::on3DMouseMove(const Gui3DMouseEvent& evt) RayInfo ri; if (gServerContainer.castRay(startPnt, endPnt, PlayerObjectType | VehicleObjectType, &ri)) - mCurPlayer = ri.object; + { + if (mSelectFollow) + mCurFollowObject = ri.object; + else + mCurPlayer = ri.object; + } else + { + mCurFollowObject = NULL; mCurPlayer = NULL; - + } } void NavMeshTestTool::onRender3D() @@ -310,10 +333,15 @@ void NavMeshTestTool::onRender3D() dd.immediateRender(); + if (!mCurFollowObject.isNull()) + renderBoxOutline(mCurFollowObject->getWorldBox(), ColorI::LIGHT); if (!mCurPlayer.isNull()) renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE); if (!mPlayer.isNull()) renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN); + if (!mFollowObject.isNull()) + renderBoxOutline(mFollowObject->getWorldBox(), ColorI::WHITE); + } bool NavMeshTestTool::updateGuiInfo() @@ -326,10 +354,25 @@ bool NavMeshTestTool::updateGuiInfo() String text; + if (mPlayer) + text = "LMB To Select move Destination. LSHIFT+LMB To Deselect Current Bot."; + if (mCurPlayer != NULL && mCurPlayer != mPlayer) + text = "LMB To select Bot."; + + if (mPlayer == NULL) + { + text = "LMB To place start/end for test path."; + } + + if (mSpawnClass != String::EmptyString && mSpawnDatablock != String::EmptyString) + text += " CTRL+LMB To spawn a new Bot."; + if (statusbar) Con::executef(statusbar, "setInfo", text.c_str()); text = ""; + if (mPlayer) + text = String::ToString("Bot Selected: %d", mPlayer->getId()); if (selectionBar) selectionBar->setText(text); @@ -342,12 +385,24 @@ S32 NavMeshTestTool::getPlayerId() return mPlayer.isNull() ? 0 : mPlayer->getId(); } +S32 NavMeshTestTool::getFollowObjectId() +{ + return mFollowObject.isNull() ? 0 : mFollowObject->getId(); +} + + DefineEngineMethod(NavMeshTestTool, getPlayer, S32, (), , "@brief Return the current player id.") { return object->getPlayerId(); } +DefineEngineMethod(NavMeshTestTool, getFollowObject, S32, (), , + "@brief Return the current follow object id.") +{ + return object->getFollowObjectId(); +} + DefineEngineMethod(NavMeshTestTool, setSpawnClass, void, (String className), , "") { object->setSpawnClass(className); @@ -358,3 +413,9 @@ DefineEngineMethod(NavMeshTestTool, setSpawnDatablock, void, (String dbName), , object->setSpawnDatablock(dbName); } +DefineEngineMethod(NavMeshTestTool, followSelectMode, void, (), , + "@brief Set NavMeshTool to select a follow object.") +{ + return object->followSelectMode(); +} + diff --git a/Engine/source/navigation/navMeshTools/navMeshTestTool.h b/Engine/source/navigation/navMeshTools/navMeshTestTool.h index 1dbeb8341..2ee56e43e 100644 --- a/Engine/source/navigation/navMeshTools/navMeshTestTool.h +++ b/Engine/source/navigation/navMeshTools/navMeshTestTool.h @@ -18,11 +18,15 @@ protected: String mSpawnDatablock; SimObjectPtr mPlayer; SimObjectPtr mCurPlayer; + SimObjectPtr mFollowObject; + SimObjectPtr mCurFollowObject; Point3F mPathStart; Point3F mPathEnd; NavPath* mTestPath; LinkData mLinkTypes; dtQueryFilter mFilter; + bool mSelectFollow; + public: DECLARE_CONOBJECT(NavMeshTestTool); @@ -44,9 +48,11 @@ public: bool updateGuiInfo() override; S32 getPlayerId(); + S32 getFollowObjectId(); void setSpawnClass(String className) { mSpawnClass = className; } void setSpawnDatablock(String dbName) { mSpawnDatablock = dbName; } + void followSelectMode() { mSelectFollow = true; } }; diff --git a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui index d22792360..ee81b620e 100644 --- a/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui +++ b/Templates/BaseGame/game/tools/navEditor/NavEditorGui.gui @@ -294,7 +294,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "SelectActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -370,7 +370,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "LinkActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -386,7 +386,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "CoverActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -411,7 +411,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "TileActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiButtonCtrl() { Profile = "ToolsGuiButtonProfile"; @@ -427,7 +427,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { { internalName = "TestActions"; position = "7 21"; - extent = "190 64"; + extent = "190 136"; new GuiControl() { profile = "GuiDefaultProfile"; Extent = "190 20"; @@ -481,7 +481,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { Extent = "90 18"; text = "Delete"; tooltipProfile = "GuiToolTipProfile"; - tooltip = "Delete Selected."; + tooltip = "Delete Selected Bot."; command = "NavMeshTools->TestTool.getPlayer().delete();NavInspector.inspect();"; }; new GuiButtonCtrl() { @@ -505,7 +505,7 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { HorizSizing = "right"; VertSizing = "bottom"; Extent = "90 18"; - text = "Follow"; + text = "Select Follow"; command = "NavMeshTools->TestTool.followObject();"; }; new GuiButtonCtrl() { @@ -519,6 +519,20 @@ $guiContent = new GuiNavEditorCtrl(NavEditorGui, EditorGuiGroup) { command = "NavMeshTools->TestTool.stop();"; }; }; + new GuiControl() { + profile = "GuiDefaultProfile"; + Extent = "190 18"; + + new GuiButtonCtrl() { + Profile = "ToolsGuiButtonProfile"; + buttonType = "PushButton"; + HorizSizing = "right"; + VertSizing = "bottom"; + Extent = "90 18"; + text = "Toggle Follow"; + command = "NavMeshTools->TestTool.toggleFollow();"; + }; + }; }; }; new GuiContainer(NavEditorInspector){ diff --git a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript index 44cdb0377..232a160b4 100644 --- a/Templates/BaseGame/game/tools/navEditor/navEditor.tscript +++ b/Templates/BaseGame/game/tools/navEditor/navEditor.tscript @@ -431,7 +431,7 @@ function NavMeshTestTool::onActivated(%this) %properties->TestProperties.setVisible(false); %classList = enumerateConsoleClasses("Player") TAB enumerateConsoleClasses("Vehicle"); - echo(%classList); + //echo(%classList); SpawnClassSelector.clear(); foreach$(%class in %classList) @@ -498,6 +498,23 @@ function NavMeshTestTool::stop(%this) } } +function NavMeshTestTool::toggleFollow(%this) +{ + + if(isObject(%this.getFollowObject()) && isObject(%this.getPlayer())) + { + if(%this.getPlayer().isMemberOfClass("AIPlayer")) + %this.getPlayer().followObject(%this.getFollowObject(), "2.0"); + else + %this.getPlayer().getAIController().followObject(%this.getFollowObject(), %this.getPlayer().getDatablock().aiControllerData.mFollowTolerance); + } +} + +function NavMeshTestTool::followObject(%this) +{ + %this.followSelectMode(); +} + function SpawnClassSelector::onSelect(%this, %id) { %className = %this.getTextById(%id);