mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-04-29 16:25:42 +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;
|
mTargetInLOS = false;
|
||||||
mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
|
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;
|
mIsAiControlled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +142,27 @@ void AIPlayer::initPersistFields()
|
||||||
|
|
||||||
endGroup( "AI" );
|
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();
|
Parent::initPersistFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,6 +179,16 @@ bool AIPlayer::onAdd()
|
||||||
return true;
|
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
|
* Sets the speed at which this AI moves
|
||||||
*
|
*
|
||||||
|
|
@ -168,6 +205,11 @@ void AIPlayer::setMoveSpeed( F32 speed )
|
||||||
void AIPlayer::stopMove()
|
void AIPlayer::stopMove()
|
||||||
{
|
{
|
||||||
mMoveState = ModeStop;
|
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 location = eye.getPosition();
|
||||||
Point3F rotation = getRotation();
|
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
|
// Orient towards the aim point, aim object, or towards
|
||||||
// our destination.
|
// our destination.
|
||||||
if (mAimObject || mAimLocationSet || mMoveState != ModeStop)
|
if (mAimObject || mAimLocationSet || mMoveState != ModeStop)
|
||||||
|
|
@ -340,7 +408,11 @@ bool AIPlayer::getAIMove(Move *movePtr)
|
||||||
if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance)
|
if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance)
|
||||||
{
|
{
|
||||||
mMoveState = ModeStop;
|
mMoveState = ModeStop;
|
||||||
|
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||||
|
onReachDestination();
|
||||||
|
#else
|
||||||
throwCallback("onReachDestination");
|
throwCallback("onReachDestination");
|
||||||
|
#endif // TORQUE_NAVIGATION_ENABLED
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -409,7 +481,11 @@ bool AIPlayer::getAIMove(Move *movePtr)
|
||||||
if ( mMoveState != ModeSlowing || locationDelta == 0 )
|
if ( mMoveState != ModeSlowing || locationDelta == 0 )
|
||||||
{
|
{
|
||||||
mMoveState = ModeStuck;
|
mMoveState = ModeStuck;
|
||||||
|
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||||
|
onStuck();
|
||||||
|
#else
|
||||||
throwCallback("onMoveStuck");
|
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
|
// 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,
|
// run from the eye position to the center of the object's bounding,
|
||||||
// which is not very accurate.
|
// which is not very accurate.
|
||||||
if (mAimObject)
|
if (mAimObject) {
|
||||||
{
|
MatrixF eyeMat;
|
||||||
if (checkInLos(mAimObject.getPointer()))
|
getEyeTransform(&eyeMat);
|
||||||
{
|
eyeMat.getColumn(3,&location);
|
||||||
if (!mTargetInLOS)
|
Point3F targetLoc = mAimObject->getBoxCenter();
|
||||||
{
|
|
||||||
throwCallback("onTargetEnterLOS");
|
// This ray ignores non-static shapes. Cast Ray returns true
|
||||||
mTargetInLOS = 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)
|
else
|
||||||
{
|
if (!mTargetInLOS) {
|
||||||
throwCallback("onTargetExitLOS");
|
throwCallback( "onTargetEnterLOS" );
|
||||||
mTargetInLOS = false;
|
mTargetInLOS = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replicate the trigger state into the move so that
|
// Replicate the trigger state into the move so that
|
||||||
// triggers can be controlled from scripts.
|
// 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);
|
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;
|
mLastLocation = location;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -457,6 +558,453 @@ void AIPlayer::throwCallback( const char *name )
|
||||||
Con::executef(getDataBlock(), name, getIdString());
|
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
|
// 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")
|
"@checkEnabled check whether the object can take damage and if so is still alive.(Defaults to false)\n")
|
||||||
{
|
{
|
||||||
return object->checkInFoV(obj, fov, checkEnabled);
|
return object->checkInFoV(obj, fov, checkEnabled);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,11 @@
|
||||||
#include "T3D/player.h"
|
#include "T3D/player.h"
|
||||||
#endif
|
#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 {
|
class AIPlayer : public Player {
|
||||||
|
|
||||||
|
|
@ -61,6 +66,90 @@ private:
|
||||||
// Utility Methods
|
// Utility Methods
|
||||||
void throwCallback( const char *name );
|
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:
|
public:
|
||||||
DECLARE_CONOBJECT( AIPlayer );
|
DECLARE_CONOBJECT( AIPlayer );
|
||||||
|
|
||||||
|
|
@ -70,6 +159,9 @@ public:
|
||||||
static void initPersistFields();
|
static void initPersistFields();
|
||||||
|
|
||||||
bool onAdd();
|
bool onAdd();
|
||||||
|
#ifdef TORQUE_NAVIGATION_ENABLED
|
||||||
|
void onRemove();
|
||||||
|
#endif // TORQUE_NAVIGATION_ENABLED
|
||||||
|
|
||||||
virtual bool getAIMove( Move *move );
|
virtual bool getAIMove( Move *move );
|
||||||
|
|
||||||
|
|
@ -91,6 +183,38 @@ public:
|
||||||
void setMoveDestination( const Point3F &location, bool slowdown );
|
void setMoveDestination( const Point3F &location, bool slowdown );
|
||||||
Point3F getMoveDestination() const { return mMoveDestination; }
|
Point3F getMoveDestination() const { return mMoveDestination; }
|
||||||
void stopMove();
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -1293,6 +1293,56 @@ bool River::collideBox(const Point3F &start, const Point3F &end, RayInfo* info)
|
||||||
return false;
|
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
|
F32 River::getWaterCoverage( const Box3F &worldBox ) const
|
||||||
{
|
{
|
||||||
PROFILE_SCOPE( River_GetWaterCoverage );
|
PROFILE_SCOPE( River_GetWaterCoverage );
|
||||||
|
|
|
||||||
|
|
@ -404,6 +404,7 @@ public:
|
||||||
virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
|
virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
|
||||||
virtual bool collideBox(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 containsPoint( const Point3F& point ) const { return containsPoint( point, NULL ); }
|
||||||
|
virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
|
||||||
|
|
||||||
// WaterObject
|
// WaterObject
|
||||||
virtual F32 getWaterCoverage( const Box3F &worldBox ) const;
|
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);
|
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
|
F32 WaterBlock::getWaterCoverage( const Box3F &testBox ) const
|
||||||
{
|
{
|
||||||
Box3F wbox = getWorldBox();
|
Box3F wbox = getWorldBox();
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ public:
|
||||||
virtual void inspectPostApply();
|
virtual void inspectPostApply();
|
||||||
virtual void setTransform( const MatrixF & mat );
|
virtual void setTransform( const MatrixF & mat );
|
||||||
virtual void setScale( const Point3F &scale );
|
virtual void setScale( const Point3F &scale );
|
||||||
|
virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
|
||||||
|
|
||||||
// WaterObject
|
// WaterObject
|
||||||
virtual F32 getWaterCoverage( const Box3F &worldBox ) const;
|
virtual F32 getWaterCoverage( const Box3F &worldBox ) const;
|
||||||
|
|
|
||||||
|
|
@ -816,6 +816,48 @@ F32 WaterPlane::distanceTo( const Point3F& point ) const
|
||||||
return ( point.z - getPosition().z );
|
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()
|
void WaterPlane::inspectPostApply()
|
||||||
{
|
{
|
||||||
Parent::inspectPostApply();
|
Parent::inspectPostApply();
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ public:
|
||||||
virtual void inspectPostApply();
|
virtual void inspectPostApply();
|
||||||
virtual void setTransform( const MatrixF & mat );
|
virtual void setTransform( const MatrixF & mat );
|
||||||
virtual F32 distanceTo( const Point3F& point ) const;
|
virtual F32 distanceTo( const Point3F& point ) const;
|
||||||
|
virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
|
||||||
|
|
||||||
// WaterObject
|
// WaterObject
|
||||||
virtual F32 getWaterCoverage( const Box3F &worldBox ) const;
|
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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
|
@ -25,16 +25,20 @@
|
||||||
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
#include "torqueRecast.h"
|
|
||||||
#include "scene/sceneObject.h"
|
#include "scene/sceneObject.h"
|
||||||
|
#include "collision/concretePolyList.h"
|
||||||
#include "recastPolyList.h"
|
#include "recastPolyList.h"
|
||||||
|
#include "util/messaging/eventManager.h"
|
||||||
|
|
||||||
|
#include "torqueRecast.h"
|
||||||
#include "duDebugDrawTorque.h"
|
#include "duDebugDrawTorque.h"
|
||||||
|
#include "coverPoint.h"
|
||||||
|
|
||||||
#include <Recast.h>
|
#include <Recast.h>
|
||||||
#include <DetourNavMesh.h>
|
#include <DetourNavMesh.h>
|
||||||
#include <DetourNavMeshBuilder.h>
|
#include <DetourNavMeshBuilder.h>
|
||||||
#include <DebugDraw.h>
|
#include <DebugDraw.h>
|
||||||
|
#include <DetourNavMeshQuery.h>
|
||||||
|
|
||||||
/// @class NavMesh
|
/// @class NavMesh
|
||||||
/// Represents a set of bounds within which a Recast navigation mesh is generated.
|
/// 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);
|
bool build(bool background = true, bool saveIntermediates = false);
|
||||||
/// Stop a build in progress.
|
/// Stop a build in progress.
|
||||||
void cancelBuild();
|
void cancelBuild();
|
||||||
|
/// Generate cover points from a nav mesh.
|
||||||
|
bool createCoverPoints();
|
||||||
|
/// Remove all cover points
|
||||||
|
void deleteCoverPoints();
|
||||||
|
|
||||||
/// Save the navmesh to a file.
|
/// Save the navmesh to a file.
|
||||||
bool save();
|
bool save();
|
||||||
|
|
@ -65,9 +73,15 @@ public:
|
||||||
/// Instantly rebuild a specific tile.
|
/// Instantly rebuild a specific tile.
|
||||||
void buildTile(const U32 &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.)
|
/// Data file to store this nav mesh in. (From engine executable dir.)
|
||||||
StringTableEntry mFileName;
|
StringTableEntry mFileName;
|
||||||
|
|
||||||
|
/// Name of the SimSet to store cover points in. (Usually a SimGroup.)
|
||||||
|
StringTableEntry mCoverSet;
|
||||||
|
|
||||||
/// Cell width and height.
|
/// Cell width and height.
|
||||||
F32 mCellSize, mCellHeight;
|
F32 mCellSize, mCellHeight;
|
||||||
/// @name Actor data
|
/// @name Actor data
|
||||||
|
|
@ -90,6 +104,17 @@ public:
|
||||||
U32 mMaxPolysPerTile;
|
U32 mMaxPolysPerTile;
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
|
/// @name Water
|
||||||
|
/// @{
|
||||||
|
enum WaterMethod {
|
||||||
|
Ignore,
|
||||||
|
Solid,
|
||||||
|
Impassable
|
||||||
|
};
|
||||||
|
|
||||||
|
WaterMethod mWaterMethod;
|
||||||
|
/// @}
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
/// Return the index of the tile included by this point.
|
/// Return the index of the tile included by this point.
|
||||||
|
|
@ -98,6 +123,68 @@ public:
|
||||||
/// Return the box of a given tile.
|
/// Return the box of a given tile.
|
||||||
Box3F getTileBox(U32 id);
|
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
|
/// @name SimObject
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
|
|
@ -142,6 +229,8 @@ public:
|
||||||
|
|
||||||
void prepRenderImage(SceneRenderState *state);
|
void prepRenderImage(SceneRenderState *state);
|
||||||
void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
|
void render(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat);
|
||||||
|
void renderLinks(duDebugDraw &dd);
|
||||||
|
void renderTileData(duDebugDrawTorque &dd, U32 tile);
|
||||||
|
|
||||||
bool mAlwaysRender;
|
bool mAlwaysRender;
|
||||||
|
|
||||||
|
|
@ -151,6 +240,12 @@ public:
|
||||||
~NavMesh();
|
~NavMesh();
|
||||||
DECLARE_CONOBJECT(NavMesh);
|
DECLARE_CONOBJECT(NavMesh);
|
||||||
|
|
||||||
|
/// Return the server-side NavMesh SimSet.
|
||||||
|
static SimSet *getServerSet();
|
||||||
|
|
||||||
|
/// Return the EventManager for all NavMeshes.
|
||||||
|
static EventManager *getEventManager();
|
||||||
|
|
||||||
void inspectPostApply();
|
void inspectPostApply();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -165,6 +260,9 @@ private:
|
||||||
/// Builds the next tile in the dirty list.
|
/// Builds the next tile in the dirty list.
|
||||||
void buildNextTile();
|
void buildNextTile();
|
||||||
|
|
||||||
|
/// Save imtermediate navmesh creation data?
|
||||||
|
bool mSaveIntermediates;
|
||||||
|
|
||||||
/// @name Tiles
|
/// @name Tiles
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
|
|
@ -223,6 +321,9 @@ private:
|
||||||
/// List of tiles.
|
/// List of tiles.
|
||||||
Vector<Tile> mTiles;
|
Vector<Tile> mTiles;
|
||||||
|
|
||||||
|
/// List of tile intermediate data.
|
||||||
|
Vector<TileData> mTileData;
|
||||||
|
|
||||||
/// List of indices to the tile array which are dirty.
|
/// List of indices to the tile array which are dirty.
|
||||||
std::queue<U32> mDirtyTiles;
|
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
|
/// @name Intermediate data
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
|
|
@ -244,6 +372,21 @@ private:
|
||||||
void updateConfig();
|
void updateConfig();
|
||||||
|
|
||||||
dtNavMesh *nm;
|
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();
|
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
|
#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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
|
@ -22,10 +22,12 @@
|
||||||
|
|
||||||
#include "torqueRecast.h"
|
#include "torqueRecast.h"
|
||||||
#include "navPath.h"
|
#include "navPath.h"
|
||||||
|
#include "duDebugDrawTorque.h"
|
||||||
|
|
||||||
#include "console/consoleTypes.h"
|
#include "console/consoleTypes.h"
|
||||||
#include "console/engineAPI.h"
|
#include "console/engineAPI.h"
|
||||||
#include "console/typeValidators.h"
|
#include "console/typeValidators.h"
|
||||||
|
#include "math/mathTypes.h"
|
||||||
|
|
||||||
#include "scene/sceneRenderState.h"
|
#include "scene/sceneRenderState.h"
|
||||||
#include "gfx/gfxDrawUtil.h"
|
#include "gfx/gfxDrawUtil.h"
|
||||||
|
|
@ -44,8 +46,7 @@ NavPath::NavPath() :
|
||||||
mFrom(0.0f, 0.0f, 0.0f),
|
mFrom(0.0f, 0.0f, 0.0f),
|
||||||
mTo(0.0f, 0.0f, 0.0f)
|
mTo(0.0f, 0.0f, 0.0f)
|
||||||
{
|
{
|
||||||
mTypeMask |= StaticShapeObjectType | MarkerObjectType;
|
mTypeMask |= MarkerObjectType;
|
||||||
mNetFlags.clear(Ghostable);
|
|
||||||
|
|
||||||
mMesh = NULL;
|
mMesh = NULL;
|
||||||
mWaypoints = NULL;
|
mWaypoints = NULL;
|
||||||
|
|
@ -56,43 +57,51 @@ NavPath::NavPath() :
|
||||||
mToSet = false;
|
mToSet = false;
|
||||||
mLength = 0.0f;
|
mLength = 0.0f;
|
||||||
|
|
||||||
|
mCurIndex = -1;
|
||||||
mIsLooping = false;
|
mIsLooping = false;
|
||||||
|
mAutoUpdate = false;
|
||||||
|
mIsSliced = false;
|
||||||
|
|
||||||
|
mMaxIterations = 1;
|
||||||
|
|
||||||
mAlwaysRender = false;
|
mAlwaysRender = false;
|
||||||
mXray = false;
|
mXray = false;
|
||||||
|
mRenderSearch = false;
|
||||||
|
|
||||||
mQuery = dtAllocNavMeshQuery();
|
mQuery = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
NavPath::~NavPath()
|
NavPath::~NavPath()
|
||||||
{
|
{
|
||||||
// Required for Detour.
|
|
||||||
dtFreeNavMeshQuery(mQuery);
|
dtFreeNavMeshQuery(mQuery);
|
||||||
mQuery = NULL;
|
mQuery = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavPath::setProtectedMesh(void *obj, const char *index, const char *data)
|
void NavPath::checkAutoUpdate()
|
||||||
{
|
{
|
||||||
NavMesh *mesh = NULL;
|
EventManager *em = NavMesh::getEventManager();
|
||||||
NavPath *object = static_cast<NavPath*>(obj);
|
em->removeAll(this);
|
||||||
|
if(mMesh)
|
||||||
if(Sim::findObject(data, mesh))
|
{
|
||||||
object->mMesh = mesh;
|
if(mAutoUpdate)
|
||||||
|
{
|
||||||
return false;
|
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);
|
NavPath *object = static_cast<NavPath*>(obj);
|
||||||
|
|
||||||
if(object->mMesh.isNull())
|
if(Sim::findObject(data, object->mMesh))
|
||||||
return "";
|
object->checkAutoUpdate();
|
||||||
|
|
||||||
if(object->mMesh->getName())
|
return true;
|
||||||
return object->mMesh->getName();
|
|
||||||
else
|
|
||||||
return object->mMesh->getIdString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavPath::setProtectedWaypoints(void *obj, const char *index, const char *data)
|
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;
|
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)
|
bool NavPath::setProtectedFrom(void *obj, const char *index, const char *data)
|
||||||
{
|
{
|
||||||
NavPath *object = static_cast<NavPath*>(obj);
|
NavPath *object = static_cast<NavPath*>(obj);
|
||||||
|
|
@ -150,7 +169,7 @@ const char *NavPath::getProtectedFrom(void *obj, const char *data)
|
||||||
if(object->mFromSet)
|
if(object->mFromSet)
|
||||||
return data;
|
return data;
|
||||||
else
|
else
|
||||||
return "";
|
return StringTable->insert("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *NavPath::getProtectedTo(void *obj, const char *data)
|
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)
|
if(object->mToSet)
|
||||||
return data;
|
return data;
|
||||||
else
|
else
|
||||||
return "";
|
return StringTable->insert("");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavPath::setProtectedAlwaysRender(void *obj, const char *index, const char *data)
|
IRangeValidator ValidIterations(1, S32_MAX);
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
void NavPath::initPersistFields()
|
void NavPath::initPersistFields()
|
||||||
{
|
{
|
||||||
|
|
@ -195,25 +195,52 @@ void NavPath::initPersistFields()
|
||||||
&setProtectedTo, &getProtectedTo,
|
&setProtectedTo, &getProtectedTo,
|
||||||
"World location this path should end at.");
|
"World location this path should end at.");
|
||||||
|
|
||||||
addProtectedField("mesh", TYPEID<NavMesh>(), Offset(mMesh, NavPath),
|
addProtectedField("mesh", TypeRealString, Offset(mMeshName, NavPath),
|
||||||
&setProtectedMesh, &getProtectedMesh,
|
&setProtectedMesh, &defaultProtectedGetFn,
|
||||||
"NavMesh object this path travels within.");
|
"Name of the NavMesh object this path travels within.");
|
||||||
addProtectedField("waypoints", TYPEID<SimPath::Path>(), Offset(mWaypoints, NavPath),
|
addProtectedField("waypoints", TYPEID<SimPath::Path>(), Offset(mWaypoints, NavPath),
|
||||||
&setProtectedWaypoints, &defaultProtectedGetFn,
|
&setProtectedWaypoints, &defaultProtectedGetFn,
|
||||||
"Path containing waypoints for this NavPath to visit.");
|
"Path containing waypoints for this NavPath to visit.");
|
||||||
|
|
||||||
addField("isLooping", TypeBool, Offset(mIsLooping, NavPath),
|
addField("isLooping", TypeBool, Offset(mIsLooping, NavPath),
|
||||||
"Does this path loop?");
|
"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");
|
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");
|
addGroup("NavPath Render");
|
||||||
|
|
||||||
addProtectedField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavMesh),
|
addField("alwaysRender", TypeBool, Offset(mAlwaysRender, NavPath),
|
||||||
&setProtectedAlwaysRender, &defaultProtectedGetFn,
|
"Render this NavPath even when not selected.");
|
||||||
"Display this NavPath even outside the editor.");
|
|
||||||
addField("xray", TypeBool, Offset(mXray, NavPath),
|
addField("xray", TypeBool, Offset(mXray, NavPath),
|
||||||
"Render this NavPath through other objects.");
|
"Render this NavPath through other objects.");
|
||||||
|
addField("renderSearch", TypeBool, Offset(mRenderSearch, NavPath),
|
||||||
|
"Render the closed list of this NavPath's search.");
|
||||||
|
|
||||||
endGroup("NavPath Render");
|
endGroup("NavPath Render");
|
||||||
|
|
||||||
|
|
@ -225,59 +252,62 @@ bool NavPath::onAdd()
|
||||||
if(!Parent::onAdd())
|
if(!Parent::onAdd())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
addToScene();
|
if(gEditingMission)
|
||||||
|
|
||||||
// Ghost immediately if the editor's already open.
|
|
||||||
if(gEditingMission || mAlwaysRender)
|
|
||||||
mNetFlags.set(Ghostable);
|
mNetFlags.set(Ghostable);
|
||||||
|
|
||||||
// Automatically find a path if we can.
|
|
||||||
if(isServerObject())
|
|
||||||
plan();
|
|
||||||
|
|
||||||
// Set initial world bounds and stuff.
|
|
||||||
resize();
|
resize();
|
||||||
|
|
||||||
|
addToScene();
|
||||||
|
|
||||||
|
if(isServerObject())
|
||||||
|
{
|
||||||
|
mQuery = dtAllocNavMeshQuery();
|
||||||
|
if(!mQuery)
|
||||||
|
return false;
|
||||||
|
checkAutoUpdate();
|
||||||
|
if(!plan())
|
||||||
|
setProcessTick(true);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavPath::onRemove()
|
void NavPath::onRemove()
|
||||||
{
|
{
|
||||||
// Remove from simulation.
|
|
||||||
removeFromScene();
|
|
||||||
|
|
||||||
Parent::onRemove();
|
Parent::onRemove();
|
||||||
|
|
||||||
|
removeFromScene();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavPath::init()
|
bool NavPath::init()
|
||||||
{
|
{
|
||||||
// Check that enough data is provided.
|
mStatus = DT_FAILURE;
|
||||||
if(mMesh.isNull() || !mMesh->getNavMesh())
|
|
||||||
|
// Check that all the right data is provided.
|
||||||
|
if(!mMesh || !mMesh->getNavMesh())
|
||||||
return false;
|
return false;
|
||||||
if(!(mFromSet && mToSet) && !(!mWaypoints.isNull() && mWaypoints->size()))
|
if(!(mFromSet && mToSet) && !(mWaypoints && mWaypoints->size()))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Initialise query in Detour.
|
// Initialise our query.
|
||||||
if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen)))
|
if(dtStatusFailed(mQuery->init(mMesh->getNavMesh(), MaxPathLen)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
mPoints.clear();
|
mPoints.clear();
|
||||||
|
mFlags.clear();
|
||||||
mVisitPoints.clear();
|
mVisitPoints.clear();
|
||||||
mLength = 0.0f;
|
mLength = 0.0f;
|
||||||
|
|
||||||
// Send path data to clients who are ghosting this object.
|
|
||||||
if(isServerObject())
|
if(isServerObject())
|
||||||
setMaskBits(PathMask);
|
setMaskBits(PathMask);
|
||||||
|
|
||||||
// Add points we need to visit in reverse order.
|
// Add points we need to visit in reverse order.
|
||||||
if(mWaypoints && mWaypoints->size())
|
if(mWaypoints && mWaypoints->size())
|
||||||
{
|
{
|
||||||
// Add destination. For looping paths, that includes 'from'.
|
|
||||||
if(mIsLooping && mFromSet)
|
if(mIsLooping && mFromSet)
|
||||||
mVisitPoints.push_back(mFrom);
|
mVisitPoints.push_back(mFrom);
|
||||||
if(mToSet)
|
if(mToSet)
|
||||||
mVisitPoints.push_front(mTo);
|
mVisitPoints.push_front(mTo);
|
||||||
// Add waypoints.
|
|
||||||
for(S32 i = mWaypoints->size() - 1; i >= 0; i--)
|
for(S32 i = mWaypoints->size() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
SceneObject *s = dynamic_cast<SceneObject*>(mWaypoints->at(i));
|
SceneObject *s = dynamic_cast<SceneObject*>(mWaypoints->at(i));
|
||||||
|
|
@ -289,13 +319,11 @@ bool NavPath::init()
|
||||||
mVisitPoints.push_front(s->getPosition());
|
mVisitPoints.push_front(s->getPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add source (only ever specified by 'from').
|
|
||||||
if(mFromSet)
|
if(mFromSet)
|
||||||
mVisitPoints.push_back(mFrom);
|
mVisitPoints.push_back(mFrom);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Add (from,) to and from
|
|
||||||
if(mIsLooping)
|
if(mIsLooping)
|
||||||
mVisitPoints.push_back(mFrom);
|
mVisitPoints.push_back(mFrom);
|
||||||
mVisitPoints.push_back(mTo);
|
mVisitPoints.push_back(mTo);
|
||||||
|
|
@ -316,7 +344,6 @@ void NavPath::resize()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow a box to just fit over all our points.
|
|
||||||
Point3F max(mPoints[0]), min(mPoints[0]), pos(0.0f);
|
Point3F max(mPoints[0]), min(mPoints[0]), pos(0.0f);
|
||||||
for(U32 i = 1; i < mPoints.size(); i++)
|
for(U32 i = 1; i < mPoints.size(); i++)
|
||||||
{
|
{
|
||||||
|
|
@ -341,20 +368,38 @@ void NavPath::resize()
|
||||||
|
|
||||||
bool NavPath::plan()
|
bool NavPath::plan()
|
||||||
{
|
{
|
||||||
|
// Initialise filter.
|
||||||
|
mFilter.setIncludeFlags(mLinkTypes.getFlags());
|
||||||
|
|
||||||
|
// Initialise query and visit locations.
|
||||||
if(!init())
|
if(!init())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(!visitNext())
|
if(mIsSliced)
|
||||||
return false;
|
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());
|
while(update());
|
||||||
|
mMaxIterations = store;
|
||||||
if(!finalise())
|
return finalise();
|
||||||
return false;
|
|
||||||
|
|
||||||
resize();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NavPath::visitNext()
|
bool NavPath::visitNext()
|
||||||
|
|
@ -364,23 +409,32 @@ bool NavPath::visitNext()
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Current leg of journey.
|
// Current leg of journey.
|
||||||
Point3F start = mVisitPoints[s-1];
|
Point3F &start = mVisitPoints[s-1];
|
||||||
Point3F end = mVisitPoints[s-2];
|
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.
|
// Convert to Detour-friendly coordinates and data structures.
|
||||||
F32 from[] = {start.x, start.z, -start.y};
|
F32 from[] = {start.x, start.z, -start.y};
|
||||||
F32 to[] = {end.x, end.z, -end.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;
|
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",
|
Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
|
||||||
start.x, start.y, start.z, getIdString());
|
start.x, start.y, start.z, getIdString());
|
||||||
return false;
|
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",
|
Con::errorf("No NavMesh polygon near visit point (%g, %g, %g) of NavPath %s",
|
||||||
end.x, end.y, end.z, getIdString());
|
end.x, end.y, end.z, getIdString());
|
||||||
|
|
@ -397,20 +451,16 @@ bool NavPath::visitNext()
|
||||||
|
|
||||||
bool NavPath::update()
|
bool NavPath::update()
|
||||||
{
|
{
|
||||||
// StatusInProgress means a query is underway.
|
|
||||||
if(dtStatusInProgress(mStatus))
|
if(dtStatusInProgress(mStatus))
|
||||||
mStatus = mQuery->updateSlicedFindPath(INT_MAX, NULL);
|
mStatus = mQuery->updateSlicedFindPath(mMaxIterations, NULL);
|
||||||
// StatusSucceeded means the query found its destination.
|
|
||||||
if(dtStatusSucceed(mStatus))
|
if(dtStatusSucceed(mStatus))
|
||||||
{
|
{
|
||||||
// Finalize the path. Need to use the static path length cap again.
|
// Add points from this leg.
|
||||||
dtPolyRef path[MaxPathLen];
|
dtPolyRef path[MaxPathLen];
|
||||||
S32 pathLen;
|
S32 pathLen;
|
||||||
mStatus = mQuery->finalizeSlicedFindPath(path, &pathLen, MaxPathLen);
|
mStatus = mQuery->finalizeSlicedFindPath(path, &pathLen, MaxPathLen);
|
||||||
// Apparently stuff can go wrong during finalizing, so check the status again.
|
|
||||||
if(dtStatusSucceed(mStatus) && pathLen)
|
if(dtStatusSucceed(mStatus) && pathLen)
|
||||||
{
|
{
|
||||||
// These next few blocks are straight from Detour example code.
|
|
||||||
F32 straightPath[MaxPathLen * 3];
|
F32 straightPath[MaxPathLen * 3];
|
||||||
S32 straightPathLen;
|
S32 straightPathLen;
|
||||||
dtPolyRef straightPathPolys[MaxPathLen];
|
dtPolyRef straightPathPolys[MaxPathLen];
|
||||||
|
|
@ -422,19 +472,19 @@ bool NavPath::update()
|
||||||
F32 from[] = {start.x, start.z, -start.y};
|
F32 from[] = {start.x, start.z, -start.y};
|
||||||
F32 to[] = {end.x, end.z, -end.y};
|
F32 to[] = {end.x, end.z, -end.y};
|
||||||
|
|
||||||
// Straightens out the path.
|
|
||||||
mQuery->findStraightPath(from, to, path, pathLen,
|
mQuery->findStraightPath(from, to, path, pathLen,
|
||||||
straightPath, straightPathFlags,
|
straightPath, straightPathFlags,
|
||||||
straightPathPolys, &straightPathLen, MaxPathLen);
|
straightPathPolys, &straightPathLen, MaxPathLen);
|
||||||
|
|
||||||
// Convert Detour point path to list of Torque points.
|
|
||||||
s = mPoints.size();
|
s = mPoints.size();
|
||||||
mPoints.increment(straightPathLen);
|
mPoints.increment(straightPathLen);
|
||||||
|
mFlags.increment(straightPathLen);
|
||||||
for(U32 i = 0; i < straightPathLen; i++)
|
for(U32 i = 0; i < straightPathLen; i++)
|
||||||
{
|
{
|
||||||
F32 *f = straightPath + i * 3;
|
F32 *f = straightPath + i * 3;
|
||||||
mPoints[s + i] = RCtoDTS(f);
|
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)
|
if(s > 0 || i > 0)
|
||||||
mLength += (mPoints[s+i] - mPoints[s+i-1]).len();
|
mLength += (mPoints[s+i] - mPoints[s+i-1]).len();
|
||||||
}
|
}
|
||||||
|
|
@ -467,30 +517,37 @@ bool NavPath::update()
|
||||||
|
|
||||||
bool NavPath::finalise()
|
bool NavPath::finalise()
|
||||||
{
|
{
|
||||||
// Stop ticking.
|
|
||||||
setProcessTick(false);
|
setProcessTick(false);
|
||||||
|
|
||||||
// Reset world bounds and stuff.
|
|
||||||
resize();
|
resize();
|
||||||
|
|
||||||
return dtStatusSucceed(mStatus);
|
return success();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavPath::processTick(const Move *move)
|
void NavPath::processTick(const Move *move)
|
||||||
{
|
{
|
||||||
|
if(!mMesh)
|
||||||
|
if(Sim::findObject(mMeshName.c_str(), mMesh))
|
||||||
|
plan();
|
||||||
if(dtStatusInProgress(mStatus))
|
if(dtStatusInProgress(mStatus))
|
||||||
update();
|
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];
|
return mPoints[idx];
|
||||||
Con::errorf("Trying to access out-of-bounds path index %d (path length: %d)!", idx, getCount());
|
|
||||||
return Point3F(0,0,0);
|
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();
|
return mPoints.size();
|
||||||
}
|
}
|
||||||
|
|
@ -498,18 +555,11 @@ S32 NavPath::getCount()
|
||||||
void NavPath::onEditorEnable()
|
void NavPath::onEditorEnable()
|
||||||
{
|
{
|
||||||
mNetFlags.set(Ghostable);
|
mNetFlags.set(Ghostable);
|
||||||
if(isClientObject() && !mAlwaysRender)
|
|
||||||
addToScene();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavPath::onEditorDisable()
|
void NavPath::onEditorDisable()
|
||||||
{
|
{
|
||||||
if(!mAlwaysRender)
|
mNetFlags.clear(Ghostable);
|
||||||
{
|
|
||||||
mNetFlags.clear(Ghostable);
|
|
||||||
if(isClientObject())
|
|
||||||
removeFromScene();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NavPath::inspectPostApply()
|
void NavPath::inspectPostApply()
|
||||||
|
|
@ -530,7 +580,7 @@ void NavPath::prepRenderImage(SceneRenderState *state)
|
||||||
{
|
{
|
||||||
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
||||||
ri->renderDelegate.bind(this, &NavPath::renderSimple);
|
ri->renderDelegate.bind(this, &NavPath::renderSimple);
|
||||||
ri->type = RenderPassManager::RIT_Object;
|
ri->type = RenderPassManager::RIT_Editor;
|
||||||
ri->translucentSort = true;
|
ri->translucentSort = true;
|
||||||
ri->defaultKey = 1;
|
ri->defaultKey = 1;
|
||||||
state->getRenderPass()->addInst(ri);
|
state->getRenderPass()->addInst(ri);
|
||||||
|
|
@ -577,6 +627,18 @@ void NavPath::renderSimple(ObjectRenderInst *ri, SceneRenderState *state, BaseMa
|
||||||
for (U32 i = 0; i < mPoints.size(); i++)
|
for (U32 i = 0; i < mPoints.size(); i++)
|
||||||
PrimBuild::vertex3fv(mPoints[i]);
|
PrimBuild::vertex3fv(mPoints[i]);
|
||||||
PrimBuild::end();
|
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)
|
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(mIsLooping);
|
||||||
stream->writeFlag(mAlwaysRender);
|
stream->writeFlag(mAlwaysRender);
|
||||||
stream->writeFlag(mXray);
|
stream->writeFlag(mXray);
|
||||||
|
stream->writeFlag(mRenderSearch);
|
||||||
|
|
||||||
if(stream->writeFlag(mFromSet))
|
if(stream->writeFlag(mFromSet))
|
||||||
mathWrite(*stream, mFrom);
|
mathWrite(*stream, mFrom);
|
||||||
|
|
@ -596,7 +659,10 @@ U32 NavPath::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
|
||||||
{
|
{
|
||||||
stream->writeInt(mPoints.size(), 32);
|
stream->writeInt(mPoints.size(), 32);
|
||||||
for(U32 i = 0; i < mPoints.size(); i++)
|
for(U32 i = 0; i < mPoints.size(); i++)
|
||||||
|
{
|
||||||
mathWrite(*stream, mPoints[i]);
|
mathWrite(*stream, mPoints[i]);
|
||||||
|
stream->writeInt(mFlags[i], 16);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return retMask;
|
return retMask;
|
||||||
|
|
@ -609,6 +675,7 @@ void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream)
|
||||||
mIsLooping = stream->readFlag();
|
mIsLooping = stream->readFlag();
|
||||||
mAlwaysRender = stream->readFlag();
|
mAlwaysRender = stream->readFlag();
|
||||||
mXray = stream->readFlag();
|
mXray = stream->readFlag();
|
||||||
|
mRenderSearch = stream->readFlag();
|
||||||
|
|
||||||
if((mFromSet = stream->readFlag()) == true)
|
if((mFromSet = stream->readFlag()) == true)
|
||||||
mathRead(*stream, &mFrom);
|
mathRead(*stream, &mFrom);
|
||||||
|
|
@ -618,27 +685,55 @@ void NavPath::unpackUpdate(NetConnection *conn, BitStream *stream)
|
||||||
if(stream->readFlag())
|
if(stream->readFlag())
|
||||||
{
|
{
|
||||||
mPoints.clear();
|
mPoints.clear();
|
||||||
|
mFlags.clear();
|
||||||
mPoints.setSize(stream->readInt(32));
|
mPoints.setSize(stream->readInt(32));
|
||||||
|
mFlags.setSize(mPoints.size());
|
||||||
for(U32 i = 0; i < mPoints.size(); i++)
|
for(U32 i = 0; i < mPoints.size(); i++)
|
||||||
{
|
{
|
||||||
Point3F p;
|
Point3F p;
|
||||||
mathRead(*stream, &p);
|
mathRead(*stream, &p);
|
||||||
mPoints[i] = p;
|
mPoints[i] = p;
|
||||||
|
mFlags[i] = stream->readInt(16);
|
||||||
}
|
}
|
||||||
resize();
|
resize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DefineEngineMethod(NavPath, replan, bool, (),,
|
DefineEngineMethod(NavPath, plan, bool, (),,
|
||||||
"@brief Find a path using the already-specified path properties.")
|
"@brief Find a path using the already-specified path properties.")
|
||||||
{
|
{
|
||||||
return object->plan();
|
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.")
|
"@brief Return the number of nodes in this path.")
|
||||||
{
|
{
|
||||||
return object->getCount();
|
return object->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
|
DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
|
||||||
|
|
@ -647,8 +742,14 @@ DefineEngineMethod(NavPath, getNode, Point3F, (S32 idx),,
|
||||||
return object->getNode(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, (),,
|
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();
|
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
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
// of this software and associated documentation files (the "Software"), to
|
// of this software and associated documentation files (the "Software"), to
|
||||||
|
|
@ -30,33 +30,33 @@
|
||||||
|
|
||||||
class NavPath: public SceneObject {
|
class NavPath: public SceneObject {
|
||||||
typedef SceneObject Parent;
|
typedef SceneObject Parent;
|
||||||
/// Maximum size of Detour path.
|
static const U32 MaxPathLen = 2048;
|
||||||
static const U32 MaxPathLen = 1024;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/// @name NavPath
|
/// @name NavPath
|
||||||
/// Functions for planning and accessing the path.
|
/// Functions for planning and accessing the path.
|
||||||
/// @{
|
/// @{
|
||||||
|
|
||||||
SimObjectPtr<NavMesh> mMesh;
|
String mMeshName;
|
||||||
SimObjectPtr<SimPath::Path> mWaypoints;
|
NavMesh *mMesh;
|
||||||
|
SimPath::Path *mWaypoints;
|
||||||
|
|
||||||
/// Location to start at.
|
|
||||||
Point3F mFrom;
|
Point3F mFrom;
|
||||||
/// Has a starting location been set?
|
|
||||||
bool mFromSet;
|
bool mFromSet;
|
||||||
/// Location to end at.
|
|
||||||
Point3F mTo;
|
Point3F mTo;
|
||||||
/// Has an end been set?
|
|
||||||
bool mToSet;
|
bool mToSet;
|
||||||
|
|
||||||
/// This path should include a segment from the end to the start.
|
|
||||||
bool mIsLooping;
|
bool mIsLooping;
|
||||||
|
bool mAutoUpdate;
|
||||||
|
bool mIsSliced;
|
||||||
|
|
||||||
|
S32 mMaxIterations;
|
||||||
|
|
||||||
/// Render even when not selected in the editor.
|
|
||||||
bool mAlwaysRender;
|
bool mAlwaysRender;
|
||||||
/// Render on top of other objects.
|
|
||||||
bool mXray;
|
bool mXray;
|
||||||
|
bool mRenderSearch;
|
||||||
|
|
||||||
|
/// What sort of link types are we allowed to move on?
|
||||||
|
LinkData mLinkTypes;
|
||||||
|
|
||||||
/// Plan the path.
|
/// Plan the path.
|
||||||
bool plan();
|
bool plan();
|
||||||
|
|
@ -69,20 +69,28 @@ public:
|
||||||
/// @return True if the plan was successful overall.
|
/// @return True if the plan was successful overall.
|
||||||
bool finalise();
|
bool finalise();
|
||||||
|
|
||||||
|
/// Did the path plan successfully?
|
||||||
|
bool success() const { return dtStatusSucceed(mStatus); }
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
/// @name Path interface
|
/// @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.
|
/// 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.
|
/// Create appropriate data structures and stuff.
|
||||||
bool init();
|
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();
|
bool visitNext();
|
||||||
|
|
||||||
/// Detour path query.
|
|
||||||
dtNavMeshQuery *mQuery;
|
dtNavMeshQuery *mQuery;
|
||||||
/// Current status of our Detour query.
|
|
||||||
dtStatus mStatus;
|
dtStatus mStatus;
|
||||||
/// Filter that provides the movement costs for paths.
|
|
||||||
dtQueryFilter mFilter;
|
dtQueryFilter mFilter;
|
||||||
|
S32 mCurIndex;
|
||||||
/// List of points the path should visit (waypoints, if you will).
|
|
||||||
Vector<Point3F> mVisitPoints;
|
|
||||||
/// List of points in the final path.
|
|
||||||
Vector<Point3F> mPoints;
|
Vector<Point3F> mPoints;
|
||||||
|
Vector<unsigned short> mFlags;
|
||||||
/// Total length of path in world units.
|
Vector<Point3F> mVisitPoints;
|
||||||
F32 mLength;
|
F32 mLength;
|
||||||
|
|
||||||
/// Resets our world transform and bounds to fit our point list.
|
/// Resets our world transform and bounds to fit our point list.
|
||||||
void resize();
|
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 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 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 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 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);
|
static const char *getProtectedTo(void *obj, const char *data);
|
||||||
/// @}
|
/// @}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,4 +69,36 @@ enum PolyFlags {
|
||||||
AllFlags = 0xffff
|
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
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -562,7 +562,7 @@ bool TerrainBlock::buildPolyList(PolyListContext context, AbstractPolyList* poly
|
||||||
|
|
||||||
// Add the missing points
|
// Add the missing points
|
||||||
U32 vi[5];
|
U32 vi[5];
|
||||||
for (S32 i = 0; i < 4 ; i++)
|
for (int i = 0; i < 4 ; i++)
|
||||||
{
|
{
|
||||||
S32 dx = i >> 1;
|
S32 dx = i >> 1;
|
||||||
S32 dy = dx ^ (i & 1);
|
S32 dy = dx ^ (i & 1);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue