mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-02-19 14:43:47 +00:00
Added basic Walkabout with #define renamed and no editor.
This commit is contained in:
parent
c08413ffde
commit
f4c940f4fe
22 changed files with 3725 additions and 196 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
187
Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp
Normal file
187
Engine/source/gui/containers/guiFlexibleArrayCtrl.cpp
Normal 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
|
||||
70
Engine/source/gui/containers/guiFlexibleArrayCtrl.h
Normal file
70
Engine/source/gui/containers/guiFlexibleArrayCtrl.h
Normal 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
|
||||
344
Engine/source/navigation/coverPoint.cpp
Normal file
344
Engine/source/navigation/coverPoint.cpp
Normal 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);
|
||||
}
|
||||
136
Engine/source/navigation/coverPoint.h
Normal file
136
Engine/source/navigation/coverPoint.h
Normal 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
|
||||
641
Engine/source/navigation/guiNavEditorCtrl.cpp
Normal file
641
Engine/source/navigation/guiNavEditorCtrl.cpp
Normal 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);
|
||||
}
|
||||
189
Engine/source/navigation/guiNavEditorCtrl.h
Normal file
189
Engine/source/navigation/guiNavEditorCtrl.h
Normal 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
|
||||
|
||||
|
||||
|
||||
69
Engine/source/navigation/navContext.cpp
Normal file
69
Engine/source/navigation/navContext.cpp
Normal 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];
|
||||
}
|
||||
68
Engine/source/navigation/navContext.h
Normal file
68
Engine/source/navigation/navContext.h
Normal 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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
/// @}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue