Added basic Walkabout with #define renamed and no editor.

This commit is contained in:
Daniel Buckmaster 2014-11-28 19:42:10 +11:00
parent c08413ffde
commit f4c940f4fe
22 changed files with 3725 additions and 196 deletions

View file

@ -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<CoverPoint*>(obj);
if(!p || p->isOccupied())
return;
CoverSearch *s = static_cast<CoverSearch*>(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<NavMesh*>(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);
}
}

View file

@ -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<NavPath> 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<CoverPoint> 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<SceneObject> 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<NavMesh> 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

View file

@ -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<const RiverSegment*> 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 );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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).<br>"
"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 <i>fillRowFirst</i> set to "
"true may be arranged as follows:\n\n"
"<pre>\n"
"1 2 ...3... 4\n"
"..5.. 6 7 .8.\n"
"9 ....10....\n"
"</pre>\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<GuiControl*>(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

View file

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

View file

@ -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<CoverPointSize>(), 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::VertexType> 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<ObjectRenderInst>();
// 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);
}

View file

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

View file

@ -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<SimGroup*>(cleanup);
missionCleanup->addObject(obj);
}
mPlayer = static_cast<AIPlayer*>(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<AIPlayer*>(ri.object))
{
mPlayer = dynamic_cast<AIPlayer*>(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<AIPlayer*>(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<ObjectRenderInst>();
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);
}

View file

@ -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<NavMesh> mMesh;
/// @name Link mode
/// @{
Point3F mLinkStart;
S32 mCurLink;
S32 mLink;
/// @}
/// @name Tile mode
/// @{
S32 mCurTile;
S32 mTile;
duDebugDrawTorque dd;
/// @}
/// @name Test mode
/// @{
SimObjectPtr<AIPlayer> mPlayer;
SimObjectPtr<AIPlayer> 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

View file

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

View file

@ -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 <Recast.h>
/// @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

File diff suppressed because it is too large Load diff

View file

@ -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 <queue>
#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 <Recast.h>
#include <DetourNavMesh.h>
#include <DetourNavMeshBuilder.h>
#include <DebugDraw.h>
#include <DetourNavMeshQuery.h>
/// @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<Tile> mTiles;
/// List of tile intermediate data.
Vector<TileData> mTileData;
/// List of indices to the tile array which are dirty.
std::queue<U32> mDirtyTiles;
@ -234,6 +335,33 @@ private:
/// @}
/// @name Off-mesh links
/// @{
enum SelectState {
Unselected,
Hovered,
Selected
};
Vector<F32> mLinkVerts; ///< Coordinates of each link vertex
Vector<bool> mLinksUnsynced; ///< Are the editor links unsynced from the mesh?
Vector<F32> mLinkRads; ///< Radius of each link
Vector<U8> mLinkDirs; ///< Direction (one-way or bidirectional)
Vector<U8> mLinkAreas; ///< Area ID
Vector<unsigned short> mLinkFlags; ///< Flags
Vector<U32> mLinkIDs; ///< ID number of each link
Vector<U8> mLinkSelectStates; ///< Selection state of links
Vector<bool> 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<SimSet> smServerSet;
/// Use this object to manage update events.
static SimObjectPtr<EventManager> smEventManager;
};
typedef NavMesh::WaterMethod NavMeshWaterMethod;
DefineEnumType(NavMeshWaterMethod);
#endif

View file

@ -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<NavPath*>(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<NavPath*>(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<NavPath*>(obj);
object->mAutoUpdate = dAtob(data);
object->checkAutoUpdate();
return false;
}
bool NavPath::setProtectedFrom(void *obj, const char *index, const char *data)
{
NavPath *object = static_cast<NavPath*>(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<NavPath*>(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<NavMesh>(), 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<SimPath::Path>(), 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<SceneObject*>(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<ObjectRenderInst>();
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<NavPath*>(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();
}

View file

@ -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<NavMesh> mMesh;
SimObjectPtr<SimPath::Path> 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<Point3F> *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<Point3F> mVisitPoints;
/// List of points in the final path.
S32 mCurIndex;
Vector<Point3F> mPoints;
/// Total length of path in world units.
Vector<unsigned short> mFlags;
Vector<Point3F> 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);
/// @}
};

View file

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

View file

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