From f4c940f4fef1e14fd01fed04702b96fec4198030 Mon Sep 17 00:00:00 2001 From: Daniel Buckmaster Date: Fri, 28 Nov 2014 19:42:10 +1100 Subject: [PATCH] Added basic Walkabout with #define renamed and no editor. --- Engine/source/T3D/aiPlayer.cpp | 578 ++++++++++++- Engine/source/T3D/aiPlayer.h | 124 +++ Engine/source/environment/river.cpp | 50 ++ Engine/source/environment/river.h | 1 + Engine/source/environment/waterBlock.cpp | 46 ++ Engine/source/environment/waterBlock.h | 1 + Engine/source/environment/waterPlane.cpp | 42 + Engine/source/environment/waterPlane.h | 1 + .../gui/containers/guiFlexibleArrayCtrl.cpp | 187 +++++ .../gui/containers/guiFlexibleArrayCtrl.h | 70 ++ Engine/source/navigation/coverPoint.cpp | 344 ++++++++ Engine/source/navigation/coverPoint.h | 136 ++++ Engine/source/navigation/guiNavEditorCtrl.cpp | 641 +++++++++++++++ Engine/source/navigation/guiNavEditorCtrl.h | 189 +++++ Engine/source/navigation/navContext.cpp | 69 ++ Engine/source/navigation/navContext.h | 68 ++ Engine/source/navigation/navMesh.cpp | 766 +++++++++++++++++- Engine/source/navigation/navMesh.h | 156 +++- Engine/source/navigation/navPath.cpp | 329 +++++--- Engine/source/navigation/navPath.h | 89 +- Engine/source/navigation/torqueRecast.h | 32 + Engine/source/terrain/terrCollision.cpp | 2 +- 22 files changed, 3725 insertions(+), 196 deletions(-) create mode 100644 Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp create mode 100644 Engine/source/gui/containers/guiFlexibleArrayCtrl.h create mode 100644 Engine/source/navigation/coverPoint.cpp create mode 100644 Engine/source/navigation/coverPoint.h create mode 100644 Engine/source/navigation/guiNavEditorCtrl.cpp create mode 100644 Engine/source/navigation/guiNavEditorCtrl.h create mode 100644 Engine/source/navigation/navContext.cpp create mode 100644 Engine/source/navigation/navContext.h diff --git a/Engine/source/T3D/aiPlayer.cpp b/Engine/source/T3D/aiPlayer.cpp index 31ada9970..feab8322b 100644 --- a/Engine/source/T3D/aiPlayer.cpp +++ b/Engine/source/T3D/aiPlayer.cpp @@ -100,6 +100,12 @@ AIPlayer::AIPlayer() mTargetInLOS = false; mAimOffset = Point3F(0.0f, 0.0f, 0.0f); +#ifdef TORQUE_NAVIGATION_ENABLED + mJump = None; + mNavSize = Regular; + mLinkTypes = LinkData(AllFlags); +#endif // TORQUE_NAVIGATION_ENABLED + mIsAiControlled = true; } @@ -136,6 +142,27 @@ void AIPlayer::initPersistFields() endGroup( "AI" ); +#ifdef TORQUE_NAVIGATION_ENABLED + addGroup("Pathfinding"); + + addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, AIPlayer), + "Allow the character to walk on dry land."); + addField("allowJump", TypeBool, Offset(mLinkTypes.jump, AIPlayer), + "Allow the character to use jump links."); + addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, AIPlayer), + "Allow the character to use drop links."); + addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, AIPlayer), + "Allow the character tomove in water."); + addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, AIPlayer), + "Allow the character to jump ledges."); + addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, AIPlayer), + "Allow the character to use climb links."); + addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, AIPlayer), + "Allow the character to use teleporters."); + + endGroup("Pathfinding"); +#endif // TORQUE_NAVIGATION_ENABLED + Parent::initPersistFields(); } @@ -152,6 +179,16 @@ bool AIPlayer::onAdd() return true; } +#ifdef TORQUE_NAVIGATION_ENABLED +void AIPlayer::onRemove() +{ + clearPath(); + clearCover(); + clearFollow(); + Parent::onRemove(); +} +#endif // TORQUE_NAVIGATION_ENABLED + /** * Sets the speed at which this AI moves * @@ -168,6 +205,11 @@ void AIPlayer::setMoveSpeed( F32 speed ) void AIPlayer::stopMove() { mMoveState = ModeStop; +#ifdef TORQUE_NAVIGATION_ENABLED + clearPath(); + clearCover(); + clearFollow(); +#endif // TORQUE_NAVIGATION_ENABLED } /** @@ -258,6 +300,32 @@ bool AIPlayer::getAIMove(Move *movePtr) Point3F location = eye.getPosition(); Point3F rotation = getRotation(); +#ifdef TORQUE_NAVIGATION_ENABLED + if(mDamageState == Enabled) + { + if(mMoveState != ModeStop) + updateNavMesh(); + if(!mFollowData.object.isNull()) + { + if(mPathData.path.isNull()) + { + if((getPosition() - mFollowData.object->getPosition()).len() > mFollowData.radius) + followObject(mFollowData.object, mFollowData.radius); + } + else + { + if((mPathData.path->mTo - mFollowData.object->getPosition()).len() > mFollowData.radius) + repath(); + else if((getPosition() - mFollowData.object->getPosition()).len() < mFollowData.radius) + { + clearPath(); + mMoveState = ModeStop; + } + } + } + } +#endif // TORQUE_NAVIGATION_ENABLED + // Orient towards the aim point, aim object, or towards // our destination. if (mAimObject || mAimLocationSet || mMoveState != ModeStop) @@ -340,7 +408,11 @@ bool AIPlayer::getAIMove(Move *movePtr) if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance) { mMoveState = ModeStop; +#ifdef TORQUE_NAVIGATION_ENABLED + onReachDestination(); +#else throwCallback("onReachDestination"); +#endif // TORQUE_NAVIGATION_ENABLED } else { @@ -409,7 +481,11 @@ bool AIPlayer::getAIMove(Move *movePtr) if ( mMoveState != ModeSlowing || locationDelta == 0 ) { mMoveState = ModeStuck; +#ifdef TORQUE_NAVIGATION_ENABLED + onStuck(); +#else throwCallback("onMoveStuck"); +#endif // TORQUE_NAVIGATION_ENABLED } } } @@ -419,28 +495,53 @@ bool AIPlayer::getAIMove(Move *movePtr) // Test for target location in sight if it's an object. The LOS is // run from the eye position to the center of the object's bounding, // which is not very accurate. - if (mAimObject) - { - if (checkInLos(mAimObject.getPointer())) - { - if (!mTargetInLOS) - { - throwCallback("onTargetEnterLOS"); - mTargetInLOS = true; + if (mAimObject) { + MatrixF eyeMat; + getEyeTransform(&eyeMat); + eyeMat.getColumn(3,&location); + Point3F targetLoc = mAimObject->getBoxCenter(); + + // This ray ignores non-static shapes. Cast Ray returns true + // if it hit something. + RayInfo dummy; + if (getContainer()->castRay( location, targetLoc, + StaticShapeObjectType | StaticObjectType | + TerrainObjectType, &dummy)) { + if (mTargetInLOS) { + throwCallback( "onTargetExitLOS" ); + mTargetInLOS = false; } } - else if (mTargetInLOS) - { - throwCallback("onTargetExitLOS"); - mTargetInLOS = false; - } + else + if (!mTargetInLOS) { + throwCallback( "onTargetEnterLOS" ); + mTargetInLOS = true; + } } // Replicate the trigger state into the move so that // triggers can be controlled from scripts. - for( S32 i = 0; i < MaxTriggerKeys; i++ ) + for( int i = 0; i < MaxTriggerKeys; i++ ) movePtr->trigger[i] = getImageTriggerState(i); +#ifdef TORQUE_NAVIGATION_ENABLED + if(mJump == Now) + { + movePtr->trigger[2] = true; + mJump = None; + } + else if(mJump == Ledge) + { + // If we're not touching the ground, jump! + RayInfo info; + if(!getContainer()->castRay(getPosition(), getPosition() - Point3F(0, 0, 0.4f), StaticShapeObjectType, &info)) + { + movePtr->trigger[2] = true; + mJump = None; + } + } +#endif // TORQUE_NAVIGATION_ENABLED + mLastLocation = location; return true; @@ -457,6 +558,453 @@ void AIPlayer::throwCallback( const char *name ) Con::executef(getDataBlock(), name, getIdString()); } +#ifdef TORQUE_NAVIGATION_ENABLED +/** + * Called when we get within mMoveTolerance of our destination set using + * setMoveDestination(). Only fires the script callback if we are at the end + * of a pathfinding path, or have no pathfinding path. + */ +void AIPlayer::onReachDestination() +{ + if(!mPathData.path.isNull()) + { + if(mPathData.index == mPathData.path->size() - 1) + { + // Handle looping paths. + if(mPathData.path->mIsLooping) + moveToNode(0); + // Otherwise end path. + else + { + clearPath(); + throwCallback("onReachDestination"); + } + } + else + { + moveToNode(mPathData.index + 1); + // Throw callback every time if we're on a looping path. + //if(mPathData.path->mIsLooping) + //throwCallback("onReachDestination"); + } + } + else + throwCallback("onReachDestination"); +} + +/** + * Called when we move less than mMoveStuckTolerance in a tick, signalling + * that some obstacle is preventing us from getting where we need to go. + */ +void AIPlayer::onStuck() +{ + if(!mPathData.path.isNull()) + repath(); + else + throwCallback("onMoveStuck"); +} + +// -------------------------------------------------------------------------------------------- +// Pathfinding +// -------------------------------------------------------------------------------------------- + +void AIPlayer::clearPath() +{ + // Only delete if we own the path. + if(!mPathData.path.isNull() && mPathData.owned) + mPathData.path->deleteObject(); + // Reset path data. + mPathData = PathData(); +} + +void AIPlayer::clearCover() +{ + // Notify cover that we are no longer on our way. + if(!mCoverData.cover.isNull()) + mCoverData.cover->setOccupied(false); + mCoverData = CoverData(); +} + +void AIPlayer::clearFollow() +{ + mFollowData = FollowData(); +} + +void AIPlayer::moveToNode(S32 node) +{ + if(mPathData.path.isNull()) + return; + + // -1 is shorthand for 'last path node'. + if(node == -1) + node = mPathData.path->size() - 1; + + // Consider slowing down on the last path node. + setMoveDestination(mPathData.path->getNode(node), false); + + // Check flags for this segment. + if(mPathData.index) + { + U16 flags = mPathData.path->getFlags(node - 1); + // Jump if we must. + if(flags & LedgeFlag) + mJump = Ledge; + else if(flags & JumpFlag) + mJump = Now; + else + // Catch pathing errors. + mJump = None; + } + + // Store current index. + mPathData.index = node; +} + +bool AIPlayer::setPathDestination(const Point3F &pos) +{ + // Pathfinding only happens on the server. + if(!isServerObject()) + return false; + + if(!getNavMesh()) + updateNavMesh(); + // If we can't find a mesh, just move regularly. + if(!getNavMesh()) + { + //setMoveDestination(pos); + return false; + } + + // Create a new path. + NavPath *path = new NavPath(); + if(path) + { + path->mMesh = getNavMesh(); + path->mFrom = getPosition(); + path->mTo = pos; + path->mFromSet = path->mToSet = true; + path->mAlwaysRender = true; + path->mLinkTypes = mLinkTypes; + path->mXray = true; + // Paths plan automatically upon being registered. + if(!path->registerObject()) + { + delete path; + return false; + } + } + else + return false; + + if(path->success()) + { + // Clear any current path we might have. + clearPath(); + clearCover(); + clearFollow(); + // Store new path. + mPathData.path = path; + mPathData.owned = true; + // Skip node 0, which we are currently standing on. + moveToNode(1); + return true; + } + else + { + // Just move normally if we can't path. + //setMoveDestination(pos, true); + //return; + //throwCallback("onPathFailed"); + path->deleteObject(); + return false; + } +} + +DefineEngineMethod(AIPlayer, setPathDestination, bool, (Point3F goal),, + "@brief Tells the AI to find a path to the location provided\n\n" + + "@param goal Coordinates in world space representing location to move to.\n" + "@return True if a path was found.\n\n" + + "@see getPathDestination()\n" + "@see setMoveDestination()\n") +{ + return object->setPathDestination(goal); +} + +Point3F AIPlayer::getPathDestination() const +{ + if(!mPathData.path.isNull()) + return mPathData.path->mTo; + return Point3F(0, 0, 0); +} + +DefineEngineMethod(AIPlayer, getPathDestination, Point3F, (),, + "@brief Get the AIPlayer's current pathfinding destination.\n\n" + + "@return Returns a point containing the \"x y z\" position " + "of the AIPlayer's current path destination. If no path destination " + "has yet been set, this returns \"0 0 0\"." + + "@see setPathDestination()\n") +{ + return object->getPathDestination(); +} + +void AIPlayer::followNavPath(NavPath *path) +{ + if(!isServerObject()) + return; + + // Get rid of our current path. + clearPath(); + clearCover(); + clearFollow(); + + // Follow new path. + mPathData.path = path; + mPathData.owned = false; + // Start from 0 since we might not already be there. + moveToNode(0); +} + +DefineEngineMethod(AIPlayer, followNavPath, void, (SimObjectId obj),, + "@brief Tell the AIPlayer to follow a path.\n\n" + + "@param obj ID of a NavPath object for the character to follow.") +{ + NavPath *path; + if(Sim::findObject(obj, path)) + object->followNavPath(path); +} + +void AIPlayer::followObject(SceneObject *obj, F32 radius) +{ + if(!isServerObject()) + return; + + if(setPathDestination(obj->getPosition())) + { + clearCover(); + mFollowData.object = obj; + mFollowData.radius = radius; + } +} + +DefineEngineMethod(AIPlayer, followObject, void, (SimObjectId obj, F32 radius),, + "@brief Tell the AIPlayer to follow another object.\n\n" + + "@param obj ID of the object to follow.\n" + "@param radius Maximum distance we let the target escape to.") +{ + SceneObject *follow; + if(Sim::findObject(obj, follow)) + object->followObject(follow, radius); +} + +void AIPlayer::repath() +{ + // Ineffectual if we don't have a path, or are using someone else's. + if(mPathData.path.isNull() || !mPathData.owned) + return; + + // If we're following, get their position. + if(!mFollowData.object.isNull()) + mPathData.path->mTo = mFollowData.object->getPosition(); + // Update from position and replan. + mPathData.path->mFrom = getPosition(); + mPathData.path->plan(); + // Move to first node (skip start pos). + moveToNode(1); +} + +DefineEngineMethod(AIPlayer, repath, void, (),, + "@brief Tells the AI to re-plan its path. Does nothing if the character " + "has no path, or if it is following a mission path.\n\n") +{ + object->repath(); +} + +struct CoverSearch +{ + Point3F loc; + Point3F from; + F32 dist; + F32 best; + CoverPoint *point; + CoverSearch() : loc(0, 0, 0), from(0, 0, 0) + { + best = -FLT_MAX; + point = NULL; + dist = FLT_MAX; + } +}; + +static void findCoverCallback(SceneObject *obj, void *key) +{ + CoverPoint *p = dynamic_cast(obj); + if(!p || p->isOccupied()) + return; + CoverSearch *s = static_cast(key); + Point3F dir = s->from - p->getPosition(); + dir.normalizeSafe(); + // Score first based on angle of cover point to enemy. + F32 score = mDot(p->getNormal(), dir); + // Score also based on distance from seeker. + score -= (p->getPosition() - s->loc).len() / s->dist; + // Finally, consider cover size. + score += (p->getSize() + 1) / CoverPoint::NumSizes; + score *= p->getQuality(); + if(score > s->best) + { + s->best = score; + s->point = p; + } +} + +bool AIPlayer::findCover(const Point3F &from, F32 radius) +{ + if(radius <= 0) + return false; + + // Create a search state. + CoverSearch s; + s.loc = getPosition(); + s.dist = radius; + // Direction we seek cover FROM. + s.from = from; + + // Find cover points. + Box3F box(radius * 2.0f); + box.setCenter(getPosition()); + getContainer()->findObjects(box, MarkerObjectType, findCoverCallback, &s); + + // Go to cover! + if(s.point) + { + // Calling setPathDestination clears cover... + bool foundPath = setPathDestination(s.point->getPosition()); + // Now store the cover info. + mCoverData.cover = s.point; + s.point->setOccupied(true); + return foundPath; + } + return false; +} + +DefineEngineMethod(AIPlayer, findCover, S32, (Point3F from, F32 radius),, + "@brief Tells the AI to find cover nearby.\n\n" + + "@param from Location to find cover from (i.e., enemy position).\n" + "@param radius Distance to search for cover.\n" + "@return Cover point ID if cover was found, -1 otherwise.\n\n") +{ + if(object->findCover(from, radius)) + { + CoverPoint* cover = object->getCover(); + return cover ? cover->getId() : -1; + } + else + { + return -1; + } +} + +NavMesh *AIPlayer::findNavMesh() const +{ + // Search for NavMeshes that contain us entirely with the smallest possible + // volume. + NavMesh *mesh = NULL; + SimSet *set = NavMesh::getServerSet(); + for(U32 i = 0; i < set->size(); i++) + { + NavMesh *m = static_cast(set->at(i)); + if(m->getWorldBox().isContained(getWorldBox())) + { + // Check that mesh size is appropriate. + if(mMount.object) // Should use isMounted() but it's not const. Grr. + { + if(!m->mVehicles) + continue; + } + else + { + if(getNavSize() == Small && !m->mSmallCharacters || + getNavSize() == Regular && !m->mRegularCharacters || + getNavSize() == Large && !m->mLargeCharacters) + continue; + } + if(!mesh || m->getWorldBox().getVolume() < mesh->getWorldBox().getVolume()) + mesh = m; + } + } + return mesh; +} + +DefineEngineMethod(AIPlayer, findNavMesh, S32, (),, + "@brief Get the NavMesh object this AIPlayer is currently using.\n\n" + + "@return The ID of the NavPath object this character is using for " + "pathfinding. This is determined by the character's location, " + "navigation type and other factors. Returns -1 if no NavMesh is " + "found.") +{ + NavMesh *mesh = object->getNavMesh(); + return mesh ? mesh->getId() : -1; +} + +void AIPlayer::updateNavMesh() +{ + NavMesh *old = mNavMesh; + if(mNavMesh.isNull()) + mNavMesh = findNavMesh(); + else + { + if(!mNavMesh->getWorldBox().isContained(getWorldBox())) + mNavMesh = findNavMesh(); + } + // See if we need to update our path. + if(mNavMesh != old && !mPathData.path.isNull()) + { + setPathDestination(mPathData.path->mTo); + } +} + +DefineEngineMethod(AIPlayer, getNavMesh, S32, (),, + "@brief Return the NavMesh this AIPlayer is using to navigate.\n\n") +{ + NavMesh *m = object->getNavMesh(); + return m ? m->getId() : 0; +} + +DefineEngineMethod(AIPlayer, setNavSize, void, (const char *size),, + "@brief Set the size of NavMesh this character uses. One of \"Small\", \"Regular\" or \"Large\".") +{ + if(!dStrcmp(size, "Small")) + object->setNavSize(AIPlayer::Small); + else if(!dStrcmp(size, "Regular")) + object->setNavSize(AIPlayer::Regular); + else if(!dStrcmp(size, "Large")) + object->setNavSize(AIPlayer::Large); + else + Con::errorf("AIPlayer::setNavSize: no such size '%s'.", size); +} + +DefineEngineMethod(AIPlayer, getNavSize, const char*, (),, + "@brief Return the size of NavMesh this character uses for pathfinding.") +{ + switch(object->getNavSize()) + { + case AIPlayer::Small: + return "Small"; + case AIPlayer::Regular: + return "Regular"; + case AIPlayer::Large: + return "Large"; + } + return ""; +} +#endif // TORQUE_NAVIGATION_ENABLED // -------------------------------------------------------------------------------------------- // Console Functions @@ -713,4 +1261,4 @@ DefineEngineMethod(AIPlayer, checkInFoV, bool, (ShapeBase* obj, F32 fov, bool ch "@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n") { return object->checkInFoV(obj, fov, checkEnabled); -} \ No newline at end of file +} diff --git a/Engine/source/T3D/aiPlayer.h b/Engine/source/T3D/aiPlayer.h index 86da43edb..ca5b2d596 100644 --- a/Engine/source/T3D/aiPlayer.h +++ b/Engine/source/T3D/aiPlayer.h @@ -27,6 +27,11 @@ #include "T3D/player.h" #endif +#ifdef TORQUE_NAVIGATION_ENABLED +#include "walkabout/navPath.h" +#include "walkabout/navMesh.h" +#include "walkabout/coverPoint.h" +#endif // TORQUE_NAVIGATION_ENABLED class AIPlayer : public Player { @@ -61,6 +66,90 @@ private: // Utility Methods void throwCallback( const char *name ); +#ifdef TORQUE_NAVIGATION_ENABLED +public: + /// Get cover we are moving to. + CoverPoint *getCover() { return mCoverData.cover; } + +private: + /// Should we jump? + enum JumpStates { + None, ///< No, don't jump. + Now, ///< Jump immediately. + Ledge, ///< Jump when we walk off a ledge. + } mJump; + + /// Stores information about a path. + struct PathData { + /// Pointer to path object. + SimObjectPtr path; + /// Do we own our path? If so, we will delete it when finished. + bool owned; + /// Path node we're at. + U32 index; + /// Default constructor. + PathData() : path(NULL) + { + owned = false; + index = 0; + } + }; + + /// Path we are currently following. + PathData mPathData; + + /// Clear out the current path. + void clearPath(); + + /// Get the current path we're following. + NavPath *getPath() { return mPathData.path; } + + /// Stores information about our cover. + struct CoverData { + /// Pointer to a cover point. + SimObjectPtr cover; + /// Default constructor. + CoverData() : cover(NULL) + { + } + }; + + /// Current cover we're trying to get to. + CoverData mCoverData; + + /// Stop searching for cover. + void clearCover(); + + /// Information about a target we're following. + struct FollowData { + /// Object to follow. + SimObjectPtr object; + /// Distance at whcih to follow. + F32 radius; + /// Default constructor. + FollowData() : object(NULL) + { + radius = 5.0f; + } + }; + + /// Current object we're following. + FollowData mFollowData; + + /// Stop following me! + void clearFollow(); + + /// NavMesh we pathfind on. + SimObjectPtr mNavMesh; + + /// Move to the specified node in the current path. + void moveToNode(S32 node); + +protected: + virtual void onReachDestination(); + virtual void onStuck(); +#endif // TORQUE_NAVIGATION_ENABLED + public: DECLARE_CONOBJECT( AIPlayer ); @@ -70,6 +159,9 @@ public: static void initPersistFields(); bool onAdd(); +#ifdef TORQUE_NAVIGATION_ENABLED + void onRemove(); +#endif // TORQUE_NAVIGATION_ENABLED virtual bool getAIMove( Move *move ); @@ -91,6 +183,38 @@ public: void setMoveDestination( const Point3F &location, bool slowdown ); Point3F getMoveDestination() const { return mMoveDestination; } void stopMove(); + +#ifdef TORQUE_NAVIGATION_ENABLED + /// @name Pathfinding + /// @{ + + enum NavSize { + Small, + Regular, + Large + } mNavSize; + void setNavSize(NavSize size) { mNavSize = size; updateNavMesh(); } + NavSize getNavSize() const { return mNavSize; } + + bool setPathDestination(const Point3F &pos); + Point3F getPathDestination() const; + + void followNavPath(NavPath *path); + void followObject(SceneObject *obj, F32 radius); + + void repath(); + + bool findCover(const Point3F &from, F32 radius); + + NavMesh *findNavMesh() const; + void updateNavMesh(); + NavMesh *getNavMesh() const { return mNavMesh; } + + /// Types of link we can use. + LinkData mLinkTypes; + + /// @} +#endif // TORQUE_NAVIGATION_ENABLED }; #endif diff --git a/Engine/source/environment/river.cpp b/Engine/source/environment/river.cpp index 59a16230f..c1039f912 100644 --- a/Engine/source/environment/river.cpp +++ b/Engine/source/environment/river.cpp @@ -1293,6 +1293,56 @@ bool River::collideBox(const Point3F &start, const Point3F &end, RayInfo* info) return false; } +bool River::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere ) +{ + Vector hitSegments; + for ( U32 i = 0; i < mSegments.size(); i++ ) + { + const RiverSegment &segment = mSegments[i]; + if ( segment.worldbounds.isOverlapped( box ) ) + { + hitSegments.push_back( &segment ); + } + } + + if ( !hitSegments.size() ) + return false; + + polyList->setObject( this ); + polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) ); + + for ( U32 i = 0; i < hitSegments.size(); i++ ) + { + const RiverSegment* segment = hitSegments[i]; + for ( U32 k = 0; k < 2; k++ ) + { + // gIdxArray[0] gives us the top plane (see table definition). + U32 idx0 = gIdxArray[0][k][0]; + U32 idx1 = gIdxArray[0][k][1]; + U32 idx2 = gIdxArray[0][k][2]; + + const Point3F &v0 = (*segment)[idx0]; + const Point3F &v1 = (*segment)[idx1]; + const Point3F &v2 = (*segment)[idx2]; + + // Add vertices to poly list. + U32 i0 = polyList->addPoint(v0); + polyList->addPoint(v1); + polyList->addPoint(v2); + + // Add plane between them. + polyList->begin(0, 0); + polyList->vertex(i0); + polyList->vertex(i0+1); + polyList->vertex(i0+2); + polyList->plane(i0, i0+1, i0+2); + polyList->end(); + } + } + + return true; +} + F32 River::getWaterCoverage( const Box3F &worldBox ) const { PROFILE_SCOPE( River_GetWaterCoverage ); diff --git a/Engine/source/environment/river.h b/Engine/source/environment/river.h index cd6c044a2..acb7f2926 100644 --- a/Engine/source/environment/river.h +++ b/Engine/source/environment/river.h @@ -404,6 +404,7 @@ public: virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info); virtual bool containsPoint( const Point3F& point ) const { return containsPoint( point, NULL ); } + virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere ); // WaterObject virtual F32 getWaterCoverage( const Box3F &worldBox ) const; diff --git a/Engine/source/environment/waterBlock.cpp b/Engine/source/environment/waterBlock.cpp index 50494355c..df692fad0 100644 --- a/Engine/source/environment/waterBlock.cpp +++ b/Engine/source/environment/waterBlock.cpp @@ -652,6 +652,52 @@ bool WaterBlock::castRay( const Point3F &start, const Point3F &end, RayInfo *inf return mObjBox.isContained(info->point); } +bool WaterBlock::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& ) +{ + if(context == PLC_Navigation && box.isOverlapped(mWorldBox)) + { + polyList->setObject( this ); + MatrixF mat(true); + Point3F pos = getPosition(); + pos.x = pos.y = 0; + mat.setPosition(pos); + polyList->setTransform( &mat, Point3F(1, 1, 1) ); + + Box3F ov = box.getOverlap(mWorldBox); + Point3F + p0(ov.minExtents.x, ov.maxExtents.y, 0), + p1(ov.maxExtents.x, ov.maxExtents.y, 0), + p2(ov.maxExtents.x, ov.minExtents.y, 0), + p3(ov.minExtents.x, ov.minExtents.y, 0); + + // Add vertices to poly list. + U32 v0 = polyList->addPoint(p0); + polyList->addPoint(p1); + polyList->addPoint(p2); + polyList->addPoint(p3); + + // Add plane between first three vertices. + polyList->begin(0, 0); + polyList->vertex(v0); + polyList->vertex(v0+1); + polyList->vertex(v0+2); + polyList->plane(v0, v0+1, v0+2); + polyList->end(); + + // Add plane between last three vertices. + polyList->begin(0, 1); + polyList->vertex(v0+2); + polyList->vertex(v0+3); + polyList->vertex(v0); + polyList->plane(v0+2, v0+3, v0); + polyList->end(); + + return true; + } + + return false; +} + F32 WaterBlock::getWaterCoverage( const Box3F &testBox ) const { Box3F wbox = getWorldBox(); diff --git a/Engine/source/environment/waterBlock.h b/Engine/source/environment/waterBlock.h index fbbb04246..c052e242f 100644 --- a/Engine/source/environment/waterBlock.h +++ b/Engine/source/environment/waterBlock.h @@ -127,6 +127,7 @@ public: virtual void inspectPostApply(); virtual void setTransform( const MatrixF & mat ); virtual void setScale( const Point3F &scale ); + virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere ); // WaterObject virtual F32 getWaterCoverage( const Box3F &worldBox ) const; diff --git a/Engine/source/environment/waterPlane.cpp b/Engine/source/environment/waterPlane.cpp index afa3dc254..261ba7f09 100644 --- a/Engine/source/environment/waterPlane.cpp +++ b/Engine/source/environment/waterPlane.cpp @@ -816,6 +816,48 @@ F32 WaterPlane::distanceTo( const Point3F& point ) const return ( point.z - getPosition().z ); } +bool WaterPlane::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& ) +{ + if(context == PLC_Navigation) + { + polyList->setObject( this ); + polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) ); + + F32 z = getPosition().z; + Point3F + p0(box.minExtents.x, box.maxExtents.y, z), + p1(box.maxExtents.x, box.maxExtents.y, z), + p2(box.maxExtents.x, box.minExtents.y, z), + p3(box.minExtents.x, box.minExtents.y, z); + + // Add vertices to poly list. + U32 v0 = polyList->addPoint(p0); + polyList->addPoint(p1); + polyList->addPoint(p2); + polyList->addPoint(p3); + + // Add plane between first three vertices. + polyList->begin(0, 0); + polyList->vertex(v0); + polyList->vertex(v0+1); + polyList->vertex(v0+2); + polyList->plane(v0, v0+1, v0+2); + polyList->end(); + + // Add plane between last three vertices. + polyList->begin(0, 1); + polyList->vertex(v0+2); + polyList->vertex(v0+3); + polyList->vertex(v0); + polyList->plane(v0+2, v0+3, v0); + polyList->end(); + + return true; + } + + return false; +} + void WaterPlane::inspectPostApply() { Parent::inspectPostApply(); diff --git a/Engine/source/environment/waterPlane.h b/Engine/source/environment/waterPlane.h index 8902d2a64..682e34074 100644 --- a/Engine/source/environment/waterPlane.h +++ b/Engine/source/environment/waterPlane.h @@ -120,6 +120,7 @@ public: virtual void inspectPostApply(); virtual void setTransform( const MatrixF & mat ); virtual F32 distanceTo( const Point3F& point ) const; + virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere ); // WaterObject virtual F32 getWaterCoverage( const Box3F &worldBox ) const; diff --git a/Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp b/Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp new file mode 100644 index 000000000..93a8e5ca3 --- /dev/null +++ b/Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp @@ -0,0 +1,187 @@ +#ifdef TORQUE_WALKABOUT_EXTRAS_ENABLED +//----------------------------------------------------------------------------- +// Copyright (c) 2012 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "console/engineAPI.h" +#include "platform/platform.h" +#include "gui/containers/guiFlexibleArrayCtrl.h" +#include "platform/types.h" + +GuiFlexibleArrayControl::GuiFlexibleArrayControl() +{ + mRows = 0; + mRowSpacing = 0; + mColSpacing = 0; + mIsContainer = true; + + mResizing = false; + + mFrozen = false; + + mPadding.set(0, 0, 0, 0); +} + +GuiFlexibleArrayControl::~GuiFlexibleArrayControl() +{ +} + +IMPLEMENT_CONOBJECT(GuiFlexibleArrayControl); + +ConsoleDocClass( GuiFlexibleArrayControl, + "@brief A container that arranges children into a grid.\n\n" + + "This container maintains a 2D grid of GUI controls. If one is added, deleted, " + "or resized, then the grid is updated. The insertion order into the grid is " + "determined by the internal order of the children (ie. the order of addition).
" + + "Children are added to the grid by row or column until they fill the assocated " + "GuiFlexibleArrayControl extent (width or height). For example, a " + "GuiFlexibleArrayControl with 10 children, and fillRowFirst set to " + "true may be arranged as follows:\n\n" + + "
\n"
+   "1  2  ...3...  4\n"
+   "..5..  6  7  .8.\n"
+   "9 ....10....\n"
+   "
\n" + + "@tsexample\n" + "new GuiFlexibleArrayControl()\n" + "{\n" + " colSpacing = \"2\";\n" + " rowSpacing = \"2\";\n" + " frozen = \"0\";\n" + " padding = \"0 0 0 0\";\n" + " //Properties not specific to this control have been omitted from this example.\n" + "};\n" + "@endtsexample\n\n" + + "@see GuiDynamicCtrlArrayControl\n" + "@ingroup GuiContainers" +); + +// ConsoleObject... + +void GuiFlexibleArrayControl::initPersistFields() +{ + addField("rowCount", TypeS32, Offset( mRows, GuiFlexibleArrayControl), + "Number of rows the child controls have been arranged into. This value " + "is calculated automatically when children are added, removed or resized; " + "writing it directly has no effect."); + + addField("rowSpacing", TypeS32, Offset(mRowSpacing, GuiFlexibleArrayControl), + "Spacing between rows"); + + addField("colSpacing", TypeS32, Offset(mColSpacing, GuiFlexibleArrayControl), + "Spacing between columns"); + + addField("frozen", TypeBool, Offset(mFrozen, GuiFlexibleArrayControl), + "When true, the array will not update when new children are added or in " + "response to child resize events. This is useful to prevent unnecessary " + "resizing when adding, removing or resizing a number of child controls."); + + addField("padding", TypeRectSpacingI, Offset(mPadding, GuiFlexibleArrayControl), + "Padding around the top, bottom, left, and right of this control. This " + "reduces the area available for child controls."); + + Parent::initPersistFields(); +} + +void GuiFlexibleArrayControl::inspectPostApply() +{ + resize(getPosition(), getExtent()); + Parent::inspectPostApply(); +} + +void GuiFlexibleArrayControl::addObject(SimObject *obj) +{ + Parent::addObject(obj); + + if(!mFrozen) + refresh(); +} + +bool GuiFlexibleArrayControl::resize(const Point2I &newPosition, const Point2I &newExtent) +{ + if(size() == 0) + return Parent::resize(newPosition, newExtent); + + if(mResizing) + return false; + + mResizing = true; + + // Place each child. + S32 childcount = 0; + Point2I pos(mPadding.left, mPadding.top); + mRows = 0; + S32 rowHeight = 0; + for(S32 i = 0; i < size(); i++) + { + GuiControl *gc = dynamic_cast(operator [](i)); + if(gc && gc->isVisible()) + { + if(pos.x + gc->getWidth() > getExtent().x - mPadding.right) + { + pos.y += rowHeight + mRowSpacing; + pos.x = mPadding.left; + rowHeight = 0; + mRows++; + } + gc->setPosition(pos); + + rowHeight = getMax(rowHeight, gc->getHeight()); + + pos.x += mColSpacing + gc->getWidth(); + childcount++; + } + } + + Point2I realExtent(newExtent); + realExtent.y = pos.y + rowHeight; + realExtent.y += mPadding.bottom; + + mResizing = false; + + return Parent::resize(newPosition, realExtent); +} + +void GuiFlexibleArrayControl::childResized(GuiControl *child) +{ + Parent::childResized(child); + + if ( !mFrozen ) + refresh(); +} + +void GuiFlexibleArrayControl::refresh() +{ + resize(getPosition(), getExtent()); +} + +DefineEngineMethod( GuiFlexibleArrayControl, refresh, void, (),, + "Recalculates the position and size of this control and all its children." ) +{ + object->refresh(); +} + +#endif // TORQUE_WALKABOUT_EXTRAS_ENABLED diff --git a/Engine/source/gui/containers/guiFlexibleArrayCtrl.h b/Engine/source/gui/containers/guiFlexibleArrayCtrl.h new file mode 100644 index 000000000..45059686a --- /dev/null +++ b/Engine/source/gui/containers/guiFlexibleArrayCtrl.h @@ -0,0 +1,70 @@ +#ifdef TORQUE_WALKABOUT_EXTRAS_ENABLED +//----------------------------------------------------------------------------- +// Copyright (c) 2012 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _GUIFLEXIBLEARRAYCTRL_H_ +#define _GUIFLEXIBLEARRAYCTRL_H_ + +#include "gui/core/guiControl.h" + +class GuiFlexibleArrayControl : public GuiControl +{ + typedef GuiControl Parent; + +public: + + GuiFlexibleArrayControl(); + virtual ~GuiFlexibleArrayControl(); + + DECLARE_CONOBJECT(GuiFlexibleArrayControl); + DECLARE_CATEGORY( "Gui Containers" ); + + // ConsoleObject + static void initPersistFields(); + + // SimObject + void inspectPostApply(); + + // SimSet + void addObject(SimObject *obj); + + // GuiControl + bool resize(const Point2I &newPosition, const Point2I &newExtent); + void childResized(GuiControl *child); + + // GuiFlexibleArrayCtrl + void refresh(); + +protected: + + S32 mRows; + S32 mRowSpacing; + S32 mColSpacing; + bool mResizing; + bool mFrozen; + + RectSpacingI mPadding; +}; + +#endif // _GUIFLEXIBLEARRAYCTRL_H_ + +#endif // TORQUE_WALKABOUT_EXTRAS_ENABLED diff --git a/Engine/source/navigation/coverPoint.cpp b/Engine/source/navigation/coverPoint.cpp new file mode 100644 index 000000000..9db870f18 --- /dev/null +++ b/Engine/source/navigation/coverPoint.cpp @@ -0,0 +1,344 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "coverPoint.h" + +#include "math/mathIO.h" +#include "scene/sceneRenderState.h" +#include "core/stream/bitStream.h" +#include "materials/sceneData.h" +#include "gfx/gfxDebugEvent.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDrawUtil.h" +#include "renderInstance/renderPassManager.h" +#include "console/engineAPI.h" + +extern bool gEditingMission; + +IMPLEMENT_CO_NETOBJECT_V1(CoverPoint); + +ConsoleDocClass(CoverPoint, + "@brief A type of marker that designates a location AI characters can take cover.\n\n" +); + +ImplementEnumType(CoverPointSize, + "The size of a cover point.\n") + { CoverPoint::Prone, "Prone", "Only provides cover when prone.\n" }, + { CoverPoint::Crouch, "Crouch", "Only provides cover when crouching.\n" }, + { CoverPoint::Stand, "Stand", "Provides cover when standing.\n" }, +EndImplementEnumType; + +//----------------------------------------------------------------------------- +// Object setup and teardown +//----------------------------------------------------------------------------- +CoverPoint::CoverPoint() +{ + mNetFlags.clear(Ghostable); + mTypeMask |= MarkerObjectType; + mSize = Stand; + mQuality = 1.0f; + mOccupied = false; + mPeekLeft = false; + mPeekRight = false; + mPeekOver = false; +} + +CoverPoint::~CoverPoint() +{ +} + +//----------------------------------------------------------------------------- +// Object Editing +//----------------------------------------------------------------------------- +void CoverPoint::initPersistFields() +{ + addGroup("CoverPoint"); + + addField("size", TYPEID(), Offset(mSize, CoverPoint), + "The size of this cover point."); + + addField("quality", TypeF32, Offset(mQuality, CoverPoint), + "Reliability of this point as solid cover. (0...1)"); + + addField("peekLeft", TypeBool, Offset(mPeekLeft, CoverPoint), + "Can characters look left around this cover point?"); + addField("peekRight", TypeBool, Offset(mPeekRight, CoverPoint), + "Can characters look right around this cover point?"); + addField("peekOver", TypeBool, Offset(mPeekOver, CoverPoint), + "Can characters look over the top of this cover point?"); + + endGroup("CoverPoint"); + + Parent::initPersistFields(); +} + +bool CoverPoint::onAdd() +{ + if(!Parent::onAdd()) + return false; + + // Set up a 1x1x1 bounding box + mObjBox.set(Point3F(-0.5f, -0.5f, -0.5f), + Point3F( 0.5f, 0.5f, 0.5f)); + resetWorldBox(); + + if(gEditingMission) + onEditorEnable(); + + addToScene(); + + return true; +} + +void CoverPoint::onRemove() +{ + if(gEditingMission) + onEditorDisable(); + + removeFromScene(); + + Parent::onRemove(); + + for(U32 i = 0; i < NumSizes; i++) + smVertexBuffer[i] = NULL; +} + +void CoverPoint::setTransform(const MatrixF & mat) +{ + Parent::setTransform(mat); + setMaskBits(TransformMask); +} + +void CoverPoint::onEditorEnable() +{ + mNetFlags.set(Ghostable); +} + +void CoverPoint::onEditorDisable() +{ + mNetFlags.clear(Ghostable); +} + +void CoverPoint::inspectPostApply() +{ + setMaskBits(TransformMask); +} + +U32 CoverPoint::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + stream->writeInt(mSize, 4); + + stream->writeFlag(mOccupied); + + stream->writeFlag(peekLeft()); + stream->writeFlag(peekRight()); + stream->writeFlag(peekOver()); + + // Write our transform information + if(stream->writeFlag(mask & TransformMask)) + { + mathWrite(*stream, getTransform()); + mathWrite(*stream, getScale()); + } + + return retMask; +} + +void CoverPoint::unpackUpdate(NetConnection *conn, BitStream *stream) +{ + Parent::unpackUpdate(conn, stream); + + mSize = (Size)stream->readInt(4); + + setOccupied(stream->readFlag()); + + mPeekLeft = stream->readFlag(); + mPeekRight = stream->readFlag(); + mPeekOver = stream->readFlag(); + + if(stream->readFlag()) // TransformMask + { + mathRead(*stream, &mObjToWorld); + mathRead(*stream, &mObjScale); + + setTransform(mObjToWorld); + } +} + +//----------------------------------------------------------------------------- +// Functionality +//----------------------------------------------------------------------------- + +Point3F CoverPoint::getNormal() const +{ + return getTransform().getForwardVector(); +} + +DefineEngineMethod(CoverPoint, isOccupied, bool, (),, + "@brief Returns true if someone is already using this cover point.") +{ + return object->isOccupied(); +} + +//----------------------------------------------------------------------------- +// Object Rendering +//----------------------------------------------------------------------------- + +GFXStateBlockRef CoverPoint::smNormalSB; +GFXVertexBufferHandle CoverPoint::smVertexBuffer[CoverPoint::NumSizes]; + +void CoverPoint::initGFXResources() +{ + if(smVertexBuffer[0] != NULL) + return; + + static const Point3F planePoints[4] = + { + Point3F(-1.0f, 0.0f, 0.0f), Point3F(-1.0f, 0.0f, 2.0f), + Point3F( 1.0f, 0.0f, 0.0f), Point3F( 1.0f, 0.0f, 2.0f), + }; + + static const U32 planeFaces[6] = + { + 0, 1, 2, + 1, 2, 3 + }; + + static const Point3F scales[NumSizes] = + { + Point3F(1.0f, 1.0f, 0.5f), // Prone + Point3F(1.0f, 1.0f, 1.0f), // Crouch + Point3F(1.0f, 1.0f, 2.0f) // Stand + }; + + static const ColorI colours[NumSizes] = + { + ColorI(180, 0, 0, 128), // Prone + ColorI(250, 200, 90, 128), // Crouch + ColorI( 80, 190, 20, 128) // Stand + }; + + for(U32 i = 0; i < NumSizes; i++) + { + // Fill the vertex buffer + VertexType *pVert = NULL; + smVertexBuffer[i].set(GFX, 6, GFXBufferTypeStatic); + + pVert = smVertexBuffer[i].lock(); + for(U32 j = 0; j < 6; j++) + { + pVert[j].point = planePoints[planeFaces[j]] * scales[i] * 0.5f; + pVert[j].normal = Point3F(0.0f, -1.0f, 0.0f); + pVert[j].color = colours[i]; + } + smVertexBuffer[i].unlock(); + } + + // Set up our StateBlock + GFXStateBlockDesc desc; + desc.cullDefined = true; + desc.cullMode = GFXCullNone; + desc.setBlend(true); + smNormalSB = GFX->createStateBlock(desc); +} + +void CoverPoint::prepRenderImage(SceneRenderState *state) +{ + // Allocate an ObjectRenderInst so that we can submit it to the RenderPassManager + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + + // Now bind our rendering function so that it will get called + ri->renderDelegate.bind(this, &CoverPoint::render); + + // Set our RenderInst as a standard object render + ri->type = RenderPassManager::RIT_Editor; + + // Set our sorting keys to a default value + ri->defaultKey = 0; + ri->defaultKey2 = 0; + + // Submit our RenderInst to the RenderPassManager + state->getRenderPass()->addInst(ri); +} + +void CoverPoint::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat) +{ + initGFXResources(); + + if(overrideMat) + return; + + if(smVertexBuffer[mSize].isNull()) + return; + + PROFILE_SCOPE(CoverPoint_Render); + + // Set up a GFX debug event (this helps with debugging rendering events in external tools) + GFXDEBUGEVENT_SCOPE(CoverPoint_Render, ColorI::RED); + + // GFXTransformSaver is a handy helper class that restores + // the current GFX matrices to their original values when + // it goes out of scope at the end of the function + GFXTransformSaver saver; + + // Calculate our object to world transform matrix + MatrixF objectToWorld = getRenderTransform(); + objectToWorld.scale(getScale()); + + // Apply our object transform + GFX->multWorld(objectToWorld); + + // Set the state block + GFX->setStateBlock(smNormalSB); + + // Set up the "generic" shaders + // These handle rendering on GFX layers that don't support + // fixed function. Otherwise they disable shaders. + GFX->setupGenericShaders(GFXDevice::GSModColorTexture); + + // Set the vertex buffer + GFX->setVertexBuffer(smVertexBuffer[mSize]); + + // Draw our triangles + GFX->drawPrimitive(GFXTriangleList, 0, 2); + + // Data for decorations. + GFXStateBlockDesc desc; + F32 height = (float)(mSize + 1) / NumSizes * 2.0f; + + // Draw an X if we're occupied. + if(isOccupied()) + { + GFX->getDrawUtil()->drawArrow(desc, Point3F(-0.5, 0, 0), Point3F(0.5, 0, height), ColorI::RED); + GFX->getDrawUtil()->drawArrow(desc, Point3F(0.5, 0, 0), Point3F(-0.5, 0, height), ColorI::RED); + } + + // Draw arrows to represent peek directions. + if(peekLeft()) + GFX->getDrawUtil()->drawArrow(desc, Point3F(0, 0, height * 0.5), Point3F(-0.5, 0, height * 0.5), ColorI::GREEN); + if(peekRight()) + GFX->getDrawUtil()->drawArrow(desc, Point3F(0, 0, height * 0.5), Point3F(0.5, 0, height * 0.5), ColorI::GREEN); + if(peekOver()) + GFX->getDrawUtil()->drawArrow(desc, Point3F(0, 0, height * 0.5), Point3F(0, 0, height), ColorI::GREEN); +} diff --git a/Engine/source/navigation/coverPoint.h b/Engine/source/navigation/coverPoint.h new file mode 100644 index 000000000..4950767a9 --- /dev/null +++ b/Engine/source/navigation/coverPoint.h @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _COVERPOINT_H_ +#define _COVERPOINT_H_ + +#ifndef _SCENEOBJECT_H_ +#include "scene/sceneObject.h" +#endif +#ifndef _GFXSTATEBLOCK_H_ +#include "gfx/gfxStateBlock.h" +#endif +#ifndef _GFXVERTEXBUFFER_H_ +#include "gfx/gfxVertexBuffer.h" +#endif +#ifndef _GFXPRIMITIVEBUFFER_H_ +#include "gfx/gfxPrimitiveBuffer.h" +#endif + +class BaseMatInstance; + +class CoverPoint : public SceneObject +{ + typedef SceneObject Parent; + + /// Network mask bits. + enum MaskBits + { + TransformMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1 + }; + +public: + CoverPoint(); + virtual ~CoverPoint(); + + DECLARE_CONOBJECT(CoverPoint); + + /// Amount of cover provided at this point. + enum Size { + Prone, + Crouch, + Stand, + NumSizes + }; + + void setSize(Size size) { mSize = size; setMaskBits(TransformMask); } + Size getSize() const { return mSize; } + + /// Is this point occupied? + bool isOccupied() const { return mOccupied; } + void setOccupied(bool occ) { mOccupied = occ; setMaskBits(TransformMask); } + + /// Get the normal of this point (i.e., the direction from which it provides cover). + Point3F getNormal() const; + + F32 getQuality() const { return mQuality; } + + bool peekLeft() const { return mPeekLeft; } + bool peekRight() const { return mPeekRight; } + bool peekOver() const { return mPeekOver; } + + void setPeek(bool left, bool right, bool over) { mPeekLeft = left, mPeekRight = right, mPeekOver = over; } + + /// Init static buffers for rendering. + static void initGFXResources(); + + /// @name SceneObject + /// @{ + static void initPersistFields(); + + bool onAdd(); + void onRemove(); + + void onEditorEnable(); + void onEditorDisable(); + void inspectPostApply(); + + void setTransform(const MatrixF &mat); + + void prepRenderImage(SceneRenderState *state); + void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); + /// @} + + /// @name NetObject + /// @{ + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + /// @} + +protected: + +private: + typedef GFXVertexPCN VertexType; + static GFXStateBlockRef smNormalSB; + static GFXVertexBufferHandle smVertexBuffer[NumSizes]; + + /// Size of this point. + Size mSize; + /// Quality of this point as cover. + F32 mQuality; + + /// Can we look left around this cover? + bool mPeekLeft; + /// Can we look right around this cover? + bool mPeekRight; + /// Can we look up over this cover? + bool mPeekOver; + + /// Is this point currently occupied? + bool mOccupied; +}; + +typedef CoverPoint::Size CoverPointSize; +DefineEnumType(CoverPointSize); + +#endif diff --git a/Engine/source/navigation/guiNavEditorCtrl.cpp b/Engine/source/navigation/guiNavEditorCtrl.cpp new file mode 100644 index 000000000..872afdb45 --- /dev/null +++ b/Engine/source/navigation/guiNavEditorCtrl.cpp @@ -0,0 +1,641 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "platform/platform.h" +#include "guiNavEditorCtrl.h" +#include "duDebugDrawTorque.h" +#include "console/engineAPI.h" + +#include "console/consoleTypes.h" +#include "math/util/frustum.h" +#include "math/mathUtils.h" +#include "gfx/primBuilder.h" +#include "gfx/gfxDrawUtil.h" +#include "scene/sceneRenderState.h" +#include "scene/sceneManager.h" +#include "gui/core/guiCanvas.h" +#include "gui/buttons/guiButtonCtrl.h" +#include "gui/worldEditor/undoActions.h" +#include "T3D/gameBase/gameConnection.h" + +IMPLEMENT_CONOBJECT(GuiNavEditorCtrl); + +ConsoleDocClass(GuiNavEditorCtrl, + "@brief GUI tool that makes up the Navigation Editor\n\n" + "Editor use only.\n\n" + "@internal" + ); + +// Each of the mode names directly correlates with the Nav Editor's tool palette. +const String GuiNavEditorCtrl::mSelectMode = "SelectMode"; +const String GuiNavEditorCtrl::mLinkMode = "LinkMode"; +const String GuiNavEditorCtrl::mCoverMode = "CoverMode"; +const String GuiNavEditorCtrl::mTileMode = "TileMode"; +const String GuiNavEditorCtrl::mTestMode = "TestMode"; + +GuiNavEditorCtrl::GuiNavEditorCtrl() +{ + mMode = mSelectMode; + mIsDirty = false; + mStartDragMousePoint = InvalidMousePoint; + mMesh = NULL; + mCurTile = mTile = -1; + mPlayer = mCurPlayer = NULL; + mSpawnClass = mSpawnDatablock = ""; + mLinkStart = Point3F::Max; + mLink = mCurLink = -1; +} + +GuiNavEditorCtrl::~GuiNavEditorCtrl() +{ +} + +void GuiNavEditorUndoAction::undo() +{ +} + +bool GuiNavEditorCtrl::onAdd() +{ + if(!Parent::onAdd()) + return false; + + GFXStateBlockDesc desc; + desc.fillMode = GFXFillSolid; + desc.setBlend(false); + desc.setZReadWrite(false, false); + desc.setCullMode(GFXCullNone); + + mZDisableSB = GFX->createStateBlock(desc); + + desc.setZReadWrite(true, true); + mZEnableSB = GFX->createStateBlock(desc); + + SceneManager::getPreRenderSignal().notify(this, &GuiNavEditorCtrl::_prepRenderImage); + + return true; +} + +void GuiNavEditorCtrl::initPersistFields() +{ + addField("isDirty", TypeBool, Offset(mIsDirty, GuiNavEditorCtrl)); + + addField("spawnClass", TypeRealString, Offset(mSpawnClass, GuiNavEditorCtrl), + "Class of object to spawn in test mode."); + addField("spawnDatablock", TypeRealString, Offset(mSpawnDatablock, GuiNavEditorCtrl), + "Datablock to give new objects in test mode."); + + Parent::initPersistFields(); +} + +void GuiNavEditorCtrl::onSleep() +{ + Parent::onSleep(); + + //mMode = mSelectMode; +} + +void GuiNavEditorCtrl::selectMesh(NavMesh *mesh) +{ + mesh->setSelected(true); + mMesh = mesh; +} + +DefineEngineMethod(GuiNavEditorCtrl, selectMesh, void, (S32 id),, + "@brief Select a NavMesh object.") +{ + NavMesh *obj; + if(Sim::findObject(id, obj)) + object->selectMesh(obj); +} + +S32 GuiNavEditorCtrl::getMeshId() +{ + return mMesh.isNull() ? 0 : mMesh->getId(); +} + +DefineEngineMethod(GuiNavEditorCtrl, getMesh, S32, (),, + "@brief Select a NavMesh object.") +{ + return object->getMeshId(); +} + +S32 GuiNavEditorCtrl::getPlayerId() +{ + return mPlayer.isNull() ? 0 : mPlayer->getId(); +} + +DefineEngineMethod(GuiNavEditorCtrl, getPlayer, S32, (),, + "@brief Select a NavMesh object.") +{ + return object->getPlayerId(); +} + +void GuiNavEditorCtrl::deselect() +{ + if(!mMesh.isNull()) + mMesh->setSelected(false); + mMesh = NULL; + mPlayer = mCurPlayer = NULL; + mCurTile = mTile = -1; + mLinkStart = Point3F::Max; + mLink = mCurLink = -1; +} + +DefineEngineMethod(GuiNavEditorCtrl, deselect, void, (),, + "@brief Deselect whatever is currently selected in the editor.") +{ + object->deselect(); +} + +void GuiNavEditorCtrl::deleteLink() +{ + if(!mMesh.isNull() && mLink != -1) + { + mMesh->selectLink(mLink, false); + mMesh->deleteLink(mLink); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } +} + +DefineEngineMethod(GuiNavEditorCtrl, deleteLink, void, (),, + "@brief Delete the currently selected link.") +{ + object->deleteLink(); +} + +void GuiNavEditorCtrl::setLinkFlags(const LinkData &d) +{ + if(mMode == mLinkMode && !mMesh.isNull() && mLink != -1) + { + mMesh->setLinkFlags(mLink, d); + } +} + +DefineEngineMethod(GuiNavEditorCtrl, setLinkFlags, void, (U32 flags),, + "@Brief Set jump and drop properties of the selected link.") +{ + object->setLinkFlags(LinkData(flags)); +} + +void GuiNavEditorCtrl::buildTile() +{ + if(!mMesh.isNull() && mTile != -1) + mMesh->buildTile(mTile); +} + +DefineEngineMethod(GuiNavEditorCtrl, buildTile, void, (),, + "@brief Build the currently selected tile.") +{ + object->buildTile(); +} + +void GuiNavEditorCtrl::spawnPlayer(const Point3F &pos) +{ + SceneObject *obj = (SceneObject*)Sim::spawnObject(mSpawnClass, mSpawnDatablock); + if(obj) + { + MatrixF mat(true); + mat.setPosition(pos); + obj->setTransform(mat); + SimObject* cleanup = Sim::findObject("MissionCleanup"); + if(cleanup) + { + SimGroup* missionCleanup = dynamic_cast(cleanup); + missionCleanup->addObject(obj); + } + mPlayer = static_cast(obj); + Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags())); + } +} + +DefineEngineMethod(GuiNavEditorCtrl, spawnPlayer, void, (),, + "@brief Spawn an AIPlayer at the centre of the screen.") +{ + Point3F c; + if(object->get3DCentre(c)) + object->spawnPlayer(c); +} + +void GuiNavEditorCtrl::get3DCursor(GuiCursor *&cursor, + bool &visible, + const Gui3DMouseEvent &event_) +{ + //cursor = mAddNodeCursor; + //visible = false; + + cursor = NULL; + visible = false; + + GuiCanvas *root = getRoot(); + if(!root) + return; + + S32 currCursor = PlatformCursorController::curArrow; + + if(root->mCursorChanged == currCursor) + return; + + PlatformWindow *window = root->getPlatformWindow(); + PlatformCursorController *controller = window->getCursorController(); + + // We've already changed the cursor, + // so set it back before we change it again. + if(root->mCursorChanged != -1) + controller->popCursor(); + + // Now change the cursor shape + controller->pushCursor(currCursor); + root->mCursorChanged = currCursor; +} + +bool GuiNavEditorCtrl::get3DCentre(Point3F &pos) +{ + Point3F screen, start, end; + + screen.set(getExtent().x / 2, getExtent().y / 2, 0); + unproject(screen, &start); + + screen.z = 1.0f; + unproject(screen, &end); + + RayInfo ri; + if(gServerContainer.castRay(start, end, StaticObjectType, &ri)) + { + pos = ri.point; + return true; + } + return false; +} + +void GuiNavEditorCtrl::on3DMouseDown(const Gui3DMouseEvent & event) +{ + mGizmo->on3DMouseDown(event); + + if(!isFirstResponder()) + setFirstResponder(); + + mouseLock(); + + // Construct a LineSegment from the camera position to 1000 meters away in + // the direction clicked. + // If that segment hits the terrain, truncate the ray to only be that length. + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + + U8 keys = Input::getModifierKeys(); + bool shift = keys & SI_LSHIFT; + bool ctrl = keys & SI_LCTRL; + bool alt = keys & SI_LALT; + + if(mMode == mLinkMode && !mMesh.isNull()) + { + if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + U32 link = mMesh->getLink(ri.point); + if(link != -1) + { + if(mLink != -1) + mMesh->selectLink(mLink, false); + mMesh->selectLink(link, true, false); + mLink = link; + LinkData d = mMesh->getLinkFlags(mLink); + Con::executef(this, "onLinkSelected", Con::getIntArg(d.getFlags())); + } + else + { + if(mLink != -1) + { + mMesh->selectLink(mLink, false); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } + else + { + if(mLinkStart != Point3F::Max) + { + mMesh->addLink(mLinkStart, ri.point); + if(!shift) + mLinkStart = Point3F::Max; + } + else + { + mLinkStart = ri.point; + } + } + } + } + else + { + mMesh->selectLink(mLink, false); + mLink = -1; + Con::executef(this, "onLinkDeselected"); + } + } + + if(mMode == mTileMode && !mMesh.isNull()) + { + if(gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri)) + { + mTile = mMesh->getTile(ri.point); + dd.clear(); + mMesh->renderTileData(dd, mTile); + } + } + + if(mMode == mTestMode) + { + // Spawn new character + if(ctrl) + { + if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + spawnPlayer(ri.point); + } + // Deselect character + else if(shift) + { + mPlayer = NULL; + Con::executef(this, "onPlayerDeselected"); + } + // Select/move character + else + { + if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri)) + { + if(dynamic_cast(ri.object)) + { + mPlayer = dynamic_cast(ri.object); + Con::executef(this, "onPlayerSelected", Con::getIntArg(mPlayer->mLinkTypes.getFlags())); + } + } + else if(!mPlayer.isNull() && gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + mPlayer->setPathDestination(ri.point); + } + } +} + +void GuiNavEditorCtrl::on3DMouseUp(const Gui3DMouseEvent & event) +{ + // Keep the Gizmo up to date. + mGizmo->on3DMouseUp(event); + + mouseUnlock(); +} + +void GuiNavEditorCtrl::on3DMouseMove(const Gui3DMouseEvent & event) +{ + //if(mSelRiver != NULL && mSelNode != -1) + //mGizmo->on3DMouseMove(event); + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + + if(mMode == mLinkMode && !mMesh.isNull()) + { + if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + { + U32 link = mMesh->getLink(ri.point); + if(link != -1) + { + if(link != mLink) + { + if(mCurLink != -1) + mMesh->selectLink(mCurLink, false); + mMesh->selectLink(link, true, true); + } + mCurLink = link; + } + else + { + if(mCurLink != mLink) + mMesh->selectLink(mCurLink, false); + mCurLink = -1; + } + } + else + { + mMesh->selectLink(mCurLink, false); + mCurLink = -1; + } + } + + // Select a tile from our current NavMesh. + if(mMode == mTileMode && !mMesh.isNull()) + { + if(gServerContainer.castRay(startPnt, endPnt, StaticObjectType, &ri)) + mCurTile = mMesh->getTile(ri.point); + else + mCurTile = -1; + } + + if(mMode == mTestMode) + { + if(gServerContainer.castRay(startPnt, endPnt, PlayerObjectType, &ri)) + mCurPlayer = dynamic_cast(ri.object); + else + mCurPlayer = NULL; + } +} + +void GuiNavEditorCtrl::on3DMouseDragged(const Gui3DMouseEvent & event) +{ + mGizmo->on3DMouseDragged(event); + if(mGizmo->isDirty()) + { + Point3F pos = mGizmo->getPosition(); + Point3F scale = mGizmo->getScale(); + const MatrixF &mat = mGizmo->getTransform(); + VectorF normal; + mat.getColumn(2, &normal); + + //mSelRiver->setNode(pos, scale.x, scale.z, normal, mSelNode); + mIsDirty = true; + } +} + +void GuiNavEditorCtrl::on3DMouseEnter(const Gui3DMouseEvent & event) +{ +} + +void GuiNavEditorCtrl::on3DMouseLeave(const Gui3DMouseEvent & event) +{ +} + +void GuiNavEditorCtrl::updateGuiInfo() +{ +} + +void GuiNavEditorCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + PROFILE_SCOPE(GuiNavEditorCtrl_OnRender); + + Parent::onRender(offset, updateRect); + return; +} + +static void renderBoxOutline(const Box3F &box, const ColorI &col) +{ + if(box != Box3F::Invalid) + { + GFXStateBlockDesc desc; + desc.setCullMode(GFXCullNone); + desc.setFillModeSolid(); + desc.setZReadWrite(true, false); + desc.setBlend(true); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 20)); + desc.setFillModeWireframe(); + desc.setBlend(false); + GFX->getDrawUtil()->drawCube(desc, box, ColorI(col, 255)); + } +} + +void GuiNavEditorCtrl::renderScene(const RectI & updateRect) +{ + GFX->setStateBlock(mZDisableSB); + + // get the projected size... + GameConnection* connection = GameConnection::getConnectionToServer(); + if(!connection) + return; + + // Grab the camera's transform + MatrixF mat; + connection->getControlCameraTransform(0, &mat); + + // Get the camera position + Point3F camPos; + mat.getColumn(3,&camPos); + + if(mMode == mLinkMode) + { + if(mLinkStart != Point3F::Max) + { + GFXStateBlockDesc desc; + desc.setBlend(false); + desc.setZReadWrite(true ,true); + MatrixF mat(true); + mat.setPosition(mLinkStart); + Point3F scale(0.8f, 0.8f, 0.8f); + GFX->getDrawUtil()->drawTransform(desc, mat, &scale); + } + } + + if(mMode == mTileMode && !mMesh.isNull()) + { + renderBoxOutline(mMesh->getTileBox(mCurTile), ColorI::BLUE); + renderBoxOutline(mMesh->getTileBox(mTile), ColorI::GREEN); + if(Con::getBoolVariable("$Nav::Editor::renderVoxels", false)) dd.renderGroup(0); + if(Con::getBoolVariable("$Nav::Editor::renderInput", false)) + { + dd.depthMask(false); + dd.renderGroup(1); + dd.depthMask(true); + } + } + + if(mMode == mTestMode) + { + if(!mCurPlayer.isNull()) + renderBoxOutline(mCurPlayer->getWorldBox(), ColorI::BLUE); + if(!mPlayer.isNull()) + renderBoxOutline(mPlayer->getWorldBox(), ColorI::GREEN); + } + + duDebugDrawTorque d; + if(!mMesh.isNull()) + mMesh->renderLinks(d); + d.render(); + + // Now draw all the 2d stuff! + GFX->setClipRect(updateRect); +} + +bool GuiNavEditorCtrl::getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos) +{ + // Find clicked point on the terrain + + Point3F startPnt = event.pos; + Point3F endPnt = event.pos + event.vec * 1000.0f; + + RayInfo ri; + bool hit; + + hit = gServerContainer.castRay(startPnt, endPnt, StaticShapeObjectType, &ri); + tpos = ri.point; + + return hit; +} + +void GuiNavEditorCtrl::setMode(String mode, bool sourceShortcut = false) +{ + mMode = mode; + Con::executef(this, "onModeSet", mode); + + if(sourceShortcut) + Con::executef(this, "paletteSync", mode); +} + +void GuiNavEditorCtrl::submitUndo(const UTF8 *name) +{ + // Grab the mission editor undo manager. + UndoManager *undoMan = NULL; + if(!Sim::findObject("EUndoManager", undoMan)) + { + Con::errorf("GuiNavEditorCtrl::submitUndo() - EUndoManager not found!"); + return; + } + + // Setup the action. + GuiNavEditorUndoAction *action = new GuiNavEditorUndoAction(name); + + action->mNavEditor = this; + + undoMan->addAction(action); +} + +void GuiNavEditorCtrl::_prepRenderImage(SceneManager* sceneGraph, const SceneRenderState* state) +{ + /*if(isAwake() && River::smEditorOpen && mSelRiver) + { + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->type = RenderPassManager::RIT_Editor; + ri->renderDelegate.bind(this, &GuiNavEditorCtrl::_renderSelectedRiver); + ri->defaultKey = 100; + state->getRenderPass()->addInst(ri); + }*/ +} + +ConsoleMethod(GuiNavEditorCtrl, getMode, const char*, 2, 2, "") +{ + return object->getMode(); +} + +ConsoleMethod(GuiNavEditorCtrl, setMode, void, 3, 3, "setMode(String mode)") +{ + String newMode = (argv[2]); + object->setMode(newMode); +} diff --git a/Engine/source/navigation/guiNavEditorCtrl.h b/Engine/source/navigation/guiNavEditorCtrl.h new file mode 100644 index 000000000..3250f6f54 --- /dev/null +++ b/Engine/source/navigation/guiNavEditorCtrl.h @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _GUINAVEDITORCTRL_H_ +#define _GUINAVEDITORCTRL_H_ + +#ifndef _EDITTSCTRL_H_ +#include "gui/worldEditor/editTSCtrl.h" +#endif +#ifndef _UNDO_H_ +#include "util/undo.h" +#endif +#ifndef _GIZMO_H_ +#include "gui/worldEditor/gizmo.h" +#endif + +#include "navMesh.h" +#include "T3D/aiPlayer.h" + +struct ObjectRenderInst; +class SceneManager; +class SceneRenderState; +class BaseMatInstance; + +class GuiNavEditorCtrl : public EditTSCtrl +{ + typedef EditTSCtrl Parent; + friend class GuiNavEditorUndoAction; + +public: + static const String mSelectMode; + static const String mLinkMode; + static const String mCoverMode; + static const String mTileMode; + static const String mTestMode; + + GuiNavEditorCtrl(); + ~GuiNavEditorCtrl(); + + DECLARE_CONOBJECT(GuiNavEditorCtrl); + + /// @name SimObject + /// @{ + + bool onAdd(); + static void initPersistFields(); + + /// @} + + /// @name GuiControl + /// @{ + + virtual void onSleep(); + virtual void onRender(Point2I offset, const RectI &updateRect); + + /// @} + + /// @name EditTSCtrl + /// @{ + + void get3DCursor(GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_); + bool get3DCentre(Point3F &pos); + void on3DMouseDown(const Gui3DMouseEvent & event); + void on3DMouseUp(const Gui3DMouseEvent & event); + void on3DMouseMove(const Gui3DMouseEvent & event); + void on3DMouseDragged(const Gui3DMouseEvent & event); + void on3DMouseEnter(const Gui3DMouseEvent & event); + void on3DMouseLeave(const Gui3DMouseEvent & event); + void updateGuiInfo(); + void renderScene(const RectI & updateRect); + + /// @} + + /// @name GuiNavEditorCtrl + /// @{ + + bool getStaticPos(const Gui3DMouseEvent & event, Point3F &tpos); + + void setMode(String mode, bool sourceShortcut); + String getMode() { return mMode; } + + void selectMesh(NavMesh *mesh); + void deselect(); + + S32 getMeshId(); + S32 getPlayerId(); + + String mSpawnClass; + String mSpawnDatablock; + + void deleteLink(); + void setLinkFlags(const LinkData &d); + + void buildTile(); + + void spawnPlayer(const Point3F &pos); + + /// @} + +protected: + + void _prepRenderImage(SceneManager* sceneGraph, const SceneRenderState* sceneState); + + void submitUndo(const UTF8 *name = "Action"); + + GFXStateBlockRef mZDisableSB; + GFXStateBlockRef mZEnableSB; + + bool mSavedDrag; + bool mIsDirty; + + String mMode; + + /// Currently-selected NavMesh. + SimObjectPtr mMesh; + + /// @name Link mode + /// @{ + + Point3F mLinkStart; + S32 mCurLink; + S32 mLink; + + /// @} + + /// @name Tile mode + /// @{ + + S32 mCurTile; + S32 mTile; + + duDebugDrawTorque dd; + + /// @} + + /// @name Test mode + /// @{ + + SimObjectPtr mPlayer; + SimObjectPtr mCurPlayer; + + /// @} + + Gui3DMouseEvent mLastMouseEvent; + +#define InvalidMousePoint Point2I(-100,-100) + Point2I mStartDragMousePoint; +}; + +class GuiNavEditorUndoAction : public UndoAction +{ +public: + GuiNavEditorUndoAction(const UTF8* actionName) : UndoAction(actionName) + { + } + + GuiNavEditorCtrl *mNavEditor; + + SimObjectId mObjId; + F32 mMetersPerSegment; + U32 mSegmentsPerBatch; + + virtual void undo(); + virtual void redo() { undo(); } +}; + +#endif + + + diff --git a/Engine/source/navigation/navContext.cpp b/Engine/source/navigation/navContext.cpp new file mode 100644 index 000000000..b338b314c --- /dev/null +++ b/Engine/source/navigation/navContext.cpp @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#include "navContext.h" +#include "console/sim.h" + +void NavContext::doResetLog() +{ +} + +void NavContext::log(const rcLogCategory category, const String &msg) +{ + doLog(category, msg.c_str(), msg.length()); +} + +void NavContext::doLog(const rcLogCategory category, const char* msg, const int len) +{ + if(category == RC_LOG_ERROR) + Con::errorf(msg); + else + Con::printf(msg); +} + +void NavContext::doResetTimers() +{ + for(U32 i = 0; i < RC_MAX_TIMERS; i++) + { + mTimers[i][0] = -1; + mTimers[i][1] = -1; + } +} + +void NavContext::doStartTimer(const rcTimerLabel label) +{ + // Store starting time. + mTimers[label][0] = Platform::getRealMilliseconds(); +} + +void NavContext::doStopTimer(const rcTimerLabel label) +{ + // Compute final time based on starting time. + mTimers[label][1] = Platform::getRealMilliseconds() - mTimers[label][0]; +} + +int NavContext::doGetAccumulatedTime(const rcTimerLabel label) const +{ + return mTimers[label][1] == -1 + ? Platform::getRealMilliseconds() - mTimers[label][0] + : mTimers[label][1]; +} diff --git a/Engine/source/navigation/navContext.h b/Engine/source/navigation/navContext.h new file mode 100644 index 000000000..1033465c6 --- /dev/null +++ b/Engine/source/navigation/navContext.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2014 Daniel Buckmaster +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +#ifndef _NAV_CONTEXT_H_ +#define _NAV_CONTEXT_H_ + +#include "torqueRecast.h" +#include + +/// @brief Implements the rcContext interface in Torque. +class NavContext: public rcContext { +public: + /// Default constructor. + NavContext() : rcContext(true) { doResetTimers(); } + + void log(const rcLogCategory category, const String &msg); + +protected: + /// Clears all log entries. + virtual void doResetLog(); + + /// Logs a message. + /// @param[in] category The category of the message. + /// @param[in] msg The formatted message. + /// @param[in] len The length of the formatted message. + virtual void doLog(const rcLogCategory category, const char* msg, const int len); + + /// Clears all timers. (Resets all to unused.) + virtual void doResetTimers(); + + /// Starts the specified performance timer. + /// @param[in] label The category of timer. + virtual void doStartTimer(const rcTimerLabel label); + + /// Stops the specified performance timer. + /// @param[in] label The category of the timer. + virtual void doStopTimer(const rcTimerLabel label); + + /// Returns the total accumulated time of the specified performance timer. + /// @param[in] label The category of the timer. + /// @return The accumulated time of the timer, or -1 if timers are disabled or the timer has never been started. + virtual int doGetAccumulatedTime(const rcTimerLabel label) const; + +private: + /// Store start time and final time for each timer. + S32 mTimers[RC_MAX_TIMERS][2]; +}; + +#endif diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 17040b3a6..c078988be 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2013 GarageGames, LLC +// Copyright (c) 2014 Daniel Buckmaster // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,6 +23,7 @@ #include #include "navMesh.h" +#include "navContext.h" #include #include @@ -46,13 +47,111 @@ IMPLEMENT_CO_NETOBJECT_V1(NavMesh); const U32 NavMesh::mMaxVertsPerPoly = 3; +SimObjectPtr NavMesh::smServerSet = NULL; + +ImplementEnumType(NavMeshWaterMethod, + "The method used to include water surfaces in the NavMesh.\n") + { NavMesh::Ignore, "Ignore", "Ignore all water surfaces.\n" }, + { NavMesh::Solid, "Solid", "Treat water surfaces as solid and walkable.\n" }, + { NavMesh::Impassable, "Impassable", "Treat water as an impassable obstacle.\n" }, +EndImplementEnumType; + +SimSet *NavMesh::getServerSet() +{ + if(!smServerSet) + { + SimSet *set = NULL; + if(Sim::findObject("ServerNavMeshSet", set)) + smServerSet = set; + else + { + smServerSet = new SimSet(); + smServerSet->registerObject("ServerNavMeshSet"); + Sim::getRootGroup()->addObject(smServerSet); + } + } + return smServerSet; +} + +SimObjectPtr NavMesh::smEventManager = NULL; + +EventManager *NavMesh::getEventManager() +{ + if(!smEventManager) + { + smEventManager = new EventManager(); + smEventManager->registerObject("NavEventManager"); + Sim::getRootGroup()->addObject(smEventManager); + smEventManager->setMessageQueue("NavEventManagerQueue"); + smEventManager->registerEvent("NavMeshCreated"); + smEventManager->registerEvent("NavMeshRemoved"); + smEventManager->registerEvent("NavMeshStartUpdate"); + smEventManager->registerEvent("NavMeshUpdate"); + smEventManager->registerEvent("NavMeshTileUpdate"); + smEventManager->registerEvent("NavMeshUpdateBox"); + smEventManager->registerEvent("NavMeshObstacleAdded"); + smEventManager->registerEvent("NavMeshObstacleRemoved"); + } + return smEventManager; +} + +DefineConsoleFunction(getNavMeshEventManager, S32, (),, + "@brief Get the EventManager object for all NavMesh updates.") +{ + return NavMesh::getEventManager()->getId(); +} + +DefineConsoleFunction(WalkaboutUpdateAll, void, (S32 objid, bool remove), (0, false), + "@brief Update all NavMesh tiles that intersect the given object's world box.") +{ + SceneObject *obj; + if(!Sim::findObject(objid, obj)) + return; + if(remove) + obj->disableCollision(); + SimSet *set = NavMesh::getServerSet(); + for(U32 i = 0; i < set->size(); i++) + { + NavMesh *m = static_cast(set->at(i)); + m->buildTiles(obj->getWorldBox()); + } + if(remove) + obj->enableCollision(); +} + +DefineConsoleFunction(WalkaboutUpdateMesh, void, (S32 meshid, S32 objid, bool remove), (0, 0, false), + "@brief Update all tiles in a given NavMesh that intersect the given object's world box.") +{ + NavMesh *mesh; + SceneObject *obj; + if(!Sim::findObject(meshid, mesh)) + { + Con::errorf("WalkaboutUpdateMesh: cannot find NavMesh %d", meshid); + return; + } + if(!Sim::findObject(objid, obj)) + { + Con::errorf("WalkaboutUpdateMesh: cannot find SceneObject %d", objid); + return; + } + if(remove) + obj->disableCollision(); + mesh->buildTiles(obj->getWorldBox()); + if(remove) + obj->enableCollision(); +} + NavMesh::NavMesh() { mTypeMask |= StaticShapeObjectType | MarkerObjectType; mFileName = StringTable->insert(""); mNetFlags.clear(Ghostable); + mSaveIntermediates = true; nm = NULL; + ctx = NULL; + + mWaterMethod = Ignore; dMemset(&cfg, 0, sizeof(cfg)); mCellSize = mCellHeight = 0.2f; @@ -70,6 +169,16 @@ NavMesh::NavMesh() mTileSize = 10.0f; mMaxPolysPerTile = 128; + mSmallCharacters = false; + mRegularCharacters = true; + mLargeCharacters = false; + mVehicles = false; + + mCoverSet = StringTable->insert(""); + mInnerCover = false; + mCoverDist = 1.0f; + mPeekDist = 0.7f; + mAlwaysRender = false; mBuilding = false; @@ -79,6 +188,8 @@ NavMesh::~NavMesh() { dtFreeNavMesh(nm); nm = NULL; + delete ctx; + ctx = NULL; } bool NavMesh::setProtectedDetailSampleDist(void *obj, const char *index, const char *data) @@ -122,6 +233,9 @@ void NavMesh::initPersistFields() addField("fileName", TypeString, Offset(mFileName, NavMesh), "Name of the data file to store this navmesh in (relative to engine executable)."); + addField("waterMethod", TYPEID(), Offset(mWaterMethod, NavMesh), + "The method to use to handle water surfaces."); + addFieldV("cellSize", TypeF32, Offset(mCellSize, NavMesh), &ValidCellSize, "Length/width of a voxel."); addFieldV("cellHeight", TypeF32, Offset(mCellHeight, NavMesh), &ValidCellSize, @@ -138,8 +252,32 @@ void NavMesh::initPersistFields() addFieldV("walkableSlope", TypeF32, Offset(mWalkableSlope, NavMesh), &ValidSlopeAngle, "Maximum walkable slope in degrees."); + addField("smallCharacters", TypeBool, Offset(mSmallCharacters, NavMesh), + "Is this NavMesh for smaller-than-usual characters?"); + addField("regularCharacters", TypeBool, Offset(mRegularCharacters, NavMesh), + "Is this NavMesh for regular-sized characters?"); + addField("largeCharacters", TypeBool, Offset(mLargeCharacters, NavMesh), + "Is this NavMesh for larger-than-usual characters?"); + addField("vehicles", TypeBool, Offset(mVehicles, NavMesh), + "Is this NavMesh for characters driving vehicles?"); + endGroup("NavMesh Options"); + addGroup("NavMesh Annotations"); + + addField("coverGroup", TypeString, Offset(mCoverSet, NavMesh), + "Name of the SimGroup to store cover points in."); + + addField("innerCover", TypeBool, Offset(mInnerCover, NavMesh), + "Add cover points everywhere, not just on corners?"); + + addField("coverDist", TypeF32, Offset(mCoverDist, NavMesh), + "Distance from the edge of the NavMesh to search for cover."); + addField("peekDist", TypeF32, Offset(mPeekDist, NavMesh), + "Distance to the side of each cover point that peeking happens."); + + endGroup("NavMesh Annotations"); + addGroup("NavMesh Rendering"); addProtectedField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavMesh), @@ -178,8 +316,8 @@ bool NavMesh::onAdd() if(!Parent::onAdd()) return false; - mObjBox.set(Point3F(-10.0f, -10.0f, -1.0f), - Point3F( 10.0f, 10.0f, 1.0f)); + mObjBox.set(Point3F(-1.0f, -1.0f, -1.0f), + Point3F( 1.0f, 1.0f, 1.0f)); resetWorldBox(); addToScene(); @@ -193,7 +331,19 @@ bool NavMesh::onAdd() if(isServerObject()) { +#ifdef WALKABOUT_DEMO + if(getServerSet()->size() >= 1) + { + Con::errorf("Sorry, the demo binary only allows one NavMesh to exist at a time."); + Con::executef("OnWalkaboutDemoLimit"); + return false; + } +#endif + getServerSet()->addObject(this); + ctx = new NavContext(); setProcessTick(true); + if(getEventManager()) + getEventManager()->postEvent("NavMeshCreated", getIdString()); } load(); @@ -203,6 +353,9 @@ bool NavMesh::onAdd() void NavMesh::onRemove() { + if(getEventManager()) + getEventManager()->postEvent("NavMeshRemoved", getIdString()); + removeFromScene(); Parent::onRemove(); @@ -218,13 +371,222 @@ void NavMesh::setScale(const VectorF &scale) Parent::setScale(scale); } +S32 NavMesh::addLink(const Point3F &from, const Point3F &to, U32 flags) +{ + Point3F rcFrom = DTStoRC(from), rcTo = DTStoRC(to); + mLinkVerts.push_back(rcFrom.x); + mLinkVerts.push_back(rcFrom.y); + mLinkVerts.push_back(rcFrom.z); + mLinkVerts.push_back(rcTo.x); + mLinkVerts.push_back(rcTo.y); + mLinkVerts.push_back(rcTo.z); + mLinksUnsynced.push_back(true); + mLinkRads.push_back(mWalkableRadius); + mLinkDirs.push_back(0); + mLinkAreas.push_back(OffMeshArea); + if (flags == 0) { + Point3F dir = to - from; + F32 drop = -dir.z; + dir.z = 0; + // If we drop more than we travel horizontally, we're a drop link. + if(drop > dir.len()) + mLinkFlags.push_back(DropFlag); + else + mLinkFlags.push_back(JumpFlag); + } + mLinkIDs.push_back(1000 + mCurLinkID); + mLinkSelectStates.push_back(Unselected); + mDeleteLinks.push_back(false); + mCurLinkID++; + return mLinkIDs.size() - 1; +} + +DefineEngineMethod(NavMesh, addLink, S32, (Point3F from, Point3F to, U32 flags), (0), + "Add a link to this NavMesh between two points.\n\n" + "") +{ + return object->addLink(from, to, flags); +} + +S32 NavMesh::getLink(const Point3F &pos) +{ + for(U32 i = 0; i < mLinkIDs.size(); i++) + { + if(mDeleteLinks[i]) + continue; + SphereF start(getLinkStart(i), mLinkRads[i]); + SphereF end(getLinkEnd(i), mLinkRads[i]); + if(start.isContained(pos) || end.isContained(pos)) + return i; + } + return -1; +} + +DefineEngineMethod(NavMesh, getLink, S32, (Point3F pos),, + "Get the off-mesh link closest to a given world point.") +{ + return object->getLink(pos); +} + +S32 NavMesh::getLinkCount() +{ + return mLinkIDs.size(); +} + +DefineEngineMethod(NavMesh, getLinkCount, S32, (),, + "Return the number of links this mesh has.") +{ + return object->getLinkCount(); +} + +LinkData NavMesh::getLinkFlags(U32 idx) +{ + if(idx < mLinkIDs.size()) + { + return LinkData(mLinkFlags[idx]); + } + return LinkData(); +} + +DefineEngineMethod(NavMesh, getLinkFlags, S32, (U32 id),, + "Get the flags set for a particular off-mesh link.") +{ + return object->getLinkFlags(id).getFlags(); +} + +void NavMesh::setLinkFlags(U32 idx, const LinkData &d) +{ + if(idx < mLinkIDs.size()) + { + mLinkFlags[idx] = d.getFlags(); + mLinksUnsynced[idx] = true; + } +} + +DefineEngineMethod(NavMesh, setLinkFlags, void, (U32 id, U32 flags),, + "Set the flags of a particular off-mesh link.") +{ + LinkData d(flags); + object->setLinkFlags(id, d); +} + +Point3F NavMesh::getLinkStart(U32 idx) +{ + return RCtoDTS(Point3F( + mLinkVerts[idx*6], + mLinkVerts[idx*6 + 1], + mLinkVerts[idx*6 + 2])); +} + +DefineEngineMethod(NavMesh, getLinkStart, Point3F, (U32 id),, + "Get the starting point of an off-mesh link.") +{ + return object->getLinkStart(id); +} + +Point3F NavMesh::getLinkEnd(U32 idx) +{ + return RCtoDTS(Point3F( + mLinkVerts[idx*6 + 3], + mLinkVerts[idx*6 + 4], + mLinkVerts[idx*6 + 5])); +} + +DefineEngineMethod(NavMesh, getLinkEnd, Point3F, (U32 id),, + "Get the ending point of an off-mesh link.") +{ + return object->getLinkEnd(id); +} + +void NavMesh::selectLink(U32 idx, bool select, bool hover) +{ + if(idx < mLinkIDs.size()) + { + if(!select) + mLinkSelectStates[idx] = Unselected; + else + mLinkSelectStates[idx] = hover ? Hovered : Selected; + } +} + +void NavMesh::eraseLink(U32 i) +{ + mLinkVerts.erase(i*6, 6); + mLinksUnsynced.erase(i); + mLinkRads.erase(i); + mLinkDirs.erase(i); + mLinkAreas.erase(i); + mLinkFlags.erase(i); + mLinkIDs.erase(i); + mLinkSelectStates.erase(i); + mDeleteLinks.erase(i); +} + +void NavMesh::eraseLinks() +{ + mLinkVerts.clear(); + mLinksUnsynced.clear(); + mLinkRads.clear(); + mLinkDirs.clear(); + mLinkAreas.clear(); + mLinkFlags.clear(); + mLinkIDs.clear(); + mLinkSelectStates.clear(); + mDeleteLinks.clear(); +} + +void NavMesh::setLinkCount(U32 c) +{ + eraseLinks(); + mLinkVerts.setSize(c * 6); + mLinksUnsynced.setSize(c); + mLinkRads.setSize(c); + mLinkDirs.setSize(c); + mLinkAreas.setSize(c); + mLinkFlags.setSize(c); + mLinkIDs.setSize(c); + mLinkSelectStates.setSize(c); + mDeleteLinks.setSize(c); +} + +void NavMesh::deleteLink(U32 idx) +{ + if(idx < mLinkIDs.size()) + { + mDeleteLinks[idx] = true; + if(mLinksUnsynced[idx]) + eraseLink(idx); + else + mLinksUnsynced[idx] = true; + } +} + +DefineEngineMethod(NavMesh, deleteLink, void, (U32 id),, + "Delete a given off-mesh link.") +{ + object->deleteLink(id); +} + +DefineEngineMethod(NavMesh, deleteLinks, void, (),, + "Deletes all off-mesh links on this NavMesh.") +{ + //object->eraseLinks(); +} + bool NavMesh::build(bool background, bool saveIntermediates) { if(mBuilding) cancelBuild(); + else + { + if(getEventManager()) + getEventManager()->postEvent("NavMeshStartUpdate", getIdString()); + } mBuilding = true; + ctx->startTimer(RC_TIMER_TOTAL); + dtFreeNavMesh(nm); // Allocate a new navmesh. nm = dtAllocNavMesh(); @@ -251,6 +613,19 @@ bool NavMesh::build(bool background, bool saveIntermediates) return false; } + // Update links to be deleted. + for(U32 i = 0; i < mLinkIDs.size();) + { + if(mDeleteLinks[i]) + eraseLink(i); + else + i++; + } + mLinksUnsynced.fill(false); + mCurLinkID = 0; + + mSaveIntermediates = saveIntermediates; + updateTiles(true); if(!background) @@ -271,6 +646,7 @@ DefineEngineMethod(NavMesh, build, bool, (bool background, bool save), (true, fa void NavMesh::cancelBuild() { while(mDirtyTiles.size()) mDirtyTiles.pop(); + ctx->stopTimer(RC_TIMER_TOTAL); mBuilding = false; } @@ -338,6 +714,7 @@ void NavMesh::updateTiles(bool dirty) return; mTiles.clear(); + mTileData.clear(); while(mDirtyTiles.size()) mDirtyTiles.pop(); const Box3F &box = DTStoRC(getWorldBox()); @@ -373,6 +750,9 @@ void NavMesh::updateTiles(bool dirty) if(dirty) mDirtyTiles.push(mTiles.size() - 1); + + if(mSaveIntermediates) + mTileData.increment(); } } } @@ -392,27 +772,47 @@ void NavMesh::buildNextTile() const Tile &tile = mTiles[i]; // Intermediate data for tile build. TileData tempdata; + TileData &tdata = mSaveIntermediates ? mTileData[i] : tempdata; // Generate navmesh for this tile. U32 dataSize = 0; - unsigned char* data = buildTileData(tile, tempdata, dataSize); + unsigned char* data = buildTileData(tile, tdata, dataSize); if(data) { // Remove any previous data. nm->removeTile(nm->getTileRefAt(tile.x, tile.y, 0), 0, 0); // Add new data (navmesh owns and deletes the data). dtStatus status = nm->addTile(data, dataSize, DT_TILE_FREE_DATA, 0, 0); - + int success = 1; if(dtStatusFailed(status)) { + success = 0; dtFree(data); } + if(getEventManager()) + { + String str = String::ToString("%d %d %d (%d, %d) %d %.3f %s", + getId(), + i, mTiles.size(), + tile.x, tile.y, + success, + ctx->getAccumulatedTime(RC_TIMER_TOTAL) / 1000.0f, + castConsoleTypeToString(tile.box)); + getEventManager()->postEvent("NavMeshTileUpdate", str.c_str()); + setMaskBits(LoadFlag); + } } // Did we just build the last tile? if(!mDirtyTiles.size()) { + ctx->stopTimer(RC_TIMER_TOTAL); + if(getEventManager()) + { + String str = String::ToString("%d %.3f", getId(), ctx->getAccumulatedTime(RC_TIMER_TOTAL) / 1000.0f); + getEventManager()->postEvent("NavMeshUpdate", str.c_str()); + setMaskBits(LoadFlag); + } mBuilding = false; } - setMaskBits(BuildFlag); } } @@ -439,7 +839,16 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat info.context = PLC_Navigation; info.boundingBox = box; info.polyList = &data.geom; - getContainer()->findObjects(box, StaticObjectType, buildCallback, &info); + info.key = this; + getContainer()->findObjects(box, StaticShapeObjectType | TerrainObjectType, buildCallback, &info); + + // Parse water objects into the same list, but remember how much geometry was /not/ water. + U32 nonWaterVertCount = data.geom.getVertCount(); + U32 nonWaterTriCount = data.geom.getTriCount(); + if(mWaterMethod != Ignore) + { + getContainer()->findObjects(box, WaterObjectType, buildCallback, &info); + } // Check for no geometry. if(!data.geom.getVertCount()) @@ -450,9 +859,6 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat width = cfg.tileSize + cfg.borderSize * 2; height = cfg.tileSize + cfg.borderSize * 2; - // Create a dummy context. - rcContext ctx(false); - // Create a heightfield to voxelise our input geometry. data.hf = rcAllocHeightfield(); if(!data.hf) @@ -460,7 +866,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat Con::errorf("Out of memory (rcHeightField) for NavMesh %s", getIdString()); return NULL; } - if(!rcCreateHeightfield(&ctx, *data.hf, width, height, tileBmin, tileBmax, cfg.cs, cfg.ch)) + if(!rcCreateHeightfield(ctx, *data.hf, width, height, tileBmin, tileBmax, cfg.cs, cfg.ch)) { Con::errorf("Could not generate rcHeightField for NavMesh %s", getIdString()); return NULL; @@ -474,20 +880,32 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat } dMemset(areas, 0, data.geom.getTriCount() * sizeof(unsigned char)); - // Filter triangles by angle and rasterize. - rcMarkWalkableTriangles(&ctx, cfg.walkableSlopeAngle, + // Mark walkable triangles with the appropriate area flags, and rasterize. + if(mWaterMethod == Solid) + { + // Treat water as solid: i.e. mark areas as walkable based on angle. + rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, + data.geom.getVerts(), data.geom.getVertCount(), + data.geom.getTris(), data.geom.getTriCount(), areas); + } + else + { + // Treat water as impassable: leave all area flags 0. + rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, + data.geom.getVerts(), nonWaterVertCount, + data.geom.getTris(), nonWaterTriCount, areas); + } + rcRasterizeTriangles(ctx, data.geom.getVerts(), data.geom.getVertCount(), - data.geom.getTris(), data.geom.getTriCount(), areas); - rcRasterizeTriangles(&ctx, data.geom.getVerts(), data.geom.getVertCount(), data.geom.getTris(), areas, data.geom.getTriCount(), *data.hf, cfg.walkableClimb); delete[] areas; // Filter out areas with low ceilings and other stuff. - rcFilterLowHangingWalkableObstacles(&ctx, cfg.walkableClimb, *data.hf); - rcFilterLedgeSpans(&ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf); - rcFilterWalkableLowHeightSpans(&ctx, cfg.walkableHeight, *data.hf); + rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *data.hf); + rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf); + rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *data.hf); data.chf = rcAllocCompactHeightfield(); if(!data.chf) @@ -495,12 +913,12 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat Con::errorf("Out of memory (rcCompactHeightField) for NavMesh %s", getIdString()); return NULL; } - if(!rcBuildCompactHeightfield(&ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf, *data.chf)) + if(!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, *data.hf, *data.chf)) { Con::errorf("Could not generate rcCompactHeightField for NavMesh %s", getIdString()); return NULL; } - if(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *data.chf)) + if(!rcErodeWalkableArea(ctx, cfg.walkableRadius, *data.chf)) { Con::errorf("Could not erode walkable area for NavMesh %s", getIdString()); return NULL; @@ -510,12 +928,12 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat // Todo: mark areas here. //const ConvexVolume* vols = m_geom->getConvexVolumes(); //for (int i = 0; i < m_geom->getConvexVolumeCount(); ++i) - //rcMarkConvexPolyArea(m_NULL, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); + //rcMarkConvexPolyArea(m_ctx, vols[i].verts, vols[i].nverts, vols[i].hmin, vols[i].hmax, (unsigned char)vols[i].area, *m_chf); //-------------------------- if(false) { - if(!rcBuildRegionsMonotone(&ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + if(!rcBuildRegionsMonotone(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) { Con::errorf("Could not build regions for NavMesh %s", getIdString()); return NULL; @@ -523,12 +941,12 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat } else { - if(!rcBuildDistanceField(&ctx, *data.chf)) + if(!rcBuildDistanceField(ctx, *data.chf)) { Con::errorf("Could not build distance field for NavMesh %s", getIdString()); return NULL; } - if(!rcBuildRegions(&ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) + if(!rcBuildRegions(ctx, *data.chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea)) { Con::errorf("Could not build regions for NavMesh %s", getIdString()); return NULL; @@ -541,7 +959,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat Con::errorf("Out of memory (rcContourSet) for NavMesh %s", getIdString()); return NULL; } - if(!rcBuildContours(&ctx, *data.chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *data.cs)) + if(!rcBuildContours(ctx, *data.chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *data.cs)) { Con::errorf("Could not construct rcContourSet for NavMesh %s", getIdString()); return NULL; @@ -558,7 +976,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat Con::errorf("Out of memory (rcPolyMesh) for NavMesh %s", getIdString()); return NULL; } - if(!rcBuildPolyMesh(&ctx, *data.cs, cfg.maxVertsPerPoly, *data.pm)) + if(!rcBuildPolyMesh(ctx, *data.cs, cfg.maxVertsPerPoly, *data.pm)) { Con::errorf("Could not construct rcPolyMesh for NavMesh %s", getIdString()); return NULL; @@ -570,7 +988,7 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat Con::errorf("Out of memory (rcPolyMeshDetail) for NavMesh %s", getIdString()); return NULL; } - if(!rcBuildPolyMeshDetail(&ctx, *data.pm, *data.chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *data.pmd)) + if(!rcBuildPolyMeshDetail(ctx, *data.pm, *data.chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *data.pmd)) { Con::errorf("Could not construct rcPolyMeshDetail for NavMesh %s", getIdString()); return NULL; @@ -612,6 +1030,14 @@ unsigned char *NavMesh::buildTileData(const Tile &tile, TileData &data, U32 &dat params.detailTris = data.pmd->tris; params.detailTriCount = data.pmd->ntris; + params.offMeshConVerts = mLinkVerts.address(); + params.offMeshConRad = mLinkRads.address(); + params.offMeshConDir = mLinkDirs.address(); + params.offMeshConAreas = mLinkAreas.address(); + params.offMeshConFlags = mLinkFlags.address(); + params.offMeshConUserID = mLinkIDs.address(); + params.offMeshConCount = mLinkIDs.size(); + params.walkableHeight = mWalkableHeight; params.walkableRadius = mWalkableRadius; params.walkableClimb = mWalkableClimb; @@ -654,6 +1080,8 @@ void NavMesh::buildTiles(const Box3F &box) // Mark as dirty. mDirtyTiles.push(i); } + if(mDirtyTiles.size()) + ctx->startTimer(RC_TIMER_TOTAL); } DefineEngineMethod(NavMesh, buildTiles, void, (Box3F box),, @@ -667,9 +1095,203 @@ void NavMesh::buildTile(const U32 &tile) if(tile < mTiles.size()) { mDirtyTiles.push(tile); + ctx->startTimer(RC_TIMER_TOTAL); } } +void NavMesh::buildLinks() +{ + // Make sure we've already built or loaded. + if(!nm) + return; + // Iterate over tiles. + for(U32 i = 0; i < mTiles.size(); i++) + { + const Tile &tile = mTiles[i]; + // Iterate over links + for(U32 j = 0; j < mLinkIDs.size(); j++) + { + if(tile.box.isContained(getLinkStart(j)) || + tile.box.isContained(getLinkEnd(j)) && + mLinksUnsynced[j]) + { + // Mark tile for build. + mDirtyTiles.push(i); + // Delete link if necessary + if(mDeleteLinks[j]) + { + eraseLink(j); + j--; + } + else + mLinksUnsynced[j] = false; + } + } + } + if(mDirtyTiles.size()) + ctx->startTimer(RC_TIMER_TOTAL); +} + +DefineEngineMethod(NavMesh, buildLinks, void, (),, + "@brief Build tiles of this mesh where there are unsynchronised links.") +{ + object->buildLinks(); +} + +void NavMesh::deleteCoverPoints() +{ + SimSet *set = NULL; + if(Sim::findObject(mCoverSet, set)) + set->deleteAllObjects(); +} + +DefineEngineMethod(NavMesh, deleteCoverPoints, void, (),, + "@brief Remove all cover points for this NavMesh.") +{ + object->deleteCoverPoints(); +} + +bool NavMesh::createCoverPoints() +{ + if(!nm || !isServerObject()) + return false; + + SimSet *set = NULL; + if(Sim::findObject(mCoverSet, set)) + { + set->deleteAllObjects(); + } + else + { + set = new SimGroup(); + if(set->registerObject(mCoverSet)) + { + getGroup()->addObject(set); + } + else + { + delete set; + set = getGroup(); + } + } + + dtNavMeshQuery *query = dtAllocNavMeshQuery(); + if(!query || dtStatusFailed(query->init(nm, 1))) + return false; + + dtQueryFilter f; + + // Iterate over all polys in our navmesh. + const int MAX_SEGS = 6; + for(U32 i = 0; i < nm->getMaxTiles(); ++i) + { + const dtMeshTile* tile = ((const dtNavMesh*)nm)->getTile(i); + if(!tile->header) continue; + const dtPolyRef base = nm->getPolyRefBase(tile); + for(U32 j = 0; j < tile->header->polyCount; ++j) + { + const dtPolyRef ref = base | j; + float segs[MAX_SEGS*6]; + int nsegs = 0; + query->getPolyWallSegments(ref, &f, segs, NULL, &nsegs, MAX_SEGS); + for(int j = 0; j < nsegs; ++j) + { + const float* sa = &segs[j*6]; + const float* sb = &segs[j*6+3]; + Point3F a = RCtoDTS(sa), b = RCtoDTS(sb); + F32 len = (b - a).len(); + if(len < mWalkableRadius * 2) + continue; + Point3F edge = b - a; + edge.normalize(); + // Number of points to try placing - for now, one at each end. + U32 pointCount = (len > mWalkableRadius * 4) ? 2 : 1; + for(U32 i = 0; i < pointCount; i++) + { + MatrixF mat; + Point3F pos; + // If we're only placing one point, put it in the middle. + if(pointCount == 1) + pos = a + edge * len / 2; + // Otherwise, stand off from edge ends. + else + { + if(i % 2) + pos = a + edge * (i/2+1) * mWalkableRadius; + else + pos = b - edge * (i/2+1) * mWalkableRadius; + } + CoverPointData data; + if(testEdgeCover(pos, edge, data)) + { + CoverPoint *m = new CoverPoint(); + if(!m->registerObject()) + delete m; + else + { + m->setTransform(data.trans); + m->setSize(data.size); + m->setPeek(data.peek[0], data.peek[1], data.peek[2]); + if(set) + set->addObject(m); + } + } + } + } + } + } + return true; +} + +DefineEngineMethod(NavMesh, createCoverPoints, bool, (),, + "@brief Create cover points for this NavMesh.") +{ + return object->createCoverPoints(); +} + +bool NavMesh::testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointData &data) +{ + data.peek[0] = data.peek[1] = data.peek[2] = false; + // Get the edge normal. + Point3F norm; + mCross(dir, Point3F(0, 0, 1), &norm); + RayInfo ray; + U32 hits = 0; + for(U32 j = 0; j < CoverPoint::NumSizes; j++) + { + Point3F test = pos + Point3F(0.0f, 0.0f, mWalkableHeight * j / CoverPoint::NumSizes); + if(getContainer()->castRay(test, test + norm * mCoverDist, StaticObjectType, &ray)) + { + // Test peeking. + Point3F left = test + dir * mPeekDist; + data.peek[0] = !getContainer()->castRay(test, left, StaticObjectType, &ray) + && !getContainer()->castRay(left, left + norm * mCoverDist, StaticObjectType, &ray); + + Point3F right = test - dir * mPeekDist; + data.peek[1] = !getContainer()->castRay(test, right, StaticObjectType, &ray) + && !getContainer()->castRay(right, right + norm * mCoverDist, StaticObjectType, &ray); + + Point3F over = test + Point3F(0, 0, 1) * 0.2f; + data.peek[2] = !getContainer()->castRay(test, over, StaticObjectType, &ray) + && !getContainer()->castRay(over, over + norm * mCoverDist, StaticObjectType, &ray); + + if(mInnerCover || data.peek[0] || data.peek[1] || data.peek[2]) + hits++; + // If we couldn't peek here, we may be able to peek further up. + } + else + // No cover at this height - break off. + break; + } + if(hits > 0) + { + data.size = (CoverPoint::Size)(hits - 1); + data.trans = MathUtils::createOrientFromDir(norm); + data.trans.setPosition(pos); + } + return hits > 0; +} + void NavMesh::renderToDrawer() { dd.clear(); @@ -751,6 +1373,66 @@ void NavMesh::render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInsta } } +void NavMesh::renderLinks(duDebugDraw &dd) +{ + if(mBuilding) + return; + dd.depthMask(false); + dd.begin(DU_DRAW_LINES); + for(U32 i = 0; i < mLinkIDs.size(); i++) + { + unsigned int col = 0; + switch(mLinkSelectStates[i]) + { + case Unselected: col = mLinksUnsynced[i] ? duRGBA(255, 0, 0, 200) : duRGBA(0, 0, 255, 255); break; + case Hovered: col = duRGBA(255, 255, 255, 255); break; + case Selected: col = duRGBA(0, 255, 0, 255); break; + } + F32 *s = &mLinkVerts[i*6]; + F32 *e = &mLinkVerts[i*6 + 3]; + if(!mDeleteLinks[i]) + duAppendCircle(&dd, s[0], s[1], s[2], mLinkRads[i], col); + duAppendArc(&dd, + s[0], s[1], s[2], + e[0], e[1], e[2], + 0.3f, + 0.0f, mLinkFlags[i] == DropFlag ? 0.0f : 0.4f, + col); + if(!mDeleteLinks[i]) + duAppendCircle(&dd, e[0], e[1], e[2], mLinkRads[i], col); + } + dd.end(); + dd.depthMask(true); +} + +void NavMesh::renderTileData(duDebugDrawTorque &dd, U32 tile) +{ + if(tile >= mTileData.size()) + return; + if(nm) + { + dd.beginGroup(0); + if(mTileData[tile].chf) duDebugDrawCompactHeightfieldSolid(&dd, *mTileData[tile].chf); + + dd.beginGroup(1); + int col = duRGBA(255, 0, 255, 255); + RecastPolyList &in = mTileData[tile].geom; + dd.begin(DU_DRAW_LINES); + const F32 *verts = in.getVerts(); + const S32 *tris = in.getTris(); + for(U32 t = 0; t < in.getTriCount(); t++) + { + dd.vertex(&verts[tris[t*3]*3], col); + dd.vertex(&verts[tris[t*3+1]*3], col); + dd.vertex(&verts[tris[t*3+1]*3], col); + dd.vertex(&verts[tris[t*3+2]*3], col); + dd.vertex(&verts[tris[t*3+2]*3], col); + dd.vertex(&verts[tris[t*3]*3], col); + } + dd.end(); + } +} + void NavMesh::onEditorEnable() { mNetFlags.set(Ghostable); @@ -864,6 +1546,19 @@ bool NavMesh::load() nm->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); } + S32 s; + fread(&s, sizeof(S32), 1, fp); + setLinkCount(s); + fread(const_cast(mLinkVerts.address()), sizeof(F32), s * 6, fp); + fread(const_cast(mLinkRads.address()), sizeof(F32), s, fp); + fread(const_cast(mLinkDirs.address()), sizeof(U8), s, fp); + fread(const_cast(mLinkAreas.address()), sizeof(U8), s, fp); + fread(const_cast(mLinkFlags.address()), sizeof(unsigned short), s, fp); + fread(const_cast(mLinkIDs.address()), sizeof(U32), s, fp); + mLinksUnsynced.fill(false); + mLinkSelectStates.fill(Unselected); + mDeleteLinks.fill(false); + fclose(fp); updateTiles(); @@ -871,6 +1566,8 @@ bool NavMesh::load() if(isServerObject()) { setMaskBits(LoadFlag); + if(getEventManager()) + getEventManager()->postEvent("NavMeshUpdate", getIdString()); } return true; @@ -884,6 +1581,11 @@ DefineEngineMethod(NavMesh, load, bool, (),, bool NavMesh::save() { +#ifdef WALKABOUT_DEMO + Con::errorf("Sorry, this demo code doesn't allow you to save NavMeshes to files."); + Con::executef("OnWalkaboutDemoSave"); + return false; +#else if(!dStrlen(mFileName) || !nm) return false; @@ -920,9 +1622,19 @@ bool NavMesh::save() fwrite(tile->data, tile->dataSize, 1, fp); } + S32 s = mLinkIDs.size(); + fwrite(&s, sizeof(S32), 1, fp); + fwrite(mLinkVerts.address(), sizeof(F32), s * 6, fp); + fwrite(mLinkRads.address(), sizeof(F32), s, fp); + fwrite(mLinkDirs.address(), sizeof(U8), s, fp); + fwrite(mLinkAreas.address(), sizeof(U8), s, fp); + fwrite(mLinkFlags.address(), sizeof(unsigned short), s, fp); + fwrite(mLinkIDs.address(), sizeof(U32), s, fp); + fclose(fp); return true; +#endif } DefineEngineMethod(NavMesh, save, void, (),, diff --git a/Engine/source/navigation/navMesh.h b/Engine/source/navigation/navMesh.h index 0684aedf0..2d689dd57 100644 --- a/Engine/source/navigation/navMesh.h +++ b/Engine/source/navigation/navMesh.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2013 GarageGames, LLC +// Copyright (c) 2014 Daniel Buckmaster // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -25,16 +25,20 @@ #include -#include "torqueRecast.h" #include "scene/sceneObject.h" +#include "collision/concretePolyList.h" #include "recastPolyList.h" +#include "util/messaging/eventManager.h" +#include "torqueRecast.h" #include "duDebugDrawTorque.h" +#include "coverPoint.h" #include #include #include #include +#include /// @class NavMesh /// Represents a set of bounds within which a Recast navigation mesh is generated. @@ -53,6 +57,10 @@ public: bool build(bool background = true, bool saveIntermediates = false); /// Stop a build in progress. void cancelBuild(); + /// Generate cover points from a nav mesh. + bool createCoverPoints(); + /// Remove all cover points + void deleteCoverPoints(); /// Save the navmesh to a file. bool save(); @@ -65,9 +73,15 @@ public: /// Instantly rebuild a specific tile. void buildTile(const U32 &tile); + /// Rebuild parts of the navmesh where links have changed. + void buildLinks(); + /// Data file to store this nav mesh in. (From engine executable dir.) StringTableEntry mFileName; + /// Name of the SimSet to store cover points in. (Usually a SimGroup.) + StringTableEntry mCoverSet; + /// Cell width and height. F32 mCellSize, mCellHeight; /// @name Actor data @@ -90,6 +104,17 @@ public: U32 mMaxPolysPerTile; /// @} + /// @name Water + /// @{ + enum WaterMethod { + Ignore, + Solid, + Impassable + }; + + WaterMethod mWaterMethod; + /// @} + /// @} /// Return the index of the tile included by this point. @@ -98,6 +123,68 @@ public: /// Return the box of a given tile. Box3F getTileBox(U32 id); + /// @name Links + /// @{ + + /// Add an off-mesh link. + S32 addLink(const Point3F &from, const Point3F &to, U32 flags = 0); + + /// Get the ID of the off-mesh link near the point. + S32 getLink(const Point3F &pos); + + /// Get the number of links this mesh has. + S32 getLinkCount(); + + /// Get the starting point of a link. + Point3F getLinkStart(U32 idx); + + /// Get the ending point of a link. + Point3F getLinkEnd(U32 idx); + + /// Get the flags used by a link. + LinkData getLinkFlags(U32 idx); + + /// Set flags used by a link. + void setLinkFlags(U32 idx, const LinkData &d); + + /// Set the selected state of a link. + void selectLink(U32 idx, bool select, bool hover = true); + + /// Delete the selected link. + void deleteLink(U32 idx); + + /// @} + + /// Should small characters use this mesh? + bool mSmallCharacters; + /// Should regular-sized characters use this mesh? + bool mRegularCharacters; + /// Should large characters use this mesh? + bool mLargeCharacters; + /// Should vehicles use this mesh? + bool mVehicles; + + /// @name Annotations + /// @{ + + /// Should we automatically generate jump-down links? + bool mJumpDownLinks; + /// Height of a 'small' jump link. + F32 mJumpLinkSmall; + /// Height of a 'large' jump link. + F32 mJumpLinkLarge; + + /// Distance to search for cover. + F32 mCoverDist; + + /// Distance to search horizontally when peeking around cover. + F32 mPeekDist; + + /// Add cover to walls that don't have corners? + bool mInnerCover; + + /// @} + /// @name SimObject /// @{ @@ -142,6 +229,8 @@ public: void prepRenderImage(SceneRenderState *state); void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat); + void renderLinks(duDebugDraw &dd); + void renderTileData(duDebugDrawTorque &dd, U32 tile); bool mAlwaysRender; @@ -151,6 +240,12 @@ public: ~NavMesh(); DECLARE_CONOBJECT(NavMesh); + /// Return the server-side NavMesh SimSet. + static SimSet *getServerSet(); + + /// Return the EventManager for all NavMeshes. + static EventManager *getEventManager(); + void inspectPostApply(); protected: @@ -165,6 +260,9 @@ private: /// Builds the next tile in the dirty list. void buildNextTile(); + /// Save imtermediate navmesh creation data? + bool mSaveIntermediates; + /// @name Tiles /// @{ @@ -223,6 +321,9 @@ private: /// List of tiles. Vector mTiles; + /// List of tile intermediate data. + Vector mTileData; + /// List of indices to the tile array which are dirty. std::queue mDirtyTiles; @@ -234,6 +335,33 @@ private: /// @} + /// @name Off-mesh links + /// @{ + + enum SelectState { + Unselected, + Hovered, + Selected + }; + + Vector mLinkVerts; ///< Coordinates of each link vertex + Vector mLinksUnsynced; ///< Are the editor links unsynced from the mesh? + Vector mLinkRads; ///< Radius of each link + Vector mLinkDirs; ///< Direction (one-way or bidirectional) + Vector mLinkAreas; ///< Area ID + Vector mLinkFlags; ///< Flags + Vector mLinkIDs; ///< ID number of each link + Vector mLinkSelectStates; ///< Selection state of links + Vector mDeleteLinks; ///< Link will be deleted next build. + + U32 mCurLinkID; + + void eraseLink(U32 idx); + void eraseLinks(); + void setLinkCount(U32 c); + + /// @} + /// @name Intermediate data /// @{ @@ -244,6 +372,21 @@ private: void updateConfig(); dtNavMesh *nm; + rcContext *ctx; + + /// @} + + /// @name Cover + /// @{ + + struct CoverPointData { + MatrixF trans; + CoverPoint::Size size; + bool peek[3]; + }; + + /// Attempt to place cover points along a given edge. + bool testEdgeCover(const Point3F &pos, const VectorF &dir, CoverPointData &data); /// @} @@ -269,6 +412,15 @@ private: void renderToDrawer(); /// @} + + /// Server-side set for all NavMesh objects. + static SimObjectPtr smServerSet; + + /// Use this object to manage update events. + static SimObjectPtr smEventManager; }; +typedef NavMesh::WaterMethod NavMeshWaterMethod; +DefineEnumType(NavMeshWaterMethod); + #endif diff --git a/Engine/source/navigation/navPath.cpp b/Engine/source/navigation/navPath.cpp index 93c1a7a28..dfc3e1512 100644 --- a/Engine/source/navigation/navPath.cpp +++ b/Engine/source/navigation/navPath.cpp @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2013 GarageGames, LLC +// Copyright (c) 2014 Daniel Buckmaster // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -22,10 +22,12 @@ #include "torqueRecast.h" #include "navPath.h" +#include "duDebugDrawTorque.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" #include "console/typeValidators.h" +#include "math/mathTypes.h" #include "scene/sceneRenderState.h" #include "gfx/gfxDrawUtil.h" @@ -44,8 +46,7 @@ NavPath::NavPath() : mFrom(0.0f, 0.0f, 0.0f), mTo(0.0f, 0.0f, 0.0f) { - mTypeMask |= StaticShapeObjectType | MarkerObjectType; - mNetFlags.clear(Ghostable); + mTypeMask |= MarkerObjectType; mMesh = NULL; mWaypoints = NULL; @@ -56,43 +57,51 @@ NavPath::NavPath() : mToSet = false; mLength = 0.0f; + mCurIndex = -1; mIsLooping = false; + mAutoUpdate = false; + mIsSliced = false; + + mMaxIterations = 1; mAlwaysRender = false; mXray = false; + mRenderSearch = false; - mQuery = dtAllocNavMeshQuery(); + mQuery = NULL; } NavPath::~NavPath() { - // Required for Detour. dtFreeNavMeshQuery(mQuery); mQuery = NULL; } -bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data) +void NavPath::checkAutoUpdate() { - NavMesh *mesh = NULL; - NavPath *object = static_cast(obj); - - if(Sim::findObject(data, mesh)) - object->mMesh = mesh; - - return false; + EventManager *em = NavMesh::getEventManager(); + em->removeAll(this); + if(mMesh) + { + if(mAutoUpdate) + { + em->subscribe(this, "NavMeshRemoved"); + em->subscribe(this, "NavMeshUpdate"); + em->subscribe(this, "NavMeshUpdateBox"); + em->subscribe(this, "NavMeshObstacleAdded"); + em->subscribe(this, "NavMeshObstacleRemoved"); + } + } } -const char *NavPath::getProtectedMesh(void *obj, const char *data) +bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data) { NavPath *object = static_cast(obj); - if(object->mMesh.isNull()) - return ""; + if(Sim::findObject(data, object->mMesh)) + object->checkAutoUpdate(); - if(object->mMesh->getName()) - return object->mMesh->getName(); - else - return object->mMesh->getIdString(); + return true; } bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *data) @@ -111,6 +120,16 @@ bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *da return false; } +bool NavPath::setProtectedAutoUpdate(void *obj, const char *index, const char *data) +{ + NavPath *object = static_cast(obj); + + object->mAutoUpdate = dAtob(data); + object->checkAutoUpdate(); + + return false; +} + bool NavPath::setProtectedFrom(void *obj, const char *index, const char *data) { NavPath *object = static_cast(obj); @@ -150,7 +169,7 @@ const char *NavPath::getProtectedFrom(void *obj, const char *data) if(object->mFromSet) return data; else - return ""; + return StringTable->insert(""); } const char *NavPath::getProtectedTo(void *obj, const char *data) @@ -160,29 +179,10 @@ const char *NavPath::getProtectedTo(void *obj, const char *data) if(object->mToSet) return data; else - return ""; + return StringTable->insert(""); } -bool NavPath::setProtectedAlwaysRender(void *obj, const char *index, const char *data) -{ - NavPath *path = static_cast(obj); - bool always = dAtob(data); - if(always) - { - if(!gEditingMission) - path->mNetFlags.set(Ghostable); - } - else - { - if(!gEditingMission) - path->mNetFlags.clear(Ghostable); - } - path->mAlwaysRender = always; - path->setMaskBits(PathMask); - return true; -} - -static IRangeValidator NaturalNumber(1, S32_MAX); +IRangeValidator ValidIterations(1, S32_MAX); void NavPath::initPersistFields() { @@ -195,25 +195,52 @@ void NavPath::initPersistFields() &setProtectedTo, &getProtectedTo, "World location this path should end at."); - addProtectedField("mesh", TYPEID(), Offset(mMesh, NavPath), - &setProtectedMesh, &getProtectedMesh, - "NavMesh object this path travels within."); + addProtectedField("mesh", TypeRealString, Offset(mMeshName, NavPath), + &setProtectedMesh, &defaultProtectedGetFn, + "Name of the NavMesh object this path travels within."); addProtectedField("waypoints", TYPEID(), Offset(mWaypoints, NavPath), &setProtectedWaypoints, &defaultProtectedGetFn, "Path containing waypoints for this NavPath to visit."); addField("isLooping", TypeBool, Offset(mIsLooping, NavPath), "Does this path loop?"); + addField("isSliced", TypeBool, Offset(mIsSliced, NavPath), + "Plan this path over multiple updates instead of all at once."); + addFieldV("maxIterations", TypeS32, Offset(mMaxIterations, NavPath), &ValidIterations, + "Maximum iterations of path planning this path does per tick."); + addProtectedField("autoUpdate", TypeBool, Offset(mAutoUpdate, NavPath), + &setProtectedAutoUpdate, &defaultProtectedGetFn, + "If set, this path will automatically replan when its navigation mesh changes."); endGroup("NavPath"); + addGroup("Flags"); + + addField("allowWalk", TypeBool, Offset(mLinkTypes.walk, NavPath), + "Allow the path to use dry land."); + addField("allowJump", TypeBool, Offset(mLinkTypes.jump, NavPath), + "Allow the path to use jump links."); + addField("allowDrop", TypeBool, Offset(mLinkTypes.drop, NavPath), + "Allow the path to use drop links."); + addField("allowSwim", TypeBool, Offset(mLinkTypes.swim, NavPath), + "Allow the path tomove in water."); + addField("allowLedge", TypeBool, Offset(mLinkTypes.ledge, NavPath), + "Allow the path to jump ledges."); + addField("allowClimb", TypeBool, Offset(mLinkTypes.climb, NavPath), + "Allow the path to use climb links."); + addField("allowTeleport", TypeBool, Offset(mLinkTypes.teleport, NavPath), + "Allow the path to use teleporters."); + + endGroup("Flags"); + addGroup("NavPath Render"); - - addProtectedField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavMesh), - &setProtectedAlwaysRender, &defaultProtectedGetFn, - "Display this NavPath even outside the editor."); + + addField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavPath), + "Render this NavPath even when not selected."); addField("xray", TypeBool, Offset(mXray, NavPath), "Render this NavPath through other objects."); + addField("renderSearch", TypeBool, Offset(mRenderSearch, NavPath), + "Render the closed list of this NavPath's search."); endGroup("NavPath Render"); @@ -225,59 +252,62 @@ bool NavPath::onAdd() if(!Parent::onAdd()) return false; - addToScene(); - - // Ghost immediately if the editor's already open. - if(gEditingMission || mAlwaysRender) + if(gEditingMission) mNetFlags.set(Ghostable); - // Automatically find a path if we can. - if(isServerObject()) - plan(); - - // Set initial world bounds and stuff. resize(); + addToScene(); + + if(isServerObject()) + { + mQuery = dtAllocNavMeshQuery(); + if(!mQuery) + return false; + checkAutoUpdate(); + if(!plan()) + setProcessTick(true); + } + return true; } void NavPath::onRemove() { - // Remove from simulation. - removeFromScene(); - Parent::onRemove(); + + removeFromScene(); } bool NavPath::init() { - // Check that enough data is provided. - if(mMesh.isNull() || !mMesh->getNavMesh()) + mStatus = DT_FAILURE; + + // Check that all the right data is provided. + if(!mMesh || !mMesh->getNavMesh()) return false; - if(!(mFromSet && mToSet) && !(!mWaypoints.isNull() && mWaypoints->size())) + if(!(mFromSet && mToSet) && !(mWaypoints && mWaypoints->size())) return false; - // Initialise query in Detour. + // Initialise our query. if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen))) return false; mPoints.clear(); + mFlags.clear(); mVisitPoints.clear(); mLength = 0.0f; - // Send path data to clients who are ghosting this object. if(isServerObject()) setMaskBits(PathMask); // Add points we need to visit in reverse order. if(mWaypoints && mWaypoints->size()) { - // Add destination. For looping paths, that includes 'from'. if(mIsLooping && mFromSet) mVisitPoints.push_back(mFrom); if(mToSet) mVisitPoints.push_front(mTo); - // Add waypoints. for(S32 i = mWaypoints->size() - 1; i >= 0; i--) { SceneObject *s = dynamic_cast(mWaypoints->at(i)); @@ -289,13 +319,11 @@ bool NavPath::init() mVisitPoints.push_front(s->getPosition()); } } - // Add source (only ever specified by 'from'). if(mFromSet) mVisitPoints.push_back(mFrom); } else { - // Add (from,) to and from if(mIsLooping) mVisitPoints.push_back(mFrom); mVisitPoints.push_back(mTo); @@ -316,7 +344,6 @@ void NavPath::resize() return; } - // Grow a box to just fit over all our points. Point3F max(mPoints[0]), min(mPoints[0]), pos(0.0f); for(U32 i = 1; i < mPoints.size(); i++) { @@ -341,20 +368,38 @@ void NavPath::resize() bool NavPath::plan() { + // Initialise filter. + mFilter.setIncludeFlags(mLinkTypes.getFlags()); + + // Initialise query and visit locations. if(!init()) return false; - if(!visitNext()) - return false; + if(mIsSliced) + return planSliced(); + else + return planInstant(); +} +bool NavPath::planSliced() +{ + bool visited = visitNext(); + + if(visited) + setProcessTick(true); + + return visited; +} + +bool NavPath::planInstant() +{ + setProcessTick(false); + visitNext(); + S32 store = mMaxIterations; + mMaxIterations = INT_MAX; while(update()); - - if(!finalise()) - return false; - - resize(); - - return true; + mMaxIterations = store; + return finalise(); } bool NavPath::visitNext() @@ -364,23 +409,32 @@ bool NavPath::visitNext() return false; // Current leg of journey. - Point3F start = mVisitPoints[s-1]; - Point3F end = mVisitPoints[s-2]; + Point3F &start = mVisitPoints[s-1]; + Point3F &end = mVisitPoints[s-2]; + + // Drop to height of statics. + RayInfo info; + if(getContainer()->castRay(start, start - Point3F(0, 0, mMesh->mWalkableHeight * 2.0f), StaticObjectType, &info)) + start = info.point; + if(getContainer()->castRay(end + Point3F(0, 0, 0.1f), end - Point3F(0, 0, mMesh->mWalkableHeight * 2.0f), StaticObjectType, &info)) + end = info.point; // Convert to Detour-friendly coordinates and data structures. F32 from[] = {start.x, start.z, -start.y}; F32 to[] = {end.x, end.z, -end.y}; - F32 extents[] = {1.0f, 1.0f, 1.0f}; + F32 extx = mMesh->mWalkableRadius * 4.0f; + F32 extz = mMesh->mWalkableHeight; + F32 extents[] = {extx, extz, extx}; dtPolyRef startRef, endRef; - if(dtStatusFailed(mQuery->findNearestPoly(from, extents, &mFilter, &startRef, from)) || !startRef) + if(dtStatusFailed(mQuery->findNearestPoly(from, extents, &mFilter, &startRef, NULL)) || !startRef) { Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s", start.x, start.y, start.z, getIdString()); return false; } - if(dtStatusFailed(mQuery->findNearestPoly(to, extents, &mFilter, &endRef, to)) || !startRef) + if(dtStatusFailed(mQuery->findNearestPoly(to, extents, &mFilter, &endRef, NULL)) || !endRef) { Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s", end.x, end.y, end.z, getIdString()); @@ -397,20 +451,16 @@ bool NavPath::visitNext() bool NavPath::update() { - // StatusInProgress means a query is underway. if(dtStatusInProgress(mStatus)) - mStatus = mQuery->updateSlicedFindPath(INT_MAX, NULL); - // StatusSucceeded means the query found its destination. + mStatus = mQuery->updateSlicedFindPath(mMaxIterations, NULL); if(dtStatusSucceed(mStatus)) { - // Finalize the path. Need to use the static path length cap again. + // Add points from this leg. dtPolyRef path[MaxPathLen]; S32 pathLen; mStatus = mQuery->finalizeSlicedFindPath(path, &pathLen, MaxPathLen); - // Apparently stuff can go wrong during finalizing, so check the status again. if(dtStatusSucceed(mStatus) && pathLen) { - // These next few blocks are straight from Detour example code. F32 straightPath[MaxPathLen * 3]; S32 straightPathLen; dtPolyRef straightPathPolys[MaxPathLen]; @@ -422,19 +472,19 @@ bool NavPath::update() F32 from[] = {start.x, start.z, -start.y}; F32 to[] = {end.x, end.z, -end.y}; - // Straightens out the path. mQuery->findStraightPath(from, to, path, pathLen, straightPath, straightPathFlags, straightPathPolys, &straightPathLen, MaxPathLen); - // Convert Detour point path to list of Torque points. s = mPoints.size(); mPoints.increment(straightPathLen); + mFlags.increment(straightPathLen); for(U32 i = 0; i < straightPathLen; i++) { F32 *f = straightPath + i * 3; mPoints[s + i] = RCtoDTS(f); - // Accumulate length if we're not the first vertex. + mMesh->getNavMesh()->getPolyFlags(straightPathPolys[i], &mFlags[s + i]); + // Add to length if(s > 0 || i > 0) mLength += (mPoints[s+i] - mPoints[s+i-1]).len(); } @@ -467,30 +517,37 @@ bool NavPath::update() bool NavPath::finalise() { - // Stop ticking. setProcessTick(false); - // Reset world bounds and stuff. resize(); - return dtStatusSucceed(mStatus); + return success(); } void NavPath::processTick(const Move *move) { + if(!mMesh) + if(Sim::findObject(mMeshName.c_str(), mMesh)) + plan(); if(dtStatusInProgress(mStatus)) update(); } -Point3F NavPath::getNode(S32 idx) +Point3F NavPath::getNode(S32 idx) const { - if(idx < getCount() && idx >= 0) + if(idx < size() && idx >= 0) return mPoints[idx]; - Con::errorf("Trying to access out-of-bounds path index %d (path length: %d)!", idx, getCount()); return Point3F(0,0,0); } -S32 NavPath::getCount() +U16 NavPath::getFlags(S32 idx) const +{ + if(idx < size() && idx >= 0) + return mFlags[idx]; + return 0; +} + +S32 NavPath::size() const { return mPoints.size(); } @@ -498,18 +555,11 @@ S32 NavPath::getCount() void NavPath::onEditorEnable() { mNetFlags.set(Ghostable); - if(isClientObject() && !mAlwaysRender) - addToScene(); } void NavPath::onEditorDisable() { - if(!mAlwaysRender) - { - mNetFlags.clear(Ghostable); - if(isClientObject()) - removeFromScene(); - } + mNetFlags.clear(Ghostable); } void NavPath::inspectPostApply() @@ -530,7 +580,7 @@ void NavPath::prepRenderImage(SceneRenderState *state) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind(this, &NavPath::renderSimple); - ri->type = RenderPassManager::RIT_Object; + ri->type = RenderPassManager::RIT_Editor; ri->translucentSort = true; ri->defaultKey = 1; state->getRenderPass()->addInst(ri); @@ -577,6 +627,18 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa for (U32 i = 0; i < mPoints.size(); i++) PrimBuild::vertex3fv(mPoints[i]); PrimBuild::end(); + + if(mRenderSearch && getServerObject()) + { + NavPath *np = static_cast(getServerObject()); + if(np->mQuery && !dtStatusSucceed(np->mStatus)) + { + duDebugDrawTorque dd; + dd.overrideColor(duRGBA(250, 20, 20, 255)); + duDebugDrawNavMeshNodes(&dd, *np->mQuery); + dd.render(); + } + } } U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) @@ -586,6 +648,7 @@ U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) stream->writeFlag(mIsLooping); stream->writeFlag(mAlwaysRender); stream->writeFlag(mXray); + stream->writeFlag(mRenderSearch); if(stream->writeFlag(mFromSet)) mathWrite(*stream, mFrom); @@ -596,7 +659,10 @@ U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream) { stream->writeInt(mPoints.size(), 32); for(U32 i = 0; i < mPoints.size(); i++) + { mathWrite(*stream, mPoints[i]); + stream->writeInt(mFlags[i], 16); + } } return retMask; @@ -609,6 +675,7 @@ void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream) mIsLooping = stream->readFlag(); mAlwaysRender = stream->readFlag(); mXray = stream->readFlag(); + mRenderSearch = stream->readFlag(); if((mFromSet = stream->readFlag()) == true) mathRead(*stream, &mFrom); @@ -618,27 +685,55 @@ void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream) if(stream->readFlag()) { mPoints.clear(); + mFlags.clear(); mPoints.setSize(stream->readInt(32)); + mFlags.setSize(mPoints.size()); for(U32 i = 0; i < mPoints.size(); i++) { Point3F p; mathRead(*stream, &p); mPoints[i] = p; + mFlags[i] = stream->readInt(16); } resize(); } } -DefineEngineMethod(NavPath, replan, bool, (),, +DefineEngineMethod(NavPath, plan, bool, (),, "@brief Find a path using the already-specified path properties.") { return object->plan(); } -DefineEngineMethod(NavPath, getCount, S32, (),, +DefineEngineMethod(NavPath, onNavMeshUpdate, void, (const char *data),, + "@brief Callback when this path's NavMesh is loaded or rebuilt.") +{ + if(object->mMesh && !dStrcmp(data, object->mMesh->getIdString())) + object->plan(); +} + +DefineEngineMethod(NavPath, onNavMeshUpdateBox, void, (const char *data),, + "@brief Callback when a particular area in this path's NavMesh is rebuilt.") +{ + String s(data); + U32 space = s.find(' '); + if(space != String::NPos) + { + String id = s.substr(0, space); + if(!object->mMesh || id.compare(object->mMesh->getIdString())) + return; + String boxstr = s.substr(space + 1); + Box3F box; + castConsoleTypeFromString(box, boxstr.c_str()); + if(object->getWorldBox().isOverlapped(box)) + object->plan(); + } +} + +DefineEngineMethod(NavPath, size, S32, (),, "@brief Return the number of nodes in this path.") { - return object->getCount(); + return object->size(); } DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),, @@ -647,8 +742,14 @@ DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),, return object->getNode(idx); } +DefineEngineMethod(NavPath, getFlags, S32, (S32 idx),, + "@brief Get a specified node along the path.") +{ + return (S32)object->getFlags(idx); +} + DefineEngineMethod(NavPath, getLength, F32, (),, - "@brief Get the length of this path in Torque units (i.e. the total distance it covers).") + "@brief Get the length of this path.") { return object->getLength(); } diff --git a/Engine/source/navigation/navPath.h b/Engine/source/navigation/navPath.h index d7ba3d917..29a291963 100644 --- a/Engine/source/navigation/navPath.h +++ b/Engine/source/navigation/navPath.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// Copyright (c) 2013 GarageGames, LLC +// Copyright (c) 2014 Daniel Buckmaster // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -30,33 +30,33 @@ class NavPath: public SceneObject { typedef SceneObject Parent; - /// Maximum size of Detour path. - static const U32 MaxPathLen = 1024; - + static const U32 MaxPathLen = 2048; public: /// @name NavPath /// Functions for planning and accessing the path. /// @{ - SimObjectPtr mMesh; - SimObjectPtr mWaypoints; + String mMeshName; + NavMesh *mMesh; + SimPath::Path *mWaypoints; - /// Location to start at. Point3F mFrom; - /// Has a starting location been set? bool mFromSet; - /// Location to end at. Point3F mTo; - /// Has an end been set? bool mToSet; - /// This path should include a segment from the end to the start. bool mIsLooping; + bool mAutoUpdate; + bool mIsSliced; + + S32 mMaxIterations; - /// Render even when not selected in the editor. bool mAlwaysRender; - /// Render on top of other objects. bool mXray; + bool mRenderSearch; + + /// What sort of link types are we allowed to move on? + LinkData mLinkTypes; /// Plan the path. bool plan(); @@ -69,20 +69,28 @@ public: /// @return True if the plan was successful overall. bool finalise(); + /// Did the path plan successfully? + bool success() const { return dtStatusSucceed(mStatus); } + /// @} /// @name Path interface + /// These functions are provided to make NavPath behave + /// similarly to the existing Path class, despite NavPath + /// not being a SimSet. /// @{ - /// Return world-space position of a path node. - /// @param[in] idx Node index to retrieve. - Point3F getNode(S32 idx); - - /// Return the number of nodes in this path. - S32 getCount(); - /// Return the length of this path. - F32 getLength() { return mLength; }; + F32 getLength() const { return mLength; }; + + /// Get the number of nodes in a path. + S32 size() const; + + /// Return world-space position of a path node. + Point3F getNode(S32 idx) const; + + /// Get the flags for a given path node. + U16 getFlags(S32 idx) const; /// @} @@ -128,39 +136,46 @@ private: /// Create appropriate data structures and stuff. bool init(); - /// 'Visit' the most recent two points on our visit list. + /// Plan the path. + bool planInstant(); + + /// Start a sliced plan. + /// @return True if the plan initialised successfully. + bool planSliced(); + + /// Add points of the path between the two specified points. + //bool addPoints(Point3F from, Point3F to, Vector *points); + + /// 'Visit' the last two points on our visit list. bool visitNext(); - /// Detour path query. dtNavMeshQuery *mQuery; - /// Current status of our Detour query. dtStatus mStatus; - /// Filter that provides the movement costs for paths. dtQueryFilter mFilter; - - /// List of points the path should visit (waypoints, if you will). - Vector mVisitPoints; - /// List of points in the final path. + S32 mCurIndex; Vector mPoints; - - /// Total length of path in world units. + Vector mFlags; + Vector mVisitPoints; F32 mLength; /// Resets our world transform and bounds to fit our point list. void resize(); - /// @name Protected console getters/setters - /// @{ + /// Function used to set mMesh object from console. static bool setProtectedMesh(void *obj, const char *index, const char *data); - static const char *getProtectedMesh(void *obj, const char *data); + + /// Function used to set mWaypoints from console. static bool setProtectedWaypoints(void *obj, const char *index, const char *data); - static bool setProtectedAlwaysRender(void *obj, const char *index, const char *data); + void checkAutoUpdate(); + /// Function used to protect auto-update flag. + static bool setProtectedAutoUpdate(void *obj, const char *index, const char *data); + /// @name Protected from and to vectors + /// @{ static bool setProtectedFrom(void *obj, const char *index, const char *data); - static const char *getProtectedFrom(void *obj, const char *data); - static bool setProtectedTo(void *obj, const char *index, const char *data); + static const char *getProtectedFrom(void *obj, const char *data); static const char *getProtectedTo(void *obj, const char *data); /// @} }; diff --git a/Engine/source/navigation/torqueRecast.h b/Engine/source/navigation/torqueRecast.h index 0e79e5cf7..57fe7fdc5 100644 --- a/Engine/source/navigation/torqueRecast.h +++ b/Engine/source/navigation/torqueRecast.h @@ -69,4 +69,36 @@ enum PolyFlags { AllFlags = 0xffff }; +/// Stores information about a link. +struct LinkData { + bool walk; + bool jump; + bool drop; + bool swim; + bool ledge; + bool climb; + bool teleport; + LinkData(unsigned short flags = 0) + { + walk = flags & WalkFlag; + jump = flags & JumpFlag; + drop = flags & DropFlag; + swim = flags & SwimFlag; + ledge = flags & LedgeFlag; + climb = flags & ClimbFlag; + teleport = flags & TeleportFlag; + } + unsigned short getFlags() const + { + return + (walk ? WalkFlag : 0) | + (jump ? JumpFlag : 0) | + (drop ? DropFlag : 0) | + (swim ? SwimFlag : 0) | + (ledge ? LedgeFlag : 0) | + (climb ? ClimbFlag : 0) | + (teleport ? TeleportFlag : 0); + } +}; + #endif diff --git a/Engine/source/terrain/terrCollision.cpp b/Engine/source/terrain/terrCollision.cpp index c55fc9ff5..5388d9292 100644 --- a/Engine/source/terrain/terrCollision.cpp +++ b/Engine/source/terrain/terrCollision.cpp @@ -562,7 +562,7 @@ bool TerrainBlock::buildPolyList(PolyListContext context, AbstractPolyList* poly // Add the missing points U32 vi[5]; - for (S32 i = 0; i < 4 ; i++) + for (int i = 0; i < 4 ; i++) { S32 dx = i >> 1; S32 dy = dx ^ (i & 1);