t2 engine svn checkout

This commit is contained in:
loop 2024-01-07 04:36:33 +00:00
commit ff569bd2ae
988 changed files with 394180 additions and 0 deletions

3133
Engine.dsp Normal file

File diff suppressed because it is too large Load diff

34
Engine.plg Normal file
View file

@ -0,0 +1,34 @@
<html>
<body>
<pre>
<h1>Build Log</h1>
<h3>
--------------------Configuration: Engine - Win32 Release--------------------
</h3>
Compiling interior/itfdump.asm
mBox.cc
mConsoleFunctions.cc
mMathFn.cc
mMath_C.cc
mMatrix.cc
mPlaneTransformer.cc
mQuadPatch.cc
mQuat.cc
mRandom.cc
mSolver.cc
mSplinePatch.cc
mathTypes.cc
mathUtils.cc
mMathAMD.cc
mMathSSE.cc
Linking out.VC6.RELEASE/v12_RELEASE.exe
cp out.VC6.RELEASE/v12_RELEASE* ../example
<h3>Results</h3>
v12_RELEASE.exe - 0 error(s), 0 warning(s)
</pre>
</body>
</html>

34
Makefile Normal file
View file

@ -0,0 +1,34 @@
#--------------------------------------
# include and verify the users mk/conf.mk
-include ../mk/conf.mk
ifndef CONFIG_STATUS
doConfigure:
$(warning Configuration file not defined)
#@make --no-print-directory -f ../mk/configure.mk
else
ifeq ($(CONFIG_STATUS),INVALID)
doConfigure:
$(warning Invalid Configuration file)
#@make --no-print-directory -f mk/configure.mk
else
include ../mk/conf.$(COMPILER).$(OS).mk
include ../mk/conf.$(COMPILER).mk
endif
endif
include targets.v12.mk
include ../mk/conf.common.mk
#default:
# echo default.
ifneq ($(MAKECMDGOALS),clean)
-include $(addprefix $(DIR.OBJ)/, $(addsuffix $(DEP), $(basename $(filter %.cc %.c,$(SOURCES)))))
endif

2975
ai/aiConnection.cc Normal file

File diff suppressed because it is too large Load diff

403
ai/aiConnection.h Normal file
View file

@ -0,0 +1,403 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AICONNECTION_H_
#define _AICONNECTION_H_
#ifndef _MPOINT_H_
#include "math/mPoint.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _GAMECONNECTION_H_
#include "game/gameConnection.h"
#endif
#ifndef _MOVEMANAGER_H_
#include "game/moveManager.h"
#endif
#ifndef _PLAYER_H_
#include "game/player.h"
#endif
#ifndef _PROJECTILE_H_
#include "game/projectile.h"
#endif
#ifndef _GRAPH_H_
#include "ai/graph.h"
#endif
#ifndef _AINAVJETTING_H_
#include "ai/aiNavJetting.h"
#endif
//#define DEBUG_AI 1
class AIStep;
class AITask;
class AIConnection : public GameConnection
{
typedef GameConnection Parent;
//moves for the AI are not queued up, since they are moved on the server
Move mMove;
F32 mMoveSpeed;
S32 mMoveMode;
S32 mMoveModePending;
F32 mMoveTolerance; //how close to the destination before we stop
Point3F mMoveDestination; //this is the desired final destination
Point3F mNodeLocation; //this is a node along the path towards our final destination
Point3F mMoveLocation; //this is direction to take to arrive at the node, or the destination
Point3F mEvadeLocation; //if mEvadingCounter > 0, this location will override the above two
Point3F mAimLocation;
S32 mLookAtTargetTimeMS; //used to slow down the aim twitching...
//vars used to determine when to force a path recalc
Point3F mPrevNodeLocation;
Point3F mInitialLocation;
//Engagement vars - should be able to shoot at anyone regardless of step/task
SimObjectPtr<GameConnection> mEngageTarget;
enum EngageStates
{
ChooseWeapon,
OutOfRange,
ReloadWeapon,
FindTargetPoint,
AimAtTarget,
FireWeapon,
Finished
};
S32 mEngageState;
S32 mStateCounter;
S32 mDelayCounter;
S32 mPackCheckCounter;
bool mAimAtLazedTarget;
S32 mTurretMountedId;
//vehicle piloting vars
Point3F mPilotDestination;
Point3F mPilotAimLocation;
F32 mPilotSpeed;
F32 mPitchUpMax;
F32 mPitchDownMax;
F32 mPitchIncMax;
//piloting debug vars
Point3F mVehicleLocation;
F32 mPilotDistToDest2D;
F32 mPilotCurVelocity;
F32 mPilotCurThrust;
F32 mCurrentPitch;
F32 mPreviousPitch;
F32 mDesiredPitch;
F32 mPitchIncrement;
F32 mCurrentYaw;
StringTableEntry mProjectileName;
ProjectileData *mProjectile;
SimObjectPtr<Projectile> mEnemyProjectile;
S32 mProjectileCounter;
S32 mEvadingCounter;
bool mIsEvading;
Point3F mImpactLocation; //not really used for anything but debugging...
SimObjectPtr<GameConnection> mVictim;
SimObjectPtr<Player> mCorpse;
Point3F mCorpseLocation;
S32 mVictimTime;
//if no engagement target, we may have a gamebase object to destroy/repair
SimObjectPtr<ShapeBase> mTargetObject;
S32 mObjectMode;
//these vars are used for both engagetarget and targetobject
F32 mRangeToTarget;
S32 mCheckTargetLOSCounter;
bool mTargetInRange;
bool mTargetInSight;
public:
enum ObjectModes
{
DestroyObject = 0,
RepairObject,
LazeObject,
MortarObject,
MissileVehicle,
MissileNoLock,
AttackMode1,
AttackMode2,
AttackMode3,
AttackMode4,
NumObjectModes
};
private:
//NAV Graph vars
NavigationPath mPath;
AIJetting mJetting;
Point3F mPathDest;
bool mNewPath;
bool mNavUsingJet;
public:
S32 mEngageMinDistance;
S32 mEngageMaxDistance;
S32 mTriggerCounter;
S32 mScriptTriggerCounter;
F32 mWeaponEnergy;
F32 mWeaponErrorFactor;
bool mFiring;
ShapeBaseImageData* mMountedImage;
//temp vars recalculated and used each process loop
Point3F mLocation;
Point3F mVelocity;
Point3F mVelocity2D;
Point3F mRotation;
Point3F mHeadRotation;
Point3F mMuzzlePosition;
Point3F mEyePosition;
F32 mDamage;
F32 mEnergy;
F32 mEnergyReserve; //navigation must not use energy below the reserve
F32 mEnergyFloat; //used to determine whether we are recharging
bool mEnergyRecharge;
bool mEnergyAvailable;
bool mHeadingDownhill;
bool mInWater;
bool mOutdoors;
F32 mDotOffCourse;
Player *mTargetPlayer;
Point3F mTargLocation;
Point3F mTargVelocity;
Point3F mTargVelocity2D;
Point3F mTargRotation;
F32 mTargEnergy;
F32 mTargDamage;
bool mTargInWater;
Point3F mObjectLocation;
bool mObjectInWater;
//skill related vars
F32 mSkillLevel;
S32 mTargStillTimeMS;
F32 mTargPrevTimeMS;
Point3F mTargPrevLocation[4];
S32 mChangeWeaponCounter;
F32 mDistToTarg2D;
F32 mDistToNode2D;
F32 mDistToObject2D;
enum
{
ModeStop = 0,
ModeWalk,
ModeGainHeight,
ModeExpress,
ModeMountVehicle,
ModeStuck,
ModeCount
};
//vars to determine/handle if we're stuck
Point3F mStuckLocation;
Point3F mStuckDestination;
bool mStuckInitialized;
S32 mStuckTimer;
bool mStuckTryJump;
bool mStuckJumpInitialized;
S32 mStuckJumpTimer;
SimObjectPtr<GameBase> mAvoidingObject;
Point3F mAvoidSourcePoint;
Point3F mAvoidDestinationPoint;
Point3F mAvoidMovePoint;
bool mAvoidForcedPath;
protected:
void scriptProcessEngagement();
void scriptChooseEngageWeapon(F32 distToTarg);
void scriptChooseObjectWeapon(F32 distToTarg);
void aiDebugStuff();
private:
enum
{
FireTrigger = 0,
JumpTrigger = 2,
JetTrigger = 3,
GrenadeTrigger = 4,
MineTrigger = 5
};
bool mTriggers[MaxTriggerKeys];
//Step and Task members
AIStep *mStep;
Vector<AITask*> mTaskList;
AITask *mCurrentTask;
S32 mCurrentTaskTime;
//DETECTION MEMBERS
struct PlayerDetectionEntry
{
S32 playerId;
bool playerLOS;
S32 playerLOSTime;
Point3F playerLastPosition;
};
Vector<PlayerDetectionEntry> mPlayerDetectionTable;
S32 mPlayerDetectionIndex;
S32 mPlayerDetectionCounter;
S32 mDetectHiddenPeriod; //even without LOS, the player will know where the enemy is until
//this detectHiddenPeriod is over... after that, they'll only use
//the last known location...
S32 mBlindedTimer; //used
public:
AIConnection();
~AIConnection();
public:
DECLARE_CONOBJECT(AIConnection);
static void consoleInit();
F32 fixRadAngle(F32 angle);
static F32 get2DDot(const Point3F &vec1, const Point3F &vec2);
static F32 get2DAngle(const Point3F &endPt, const Point3F &basePt);
F32 getOutdoorRadius(const Point3F &location); //returns the outdoor freedom radius, or -1 if indoors
void setSkillLevel(F32 level);
F32 getSkillLevel() { return mSkillLevel; }
void setMoveSpeed(F32 speed);
void setMoveMode(S32 mode, bool abortStuckCode = false);
void setMoveTolerance(F32 tolerance);
void setMoveLocation(const Point3F &location);
void setMoveDestination(const Point3F &location);
void setTurretMounted(S32 turretId) { mTurretMountedId = turretId; }
void setAimLocation(const Point3F &location);
void setWeaponInfo(StringTableEntry projectile, S32 minDist, S32 maxDist, S32 triggerCount, F32 energyRequired, F32 errorFactor = 1.0f);
void setEnergyLevels(F32 eReserve, F32 eFloat = 0.10f);
bool setScriptAimLocation(const Point3F &location, S32 duration); //returns false if we have a target object/player
//here are the vehicle piloting methods
void setPilotPitchRange(F32 pitchUpMax, F32 pitchDownMax, F32 pitchIncMax);
void setPilotDestination(const Point3F &dest, F32 maxSpeed);
void setPilotAimLocation(const Point3F &dest);
//bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result);
Point3F dopeAimLocation(const Point3F &startLocation, const Point3F &aimLocation);
F32 angleDifference(F32 angle1, F32 angle2);
Point3F correctHeading();
Point3F avoidPlayers(Player *player, const Point3F &desiredDestination, bool destIsFinal);
void setEngageTarget(GameConnection *target);
S32 getEngageTarget();
void setTargetObject(ShapeBase *targetObject, F32 range = 35.0f, S32 objectMode = DestroyObject);
S32 getTargetObject();
bool TargetInRange() { return mTargetInRange || mTargetInSight; }
bool TargetInSight() { return mTargetInSight; }
void setVictim(GameConnection *victim, Player *corpse);
S32 getVictimCorpse();
S32 getVictimTime();
F32 getMoveSpeed() { return mMoveSpeed; }
S32 getMoveMode() { return mMoveMode; }
F32 getMoveTolerance() { return mMoveTolerance; }
Point3F getMoveDestination() { return mMoveDestination; }
Point3F getMoveLocation() { return mMoveLocation; }
Point3F getAimLocation() { return mAimLocation; }
bool scriptIsAiming() { return mLookAtTargetTimeMS > Sim::getCurrentTime(); }
void setPathDest(const Point3F * dest = NULL);
const Point3F * getPathDest();
void setPathCapabilities(Player * player);
F32 getPathDistance(const Point3F &destination, const Point3F &source);
F32 getPathDistRemaining(F32 maxDist);
Point3F getLOSLocation(const Point3F &targetPoint, F32 minDistance, F32 maxDistance, const Point3F &nearPoint);
Point3F getHideLocation(const Point3F &targetPoint, F32 range, const Point3F &nearPoint, F32 hideLength);
void scriptSustainFire(S32 count = 1);
void pressFire(bool value = true) { mTriggers[FireTrigger] = value; }
void pressJump() { mTriggers[JumpTrigger] = true; }
void pressJet() { mTriggers[JetTrigger] = true; }
void pressGrenade() { mTriggers[GrenadeTrigger] = true; }
void pressMine() { mTriggers[MineTrigger] = true; }
bool isFiring() { return mTriggers[FireTrigger]; }
bool isJumping() { return mTriggers[JumpTrigger]; }
bool isJetting() { return mTriggers[JetTrigger]; }
void getMoveList(Move**,U32* numMoves);
void clearMoves(U32) { }
bool areMovesPending() { return true; }
//function to update tasks, etc...
void process(ShapeBase *ctrlObject);
void processMovement(Player *player);
void processVehicleMovement(Player *player);
void processPilotVehicle(Vehicle *myVehicle);
void processEngagement(Player *player);
void setEvadeLocation(const Point3F &dangerLocation, S32 durationTicks = 0);
void initProcessVars(Player *player);
void updateDetectionTable(Player *player);
bool hasLOSToClient(S32 clientId, S32 &losTime, Point3F &lastLocation);
void clientDetected(S32 targId);
void setDetectPeriod(S32 value) { mDetectHiddenPeriod = value; }
S32 getDetectPeriod() { return mDetectHiddenPeriod; }
void setBlinded(S32 duration);
//step and task methods
void setStep(AIStep *step);
void clearStep();
const char *getStepStatus();
const char *getStepName();
void clearTasks();
void addTask(AITask *task);
void removeTask(S32 id);
AITask *getCurrentTask() { return mCurrentTask; }
S32 getTaskTime() { return mCurrentTaskTime; }
void listTasks();
void missionCycleCleanup();
};
extern const char *gTargetObjectMode[AIConnection::NumObjectModes];
class AISlicer
{
// MRandomLCG mRand;
// bool mEnabled;
U32 mDelay;
U32 mLastTime;
// U32 mCapPileUp;
U32 mDebugTotal;
F32 mBudget;
public:
AISlicer();
void init(U32 delay = 22, S32 maxPerTick = 1);
void reset();
bool ready(S32 &counter, S32 resetVal);
};
extern AISlicer gCalcWeightSlicer;
extern AISlicer gScriptEngageSlicer;
extern bool gAISystemEnabled;
#endif

766
ai/aiConsole.cc Normal file
View file

@ -0,0 +1,766 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "math/mMatrix.h"
#include "console/console.h"
#include "console/simBase.h"
#include "game/gameBase.h"
#include "game/moveManager.h"
#include "game/player.h"
#include "ai/aiConnection.h"
#include "ai/aiStep.h"
#include "ai/aiNavStep.h"
#include "ai/aiTask.h"
#include "core/idGenerator.h"
static S32 cAIConnect(SimObject *, S32 argc, const char** argv)
{
//create the connection
AIConnection *aiConnection = new AIConnection();
aiConnection->registerObject();
//add the AI to the client group
SimGroup *g = Sim::getClientGroup();
g->addObject(aiConnection);
//execute the console function
const char *teamStr = "-2";
const char *skillStr = "0.5";
const char *offenseStr = "1";
const char *voicePitchStr = "0";
const char *voiceStr = "";
if (argc >= 3)
teamStr = argv[2];
if (argc >= 4)
skillStr = argv[3];
if (argc >= 5)
{
if (!dStricmp(argv[4], "true") || dAtoi(argv[4]) > 0)
offenseStr = "1";
else
offenseStr = "0";
}
if (argc >= 6)
voiceStr = argv[5];
if (argc >= 7)
voicePitchStr = argv[6];
Con::executef(aiConnection, 7, "onAIConnect", argv[1], teamStr, skillStr, offenseStr, voiceStr, voicePitchStr);
return aiConnection->getId();
}
static void cAIDrop(SimObject *obj, S32, const char**)
{
//call the script function
AIConnection *ai = static_cast<AIConnection*>(obj);
Con::executef(ai, 1, "onAIDrop");
//next, call the game disconnection code...
Con::printf("AI Client %d is disconnected.", ai->getId());
ai->setDisconnectReason( "Removed" );
//finally, delete the object
ai->deleteObject();
}
static F32 cAIGetGraphDistance(SimObject *, S32, const char** argv)
{
Point3F dest(0, 0, 0);
dSscanf(argv[1], "%f %f %f", &dest.x, &dest.y, &dest.z);
Point3F source(0, 0, 0);
dSscanf(argv[2], "%f %f %f", &source.x, &source.y, &source.z);
return NavigationGraph::fastDistance(source, dest);
}
// Intended for initialization of whatever management is central to all bots.
AISlicer gCalcWeightSlicer;
AISlicer gScriptEngageSlicer;
static bool cAISlicerInit(SimObject *, S32, const char**)
{
gCalcWeightSlicer.init(30, 1);
gScriptEngageSlicer.init(15, 2);
return true;
}
static bool cAISlicerReset(SimObject *, S32, const char**)
{
gCalcWeightSlicer.reset();
gScriptEngageSlicer.reset();
return true;
}
static void cAISystemEnabled(SimObject *, S32 argc, const char **argv)
{
bool status = true;
if (argc == 2)
status = ((!dStricmp(argv[1], "true")) || (dAtoi(argv[1]) != 0));
gAISystemEnabled = status;
}
bool gAISystemEnabled = false;
void AIInit()
{
Con::addCommand("aiConnect", cAIConnect, "aiConnect(name [, team , skill, offense, voice, voicePitch]);", 2, 7);
Con::addCommand("AIGetPathDistance", cAIGetGraphDistance, "AIGetPathDistance(fromPoint, toPoint);", 3, 3);
Con::addCommand("AISlicerInit", cAISlicerInit, "AISlicerInit();", 1, 1);
Con::addCommand("AISlicerReset", cAISlicerReset, "AISlicerReset();", 1, 1);
Con::addCommand("AISystemEnabled", cAISystemEnabled, "AISystemEnabled([bool]);", 1, 2);
}
static void cAISetSkillLevel(SimObject *obj, S32, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setSkillLevel(dAtof(argv[2]));
}
static F32 cAIGetSkillLevel(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getSkillLevel();
}
static void cAISetMoveSpeed(SimObject *obj, S32, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setMoveSpeed(dAtoi(argv[2]));
}
static void cAIStop(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setMoveMode(AIConnection::ModeStop);
}
static void cAIStepMove(SimObject *obj, S32 argc, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
//first, clear the current step
ai->clearStep();
//set the "move to" destination
VectorF v(0,0,0);
dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z);
ai->setMoveDestination(Point3F(v.x, v.y, v.z));
//set the move mode if given
if (argc >= 4)
ai->setMoveTolerance(dAtof(argv[3]));
else
ai->setMoveTolerance(0.25f);
//don't change the move mode if we're stuck...
if (ai->getMoveMode() != AIConnection::ModeStuck)
{
if (argc >= 5)
ai->setMoveMode(dAtoi(argv[4]));
else
ai->setMoveMode(AIConnection::ModeExpress);
}
}
static bool cAIAimAtLocation(SimObject *obj, S32 argc, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
VectorF v(0,0,0);
dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z);
S32 duration = 1500;
if (argc == 4)
duration = getMax(0, dAtoi(argv[3]));
return ai->setScriptAimLocation(Point3F(v.x, v.y, v.z), duration);
}
static const char* cAIGetAimLocation(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
Point3F aimPoint = ai->getAimLocation();
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z);
return returnBuffer;
}
static bool cAIIsMountingVehicle(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return (ai->getMoveMode() == AIConnection::ModeMountVehicle);
}
static void cAISetTurretMounted(SimObject *obj, S32, const char**argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setTurretMounted(dAtoi(argv[2]));
}
static void cAISetPilotDestination(SimObject *obj, S32 argc, const char**argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
VectorF v(0,0,0);
dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z);
F32 speed = 1.0f;
if (argc >= 4)
speed = dAtof(argv[3]);
ai->setPilotDestination(v, speed);
}
static void cAISetPilotAimLocation(SimObject *obj, S32, const char**argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
VectorF v(0,0,0);
dSscanf(argv[2], "%f %f %f", &v.x, &v.y, &v.z);
ai->setPilotAimLocation(v);
}
static void cAISetPilotPitchRange(SimObject *obj, S32, const char**argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setPilotPitchRange(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
}
static F32 cAIGetPathDistance(SimObject *obj, S32 argc, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
Point3F dest(0, 0, 0);
dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z);
Point3F source(-1, -1, -1);
if (argc == 4)
dSscanf(argv[3], "%f %f %f", &source.x, &source.y, &source.z);
return ai->getPathDistance(dest, source);
}
static F32 cAIPathDistRemaining(SimObject *obj, S32, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getPathDistRemaining(dAtof(argv[2]));
}
static const char* cAIGetLOSLocation(SimObject *obj, S32 argc, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
Point3F dest(0, 0, 0);
dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z);
F32 minDist = -1;
if (argc >= 4)
minDist = dAtof(argv[3]);
F32 maxDist = 1e6;
if (argc >= 5)
maxDist = dAtof(argv[4]);
Point3F nearPoint(-1, -1, -1);
if (argc >= 6)
dSscanf(argv[5], "%f %f %f", &nearPoint.x, &nearPoint.y, &nearPoint.z);
Point3F graphPoint = ai->getLOSLocation(dest, minDist, maxDist, nearPoint);
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%f %f %f", graphPoint.x, graphPoint.y, graphPoint.z);
return returnBuffer;
}
static const char* cAIGetHideLocation(SimObject *obj, S32, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
Point3F dest(0, 0, 0);
dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z);
F32 range = dAtof(argv[3]);
Point3F nearPoint(0, 0, 0);
dSscanf(argv[4], "%f %f %f", &nearPoint.x, &nearPoint.y, &nearPoint.z);
F32 hideLength = dAtof(argv[5]);
Point3F graphPoint = ai->getHideLocation(dest, range, nearPoint, hideLength);
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%f %f %f", graphPoint.x, graphPoint.y, graphPoint.z);
return returnBuffer;
}
static void cAISetEngageTarget(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
//find the target
GameConnection *target;
if (Sim::findObject(argv[2], target))
ai->setEngageTarget(target);
else
ai->setEngageTarget(NULL);
}
static const char* cAIGetEngageTarget(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 targetId = ai->getEngageTarget();
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", targetId);
return returnBuffer;
}
static void cAISetVictim(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
//find the victim client
GameConnection *victim;
if (! Sim::findObject(argv[2], victim))
return;
//find the corpse
Player *corpse;
if (! Sim::findObject(argv[3], corpse))
return;
ai->setVictim(victim, corpse);
}
static S32 cAIGetVictimCorpse(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getVictimCorpse();
}
static S32 cAIGetVictimTime(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getVictimTime();
}
static bool cAIHasLOSToClient(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 losTime;
Point3F losLocation;
return ai->hasLOSToClient(dAtoi(argv[2]), losTime, losLocation);
}
static S32 cAIGetClientLOSTime(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 losTime;
Point3F losLocation;
ai->hasLOSToClient(dAtoi(argv[2]), losTime, losLocation);
return losTime;
}
static const char* cAIGetDetectLocation(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 losTime;
Point3F losLocation(0, 0, 0);
ai->hasLOSToClient(dAtoi(argv[2]), losTime, losLocation);
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%f %f %f", losLocation.x, losLocation.y, losLocation.z);
return returnBuffer;
}
static void cAIClientDetected(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->clientDetected(dAtoi(argv[2]));
}
static void cAISetDetectPeriod(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setDetectPeriod(dAtoi(argv[2]));
}
static S32 cAIGetDetectPeriod(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getDetectPeriod();
}
static void cAISetBlinded(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->setBlinded(dAtoi(argv[2]));
}
static void cAISetDangerLocation(SimObject *obj, S32 argc, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
Point3F dest(0, 0, 0);
dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z);
S32 duration = 0;
if (argc == 4)
duration = dAtoi(argv[3]);
ai->setEvadeLocation(dest, duration);
}
const char *gTargetObjectMode[AIConnection::NumObjectModes] =
{
"Destroy",
"Repair",
"Laze",
"Mortar",
"Missile",
"MissileNoLock",
"AttackMode1",
"AttackMode2",
"AttackMode3",
"AttackMode4"
};
static void cAISetTargetObject(SimObject *obj, S32 argc, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
//find the target
ShapeBase *targetObject;
if (Sim::findObject(argv[2], targetObject))
{
F32 range = 30;
if (argc >= 4)
range = dAtof(argv[3]);
S32 objectMode = AIConnection::DestroyObject;
if (argc == 5)
{
for (int i = 0; i < AIConnection::NumObjectModes; i++)
{
if (! dStricmp(argv[4], gTargetObjectMode[i]))
{
objectMode = i;
break;
}
}
}
ai->setTargetObject(targetObject, range, objectMode);
}
else
ai->setTargetObject(NULL);
}
static const char* cAIGetTargetObject(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 targetId = ai->getTargetObject();
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", targetId);
return returnBuffer;
}
static void cAIStepRangeObject(SimObject *obj, S32 argc, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
//find the target
GameBase *targetObject;
if (Sim::findObject(argv[2], targetObject))
{
F32 minDist, maxDist;
minDist = dAtof(argv[4]);
maxDist = dAtof(argv[5]);
Point3F fromLocation(0, 0, 0);
AIStep *step;
if (argc == 7)
{
dSscanf(argv[6], "%f %f %f", &fromLocation.x, &fromLocation.y, &fromLocation.z);
step = new AIStepRangeObject(targetObject, argv[3], &minDist, &maxDist, &fromLocation);
}
else
{
step = new AIStepRangeObject(targetObject, argv[3], &minDist, &maxDist, NULL);
}
step->registerObject("AIStepRangeObject");
ai->addObject(step);
ai->setStep(step);
}
}
static bool cAITargetInSight(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->TargetInSight();
}
static bool cAITargetInRange(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->TargetInRange();
}
static void cAIPressFire(SimObject *obj, S32 argc, const char** argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 count = 1;
if (argc == 3)
count = dAtoi(argv[2]);
ai->scriptSustainFire(count);
}
static void cAIPressJump(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->pressJump();
}
static void cAIPressJet(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->pressJet();
}
static void cAIPressGrenade(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->pressGrenade();
}
static void cAIPressMine(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->pressMine();
}
static void cAISetWeaponInfo(SimObject *obj, S32 argc, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
S32 triggerCount = (argc >= 6 ? dAtoi(argv[5]) : 1);
F32 requiredEnergy = (argc >= 7 ? dAtof(argv[6]) : 0.0);
F32 errorFactor = (argc >= 8 ? dAtof(argv[7]) : 1.0);
ai->setWeaponInfo(argv[2], dAtoi(argv[3]), dAtoi(argv[4]), triggerCount, requiredEnergy, errorFactor);
}
static void cAIClearStep(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->clearStep();
}
static void cAIStepEscort(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->clearStep();
//find the target
GameConnection *target;
if (Sim::findObject(argv[2], target))
{
AIStep *step = new AIStepEscort(target);
step->registerObject("AIStepEscort");
ai->addObject(step);
ai->setStep(step);
}
}
static void cAIStepJet(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
Point3F dest(0,0,0);
dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z);
if (AIStep * step = new AIStepJet(dest)) {
ai->clearStep();
step->registerObject("AIStepJet");
ai->addObject(step);
ai->setStep(step);
}
}
static void cAISetPath(SimObject *obj, S32 argc, const char **argv)
{
if (AIConnection * ai = dynamic_cast<AIConnection*>(obj)) {
if (argc == 3) {
Point3F dest(0,0,0);
dSscanf(argv[2], "%f %f %f", &dest.x, &dest.y, &dest.z);
ai->setPathDest(&dest);
}
else
ai->setPathDest();
}
}
static void cAIStepEngage(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
//the engage step functions better if it can be left to run uninterrupted -
//find out if we're already engaging this target before resetting the step...
GameConnection *target;
if (Sim::findObject(argv[2], target))
{
S32 curTarget = ai->getEngageTarget();
const char *curStepName = ai->getStepName();
if (!dStricmp(curStepName, "AIStepEngage") && (curTarget == target->getId()))
return;
//must be someone new we're to engage...
AIStep *step = new AIStepEngage(target);
step->registerObject("AIStepEngage");
ai->addObject(step);
ai->setStep(step);
}
else
ai->clearStep();
}
static void cAIStepIdle(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->clearStep();
Point3F idleLocation(0,0,0);
dSscanf(argv[2], "%f %f %f", &idleLocation.x, &idleLocation.y, &idleLocation.z);
AIStep *step = new AIStepIdlePatrol(&idleLocation);
step->registerObject("AIStepIdlePatrol");
ai->addObject(step);
ai->setStep(step);
}
static const char* cAIStepGetStatus(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getStepStatus();
}
static const char* cAIStepGetName(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getStepName();
}
static void cAIClearTasks(SimObject *obj, S32, const char**)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->clearTasks();
}
static const char* cAIAddTask(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
AITask *newTask = new AITask();
newTask->registerObject(argv[2]);
ai->addObject(newTask);
ai->addTask(newTask);
return avar("%d", newTask->getId());
}
static void cAIRemoveTask(SimObject *obj, S32, const char **argv)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->removeTask(dAtoi(argv[2]));
}
static void cAIListTasks(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->listTasks();
}
static S32 cAIGetTaskId(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
AITask *curTask = ai->getCurrentTask();
if (bool(curTask))
return curTask->getId();
else
return -1;
}
static const char* cAIGetTaskName(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
AITask *curTask = ai->getCurrentTask();
if (bool(curTask))
return curTask->getName();
else
return "";
}
static S32 cAIGetTaskTime(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
return ai->getTaskTime();
}
static void cAIMissionCycleCleanup(SimObject *obj, S32, const char **)
{
AIConnection *ai = static_cast<AIConnection*>(obj);
ai->missionCycleCleanup();
}
void AIConnection::consoleInit()
{
Con::addCommand("AIConnection", "drop", cAIDrop, "ai.drop()", 2, 2);
Con::addCommand("AIConnection", "setSkillLevel", cAISetSkillLevel, "ai.setSkillLevel(float)", 3, 3);
Con::addCommand("AIConnection", "getSkillLevel", cAIGetSkillLevel, "ai.getSkillLevel()", 2, 2);
Con::addCommand("AIConnection", "setEngageTarget", cAISetEngageTarget, "ai.setEngageTarget(client)", 3, 3);
Con::addCommand("AIConnection", "getEngageTarget", cAIGetEngageTarget, "ai.getEngageTarget()", 2, 2);
Con::addCommand("AIConnection", "setVictim", cAISetVictim, "ai.setVictim(client, corpseObject)", 4, 4);
Con::addCommand("AIConnection", "getVictimCorpse", cAIGetVictimCorpse, "ai.getVictimCorpse()", 2, 2);
Con::addCommand("AIConnection", "getVictimTime", cAIGetVictimTime, "ai.getVictimTime()", 2, 2);
Con::addCommand("AIConnection", "hasLOSToClient", cAIHasLOSToClient, "ai.hasLOSToClient(client)", 3, 3);
Con::addCommand("AIConnection", "getClientLOSTime", cAIGetClientLOSTime, "ai.getClientLOSTime(client)", 3, 3);
Con::addCommand("AIConnection", "getDetectLocation", cAIGetDetectLocation, "ai.getDetectLocation(client)", 3, 3);
Con::addCommand("AIConnection", "clientDetected", cAIClientDetected, "ai.clientDetected(client)", 3, 3);
Con::addCommand("AIConnection", "setDetectPeriod", cAISetDetectPeriod, "ai.setDetectPeriod()", 3, 3);
Con::addCommand("AIConnection", "getDetectPeriod", cAIGetDetectPeriod, "ai.getDetectPeriod()", 2, 2);
Con::addCommand("AIConnection", "setBlinded", cAISetBlinded, "ai.setBlinded(durationMS)", 3, 3);
Con::addCommand("AIConnection", "setDangerLocation", cAISetDangerLocation, "ai.setDangerLocation(point3F [, durationTicks])", 3, 4);
Con::addCommand("AIConnection", "setTargetObject", cAISetTargetObject, "ai.setTargetObject(object [, range, mode: destroy/repair/laze])", 3, 5);
Con::addCommand("AIConnection", "getTargetObject", cAIGetTargetObject, "ai.getTargetObject()", 2, 2);
Con::addCommand("AIConnection", "targetInSight", cAITargetInSight, "ai.targetInSight()", 2, 2);
Con::addCommand("AIConnection", "targetInRange", cAITargetInRange, "ai.targetInRange()", 2, 2);
Con::addCommand("AIConnection", "pressFire", cAIPressFire, "ai.pressFire([sustain count])", 2, 3);
Con::addCommand("AIConnection", "pressJump", cAIPressJump, "ai.pressJump()", 2, 2);
Con::addCommand("AIConnection", "pressJet", cAIPressJet, "ai.pressJet()", 2, 2);
Con::addCommand("AIConnection", "pressGrenade", cAIPressGrenade, "ai.pressGrenade()", 2, 2);
Con::addCommand("AIConnection", "pressMine", cAIPressMine, "ai.pressMine()", 2, 2);
Con::addCommand("AIConnection", "setWeaponInfo", cAISetWeaponInfo, "ai.setWeaponInfo(projectile, minDist, maxDist [, triggerCount, requiredEnergy, errorFactor]);", 5, 8);
Con::addCommand("AIConnection", "aimAt", cAIAimAtLocation, "bool ai.aimAt(point [, duration MS])", 3, 4);
Con::addCommand("AIConnection", "getAimLocation", cAIGetAimLocation, "ai.getAimLocation()", 2, 2);
Con::addCommand("AIConnection", "isMountingVehicle", cAIIsMountingVehicle, "ai.isMountingVehicle()", 2, 2);
Con::addCommand("AIConnection", "setTurretMounted", cAISetTurretMounted, "ai.setTurretMounted(turretId)", 3, 3);
Con::addCommand("AIConnection", "setPilotDestination", cAISetPilotDestination, "ai.setPilotDestination(point3F [, maxSpeed])", 3, 4);
Con::addCommand("AIConnection", "setPilotAim", cAISetPilotAimLocation, "ai.setPilotAim(point3F)", 3, 3);
Con::addCommand("AIConnection", "setPilotPitchRange", cAISetPilotPitchRange, "ai.setPilotPitchRange(pitchUpMax, pitchDownMax, pitchIncMax)", 5, 5);
Con::addCommand("AIConnection", "getPathDistance", cAIGetPathDistance, "ai.getPathDistance(destination [, source])", 3, 4);
Con::addCommand("AIConnection", "pathDistRemaining", cAIPathDistRemaining, "ai.pathDistRemaining(maxDist)", 3, 3);
Con::addCommand("AIConnection", "getLOSLocation", cAIGetLOSLocation, "ai.getLOSLocation(targetPoint [, minDist, maxDist, nearPoint])", 3, 6);
Con::addCommand("AIConnection", "getHideLocation", cAIGetHideLocation, "ai.getHideLocation(targetPoint, range, nearPoint, hideLength)", 6, 6);
//Step script fuctions
Con::addCommand("AIConnection", "clearStep", cAIClearStep, "ai.clearStep()", 2, 2);
Con::addCommand("AIConnection", "getStepStatus", cAIStepGetStatus, "ai.getStepStatus()", 2, 2);
Con::addCommand("AIConnection", "getStepName", cAIStepGetName, "ai.getStepName()", 2, 2);
Con::addCommand("AIConnection", "stop", cAIStop, "ai.stop()", 2, 2);
Con::addCommand("AIConnection", "stepMove", cAIStepMove, "ai.stepMove(point3 [, tolerance, mode])", 3, 5);
Con::addCommand("AIConnection", "stepEscort", cAIStepEscort, "ai.stepEscort(client)", 3, 3);
Con::addCommand("AIConnection", "stepEngage", cAIStepEngage, "ai.stepEngage(client)", 3, 3);
Con::addCommand("AIConnection", "stepRangeObject", cAIStepRangeObject, "ai.stepRangeObject(object, weapon, minDist, maxDist [, nearLocation])", 6, 7);
Con::addCommand("AIConnection", "stepIdle", cAIStepIdle, "ai.stepIdle(point3)", 3, 3);
Con::addCommand("AIConnection", "stepJet", cAIStepJet, "ai.stepJet(toLoc)", 3, 3);
Con::addCommand("AIConnection", "setPath", cAISetPath, "ai.setPath([toLoc])", 2, 3);
Con::addCommand("AIConnection", "clearTasks", cAIClearTasks, "ai.clearTasks()", 2, 2);
Con::addCommand("AIConnection", "addTask", cAIAddTask, "ai.addTask(taskName)", 3, 3);
Con::addCommand("AIConnection", "removeTask", cAIRemoveTask, "ai.removeTask(id)", 3, 3);
Con::addCommand("AIConnection", "listTasks", cAIListTasks, "ai.listTasks()", 2, 2);
Con::addCommand("AIConnection", "getTaskId", cAIGetTaskId, "ai.getTaskId()", 2, 2);
Con::addCommand("AIConnection", "getTaskName", cAIGetTaskName, "ai.getTaskName()", 2, 2);
Con::addCommand("AIConnection", "getTaskTime", cAIGetTaskTime, "ai.getTaskTime()", 2, 2);
Con::addCommand("AIConnection", "missionCycleCleanup", cAIMissionCycleCleanup, "ai.missionCycleCleanup()", 2, 2);
}

246
ai/aiDebug.cc Normal file
View file

@ -0,0 +1,246 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/aiConnection.h"
#include "ai/aiTask.h"
#include "ai/aiStep.h"
#include "game/vehicle.h"
void AIConnection::aiDebugStuff()
{
//DEBUG - setup
S32 lineNum = 16;
char idBuf[16];
dSprintf(idBuf, sizeof(idBuf), "%d", getId());
//DEBUG - check if we're debugging this client
if (Con::getIntVariable("$AIDebugClient") != getId())
return;
//make sure we have a valid control object
ShapeBase *ctrlObject = getControlObject();
if (! ctrlObject)
{
Con::executef(4, "aiDebugText", idBuf, "15", "CLIENT IS DEAD!");
return;
}
//get the control object
Player *myPlayer = NULL;
myPlayer = dynamic_cast<Player*>(ctrlObject);
Vehicle *myVehicle = NULL;
myVehicle = dynamic_cast<Vehicle*>(ctrlObject);
//DEBUG - Clear text lines
char textBuf[256];
for (S32 i = lineNum; i < 64; i++)
Con::executef(4, "aiDebugText", idBuf, avar("%d", i), "");
//DEBUG - current task
if (mCurrentTask)
dSprintf(textBuf, sizeof(textBuf), "Current task: %d: %s %d", mCurrentTask->getId(), mCurrentTask->getName(), mCurrentTask->getWeight());
else
dSprintf(textBuf, sizeof(textBuf), "NO TASK!");
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - current step
if (mStep)
dSprintf(textBuf, sizeof(textBuf), "Current step: %s", mStep->getName());
else
dSprintf(textBuf, sizeof(textBuf), "NO STEP!");
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//find out if the control object is a player
if (myPlayer)
{
//Debug - Move Mode
switch (mMoveMode)
{
case ModeStop: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Stop", mMoveMode); break;
case ModeWalk: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Walk", mMoveMode); break;
case ModeGainHeight: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: GainHeight", mMoveMode); break;
case ModeExpress: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Express", mMoveMode); break;
case ModeMountVehicle: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: MountVehicle", mMoveMode); break;
case ModeStuck: dSprintf(textBuf, sizeof(textBuf), "Move Mode = %d: Stuck", mMoveMode); break;
default: dSprintf(textBuf, sizeof(textBuf), "Move Mode unknown"); break;
}
//if we're using the jet state machine, indicate this as well
if (mNavUsingJet)
dStrcpy(&textBuf[dStrlen(textBuf)], " USING JET STATE MACHINE");
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - distance to destination
dSprintf(textBuf, sizeof(textBuf), "Dist to node = %.3f", mDistToNode2D);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - final destination
dSprintf(textBuf, sizeof(textBuf), "Destination is %.3f %.3f %.3f", mMoveDestination.x, mMoveDestination.y, mMoveDestination.z);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - velocity
dSprintf(textBuf, sizeof(textBuf), "Velocity = %.3f", mVelocity.len());
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - energy
F32 jetEnergy = mPath.jetWillNeedEnergy(4);
dSprintf(textBuf, sizeof(textBuf), "Energy = %.3f, jet will require: %.3f", mEnergy, jetEnergy);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - damage
dSprintf(textBuf, sizeof(textBuf), "Damage = %.3f", mDamage);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - downhill
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mHeadingDownhill ? "heading DOWNHILL" : "heading UPHILL");
//DEBUG - outdoors
F32 freedomRadius;
if (mPath.locationIsOutdoors(mLocation, &freedomRadius))
dSprintf(textBuf, sizeof(textBuf), "OUTDOORS: %.3f", freedomRadius);
else
dSprintf(textBuf, sizeof(textBuf), "Indoors");
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - me/targ in water
dSprintf(textBuf, sizeof(textBuf), "%s | %s", mInWater ? "IN WATER" : "not in water", mTargInWater ? "TARGET IN WATER" : " targ not in water");
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - jetting
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mTriggers[JetTrigger] ? "JETTING" : "");
//DEBUG - is stuck
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mMoveMode == ModeStuck ? "STUCK!!!" : "");
//DEBUG - engage target
dSprintf(textBuf, sizeof(textBuf), "Engage Target: %d", bool(mEngageTarget) ? mEngageTarget->getId() : -1);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - target object
dSprintf(textBuf, sizeof(textBuf), "Target Object: %d", bool(mTargetObject) ? mTargetObject->getId() : -1);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - target object
dSprintf(textBuf, sizeof(textBuf), "%s %s", (mTargetInSight ? "TARG IN SIGHT" : "targ not in sight"),
(mTargetInRange ? "TARG IN RANGE" : "targ not in range"));
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - engage state
switch (mEngageState)
{
case ChooseWeapon: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: ChooseWeapon", mEngageState); break;
case OutOfRange: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: OutOfRange", mEngageState); break;
case ReloadWeapon: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: ReloadWeapon", mEngageState); break;
case FindTargetPoint: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: FindTargetPoint", mEngageState); break;
case AimAtTarget: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: AimAtTarget", mEngageState); break;
case FireWeapon: dSprintf(textBuf, sizeof(textBuf), "Engage State = %d: FireWeapon", mEngageState); break;
default: dSprintf(textBuf, sizeof(textBuf), "Engage State unknown"); break;
}
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - current weapon
dSprintf(textBuf, sizeof(textBuf), "Current weapon: %s", mProjectileName);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - firing
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mTriggers[FireTrigger] ? "FIRING" : "");
//DEBUG - distance to target
if (bool(mTargetPlayer))
dSprintf(textBuf, sizeof(textBuf), "Dist to target = %.3f", mDistToTarg2D);
else if (bool(mTargetObject))
dSprintf(textBuf, sizeof(textBuf), "Dist to target = %.3f", mDistToObject2D);
else
dSprintf(textBuf, sizeof(textBuf), "Dist to target = NO TARGET");
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - clear lines
Con::executef(2, "aiDebugClearLines", idBuf);
//DEBUG - draw the projectile path
char startPt[64], endPt[64];
if (bool(mEnemyProjectile))
{
Point3F projLocation;
MatrixF const& projTransform = mEnemyProjectile->getTransform();
projTransform.getColumn(3, &projLocation);
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", projLocation.x, projLocation.y, projLocation.z);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mImpactLocation.x, mImpactLocation.y, mImpactLocation.z);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 0.0");
}
//Debug - draw the node location
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mLocation.x, mLocation.y, mLocation.z + 0.1f);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mNodeLocation.x, mNodeLocation.y, mNodeLocation.z + 0.1f);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 1.0");
//DEBUG - draw the move location
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mLocation.x, mLocation.y, mLocation.z + 0.2f);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mMoveLocation.x, mMoveLocation.y, mMoveLocation.z + 0.2f);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "0.0 1.0 1.0");
//Debug - draw the move destination
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mLocation.x, mLocation.y, mLocation.z);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mMoveDestination.x, mMoveDestination.y, mMoveDestination.z);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "0.0 0.0 1.0");
//Debug - draw the aim location
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mMuzzlePosition.x, mMuzzlePosition.y, mMuzzlePosition.z);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mAimLocation.x, mAimLocation.y, mAimLocation.z);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 0.0");
}
//debugging code for if the bot is piloting a vehicle
else if (myVehicle)
{
//Debug - destination
dSprintf(textBuf, sizeof(textBuf), "Destination: %.3f %.3f %.3f", mPilotDestination.x, mPilotDestination.y, mPilotDestination.z);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//Debug - distance to dest
dSprintf(textBuf, sizeof(textBuf), "Distance to dest2D: %.3f", mPilotDistToDest2D);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//Debug - aim location
dSprintf(textBuf, sizeof(textBuf), "Destination: %.3f %.3f %.3f", mPilotAimLocation.x, mPilotAimLocation.y, mPilotAimLocation.z);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//Debug - current speed/thrust
dSprintf(textBuf, sizeof(textBuf), "Velocity: %.3f, Thrust: %.3f", mPilotCurVelocity, mPilotCurThrust);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//Debug - current pitch, desired pitch, pitch inc
dSprintf(textBuf, sizeof(textBuf), "current pitch: %.3f, desired pitch: %.3f, pitch inc: %.3f", mCurrentPitch, mDesiredPitch, mPitchIncrement);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//Debug - current pitch, desired pitch, pitch inc
dSprintf(textBuf, sizeof(textBuf), "pitchUpMax: %.3f, pitchDownMax: %.3f", mPitchUpMax, mPitchDownMax);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//Debug - current yaw, yaw diff
dSprintf(textBuf, sizeof(textBuf), "curYaw: %.3f", mCurrentYaw);
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), textBuf);
//DEBUG - jetting
Con::executef(4, "aiDebugText", idBuf, avar("%d", lineNum++), mTriggers[JetTrigger] ? "JETTING" : "");
//DEBUG - clear lines
char startPt[64], endPt[64];
Con::executef(2, "aiDebugClearLines", idBuf);
//Debug - draw the vehicle destination
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mVehicleLocation.x, mVehicleLocation.y, mVehicleLocation.z);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mPilotDestination.x, mPilotDestination.y, mPilotDestination.z);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "0.0 0.0 1.0");
//Debug - draw the vehicle aim location
dSprintf(startPt, sizeof(startPt), "%.3f %.3f %.3f", mVehicleLocation.x, mVehicleLocation.y, mVehicleLocation.z + 0.1f);
dSprintf(endPt, sizeof(endPt), "%.3f %.3f %.3f", mPilotAimLocation.x, mPilotAimLocation.y, mPilotAimLocation.z + 0.1f);
Con::executef(5, "aiDebugLine", idBuf, startPt, endPt, "1.0 0.0 0.0");
}
}

341
ai/aiMath.cc Normal file
View file

@ -0,0 +1,341 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/aiMath.h"
#include "ai/tBinHeap.h"
#include "core/realComp.h"
//-------------------------------------------------------------------------------------
F32 solveForZ(const PlaneF& plane, const Point3F& point)
{
AssertFatal(mFabs(plane.z) > 0.0001, "solveForZ() expects non-vertical plane");
return (-plane.d - plane.x * point.x - plane.y * point.y) / plane.z;
}
//-------------------------------------------------------------------------------------
// Simple "iterator", used as in-
// for (mArea.start(step); mArea.pointInRect(step); mArea.step(step))
// ...
bool GridArea::start(Point2I& steppingPoint) const
{
if (isValidRect())
{
steppingPoint = point;
return true;
}
return false;
}
bool GridArea::step(Point2I& steppingPoint) const
{
if (++steppingPoint.x >= (point.x + extent.x))
{
steppingPoint.x = point.x;
if (++steppingPoint.y >= (point.y + extent.y))
return false;
}
return true;
}
//-------------------------------------------------------------------------------------
GridVisitor::GridVisitor(const GridArea & area)
: mArea(area.point, area.extent)
{
mPostCheck = mPreCheck = true;
}
// Default versions of virtuals. The Before and After callbacks just remove themselves:
bool GridVisitor::beforeDivide(const GridArea&,S32) {return !(mPreCheck = false);}
bool GridVisitor::atLevelZero(const GridArea&) {return true; }
bool GridVisitor::afterDivide(const GridArea&,S32,bool) {return !(mPostCheck= false);}
// Recursive region traversal.
// R is a box of width 2^L, and aligned on like boundary (L low bits all zero)
bool GridVisitor::recurse(GridArea R, S32 L)
{
bool success = true;
if (! R.overlaps(mArea)) {
success = false;
}
else if (L == 0) {
success = atLevelZero(R);
}
else if (mPreCheck && mArea.contains(R) && !beforeDivide(R, L)) { // early out?
success = false;
}
else {
// Made it to sub-division!
S32 half = 1 << (L - 1);
for (S32 y = half; y >= 0; y -= half)
for (S32 x = half; x >= 0; x -= half)
if (!recurse(GridArea(R.point.x + x, R.point.y + y, half, half), L - 1))
success = false;
// Post order stuff-
if (mPostCheck && mArea.contains(R) && !afterDivide(R, L, success))
success = false;
}
return success;
}
bool GridVisitor::traverse()
{
S32 level = 1;
GridArea powerTwoRect (-1, -1, 2, 2);
// Find the power-of-two-sized rect that encloses user-supplied grid.
while (!powerTwoRect.contains(mArea))
if (++level < 31) {
powerTwoRect.point *= 2;
powerTwoRect.extent *= 2;
}
else
return false;
// We have our rect and starting level, perform the recursive traversal:
return recurse(powerTwoRect, level);
}
//-------------------------------------------------------------------------------------
// Find distance of point from the segment. If the point's projection onto
// line isn't within the segment, then take distance from the endpoints.
//
// This also leaves the closest point set in soln.
//
F32 LineSegment::distance(const Point3F & p)
{
VectorF vec1 = b - a, vec2 = p - a;
F32 len = vec1.len(), dist = vec2.len();
soln = a; // good starting default.
// First check for case where A and B are basically same (avoid overflow) -
// can return distance from either point.
if( len > 0.001 )
{
// normalize vector from A to B and get projection of AP along it.
F32 dot = mDot( vec1 *= (1/len), vec2 );
if(dot >= 0)
{
if(dot <= len)
{
F32 sideSquared = (dist * dist) - (dot * dot);
if (sideSquared <= 0) // slight imprecision led to NaN
dist = sideSquared = 0;
else
dist = mSqrt(sideSquared);
soln += (vec1 * dot);
}
else
{
soln = b;
dist = (b - p).len();
}
}
}
return dist;
}
// Special check which does different math in 3D and 2D. If the test is passed
// in 3d, then do a 2d check.
bool LineSegment::botDistCheck(const Point3F& p, F32 dist3, F32 dist2)
{
F32 d3 = distance(p);
if (d3 < dist3)
{
Point2F proj2d(soln.x - p.x, soln.y - p.y);
F32 d2 = proj2d.lenSquared();
dist2 *= dist2;
return (d2 < dist2);
}
return false;
}
//-------------------------------------------------------------------------------------
// Get a list of grid offsets that "spirals" out in order of closeness.
// This is used by the navigation (preprocess) code to find nearest
// obstructed grid location, and it uses this for its search order.
S32 getGridSpiral(Vector<Point2I> & spiral, Vector<F32> & dists, S32 radius)
{
S32 x, y, i;
Vector<Point2I> pool;
BinHeap<F32> heap;
// Build all the points that we'll want to consider. We're going to
// leave off those points that are outside of circle.
for( y = -radius; y <= radius; y++ )
for( x = -radius; x <= radius; x++ )
{
F32 dist = Point2F( F32(x), F32(y) ).len();
if( dist <= F32(radius) + 0.00001 )
{
pool.push_back( Point2I(x,y) );
heap.insert( -dist ); // negate for sort order
}
}
// Get the elements out in order.
heap.buildHeap();
while( (i = heap.headIndex()) >= 0 )
{
spiral.push_back( pool[i] );
dists.push_back( -heap[i] ); // get the (sign-restored) distance
heap.removeHead();
}
return spiral.size();
}
//-----------------------------------------------------------------
// Line Stepper class.
void LineStepper::init(const Point3F & a, const Point3F & b)
{
solution = A = a, B = b;
total = (dir1 = B - A).len();
soFar = advance = 0.0f;
if( total > 0.00001 )
{
dir1 *= (1 / total);
}
else
{
total = 0.0f;
dir1.set(0,0,0);
}
}
//
// Finds the intersection of the line with the sphere on the way out. If there
// are two intersections, and we're starting outside, this should find the
// second.
//
F32 LineStepper::getOutboundIntersection(const SphereF & S)
{
// get projection of sphere center along our line
F32 project = mDot( S.center - A, dir1 );
// find point nearest to center of sphere
Point3F nearPoint = A + (dir1 * project);
// if this isn't in the sphere, then there's no intersection. negative return flags this.
if( ! S.isContained(nearPoint) )
return -1.0f;
// there is an intersection, figure how far from nearest point it is
F32 length = mSqrt( S.radius*S.radius - (nearPoint-S.center).lenSquared() );
// find the solution point and advance amount (which is return value)
solution = nearPoint + dir1 * length;
return( advance = length + project );
}
const Point3F& LineStepper::advanceToSolution()
{
if( advance != 0.0f )
{
soFar += advance;
A = solution;
advance = 0.0f;
}
return solution;
}
//---------------------------------------------------------------
// Finds the center of Mass of a CONVEX polygon.
// Verts should contain an array of Point2F's, in order,
// and of size n, that will represent the polygon.
//---------------------------------------------------------------
bool polygonCM(S32 n, Point2F *verts, Point2F *CM)
{
if(!verts || n < 3)
return false;
F32 area, area2 = 0.0;
Point2F cent;
CM->x = 0;
CM->y = 0;
U32 i;
for(i = 1; i < (n-1); i++)
{
triCenter(verts[0], verts[i], verts[i+1], cent);
area = triArea(verts[0], verts[i], verts[i+1]);
CM->x += area * cent.x;
CM->y += area * cent.y;
area2 += area;
}
CM->x /= 3 * area2;
CM->y /= 3 * area2;
return true;
}
//-------------------------------------------------------------------------------------
// Lifted from tools/morian/CSGBrush.cc. This converts to doubles for insurance, which
// shouldn't pose a speed problem since this is mainly used in graph preprocess.
bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut)
{
Point3D p1(p.x, p.y, p.z);
Point3D p2(q.x, q.y, q.z);
Point3D p3(r.x, r.y, r.z);
F64 d1 = p.d, d2 = q.d, d3 = r.d;
F64 bc = (p2.y * p3.z) - (p3.y * p2.z);
F64 ac = (p2.x * p3.z) - (p3.x * p2.z);
F64 ab = (p2.x * p3.y) - (p3.x * p2.y);
F64 det = (p1.x * bc) - (p1.y * ac) + (p1.z * ab);
// Parallel planes
if (mFabsD(det) < 1e-5)
return false;
F64 dc = (d2 * p3.z) - (d3 * p2.z);
F64 db = (d2 * p3.y) - (d3 * p2.y);
F64 ad = (d3 * p2.x) - (d2 * p3.x);
F64 detInv = 1.0 / det;
pOut->x = ((p1.y * dc) - (d1 * bc) - (p1.z * db)) * detInv;
pOut->y = ((d1 * ac) - (p1.x * dc) - (p1.z * ad)) * detInv;
pOut->z = ((p1.y * ad) + (p1.x * db) - (d1 * ab)) * detInv;
return true;
}
bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result)
{
if (v1.isZero() || v2.isZero() || (result == NULL))
return false;
Point3F v1Norm = v1, v2Norm = v2;
v1Norm.normalize();
v2Norm.normalize();
if ((! isZero(1.0f - v1Norm.len())) || (! isZero(1.0f - v2Norm.len())))
return false;
//make sure the vectors are non-colinear
F32 dot = mDot(v1Norm, v2Norm);
if (dot > 0.999f || dot < -0.999f)
return false;
//find the cross product, and normalize the result
mCross(v1Norm, v2Norm, result);
result->normalize();
return true;
}

349
ai/aiMath.h Normal file
View file

@ -0,0 +1,349 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHMATH_H_
#define _GRAPHMATH_H_
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _MSPHERE_H_
#include "math/mSphere.h"
#endif
#ifndef _MATHIO_H_
#include "math/mathIO.h"
#endif
//-------------------------------------------------------------------------------------
// One-line convenience functions:
inline bool validArrayIndex(S32 index, S32 arrayLength)
{
return U32(index) < U32(arrayLength); // (Unsigned compare handles < 0 case too)
}
inline F32 triArea(const Point2F& a, const Point2F& b, const Point2F& c)
{
return mFabs((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y));
}
inline void triCenter(const Point2F& p1, const Point2F& p2, const Point2F& p3, Point2F &c)
{
c.x = p1.x + p2.x + p3.x;
c.y = p1.y + p2.y + p3.y;
}
// prototype for finding the CM(center of mass) of a convex poly
bool polygonCM(S32 n, Point2F *verts, Point2F *CM);
// Find point at intersection of three planes if such exists (returns true if so).
bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut);
// Lookup into NxN table in which one entry is stored for each pair of unique indices.
inline S32 triangularTableIndex(S32 i1, S32 i2) {
if (i1 > i2)
return (((i1 - 1) * i1) >> 1) + i2;
else
return (((i2 - 1) * i2) >> 1) + i1;
}
//-------------------------------------------------------------------------------------
// Where vertical line through point hits plane (whose normal.z != 0):
F32 solveForZ(const PlaneF& plane, const Point3F& point);
// Cross product, but with a check for bad input (parallel vectors):
bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result);
//-------------------------------------------------------------------------------------
// Encapsulate some common operations on the grid rectangles
class GridArea : public RectI
{
public:
GridArea() { }
GridArea(const Point2I& pt,const Point2I& ext) : RectI(pt,ext) {}
GridArea(S32 L, S32 T, S32 W, S32 H) : RectI(L,T,W,H) {}
GridArea(const GridArea & g) : RectI(g.point,g.extent) {}
// grid to index, and vice versa:
S32 getIndex(Point2I pos) const;
Point2I getPos(S32 index) const;
// step through grid in index order (Y outer loop):
bool start(Point2I &p) const;
bool step(Point2I &p) const;
};
inline S32 GridArea::getIndex(Point2I p) const
{
p -= point;
if( validArrayIndex(p.x, extent.x) )
if( validArrayIndex(p.y, extent.y) )
return p.y * extent.x + p.x;
return -1;
}
inline Point2I GridArea::getPos(S32 index) const
{
Point2I P;
P.x = point.x + index % extent.x;
P.y = point.y + index / extent.x;
return P;
}
//-------------------------------------------------------------------------------------
// Linear range mapping.
// Get value that is pct way between first and last. Note this is probably a better
// way of getting midpoints than (A+B)*.5 due to rounding.
template <class T> T scaleBetween(T first, T last, F32 pct)
{
return first + (pct * (last - first));
}
// Get percent of way of value between min and max, clamping return to range [0,1]
template <class T> F32 getPercentBetween(T value, T min, T max)
{
if(mFabs((max - min)) < 0.0001)
return 0.0f;
else if(min < max){
if (value <= min) return 0.0f;
else if (value >= max) return 1.0f;
}
else if (min > max){
if (value >= min) return 0.0f;
else if (value <= max) return 1.0f;
}
return F32((value - min) / (max - min));
}
// Map value from domain into the given range, capping on ends-
template <class T> T mapValueLinear(T value, T dmin, T dmax, T rmin, T rmax)
{
return scaleBetween(rmin, rmax, getPercentBetween(value, dmin, dmax));
}
template <class T> T mapValueQuadratic(T value, T dmin, T dmax, T rmin, T rmax)
{
F32 pct = getPercentBetween(value, dmin, dmax);
return scaleBetween(rmin, rmax, pct * pct);
}
template <class T> T mapValueSqrt(T value, T dmin, T dmax, T rmin, T rmax)
{
F32 pct = getPercentBetween(value, dmin, dmax);
return scaleBetween(rmin, rmax, mSqrt(pct));
}
//-------------------------------------------------------------------------------------
// An object to do a quad-tree traversal of a grid region.
class GridVisitor
{
protected:
bool mPreCheck;
bool mPostCheck;
bool recurse(GridArea rect, S32 level);
public:
const GridArea mArea;
GridVisitor(const GridArea & area);
// virtuals - how you get visited:
virtual bool beforeDivide(const GridArea& R, S32 level);
virtual bool atLevelZero(const GridArea& R);
virtual bool afterDivide(const GridArea& R, S32 level, bool success);
// call the traversal:
bool traverse();
};
//-------------------------------------------------------------------------------------
// Return if the two points are within the given threshold distance.
template <class PointType, class DistType>
bool within(PointType p1, const PointType & p2, DistType thresh)
{
return (p1 -= p2).lenSquared() <= (thresh * thresh);
}
inline bool within_2D(const Point3F & A, const Point3F & B, F32 D)
{
Point2F A2d( A.x, A.y );
Point2F B2d( B.x, B.y );
return within( A2d, B2d, D );
}
//-------------------------------------------------------------------------------------
// Get a list of grid offsets that "spirals" out in order of closeness.
S32 getGridSpiral(Vector<Point2I> & spiral, Vector<F32> & dists, S32 radius);
//-------------------------------------------------------------------------------------
// Given a point, used to find closest distance/point on segment.
class LineSegment
{
Point3F a, b;
Point3F soln;
public:
LineSegment(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;}
LineSegment() {soln=a=b=Point3F(0,0,0);}
public:
void set(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;}
F32 distance(const Point3F& p);
bool botDistCheck(const Point3F& p, F32 d3, F32 d2);
Point3F solution() {return soln;}
Point3F getEnd(bool which) {return which ? b : a;}
};
//-------------------------------------------------------------------------------------
class LineStepper
{
Point3F A, B;
Point3F dir1; // unit direction vector from A to B.
F32 total; // total dist from A to B.
F32 soFar; // how far we've come.
F32 advance; // solutions to queries are kept until the user
Point3F solution; // tells us to "officially" advance.
public:
LineStepper(const Point3F & a, const Point3F & b) { init(a,b); }
const Point3F & getSolution() { return solution; }
F32 distSoFar() { return soFar; }
F32 totalDist() { return total; }
F32 remainingDist() { return total-soFar; }
// Methods that actually do stuff:
void init(const Point3F & a, const Point3F & b);
F32 getOutboundIntersection(const SphereF & s);
const Point3F & advanceToSolution();
};
//-------------------------------------------------------------------------------------
// reverse elements in place
template <class T> Vector<T> & reverseVec( Vector<T> & vector )
{
for(S32 halfWay = (vector.size() >> 1); halfWay; /* dec'd in loop */ )
{
T & farOne = * (vector.end() - halfWay--);
T & nearOne = * (vector.begin() + halfWay);
T tmp=farOne; farOne=nearOne; nearOne=tmp; // swap
}
return vector;
}
//--------------------------------------------------------------------------
// Read/Write a vector of items, using T.read(stream), T.write(stream).
// Skip function reads same amount as readVector1(), bypassing data.
template <class T> bool readVector1(Stream & stream, Vector<T> & vec)
{
S32 num, i;
bool Ok = stream.read( & num );
for( i = 0, vec.setSize( num ); i < num && Ok; i++ )
Ok = vec[i].read( stream );
return Ok;
}
template <class T> bool writeVector1(Stream & stream, const Vector<T> & vec)
{
bool Ok = stream.write( vec.size() );
for( U32 i = 0; i < vec.size() && Ok; i++ )
Ok = vec[i].write( stream );
return Ok;
}
//-------------------------------------------------------------------------------------
// Read/Write a vector of items, using stream.read(T), stream.write(T).
template <class T> bool readVector2(Stream & stream, Vector<T> & vec)
{
S32 num, i;
bool Ok = stream.read( & num );
for( i = 0, vec.setSize( num ); i < num && Ok; i++ )
Ok = stream.read(&vec[i]);
return Ok;
}
template <class T> bool writeVector2(Stream & stream, const Vector<T> & vec)
{
bool Ok = stream.write( vec.size() );
for( S32 i = 0; i < vec.size() && Ok; i++ )
Ok = stream.write(vec[i]);
return Ok;
}
//-------------------------------------------------------------------------------------
// Functions to read / write a vector of items that define mathRead() / mathWrite().
template <class T> bool mathReadVector(Vector<T>& vec, Stream& stream)
{
S32 num, i;
bool Ok = stream.read(&num);
for (i = 0, vec.setSize(num); i < num && Ok; i++ )
Ok = mathRead(stream, &vec[i]);
return Ok;
}
template <class T> bool mathWriteVector(const Vector<T>& vec, Stream& stream)
{
bool Ok = stream.write(vec.size());
for (S32 i = 0; i < vec.size() && Ok; i++)
Ok = mathWrite(stream, vec[i]);
return Ok;
}
//-------------------------------------------------------------------------------------
// Perform setSize() and force construction of all the elements. Done to address some
// difficulty experienced in getting virtual function table pointer constructed.
template <class T>
void setSizeAndConstruct(Vector<T> & vector, S32 size)
{
vector.setSize(size);
T * tList = new T [size];
S32 memSize = size * sizeof(T);
dMemcpy(vector.address(), tList, memSize);
delete [] tList;
}
template <class T>
void destructAndClear(Vector<T> & vector)
{
for (S32 i = vector.size() - 1; i >= 0; i--)
vector[i].~T();
vector.clear();
}
template <class T>
void setSizeAndClear(Vector<T> & vector, S32 size, S32 value=0)
{
vector.setSize( size );
dMemset(vector.address(), value, vector.memSize());
}
//--------------------------------------------------------------------------
// Read a vector with construction
template <class T> bool constructVector(Stream & stream, Vector<T> & vec)
{
S32 num, i;
bool Ok = stream.read(&num);
if(num && Ok)
{
setSizeAndConstruct(vec, num);
for (i = 0; i < num && Ok; i++)
Ok = vec[i].read(stream);
}
return Ok;
}
#endif

485
ai/aiNavJetting.cc Normal file
View file

@ -0,0 +1,485 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/aiConnection.h"
#include "ai/aiNavJetting.h"
#include "ai/graphLOS.h"
//-------------------------------------------------------------------------------------
#define GravityConstant 20.0
#define AmountInFront 0.37
#define FastVelocity 10.0
#define PullInPercent 0.63
#define HitPointThresh 1.2
#define JumpWaitCount 3
#define SeekAboveChute 17.0f
//-------------------------------------------------------------------------------------
bool AIJetting::init(const Point3F& dest, bool intoMount, NavJetting * jetInfo)
{
mSeekDest = dest;
mFirstTime = true;
mWillBonk = false;
mLaunchSpeed = 0.1;
mIntoMount = intoMount;
mStatus = AIJetWorking;
mJetInfo = jetInfo;
return true;
}
//-------------------------------------------------------------------------------------
// Run LOS to see if we can safely jump to our destination.
static const U32 scBonkMask = InteriorObjectType|StaticShapeObjectType
|StaticObjectType|TerrainObjectType;
// We don't worry about this if we're jetting up more than 2 meters (in that case
// there shouldn't be a chance of bonking). Also, we don't want to suppress
// take-off jump for long hops (these edges should already be well checked). Mainly
// this check is for those chute connections which had the bonk check suppressed...
bool AIJetting::willBonk(Point3F src, Point3F dst)
{
if (dst.z - src.z < 2.0)
{
Point2F vec2D(src.x-dst.x, src.y-dst.y);
if (vec2D.lenSquared() < (LiberalBonkXY * LiberalBonkXY))
{
src.z = dst.z = (getMax(src.z, dst.z) + 3.7);
Loser los(scBonkMask);
if (!los.haveLOS(src, dst))
return false; // Disable temporarily...
}
}
return false;
}
//-------------------------------------------------------------------------------------
// Called whenever we move the state along.
void AIJetting::newState(S16 state, S16 counter /*=0*/)
{
mCounter = counter;
mState = state;
}
//-------------------------------------------------------------------------------------
// Get distance from landing "wall" - with (dist < 0) meaning we're that much beyond it
F32 AIJetting::distFromWall(const Point3F& loc)
{
VectorF vecToWall = (mWallPoint - loc);
vecToWall.z = 0;
return mDot(vecToWall, mWallNormal);
}
// Assuming we're jetting up - see if it looks like the top of a chute up there.
static bool upChute(const Point3F& from, F32 top, const VectorF& normal, Point3F& soln)
{
RayInfo coll;
Point3F to(from.x, from.y, top + 20.0f);
if (gServerContainer.castRay(from, to, InteriorObjectType, &coll)) {
if (mDot(normal, coll.normal) > 0.05) {
coll.normal.z = 0;
coll.normal.normalize();
if (mDot(normal, coll.normal) > 0.9) { // ~ 25 deg
soln = coll.point;
// return true;
return false; // this has problems with larger chutes... oops.
}
}
}
return false;
}
bool AIJetting::figureLandingDist(AIConnection* ai, F32& dist)
{
F32 A = -(GravityConstant * 0.5);
F32 B = ai->mVelocity.z;
F32 C = (ai->mLocation.z - mLandPoint.z);
F32 solutions[2];
U32 N = mSolveQuadratic(A, B, C, solutions);
if (N > 0)
{
// use the larger time solution (first will be negative, or coming up on soln)
F32 T = solutions[N-1];
// see where this will put us in XY -
dist = T * ai->mVelocity2D.len();
return true;
}
return false;
}
// Do first time setup, plus handle other processing on each frame.
bool AIJetting::firstTimeStuff(AIConnection* ai, Player * player)
{
if (mFirstTime)
{
mFirstTime = false;
mCanInterupt = true;
mWillBonk = false;
mLandPoint = mSeekDest;
mWallNormal = (mSeekDest - ai->mLocation);
mWallNormal.z = 0;
if ((mTotal2D = mWallNormal.len()) < GraphJetFailXY) {
mStatus = AIJetFail;
return false;
}
F32 zDiff = (mSeekDest.z - ai->mLocation.z);
mSlope = (zDiff / mTotal2D);
// Set our desired launch speed based on slope. Zero slope -> full speed,
// Steep slope -> Zero speed. Maps negative slopes to full speed.
mLaunchSpeed = mapValueQuadratic(mSlope, 1.3f, 0.0f, 0.0f, 1.0f);
// Wall normal points along our path in XY plane.
mWallNormal /= mTotal2D;
mUpChute = (mJetInfo && mJetInfo->mChuteUp);
// our dest will be a unit or so beyond for sake of aiming
mWallPoint = mLandPoint;
mSeekDest += (mWallNormal * 1.1);
// Hop over walls-
if (mJetInfo)
mSeekDest.z += mJetInfo->mHopOver;
newState(AssureClear);
}
// Variable that is set when they should look where their heading for right effect-
mShouldAim = false;
if (mIntoMount && player->isMounted()) {
mStatus = AIJetSuccess;
return false;
}
return true;
}
//-------------------------------------------------------------------------------------
// Local function - call it when you should aim - bool variable is always cleared
// unless this is called.
void AIJetting::setAim(const Point3F& /*always mSeekDest now*/)
{
mShouldAim = true;
}
// Public function - find out if should and where at.
bool AIJetting::shouldAimAt(Point3F& atWhere)
{
if (mShouldAim)
atWhere = mSeekDest;
return mShouldAim;
}
//-------------------------------------------------------------------------------------
// We may still need to nudge a little bit to assure we have clearance (the generated
// jetting edges need to sometimes hug a bit close (else some makeable connections get
// get thrown out) so this is how we handle the occasional problem).
static const U32 sLOSMask = InteriorObjectType | StaticShapeObjectType |
StaticObjectType | TerrainObjectType;
static const F32 sClearDistance = 1.4f;
bool AIJetting::assureClear(AIConnection* ai, Player* )
{
// Get loc a little bit above the feet-
Point3F botLoc(ai->mLocation.x, ai->mLocation.y, ai->mLocation.z + 0.2);
if (botLoc.z < mSeekDest.z - 2.0)
{
// This shouldn't go on very long, just need a nudge if anything...
if (++mCounter < 32)
{
Point3F vec = botLoc;
(vec -= mSeekDest).z = 0.0f;
// Usual checks just in case....
F32 len = vec.len();
if (len > 0.1)
{
Loser loser(sLOSMask);
Point3F clearLoc(botLoc.x, botLoc.y, mSeekDest.z);
// Compute vector to come in by-
vec *= (sClearDistance / len);
clearLoc -= vec;
if (!loser.haveLOS(botLoc, clearLoc))
{
// Seek away, our vec contains which way to go...
ai->setMoveLocation(botLoc += vec);
ai->setMoveSpeed(0.6);
return false;
}
}
}
}
mWillBonk = willBonk(ai->mLocation, mSeekDest);
newState(AwaitEnergy);
return false;
}
//-------------------------------------------------------------------------------------
// Wait for amount of energy we think we need.
bool AIJetting::awaitEnergy(AIConnection* ai, Player* player)
{
ai->setMoveLocation(mSeekDest);
if (ai->mVelocity.lenSquared() < 0.2)
{
if (player->getEnergyValue() > 0.99)
{
newState(PrepareToJump);
}
else
{
// Run the energy calculation every frame. We need to use the same methods
// that the graph uses to decide that a given hop is makeable with a certain
// amount of energy ability configuration.
F32 ratings[2];
JetManager::Ability ability;
// Tribes player jetting was remove from the player class.
#if 0
ability.dur = player->getJetAbility(ability.acc, ability.dur, ability.v0);
#else
ability.acc = 0;
ability.dur = 0;
ability.v0 = 0;
#endif
gNavGraph->jetManager().calcJetRatings(ratings, ability);
F32 jetD = gNavGraph->jetManager().jetDistance(ai->mLocation, mSeekDest);
if (jetD < ratings[!mWillBonk && player->canJump()])
newState(PrepareToJump);
}
}
else
ai->setMoveSpeed(0);
return false;
}
//-------------------------------------------------------------------------------------
bool AIJetting::prepareToJump(AIConnection* ai, Player* player)
{
ai->setMoveLocation(mSeekDest);
ai->setMoveSpeed(0);
if (++mCounter >= JumpWaitCount)
{
// Can't check if can jump so often right now - so just do it once...
bool jumpReady = (mCounter==JumpWaitCount) && (player->haveContact() || player->isMounted());
// HACK to remedy problem with never finding a contact surface sometimes.
if (!jumpReady && (mCounter > JumpWaitCount * 8))
jumpReady = (ai->mVelocity.lenSquared() < 0.04);
if (jumpReady)
{
mJumpPoint = ai->mLocation;
Point3F here = ai->mLocation;
here.z += 100;
ai->setMoveLocation(here);
if (!mWillBonk || player->isMounted())
ai->pressJump();
ai->pressJet();
newState(InTheAir);
mCanInterupt = false;
}
}
return false;
}
//-------------------------------------------------------------------------------------
bool AIJetting::inTheAir(AIConnection* ai, Player* player)
{
bool advanceState = false;
if (player->haveContact()) {
if (++mCounter >= 2) {
mStatus = AIJetFail;
return true;
}
}
else
mCounter = 0;
ai->setMoveLocation(mSeekDest);
if (mUpChute) {
// Basically jet until bonk as long as we're going vertically
if (ai->mVelocity2D.lenSquared() > 0.04 || ai->mVelocity.z < -0.1)
mUpChute = false;
else {
ai->setMoveSpeed(0.0f);
ai->pressJet();
}
}
if (!mUpChute) {
if (ai->mLocation.z > mSeekDest.z) {
// Must monitor our Z velocity-
if (ai->mVelocity.z < 0)
ai->setMoveSpeed(0.0f);
else
ai->setMoveSpeed(1.0f);
ai->pressJet();
}
else {
ai->setMoveSpeed(0.0f);
F32 zSpeed = ai->mVelocity.z;
F32 howHigh = zSpeed * (zSpeed / GravityConstant);
if ((zSpeed < 0.01) || (ai->mLocation.z + howHigh) < (mSeekDest.z + 1.2))
ai->pressJet();
}
}
// get 2D distance to wall:
F32 wallD = distFromWall(ai->mLocation);
if (wallD < mTotal2D * 0.5)
setAim(mSeekDest);
if (wallD < 0.1)
advanceState = true;
// We need a good check for failure. One simple measure for failure might be to
// look at component of lateral velocity along the vector to the destination.
// Also, if we run out of energy and are far from destination...
if (!advanceState)
{
F32 landD;
if (AIJetting::figureLandingDist(ai, landD))
if( landD > (wallD - AmountInFront))
advanceState = true;
}
if (advanceState)
newState(SlowToLand);
return false;
}
//-------------------------------------------------------------------------------------
bool AIJetting::slowToLand(AIConnection* ai, Player* player)
{
if (player->haveContact())
{
// First clause meant to handle case where we didn't get off the ledge-
if (ai->mLocation.z - mLandPoint.z > 1.3 && distFromWall(ai->mLocation) > 1.0) {
newState(PrepareToJump, JumpWaitCount-1);
}
//==> We need to use some volume information from the destination.
else {
if (!within(ai->mLocation, mLandPoint, 4.0))
{
mStatus = AIJetFail;
return true;
}
else {
mCanInterupt = true;
newState(WalkToPoint);
}
}
}
else
{
Point2F vel2(ai->mVelocity.x, ai->mVelocity.y);
F32 speed2 = vel2.len();
setAim(mSeekDest);
// Use velocity checks to finish it up-
F32 zvel = mFabs(ai->mVelocity.z);
if (speed2 < 0.1 && zvel < 0.1)
return true;
F32 wallD = distFromWall(ai->mLocation), landD;
bool beyond = figureLandingDist(ai, landD) && (landD > wallD- AmountInFront);
bool pullIn = (speed2 * PullInPercent) > wallD && speed2 > 0.7;
if (beyond && pullIn) // try to slow down
{
ai->setMoveLocation(mJumpPoint);
ai->setMoveSpeed(1.0f);
ai->pressJet();
}
else
{
ai->setMoveLocation(ai->mLocation);
ai->setMoveSpeed(0.0f);
if(! beyond)
ai->pressJet();
}
}
return false;
}
//-------------------------------------------------------------------------------------
bool AIJetting::walkToPoint(AIConnection* ai, Player*)
{
#if 0
mStatus = AIJetSuccess;
ai->setMoveSpeed(0.0f);
return true;
#else
ai->setMoveLocation(mLandPoint);
ai->setMoveTolerance(HitPointThresh * 0.6);
ai->setMoveSpeed(0.3f);
if (within_2D(ai->mLocation, mLandPoint, HitPointThresh) || ++mCounter > 10)
{
// wound up under or over the point...
if (mFabs(ai->mLocation.z - mLandPoint.z) > HitPointThresh)
mStatus = AIJetFail;
else
mStatus = AIJetSuccess;
ai->setMoveSpeed(0.0f);
return true;
}
return false;
#endif
}
//-------------------------------------------------------------------------------------
// Returns true when done.
bool AIJetting::process(AIConnection* ai, Player* player)
{
if (!firstTimeStuff(ai, player))
return true;
switch(mState)
{
case AssureClear: return assureClear(ai, player);
case AwaitEnergy: return awaitEnergy(ai, player);
case PrepareToJump: return prepareToJump(ai, player);
case InTheAir: return inTheAir(ai, player);
case SlowToLand: return slowToLand(ai, player);
case WalkToPoint: return walkToPoint(ai, player);
}
return true;
}

79
ai/aiNavJetting.h Normal file
View file

@ -0,0 +1,79 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AINAVJETTING_H_
#define _AINAVJETTING_H_
enum AIJetStatus
{
AIJetInactive,
AIJetWorking,
AIJetFail,
AIJetSuccess
};
class AIConnection;
class AIJetting
{
protected:
enum JettingStates{
AssureClear,
AwaitEnergy,
PrepareToJump,
InTheAir,
SlowToLand,
WalkToPoint,
};
bool mFirstTime;
bool mUpChute;
bool mIntoMount;
bool mShouldAim;
bool mCanInterupt;
bool mWillBonk;
Point3F mSeekDest;
Point3F mLandPoint;
VectorF mWallNormal;
Point3F mJumpPoint;
Point3F mWallPoint;
Point3F mTopOfChute;
F32 mTotal2D;
F32 mLaunchSpeed;
F32 mSlope;
S32 mState;
S32 mCounter;
AIJetStatus mStatus;
NavJetting * mJetInfo;
void newState(S16 state, S16 counter = 0);
F32 distFromWall(const Point3F& loc);
void setAim(const Point3F& at);
bool willBonk(Point3F src, Point3F dst);
bool figureLandingDist(AIConnection* ai, F32& dist);
bool firstTimeStuff(AIConnection* ai, Player* player);
// States:
bool assureClear(AIConnection* ai, Player* player);
bool awaitEnergy(AIConnection* ai, Player* player);
bool prepareToJump(AIConnection* ai, Player* player);
bool inTheAir(AIConnection* ai, Player* player);
bool slowToLand(AIConnection* ai, Player* player);
bool walkToPoint(AIConnection* ai, Player* player);
public:
AIJetting() {reset();}
AIJetStatus status() {return mStatus;}
void reset() {mStatus = AIJetInactive;}
bool init(const Point3F& dest, bool intoMount = false, NavJetting * inf = 0);
bool process(AIConnection* ai, Player* player);
bool shouldAimAt(Point3F& atWhere);
bool badTimeToSearch() const {return true;} // {return !mCanInterupt;}
};
#endif

28
ai/aiNavStep.cc Normal file
View file

@ -0,0 +1,28 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/aiConnection.h"
#include "ai/aiNavStep.h"
AIStepJet::AIStepJet(const Point3F& dest)
{
mJetting.init(dest);
}
void AIStepJet::process(AIConnection* ai, Player* player)
{
Parent::process(ai, player);
if (mStatus != InProgress)
return;
ai->setMoveMode(AIConnection::ModeWalk);
if( mJetting.process(ai, player) )
mStatus = (mJetting.status() == AIJetSuccess ? Finished : Failed);
}

32
ai/aiNavStep.h Normal file
View file

@ -0,0 +1,32 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AINAVSTEP_H_
#define _AINAVSTEP_H_
#ifndef _AISTEP_H_
#include "ai/aiStep.h"
#endif
#ifndef _GRAPH_H_
#include "ai/graph.h"
#endif
#ifndef _AINAVJETTING_H_
#include "ai/aiNavJetting.h"
#endif
class AIStepJet : public AIStep
{
protected:
typedef AIStep Parent;
AIJetting mJetting;
public:
AIStepJet(const Point3F& dest);
void process(AIConnection* ai, Player* player);
};
#endif

183
ai/aiObjective.cc Normal file
View file

@ -0,0 +1,183 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/consoleTypes.h"
#include "console/consoleInternal.h"
#include "Core/bitStream.h"
#include "ai/aiObjective.h"
IMPLEMENT_CO_NETOBJECT_V1(AIObjective);
Sphere AIObjective::smSphere(Sphere::Octahedron);
//---------------------------------------------------------------------------//
//AIObjective Methods
AIObjective::AIObjective()
{
mTypeMask = StaticObjectType;
mDescription = StringTable->insert("");
mMode = StringTable->insert("");
mTargetClient = StringTable->insert("");
mTargetObject = StringTable->insert("");
mTargetClientId = -1;
mTargetObjectId = -1;
mLocation.set(0, 0, 0);
mWeightLevel1 = 0;
mWeightLevel2 = 0;
mWeightLevel3 = 0;
mWeightLevel4 = 0;
mOffense = false;
mDefense = false;
mRequiredEquipment = StringTable->insert("");
mDesiredEquipment = StringTable->insert("");
mBuyEquipmentSets = StringTable->insert("");
mCannedChat = StringTable->insert("");
mIssuedByHuman = false;
mIssuedByClientId = -1;
mForceClientId = -1;
mLocked = false;
//this will allow us to access the persist fields from script
setModStaticFields(true);
}
//----------------------------------------------------------------------------
bool AIObjective::onAdd()
{
if(!Parent::onAdd())
return(false);
//set the task namespace
const char *name = getName();
if(name && name[0] && getClassRep())
{
Namespace *parent = getClassRep()->getNameSpace();
Con::linkNamespaces(parent->mName, name);
mNameSpace = Con::lookupNamespace(name);
}
if(!isClientObject())
setMaskBits(UpdateSphereMask);
return(true);
}
//----------------------------------------------------------------------------
void AIObjective::inspectPostApply()
{
Parent::inspectPostApply();
setMaskBits(UpdateSphereMask);
}
void AIObjective::consoleInit()
{
Parent::consoleInit();
}
//----------------------------------------------------------------------------
void AIObjective::initPersistFields()
{
Parent::initPersistFields();
addField("description", TypeString, Offset(mDescription, AIObjective));
addField("mode", TypeString, Offset(mMode, AIObjective));
addField("targetClient", TypeString, Offset(mTargetClient, AIObjective));
addField("targetObject", TypeString, Offset(mTargetObject, AIObjective));
addField("targetClientId", TypeS32, Offset(mTargetClientId, AIObjective));
addField("targetObjectId", TypeS32, Offset(mTargetObjectId, AIObjective));
addField("location", TypePoint3F, Offset(mLocation, AIObjective));
addField("weightLevel1", TypeS32, Offset(mWeightLevel1, AIObjective));
addField("weightLevel2", TypeS32, Offset(mWeightLevel2, AIObjective));
addField("weightLevel3", TypeS32, Offset(mWeightLevel3, AIObjective));
addField("weightLevel4", TypeS32, Offset(mWeightLevel4, AIObjective));
addField("offense", TypeBool, Offset(mOffense, AIObjective));
addField("defense", TypeBool, Offset(mDefense, AIObjective));
addField("equipment", TypeString, Offset(mRequiredEquipment, AIObjective));
addField("desiredEquipment", TypeString, Offset(mDesiredEquipment, AIObjective));
addField("buyEquipmentSet", TypeString, Offset(mBuyEquipmentSets, AIObjective));
addField("chat", TypeString, Offset(mCannedChat, AIObjective));
addField("issuedByHuman", TypeBool, Offset(mIssuedByHuman, AIObjective));
addField("issuedByClientId", TypeS32, Offset(mIssuedByClientId, AIObjective));
addField("forceClientId", TypeS32, Offset(mForceClientId, AIObjective));
addField("locked", TypeBool, Offset(mLocked, AIObjective));
}
//---------------------------------------------------------------------------//
U32 AIObjective::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
//
if(stream->writeFlag(mask & UpdateSphereMask))
{
}
return(retMask);
}
void AIObjective::unpackUpdate(NetConnection * con, BitStream * stream)
{
Parent::unpackUpdate(con, stream);
if(stream->readFlag())
{
}
}
//---------------------------------------------------------------------------//
//AIObjectiveQ Methods
IMPLEMENT_CONOBJECT(AIObjectiveQ);
static void cAIQSortByWeight(SimObject *obj, S32, const char **)
{
AIObjectiveQ *objectiveQ = static_cast<AIObjectiveQ*>(obj);
objectiveQ->sortByWeight();
}
void AIObjectiveQ::consoleInit()
{
Parent::consoleInit();
Con::addCommand("AIObjectiveQ", "sortByWeight", cAIQSortByWeight, "aiQ.sortByWeight()", 2, 2);
}
void AIObjectiveQ::addObject(SimObject *obj)
{
//only aioObjectives can be part of an objective Queue
AIObjective *objective = dynamic_cast<AIObjective*>(obj);
if (! objective)
{
Con::printf("Error AIObjective::addObject() - attempting to add something other than an AIObjective!");
//print the error, but don't return - in case I need to revert the ai scripts...
//return;
}
Parent::addObject(obj);
}
S32 QSORT_CALLBACK AIObjectiveQ::compareWeight(const void* a,const void* b)
{
return (*reinterpret_cast<const AIObjective* const*>(b))->getSortWeight() -
(*reinterpret_cast<const AIObjective* const*>(a))->getSortWeight();
}
void AIObjectiveQ::sortByWeight()
{
dQsort(objectList.address(), objectList.size(), sizeof(SimObject *), compareWeight);
}

105
ai/aiObjective.h Normal file
View file

@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AIOBJECTIVE_H_
#define _AIOBJECTIVE_H_
#ifndef _BITSTREAM_H_
#include "core/bitStream.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _SHAPEBASE_H_
#include "game/shapeBase.h"
#endif
#ifndef _MATHIO_H_
#include "math/mathIO.h"
#endif
#ifndef _SPHERE_H_
#include "game/sphere.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _MISSIONMARKER_H_
#include "game/missionMarker.h"
#endif
class AIObjective : public MissionMarker
{
private:
typedef MissionMarker Parent;
static Sphere smSphere;
StringTableEntry mDescription;
StringTableEntry mMode;
StringTableEntry mTargetClient;
StringTableEntry mTargetObject;
S32 mTargetClientId;
S32 mTargetObjectId;
Point3F mLocation;
S32 mWeightLevel1;
S32 mWeightLevel2;
S32 mWeightLevel3;
S32 mWeightLevel4;
bool mOffense;
bool mDefense;
StringTableEntry mRequiredEquipment;
StringTableEntry mDesiredEquipment;
StringTableEntry mBuyEquipmentSets;
StringTableEntry mCannedChat;
bool mLocked;
bool mIssuedByHuman;
S32 mIssuedByClientId;
S32 mForceClientId;
public:
AIObjective();
S32 getSortWeight() const { return mWeightLevel1; }
static void initPersistFields();
static void consoleInit();
// SimObject
bool onAdd();
void inspectPostApply();
// NetObject
enum SpawnSphereMasks {
UpdateSphereMask = Parent::NextFreeMask,
NextFreeMask = Parent::NextFreeMask << 1
};
// NetObject
U32 packUpdate(NetConnection *, U32, BitStream *);
void unpackUpdate(NetConnection *, BitStream *);
DECLARE_CONOBJECT(AIObjective);
};
class AIObjectiveQ : public SimSet
{
private:
typedef SimSet Parent;
public:
DECLARE_CONOBJECT(AIObjectiveQ);
static void consoleInit();
void addObject(SimObject *obj);
static S32 QSORT_CALLBACK compareWeight(const void* a,const void* b);
void sortByWeight();
};
#endif

446
ai/aiPlayer.cc Normal file
View file

@ -0,0 +1,446 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/aiPlayer.h"
#include "core/realcomp.h"
#include "math/mMatrix.h"
#include "game/moveManager.h"
/**
* Constructor
*/
AIPlayer::AIPlayer() {
mMoveMode = ModeStop;
mMoveDestination.set( 0.0f, 0.0f, 0.0f );
mAimLocation.set( 0.0f, 0.0f, 0.0f );
mMoveSpeed = 0.0f;
mMoveTolerance = 0.25f;
// Clear the triggers
for( int i = 0; i < MaxTriggerKeys; i++ )
mTriggers[i] = false;
mAimToDestination = true;
mRotation.set( 0.0f, 0.0f, 0.0f );
mLocation.set( 0.0f, 0.0f, 0.0f );
mPlayer = NULL;
}
/**
* Sets the object the bot is targeting
*
* @param targetObject The object to target
*/
void AIPlayer::setTargetObject( ShapeBase *targetObject ) {
if ( !targetObject || !bool( mTargetObject ) || targetObject->getId() != mTargetObject->getId() )
mTargetInSight = false;
mTargetObject = targetObject;
}
/**
* Returns the target object
*
* @return Object bot is targeting
*/
S32 AIPlayer::getTargetObject() {
if( bool( mTargetObject ) )
return mTargetObject->getId();
else
return -1;
}
/**
* Sets the speed at which this AI moves
*
* @param speed Speed to move, default player was 10
*/
void AIPlayer::setMoveSpeed( F32 speed ) {
if( speed <= 0.0f )
mMoveSpeed = 0.0f;
else
mMoveSpeed = getMin( 1.0f, speed );
}
/**
* Sets the movement mode for this AI
*
* @param mode Movement mode, see enum
*/
void AIPlayer::setMoveMode( S32 mode ) {
if( mode < 0 || mode >= ModeCount )
mode = 0;
mMoveMode = mode;
}
/**
* Sets how far away from the move location is considered
* "on target"
*
* @param tolerance Movement tolerance for error
*/
void AIPlayer::setMoveTolerance( F32 tolerance ) {
mMoveTolerance = getMax( 0.1f, tolerance );
}
/**
* Sets the location for the bot to run to
*
* @param location Point to run to
*/
void AIPlayer::setMoveDestination( const Point3F &location ) {
// Ok, here's the story...we're going to aim where we are going UNLESS told otherwise
if( mAimToDestination ) {
mAimLocation = location;
mAimLocation.z = 0.0f;
}
mMoveDestination = location;
// TEST CODE
RayInfo dummy;
if( mPlayer ) {
if( !mPlayer->getContainer()->castRay( mLocation, location, InteriorObjectType |
StaticShapeObjectType | StaticObjectType |
TerrainObjectType, &dummy ) )
Con::printf( "I can see the target." );
else
Con::printf( "I can't see my target!! AAAAHHHHH!" );
}
}
/**
* Sets the location for the bot to aim at
*
* @param location Point to aim at
*/
void AIPlayer::setAimLocation( const Point3F &location ) {
mAimLocation = location;
mAimToDestination = false;
}
/**
* Clears the aim location and sets it to the bot's
* current destination so he looks where he's going
*/
void AIPlayer::clearAim() {
mAimLocation = Point3F( 0.0f, 0.0f, 0.0f );
mAimToDestination = true;
}
/**
* This method gets the move list for an object, in the case
* of the AI, it actually calculates the moves, and then
* sends them down the pipe.
*
* @param movePtr Pointer to move the move list into
* @param numMoves Number of moves in the move list
*/
void AIPlayer::getMoveList( Move **movePtr,U32 *numMoves ) {
//initialize the move structure and return pointers
mMove = NullMove;
*movePtr = &mMove;
*numMoves = 1;
// Check if we got a player
mPlayer = NULL;
mPlayer = dynamic_cast<Player *>( getControlObject() );
// We got a something controling us?
if( !mPlayer )
return;
// If system is disabled, don't process
if ( !gAISystemEnabled )
return;
// What is The Matrix?
MatrixF moveMatrix;
moveMatrix.set( EulerF( 0, 0, 0 ) );
moveMatrix.setColumn( 3, Point3F( 0, 0, 0 ) );
moveMatrix.transpose();
// Position / rotation variables
F32 curYaw, curPitch;
F32 newYaw, newPitch;
F32 xDiff, yDiff, zDiff;
// Check if we are dead, if so, throw the script callback -- PUT THIS IN AIPLAYER
//if( !dStricmp( mPlayer->getStateName(), "dead" ) ) {
// TO-DO: Script callback
// return;
//}
F32 moveSpeed = mMoveSpeed;
switch( mMoveMode ) {
case ModeStop:
return; // Stop means no action, peroid
break;
case ModeIdle:
// TO-DO: Insert callback for scripted idle stuff
break;
case ModeWalk:
moveSpeed /= 2.0f; // Walking speed is half running speed
// Fall through to run
case ModeRun:
// Get my location
MatrixF const& myTransform = mPlayer->getTransform();
myTransform.getColumn( 3, &mLocation );
// Set rotation variables
mRotation = mPlayer->getRotation();
Point3F headRotation = mPlayer->getHeadRotation();
curYaw = mRotation.z;
curPitch = headRotation.x;
xDiff = mAimLocation.x - mLocation.x;
yDiff = mAimLocation.y - mLocation.y;
// first do Yaw
if( !isZero( xDiff ) || !isZero( yDiff ) ) {
// use the cur yaw between -Pi and Pi
while( curYaw > M_2PI )
curYaw -= M_2PI;
while( curYaw < -M_2PI )
curYaw += M_2PI;
// find the new yaw
newYaw = mAtan( xDiff, yDiff );
// find the yaw diff
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if( yawDiff < 0.0f )
yawDiff += M_2PI;
else if( yawDiff >= M_2PI )
yawDiff -= M_2PI;
// now make sure we take the short way around the circle
if( yawDiff > M_PI )
yawDiff -= M_2PI;
else if( yawDiff < -M_PI )
yawDiff += M_2PI;
mMove.yaw = yawDiff;
// set up the movement matrix
moveMatrix.set( EulerF( 0, 0, newYaw ) );
}
else
moveMatrix.set( EulerF( 0, 0, curYaw ) );
// next do pitch
F32 horzDist = Point2F( mAimLocation.x, mAimLocation.y ).len();
if( !isZero( horzDist ) ) {
//we shoot from the gun, not the eye...
F32 vertDist = mAimLocation.z;
newPitch = mAtan( horzDist, vertDist ) - ( M_PI / 2.0f );
F32 pitchDiff = newPitch - curPitch;
mMove.pitch = pitchDiff;
}
// finally, mMove towards mMoveDestination
xDiff = mMoveDestination.x - mLocation.x;
yDiff = mMoveDestination.y - mLocation.y;
// Check if we should mMove, or if we are 'close enough'
if( ( ( mFabs( xDiff ) > mMoveTolerance ) ||
( mFabs( yDiff ) > mMoveTolerance ) ) && ( !isZero( mMoveSpeed ) ) )
{
if( isZero( xDiff ) )
mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed );
else if( isZero( yDiff ) )
mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed );
else if( mFabs( xDiff ) > mFabs( yDiff ) ) {
F32 value = mFabs( yDiff / xDiff ) * mMoveSpeed;
mMove.y = ( mLocation.y > mMoveDestination.y ? -value : value );
mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed );
}
else {
F32 value = mFabs( xDiff / yDiff ) * mMoveSpeed;
mMove.x = ( mLocation.x > mMoveDestination.x ? -value : value );
mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed );
}
//now multiply the mMove vector by the transpose of the object rotation matrix
moveMatrix.transpose();
Point3F newMove;
moveMatrix.mulP( Point3F( mMove.x, mMove.y, 0 ), &newMove );
//and sub the result back in the mMove structure
mMove.x = newMove.x;
mMove.y = newMove.y;
}
else {
// Ok, we are close enough
setMoveMode( ModeStop );
Con::printf( "I'm close enough to my destination to stop." );
}
break;
}
// Copy over the trigger status
for( int i = 0; i < MaxTriggerKeys; i++ ) {
mMove.trigger[i] = mTriggers[i];
mTriggers[i] = false;
}
}
/**
* This method is just called to stop the bots from running amuck
* while the mission cycles
*/
void AIPlayer::missionCycleCleanup() {
setMoveMode( ModeStop );
}
/**
* Sets the move speed for an AI object, remember, walk is 1/2 run
*/
static void cAISetMoveSpeed( SimObject *obj, S32, const char** argv ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
ai->setMoveSpeed( dAtoi( argv[2] ) );
}
/**
* Stops all AI movement, halt!
*/
static void cAIStop( SimObject *obj, S32, const char** ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
ai->setMoveMode( AIPlayer::ModeStop );
}
/**
* Tells the AI to aim at the location provided
*/
static void cAIAimAtLocation( SimObject *obj, S32 argc, const char** argv ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
Point3F v( 0.0f,0.0f,0.0f );
dSscanf( argv[2], "%f %f %f", &v.x, &v.y, &v.z );
ai->setAimLocation( v );
}
/**
* Returns the point the AI is aiming at
*/
static const char *cAIGetAimLocation( SimObject *obj, S32, const char** ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
Point3F aimPoint = ai->getAimLocation();
char* returnBuffer = Con::getReturnBuffer( 256 );
dSprintf( returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z );
return returnBuffer;
}
/**
* Sets the bots target object
*/
static void cAISetTargetObject( SimObject *obj, S32 argc, const char **argv ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
// Find the target
ShapeBase *targetObject;
if( Sim::findObject( argv[2], targetObject ) )
ai->setTargetObject( targetObject );
else
ai->setTargetObject( NULL );
}
/**
* Gets the object the AI is targeting
*/
static const char *cAIGetTargetObject( SimObject *obj, S32, const char ** ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
S32 targetId = ai->getTargetObject();
char* returnBuffer = Con::getReturnBuffer( 256 );
dSprintf( returnBuffer, 256, "%d", targetId );
return returnBuffer;
}
/**
* Checks to see if the target is in sight
*/
static bool cAITargetInSight( SimObject *obj, S32, const char ** ) {
AIPlayer *ai = static_cast<AIPlayer*>( obj );
return ai->targetInSight();
}
/**
* Tells the bot the mission is cycling
*/
static void cAIMissionCycleCleanup( SimObject *obj, S32, const char ** ) {
AIPlayer *ai = static_cast<AIPlayer*>( obj );
ai->missionCycleCleanup();
}
/**
* Tells the AI to move forward 100 units...TEST FXN
*/
static void cAIMoveForward( SimObject *obj, S32, const char ** ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
ShapeBase *player = ai->getControlObject();
Point3F location;
MatrixF const &myTransform = player->getTransform();
myTransform.getColumn( 3, &location );
location.y += 100.0f;
ai->setMoveDestination( location );
} // *** /TEST FXN
/**
* Sets the AI to walk mode
*/
static void cAIWalk( SimObject *obj, S32, const char ** ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
ai->setMoveMode( AIPlayer::ModeWalk );
}
/**
* Sets the AI to run mode
*/
static void cAIRun( SimObject *obj, S32, const char ** ) {
AIPlayer *ai = static_cast<AIPlayer *>( obj );
ai->setMoveMode( AIPlayer::ModeRun );
}
/**
* Console init function
*/
void AIPlayer::consoleInit() {
// TEST FXN
Con::addCommand( "AIPlayer", "moveForward", cAIMoveForward, "ai.moveForward()", 2, 2 );
Con::addCommand( "AIPlayer", "walk", cAIWalk, "ai.walk()", 2, 2 );
Con::addCommand( "AIPlayer", "run", cAIRun, "ai.run()", 2, 2 );
Con::addCommand( "AIPlayer", "stop", cAIStop, "ai.stop()", 2, 2 );
Con::addCommand( "AIPlayer", "setMoveSpeed", cAISetMoveSpeed, "ai.setMoveSpeed( float )", 3, 3 );
Con::addCommand( "AIPlayer", "setTargetObject", cAISetTargetObject, "ai.setTargetObject( object )", 3, 4 );
Con::addCommand( "AIPlayer", "getTargetObject", cAIGetTargetObject, "ai.getTargetObject()", 2, 2 );
Con::addCommand( "AIPlayer", "targetInSight", cAITargetInSight, "ai.targetInSight()", 2, 2 );
Con::addCommand( "AIPlayer", "aimAt", cAIAimAtLocation, "ai.aimAt( point )", 3, 3 );
Con::addCommand( "AIPlayer", "getAimLocation", cAIGetAimLocation, "ai.getAimLocation()", 2, 2 );
}

92
ai/aiPlayer.h Normal file
View file

@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AIPLAYER_H_
#define _AIPLAYER_H_
#include "ai/aiConnection.h"
#include "game/player.h"
class AIPlayer : public AIConnection {
typedef AIConnection Parent;
private:
enum {
FireTrigger = 0,
JumpTrigger = 2,
JetTrigger = 3,
GrenadeTrigger = 4,
MineTrigger = 5
};
F32 mMoveSpeed;
S32 mMoveMode;
F32 mMoveTolerance; // How close to the destination before we stop
bool mTriggers[MaxTriggerKeys];
bool mTargetInSight;
Player *mPlayer;
Point3F mMoveDestination;
Point3F mLocation;
Point3F mRotation; // Euler really
bool mAimToDestination; // Why is this in here?
Point3F mAimLocation; // Because objects would have facing as well.
SimObjectPtr<ShapeBase> mTargetObject;
public:
DECLARE_CONOBJECT( AIPlayer );
enum
{
ModeStop = 0,
ModeWalk, // Walk is runSpeed / 2
ModeRun,
ModeIdle,
ModeCount // This is in there as a max index value
};
AIPlayer();
void getMoveList( Move **movePtr,U32 *numMoves );
static void consoleInit();
// ---Targeting and aiming sets/gets
void setTargetObject( ShapeBase *targetObject );
S32 getTargetObject();
bool targetInSight() { return mTargetInSight; }
// ---Movement sets/gets
void setMoveSpeed( F32 speed );
F32 getMoveSpeed() { return mMoveSpeed; }
void setMoveMode( S32 mode );
S32 getMoveMode() { return mMoveMode; }
void setMoveTolerance( F32 tolerance );
F32 getMoveTolerance() { return mMoveTolerance; }
void setMoveDestination( const Point3F &location );
Point3F getMoveDestination() { return mMoveDestination; }
// ---Facing(Aiming) sets/gets
void setAimLocation( const Point3F &location );
Point3F getAimLocation() { return mAimLocation; }
void clearAim();
// ---Other
void missionCycleCleanup();
// ---Callbacks
};
#endif

805
ai/aiStep.cc Normal file
View file

@ -0,0 +1,805 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "core/realComp.h"
#include "console/simBase.h"
#include "ai/aiConnection.h"
#include "game/player.h"
#include "game/projectile.h"
#include "math/mRandom.h"
#include "ai/aiStep.h"
AIStep::AIStep()
{
mStatus = InProgress;
}
void AIStep::process(AIConnection *client, Player *player)
{
//only process if we're still "in progress"
if (mStatus != InProgress)
return;
//make sure we have a client and a player
if (!client || !player)
mStatus = Failed;
//make sure we're not dead
if (! dStricmp(player->getStateName(), "dead"))
mStatus = Failed;
}
//-----------------------------------------------------------------------------
//ESCORT step
AIStepEscort::AIStepEscort(GameConnection *clientToEscort)
{
mInitialized = false;
mClientToEscort = clientToEscort;
mResetDestinationCounter = 0;
}
void AIStepEscort::process(AIConnection *client, Player *player)
{
Parent::process(client, player);
if (mStatus != InProgress)
return;
//make sure we have a valid target to escort
Player *targetPlayer = NULL;
if ((! bool(mClientToEscort)) || (! mClientToEscort->getControlObject()))
mStatus = Failed;
else
targetPlayer = dynamic_cast<Player*>(mClientToEscort->getControlObject());
if (! targetPlayer)
{
mStatus = Failed;
return;
}
//set the energy levels
if (! mInitialized)
{
mInitialized = true;
if (targetPlayer->isMounted())
client->setMoveMode(AIConnection::ModeMountVehicle);
else
client->setMoveMode(AIConnection::ModeExpress);
client->setMoveTolerance(4.0f);
client->setEnergyLevels(0.05f, 0.15f);
}
//get the target's current location (global coords)
MatrixF const& targetTransform = targetPlayer->getTransform();
Point3F targetLocation;
targetTransform.getColumn(3, &targetLocation);
//go back to using the euclidean distance
F32 distToTarget = getMax(client->getPathDistRemaining(20), (client->mLocation - targetLocation).len());
//cut down on the number of path searches
if (--mResetDestinationCounter <= 0)
{
mResetDestinationCounter = 5;
client->setMoveDestination(targetLocation);
}
//get the current time
S32 curTime = Sim::getCurrentTime();
//see if we're close enough to the target yet
if (distToTarget < 10.0f)
{
mProximityBuffer = true;
if (client->getMoveMode() != AIConnection::ModeStop)
{
client->setMoveMode(AIConnection::ModeStop);
mStoppedTime = curTime;
mIdleStarted = false;
}
}
else if (distToTarget < 16.0f)
{
if (mProximityBuffer)
{
if (client->getMoveMode() != AIConnection::ModeStop)
{
client->setMoveMode(AIConnection::ModeStop);
mStoppedTime = curTime;
mIdleStarted = false;
}
}
else
{
//make sure we aim at the player momentarily...
if (client->getMoveMode() == AIConnection::ModeStop)
client->setScriptAimLocation(Point3F(targetLocation.x, targetLocation.y, targetLocation.z + 1.6f), 500);
if (targetPlayer->isMounted())
client->setMoveMode(AIConnection::ModeMountVehicle);
else
client->setMoveMode(AIConnection::ModeExpress);
}
}
else
{
//make sure we aim at the player momentarily...
if (client->getMoveMode() == AIConnection::ModeStop)
client->setScriptAimLocation(Point3F(targetLocation.x, targetLocation.y, targetLocation.z + 1.6f), 500);
mProximityBuffer = false;
if (targetPlayer->isMounted())
client->setMoveMode(AIConnection::ModeMountVehicle);
else
client->setMoveMode(AIConnection::ModeExpress);
}
//now see if it's time to "idle"
if (client->getMoveMode() == AIConnection::ModeStop && curTime - mStoppedTime > 5000)
{
//init the idle vars
if (!mIdleStarted)
{
mIdleStarted = true;
mChokePoints.clear();
NavigationGraph::getChokePoints(client->mLocation, mChokePoints, 10, 65);
mIdleNextTime = 0;
}
//see if it's time to look around some more
if (curTime > mIdleNextTime)
{
mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000;
//see if we should look or play an animation
if (gRandGen.randF() < 0.06f)
{
player->setActionThread("pda", false, true, false);
}
else
{
Point3F lookLocation;
if (mChokePoints.size() == 0)
{
if (gRandGen.randF() < 0.3f)
lookLocation = targetLocation;
else
lookLocation = client->mLocation;
}
else
{
S32 index = S32(gRandGen.randF() * (mChokePoints.size() + 0.9));
if (index == mChokePoints.size())
lookLocation = targetLocation;
else
lookLocation = mChokePoints[index];
}
//add some noise to the lookLocation
Point3F tempVector = lookLocation - client->mLocation;
tempVector.z = 0;
if (tempVector.len() < 0.1f)
tempVector.set(1, 0, 0);
tempVector.normalize();
tempVector *= 10.0f;
F32 noise = 2.0f;
tempVector.x += -noise + gRandGen.randF() * 2 * noise;
tempVector.y += -noise + gRandGen.randF() * 2 * noise;
tempVector.z = 1.5f + gRandGen.randF() * 2 * noise;
Point3F newAimLocation = client->mLocation + tempVector;
//now aim at that point
if (!client->scriptIsAiming())
client->setScriptAimLocation(newAimLocation, 3000);
}
}
}
}
//-----------------------------------------------------------------------------
//ENGAGE step
AIStepEngage::AIStepEngage(GameConnection *target)
{
mInitialized = false;
mTarget = target;
mStraifeCounter = 0;
mPauseCounter = 0;
mPausing = false;
mCheckLOSCounter = 0;
mClearLOSToTarget = false;
mSearching = false;
mSearchInitialized = false;
mUsingEnergyWeapon = false;
mEnergyWeaponRecharge = 0.0f;
}
Point3F AIStepEngage::findStraifeLocation(AIConnection *client)
{
Point3F newLocation;
Point3F tempV = client->mLocation - client->mTargLocation;
Point3F newLocationVector;
if (tempV.len() < 0.001f)
tempV.set(0, 1, 0);
tempV.normalize();
mCross(tempV, Point3F(0, 0, 1), &newLocationVector);
if (newLocationVector.len() < 0.001f)
newLocationVector.set(0, 1, 0);
newLocationVector.normalize();
F32 newLocationLength = newLocationVector.len();
if (newLocationLength > 0.9f && newLocationLength < 1.1f)
{
//if we're not moving, choose randomly whether we straife right or left
if (client->mVelocity2D.len() < 1.0f)
newLocation = client->mTargLocation + (newLocationVector * client->mEngageMinDistance);
else
{
//otherwise, choose the point closest to the direction we're already moving...
F32 myAngle = AIConnection::get2DAngle(client->mLocation + client->mVelocity2D, client->mLocation);
F32 tempAngle1 = AIConnection::get2DAngle(client->mLocation, client->mTargLocation + newLocationVector);
F32 tempAngle2 = AIConnection::get2DAngle(client->mLocation, client->mTargLocation - newLocationVector);
if (mFabs(tempAngle1 - myAngle) < mFabs(tempAngle2 - myAngle))
newLocation = client->mTargLocation + (newLocationVector * client->mEngageMinDistance);
else
newLocation = client->mTargLocation - (newLocationVector * client->mEngageMinDistance);
}
}
else
{
Con::printf("DEBUG AIStepEngage::findStraifeLocation() - player and target are on top of each other...");
newLocation = client->mTargLocation;
}
//return the result
return newLocation;
}
void AIStepEngage::process(AIConnection *client, Player *player)
{
Parent::process(client, player);
if (mStatus != InProgress)
return;
//make sure the target is set
if (! mInitialized)
{
mInitialized = true;
//set the target and the energy levels
client->setMoveTolerance(2.0f);
client->setEngageTarget(mTarget);
client->setEnergyLevels(0.3f, 0.2f);
return;
}
//make sure we still have a target
if (! client->mTargetPlayer)
{
mStatus = Finished;
client->setMoveMode(AIConnection::ModeStop);
return;
}
//set the outdoor bools
bool targIsOutdoors = (client->getOutdoorRadius(client->mTargLocation) > 0);
bool playerIsOutdoors = (client->getOutdoorRadius(client->mLocation) > 0);
//set the LOS variables
S32 losTime;
Point3F losLocation;
bool hasLOS = client->hasLOSToClient(client->getEngageTarget(), losTime, losLocation);
//only gain height or straife if the target is outdoors
if (targIsOutdoors)
{
//decriment the straife counter
bool timeToStraife = false;
mStraifeCounter--;
//see if we should try to gain height
S32 mode = client->getMoveMode();
if (mode == AIConnection::ModeGainHeight)
{
//see if we need to cancel the gaining of height
if (client->mEnergy < 0.2f || client->mLocation.z - client->mTargLocation.z > 15.0f ||
client->mEnergy < client->mWeaponEnergy - client->mEnergyReserve)
{
timeToStraife = true;
}
}
else
{
//see if we should try to gain height - only if we're outdoors
if (playerIsOutdoors && mPauseCounter <= 0 && (client->mEnergy > 0.5f || client->mEnergy - client->mTargEnergy > 0.3f) &&
client->mEnergy > client->mWeaponEnergy + client->mEnergyFloat &&
client->mDistToTarg2D < 45.0f && client->mVelocity2D.len() > 5.0f &&
client->mLocation.z - client->mTargLocation.z < 15.0f && client->mSkillLevel >= 0.3f)
client->setMoveMode(AIConnection::ModeGainHeight);
//see if we need to straife
else
{
//see if we're nearish our destination
Point3F straifeVector = client->mLocation - mStraifeLocation;
straifeVector.z = 0;
if (straifeVector.len() < getMin(8, client->mEngageMinDistance))
{
client->setMoveMode(AIConnection::ModeStop);
if (!mPausing)
{
//if we have had LOS to the client within the last 5 seconds at this moment, then pause
//no need to pause if we're behind a hill
if (hasLOS || losTime < 5000)
{
F32 skillAdjust = (1.0f - client->mSkillLevel) *
(1.0f - client->mSkillLevel) *
(1.0f - client->mSkillLevel);
mPauseCounter = S32(gRandGen.randF() * 400.0f * skillAdjust);
mPausing = true;
}
}
mPauseCounter--;
}
if (mStraifeCounter <= 0 || mPauseCounter <= 0)
timeToStraife = true;
}
}
//see if we need to straife
if (timeToStraife)
{
F32 skillAdjust = (1.0f - client->mSkillLevel) *
(1.0f - client->mSkillLevel);
mStraifeCounter = 70 + S32(400.0f * skillAdjust);
mPauseCounter = 32767;
mPausing = false;
mStraifeLocation = findStraifeLocation(client);
client->setMoveMode(AIConnection::ModeExpress);
client->setMoveDestination(mStraifeLocation);
}
}
else
{
//if we've never even detected the client, technically, this is a script error -
//bots shouldn't be fighting anyone they've never seen. However...
if (losLocation == Point3F(0, 0, 0))
losLocation = client->mTargLocation;
//if the targ is Indoors, and we're outdoors, move to the client's position so that
//it's not so rediculously easy to lose the bots just by running into a doorway..
if (playerIsOutdoors)
losLocation = client->mTargLocation;
//see if we have LOS to the target (needed since we use a slightly different point than the LOS detection table
if (--mCheckLOSCounter <= 0)
{
player->disableCollision();
mCheckLOSCounter = 10;
mClearLOSToTarget = false;
RayInfo rayInfo;
Point3F startPt = player->getBoxCenter();
Point3F endPt = client->mTargLocation;
U32 mask = TerrainObjectType | InteriorObjectType | WaterObjectType | ForceFieldObjectType;
mClearLOSToTarget = (! gServerContainer.castRay(startPt, endPt, mask, &rayInfo));
player->enableCollision();
}
//see if we're already at our losLocation, is it time to start idling (searching)?
if (!mClearLOSToTarget && !mSearching && ((client->mLocation - losLocation).len() < 3.0f) && (client->getMoveMode() == AIConnection::ModeStop))
{
mSearching = true;
mSearchInitialized = false;
}
else if (mSearching)
{
//see if we should abort the search (we found our target)
if (mClearLOSToTarget)
{
mSearching = false;
}
//else initialize the search
else if (! mSearchInitialized)
{
mSearchInitialized = true;
mChokeLocation = losLocation;
mChokeIndex = -1;
NavigationGraph::getChokePoints(losLocation, mChokePoints, 10, 65);
mSearchTimer = Sim::getCurrentTime() + 3000;
}
else
{
//see if we're at the choke location, and we've been there for a few seconds...
if (((client->mLocation - mChokeLocation).len() < 3.0f) && (client->getMoveMode() == AIConnection::ModeStop) && (Sim::getCurrentTime() > mSearchTimer))
{
//remove the current choke index from the array
if (mChokeIndex >= 0)
mChokePoints.erase(mChokeIndex);
//if we've run out of choke points to check, we've lost our target...
if (mChokePoints.size() <= 0)
{
mStatus = Failed;
return;
}
else
{
mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f));
mChokeLocation = mChokePoints[mChokeIndex];
mSearchTimer = Sim::getCurrentTime() + (gRandGen.randF() * 1000) + 2000;
client->setMoveMode(AIConnection::ModeExpress);
}
}
//move to the choke point
client->setMoveDestination(mChokeLocation);
}
}
//else we either have LOS, or we're moving to the target's last known location...
else
{
client->setMoveDestination(losLocation);
//if we can see the target, and we're close enough, don't get any closer
if (mClearLOSToTarget && client->mDistToTarg2D < 15.0f)
client->setMoveMode(AIConnection::ModeStop);
else
client->setMoveMode(AIConnection::ModeExpress);
}
}
}
//-----------------------------------------------------------------------------
//RangeObject step
AIStepRangeObject::AIStepRangeObject(GameBase *targetObject, const char *projectile, F32 *minDist, F32 *maxDist, Point3F *fromLocation)
{
mInitialized = false;
mTargetObject = targetObject;
mCheckLOSCounter = 0;
//set the range distances
F32 minDistance = 0;
F32 maxDistance = 1000;
if (*minDist)
minDistance = *minDist;
if (*maxDist)
maxDistance = *maxDist;
mMinDistance = getMax(0.0f, getMin(minDistance, maxDistance));
mMaxDistance = getMax(1.0f, getMax(minDistance, maxDistance));
mFromLocation.set(-1, -1, -1);
if (fromLocation)
mFromLocation = *fromLocation;
mPrevLocation.set(0, 0, 0);
//set the mask
mLOSMask = TerrainObjectType | InteriorObjectType | PlayerObjectType | ForceFieldObjectType;
if (bool(mTargetObject))
{
Player *plyr = dynamic_cast<Player*>(targetObject);
if (bool(plyr))
mLOSMask = TerrainObjectType | InteriorObjectType | StaticShapeObjectType | ForceFieldObjectType;
}
if ((! projectile) || (! projectile[0]) || (! dStricmp(projectile, "NoAmmo")))
mProjectile = NULL;
else
{
if (! Sim::findObject(projectile, mProjectile))
{
Con::printf("AIStepRangeObject() failed - unable to find projectile datablock: %s", projectile);
mProjectile = NULL;
}
}
if (! bool(mTargetObject) || ! bool(mProjectile))
mStatus = Failed;
}
void AIStepRangeObject::process(AIConnection *client, Player *player)
{
//make sure we have a client and a player
if (!client || !player)
{
mStatus = Failed;
return;
}
//make sure we're not dead
if (! dStricmp(player->getStateName(), "Dead"))
{
mStatus = Failed;
return;
}
//make sure we still have a target object
if (! bool(mTargetObject) || ! bool(mProjectile))
{
mStatus = Failed;
return;
}
//make sure the target is set
if (! mInitialized)
{
mInitialized = true;
//set the target and the energy levels
client->setMoveTolerance(0.25f);
client->setEngageTarget(NULL);
mTargetObject->getWorldBox().getCenter(&mTargetPoint);
if (mFromLocation != Point3F(-1, -1, -1))
mGraphDestination = NavigationGraph::findLOSLocation(client->mLocation, mTargetPoint, mMinDistance, SphereF(mFromLocation, mMinDistance / 2.0f), mMaxDistance);
else
mGraphDestination = NavigationGraph::findLOSLocation(client->mLocation, mTargetPoint, mMinDistance, SphereF(client->mLocation, mMinDistance / 2.0f), mMaxDistance);
//if the range is small, the graph may not have enough resolution...
if (mMaxDistance <= 10.0f)
{
if ((mTargetPoint - mGraphDestination).len() > 10.0f)
mGraphDestination = mTargetPoint;
}
client->setMoveDestination(mGraphDestination);
client->setMoveMode(AIConnection::ModeExpress);
mStatus = InProgress;
}
//only check every 6 frames
if (--mCheckLOSCounter <= 0)
{
mCheckLOSCounter = 6;
//see if we're within range, or if we're where the graph told us to go...
F32 directionDot = AIConnection::get2DDot(mGraphDestination - client->mLocation, mTargetPoint - client->mLocation);
F32 distToTarg = (mTargetPoint - client->mLocation).len();
if (((distToTarg <= mMaxDistance || directionDot <= 0.0f) &&
(distToTarg > mMinDistance || directionDot > 0.0f)) ||
((client->mLocation - mGraphDestination).len() < 8.0f))
{
Point3F aimVectorMin, aimVectorMax;
F32 timeMin, timeMax;
bool targetInRange = mProjectile->calculateAim(mTargetPoint, Point3F(0, 0, 0), client->mMuzzlePosition, client->mVelocity, &aimVectorMin, &timeMin, &aimVectorMax, &timeMax);
if (targetInRange)
{
//if we're shooting a ballistic projectile (outside), we don't need
bool playerIsOutdoors = (client->getOutdoorRadius(client->mLocation) > 0);
bool clearLOSToTarget = false;
if (mProjectile->isBallistic && playerIsOutdoors)
clearLOSToTarget = true;
else
{
//see if we have line of site
player->disableCollision();
RayInfo rayInfo;
Point3F startPt = client->mMuzzlePosition;
Point3F endPt = mTargetPoint;
if (! gServerContainer.castRay(startPt, endPt, mLOSMask, &rayInfo))
{
clearLOSToTarget = true;
}
else
{
//if we're here, castRay() hit something, and rayInfo should have been initialized...
if (bool(rayInfo.object) && rayInfo.object->getId() == mTargetObject->getId())
clearLOSToTarget = true;
}
player->enableCollision();
}
//reset the var based on LOS
targetInRange = clearLOSToTarget;
}
if (targetInRange)
{
//stop the client
client->setMoveMode(AIConnection::ModeStop);
mPrevLocation = client->mLocation;
if (mPrevLocation == client->mLocation)
mStatus = Finished;
else
mStatus = InProgress;
}
else
{
//if we're at our graph location, but still aren't within range...
if (((client->mLocation - mGraphDestination).len() < 8.0f) && (client->getMoveMode() == AIConnection::ModeStop))
{
//try a shorter max distance and redo the graph query
mMaxDistance = getMax(mMaxDistance - 10.0f, mMinDistance);
mInitialized = false;
}
//else keep moving
else
{
client->setMoveDestination(mGraphDestination);
client->setMoveMode(AIConnection::ModeExpress);
}
mStatus = InProgress;
}
}
else
{
//if we're at our graph location, but still aren't within range...
if (((client->mLocation - mGraphDestination).len() < 8.0f) && (client->getMoveMode() == AIConnection::ModeStop))
{
//try a shorter max distance and redo the graph query
mMaxDistance = getMax(mMaxDistance - 10.0f, mMinDistance);
mInitialized = false;
}
//else keep moving
else
{
client->setMoveDestination(mGraphDestination);
client->setMoveMode(AIConnection::ModeExpress);
}
mStatus = InProgress;
}
}
}
//-----------------------------------------------------------------------------
//RangeObject step
AIStepIdlePatrol::AIStepIdlePatrol(Point3F *idleLocation)
{
mInitialized = false;
mIdleLocation.set(0, 0, 0);
if (idleLocation)
mIdleLocation = *idleLocation;
mStatus = InProgress;
}
void AIStepIdlePatrol::process(AIConnection *client, Player *player)
{
Parent::process(client, player);
if (mStatus != InProgress)
return;
//make sure the target is set
if (! mInitialized)
{
mInitialized = true;
if (mIdleLocation == Point3F(0, 0, 0))
mIdleLocation = client->mLocation;
NavigationGraph::getChokePoints(mIdleLocation, mChokePoints, 25, 65);
mOutdoorRadius = client->getOutdoorRadius(mIdleLocation);
mIdleState = MoveToLocation;
mStateInit = false;
mMoveLocation = mIdleLocation;
mHeadingHome = true;
return;
}
switch (mIdleState)
{
case MoveToLocation:
if (! mStateInit)
{
mStateInit = true;
client->setMoveDestination(mMoveLocation);
client->setMoveMode(AIConnection::ModeExpress);
client->setMoveTolerance(4.0f);
}
else if (client->getPathDistRemaining(20.0f) < 8.0f)
{
client->setMoveMode(AIConnection::ModeStop);
mIdleState = LookAround;
mStateInit = false;
}
break;
case LookAround:
{
S32 curTime = Sim::getCurrentTime();
if (! mStateInit)
{
mStateInit = true;
mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000;
mIdleEndTime = curTime + 8000 + gRandGen.randF() * 8000;
}
//see if it's time to move somewhere
if (curTime > mIdleEndTime)
{
//choose a choke point
if (mHeadingHome)
{
mHeadingHome = false;
if (mChokePoints.size() == 0)
{
mMoveLocation = mIdleLocation;
if (mOutdoorRadius > 0.0f)
{
F32 noise = getMin(mOutdoorRadius, 30.0f);
mMoveLocation.x += -noise / 2 + gRandGen.randF() * noise;
mMoveLocation.y += -noise / 2 + gRandGen.randF() * noise;
}
}
else
{
mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f));
mMoveLocation = mChokePoints[mChokeIndex];
}
}
else
{
//40% chance of heading to the idle point
if (gRandGen.randF() < 0.4f || mChokePoints.size() <= 1)
{
mHeadingHome = true;
mMoveLocation = mIdleLocation;
}
else
{
mChokeIndex = S32(gRandGen.randF() * (mChokePoints.size() - 0.1f));
mMoveLocation = mChokePoints[mChokeIndex];
}
}
//set the new state
mIdleState = MoveToLocation;
mStateInit = false;
}
//alternate between choosing a new place to look, and pausing to look there
else if (curTime > mIdleNextTime)
{
mIdleNextTime = curTime + 2000 + gRandGen.randF() * 3000;
//see if we should look or play an animation
if (gRandGen.randF() < 0.06f)
{
player->setActionThread("pda", false, true, false);
}
else
{
Point3F lookLocation;
if (mChokePoints.size() == 0)
lookLocation = mIdleLocation;
else
{
S32 index = S32(gRandGen.randF() * (mChokePoints.size() + 0.9));
if (index == mChokePoints.size())
lookLocation = mIdleLocation;
else
lookLocation = mChokePoints[index];
}
//add some noise to the lookLocation
Point3F tempVector = lookLocation - client->mLocation;
tempVector.z = 0;
if (tempVector.len() < 0.001f)
tempVector.set(0, 1, 0);
tempVector.normalize();
tempVector *= 10.0f;
F32 noise = 2.0f;
tempVector.x += -noise + gRandGen.randF() * 2 * noise;
tempVector.y += -noise + gRandGen.randF() * 2 * noise;
tempVector.z = 1.5f + gRandGen.randF() * 2 * noise;
Point3F newAimLocation = client->mLocation + tempVector;
//now aim at that point
if (!client->scriptIsAiming())
client->setScriptAimLocation(newAimLocation, 3000);
}
}
}
break;
}
}

143
ai/aiStep.h Normal file
View file

@ -0,0 +1,143 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AISTEP_H_
#define _AISTEP_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _GAMECONNECTION_H_
#include "game/gameConnection.h"
#endif
#ifndef _PLAYER_H_
#include "game/player.h"
#endif
class AIStep : public SimObject
{
public:
enum StepResult
{
InProgress = 0,
Failed,
Finished
};
int mStatus;
protected:
float get2DAngle(const Point3F &endPt, const Point3F &basePt);
public:
AIStep();
int getStatus() { return mStatus; }
virtual void process(AIConnection *ai, Player *player);
};
class AIStepEscort : public AIStep
{
typedef AIStep Parent;
bool mInitialized;
bool mProximityBuffer;
S32 mResetDestinationCounter;
SimObjectPtr<GameConnection> mClientToEscort;
//add some idle vars
S32 mStoppedTime;
Vector<Point3F> mChokePoints;
bool mIdleStarted;
S32 mIdleNextTime;
public:
AIStepEscort(GameConnection *clientToEscort = NULL);
void process(AIConnection *ai, Player *player);
};
class AIStepEngage : public AIStep
{
typedef AIStep Parent;
bool mInitialized;
SimObjectPtr<GameConnection> mTarget;
S32 mStraifeCounter;
S32 mPauseCounter;
S32 mCheckLOSCounter;
bool mClearLOSToTarget;
bool mPausing;
Point3F mStraifeLocation;
bool mUsingEnergyWeapon;
float mEnergyWeaponRecharge;
bool mSearching;
bool mSearchInitialized;
Point3F mChokeLocation;
Vector<Point3F> mChokePoints;
S32 mChokeIndex;
S32 mSearchTimer;
bool mSearchMove;
public:
AIStepEngage(GameConnection *target = NULL);
void process(AIConnection *ai, Player *player);
void initProcessVars(Player *player, Player *targPlayer);
Point3F findStraifeLocation(AIConnection *client);
};
class AIStepRangeObject : public AIStep
{
typedef AIStep Parent;
bool mInitialized;
SimObjectPtr<GameBase> mTargetObject;
ProjectileData *mProjectile;
U32 mLOSMask;
F32 mMinDistance;
F32 mMaxDistance;
Point3F mTargetPoint;
Point3F mGraphDestination;
Point3F mFromLocation;
Point3F mPrevLocation;
S32 mCheckLOSCounter;
public:
AIStepRangeObject(GameBase *targetObject = NULL, const char *projectile = NULL, F32 *minDist = NULL, F32 *maxDist = NULL, Point3F *fromLocation = NULL);
void process(AIConnection *ai, Player *player);
};
class AIStepIdlePatrol : public AIStep
{
typedef AIStep Parent;
bool mInitialized;
Point3F mIdleLocation;
Point3F mMoveLocation;
Vector<Point3F> mChokePoints;
S32 mChokeIndex;
F32 mOutdoorRadius;
enum IdleStates
{
MoveToLocation,
LookAround,
};
S32 mIdleState;
S32 mIdleNextTime;
S32 mIdleEndTime;
bool mStateInit;
bool mHeadingHome;
public:
AIStepIdlePatrol(Point3F *idleLocation = NULL);
void process(AIConnection *ai, Player *player);
};
#endif

235
ai/aiTask.cc Normal file
View file

@ -0,0 +1,235 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/consoleInternal.h"
#include "ai/aiTask.h"
IMPLEMENT_CONOBJECT(AITask);
static void cAISetWeightFreq(SimObject *obj, S32, const char **argv)
{
AITask *task = static_cast<AITask*>(obj);
// task->setWeightFreq(dAtoi(argv[2]));
// LH - did this until proper optimizations are performed since calcWeight()
// was 10% of some profiles.
task->setWeightFreq(dAtoi(argv[2]) * 3);
}
static void cAISetWeight(SimObject *obj, S32, const char **argv)
{
AITask *task = static_cast<AITask*>(obj);
task->setWeight(dAtoi(argv[2]));
}
static const char* cAIGetWeight(SimObject *obj, S32, const char **)
{
AITask *task = static_cast<AITask*>(obj);
S32 weight = task->getWeight();
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", weight);
return returnBuffer;
}
static void cAIReWeight(SimObject *obj, S32, const char **)
{
AITask *task = static_cast<AITask*>(obj);
task->reWeight();
}
static void cAISetMonitorFreq(SimObject *obj, S32, const char **argv)
{
AITask *task = static_cast<AITask*>(obj);
task->setMonitorFreq(dAtoi(argv[2]));
}
static void cAIReMonitor(SimObject *obj, S32, const char **)
{
AITask *task = static_cast<AITask*>(obj);
task->reMonitor();
}
void AITask::consoleInit()
{
Con::addCommand("AITask", "setWeightFreq", cAISetWeightFreq, "ai.setWeightFreq(freq)", 3, 3);
Con::addCommand("AITask", "setWeight", cAISetWeight, "ai.setWeight(weight)", 3, 3);
Con::addCommand("AITask", "getWeight", cAIGetWeight, "ai.getWeight()", 2, 2);
Con::addCommand("AITask", "reWeight", cAIReWeight, "ai.reWeight()", 2, 2);
Con::addCommand("AITask", "setMonitorFreq", cAISetMonitorFreq, "ai.setMonitorFreq(freq)", 3, 3);
Con::addCommand("AITask", "reMonitor", cAIReMonitor, "ai.reMonitor()", 2, 2);
}
AITask::AITask()
{
mWeightFreq = 30;
mMonitorFreq = 30;
mWeightCounter = 0;
mMonitorCounter = 0;
mWeight = 0;
}
bool AITask::onAdd()
{
if (! Parent::onAdd())
return false;
//set the task namespace
const char *name = getName();
if(name && name[0] && getClassRep())
{
Namespace *parent = getClassRep()->getNameSpace();
Con::linkNamespaces(parent->mName, name);
mNameSpace = Con::lookupNamespace(name);
}
return true;
}
void AITask::calcWeight(AIConnection *ai)
{
//make sure task is valid
if (! ai)
return;
//see if it's time to re-weight the task
// if (--mWeightCounter <= 0)
if (gCalcWeightSlicer.ready(mWeightCounter, mWeightFreq))
{
// mWeightCounter = mWeightFreq;
Con::executef(this, 2, "weight", avar("%d", ai->getId()));
}
}
void AITask::monitor(AIConnection *ai)
{
//make sure task is valid
if (! ai)
return;
if (--mMonitorCounter <= 0)
{
mMonitorCounter = mMonitorFreq;
Con::executef(this, 2, "monitor", avar("%d", ai->getId()));
}
}
void AITask::assume(AIConnection *ai)
{
//make sure task is valid
if (! ai)
return;
mMonitorCounter = 0;
Con::executef(this, 2, "assume", avar("%d", ai->getId()));
}
void AITask::retire(AIConnection *ai)
{
//make sure task is valid
if (! ai)
return;
Con::executef(this, 2, "retire", avar("%d", ai->getId()));
}
//-------------------------------------------------------------------------------------
AISlicer::AISlicer()
{
reset();
}
void AISlicer::init(U32 delay, S32 maxPerTic)
{
// mRand.setSeed(0x88);
mDelay = delay;
mLastTime = Sim::getCurrentTime();
// mCapPileUp = (getMax(maxPerTic - 1, 0) * mDelay);
maxPerTic;
mDebugTotal = 0;
mBudget = 2.0;
// mEnabled = true;
}
void AISlicer::reset()
{
init();
// mEnabled = false;
}
#if 0
// Return true if this counter goes to zero AND sufficient time has elapsed. If
// the time hasn't elapsed - leave the counter. This should ensure that every
// body gets their shot without super-high frequency guys shutting out slow guys.
bool AISlicer::ready(S32& counter, S32 resetValue)
{
U32 T = Sim::getCurrentTime();
if (T - mLastTime >= mDelay && --counter <= 0)
{
// 1/4 of time, add stagger noise to those that won't really notice it
if (resetValue > 3)
resetValue += !(mRand.randI() & 3);
counter = resetValue;
// We want to allow multiple times per tic, but also to cap how much "reserve"
// readiness is allowed to pile up.
mLastTime = getMax(mLastTime + mDelay, T - mCapPileUp);
// Verify it's handling more than one per tick Ok
mDebugTotal++;
return true;
}
return false;
}
#else
// The above is very mis-conceived, let's try something a little better-
// Note mDelay is a millisecond amount, while counter & reset value are game ticks.
bool AISlicer::ready(S32& counter, S32 resetValue)
{
bool goodToGo = false;
counter--;
if (mBudget > 0)
{
if (counter < 0)
{
mBudget -= 1.0;
counter = resetValue;
goodToGo = true;
}
}
else
{
// Budget keeps track of how many we currently are allowed to let through. We'd
// like to keep it to no more than one per mDelay milliseconds.
U32 T = Sim::getCurrentTime();
mBudget += (F32(T - mLastTime) / F32(mDelay));
mBudget = getMin(mBudget, 40.0f);
mLastTime = T;
// We have to let through tasks that have been waiting a while. Nothing will
// wait in vain for more than about 1.2 extra seconds-
if (counter < -37)
{
counter = resetValue;
goodToGo = true;
}
}
// Track total to make sure we're getting about the percentage that we want.
mDebugTotal += goodToGo;
return goodToGo;
}
#endif

55
ai/aiTask.h Normal file
View file

@ -0,0 +1,55 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AITASK_H_
#define _AITASK_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _AICONNECTION_H_
#include "ai/aiConnection.h"
#endif
#ifndef _PLAYER_H_
#include "Test/player.h"
#endif
class AITask : public SimObject
{
typedef SimObject Parent;
S32 mWeightFreq;
S32 mWeightCounter;
S32 mMonitorFreq;
S32 mMonitorCounter;
S32 mWeight;
public:
DECLARE_CONOBJECT(AITask);
static void consoleInit();
AITask();
bool onAdd();
void setWeightFreq(S32 freq) { mWeightFreq = getMax(1, freq); }
void setWeight(S32 weight) { mWeight = weight; }
void reWeight() { mWeightCounter = 0; }
S32 getWeight() { return mWeight; }
void setMonitorFreq(S32 freq) { mMonitorFreq = getMax(1, freq); }
void reMonitor() { mMonitorCounter = 0; }
//methods to call the script functions
void calcWeight(AIConnection *ai);
void monitor(AIConnection *ai);
void assume(AIConnection *ai);
void retire(AIConnection *ai);
};
#endif

1528
ai/graph.cc Normal file

File diff suppressed because it is too large Load diff

341
ai/graph.h Normal file
View file

@ -0,0 +1,341 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPH_H_
#define _GRAPH_H_
#define _GRAPH_DEBUG_
#define _GRAPH_WARNINGS_ 0
#define _GRAPH_PART_ 1
#ifndef _GRAPHDATA_H_
#include "ai/graphData.h"
#endif
#ifndef _GRAPHBASE_H_
#include "ai/graphBase.h"
#endif
#ifndef _GRAPHPARTITION_H_
#include "ai/graphPartition.h"
#endif
#ifndef _GRAPHNODES_H_
#include "ai/graphNodes.h"
#endif
#ifndef _GRAPHTRANSIENT_H_
#include "ai/graphTransient.h"
#endif
#ifndef _GRAPHLOCATE_H_
#include "ai/graphLocate.h"
#endif
#ifndef _GRAPHTHREATS_H_
#include "ai/graphThreats.h"
#endif
#ifndef _GRAPHFORCEFIELD_H_
#include "ai/graphForceField.h"
#endif
#ifndef _GRAPHJETTING_H_
#include "ai/graphJetting.h"
#endif
#ifndef _GRAPHSEARCHES_H_
#include "ai/graphSearches.h"
#endif
#ifndef _GRAPHPATH_H_
#include "ai/graphPath.h"
#endif
#ifndef _GRAPHGROUNDPLAN_H_
#include "ai/graphGroundPlan.h"
#endif
#ifndef _TEXTUREPRELOAD_H_
#include "ai/texturePreload.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
class NavigationGraph : public SimObject
{
typedef SimObject Parent;
static S32 sVersion;
friend class NavigationPath;
public:
static bool sDrawOutdoorNodes, sDrawIndoorNodes, sDrawJetConnections;
static S32 sEdgeRenderMaxOutdoor, sEdgeRenderMaxIndoor;
static S32 sProfCtrl0, sProfCtrl1;
static S32 sTotalEdgeCount;
static F32 sProcessPercent;
static U32 sLoadMemUsed;
static S32 sShowThreatened;
static bool sSeedDropOffs;
static void consoleInit();
static void initPersistFields();
static bool gotOneWeCanUse();
static bool hasSpawnLocs();
static void warning(const char* msg);
static F32 fastDistance(const Point3F& p1, const Point3F& p2);
static Point3F hideOnSlope(const Point3F& from, const Point3F& avoid,
F32 rad, F32 deg);
static Point3F hideOnDistance(const Point3F& from, const Point3F& avoid,
F32 rad, F32 hideLen = 10.0);
static Point3F findLOSLocation(const Point3F& from, const Point3F& wantToSee,
F32 minAway, const SphereF& getCloseTo, F32 capDist=1e11);
static S32 getChokePoints(const Point3F& srcPoint, Vector<Point3F>& points,
F32 minHideDist, F32 stopSearchDist=1e9);
static F32 whereToLook(Point3F P);
protected:
// .NAV or .SPN file data:
SpawnLocations mSpawnList;
EdgeInfoList mEdgeInfoList;
NodeInfoList mNodeInfoList;
GraphVolumeList mNodeVolumes;
TerrainGraphInfo mTerrainInfo;
BridgeDataList mBridgeList;
PathXRefTable mPathXRef;
ChuteHints mChutes;
LOSHashTable mLOSHashTable;
// Run time node / edge lists:
GraphNodeList mNodeList;
GraphNodeList mNodeGrid;
GraphNodeList mNonTransient;
GraphNodeList mIndoorPtrs;
IndoorNodeList mIndoorNodes;
OutdoorNode * mOutdoorNodes;
GraphNodeList mIslandPtrs;
Vector<S32> mIslandSizes;
GraphBSPTree mIndoorTree;
GraphEdge * mEdgePool;
LOSXRefTable mLOSXRef;
LOSTable * mLOSTable;
S32 mVersion;
S32 mLargestIsland;
S32 mNumOutdoor;
S32 mNumIndoor;
S32 mTransientStart;
static S32 mIncarnation;
S32 mCheckNode;
const S32 mMaxTransients;
bool mValidLOSTable;
bool mValidPathTable;
bool mHaveVolumes;
bool mPushedBridges;
bool mIsSpawnGraph;
bool mDeadlyLiquid;
F32 mSubmergedScale;
F32 mShoreLineScale;
GraphSearch * mTableBuilder;
GraphSearch * mMainSearcher;
GraphSearchLOS * mLOSSearcher;
GraphSearchDist * mDistSearcher;
GraphBoundaries mBoundaries;
SimObjectPtr<TerrainBlock> mTerrainBlock;
Vector<LineSegment> mRenderThese;
Vector<Point3F> mRenderBoxes;
GraphEdge mEdgeBuffer[MaxOnDemandEdges];
GraphEdgePtrs mVisibleEdges;
SearchThreats mThreats;
MonitorForceFields mForceFields;
JetManager mJetManager;
// Temp buffers returned (as const T &) by misc fetch methods-
Vector<S32> mTempNodeBuf;
GraphNodeList mUtilityNodeList1;
GraphNodeList mUtilityNodeList2;
GraphVolume mTempVolumeBuf;
// Inspect persist variables.
F32 mCullDensity;
ConjoinConfig mConjoin;
GridArea mCustomArea;
public:
FindGraphNode mFoundNodes[FindGraphNode::HashTableSize];
protected:
bool onAdd();
void onRemove();
S32 markIslands();
S32 doFinalFixups();
void clearLoadData();
void purgeForSpawn();
bool initInteriorNodes(const EdgeInfoList&, const NodeInfoList&, S32);
S32 getNodesInArea(GraphNodeList& listOut, GridArea gridArea);
void makeRunTimeNodes(bool useEdgePool = false);
S32 setupOutdoorNodes(const GridArea& area,const Consolidated& cons,
GraphNodeList& grid,GraphNodeList& list);
F32 distancef(const Point3F& p1, const Point3F& p2);
void chokePoints(const Point3F& S, Vector<Point3F>& pts, F32 H, F32 M);
S32 crossingSegs(const GraphNode*, const GraphEdge*, LineSegment* const);
void getInSphere(const SphereF&, GraphNodeList& I, GraphNodeList& O);
void getOutdoorList(GraphNodeList& list);
void newIncarnation();
S32 hookTransient(TransientNode&);
void unhookTransient(TransientNode&);
S32 floodPartition (U32 seed, bool needBoth, F32 jetRating);
S32 partitionOneArmor(PartitionList& list, F32 jetRating);
public:
NavigationGraph();
~NavigationGraph();
DECLARE_CONOBJECT(NavigationGraph);
F32 performTests(S32 numTests, bool aStar = 0);
bool testPlayerCanReach(Point3F A, Point3F B, const F32* ratings);
F32 lineAcrossTerrain(const Point3F& from, Point3F& to);
bool inNodeVolume(const GraphNode* node, const Point3F& point);
NodeProximity getContainment(S32 indoorNodeIndex, const Point3F& point);
bool verticallyInside(S32 indoorIndex, const Point3F& point);
bool closestPointOnVol(GraphNode*, const Point3F& pt, Point3F& soln) const;
bool possibleToJet(const Point3F& from, const Point3F& to, U32 armor=0);
F32 heightAboveFloor(S32 indoorIndex, const Point3F& point);
S32 indoorIndex(const GraphNode* node);
PlaneF getFloorPlane(GraphNode* node);
bool useVolumeTraverse(const GraphNode* from, const GraphEdge* to);
bool volumeTraverse(const GraphNode* from, const GraphEdge* to, NavigationPath::State&);
S32 checkIndoorSkip(NavigationPath::State & state);
bool volumeTraverse(const GraphNode*, const GraphEdge*, const Point3F&, U8&, Point3F&);
bool volumeTraverse(const GraphNode*, const GraphEdge*, const NavigationPath::State&);
S32 checkIndoorSkip(const GraphEdgePtrs& edges, const Point3F& cur, S32 from, Vector<U8>& visit, Point3F& seek);
bool installThreat(ShapeBase * threat, S32 team, F32 rad);
bool updateThreat(ShapeBase * threat, S32 team, F32 rad);
void patrolForProblems();
// Three types of search machines get used-
GraphSearch* getMainSearcher();
GraphSearchLOS* getLOSSearcher();
GraphSearchDist* getDistSearcher();
// Transient management
void destroyPairOfHooks(S32 idx);
S32 createPairOfHooks();
bool pushTransientPair(TransientNode& src, TransientNode& dst,
U32 team, const JetManager::ID& jetCaps);
bool canReachLoc(const FindGraphNode& src, const FindGraphNode& dst,
U32 team, const JetManager::ID& jetCaps);
void popTransientPair(TransientNode& src, TransientNode& dst);
// Graph construction methods called from console, console queries, ...
bool load(Stream&, bool isSpawn = false);
bool save(Stream&);
bool loadGraph();
bool saveGraph();
bool assemble();
bool makeGraph(bool usePool=false);
S32 makeTables();
const char * findBridges();
const char * pushBridges();
S32 scatterSeeds();
void installSeed(const Point3F& at);
S32 compactIndoorData(const Vector<S32>& itrList, S32 numOutdoor);
void setGenMagnify(const SphereF*,const Point3F*,const Point3F*,F32 xy=1,F32 z=2);
bool prepLOSTableWork(Point3F viewLoc);
bool makeLOSTableEntries();
S32 cullIslands();
bool setGround(GroundPlan * gp);
S32 randNode(const Point3F& P, F32 R, bool in, bool out);
void detectForceFields() {mForceFields.atMissionStart();}
void checkHashTable(S32 nodeCount) const;
bool sanctionSpawnLoc(GraphNode * node, const Point3F& pt) const;
const Point3F* getRandSpawnLoc(S32 nodeIndex);
const Point3F* getSpawnLoc(S32 nodeIndex);
U32 makeLOSHashTable();
void makeSpawnList();
U32 reckonMemory() const;
// Queries for nodes and edges: for hand edited; and for algorithmic
S32 setNodesAndEdges(const EdgeInfoList&, const NodeInfoList&);
S32 getNodesAndEdges(EdgeInfoList& edges, NodeInfoList& nodes);
S32 setEdgesAndNodes(const EdgeInfoList&, const NodeInfoList&);
S32 setNodeVolumes(const GraphVolumeList& volumes);
S32 setAlgorithmic(const EdgeInfoList& E, const NodeInfoList& N);
S32 setChuteHints(const Vector<Point3F>& chutes);
S32 clearAlgorithmic();
S32 findJumpableNodes();
void expandShoreline(U32 wave = 0);
// Node and edge finding -
const GraphEdgePtrs& getBlockedEdges(GameBase* byThis, U32 objTypeMask);
const GraphNodeList& getVisibleNodes(GraphNode * S, const Point3F& loc, F32 rad);
const GraphNodeList& getVisibleNodes(const Point3F& loc, F32 rad);
const GraphVolume& fetchVolume(const GraphNode* N, bool above);
S32 getNodesInBox(Box3F worldBox, GraphNodeList& listOut, bool justIndoor=false);
S32 crossNearbyVolumes(const Point3F& from, Vector<Point3F>& points);
GraphNode * terrainHookNode(const Point3F& pos, S32& index);
GraphNode * findTerrainNode(const Point3F& loc);
GraphNode * nearbyTerrainNode(const Point3F& loc);
GraphNode * closestNode(const Point3F& loc, F32 * containment = NULL);
bool haveMuzzleLOS(S32 nodeInd1, S32 nodeInd2);
bool terrainHeight(Point3F pos, F32* height, Point3F* normal=0);
F32 getRoamRadius(const Point3F &loc);
void worldToGrid(const Point3F& wPos, Point2I& gPos);
Point3F gridToWorld(const Point2I& gPos);
GridArea getGridRectangle(const Point3F& atPos, S32 gridRad);
bool customArea(GridArea& fetch) const;
GridArea getWorldRect();
// render methods
void drawNodeInfo(GraphNode * node);
const char* drawNodeInfo(const Point3F& loc);
void render(Point3F &camPos, bool drawClipped);
void drawNeighbors(GraphNode*, GraphEdgeArray, const Point3F&, F32 w=1e9);
void markNodesInSight(const Point3F& from, F32 in, F32 out, U32 cond);
void clearRenderBoxes() {mRenderBoxes.clear();}
void clearRenderSegs() {mRenderThese.clear();}
void getCrossingPoints(S32 from, Point3F* pts, GraphEdge* to);
void setRenderList(const Vector<LineSegment>& segs) {mRenderThese=segs;}
void renderInteriorNodes(Point3F camPos);
void pushRenderSeg(const LineSegment& ls);
void pushRenderBox(Point3F boxLoc, F32 zadd=1.5);
void renderTransientNodes();
GraphThreatSet showWhichThreats() const;
// One-liners
S32 incarnation() const {return mIncarnation;}
GraphNode* lookupNode(S32 X) const {return mNodeList[X];}
S32 numIslands() const {return mIslandPtrs.size();}
S32 numOutdoor() const {return mNumOutdoor;}
S32 numNodesAll() const {return mNodeList.size();}
S32 numNodes() const {return mNonTransient.size();}
bool validLOSXref() const {return mValidLOSTable;}
bool validPathXRef() const {return mValidPathTable;}
bool haveVolumes() const {return mHaveVolumes;}
bool haveForceFields() const {return (mForceFields.count() != 0);}
F32 submergedScale() const {return mSubmergedScale;}
F32 shoreLineScale() const {return mShoreLineScale;}
bool deadlyLiquids() const {return mDeadlyLiquid;}
bool haveTerrain() const {return bool(mTerrainBlock);}
S32 numBridges() const {return mBridgeList.numPositiveBridges();}
S32 largestIsland() const {return mLargestIsland;}
S32 numSpawns() const {return mSpawnList.size();}
Point3F getSpawn(S32 i) const {return mSpawnList[i];}
void printSpawnInfo() const {mSpawnList.printInfo();}
void setGenMode(bool spawn) {mIsSpawnGraph = spawn;}
void monitorForceFields() {mForceFields.monitor();}
Vector<S32>& tempNodeBuff() {return mTempNodeBuf;}
const LOSTable* getLOSXref() const {return mValidLOSTable ? mLOSTable : NULL;}
const GraphBoundary& getBoundary(S32 i) {return mBoundaries[i];}
GraphThreatSet getThreatSet(S32 T) const {return mThreats.getThreatSet(T);}
void newPartition(GraphSearch* S, U32 T) {mForceFields.informSearchFailed(S, T);}
SearchThreats* threats() {return &mThreats;}
JetManager& jetManager() {return mJetManager;}
ChuteHints& getChutes() {return mChutes;}
// We manage the data that is shared (for memory optimization) among searchers
GraphSearch::Globals mSharedSearchLists;
// Temporary - store on graph until we have a special object for it-
PreloadTextures mTextures;
};
extern NavigationGraph * gNavGraph;
#endif

220
ai/graphBSP.h Normal file
View file

@ -0,0 +1,220 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHBSP_H_
#define _GRAPHBSP_H_
template <class T> class AxisAlignedBSP
{
public:
enum {Lower, Upper};
class BSPNode {
public:
U16 mAxis;
U16 mCount;
union {
U16 mBranch[2];
T * mLeaf;
} X;
F32 mDivide;
BSPNode()
{
mAxis = 0;
mCount = 0;
mDivide = 0.0f;
X.mBranch[Lower] = X.mBranch[Upper] = 0;
X.mLeaf = NULL;
}
};
typedef Vector<BSPNode> BSPPool;
protected:
static S32 sSortAxis;
BSPPool mTree;
VectorPtr<T*> mList;
protected:
static S32 QSORT_CALLBACK compareAxis(const void* , const void* );
void sortOnAxis(VectorPtr<T*>& list, S32 axis);
void traverse(S32 ind, const Box3F& box, VectorPtr<T*>* result) const;
U16 partition(S32 start, S32 N);
public:
void makeTree(const VectorPtr<T*>& listIn);
S32 getIntersecting(VectorPtr<T*>& listOut, Box3F box) const;
void clear();
};
//-------------------------------------------------------------------------------------
template <class T> S32 AxisAlignedBSP<T>::sSortAxis;
template <class T>
S32 QSORT_CALLBACK AxisAlignedBSP<T>::compareAxis(const void* a,const void* b)
{
const F32 * locA = (*(T**)a)->location();
const F32 * locB = (*(T**)b)->location();
F32 A = locA[sSortAxis], B = locB[sSortAxis];
return (A < B ? -1 : (A > B ? 1 : 0));
}
template <class T> void AxisAlignedBSP<T>::sortOnAxis(VectorPtr<T*>& list, S32 axis)
{
sSortAxis = axis;
dQsort((void* )(list.address()), list.size(), sizeof(T* ), compareAxis);
}
//-------------------------------------------------------------------------------------
// Main tree building routine. Divides up the space at each level until done.
template <class T> U16 AxisAlignedBSP<T>::partition(S32 start, S32 N)
{
S32 place = mTree.size();
BSPNode assembleNode;
if (N < 2)
{
AssertFatal(N == 1, "Bad call to axis-aligned BSP partition");
assembleNode.mCount = 1;
assembleNode.X.mLeaf = mList[start];
mTree.push_back(assembleNode);
}
else
{
S32 mid1 = (N >> 1);
S32 mid0 = mid1 - 1;
F32 bestSeparation = -1;
VectorPtr<T*> divisions[3];
// Try all three divisions. We take the one whose halves are furthest apart
// at the divide.
for (S32 axis = 0; axis < 3; axis++)
{
divisions[axis].setSize(N);
dMemcpy(&(divisions[axis][0]), &mList[start], N * sizeof(T*));
sortOnAxis(divisions[axis], axis);
const F32 * endOfFirst = divisions[axis][mid0]->location();
const F32 * startOf2nd = divisions[axis][mid1]->location();
F32 low = endOfFirst[axis];
F32 high = startOf2nd[axis];
F32 separation = (high - low);
AssertFatal(separation >= 0, "Bad sort in axis-aligned BSP construction");
if (separation > bestSeparation)
{
bestSeparation = separation;
assembleNode.mCount = N;
assembleNode.mAxis = axis;
assembleNode.mDivide = low + (separation * 0.5f);
}
}
// Copy over the sorted elements along the axis we're dividing-
dMemcpy(&mList[start], &(divisions[assembleNode.mAxis][0]), N * sizeof(T*));
// Install the node proper, and call down to further partition.
mTree.push_back(assembleNode);
// NOTE!! This code doesn't work in release build if we haven't pre-reserved the
// !!!!!! tree. It looks like a compiler bug to me, not 100% sure though.
mTree[place].X.mBranch[Lower] = partition(start, mid1);
mTree[place].X.mBranch[Upper] = partition(start + mid1, N - mid1);
}
// Return where we're at in the tree.
return place;
}
//-------------------------------------------------------------------------------------
// Make a tree whose leaves are the nodes passed in.
template <class T> void AxisAlignedBSP<T>::makeTree(const VectorPtr<T*>& listIn)
{
mList = listIn;
mTree.clear();
AssertFatal(mList.size() < 32000, "AxisAlignedBSP::makeTree() - too many nodes");
if (mList.size()) {
mTree.reserve(mList.size() * 3);
partition(0, mList.size());
mTree.compact();
}
mList.clear();
mList.compact();
}
//-------------------------------------------------------------------------------------
// We want our check to be inclusive of both ends (Box3F method isn't above)
inline bool boxContainsPt(const Box3F& B, const Point3F& P)
{
if (P.x >= B.min.x && P.x <= B.max.x)
if (P.y >= B.min.y && P.y <= B.max.y)
return (P.z >= B.min.z && P.z <= B.max.z);
return false;
}
//-------------------------------------------------------------------------------------
template <class T> void AxisAlignedBSP<T>::traverse(S32 ind, const Box3F& box,
VectorPtr<T*>* result) const
{
const BSPNode & node = mTree[ind];
if (node.mCount == 1) { // At node-
if (boxContainsPt(box, node.X.mLeaf->location()))
result->push_back(node.X.mLeaf);
}
else {
AssertFatal(node.mCount > 1, "AxisAlignedBSP::traverse()");
if (node.mDivide > ((const F32*)(box.max))[node.mAxis])
traverse(node.X.mBranch[Lower], box, result);
else if (node.mDivide < ((const F32*)(box.min))[node.mAxis])
traverse(node.X.mBranch[Upper], box, result);
else {
Box3F splitUpper(box);
Box3F splitLower(box);
((F32*)(splitUpper.min))[node.mAxis] = node.mDivide;
((F32*)(splitLower.max))[node.mAxis] = node.mDivide;
traverse(node.X.mBranch[Lower], splitLower, result);
traverse(node.X.mBranch[Upper], splitUpper, result);
}
}
}
//-------------------------------------------------------------------------------------
// Push all nodes intersecting the box onto the user-supplied list. We don't clear
// list because we'll probably have two trees - for small nodes and large nodes.
template <class T>
S32 AxisAlignedBSP<T>::getIntersecting(VectorPtr<T*>& result, Box3F box) const
{
if (mTree.size())
traverse(0, box, &result);
return result.size();
}
//-------------------------------------------------------------------------------------
template <class T> void AxisAlignedBSP<T>::clear()
{
mTree.clear(); mTree.compact();
mList.clear(); mList.compact();
}
//-------------------------------------------------------------------------------------
#endif

388
ai/graphBase.cc Normal file
View file

@ -0,0 +1,388 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
// GraphEdge
GraphEdge::GraphEdge()
{
mDest = -1; // Destination node index
mBorder = -1; // Border segment on interiors
mDist = 1.0; // 3D dist, though jet edges are adjusted a little so ratings work
mJet = 0; // Jetting connection
mHopOver = 0; // For jetting, amount to hop up and over
// Factors that influence edge scaling-
//
mTeam = 0; // An edge that only this team can pass - i.e. force fields.
mJump = 0; // On jetting edges - is it flat enough to jump?
mLateral = 0; // Crude lateral distance - needed to know if we can jet down.
mDown = 0; // Down side of a jet?
mSteep = 0; // Can't walk up
setInverse(1.0);
}
const char * GraphEdge::problems() const
{
if (mDist < 0)
return "Dijkstra() forbids edge with negative distance";
if (getInverse() < 0)
return "Edge with negative scale";
return NULL;
}
// Given the jet rating, can this connection be traversed? If down, must satisfy
// lateral only. Used by searcher, and by partition builder. In the latter case it
// must (on 1st of its 2 passes) treat down same as up to find stranded partitions.
bool GraphEdge::canJet(const F32* ratings, bool both) const
{
if (!mDown || both)
{
F32 rating = ratings[mJump];
return (rating >= mDist);
}
else
{
F32 rating = ratings[mJump];
return rating > F32(mLateral);
}
}
// This table gives our inverse speed values, so we can keep it in a few bits. Should
// still give the desired behavior, though a little coarsely at times. Perfect paths
// in terms of travel times is not something that people seem to percieve that much...
const F32 GraphEdge::csInvSpdTab[InvTabSz + 1] =
{
0.0, 0.1, 0.2, 0.3, 0.4,
0.6, 0.8, 1.0, 1.2, 1.4,
1.7, 2.0, 2.6, 3.5, 4.6,
5.7, 6.8, 8.0, 9.2, 10.4,
12.0, 14.1, 17.0, 20.0, 24.0,
30.0, 37.0, 45.0, 54.0, 64.0,
80.0, 100.0, 125.0, 156.0, 200.0,
400.0, 800.0, 1600.0, 3200.0, 6400.0,
2.56e4, 1.02e5, 4.1e5, 1.6e6, 6.5e6,
2.6e7, 1.05e8, 4.19e8, 1.68e9, 9.9e10,
1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20,
1e21, 1e22, 1e23, 1e24,
// Sentinal value -
1e40,
};
// Find the 6-bit number using a binary search...
void GraphEdge::setInverse(F32 is)
{
S32 lo = 0;
S32 hi = InvTabSz;
AssertFatal(is < csInvSpdTab[hi] && is > csInvSpdTab[lo], "Bad edge inverse speed");
// Find the largest value we're greater than in the table-
for (S32 i = InvSpdBits; i > 0; i--)
{
S32 mid = (hi + lo >> 1);
if (is >= csInvSpdTab[mid])
lo = mid;
else
hi = mid;
}
mInverse = lo;
}
//-------------------------------------------------------------------------------------
// GraphNode Methods, Default Virtuals
GraphNode::GraphNode()
{
mIsland = -1;
mOnPath = 0;
mAvoidUntil = 0;
mLoc.set(-1,-1,-1);
// mOwnedPtr = NULL;
// mOwnedCnt = 0;
mThreats = 0;
}
// Derived classes must supply fetcher which takes a buffer to build in. This method
// patches to that, and should serve fine if pointers to these aren't kept around.
const Point3F& GraphNode::location() const
{
static Point3F buff[8];
static U8 ind = 0;
return fetchLoc(buff[ind++ & 7]);
}
// Outdoor nodes use level (and they start at -1), others don't. Though this is also
// used by path randomization to determine if it's reasonable to avoid the node.
S32 GraphNode::getLevel() const
{
return -2;
}
Point3F GraphNode::getRenderPos() const
{
Point3F buff, adjusted;
adjusted = fetchLoc(buff);
adjusted.z += 0.3;
return adjusted;
}
// Set if not already set (shouldn't be set already, caller asserts)
void GraphNode::setIsland(S32 num)
{
AssertFatal(!mFlags.test(GotIsland), "GraphNode::setIsland()");
mFlags.set(GotIsland);
mIsland = num;
}
// Ideally this ratio would accurately reflect travel speeds along the slopes.
// Here we use
// xy / total If we're going down. Hence XY is net path distance.
// total / xy If we're going up.
static F32 getBaseEdgeScale(const Point3F& src, Point3F dst)
{
F32 totalDist = (dst -= src).len();
bool goingUp = (dst.z > 0);
dst.z = 0;
F32 xyDist = dst.len();
F32 ratio = 1.0;
if (xyDist > 0.01 && totalDist > 0.01)
{
if (goingUp)
{
ratio = (totalDist / xyDist);
if (ratio < 1.2)
ratio = 1.0;
}
else
ratio = (xyDist / totalDist);
// Want to do something between square root and straight value, so we'll
// raise it to the 3/4 power here, which will bring it to 1 from either side.
ratio = mSqrt(mSqrt(ratio) * ratio);
}
return ratio;
}
F32 GraphNode::edgeScale(const GraphNode * dest) const
{
// F32 scale = 1.0;
F32 scale = getBaseEdgeScale(location(), dest->location());
if (submerged())
scale *= gNavGraph->submergedScale();
else if (shoreline())
scale *= gNavGraph->shoreLineScale();
if (dest->submerged())
scale *= gNavGraph->submergedScale();
else if (dest->shoreline())
scale *= gNavGraph->shoreLineScale();
// Searcher reserves REALLY large numbers (10^20th about) to signal search
// failure, so make sure we don't inadvertently interfere with any of that.
scale = getMin(scale, 1000000000.0f);
return scale;
}
static GraphEdge sEdgeBuff[MaxOnDemandEdges];
bool GraphNode::neighbors(const GraphNode* other) const
{
GraphEdgeArray edgeList = other->getEdges(sEdgeBuff);
S32 indexOfThis = getIndex();
while (GraphEdge * edge = edgeList++)
if (edge->mDest == indexOfThis)
return true;
return false;
}
GraphEdge * GraphNode::getEdgeTo(S32 to) const
{
GraphEdgeArray edgeList = getEdges(sEdgeBuff);
while (GraphEdge * edge = edgeList++)
if (edge->mDest == to)
return edge;
return NULL;
}
// Virtual that is only used on non-grid nodes
const GraphEdge * GraphNode::getEdgePtr() const
{
return 0;
}
// This is only called on nodes known to be terrain.
F32 GraphNode::terrHeight() const
{
return 1e17;
}
// Initial use of this is for weighted random selection of nodes for spawn points.
F32 GraphNode::radius() const
{
return 2.0;
}
// Not in yet, but we'll use this instead of radius for weighted random spawn points
F32 GraphNode::area() const
{
return 0.01;
}
// Used to know thinness of node. Spawn system avoids, as do smoothing edges.
F32 GraphNode::minDim() const
{
return 0.01;
}
const Point3F& GraphNode::getNormal() const
{
static const Point3F sStraightUp(0,0,1);
return sStraightUp;
}
// Used for finding how much a node contains a point. More negative => point is
// more inside.
NodeProximity GraphNode::containment(const Point3F& point) const
{
NodeProximity nodeProx;
nodeProx.mLateral = (point - location()).len() - radius();
return nodeProx;
}
Point3F GraphNode::randomLoc() const
{
return location();
}
// Nodes which have volumes associated. Added to allow the transient nodes to
// respond with the proper volume that they may be associated with.
S32 GraphNode::volumeIndex() const
{
return getIndex();
}
// Set the avoidance if it isn't already. The inmportant flag will override though.
void GraphNode::setAvoid(U32 duration, bool isImportant)
{
U32 curTime = Sim::getCurrentTime();
if (curTime > mAvoidUntil || isImportant)
{
mAvoidUntil = curTime + duration;
mFlags.set(StuckAvoid, isImportant);
// On avoidance, also mark neighbors-
if (isImportant)
{
GraphEdgeArray edgeList = getEdges(sEdgeBuff);
while (GraphEdge * edge = edgeList++)
{
GraphNode * node = gNavGraph->lookupNode(edge->mDest);
node->mAvoidUntil = mAvoidUntil;
node->mFlags.clear(StuckAvoid); // slight avoid here.
}
}
}
}
//-------------------------------------------------------------------------------------
// Default RegularNode Virtuals
RegularNode::RegularNode()
{
mIndex = -1;
mNormal.set(0,0,1);
}
GraphEdgeArray RegularNode::getEdges(GraphEdge*) const
{
return GraphEdgeArray(mEdges.size(), mEdges.address());
}
GraphEdge & RegularNode::pushEdge(GraphNode * node)
{
GraphEdge edge;
edge.mDest = node->getIndex();
mEdges.push_back(edge);
return mEdges.last();
}
const Point3F& RegularNode::getNormal() const
{
return mNormal;
}
//-------------------------------------------------------------------------------------
void GraphNodeList::setFlags(U32 bits)
{
for(const_iterator it = begin(); it != end(); it++)
if(*it)
(*it)->mFlags.set(bits);
}
void GraphNodeList::clearFlags(U32 bits)
{
for(const_iterator it = begin(); it != end(); it++)
if(*it)
(*it)->mFlags.clear(bits);
}
S32 GraphNodeList::searchSphere(const SphereF& sphere, const GraphNodeList& listIn)
{
clear();
F32 radiusSquared = (sphere.radius * sphere.radius);
for(const_iterator it = listIn.begin(); it != listIn.end(); it++)
if(*it)
if((sphere.center - (*it)->location()).lenSquared() < radiusSquared)
push_back(*it);
return size();
}
GraphNode * GraphNodeList::closest(const Point3F& loc, bool)
{
GraphNode * bestNode = NULL;
F32 bestRadSquared = 1e12, dSqrd;
for (const_iterator it = begin(); it != end(); it++)
if (*it)
if ((dSqrd = (loc - (*it)->location()).lenSquared()) < bestRadSquared)
{
bestRadSquared = dSqrd;
bestNode = *it;
}
return bestNode;
}
// Add the node pointer if it's not already in the list.
bool GraphNodeList::addUnique(GraphNode * node)
{
for (iterator n = begin(); n != end(); n++)
if (* n == node)
return false;
push_back(node);
return true;
}

228
ai/graphBase.h Normal file
View file

@ -0,0 +1,228 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHBASE_H_
#define _GRAPHBASE_H_
#ifndef _OVECTOR_H_
#include "ai/oVector.h"
#endif
class GraphNodeList;
class NavigationGraph;
typedef S32 GraphQIndex;
typedef U64 GraphThreatSet;
//-------------------------------------------------------------------------------------
// Graph Edges - See graphNodeBase.cc for methods, comments.
class GraphEdge
{
enum {InvSpdBits = 6, InvTabSz = 1 << InvSpdBits};
static const F32 csInvSpdTab[InvTabSz + 1];
U8 mSteep : 1;
U8 mInverse : InvSpdBits;
U8 mOnPath : 1;
U8 mJet : 1; // These are only private since they're not
U8 mDown : 1; // straight regular types. Otherwise this
U8 mJump : 1; // class is kept pretty open for the sake of
U8 mTeam : 5; // speed in the debug build (since edges are
U8 mLateral; // used a lot inlines slow things down
U8 mHopOver; // by a noticeable amount).
public:
F32 mDist;
S16 mDest;
S16 mBorder;
GraphEdge();
bool empty() const {return mDest < 0;}
bool isBorder() const {return mBorder >= 0;}
bool isJetting() const {return mJet;}
bool isDown() const {return mDown;}
bool isJump() const {return mJump;}
bool isSteep() const {return mSteep;}
U8 getTeam() const {return mTeam;}
U8 getLateral() const {return mLateral;}
F32 getHop() const {return mapU8ToJetHop(mHopOver);}
F32 getInverse() const {return csInvSpdTab[mInverse];}
bool hasHop() const {return mHopOver!=0;}
void setJetting() {mJet = 1;}
void setDown(bool b) {mDown = b;}
void setJump(bool b) {mJump = b;}
void setTeam(U8 team) {mTeam = team;}
void setLateral(U8 amt) {mLateral = amt;}
void setSteep(bool b) {mSteep = b;}
void setImpossible() {setInverse(1e37);}
void setHop(F32 amt) {mHopOver = mapJetHopToU8(amt);}
void setHop(U8 persistAmt) {mHopOver = persistAmt;}
F32 getTime() const {return (mDist * getInverse());}
void copyInverse(const GraphEdge* e) {mInverse = e->mInverse;}
bool canJet(const F32* ratings, bool both=false) const;
void setInverse(F32 inv);
const char* problems() const;
};
typedef OVector<GraphEdge> GraphEdgeList;
typedef Vector<GraphEdge*> GraphEdgePtrs;
class GraphEdgeArray
{
S32 count;
GraphEdge * edges;
public:
GraphEdgeArray() {count = 0; edges = NULL;}
GraphEdgeArray(S32 c, GraphEdge* e) {count = c; edges = e;}
GraphEdge * operator++(int) {return (count ? (count--, edges++) : 0);}
S32 numEdges() const {return count;}
void incCount() {count++;}
};
struct NodeProximity
{
F32 mLateral, mHeight, mAboveC;
void makeBad() {mLateral=1e13;}
void makeGood() {mAboveC = mLateral = -1e13;}
operator F32&() {return mLateral;}
NodeProximity() {makeBad();}
bool inside() const {return (mLateral <= 0 && mAboveC <= 0);}
bool insideZ() const {return (mAboveC <= 0);}
bool possible() const;
};
//-------------------------------------------------------------------------------------
// Virtual Base Class For Run Time Graph Nodes
#define GraphMaxOnPath 31
class GraphNode // graphNodeBase.cc
{
friend class NavigationGraph;
friend class GraphNodeList;
protected:
enum{
NodeSpecific = 0xFF,
Grid = BIT(8),
Outdoor = BIT(9),
Transient = BIT(10),
ExtendedGrid = BIT(11),
Indoor = BIT(12),
BelowPortal = BIT(13),
GotIsland = BIT(14),
Transient0 = BIT(15),
Transient1 = (Transient0<<1),
HaveTransient = (Transient0|Transient1),
PotentialSeed = BIT(19),
UsefulSeed = BIT(20),
Flat = BIT(21),
Algorithmic = BIT(22),
StuckAvoid = BIT(23),
Inventory = BIT(24),
Render0 = BIT(25),
Shadowed = BIT(26),
// Reserve three contiguous bits for shoreline, which is wider for lava.
LiquidPos = 27,
LiquidZone = (0x7 << LiquidPos),
Submerged = BIT(LiquidPos + 0),
ShoreLine = BIT(LiquidPos + 1),
LavaBuffer = BIT(LiquidPos + 2),
};
BitSet32 mFlags;
S16 mIsland;
S16 mIndex;
S8 mOnPath;
S8 mLevel;
// U16 mOwnedCnt;
// GraphEdge * mOwnedPtr;
U32 mAvoidUntil;
GraphThreatSet mThreats;
GraphEdgeList mEdges;
Point3F mLoc;
protected:
GraphEdge * pushTransientEdge(S32 dest);
void popTransientEdge();
public:
GraphNode();
// One-liners
void set(U32 bits) {mFlags.set(bits);}
void clear(U32 bits) {mFlags.clear(bits);}
bool test(U32 bits) const {return mFlags.test(bits);}
bool grid() const {return test(Grid);}
bool outdoor() const {return test(Outdoor);}
bool indoor() const {return test(Indoor);}
bool belowPortal() const {return test(BelowPortal);}
bool shoreline() const {return test(ShoreLine);}
bool shadowed() const {return test(Shadowed);}
bool submerged() const {return test(Submerged);}
bool stuckAvoid() const {return test(StuckAvoid);}
bool transient() const {return test(Transient);}
bool gotIsland() const {return test(GotIsland);}
bool inventory() const {return test(Inventory);}
bool render0() const {return test(Render0);}
bool algorithmic() const {return test(Algorithmic);}
bool flat() const {return test(Flat);}
bool canHaveBorders() const {return test(Indoor|Transient);}
bool liquidZone() const {return test(LiquidZone);}
bool potentialSeed() const {return test(PotentialSeed);}
bool usefulSeed() const {return test(UsefulSeed);}
bool uselessSeed() const {return potentialSeed() && !usefulSeed();}
S32 island() const {return mIsland;}
U32 onPath() const {return mOnPath;}
U32 avoidUntil() const {return mAvoidUntil;}
void setOnPath() {mOnPath = GraphMaxOnPath;}
void decOnPath() {if(mOnPath) mOnPath--;}
void setUsefulSeed() {set(UsefulSeed);}
GraphThreatSet& threats() {return mThreats;}
// Pure virtuals.
virtual const Point3F& fetchLoc(Point3F& buff) const = 0;
virtual GraphEdgeArray getEdges(GraphEdge* buff) const = 0;
virtual S32 getIndex() const = 0;
// Virtuals-
virtual const Point3F& location() const;
virtual const Point3F& getNormal() const;
virtual const GraphEdge* getEdgePtr() const;
virtual S32 getLevel() const;
virtual S32 volumeIndex() const;
virtual F32 edgeScale(const GraphNode* to) const;
virtual NodeProximity containment(const Point3F& loc) const;
virtual Point3F getRenderPos() const;
virtual Point3F randomLoc() const;
virtual F32 terrHeight() const;
virtual F32 radius() const;
virtual F32 minDim() const;
virtual F32 area() const;
// Other:
void setIsland(S32 islandNum);
void setAvoid(U32 duration, bool important=false);
bool neighbors(const GraphNode*) const;
GraphEdge* getEdgeTo(S32 to) const;
GraphEdge* getEdgeTo(const GraphNode* n) const {return getEdgeTo(n->getIndex());}
};
//-------------------------------------------------------------------------------------
class GraphNodeList : public VectorPtr<GraphNode *>
{
public:
void setFlags(U32 bitset);
void clearFlags(U32 bitset);
S32 searchSphere(const SphereF& sphere, const GraphNodeList& listIn);
GraphNode* closest(const Point3F& loc, bool los=false);
bool addUnique(GraphNode * node);
};
typedef AxisAlignedBSP<GraphNode> GraphBSPTree;
#endif

974
ai/graphBridge.cc Normal file
View file

@ -0,0 +1,974 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "ai/graphLOS.h"
#include "ai/graphBridge.h"
//-------------------------------------------------------------------------------------
// Controls generation-
static bool sSpawnGraph = false;
// Numbers that are different between the two types of graphs. Most jet connections
// on NAV generation use the smaller lateral value, but we special case for large
// terrain-to-terrain hops that are passed through. cf. FireStorm.
#define NAVLateralMax 60.0
#define IslandHopLateralMax 120.0
#define SPNLateralMax 100.0
static const Point3F sBoxOffNAV(IslandHopLateralMax, IslandHopLateralMax, 10000.0);
static const Point3F sBoxOffSPN(SPNLateralMax, SPNLateralMax, 10000.0);
//-------------------------------------------------------------------------------------
static const U32 sLOSMask = InteriorObjectType
| StaticShapeObjectType
| StaticObjectType
| WaterObjectType
| TerrainObjectType;
static Point3F * sBreakPt1 = 0, * sBreakPt2 = 0;
static SphereF * sMagSphere;
static F32 sBreakRangeXY = 0.5, sBreakRangeZ = 2.0;
static const S32 sGraphFanCount = 10; // ==> 1-11-1, reduced, should be fine.
static const F32 sGraphFanWidth = 1.0;
static const F32 sWideFanRaise = 1.3;
// Clear distance assures they can get over a ledge, land Dist assures there's purchase.
// Second clear dist is for case when one node is a portal (i.e. chutes)
static const F32 sClearDists[2] = {1.3, 1.0};
static const F32 sLandDists[2] = {0.4, 0.2};
static const F32 sJumpAngDot = 0.707;
// NOT SURE ABOUT THIS # Sometimes we want more - maybe allow other checks to insure
// that things work when it's small (clear dist, etc.) There are some small ledge
// hops which are needed - but hop over code will probably assure...
static const F32 sMinXY = 2.0;
static const F32 sMinXYSqrd = (sMinXY * sMinXY);
static const Point3F sMinAboveVec(0,0,2.0);
#define SlightlyOffFloor 0.157
#define MaxHopOverXY 37.0
// lhpatch- Make this # go on graph (was barely too small in one map at 17)
#define MaxWalkableXY 20.0
#define MinLateralThresh 12.0
//-------------------------------------------------------------------------------------
#define SquareOf(x) ((x)*(x))
static bool bridgeable(const Point3F& from, const Point3F& to, bool bothOutdoor)
{
F32 lateralThresh;
if (sSpawnGraph)
{
lateralThresh = SPNLateralMax;
}
else
{
if (bothOutdoor)
lateralThresh = IslandHopLateralMax;
else
lateralThresh = NAVLateralMax;
// Pull down the lateral thresh for tall hops. Basically subtract off of it for
// every meter of Z, but keep a minimum amount, which is necessary to assure
// floating bases do get bridged (if only the downward hop is doable).
if ( (lateralThresh -= mFabs(from.z - to.z)) < MinLateralThresh )
lateralThresh = MinLateralThresh;
}
Point2F lateral(from.x - to.x, from.y - to.y);
return (lateral.lenSquared() < SquareOf(lateralThresh));
}
//-------------------------------------------------------------------------------------
// Get the fan vector in passed ref, and return number of increments. horzVec
// is a horizontal vector guaranteed to have length.
static S32 calcFanVector(const VectorF& horzVec, VectorF& fanIncOut)
{
fanIncOut.set(-horzVec.y, horzVec.x, 0);
fanIncOut.normalize(sGraphFanWidth / F32(sGraphFanCount));
return sGraphFanCount;
}
//-------------------------------------------------------------------------------------
// Some code for isolating cases in the debugger. Mainly for focusing on a couple of
// nodes to see why they're not getting bridged. See navGraph.genDebug() in graph.cc
static bool doBreak() {return true;}
static bool checkEnds(const Point3F& n1, const Point3F& n2)
{
if (mFabs(n1.x - sBreakPt1->x) < sBreakRangeXY)
if (mFabs(n2.x - sBreakPt2->x) < sBreakRangeXY)
if (mFabs(n2.y - sBreakPt2->y) < sBreakRangeXY)
if (mFabs(n1.y - sBreakPt1->y) < sBreakRangeXY)
if (mFabs(n1.z - sBreakPt1->z) < sBreakRangeZ)
if (mFabs(n2.z - sBreakPt2->z) < sBreakRangeZ)
return doBreak();
return false;
}
//-------------------------------------------------------------------------------------
GraphBridge::GraphBridge(const GraphNodeList& mainList, S32 islands,
BridgeDataList& listOut)
: mMainList(mainList),
mIslands(islands),
mBridgesOut(listOut)
{
heedSeeds(false);
}
//-------------------------------------------------------------------------------------
// This is a bit indirect... but here's where we control which brdiges to attempt on
// the two passes. On the first, all seeds are ignored. On the second we only bridge
// where at least one useful seed is present, and NO useless seed is involved.
bool GraphBridge::heedThese(const GraphNode* from, const GraphNode* to)
{
if (mHeedSeeds)
if (from->usefulSeed() || to->usefulSeed())
return !(from->uselessSeed() || to->uselessSeed());
else
return false;
else
return !(from->potentialSeed() || to->potentialSeed());
}
void GraphBridge::heedSeeds(bool b)
{
mHeedSeeds = b;
}
//-------------------------------------------------------------------------------------
// Try straight distance for the ratios. After we added good scale values on the
// edges, our bridger makes too many edges since the ratios are off. Until this is
// addressed (if indeed it really needs it), let's use straight distance on edges.
F32 GraphBridge::getEdgeTime(const GraphEdge * edge)
{
return edge->mDist;
}
//-------------------------------------------------------------------------------------
// Here's where we do all the work - try to see if a bridge exists.
//
bool GraphBridge::tryToBridge(const GraphNode* from, const GraphNode* to, F32 heuristic)
{
// This is actually where the separate passes (normal, then seeding) is controlled-
if (!heedThese(from, to))
return false;
// Check for in, or near, liquids.
if (from->liquidZone() || to->liquidZone())
return false;
S32 fromIndex = from->getIndex();
S32 toIndex = to->getIndex();
Point3F fromLoc = from->location();
Point3F toLoc = to->location();
bool oneIsPortal = (from->belowPortal() ^ to->belowPortal());
// Debug stopping points.
if (sBreakPt1 && sBreakPt2)
{
if (checkEnds(fromLoc, toLoc) || checkEnds(toLoc, fromLoc))
if (oneIsPortal)
doBreak();
}
// NOTE: we can make the OffFloor amount smaller if outdoor node locations are
// raised up to always be above terrain. (U16 rounding I think)
fromLoc.z += SlightlyOffFloor;
toLoc.z += SlightlyOffFloor;
bool bothOutside = (from->outdoor() && to->outdoor());
if (from->neighbors(to))
return false;
else
{
// Prefer to bridge going UP to insure that the ratio check gets handled (larger
// travel times going up).
if (fromLoc.z > toLoc.z)
return false;
else if (fromLoc.z == toLoc.z && fromIndex > toIndex)
return false;
}
// Create low, high, mid (point above low, but on level with high).
Point3F mid, low, high;
low = fromLoc;
high = toLoc;
(mid = low).z = high.z;
F32 zDist = (high.z - low.z);
// Get 2D dist
Point2F xyVec(toLoc.x - fromLoc.x, toLoc.y - fromLoc.y);
F32 xyDistSq = xyVec.lenSquared();
// Handles all the LOS work
Loser los(sLOSMask, true);
if ((xyDistSq > 0.0001) && bridgeable(fromLoc, toLoc, bothOutside))
{
Point3F aboveM, aboveH, clearPt, landRoom, across;
bool isChute = false, chuteTop = false;
bool walkable = false, gapWalk = false;
bool canCheckJet = true, direct = false;
bool makeBridge = false;
F32 wall;
// See if we shouldn't look for jetting.
F32 xyDist = mSqrt(xyDistSq);
if (from->inventory() || to->inventory() || xyDistSq < sMinXYSqrd)
canCheckJet = false;
else
{
if (from->getNormal().z < sJumpAngDot || to->getNormal().z < sJumpAngDot)
canCheckJet = false;
}
bool checkLandRoom = (zDist > 1.0 && to->indoor());
Point3F aboveVec = sMinAboveVec;
aboveM = mid + aboveVec;
aboveH = high + aboveVec;
across = (high - mid);
across.normalize();
clearPt = mid + (sClearDists[oneIsPortal] * across);
landRoom = high - (sLandDists[oneIsPortal] * across);
// Move back on the up checks so the guy has room behind him. We have to
// raise up the low a little bit to account for slope, but of course
// we can't raise more than height.
if (canCheckJet)
{
// There are two types of chute situations here, one for if we have this
// connection goes to the top of the chute, the other for middle ones.
// For the top we do a slope check off the collision up there and are lax
// about other LOS checks. For the middle we require a couple more.
if (ChuteHints::Info info = gNavGraph->getChutes().info(low, mid))
{
isChute = true;
if (info == ChuteHints::ChuteTop)
chuteTop = true;
}
// Might still want to do a little of this on chutes... ?
if (!isChute)
{
VectorF backUpVec = (across * 0.8);
low -= backUpVec;
low.z += getMin(2.0f, zDist);
mid -= backUpVec;
aboveM -= backUpVec;
}
}
bool walkOff = los.haveLOS(mid, high);
bool hopOver = false;
if (!walkOff && xyDist < MaxHopOverXY)
{
hopOver = los.findHopLine(mid, high, 2, wall);
if (hopOver)
{
mid.z += wall;
high.z += wall;
aboveM.z += wall;
aboveH.z += wall;
}
}
if (walkOff || hopOver)
if (los.haveLOS(high, aboveH))
if (chuteTop || los.haveLOS(low, aboveM))
if (chuteTop || los.haveLOS(aboveM, aboveH))
{
// Don't do walk checks between two outdoor nodes-
if (!isChute && (!from->outdoor() || !to->outdoor()))
{
// do gap walk for special case of small zdiff or direct LOS
direct = los.haveLOS(low, high);
if (direct)
gapWalk = los.walkOverGaps(low, high, 0.8);
else if (zDist < gNavGlobs.mStepHeight)
gapWalk = los.walkOverGaps(mid, high, 0.8);
// If there's a wall to hop over, don't do the walk code. Also, if both
// nodes are in the same island, we only consider walking if the
// heuristic (ratio of best-path-so-far to straight line dist) is high.
if (walkOff)
if ((from->island() != to->island()) || (heuristic > 3.7))
walkable = los.walkOverBumps(aboveM, aboveH);
}
if (walkable || canCheckJet)
{
// Do the fanned LOS - just replicate the above slew of checks.
VectorF fanInc;
S32 numFan = calcFanVector(across, fanInc);
// Added extra checks 1-11-1: Must do a wider fanned check on
// low -> aboveM. Connections without room are slipping through
// the tests because they are diagonal. Also must from a point
// below the clear point, else they effectively can't clear on
// tall hops.
bool ignoreWide = (chuteTop || (zDist < sWideFanRaise + 0.1));
Point3F aboveL, belowC;
if (!ignoreWide)
{
aboveL.set(low.x, low.y, low.z + sWideFanRaise);
belowC.set(clearPt.x, clearPt.y, low.z + sWideFanRaise);
}
if (los.fannedLOS1(high, aboveH, fanInc, numFan))
if (los.fannedLOS1(high, mid, fanInc, numFan))
if (chuteTop || los.fannedLOS1(low, aboveM, fanInc, numFan))
if (chuteTop || los.fannedLOS2(aboveM, aboveH, fanInc, numFan))
if (ignoreWide || los.fannedLOS2(aboveL, aboveM, fanInc, numFan))
{
if (!isChute)
{
if (gapWalk)
if (direct) // in the direct case we need one more fanned LOS
gapWalk = los.fannedLOS1(low, high, fanInc, numFan);
if (gapWalk)
walkable = true;
else if (walkable) // (i.e. walkable so far in our checks)
walkable = los.fannedBumpWalk(aboveM, aboveH, fanInc, numFan);
}
if (walkable)
{
// Well, it's too bad we had to do all this work on to find long
// connections that we throw out, but we have to figure out if it
// needs jet.
makeBridge = (xyDist < MaxWalkableXY);
}
else
{
if (canCheckJet && (xyDist > GraphJetBridgeXY))
{
// The clear check is only needed for jetting connections,
// same with landing room check - which we should only use when
// going up to indoor nodes (and one LOS failure is adequate)
if (!checkLandRoom || !los.haveLOS(low, landRoom))
if (isChute || los.haveLOS(low, clearPt))
if (isChute || los.fannedLOS1(low, clearPt, fanInc, numFan))
if (ignoreWide || los.fannedLOS2(belowC, clearPt, fanInc, numFan))
{
// Check for bonk due to jumps, which add a couple meters.
// We want this to pass chutes and other indoor situations Ok,
// so do LOS up at each end and use that as endpoints.
F32 jumpAdd = (oneIsPortal && xyDist < LiberalBonkXY ? 0.6 : gNavGlobs.mJumpAdd);
aboveM.z += (los.heightUp(aboveM, jumpAdd) - 0.1);
aboveH.z += (los.heightUp(aboveH, jumpAdd) - 0.1);
if (isChute || los.fannedLOS2(aboveM, aboveH, fanInc, numFan))
makeBridge = true;
}
// LOS keeps track of this at lowest level- don't allow
// jetting connections through force fields.
if (los.mHitForceField)
makeBridge = false;
}
}
}
}
}
if (makeBridge)
{
GraphBridgeData bridge1(fromIndex, toIndex);
if (walkable)
bridge1.setWalkable();
if (hopOver)
bridge1.setHop(wall);
mBridgesOut.push_back(bridge1);
return true;
}
}
return false;
}
// We use a searcher to find those same-island nodes which we should try to bridge
// to - namely those who are far on the path relative to straight line distance.
// This will hopefully find for us all indoor jetting connections which are 'useful',
// as well as finding which terrain nodes should be bridged (like over water).
void GraphBridge::onQExtraction()
{
GraphNode * mExtractNode = extractedNode();
if (mExtractNode != mCurrent)
{
if (mSameIslandMark.test(mHead.mIndex))
{
AssertFatal(mSameIslandCount > 0, "GraphBridge::onQExtraction()");
// done when we've visited all the possibilities in the same island
if (--mSameIslandCount == 0)
setEarlyOut(true);
Point3F nodeLoc = mExtractNode->location();
F32 straightLineDist = (mStartLoc - nodeLoc).len();
if (straightLineDist > 0.01)
{
bool bothOutdoor = (mFromOutdoor && mExtractNode->outdoor());
F32 ratio = distSoFar() / straightLineDist;
F32 thresh = mRatios[bothOutdoor];
if (ratio > thresh)
{
// Same-island bridges with good ratios are considered first...
Candidate candidate(mExtractNode, -ratio);
mCandidates.push_back(candidate);
}
}
mSameIslandMark.clear(mHead.mIndex);
}
}
}
// Do all the bridging. Has two modes- spawn and regular nav. Each iteration performs
// one loop of bridging- from one source node to all nodes that need to be bridged.
S32 GraphBridge::findAllBridges()
{
S32 nodeCount = mMainList.size();
Vector<S32> islandCross(mIslands);
GraphNodeList considerList;
mSameIslandMark.setSize(nodeCount);
mSameIslandMark.clear();
mRatios[1] = 1.6; // Outdoor-to-outdoor- try a little smaller ratio than-
mRatios[0] = 2.2; // Other combinations of connections
setSizeAndClear(islandCross, mIslands);
for (S32 i = 0; i < nodeCount; i++)
{
if ((mCurrent = mMainList[i]) != NULL)
{
// Get list of all possibilities (bound box query)
mStartLoc = mCurrent->location();
if (sMagSphere)
{
Point3F pt = sMagSphere->center;
if ((pt -= mStartLoc).lenSquared() > SquareOf(sMagSphere->radius))
continue;
}
Point3F boxOff = (sSpawnGraph ? sBoxOffSPN : sBoxOffNAV);
Box3F area(mStartLoc - boxOff, mStartLoc + boxOff);
S32 curIsland = mCurrent->island();
gNavGraph->getNodesInBox(area, considerList);
// Divy out those in separate island, and mark those in the same island for
// consideration by the special seacher.
mCandidates.clear();
mSameIslandCount = 0;
for (S32 j = 0; j < considerList.size(); j++) {
GraphNode * consider = considerList[j];
if (consider != mCurrent) {
if (consider->island() == curIsland) {
if (!sSpawnGraph) {
mSameIslandMark.set(consider->getIndex());
mSameIslandCount++;
}
}
else {
// Bridges to different islands sorted by distance
F32 dist = (mStartLoc - consider->location()).lenSquared();
Candidate candidate(consider, dist);
mCandidates.push_back(candidate);
}
}
}
// Special search to find which same-island nodes need consideration. We want
// to bridge same-island nodes if their ground travel distance is far relative
// to their straight-line distance.
if (!sSpawnGraph)
{
setEarlyOut(false);
mFromOutdoor = mCurrent->outdoor();
performSearch(mCurrent, NULL);
}
// Candidates considered in order of best-ness heuristic
mCandidates.sort();
// Try to bridge to all candidates which remain.
setSizeAndClear(islandCross, mIslands);
for (CandidateList::iterator c = mCandidates.begin(); c != mCandidates.end(); c++)
{
if (sSpawnGraph)
{
// For spawn we only need to cross to an island once.
if (!islandCross[c->node->island()])
if (tryToBridge(mCurrent, c->node))
islandCross[c->node->island()] = true;
}
else
{
// For regular graph, allow a certain number of bridges to each island.
// Need to sort list, and vary base on island size. We pass in the
// heuristic # for avoiding too many walking connections.
if (islandCross[c->node->island()] < 3)
if (tryToBridge(mCurrent, c->node, -(c->heuristic)))
islandCross[c->node->island()]++;
}
}
}
}
return mBridgesOut.size();
}
S32 gCountReplacementEdges, gCountUnreachable;
// This checks to see if outdoor neighbor edges need to be modified or removed due
// to steepness. If so, a special 'replacement' bridge is created, which either
// removes the default one at startup, or converts it to a jetting connection. Note
// that this also needs to remove steep edges underwater as well.
void GraphBridge::checkOutdoor(const GraphNode* from, const GraphNode* to)
{
static const F32 stepD = 0.3;
Point3F fromLoc = from->location();
Point3F toLoc = to->location();
// Vector for stepping sideways, and across. We're given that these are
// non-zero vectors since from and to are separate neighbors.
Point3F across = (toLoc - fromLoc);
Point3F sideVec(across.y, -across.x, 0);
sideVec.normalize(0.8);
// Figure out step across vector, and number of iterations.
across.z = 0;
F32 dist2D = across.len();
S32 numSteps = S32(dist2D / stepD + 1.0);
across /= F32(numSteps);
// Get top and bottom points. Add amount should be enough given that the
// consolidator generally won't pass really large peaks between nodes.
F32 maxZ = getMax(fromLoc.z, toLoc.z);
Point3F stepVec(fromLoc.x, fromLoc.y, maxZ);
// Step three separate lines across to account for player width.
VectorF normal;
F32 height, previousZ, highestZ = -1e12;
bool removeEdge = false;
bool walkable = true;
stepVec -= sideVec;
for (F32 sideWise = 0.0; sideWise < 2.1 && !removeEdge; sideWise += 1.0)
{
Point3F point = (stepVec + sideVec * sideWise);
if (gNavGraph->terrainHeight(point, &height))
highestZ = getMax(previousZ = height, highestZ);
else
removeEdge = true;
// Do loop plus one extra for last point. Look for too steep a slope if our Z
// is increasing. This means we can't walk. Also check to see if it's possible
// to jet - namely if the highest point isn't too far above higher node.
for (S32 N = 0; N <= numSteps && !removeEdge; N++, point += across)
{
if (gNavGraph->terrainHeight(point, &height, &normal))
{
// Going up and too steep?
if (normal.z < gNavGlobs.mWalkableDot && height > previousZ)
walkable = false;
highestZ = getMax(previousZ = height, highestZ);
}
else
removeEdge = true;
}
}
// Most of the time, this should happen-
if (walkable && !removeEdge)
return;
GraphBridgeData bridge(from->getIndex(), to->getIndex());
bridge.setReplacement();
// See if we can at least jet.
if (!removeEdge &&
(from->getNormal().z >= sJumpAngDot && to->getNormal().z >= sJumpAngDot) &&
(!from->liquidZone() && !to->liquidZone()) &&
(highestZ - maxZ < 5.0)
)
{
gCountReplacementEdges++;
bridge.setHop(highestZ - maxZ + 1.0);
}
else
{
gCountUnreachable++;
bridge.setUnreachable();
}
mBridgesOut.push_back(bridge);
}
// Special pass to find those outdoor neighbors that are too steep to walk up. The
// collision checking in checkOutdoor() only masks with terrain since it is already
// established that there are no other obstructions along the ground.
void GraphBridge::trimOutdoorSteep()
{
gCountReplacementEdges = 0;
gCountUnreachable = 0;
S32 nodeCount = mMainList.size();
for (S32 i = 0; i < nodeCount; i++)
if (GraphNode * from = mMainList[i])
if (from->outdoor()) {
GraphEdgeArray edges = from->getEdges(NULL);
while (GraphEdge * e = edges++)
if (!e->isJetting())
if (GraphNode * to = gNavGraph->lookupNode(e->mDest))
if (to->outdoor())
checkOutdoor(from, to);
}
}
//-------------------------------------------------------------------------------------
S32 QSORT_CALLBACK GraphBridge::CandidateList::cmpCandidates(const void * a,const void * b)
{
F32 A = ((Candidate*)a)->heuristic;
F32 B = ((Candidate*)b)->heuristic;
return (A < B ? -1 : (A > B ? 1 : 0));
}
void GraphBridge::CandidateList::sort()
{
dQsort((void* )(this->address()), this->size(), sizeof(Candidate), cmpCandidates);
}
//-------------------------------------------------------------------------------------
// Something to make it easy to quickly check out the graph generation in a particular
// area by only doing bridge building in that vicinity.
void NavigationGraph::setGenMagnify(const SphereF * sphere,
const Point3F * end1, const Point3F * end2,
F32 xyCheck, F32 zCheck)
{
static SphereF sSphere;
static Point3F sPoint1, sPoint2;
sMagSphere = NULL;
sBreakPt2 = sBreakPt1 = NULL;
if (sphere)
*(sMagSphere = &sSphere) = *sphere;
if (end1)
*(sBreakPt1 = &sPoint1) = *end1;
if (end2)
*(sBreakPt2 = &sPoint2) = *end2;
sBreakRangeXY = xyCheck;
sBreakRangeZ = zCheck;
}
//-------------------------------------------------------------------------------------
const char * NavigationGraph::findBridges()
{
const char * errorText = NULL;
if (!mIslandPtrs.size())
errorText = "Error: islands haven't been marked in graph";
else
{
BridgeDataList bridges;
GraphBridge builder(mNodeList, numIslands(), bridges);
sSpawnGraph = mIsSpawnGraph;
Loser::mCasts = 0;
builder.findAllBridges();
builder.trimOutdoorSteep();
// set the graph variable:
mBridgeList = bridges;
}
return errorText;
}
const char * NavigationGraph::pushBridges()
{
if (mPushedBridges)
return "Bridges already pushed - do MakeGraph() to remove them";
if (mBridgeList.size() == 0)
return "No bridges exist";
for (S32 i = 0; i < mBridgeList.size(); i++)
{
GraphBridgeData & B = mBridgeList[i];
if (!B.isReplacement())
{
for (S32 k = 0; k < 2; k++)
{
RegularNode * src = dynamic_cast<RegularNode*>(mNodeList[B.nodes[k^0]]);
RegularNode * dst = dynamic_cast<RegularNode*>(mNodeList[B.nodes[k^1]]);
GraphEdge& edge = src->pushEdge(dst);
if (B.mustJet()) {
edge.setJetting();
if (B.jetClear)
edge.setHop(B.jetClear);
}
mJetManager.initEdge(edge, src, dst);
}
}
else
{
mJetManager.replaceEdge(B);
}
}
Con::printf("Connected %d bridges", mBridgeList.size());
mPushedBridges = true;
markIslands();
return NULL;
}
//-------------------------------------------------------------------------------------
GraphSeeds::GraphSeeds(NavigationGraph& graph)
: mGraph(graph),
mLoser(InteriorObjectType)
{
// Save node count for later mapping into antecedent index table.
mOriginalCount = graph.numNodes();
}
// Add the seed. We have to remember our parent (antecedent) that spawned us thinking
// that we might be potentially useful.
void GraphSeeds::pushSeed(GraphNode* antecedent, const Point3F& pos, GraphSeed::Type type)
{
S32 index = antecedent->getIndex();
GraphSeed seed(index, pos, type);
mAntecedents.push_back(index);
push_back(seed);
}
Point3F sgCheckSeedUpper(-149.0, -136.5, 166.42);
// Look at drop off from each edge of node. If it lands inside of an interior volume
// with a modicum of containment, then we want to add a seed node there.
void GraphSeeds::seedDropOffs(GraphNode * antecedent)
{
// Just for quick testing - look at our shape and see if we can get the behavior we
// want locally here-
// if (!within(antecedent->location(), sgCheckSeedUpper, 0.6))
// return;
mVolume = mGraph.fetchVolume(antecedent, false);
Vector<Point3F> &corners = mVolume.mCorners;
if (S32 N = corners.size())
{
// Simplify the loop-
corners.push_back(corners[0]);
// Do drop offs on each edge.
for (S32 i = 0; i < N; i++)
{
Point3F p0 = corners[i + 0];
Point3F p1 = corners[i + 1];
Point3F drop = scaleBetween(p0, p1, 0.5);
VectorF normal(p0.y - p1.y, p1.x - p0.x, 0);
F32 len = normal.len();
if (len > 0.01)
{
// Go out at least by clear distance. Insure we're above our plane.
normal *= ((sClearDists[0] * 1.2) / len);
drop += normal;
F32 floorZ = solveForZ(mVolume.mFloor, drop);
drop.z = (floorZ + 0.5);
if (mLoser.hitBelow(drop, 50.0f))
{
// Now see if we landed inside of a (different) node that's large,
// as defined by having good containment on this point.
drop.z += 0.2;
F32 containment;
// if (within(drop, sgCheckSeedLower, 3.0))
if (GraphNode * closest = mGraph.closestNode(drop, &containment))
{
if (closest == antecedent)
{
AssertFatal(containment > 0, "GraphSeeds::seedDropOffs()");
}
else
{
if (containment < -1.3)
{
GraphSeed::Type seedType = GraphSeed::DropOff;
// For those close to chute nodes, we make these regular nodes
// that go through full bridging process.
if (mGraph.getChutes().findNear(drop, 2.2, 1.0, mNearChutes))
seedType = GraphSeed::Regular;
}
}
}
}
}
}
}
}
// Procedure is then to cast across any of the walls we are outside of and see if
// the point that is cast across has decent containment within this selfsame node.
// Probably the containment we are looking for is a little less than the cast
// across distance. If Ok, then we create the node with our given antecedent.
// See crossNearbyVolumes() in graphFind.cc where most of this work happens.
void GraphSeeds::seedInventory(GraphNode * antecedent)
{
Vector<Point3F> crossings;
const Point3F& nodeLoc = antecedent->location();
if (S32 N = mGraph.crossNearbyVolumes(antecedent->location(), crossings))
for (S32 i = 0; i < N; i++)
if (mLoser.haveLOS(nodeLoc, crossings[i]))
pushSeed(antecedent, crossings[i], GraphSeed::Inventory);
}
S32 GraphSeeds::scatter()
{
for (S32 i = 0; i < mGraph.numNodes(); i++)
if (GraphNode * node = mGraph.lookupNode(i))
if (node->indoor())
if (node->inventory())
seedInventory(node);
else if (NavigationGraph::sSeedDropOffs)
seedDropOffs(node);
return size();
}
// Find those seeds which have antecedents that aren't part of the largest island
// in the graph. These are the seeds we will want to use. For the drop off seeds
// we only attempt to bridge to their antecedent.
void GraphSeeds::markUsefulSeeds()
{
// Make sure offsets are managed Ok - all seeds should now be nodes.
AssertFatal(mOriginalCount + size() == mGraph.numNodes(), "markUsefulSeeds()");
for (iterator it = begin(); it != end(); it++)
{
GraphNode * antecedent = mGraph.lookupNode(it->antecedent());
if (antecedent->island() != mGraph.largestIsland())
{
S32 mapIndex = mOriginalCount + (it - begin());
GraphNode * seed = mGraph.lookupNode(mapIndex);
// Bridge builder uses this bit-
seed->setUsefulSeed();
}
}
}
//-------------------------------------------------------------------------------------
#define SeedBoxW 0.3
#define SeedBoxH 2.0
// Here's where we're adding the seed into the (persisted) data lists. They will
// take effect as run time nodes on the next call to makeGraph().
void NavigationGraph::installSeed(const Point3F& pos)
{
IndoorNodeInfo nodeInfo;
nodeInfo.pos = pos;
nodeInfo.setSeed(true);
mNodeInfoList.push_back(nodeInfo);
mNodeVolumes.addVolume(pos, SeedBoxW, SeedBoxH);
}
//
// What was previously several calls from script (makeGraph, findBridges, pushBridges)
// has now been rolled altogether for the purposes of trying to fill the last holes
// that we are seeing in the graph. This assumes that script has already uploaded
// the groundPlan and floorPlan information.
//
bool NavigationGraph::assemble()
{
sSpawnGraph = mIsSpawnGraph;
Loser::mCasts = 0;
// First we need a made graph for the seeder to work.
makeGraph();
// Look for problem areas and scatter seeds.
GraphSeeds seeds(*this);
seeds.scatter();
for (S32 i = 0; i < seeds.size(); i++)
installSeed(seeds[i].location());
// Remake to create the run time seed nodes.
makeGraph();
// Now build bridges WITHOUT using the seed nodes-
BridgeDataList bridges;
GraphBridge bridgeBuilder(mNodeList, numIslands(), bridges);
bridgeBuilder.heedSeeds(false);
bridgeBuilder.findAllBridges();
// Set the bridges and connect. This finds largest island, which puts us -
mBridgeList = bridges;
pushBridges();
// - in a position to know which seeds might matter (those with stranded parents)
seeds.markUsefulSeeds();
// Now bridge seed nodes-
bridges.clear();
bridgeBuilder.heedSeeds(true);
bridgeBuilder.findAllBridges();
// Still need to trim outdoor steep connections (this adds to bridges)
bridgeBuilder.trimOutdoorSteep();
// Add the new bridges to the list and rebuild everything-
mBridgeList.merge(bridges);
makeGraph();
pushBridges();
// Done
return 0;
}

96
ai/graphBridge.h Normal file
View file

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHBRIDGE_H_
#define _GRAPHBRIDGE_H_
class GraphBridge : public GraphSearch
{
protected:
typedef GraphSearch Parent;
struct Candidate {
GraphNode * node;
F32 heuristic;
Candidate(GraphNode* n=NULL, F32 m=0.0) {node = n; heuristic = m;}
};
class CandidateList : public Vector<Candidate> {
static S32 QSORT_CALLBACK cmpCandidates(const void* , const void* );
public:
void sort();
};
protected:
// Parameters to this object-
const GraphNodeList& mMainList;
const S32 mIslands;
BridgeDataList& mBridgesOut;
// Working variables-
GraphNode * mCurrent;
Point3F mStartLoc;
BitVector mSameIslandMark;
S32 mSameIslandCount;
bool mFromOutdoor;
bool mHeedSeeds;
F32 mRatios[2];
CandidateList mCandidates;
ChutePtrList mChuteList;
protected:
void onQExtraction();
F32 getEdgeTime(const GraphEdge* e);
bool earlyOut() {return true;}
bool heedThese(const GraphNode* from, const GraphNode* to);
bool tryToBridge(const GraphNode* from, const GraphNode* to, F32 ratio=1e13);
void checkOutdoor(const GraphNode* from, const GraphNode* to);
public:
GraphBridge(const GraphNodeList& mainList, S32 islands, BridgeDataList& listOut);
S32 findAllBridges();
void trimOutdoorSteep();
void heedSeeds(bool b);
};
class GraphSeed
{
public:
enum Type {DropOff, Inventory, Regular};
protected:
S32 mAntecedent;
Point3F mLocation;
Type mType;
public:
GraphSeed(S32 A, const Point3F& P, Type T) : mAntecedent(A), mLocation(P), mType(T) {}
bool isDropOff() const {return mType == DropOff;}
bool isInventory() const {return mType == Inventory;}
S32 antecedent() const {return mAntecedent;}
const Point3F& location() const {return mLocation;}
};
class GraphSeeds : public Vector<GraphSeed>
{
protected:
NavigationGraph& mGraph;
GraphVolume mVolume;
Loser mLoser;
Vector<S32> mAntecedents;
S32 mOriginalCount;
ChutePtrList mNearChutes;
void pushSeed(GraphNode* antecedent, const Point3F&, GraphSeed::Type);
void seedDropOffs(GraphNode* antecedent);
void seedInventory(GraphNode* antecedent);
public:
GraphSeeds(NavigationGraph& G);
void markUsefulSeeds();
S32 scatter();
};
#endif

208
ai/graphBuildLOS.cc Normal file
View file

@ -0,0 +1,208 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#define MuzzleHt 0.82f
#define HeadHt 2.2f
//-------------------------------------------------------------------------------------
// Want to see stuff on screen, so we scamble node order (bunch of random swaps)
static void makeScrambler(Vector<S32>& vec, S32 size)
{
S32 i, i1, i2, count = size * 10;
for (i = 0, vec.setSize(size); i < size; i++)
vec[i] = i;
while (--count >= 0)
{
S32 t = vec[i1 = (gRandGen.randI()&0xFFFFFF) % size];
vec[i1] = vec[i2 = (gRandGen.randI()&0xFFFFFF) % size];
vec[i2] = t;
}
}
static RayInfo sColl;
static bool haveLOS(Point3F src, Point3F dst, F32 above)
{
static const U32 sMask = InteriorObjectType|TerrainObjectType;
src.z += above;
dst.z += above;
return !gServerContainer.castRay(src, dst, sMask, &sColl);
}
class MakeLOSEntries : public GraphSearch
{
const GraphNodeList& mList;
LOSXRefTable& mLOSTable;
S32 mCurrent;
GraphNode * mFromNode;
Point3F mFromLoc;
F32 mThreshDist;
U32 mStartTime, mSaveMS;
S32 mLowButNotHigh;
S32 mFromIndex;
Vector<S32> mScramble;
S32 mLOSCalls;
public:
Vector<LineSegment> mRenderSegs;
Point3F mViewLoc;
protected:
// virtuals-
void onQExtraction();
F32 getEdgeTime(const GraphEdge*);
bool earlyOut();
public:
MakeLOSEntries(const GraphNodeList& list, LOSXRefTable& xRef, Point3F view);
bool isDone() const {return mCurrent>=mList.size();}
U32 elapsedTime() const {return Platform::getRealMilliseconds()-mSaveMS;}
S32 lowNotHigh() const {return mLowButNotHigh;}
S32 numLOSCalls() const {return mLOSCalls;}
bool nextOneReady();
void workAWhile();
};
MakeLOSEntries::MakeLOSEntries(const GraphNodeList& list, LOSXRefTable& los, Point3F v)
: mList(list), mLOSTable(los), mViewLoc(v)
{
S32 N = list.size();
mLOSTable.setDims(N * (N + 1) >> 1, 2); // Alloc our 2-bit table
mViewLoc.set(-44,-31,90);
mThreshDist = 700.0f;
mCurrent = -1;
mSaveMS = Platform::getRealMilliseconds();
mLOSCalls = mLowButNotHigh = 0;
makeScrambler(mScramble, list.size());
}
// Run LOS from base to this node. We truncate the search at a certain path distance,
// which should make sense in all but a few cases that we should be aware of. It
// will make the search quicker for most worlds though.
void MakeLOSEntries::onQExtraction()
{
S32 toIndex = extractedNode()->getIndex();
if (mFromIndex < toIndex) {
//==> Path distance really not right- just need a better local query.
if (distSoFar() < mThreshDist) {
Point3F toLoc = extractedNode()->location();
bool losLow = haveLOS(mFromLoc, toLoc, MuzzleHt);
bool losHigh = haveLOS(mFromLoc, toLoc, HeadHt);
U32 tabEntry = LOSXRefTable::Hidden;
// Speed tracking
mLOSCalls += 2;
// These are strange- add to list of warning log for graph maker
mLowButNotHigh += (losLow && !losHigh);
// Crude calc of entry for now-
if(losLow)
tabEntry = LOSXRefTable::FullLOS;
else if (losHigh)
tabEntry = LOSXRefTable::MinorLOS;
// Enter into table-
mLOSTable.setEntry(mFromIndex, toIndex, tabEntry);
// Stuff to render-
if (mRenderSegs.size() < 600) {
LineSegment segment(mFromLoc, tabEntry ? toLoc : sColl.point);
if (segment.distance(mViewLoc) < 120.0f)
mRenderSegs.push_back(segment);
}
}
else // outside
setDone();
}
}
// Virtual - this will cause graph time to equal distance.
F32 MakeLOSEntries::getEdgeTime(const GraphEdge * edge)
{
return edge->mDist;
}
// Prepare a new search. Note mCurrent constructs at -1 for proper entry.
bool MakeLOSEntries::nextOneReady()
{
if (++mCurrent < mList.size()) {
mFromNode = mList[mScramble[mCurrent]];
mFromLoc = mFromNode->location();
mFromIndex = mFromNode->getIndex();
return true;
}
return false;
}
#define MillisecondWorkShift 90
// Virtual - runSearch() can be stopped in the middle.
bool MakeLOSEntries::earlyOut()
{
U32 timeElapsed = (Platform::getRealMilliseconds() - mStartTime);
return (timeElapsed > MillisecondWorkShift);
}
void MakeLOSEntries::workAWhile()
{
mStartTime = Platform::getRealMilliseconds();
while (!isDone() && !earlyOut())
if (inProgress())
runSearch(mFromNode);
else if (nextOneReady())
runSearch(mFromNode);
NavigationGraph::sProcessPercent = F32(mCurrent) / F32(mList.size());
}
bool NavigationGraph::prepLOSTableWork(Point3F viewLoc)
{
if (mTableBuilder) {
delete mTableBuilder;
mTableBuilder = NULL;
}
mTableBuilder = new MakeLOSEntries(mNonTransient, mLOSXRef, viewLoc);
return true;
}
// This gets called repeatedly until the table is built. Idea here is to slice
// the process so that some rendering can occur during this lengthy process.
bool NavigationGraph::makeLOSTableEntries()
{
MakeLOSEntries * makeEntries = dynamic_cast<MakeLOSEntries*>(mTableBuilder);
AssertFatal(makeEntries, "Graph preprocess: prepLOSTable() needed");
makeEntries->workAWhile();
mRenderThese = makeEntries->mRenderSegs;
makeEntries->mRenderSegs.clear();
if (makeEntries->isDone()) {
clearRenderSegs();
Con::printf("Total table size = %d", mLOSXRef.numBytes());
Con::printf("Performed %d LOS calls", makeEntries->numLOSCalls());
Con::printf("Elapsed time = %d milliseconds", makeEntries->elapsedTime());
if (makeEntries->lowNotHigh())
Con::printf("%d Low-not-high entries found", makeEntries->lowNotHigh());
delete mTableBuilder;
mTableBuilder = NULL;
// Convert data to better hasher-
makeLOSHashTable();
return false;
}
return true;
}

660
ai/graphConjoin.cc Normal file
View file

@ -0,0 +1,660 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graphData.h"
#include "Core/BitTables.h"
#include "ai/graphGroundPlan.h"
// Two pass consolidation:
//
// 1. Find what is maximum level that can be consolidated based on flatness (+ same-
// type-ness of the squares inside).
// 2. Actually assign the levels, limiting some based on condition II below.
//
// Here are the conditions for consolidating squares.
//
// I. A square can only be consolidated if there are no level zero squares inside
// and if all of the four subsquares have managed to be consolidated (plus
// being inside the graph region). ====> plus no empty squares.
// II. No terrain node can have a neighbor whose level differs by more than one. So
// we can't have a huge consolidated square with a bunch of little neighbors,
// for example. There are two motivations for this: We'd like to be able
// to cap the neighbor count; Second, it will help us insure that large
// nodes still make sense as distinct entries in the LOS xref table.
// III. A square can be consolidated if all sub-squares are of the same type, plus
// this square is of size 1<<L for some L,and aligned on like boundary (i.e.
// the L low bits of X and Y will be zero).
//
//=====================================================================================
#define Whatever true
GridNormalInfo::GridNormalInfo()
{
notEmpty = false;
hasSteep = false;
}
//
// Straight-up vectors are a singularity with Eulers, so just to be safe we make our
// own pseudo-Euler, a pair of angles along X and Y, like so-
//
// angle.x Counter-clockwise angle of projection of normal into XZ plane.
// angle.y Same thing in YZ plane. Ex: (5,2,1) -> PI/6.
//
Point2F GridNormalInfo::normalToAngle(const VectorF& normal)
{
Point2F angle;
angle.x = (M_PI / 2.0) - mAtan( normal.x, normal.z );
angle.y = (M_PI / 2.0) - mAtan( normal.y, normal.z );
return angle;
}
VectorF GridNormalInfo::angleToNormal(const Point2F& A)
{
VectorF normal( mCos(A.x)/mSin(A.x), mCos(A.y)/mSin(A.y), 1.0);
normal.normalize();
return normal;
}
//-------------------------------------------------------------------------------------
//
// The following 3 classes perform separate passes of the data consolidation.
// Note they only build the data (what gets persisted) - creating run time
// nodes from this data is done elsewhere.
//
class FindBestConsolidations : public GridVisitor
{
protected:
Vector<GridNormalInfo> mGridNormals;
TrackLevels & mTrackLevels;
const ConjoinConfig & mConfigure;
F32 mDotThreshold;
void getGridNormals();
bool allSameType(const GridArea& R, U8& nodeType);
bool areaIsFlat(const GridArea& R);
bool atLevelZero(const GridArea& R); // virtual
bool afterDivide(const GridArea& R, S32 level, bool success); // virtual
public:
FindBestConsolidations(const GridArea& G, const ConjoinConfig& C, TrackLevels& T);
};
class SmoothOutLevels : public GridVisitor
{
protected:
const S32 mLevel;
TrackLevels & mTrackLevels;
bool allWater(const GridArea& R);
void capLevelAt(const GridArea& R, S32 L);
void doCurrentBottom(const GridArea& R);
bool atLevelZero(const GridArea& R); // virtual
bool beforeDivide(const GridArea& R, S32 level); // virtual
public:
SmoothOutLevels(const GridArea& G, TrackLevels& T, S32 L);
};
class BuildConsolidated : public GridVisitor
{
protected:
const TrackLevels & mTrackLevels;
Consolidated & mConsolidated;
bool checkAddToList(const GridArea& R, S32 level);
bool beforeDivide(const GridArea& R, S32 level); // virtual
bool atLevelZero(const GridArea& R); // virtual
public:
BuildConsolidated(const GridArea& G, const TrackLevels& T, Consolidated& C);
};
//-------------------------------------------------------------------------------------
ConjoinConfig::ConjoinConfig()
{
maxAngleDev = 45; // (only one actually used right now...)
maxBowlDev = 70;
maxLevel = 6;
}
//-------------------------------------------------------------------------------------
// Just check that it's a valid rect for that level.
static bool checkArea(const GridArea & G, S32 L, const char * caller)
{
U16 ext = (1 << L);
U16 mask = ext - 1;
const char * problem = NULL;
if( G.point.x & mask )
problem = "X point isn't aligned";
else if ( G.point.y & mask )
problem = "Y point isn't aligned";
else if( G.extent.x != ext )
problem = "X extent is bad";
else if( G.extent.y != ext )
problem = "Y extent is bad";
AssertFatal( caller && ! problem, avar("Problem= %s in %s", problem, caller) );
return ! problem && caller;
}
TrackLevels::TrackLevels(S32 sz)
{
init(sz);
}
void TrackLevels::init(S32 size)
{
setSizeAndClear(achievedLevels, size);
setSizeAndClear(nodeTypes, size);
}
S32 TrackLevels::size() const
{
return achievedLevels.size();
}
U16 TrackLevels::getNodeType(S32 idx) const
{
return nodeTypes[idx];
}
void TrackLevels::setNodeType(S32 idx, U8 nodeType)
{
nodeTypes[idx] = nodeType;
}
// Achieved level is the highest set bit.
void TrackLevels::setAchievedLevel(S32 i, S32 level)
{
AssertFatal(i < achievedLevels.size(), "TrackLevels::setAchievedLevel()");
if(level < 0) {
achievedLevels[i] = 0;
}
else{
U16 mask = (1 << level);
AssertFatal(achievedLevels[i]==mask-1, "setAchievedLevel");
achievedLevels[i] |= mask;
}
}
// Note that TrackLevels maps the case of zero not being achieved into a -1 value.
S32 TrackLevels::getAchievedLevel(S32 idx) const
{
U16 achieved = achievedLevels[idx];
U16 L = BitTables::getPower16(achieved);
if( L-- > 0 )
return S32(L);
else
return -1;
}
// There's a problem with -1 and 0 not carrying enough information. We have some
// nodes which need to be considered as -1 for the purposes of the consolidation
// since they don't really span a square, but we need to consider them zero below
// when we're assembling the list. Pretty klunky...
S32 TrackLevels::originalNodeLevel(S32 idx) const
{
S32 level = getAchievedLevel(idx);
if(level == -1 && nodeTypes[idx])
return 0;
else
return level;
}
void TrackLevels::capLevelAt(S32 i, U16 lev)
{
AssertFatal(validArrayIndex(i, achievedLevels.size()), "TrackLevels::capLevelAt()");
U16 mask = (1 << lev + 1) - 1;
achievedLevels[i] &= mask;
}
//-------------------------------------------------------------------------------------
// Visitor to find best consolidations
FindBestConsolidations::FindBestConsolidations( const GridArea& gridArea,
const ConjoinConfig& thresholds,
TrackLevels& trackArrayOut
)
: mTrackLevels(trackArrayOut),
mConfigure(thresholds),
GridVisitor(gridArea)
{
// Pre-gather terrain normal information; convert angle values to what we need
getGridNormals();
mDotThreshold = F32(mCos(mDegToRad(thresholds.maxAngleDev)));
}
// Go through and fetch the triangle normals from the terrain, plus compute the our
// pseudo-Euler for each normal.
void FindBestConsolidations::getGridNormals()
{
TerrainBlock * terr = GroundPlan::getTerrainObj();
F32 sqrW = gNavGlobs.mSquareWidth;
F32 stepIn = (sqrW * 0.1);
Point2I stepper;
mGridNormals.clear();
for (mArea.start(stepper); mArea.pointInRect(stepper); mArea.step(stepper))
{
// this is empty by default
GridNormalInfo info;
// find out, based on split, good points to use for normal check:
Point2F loc(stepper.x * sqrW, stepper.y * sqrW);
Point2F upperCheckPt = loc;
Point2F lowerCheckPt = loc;
if( (stepper.x + stepper.y) & 1 ){
lowerCheckPt += Point2F(stepIn, stepIn);
upperCheckPt += Point2F(sqrW - stepIn, sqrW - stepIn);
}
else{
lowerCheckPt += Point2F(sqrW - stepIn, stepIn);
upperCheckPt += Point2F(stepIn, sqrW - stepIn);
}
// get slopes and convert to the pseudo-Euler
if (terr->getNormal(lowerCheckPt, &info.normals[0]))
if (terr->getNormal(upperCheckPt, &info.normals[1])) {
for (S32 both = 0; both < 2; both++) {
info.angles[both] = info.normalToAngle(info.normals[both]);
if (info.normals[both].z < gNavGlobs.mWalkableDot)
info.hasSteep = true;
}
info.notEmpty = true;
}
mGridNormals.push_back( info );
}
}
bool FindBestConsolidations::allSameType(const GridArea& R, U8& nodeType)
{
nodeType = mTrackLevels.getNodeType(mArea.getIndex(R.point));
Point2I stepper;
for (R.start(stepper); R.pointInRect(stepper); R.step(stepper))
if (mTrackLevels.getNodeType(mArea.getIndex(stepper)) != nodeType)
return false;
return true;
}
// ==>
// ==> This routine is where all the consolidation work happens and will eventually
// ==> do a lot more work to do more robust checks. Example:
// ==> We want to allow more flatness for bowls than for hills.
// ==> We may want to consolidate more in places far from action.
// ==>
bool FindBestConsolidations::areaIsFlat(const GridArea& R)
{
// Area must be of same type. If that type is submerged - then we consider
// it flat right away.
U8 nodeType;
if (!allSameType(R, nodeType))
return false;
else if (nodeType == GraphNodeSubmerged)
return true;
Point2F minAngle(M_PI, M_PI);
Point2F maxAngle(0,0);
Point2I stepper;
// First accumulate angle bounds,
for (R.start(stepper); R.pointInRect(stepper); R.step(stepper))
{
const GridNormalInfo & info = mGridNormals[ mArea.getIndex(stepper) ];
// Don't consolidate if there are non-walkable surfaces-
// Actually...
// Turns out this makes too many nodes on some maps - we'll do this differently
// if (info.hasSteep)
// return false;
for (S32 triangle = 0; triangle < 2; triangle++)
{
minAngle.x = getMin( info.angles[triangle].x, minAngle.x );
minAngle.y = getMin( info.angles[triangle].y, minAngle.y );
maxAngle.x = getMax( info.angles[triangle].x, maxAngle.x );
maxAngle.y = getMax( info.angles[triangle].y, maxAngle.y );
}
}
// Get the middle angle and the corresponding unit vector:
Point2F medianAngle = (minAngle + maxAngle) * 0.5;
VectorF medianNormal = GridNormalInfo::angleToNormal (medianAngle);
// Find maximum deviation from median slope.
F32 minDot = 1.0;
for (R.start(stepper); R.pointInRect(stepper); R.step(stepper))
{
const GridNormalInfo & info = mGridNormals[ mArea.getIndex(stepper) ];
for (S32 triangle = 0; triangle < 2; triangle++) {
// == > We can check early out here, but want to watch behavior for now
minDot = getMin( mDot(info.normals[triangle], medianNormal), minDot );
}
}
// if all dot products are sufficiently close to 1, we're hopefully flat-
return minDot > mDotThreshold;
}
// pass one of consolidation - finds maximum possible squares.
bool FindBestConsolidations::atLevelZero(const GridArea& R)
{
S32 idx = mArea.getIndex(R.point);
S32 achieved = mTrackLevels.getAchievedLevel(idx);
if( achieved < 0 )
return false;
AssertFatal( achieved == 0, "FindBest messed up at level zero." );
return true;
}
bool FindBestConsolidations::afterDivide(const GridArea& R, S32 level, bool success)
{
S32 idx = mArea.getIndex(R.point);
AssertFatal( validArrayIndex(idx, mTrackLevels.size()), "Conjoin weird idx");
// ==> Next: Look at node type as part of consolidation. Mainly Water.
// ==> Q: Will other volume types be important? (i.e. fog, ..?).
if( success && level <= MaxConsolidateLevel )
{
if (areaIsFlat( R ))
{
Point2I stepper; // set achieved level in all sub-squares.
for (R.start(stepper); R.pointInRect(stepper); R.step(stepper))
mTrackLevels.setAchievedLevel( mArea.getIndex(stepper), level );
return true;
}
}
return false;
}
//-------------------------------------------------------------------------------------
// VISITOR TO SMOOTH OUT LEVELS.
// Some of the best ones may be eliminated to satisfy condition II above.
static GridArea getParentArea(const GridArea & R, S32 L)
{
checkArea( R, L, "getParentArea one" );
L = L + 1;
Point2I roundDownPoint((R.point.x >> L) << L, (R.point.y >> L) << L);
Point2I doubleTheExtent( R.extent.x << 1, R.extent.y << 1 );
GridArea parent(roundDownPoint, doubleTheExtent);
checkArea( parent, L, "getParentArea two" );
return parent;
}
SmoothOutLevels::SmoothOutLevels(const GridArea& G, TrackLevels& T, S32 L)
: mTrackLevels(T), mLevel(L), GridVisitor(G) { }
// Cap all within the given area at L.
void SmoothOutLevels::capLevelAt(const GridArea& R, S32 L)
{
checkArea( R, L, "capping level in smoother" );
Point2I stepper;
for (R.start(stepper); R.pointInRect(stepper); R.step(stepper)) {
S32 index = mArea.getIndex(stepper);
if (index >= 0)
mTrackLevels.capLevelAt(index, L);
}
}
bool SmoothOutLevels::allWater(const GridArea& R)
{
Point2I stepper;
for (R.start(stepper); R.pointInRect(stepper); R.step(stepper))
if (mTrackLevels.getNodeType(mArea.getIndex(stepper)) != GraphNodeSubmerged)
return false;
return true;
}
// Each pass does a different bottom level to smooth it out. Within this routine we
// are guaranteed that we're at the bottom level for this pass.
void SmoothOutLevels::doCurrentBottom(const GridArea& R)
{
bool needToCapSurrounding;
S32 idx = mArea.getIndex(R.point);
S32 achieved = mTrackLevels.getAchievedLevel(idx);
// if (allWater(R))
// needToCapSurrounding = false;
// else
{
// At level 0, we cap if either the achieved is 0, or -1 (an empty square).
if (mLevel == 0)
needToCapSurrounding = (achieved <= 0);
else
needToCapSurrounding = (achieved == mLevel);
}
if (needToCapSurrounding)
{
// THIS IS THE TRICKY STEP IN SMOOTHING OUT THE LEVELS! We cap all eight of our
// same-sized neighbors- but we choose the cap level based on whether or not they
// fall inside OUR parent, or if they fall in a NEIGHBOR of our parent. If in our
// parent, then cap at our level. If in neighbor's parent, we cap at THAT level.
GridArea ourParent = getParentArea(R, mLevel);
// Loop on all 8 neighbors:
for(S32 y = -1; y <= 1; y++) for(S32 x = -1; x <= 1; x++) if (x || y)
{
Point2I neighborPoint = Point2I(x << mLevel, y << mLevel) + R.point;
S32 neighborIndex = mArea.getIndex( neighborPoint );
if (neighborIndex >= 0)
{
GridArea neighborArea(neighborPoint, R.extent);
if (ourParent.contains(neighborArea))
capLevelAt(neighborArea, mLevel);
else
{
GridArea neighborParent = getParentArea( neighborArea, mLevel );
if(mArea.contains(neighborParent))
capLevelAt(neighborParent, mLevel + 1);
}
}
}
}
}
// If the current bottom level we are looking at is the best consolidation
// that can happen for this square, go around to all neighbors and cap
// their best-so-far levels.
bool SmoothOutLevels::atLevelZero(const GridArea& R)
{
if(mLevel == 0) {
doCurrentBottom(R);
return true;
}
return false;
}
bool SmoothOutLevels::beforeDivide(const GridArea& R, S32 level)
{
checkArea( R, level, "smoother before divide" );
// AssertFatal( level >= mLevel, "Smoothing making it too far down somehow" );
if( level > mLevel ) // still at high level - tell it to divide further.
return true;
else if(level == mLevel){ //
doCurrentBottom(R);
return false;
}
else
return false;
}
//-------------------------------------------------------------------------------------
// This Visitor assembles the list into mConsolidated:
BuildConsolidated::BuildConsolidated(const GridArea& G, const TrackLevels& T, Consolidated& C)
: GridVisitor(G), mTrackLevels(T), mConsolidated(C)
{ }
bool BuildConsolidated::checkAddToList(const GridArea& R, S32 level)
{
checkArea(R, level, "checkAddToList");
if (mTrackLevels.originalNodeLevel(mArea.getIndex(R.point)) == level)
{
OutdoorNodeInfo nodeInfo;
nodeInfo.level = level;
nodeInfo.x = R.point.x;
nodeInfo.y = R.point.y;
mConsolidated.push_back(nodeInfo);
return false;
} // (ret vals needed for beforeDivide(), false will stop the depth recurse)
return true;
}
bool BuildConsolidated::beforeDivide(const GridArea& R, S32 level)
{
return checkAddToList( R, level );
}
bool BuildConsolidated::atLevelZero(const GridArea& R)
{
checkAddToList( R, 0 );
return Whatever;
}
//-------------------------------------------------------------------------------------
// This routine is supplied with those grid squares that can't conjoin.
// The 'output' is to set up the consolidated list member variable,
// and return if successful.
bool TerrainGraphInfo::buildConsolidated(const TrackLevels & whichZero,
const ConjoinConfig & configInfo)
{
TrackLevels trackData = whichZero;
GridArea gridArea(originGrid, gridDimensions);
// pass one to find best possible consolidations
FindBestConsolidations findLargestPossible(gridArea,configInfo,trackData);
findLargestPossible.traverse();
// Next, enforce maximum difference of one on the levels.
for (S32 bottom = 0; bottom < MaxConsolidateLevel; bottom++)
{
// two passes needed to properly propagate the smoothing...
for (S32 pass = 0; pass < 2; pass++)
{
SmoothOutLevels doSmoothing(gridArea, trackData, bottom);
doSmoothing.traverse();
}
}
// Now build the node list. The caller is responsible for making it
// part of the graph.
BuildConsolidated buildIt(gridArea, trackData, consolidated);
consolidated.clear();
buildIt.traverse();
return true;
}
//-------------------------------------------------------------------------------------
// Mark bit on grid square signalling if it's near steep. Will cause roam radii to
// be capped so bots don't walk off slopes, and don't advance through a slope they must
// walk around.
S32 TerrainGraphInfo::markNodesNearSteep()
{
TerrainBlock * terr = GroundPlan::getTerrainObj();
F32 startAng = M_PI * (15.0 / 8.0);
F32 angDec = M_PI / 4.0;
Point3F loc, normal;
S32 nSteep = 0;
for (S32 i = 0; i < nodeCount; i++)
{
// Get position- terrain system is grid aligned...
indexToLoc(loc, i);
loc -= originWorld;
// Could be smarter here, but it's a drop in the preprocessing bucket. Just
// check enough points to be sure to find if any triangle is steep.
for (F32 ang = startAng; ang > 0; ang -= angDec)
{
Point2F checkPos(mCos(ang) * 0.2 + loc.x, mSin(ang) * 0.2 + loc.y);
if (terr->getNormal(checkPos, &normal))
if (normal.z < gNavGlobs.mWalkableDot) {
setSteep(i);
nSteep++;
break;
}
}
}
return nSteep;
}
//-------------------------------------------------------------------------------------
// This was being done off of run time nodes- convert to just using the grid data.
bool TerrainGraphInfo::consolidateData(const ConjoinConfig & config)
{
if (nodeCount <= 0)
return false;
// This is a good place to mark the steep bits on the navigableFlags. Note it's not
// used quite the same as the hasSteep in the consolidation, which shouldn't carry
// over to other grid squares. The bit does (used for roamRadii)
markNodesNearSteep();
// Initialize NULL track data (keeps track of conjoin levels achieved per square).
TrackLevels levelData(nodeCount);
const U32 checkOpenSquare = 0xff;
// Assemble the data for the consolidator.
for (S32 i = 0; i < nodeCount; i++)
{
U8 nodeType = NavGraphNotANode;
S32 nodeLevel = -1;
if (!obstructed(i))
{
// Check for shadowed - we flag like indoor as well to make them all stay
// at the low level. We need them dense in shadow so there will be enough
// jetting connections going up.
if (shadowed(i))
{
nodeType = NavGraphIndoorNode;
}
else if (submerged(i))
{
nodeType = NavGraphSubmergedNode;
nodeLevel = 0;
}
else
{
// We have to use level -1 for things which don't have all their neighbors,
// with one exception being on the edge of the mission area. Here we just
// see if it has the neighbors it should have (returned by onSideOfArea()).
nodeType = NavGraphOutdoorNode;
if (const U32 neighbors = onSideOfArea(i)) {
if (neighborFlags[i] == neighbors)
nodeLevel = 0;
}
else if (neighborFlags[i] == checkOpenSquare)
nodeLevel = 0;
}
levelData.setAchievedLevel(i, nodeLevel);
levelData.setNodeType(i, nodeType);
}
}
bool Ok = buildConsolidated(levelData, config);
return Ok;
}

1283
ai/graphData.cc Normal file

File diff suppressed because it is too large Load diff

531
ai/graphData.h Normal file
View file

@ -0,0 +1,531 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHDATA_H_
#define _GRAPHDATA_H_
#ifndef _GRAPHMATH_H_
#include "ai/graphMath.h"
#endif
#ifndef _GRAPHDEFINES_H_
#include "ai/graphDefines.h"
#endif
#ifndef _GRAPHBSP_H_
#include "ai/graphBSP.h"
#endif
#ifndef _BITVECTORW_H_
#include "Core/bitVectorW.h"
#endif
#ifndef _BITVECTOR_H_
#include "Core/bitVector.h"
#endif
#ifndef _BITSET_H_
#include "Core/bitSet.h"
#endif
#ifndef _TERRDATA_H_
#include "terrain/terrData.h"
#endif
enum NavGraphEnum
{ // node types:
NavGraphNotANode = 0,
NavGraphOutdoorNode,
NavGraphIndoorNode,
NavGraphSubmergedNode,
// misc constants:
MaxOnDemandEdges = 13, // for nodes that create edges on demand (ie. grid nodes)
};
// This will be the next version of the of the edge data.
struct GraphEdgeInfo
{
enum
{
Jetting = BIT(0),
Algorithmic = BIT(16),
Inventory = BIT(18)
};
struct OneWay{
BitSet32 flags;
S32 res;
S32 dest;
OneWay() {dest=res=-1;}
bool isJetting() const {return flags.test(Jetting);}
} to[2];
GraphEdgeInfo();
bool isAlgorithmic() const; void setAlgorithmic(bool YN=true);
bool isInventory() const; void setInventory(bool YN = true);
// These three points define the segment between the two nodes. The segPoints
// are the two points, and the normal points in direction from 1st to 2nd node.
Point3F segPoints[2];
Point3F segNormal;
bool read(Stream & s);
bool write(Stream & s) const;
};
struct IndoorNodeInfo
{
enum
{
Algorithmic = BIT(1),
WallNode = BIT(2),
Inventory = BIT(4),
BelowPortal = BIT(5),
Seed = BIT(6)
};
BitSet32 flags;
U16 unused;
S16 antecedent;
Point3F pos;
IndoorNodeInfo();
// Access to booleans-
bool isAlgorithmic() const; void setAlgorithmic(bool YN = true);
bool isWallNode() const; void setWallNode(bool YN = true);
bool isInventory() const; void setInventory(bool YN = true);
bool isBelowPortal() const; void setBelowPortal(bool YN = true);
bool isSeed() const; void setSeed(bool YN = true);
bool read(Stream & s);
bool write(Stream & s) const;
};
// Persisted data for the terrain nodes.
struct OutdoorNodeInfo
{
U8 flags;
S8 level;
U16 height; // Probably can be taken out if we do a big graph re-versioning.
S16 x, y;
OutdoorNodeInfo();
bool read(Stream & s);
bool write(Stream & s) const;
Point2I getPoint() const {return Point2I(x,y);}
S8 getLevel() const {return level;}
};
typedef Vector<IndoorNodeInfo> NodeInfoList;
typedef Vector<GraphEdgeInfo> EdgeInfoList;
class Consolidated : public Vector<OutdoorNodeInfo>
{
public:
bool read(Stream& s) {return readVector1(s, *this);}
bool write(Stream& s) const {return writeVector1(s, *this);}
};
//-------------------------------------------------------------------------------------
struct SpawnLocations : Vector<Point3F>
{
struct Sphere
{
SphereF mSphere;
bool mInside;
S32 mCount;
S32 mOffset;
S32 mRes0;
Sphere(const Point3F& center, F32 radius);
bool read(Stream& s);
bool write(Stream& s) const;
};
typedef Vector<Sphere> Spheres;
S32 mRes0;
Spheres mSpheres;
SpawnLocations();
S32 getRandom(const SphereF&, bool inside, U32 rnd);
void printInfo() const;
void reset();
bool read(Stream& s);
bool write(Stream& s) const;
};
//-------------------------------------------------------------------------------------
// Learn information for potential bridges.
// This is old format now- See *BridgeData* below
struct GraphBridgeInfo
{
enum How {CanWalk=1, MustJet=2}; // ==> Would like to do this: CanFall=4}; !
S32 dstNode;
S32 srcNode;
F32 jetClear;
U8 howTo;
U8 res1, res2, res3;
GraphBridgeInfo(S32 src, S32 dst) {init(src, dst);}
GraphBridgeInfo() {init(-1, -1);}
void init(S32 src, S32 dst);
bool read(Stream& s);
bool write(Stream& s) const;
void setWalkable() {howTo=CanWalk;}
bool isWalkable() {return (howTo & CanWalk);}
bool mustJet() {return !isWalkable();}
};
class BridgeInfoList : public Vector<GraphBridgeInfo>
{
public:
bool read(Stream& s) {return readVector1(s, *this);}
bool write(Stream& s) const {return writeVector1(s, *this);}
};
//-------------------------------------------------------------------------------------
inline U8 mapJetHopToU8(F32 hop) {
return (U8(mFloor((hop + 0.124) * 8.0)));
}
inline F32 mapU8ToJetHop(U8 amt) {
return (F32(amt) * 0.125);
}
struct GraphBridgeData
{
// ==> Would like to have a CanFall field too....
enum How {CanWalk=1, MustJet=2, Replacement=4, Unreachable=8};
U16 nodes[2];
U8 jetClear;
U8 howTo;
GraphBridgeData(U16 src, U16 dst) {init(src, dst);}
GraphBridgeData() {init(-1, -1);}
void init(U16 n0, U16 n1);
bool read(Stream& s);
bool write(Stream& s) const;
void setWalkable() {howTo |= CanWalk;}
void setReplacement() {howTo |= Replacement;}
void setUnreachable() {howTo |= Unreachable;}
void setHop(F32 dist) {jetClear = mapJetHopToU8(dist);}
bool isWalkable() {return (howTo & CanWalk);}
bool isReplacement() {return (howTo & Replacement);}
bool isUnreachable() {return (howTo & Unreachable);}
bool mustJet() {return !isWalkable();}
};
class BridgeDataList : public Vector<GraphBridgeData>
{
S32 mReplaced[2];
S32 mSaveTotal;
public:
BridgeDataList();
S32 replaced() const;
S32 numPositiveBridges() const;
S32 accumEdgeCounts(U16 * edgeCounts);
bool readOld(Stream& s);
bool read(Stream& s);
bool write(Stream& s) const;
};
//-------------------------------------------------------------------------------------
// See graphVolume.cc
struct GraphVolume
{
PlaneF mFloor;
PlaneF mCeiling;
const PlaneF * mPlanes;
S32 mCount;
Vector<Point3F> mCorners;
};
struct GraphVolInfo
{
S32 mPlaneCount;
S32 mPlaneIndex;
bool read(Stream& s) {return s.read(&mPlaneCount) && s.read(&mPlaneIndex);}
bool write(Stream& s) const {return s.write(mPlaneCount) && s.write(mPlaneIndex);}
};
class GraphVolumeList : public Vector<GraphVolInfo>
{
bool intersectWalls(S32 i, const PlaneF& with, Vector<Point3F>& list) const;
PlaneF* getPlaneList(S32 i) {return &mPlanes[(*this)[i].mPlaneIndex];}
public:
Vector<PlaneF> mPlanes;
const PlaneF* planeArray(S32 i) const {return &mPlanes[(*this)[i].mPlaneIndex];}
S32 planeCount(S32 i) const {return this->operator[](i).mPlaneCount;}
PlaneF floorPlane(S32 i) const {return planeArray(i)[planeCount(i)-2];}
PlaneF abovePlane(S32 i) const {return planeArray(i)[planeCount(i)-1];}
S32 memSize() const {return (Vector<GraphVolInfo>::memSize() + mPlanes.memSize());}
F32 getMinExt(S32 i, Vector<Point3F>& ptBuffer);
bool closestPoint(S32 ind, const Point3F& point, Point3F& soln) const;
S32 getCorners(S32 i, Vector<Point3F>& pts, bool noTop=false) const;
void addVolume(Point3F pos, F32 boxw, F32 boxh);
void cullUnusedPlanes();
void nudgeVolumesOut();
bool read(Stream& s);
bool write(Stream& s) const;
};
//-------------------------------------------------------------------------------------
struct ChuteHint : public Point3F
{
bool read(Stream& s) {return s.read(&x) && s.read(&y) && s.read(&z);}
bool write(Stream& s) const {return s.write(x) && s.write(y) && s.write(z);}
const Point3F& location() {return *this;} // for BSP-er
};
typedef AxisAlignedBSP<ChuteHint> ChuteBSP;
typedef VectorPtr<ChuteHint*> ChutePtrList;
class ChuteHints : public Vector<ChuteHint>
{
ChuteBSP mBSP;
ChutePtrList mQuery;
void makeBSP();
public:
enum Info {NotAChute, ChuteTop, ChuteMid};
S32 findNear(const Point3F& P, S32 xy, S32 z, ChutePtrList& list) const;
S32 findNear(const Box3F& box, ChutePtrList& list) const;
void init(const Vector<Point3F>& list);
Info info(Point3F bot, const Point3F& top);
bool read(Stream& s);
bool write(Stream& s) const;
};
//-------------------------------------------------------------------------------------
struct PathXRefEntry : public BitVectorW
{
bool read(Stream& s);
bool write(Stream& s) const;
};
class PathXRefTable : public Vector<PathXRefEntry>
{
public:
~PathXRefTable();
bool read(Stream& s);
bool write(Stream& s) const;
void setSize(S32 size);
void clear();
};
//-------------------------------------------------------------------------------------
class LOSTable
{
public:
enum TwoBitCodes {Hidden, MinorLOS, MuzzleLOS, FullLOS};
virtual U32 value(S32 ind1, S32 ind2) const = 0;
virtual bool valid(S32 numNodes) const = 0;
virtual bool write(Stream& s) const = 0;
virtual bool read(Stream& s) = 0;
virtual void clear() = 0;
bool hidden(S32 i1, S32 i2) const { return (value(i1, i2) == Hidden); }
bool fullLOS(S32 i1, S32 i2) const { return (value(i1, i2) == FullLOS); }
bool muzzleLOS(S32 i1, S32 i2) const { return (value(i1, i2) >= MuzzleLOS); }
};
//-------------------------------------------------------------------------------------
class LOSXRefTable : public BitVectorW, public LOSTable
{
public:
void setEntry(S32 ind1, S32 ind2, U32 val);
U32 value(S32 ind1, S32 ind2) const;
bool valid(S32 numNodes) const;
void clear();
bool read(Stream& s);
bool write(Stream& s) const;
};
//-------------------------------------------------------------------------------------
class LOSHashTable : public LOSTable
{
public:
enum{ SegShift = 6, // seems to be the magic #
SegSize = (1 << SegShift),
SegMask = (SegSize - 1),
SegAlloc = (1 << SegShift - 2)
};
protected:
union Key {
struct {
U16 mNode, mSeg;
} mKey;
U32 mCompare;
Key(U16 node = 0, U16 seg = 0) {mKey.mNode = node; mKey.mSeg = seg;}
};
struct Segment {
U8 mLOS[SegAlloc];
Key mKey;
Segment(Key key, const U32 losInfo[SegSize]);
U32 value(U32 i) const {return 3 & mLOS[i >> 2] >> ((i & 3) << 1);}
bool read(Stream& s);
bool write(Stream& s) const;
};
typedef Vector<Segment> SegmentList;
typedef U16 IndexType;
protected:
static U32 mTabSz;
IndexType * mTable;
SegmentList mSegments;
U32 mNumNodes;
U32 mRes0, mRes1;
protected:
static U32 calcHash(Key key);
static S32 QSORT_CALLBACK cmpHashes(const void* ,const void* );
U32 calcTabSize();
void sortByHashVal();
U32 makeTheTable();
public:
LOSHashTable();
~LOSHashTable();
U32 convertTable(const LOSXRefTable& from, S32 numNodes);
U32 value(S32 ind1, S32 ind2) const;
bool valid(S32 numNodes) const;
bool write(Stream& s) const;
bool read(Stream& s);
void clear();
};
//-------------------------------------------------------------------------------------
// Data saved on graph object in MIS file-
// Configures how nodes are consolidated.
struct ConjoinConfig
{
ConjoinConfig();
F32 maxAngleDev;
F32 maxBowlDev;
S32 maxLevel;
};
//-------------------------------------------------------------------------------------
// Consolidation data structures. Used in computing, not persisted.
struct GridNormalInfo
{
bool notEmpty;
bool hasSteep;
VectorF normals[2];
Point2F angles[2];
GridNormalInfo();
static Point2F normalToAngle(const VectorF& N);
static VectorF angleToNormal(const Point2F& A);
};
#define MaxConsolidateLevel 6
class TrackLevels
{
Vector<U16> achievedLevels;
Vector<U8> nodeTypes;
public:
TrackLevels(S32 sz);
void setAchievedLevel(S32 index, S32 level);
S32 getAchievedLevel(S32 index) const;
S32 originalNodeLevel(S32 idx) const;
U16 getNodeType(S32 index) const;
void setNodeType(S32 index, U8 nodeType);
S32 size() const;
void capLevelAt(S32 idx, U16 lev);
void init(S32 size);
};
//-------------------------------------------------------------------------------------
struct TerrainGraphInfo
{
static const Point2I gridOffs[8];
static S32 smVersion;
// false until data is set or loaded:
bool haveGraph;
// persisted data:
S32 nodeCount;
S32 numShadows;
Point3F originWorld;
Point2I originGrid;
Point2I gridDimensions;
Point2I gridTopRight;
Vector<U8> navigableFlags;
Vector<U8> neighborFlags;
Vector<F32> shadowHeights;
Vector<U16> roamRadii;
Consolidated consolidated;
// Data computed from the above at load/initialize:
LineSegment boundarySegs[4];
S32 indOffs[8];
GridArea gridArea;
TerrainGraphInfo();
// small utility functions.
Point2I& indexToPos(S32 index) const;
Point3F* indexToLoc(Point3F& locOut, S32 index);
bool posToLoc(Point3F& locOut, const Point2I& p);
S32 posToIndex(Point2I pos) const;
S32 locToIndex(Point3F loc) const;
S32 locToIndexAndSphere(SphereF& s, const Point3F& L);
bool obstructed(const Point3F& L) const;
U8 squareType(S32 n) const {return (navigableFlags[n] & 7);}
bool obstructed(S32 n) const {return squareType(n)==GraphNodeObstructed;}
bool shadowed(S32 n) const {return squareType(n)==GraphNodeShadowed;}
bool submerged(S32 n) const {return squareType(n)==GraphNodeSubmerged;}
bool haveConsData() const {return consolidated.size() > 0;}
F32 shadowHeight(S32 n) const {return shadowHeights[n];}
bool inGraphArea(const Point3F& loc) const {return locToIndex(loc) >= 0;}
bool steep(S32 n) const {return navigableFlags[n] & GroundNodeSteep;}
void setSteep(S32 n) {navigableFlags[n] |= GroundNodeSteep;}
// not-as-small utility functions
// S32 needFloorPlanMojo(Vector<Point2I>& listOut, TerrainBlock* terr);
Point3F whereToInbound(const Point3F& loc);
F32 checkOpenTerrain(const Point3F& from, Point3F& to);
U32 onSideOfArea(S32 index);
// persist
bool read(Stream & s);
bool write(Stream & s) const;
// Calculation stuff (consolidation), precomputes, setup..
bool buildConsolidated(const TrackLevels & trackInf, const ConjoinConfig & config);
bool consolidateData(const ConjoinConfig & config);
S32 markNodesNearSteep();
void computeRoamRadii();
void getGridNormals(Vector<GridNormalInfo>&);
void setSideSegs();
// After new data is created or loaded, this doest last needed calcs, fixups, etc.
void doFinalDataSetup();
};
#endif

117
ai/graphDebug.cc Normal file
View file

@ -0,0 +1,117 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
static void cantFind(const Point3F& P)
{
Con::printf("Can't find node near (%f %f %f)", P.x, P.y, P.z);
}
// See if the given player can get from point A to point B.
bool NavigationGraph::testPlayerCanReach(Point3F A, Point3F B, const F32* ratings)
{
Vector<S32> indexList;
bool success = false;
GraphSearch * searcher = getMainSearcher();
if (GraphNode * src = closestNode(A))
{
if (GraphNode * dst = closestNode(B))
{
searcher->setAStar(true);
searcher->performSearch(src, dst);
searcher->setRatings(ratings);
success = searcher->getPathIndices(indexList);
}
else
cantFind(B);
}
else
cantFind(A);
return success;
}
//-------------------------------------------------------------------------------------
F32 NavigationGraph::performTests(S32 count, bool enableAStar)
{
// Seed off count - so test happens same for same count.
MRandomR250 rand(count);
S64 dijkstraIters = 0;
S64 relaxCount = 0;
U32 numSearchesDone = 0;
Vector<S32> indexList;
// Get our searcher-
GraphSearch * searcher = getMainSearcher();
// Perform random path searches. numNodes() ignores transients.
while (--count >= 0)
{
if (GraphNode * src = mNodeList[(rand.randI()&0xFFFFFFF) % numNodes()])
{
if (GraphNode * dst = mNodeList[(rand.randI()&0xFFFFFFF) % numNodes()])
{
numSearchesDone++;
searcher->setAStar(enableAStar);
dijkstraIters += searcher->performSearch(src, dst);
relaxCount += searcher->relaxCount();
searcher->getPathIndices(indexList);
}
}
}
if (numSearchesDone)
{
if (relaxCount)
{
F64 relaxAverage = F64(relaxCount) / F64(numSearchesDone);
Con::printf("Average edge relaxations = %f", F32(relaxAverage));
}
return F32(F64(dijkstraIters) / F64(numSearchesDone));
}
return 0;
}
//-------------------------------------------------------------------------------------
// Flag (for render) those nodes having LOS from the given closest point. We check
// in and out radii, and the condition is what we compare to. Used for testing.
void NavigationGraph::markNodesInSight(const Point3F& from, F32 in, F32 out, U32 cond)
{
if (!validLOSXref())
{
warning("markNodesInSight() called without valid XRef table");
return;
}
if (GraphNode * src = closestNode(from))
{
S32 srcInd = src->getIndex();
for (S32 i = 0; i < mNonTransient.size(); i++)
{
GraphNode * cur = mNonTransient[i];
F32 dist = (from - cur->location()).len();
S32 curInd = cur->getIndex();
bool markIt = false;
if (dist > in && dist < out && curInd != srcInd)
markIt = (mLOSTable->value(srcInd, curInd) == cond);
if (markIt)
cur->set(GraphNode::Render0);
else
cur->clear(GraphNode::Render0);
}
}
else
warning("markNodesInSight() couldn't find closest node");
}

102
ai/graphDefines.h Normal file
View file

@ -0,0 +1,102 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHDEFINES_H_
#define _GRAPHDEFINES_H_
enum GridOffsets {
GridBottomLeft,
GridBottom, GridLeft,
GridBottomRight, GridTopLeft,
GridRight, GridTop,
GridTopRight,
NumGridOffsets
};
enum NavGraphDefinitions {
GraphNodeClear = 0, // Navigable flags on the terrain grid information.
GraphNodeShadowed, // We're using 4, allow up to 8 types.
GraphNodeObstructed,
GraphNodeSubmerged,
GroundNodeSteep = (1 << 3), // Signals next to unwalkable slope.
GraphThreatLimit = 64,
GraphMaxTeams = 32,
AbsMaxBotCount = 64,
};
// 2.5 minutes-
#define GraphMaxNodeAvoidMS 150000
// Pull-in amount used to avoid rounding errors in indoor node volume obstuction checks
#define NodeVolumeShrink 0.014f
#define UncappedNodeVolumeHt 1000.0f
// Bridge builder assures jet connections at least a certain amount. Jet code uses a little
// smaller threshold (for not attempting) since bots might not be right on point-
#define GraphJetBridgeXY 1.7f
#define GraphJetFailXY 1.3f
#define MaxGraphNodeVolRad 25.0f
#define LiberalBonkXY 21.0f
// Repository for all graph dimension numbers.
struct NavGraphGlobals
{
// Terrain:
F32 mSquareWidth;
F32 mInverseWidth;
F32 mSquareRadius;
S32 mSquareShift;
Point3F mHalfSquare;
void setTerrNumbers(S32 shift)
{
mSquareShift = shift;
mSquareWidth = F32(1 << shift);
mInverseWidth = 1.0 / mSquareWidth;
mSquareRadius = F32(1 << shift-1);
mHalfSquare.set(mSquareRadius - 0.01, mSquareRadius - 0.01, 0.0);
}
// Walk / jet / jump configuration numbers
F32 mWalkableDot;
F32 mWalkableAngle;
F32 mJumpAdd;
F32 mTallestPlayer;
F32 mStepHeight;
F32 mPrettySteepAngle;
F32 mPrettySteepDot;
void setWalkData(F32 angle, F32 stepHeight)
{
mWalkableAngle = mDegToRad(angle);
mWalkableDot = mCos(mWalkableAngle);
mJumpAdd = 2.0;
mTallestPlayer = 2.3;
mStepHeight = stepHeight;
// Angle slightly less than max used to decide to make a transient connection
// jettable between two points outdoors. cf. graphLocate.cc
mPrettySteepAngle = mWalkableAngle * (6.0 / 7.0);
mPrettySteepDot = mCos(mPrettySteepAngle);
}
// Build a default version -
NavGraphGlobals()
{
setTerrNumbers(3); // Default shift value
setWalkData(70, 0.75); // Walk angle (min of all maxes), step height
}
};
// Defined in NavigationGraph.cc-
extern NavGraphGlobals gNavGlobs;
#endif

377
ai/graphDijkstra.cc Normal file
View file

@ -0,0 +1,377 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
// Two special back-index entries are defined for if element has been extracted or
// has not been in the queue yet. DefNotInQueue uses -1 so we can memset() array.
#define DefExtracted (1<<30)
#define DefNotInQueue GraphQIndex(-1)
#define MaskExtracted (DefExtracted-1)
#define NodeExtracted(x) (((x) & DefExtracted) && (x) > 0)
#define FlagExtracted(x) ((x)|= DefExtracted)
#define NotInQueue(x) ((x)==DefNotInQueue)
#define DoDiagnostic(x) {x;}
// The Q and Q indices are shared among all searchers. They're kept relatively private
// by using a GraphSearch subclass which is only friends with GraphSearch.
//
GraphSearch::GraphSearch()
: mHead(-1, F32(-1), F32(-1)),
mQueue(gNavGraph->mSharedSearchLists.searchQ),
mQIndices(gNavGraph->mSharedSearchLists.qIndices),
mHeuristicsVec(gNavGraph->mSharedSearchLists.heuristics),
mPartition(gNavGraph->mSharedSearchLists.partition)
{
mHead.mTime = -1;
mExtractNode = NULL;
mVisitOnExtract = true;
mVisitOnRelax = true;
mEarlyOut = true;
mSearchDist = 0.0f;
mTargetNode = -1;
mSourceNode = -1;
mIterations = 0;
mRelaxCount = 0;
mThreatSet = 0;
mInformThreats = 0;
mInformTeam = 0;
mInformRatings = NULL;
mInProgress = false;
mAStar = false;
mTargetLoc.set(0,0,0);
initializeIndices();
mHeuristicsPtr = NULL;
mRandomize = false;
}
bool GraphSearch::initializeIndices()
{
if (NavigationGraph::gotOneWeCanUse())
{
if (gNavGraph->numNodesAll() != mQIndices.size())
{
S32 totalNodes = gNavGraph->numNodesAll();
AssertFatal(totalNodes < (1 << 15), "Graph size can't exceed 32K");
mHeuristicsVec.setSize(totalNodes);
mQIndices.setSize(totalNodes);
mPartition.setSize(totalNodes);
// Largest search includes just two transients
mQueue.reserve(gNavGraph->numNodes() + 2);
doLargeReset();
return true;
}
}
return false;
}
void GraphSearch::doSmallReset()
{
mPartition.clear();
#if 1
register S32 i = mQueue.size();
register SearchRef * searchRef = &mQueue[0];
while (--i >= 0) {
mQIndices[searchRef->mIndex] = DefNotInQueue;
mHeuristicsVec[ (searchRef++)->mIndex ] = 0;
}
#else
for (S32 i = mQueue.size() - 1; i >= 0; i--) {
S32 whichNode = mQueue[i].mIndex;
mQIndices[whichNode] = DefNotInQueue;
mHeuristicsVec[whichNode] = 0;
}
#endif
}
void GraphSearch::doLargeReset()
{
dMemset(mQIndices.address(), 0xff, mQIndices.memSize());
dMemset(mHeuristicsVec.address(), 0, mHeuristicsVec.memSize());
mPartition.clear();
}
void GraphSearch::resetIndices()
{
if (! initializeIndices()) {
if (mQueue.size()) { // Compare size of search queue to total node count.
S32 ratio = mQIndices.size() / mQueue.size();
if (ratio > 7) {
doSmallReset();
return;
}
}
//==> Note could queue size sometimes be clear and the following would happen?
doLargeReset();
}
}
// Mainly for access to accumulated userdata on the nodes-
SearchRef* GraphSearch::getSearchRef(S32 nodeIndex)
{
S32 queueInd = mQIndices[nodeIndex];
AssertFatal(!NotInQueue(queueInd), "getSearchRef() called with non-queued node");
return &mQueue[queueInd & MaskExtracted];
}
// This version is just a straight lookup using a Q index.
SearchRef* GraphSearch::lookupSearchRef(S32 queueIndex)
{
return &mQueue[queueIndex & MaskExtracted];
}
inline SearchRef* GraphSearch::insertSearchRef(S32 nodeIndex, F32 dist, F32 sort)
{
S32 vecIndex = mQueue.size();
mQIndices[nodeIndex] = vecIndex;
SearchRef assemble(nodeIndex, dist, sort);
mQueue.insert(assemble);
return &mQueue[vecIndex];
}
// Called when randomizing to get edge scale factor. Turn off randomizing after a point
// to limit the slowdown caused by the (sometimes much) larger search expansion.
S32 GraphSearch::getAvoidFactor()
{
if (mIterations > 80)
mRandomize = false;
else {
// Note unsigned compare important -
U32 timeDiff = (mExtractNode->avoidUntil() - mCurrentSimTime);
if (timeDiff < GraphMaxNodeAvoidMS)
if (mExtractNode->stuckAvoid())
return 400;
else
return 25;
}
return 0;
}
F32 GraphSearch::calcHeuristic(const GraphEdge* edge)
{
F32 dist = mHeuristicsPtr[edge->mDest];
if (dist == 0)
{
#if 0
GraphNode * destNode = gNavGraph->lookupNode(edge->mDest);
dist = (destNode->location() - mTargetLoc).len();
#else
// ==> Want to try a crude distance function here to avoid square root.
RegularNode * destNode = static_cast<RegularNode *>(gNavGraph->lookupNode(edge->mDest));
dist = (destNode->mLoc - mTargetLoc).len();
#endif
mHeuristicsPtr[edge->mDest] = dist;
}
return dist;
}
// Compute new time along the edge. This is virtual since special searches
// want to play with it (cf. dist-only based searches, LOS avoid searches).
F32 GraphSearch::getEdgeTime(const GraphEdge* edge)
{
F32 edgeTime = (edge->mDist * edge->getInverse());
AssertFatal(!edge->problems(), edge->problems());
// Weight for jetting capabilities- edges that can't be traversed. Don't do it with
// transient connections (partition predictions must work out true).
if (mJetRatings && edge->isJetting() && edge->mDest < mTransientStart)
if (!edge->canJet(mJetRatings))
return SearchFailureAssure;
// Edges that only one team can go through, namely (working) force fields-
if (mTeam && edge->getTeam())
if (edge->getTeam() != mTeam)
return SearchFailureAssure;
else
edgeTime *= 1.3;
return edgeTime;
}
bool GraphSearch::runDijkstra()
{
mCurrentSimTime = Sim::getCurrentTime();
while (SearchRef * head = mQueue.head())
{
// Make COPY of head since list can move! Also, 'visitors' make use of the info.
mHead = * head;
mQueue.removeHead();
// This means search failed
if (mHead.mTime > SearchFailureThresh)
break;
// Mark visited-
mPartition.set(mHead.mIndex);
// Check if done-
if (mTargetNode == mHead.mIndex) {
mSearchDist = mHead.mDist;
break;
}
// set the extracted node. visitor may use it.
mExtractNode = gNavGraph->lookupNode(mHead.mIndex);
// callback visit - subclass uses extractedNode() to access current
if (mVisitOnExtract)
onQExtraction();
// Avoidance of nodes for randomize or for stuckness.
S32 avoidThisNode = (mRandomize ? getAvoidFactor() : false);
// Check for threat avoidance.
bool nodeThreatened = (mThreatSet && (mExtractNode->threats() & mThreatSet));
// Mark this node as extracted, remove from Queue. Do after onQExtraction().
GraphQIndex headIndex = mQIndices[mHead.mIndex];
FlagExtracted(mQIndices[mHead.mIndex]);
// Relax all neighbors (or add to queue in first place)
GraphEdgeArray edgeList = mExtractNode->getEdges(mEdgeBuffer);
while (GraphEdge * edge = edgeList++)
{
GraphQIndex queueInd = mQIndices[edge->mDest];
register SearchRef * searchRef;
if (! NodeExtracted(queueInd))
{
F32 newDist = mHead.mDist + edge->mDist;
F32 edgeTime = getEdgeTime(edge);
if (nodeThreatened)
edgeTime *= 10;
if (avoidThisNode)
edgeTime += avoidThisNode;
F32 newTime = mHead.mTime + edgeTime;
F32 sortOnThis = (mAStar ? newTime + calcHeuristic(edge) : newTime);
// Relax dist for neighbor (1st time is a "relax" down from infinity)
if (NotInQueue(queueInd)) {
searchRef = insertSearchRef(edge->mDest, newDist, sortOnThis);
searchRef->mPrev = headIndex;
searchRef->mTime = newTime;
}
else if (sortOnThis < (searchRef = &mQueue[queueInd])->mSort) {
searchRef->mTime = newTime;
searchRef->mSort = sortOnThis;
searchRef->mDist = newDist;
searchRef->mPrev = headIndex;
mQueue.changeKey(queueInd);
}
else
continue; // didn't relax - must skip relax callback
DoDiagnostic(mRelaxCount++);
if (mVisitOnRelax)
onRelaxEdge(edge);
}
}
mIterations++;
if (mEarlyOut && earlyOut())
return (mInProgress = true);
}
return (mInProgress = false);
}
void GraphSearch::initSearch(GraphNode * S, GraphNode * D)
{
mIterations = 0;
mRelaxCount = 0;
mInProgress = true;
mSearchDist = 0.0f;
mTransientStart = gNavGraph->numNodes();
mSourceNode = S->getIndex();
// Set up target if present. A* requires target.
if (D)
mTargetNode = D->getIndex(), mTargetLoc = D->location();
else
mTargetNode = -1, mInformAStar = false;
// These four search-modifying variables hold their value for only one search-
mAStar = mInformAStar; /*---------*/ mInformAStar = false;
mThreatSet = mInformThreats; /*---------*/ mInformThreats = 0;
mTeam = mInformTeam; /*---------*/ mInformTeam = 0;
mJetRatings = mInformRatings; /*---------*/ mInformRatings = NULL;
// Avoid bad inlining in debug build...
mHeuristicsPtr = mHeuristicsVec.address();
// Cleanup from last search-
resetIndices();
// Set source as first element in Q-
mQueue.clear();
insertSearchRef(S->getIndex(), 0, 0)->mTime = 0;
mQueue.buildHeap();
}
//-------------------------------------------------------------------------------------
// Find list of node indices on search that just happened. If search had target start
// back from there, else use parameter (custom searches). Return if target reached.
bool GraphSearch::getPathIndices(Vector<S32> & indices, S32 target)
{
indices.clear();
if(target < 0)
target = mTargetNode;
AssertFatal(target >= 0, "Bad use of getPathIndices");
if (mPartition.test(target))
{
// Get the list of node indices going back from target to source. Since these are
// queue indices, 0 is start (source node is first in Q), hence the while (prev).
S32 prev = (mQIndices[target] & MaskExtracted);
if (prev < mQIndices.size())
{
while (prev)
{
indices.push_back(mQueue[prev].mIndex);
prev = mQueue[prev].mPrev;
}
indices.push_back(mSourceNode);
reverseVec(indices);
return true;
}
}
return false;
}
// D can be NULL (used for visitation expansions).
S32 GraphSearch::performSearch(GraphNode * S, GraphNode * D)
{
if( D && D->island() != S->island() )
return -1;
initSearch(S, D);
runDijkstra();
return mIterations;
}
// Search that can be interupted. Returns true while in progress. User is responsible
// for not calling it again if the search has finished. cf. earlyOut() virtual.
bool GraphSearch::runSearch(GraphNode * S, GraphNode * D)
{
if (D && D->island() != S->island())
return false;
if (!mInProgress)
initSearch(S, D);
return runDijkstra();
}

562
ai/graphFind.cc Normal file
View file

@ -0,0 +1,562 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
U32 gCallsToClosestNode = 0;
//-------------------------------------------------------------------------------------
bool NavigationGraph::possibleToJet(const Point3F& from, const Point3F& to, U32)
{
// To be refined-
return (from - to).len() < 60;
}
//-------------------------------------------------------------------------------------
// This routine is used by the seed scattering approach - generate points that are
// inside nearby volumes, reflecting across one of the walls.
S32 NavigationGraph::crossNearbyVolumes(const Point3F& from, Vector<Point3F>& points)
{
Point3F size(20, 20, 20);
Box3F checkBox(from, from);
checkBox.min -= size;
checkBox.max += size;
GraphNodeList nodeList;
points.clear();
if (S32 N = getNodesInBox(checkBox, nodeList, true))
{
// Find ones we're outside of a little ways - i.e. positive containment, but not
// too low a number. The caller will do LOS checks... Also, we verify that we're
// below the cieling as well.
for (S32 i = 0; i < N; i++)
{
GraphNode * node = nodeList[i];
NodeProximity prox = node->containment(from);
// Make sure we're somewhat near. Note Z check needs to come last here...
if ((F32(prox) > 0.1) && (F32(prox) < 6.0) && prox.insideZ())
{
// It's a candidate. Now find closest point on volume....
Point3F soln;
if (closestPointOnVol(node, from, soln))
{
// ...and reflect across point..
VectorF inVec(soln.x - from.x, soln.y - from.y, 0.0);
inVec.normalize(0.8);
soln += inVec;
soln.z = solveForZ(getFloorPlane(node), soln) + 0.2;
// See if that point is reasonably well inside the volume...
if (node->containment(soln) < -0.3)
points.push_back(soln);
}
}
}
}
return points.size();
}
//-------------------------------------------------------------------------------------
S32 NavigationGraph::getNodesInBox(Box3F worldBox, GraphNodeList& listOut, bool justIndoor)
{
// Box is assumed to be square in XY, get "radius"
Point3F center;
worldBox.getCenter(&center);
listOut.clear();
if (!justIndoor && haveTerrain())
{
// Fetch terrain nodes-
F32 rad = worldBox.len_x() * 0.5;
S32 gridRadius = S32(rad / gNavGlobs.mSquareWidth) + 1;
GridArea gridArea = getGridRectangle(center, gridRadius);
getNodesInArea(listOut, gridArea);
}
// Fetch from the indoor node BSP (this one doesn't clear the list)
Point3F radExt(MaxGraphNodeVolRad, MaxGraphNodeVolRad, MaxGraphNodeVolRad);
worldBox.min -= radExt;
worldBox.max += radExt;
mIndoorTree.getIntersecting(listOut, worldBox);
return listOut.size();
}
bool NavigationGraph::haveMuzzleLOS(S32 nodeInd1, S32 nodeInd2)
{
if (nodeInd1 != nodeInd2 && mValidLOSTable)
return mLOSTable->muzzleLOS(nodeInd1, nodeInd2);
else
return true;
}
//-------------------------------------------------------------------------------------
const GraphNodeList& NavigationGraph::getVisibleNodes(GraphNode * from, const Point3F& loc, F32 rad)
{
mUtilityNodeList1.clear();
mUtilityNodeList2.clear();
if (from)
{
SphereF sphere(loc, rad);
mUtilityNodeList1.clear();
if (haveTerrain())
{
// Fetch terrain.
S32 gridRadius = S32(rad / gNavGlobs.mSquareWidth) + 1;
GridArea gridArea = getGridRectangle(loc, gridRadius);
getNodesInArea(mUtilityNodeList1, gridArea);
}
// Fetch from the indoor node BSP (this one doesn't clear the list)
Box3F checkBox(loc, loc, true);
rad += MaxGraphNodeVolRad;
Point3F radExt(rad, rad, rad);
checkBox.min -= radExt;
checkBox.max += radExt;
mIndoorTree.getIntersecting(mUtilityNodeList1, checkBox);
S32 fromInd = from->getIndex();
for (S32 i = 0; i < mUtilityNodeList1.size(); i++)
if (haveMuzzleLOS(fromInd, mUtilityNodeList1[i]->getIndex()))
mUtilityNodeList2.push_back(mUtilityNodeList1[i]);
}
return mUtilityNodeList2;
}
const GraphNodeList& NavigationGraph::getVisibleNodes(const Point3F& loc, F32 rad)
{
FindGraphNode finder(loc);
return getVisibleNodes(finder.closest(), loc, rad);
}
//-------------------------------------------------------------------------------------
#define CrossingSegAdjustUp 0.3
// Figure out what constitutes a crossing path.
S32 NavigationGraph::crossingSegs(const GraphNode * node, const GraphEdge * edge,
LineSegment * const segBuffer)
{
LineSegment * segs = segBuffer;
Point3F nodeLoc = node->location();
Point3F destLoc = lookupNode(edge->mDest)->location();
nodeLoc.z += CrossingSegAdjustUp;
destLoc.z += CrossingSegAdjustUp;
if (edge->isBorder()) {
Point3F crossingPt = getBoundary(edge->mBorder).midpoint();
crossingPt.z += CrossingSegAdjustUp;
(segs++)->set(nodeLoc, crossingPt);
(segs++)->set(crossingPt, destLoc);
}
else {
// Do non-jetting like jetting as well - otherwise we miss collisions.
// if (edge->isJetting()) {
Point3F arcCorner;
if (nodeLoc.z > destLoc.z)
(arcCorner = destLoc).z = nodeLoc.z;
else
(arcCorner = nodeLoc).z = destLoc.z;
(segs++)->set(nodeLoc, arcCorner);
(segs++)->set(arcCorner, destLoc);
}
return (segs - segBuffer);
}
// Find all edges that are blocked by the given object. This is used by the force field
// monitor code, called at mission start to find affected edges.
const GraphEdgePtrs& NavigationGraph::getBlockedEdges(GameBase* object, U32 typeMask)
{
Box3F objBox = object->getWorldBox();
GraphEdge edgeBuffer[MaxOnDemandEdges];
LineSegment segments[4];
RayInfo collision;
GraphNodeList consider;
mVisibleEdges.clear();
if (S32 numNodes = getNodesInBox(objBox, consider)) {
for (S32 i = 0; i < numNodes; i++) {
GraphNode * node = consider[i];
GraphEdgeArray edges = node->getEdges(edgeBuffer);
while (GraphEdge * edge = edges++)
for (S32 j = crossingSegs(node, edge, segments) - 1; j >= 0; j--) {
Point3F A = segments[j].getEnd(0);
Point3F B = segments[j].getEnd(1);
// Quick crude test-
if (objBox.collideLine(A, B))
// Now do LOS, must hit object in question. We must be careful
// that force fields don't overlap in navigable area.
if (gServerContainer.castRay(A, B, typeMask, &collision))
if (collision.object == object) {
mVisibleEdges.push_back(edge);
break;
}
}
}
}
// mVisibleEdges is a generic utility buffer for several queries, user copies off
return mVisibleEdges;
}
//-------------------------------------------------------------------------------------
// For an outdoor node location, fetch the roaming radius off the terrain graph
// information. Return 0.0 if nothing found.
F32 NavigationGraph::getRoamRadius(const Point3F &loc)
{
SphereF sphere;
Point3F loc2D(loc.x, loc.y, 0.0f);
if (mTerrainInfo.inGraphArea(loc2D))
if (mTerrainInfo.locToIndexAndSphere(sphere, loc2D) >= 0)
return sphere.radius;
return 0.0f;
}
//-------------------------------------------------------------------------------------
// Patch function to translate into block space. 10/27/00- added optional normal
bool NavigationGraph::terrainHeight(Point3F pos, F32 * height, Point3F * normal)
{
pos -= mTerrainInfo.originWorld;
if (mTerrainBlock) {
Point2F point2F(pos.x, pos.y);
if (mTerrainBlock->getHeight(point2F, height)) {
if (normal != NULL)
mTerrainBlock->getNormal(point2F, normal, true);
return true;
}
}
return false;
}
// Look for terrain node here using grid lookup.
GraphNode * NavigationGraph::findTerrainNode(const Point3F & atLocation)
{
Point3F loc;
bool inArea;
if (haveTerrain()) {
// Bots can enter outside of area- we don't do height check below for such locs.
if (mTerrainInfo.inGraphArea(atLocation)) {
inArea = true;
loc = atLocation;
}
else {
inArea = false;
loc = mTerrainInfo.whereToInbound(atLocation);
}
GraphNode * node = NULL;
S32 gridIndex = mTerrainInfo.locToIndex(loc);
if (gridIndex >= 0) {
if (GraphNode * node = mNodeGrid[gridIndex]) {
if (inArea) {
F32 height;
if (terrainHeight(loc, &height))
{
if (height < (loc.z + 0.04))
{
// Point3F terrLoc = node->location();
// F32 nodeZ = terrLoc.z;
F32 terrHt = node->terrHeight();
if (loc.z < (height + terrHt))
return node;
}
}
}
else {
return node;
}
}
}
}
return NULL;
}
// If the findTerrainNode() fails, we see if we're on terrain with a
// node that is within one grid location.
GraphNode * NavigationGraph::nearbyTerrainNode(const Point3F& loc)
{
GraphNode * node = NULL;
if (haveTerrain())
{
F32 H;
S32 gridIndex = mTerrainInfo.locToIndex(loc);
if( (gridIndex >= 0) && terrainHeight(loc, &H) && (H < loc.z + 0.04) )
{
// look for an immediately neighboring node - find the nearest one
S32 *offs = mTerrainInfo.indOffs, i, n;
F32 bestDist = 1e9, dSq;
for (i = 0; i < 8; i++)
if (validArrayIndex(n = gridIndex + offs[i], mTerrainInfo.nodeCount))
if (GraphNode* N = mNodeGrid[n])
if ( (dSq = (loc - N->location()).lenSquared()) < bestDist )
bestDist = dSq, node = N;
}
}
return node;
}
GraphNode * NavigationGraph::closestNode(const Point3F& loc, F32 * containment)
{
GraphNode * terrNode = findTerrainNode(loc);
gCallsToClosestNode++;
if (terrNode)
return terrNode;
else
{
terrNode = nearbyTerrainNode(loc);
Box3F box(loc, loc, true);
Point3F boundUp(0, 0, 20);
Point3F boundDown(0, 0, 80);
Point3F boundOut(35, 35, 0);
box.max += boundUp;
box.max += boundOut;
box.min -= boundDown;
box.min -= boundOut;
GraphNodeList indoor;
// We really need to use two trees - one for big nodes, and one for smaller.
// This will keep the searches much more intelligent- oversize only a little
// for the tree with small nodes - and so we'll not get so many. Oversize
// a lot for the big nodes - but there aren't nearly as many of them anyway.
mIndoorTree.getIntersecting(indoor, box);
// Find the node with the best containment metric
GraphNode * bestNode = NULL;
F32 bestMetric = 1e13;
F32 metric;
for (GraphNodeList::iterator i = indoor.begin(); i != indoor.end(); i++) {
metric = F32((*i)->containment(loc));
if (metric < bestMetric) {
bestMetric = metric;
bestNode = *i;
}
}
if (! bestNode)
return terrNode;
else {
if (terrNode) {
//==> Move this to a terrain containment function -
F32 terrMetric = (terrNode->location() - loc).len() - terrNode->radius();
if (terrMetric < bestMetric)
return terrNode;
}
if (containment)
*containment = bestMetric;
return bestNode;
}
}
}
//-------------------------------------------------------------------------------------
// Structure which remembers results of searches, and which are also kept in a hash
// table as well. Shows up in profiles now that we use canReachLoc() a lot in the
// objective weighting.
FindGraphNode::FindGraphNode()
{
init();
}
// Manage loc-to-node searches since many are the same.
FindGraphNode::FindGraphNode(const Point3F& pt, GraphNode* hint)
{
init();
setPoint(pt, hint);
}
void FindGraphNode::init()
{
mClosest = NULL;
// Should be safe value that no-one will use:
mPoint.set(-3.14159e14, 3.1e13, -2.22e22);
}
U32 FindGraphNode::calcHash(const Point3F& point)
{
register const U32 * hashNums = (const U32 *)(&point);
U32 val0 = (hashNums[0] >> 7) ^ (hashNums[0] << 5);
U32 val1 = (hashNums[1] + 77773) & 0xFFFFFFF;
U32 val2 = (hashNums[2] * 37) & 0xFFFFFFF;
return (val0 + val1 + val2) % HashTableSize;
}
void FindGraphNode::setPoint(const Point3F& point, GraphNode * hint/*=0*/)
{
// If point has not changed- we're done.
if (point == mPoint)
return;
// Hash point into our cache (which NavigationGraph holds for us)-
mPoint = point;
U32 hash = calcHash(mPoint);
FindGraphNode & cacheEntry = gNavGraph->mFoundNodes[hash];
// See if hash table contains it.
if (cacheEntry.mPoint == point)
{
mClosest = cacheEntry.mClosest;
}
else
{
// If there's a hint - see if we're inside it - that is then the closest. Don't
// put these in the hash table though since the hints usually refer to locations
// that are moving, and we don't want them walking over our cache.
if (hint && hint->containment(point) < 0)
{
mClosest = hint;
}
else
{
// Otherwise do the search-
mClosest = gNavGraph->closestNode(point);
// Put into cache. We do this even for NULL results.
cacheEntry = * this;
}
}
}
//-------------------------------------------------------------------------------------
// Grid <-> World Translation Methods
GridArea NavigationGraph::getGridRectangle(const Point3F& atPos, S32 gridRadius)
{
GridArea atRect;
Point2I atGridLoc;
worldToGrid(atPos, atGridLoc);
atRect.point.x = atGridLoc.x - gridRadius;
atRect.point.y = atGridLoc.y - gridRadius;
atRect.extent.x = atRect.extent.y = (gridRadius << 1);
return atRect;
}
void NavigationGraph::worldToGrid(const Point3F &wPos, Point2I &gPos)
{
TerrainBlock *terrain = GroundPlan::getTerrainObj();
Point3F origin;
terrain->getTransform().getColumn(3, &origin);
float x = (wPos.x - origin.x) / (float)terrain->getSquareSize();
float y = (wPos.y - origin.y) / (float)terrain->getSquareSize();
gPos.x = (S32)mFloor(x);
gPos.y = (S32)mFloor(y);
}
Point3F NavigationGraph::gridToWorld(const Point2I& gPos)
{
Point3F retVal;
if (!mTerrainInfo.posToLoc(retVal, gPos))
retVal.set(-1, -1, -1);
return retVal;
}
GridArea NavigationGraph::getWorldRect()
{
return GridArea(mTerrainInfo.originGrid, mTerrainInfo.gridDimensions);
}
//-------------------------------------------------------------------------------------
// Visitor to find terrain nodes in rectangular area.
class VisitNodeGrid : public GridVisitor
{
protected:
const GridArea& mWorld; // invariant is that the mNodeGrid
const GraphNodeList& mGrid; // corresponds to mWorld area.
GraphNodeList& mListOut;
GraphNode * getNodeAt(const GridArea& R);
public:
VisitNodeGrid( const GridArea& toVisit, const GridArea& worldArea,
const GraphNodeList& gridList, GraphNodeList& listOut )
: GridVisitor(toVisit), mWorld(worldArea),
mGrid(gridList), mListOut(listOut) { }
bool beforeDivide(const GridArea& R, S32 level);
bool atLevelZero(const GridArea& R);
};
GraphNode * VisitNodeGrid::getNodeAt(const GridArea& R)
{
S32 index = mWorld.getIndex(R.point);
AssertFatal( validArrayIndex(index, mGrid.size()), "VisitNodeGrid- bad index" );
return mGrid[index];
}
bool VisitNodeGrid::beforeDivide(const GridArea& R, S32 level)
{
if (GraphNode * node = getNodeAt(R)) {
if(node->getLevel() == level) {
mListOut.push_back(node);
return false;
}
}
return true;
}
bool VisitNodeGrid::atLevelZero(const GridArea& R)
{
if (GraphNode * node = getNodeAt(R))
mListOut.push_back( node );
return true; //N/A
}
// Query list for list of nodes within a certain area.
S32 NavigationGraph::getNodesInArea(GraphNodeList& listOut, GridArea toVisit)
{
listOut.clear();
if (haveTerrain())
{
GridArea worldRect = getWorldRect();
if (mNodeGrid.size() == (worldRect.extent.x * worldRect.extent.y))
{
if (toVisit.intersect(worldRect))
{
VisitNodeGrid visitor(toVisit, worldRect, mNodeGrid, listOut);
visitor.traverse();
}
}
}
return listOut.size();
}
void NavigationGraph::getOutdoorList(GraphNodeList& to)
{
to.setSize(mNumOutdoor);
dMemcpy(to.address(), mNodeList.address(), mNumOutdoor * sizeof(GraphNode*));
}

2392
ai/graphFloorPlan.cc Normal file

File diff suppressed because it is too large Load diff

231
ai/graphFloorPlan.h Normal file
View file

@ -0,0 +1,231 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHFLOORPLAN_H_
#define _GRAPHFLOORPLAN_H_
#define TESTSPECIAL 1
#define ITERATEMODE 0
// ************************************************************************************
#define DEBUGMODE 0 // Only use this if you want to debug ONE interior. More than
// one interior in a mission will crash with this defined to 1
// Also, this doesn't support graph builds. Use the build function
// called fpStart() and fpend() to generate nodes for the interior
// you want to debug. These functions are defined in navGraph.cs
// ************************************************************************************
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _INTERIOR_H_
#include "interior/interior.h"
#endif
#ifndef _INTERIORINSTANCE_H_
#include "interior/interiorInstance.h"
#endif
#ifndef _GRAPH_H_
#include "ai/graph.h"
#endif
#ifndef _GRAPHMATH_H_
#include "ai/graphMath.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _GRAPHGENUTILS_H_
#include "ai/graphGenUtils.h"
#endif
#ifndef _TSSHAPEINSTANCE_H_
#include "ts/tsShapeInstance.h"
#endif
#ifndef _SHAPEBASE_H_
#include "game/shapeBase.h"
#endif
//----------------------------------------------------------------
class FloorPlan: public SimObject
{
private:
// members
typedef SimObject Parent;
GraphDetails *mGraphDetails;
Interior *currInterior;
InteriorInstance *currInstance;
F32 mSlopeThresh;
U32 mNumInteriors;
U32 mExtraInteriors;
bool mDataCollected;
U8 mDivLevel;
bool newLog;
S32 numZones;
struct surfCollisionInfo
{
bool specialCase;
S32 divideEdge1;
S32 divideEdge2;
F32 factor;
surfCollisionInfo() { specialCase = false; divideEdge1 = divideEdge2 = -1; factor = 0.f; }
};
#if DEBUGMODE
InteriorDetail detail;
#endif
// static members
static Point3F sDebugBreakPoint;
static F32 sFloorThresh;
static F32 sMinDivRadius;
static F32 sMaxDivRadius;
static bool sSubDivide;
static bool sDrawConnections;
static bool sDrawVolumeHts;
static F32 sHeightDiff;
static F32 sSpecialMaxRadius;
static bool sUseSpecial;
static S32 amountToDivide;
static bool sDisableAsserts;
static bool LogMode;
public:
Vector<ShapeBase *> mStaticShapeCenterList;
Vector<ShapeBase *> mStaticShapeGeometryList;
Vector<Point3F> portalPoints;
// constructors/destructors
public:
FloorPlan();
~FloorPlan();
DECLARE_CONOBJECT(FloorPlan);
static void consoleInit();
static void initPersistFields();
static void setBreakPoint(const Point3F& pt) {sDebugBreakPoint = pt;}
// public interface
public:
void render();
void generate();
void upload2NavGraph(NavigationGraph *ng);
// simbase methods
protected:
bool onAdd();
void onRemove();
// interior extraction methods
private:
void snoopInteriors();
void extractData(Interior *interior, InteriorDetail *detail, const MatrixF &transform);
void buildEdgeTable(InteriorDetail *detail);
void buildStaticShapeGeometryNodes();
void buildStaticShapeCenterNodes();
void buildNodesFromPortals(InteriorDetail *d);
void processShape(ShapeBase **s, bool center);
void graphExtraction(InteriorDetail *d, GraphDetails *g);
// subdivision methods
private:
U32 subdivideSurfaces(InteriorDetail *d);
void splitSurface(InteriorDetail *d, DSurf &surf, U32 surfIndex, surfCollisionInfo &info);
void subdivideSurface(InteriorDetail *d, DSurf &surf, U32 surfIdx);
void subdivideEdge(InteriorDetail *d, DEdge *edge, F32 factor);
void createNewSurfaces(InteriorDetail *d, U32 surfIdx, DEdge **edges, BitSet32 &edgeTracker);
void newSurfacesSpecialCase(InteriorDetail *d, U32 surfIdx, BitSet32 &edgeTracker);
bool shouldDivideEdge(DSurf &surf, DEdge *edge);
bool shouldDivideSurf(DSurf &surf);
void createCenterNode(InteriorDetail *d, DSurf *surf, DPoint *CM);
void generateNewEdges(InteriorDetail *d, U32 cenIdx, DSurf &surf, DEdge **edges, BitSet32 &edgeTracker);
// polylist collision methods
private:
bool setupPolyList(InteriorDetail *d, DSurf &surf, F32 *ht, DVolume *vol, surfCollisionInfo &info);
Point3F getMinBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges);
Point3F getMaxBoxPoint(InteriorDetail *d, U8 n_edges, DEdge **edges);
void buildEdgeNormals(InteriorDetail *d, DSurf &surf, VectorF *normalList);
bool obstructedSurf(InteriorDetail *d, DSurf &surf, DVolume *vol, surfCollisionInfo &info);
// internal helpers
private:
void setToWallNodes(InteriorDetail *d, DSurf &surf);
void findLongestEdge(DSurf *surf);
bool validatePoint(DPoint &p);
void computeSurfaceRadius(InteriorDetail *d, DSurf *surf);
F32 maxZforSurf(InteriorDetail *d, DSurf &surf);
void newConnection(InteriorDetail *d, U32 start, U32 end, DConnection &con);
bool isSharedEdge(InteriorDetail *d, DEdge *edge_1, DEdge *edge_2, DConnection &con);
bool passDistanceCheck(DSurf &surf_1, DSurf &surf_2);
void buildConnections(InteriorDetail *d);
void findDuplicateCons(InteriorDetail *d);
bool isUsableInterface(DConnection &con);
bool offHeightDistribution(InteriorDetail *d, DSurf &surf);
bool htsDifferAlot(F32 *hts, U8 n);
void createInventoryVol(DPoint p, DVolume &vol);
void setCollisionInfoData(surfCollisionInfo &info, DSide *side, DSurf &surf);
bool isQuadSurface(InteriorDetail *d, DSurf &surf);
bool haveGoodData(DSide *sides);
bool isSteppable(InteriorDetail *d, DEdge *big, DEdge *small);
bool specSurfTest(DSurf &surf, ClippedPolyList &list, Box3F &b);
void log(const char *string);
void extractPortals(Interior *i, InteriorDetail *d, const MatrixF &transform);
bool buildPortalCenter(InteriorDetail *d, Interior *interior, Interior::Portal *sourcePortal, DPortal *portal);
bool pointsAreEqual(Point3F &a, Point3F &b);
bool inSolid(DPoint &center);
void buildZoneInfo(InteriorDetail *d);
void sortSurfaceList(InteriorDetail *d);
// rendering methods
private:
void drawEdge(Point3F, Point3F);
void drawSurface(InteriorDetail *d, DSurf &surf);
void drawNode(DPoint pos);
void renderEdgeTable(InteriorDetail *d);
void drawPoly(U8 n, Point3F *pts, bool unObstruct, bool special, S32 zone);
void drawConnections(InteriorDetail *d);
void drawVolumes(InteriorDetail *d);
void renderPolyPoints(InteriorDetail *d);
void renderSpecialNodes(InteriorDetail *d);
void drawInterfaces(InteriorDetail *d);
};
extern FloorPlan *gFloorPlan; // just for testing
// inlines
//-------------------------------------------------------------------------
inline bool FloorPlan::shouldDivideEdge(DSurf &surf, DEdge *edge)
{
return ((edge->length / surf.mLongest->length) > 0.4);
}
//-------------------------------------------------------------------------
inline bool FloorPlan::validatePoint(DPoint &p)
{
return (p.x == p.x) && (p.y == p.y) && (p.z == p.z);
}
//-------------------------------------------------------------------------
inline bool FloorPlan::isUsableInterface(DConnection &con)
{
F32 length = (con.segNodes[1] - con.segNodes[0]).len();
// return (length > 0.8);
return (length > 1.2);
}
//-------------------------------------------------------------------------
#endif

761
ai/graphFloorRender.cc Normal file
View file

@ -0,0 +1,761 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "ai/graphFloorPlan.h"
#include "ai/graphGroundPlan.h"
extern void wireCube(F32 size,Point3F pos);
#define zone1 0.1
#define zone2 0.2
#define zone3 0.3
#define zone4 0.4
#define zone5 0.5
#define zone6 0.6
#define zone7 0.7
#define zone8 0.8
#define zone9 0.9
#define zone10 1.0
#define zone11 0.1
#define zone12 0.2
#define zone13 0.3
#define zone14 0.4
#define zone15 0.5
#define zone16 0.6
#define zone17 0.7
#define zone18 0.8
#define zone19 0.9
//----------------------------------------------------------------------------
// render methods - gives visual rep of generated graph
// * must be called during engine rendering phase
//----------------------------------------------------------------------------
void FloorPlan::render()
{
#if DEBUGMODE
U32 i;
for(i = 0; i < detail.mTraversable.size(); i++)
{
// only draw this surface if it has no children
if(detail.mSurfaces[detail.mTraversable[i]].mSubSurfCount == 0)
drawSurface(&detail, detail.mSurfaces[detail.mTraversable[i]]);
}
//renderPolyPoints(&detail);
//drawConnections(&detail);
//drawInterfaces(&detail);
//renderPolyPoints(&detail);
#endif
}
void FloorPlan::drawInterfaces(InteriorDetail *d)
{
for(S32 i = 0; i < d->mConnections.size(); i++)
{
drawEdge(d->mConnections[i].segNodes[0], d->mConnections[i].segNodes[1]);
}
}
void FloorPlan::renderSpecialNodes(InteriorDetail *)
{
U32 sCount = portalPoints.size(), j;
for(j = 0; j < sCount; j++)
wireCube(0.2f, portalPoints[j]);
}
void FloorPlan::renderPolyPoints(InteriorDetail *d)
{
U32 sCount = d->polyTestPoints.size(), j;
for(j = 0; j < sCount; j++)
wireCube(0.1, d->polyTestPoints[j]);
}
//----------------------------------------------------------------------------
void FloorPlan::renderEdgeTable(InteriorDetail *d)
{
DEdge *walk;
DPoint p1, p2;
U32 i;
for(i = 0; i < d->mEdgeTable.tableSize(); i++)
{
walk = d->mEdgeTable.getBucket(i);
while(walk)
{
if(walk->flags.test(DEdge::divGenerated))
{
p1 = d->mPoints[walk->start];
p2 = d->mPoints[walk->end];
bool p1ok = validatePoint(p1);
bool p2ok = validatePoint(p2);
AssertFatal(p1ok, "invalid Point3F");
AssertFatal(p2ok, "invalid Point3F");
drawEdge(p1, p2);
}
walk = walk->next;
}
}
}
//----------------------------------------------------------------------------
void FloorPlan::drawSurface(InteriorDetail *d, DSurf &surf)
{
Point3F pts[32];
U32 i;
for(i = 0; i < surf.mNumEdges; i++)
{
if(!surf.mFlipStates.test(BIT(i)))
pts[i] = d->mPoints[surf.mEdges[i]->start];
else
pts[i] = d->mPoints[surf.mEdges[i]->end];
}
drawPoly(i, pts, surf.mFlags.test(DSurf::unObstructed), surf.mFlags.test(DSurf::tooSmall), surf.mZone);
}
//----------------------------------------------------------------------------
void FloorPlan::drawPoly(U8 n, Point3F *pts, bool, bool, S32 zone)
{
switch(zone)
{
case -1:
case 0:
glColor3f(1, 1, 1);
break;
case 1:
glColor3f(zone10, zone1, zone1);
break;
case 2:
glColor3f(zone2, zone10, zone2);
break;
case 3:
glColor3f(zone3, zone3, zone10);
break;
case 4:
glColor3f(zone10, zone4, zone4);
break;
case 5:
glColor3f(zone5, zone10, zone5);
break;
case 6:
glColor3f(zone6, zone6, zone10);
break;
case 7:
glColor3f(zone10, zone7, zone7);
break;
case 8:
glColor3f(zone8, zone10, zone8);
break;
case 9:
glColor3f(zone9, zone9, zone10);
break;
case 10:
glColor3f(zone1, zone10, zone10);
break;
case 11:
glColor3f(zone10, zone11, zone11);
break;
case 12:
glColor3f(zone12, zone10, zone12);
break;
case 13:
glColor3f(zone13, zone13, zone10);
break;
case 14:
glColor3f(zone10, zone14, zone14);
break;
case 15:
glColor3f(zone15, zone10, zone15);
break;
case 16:
glColor3f(zone16, zone16, zone10);
break;
case 17:
glColor3f(zone10, zone17, zone17);
break;
case 18:
glColor3f(zone18, zone10, zone18);
break;
case 19:
glColor3f(zone19, zone19, zone10);
break;
}
U32 i;
glBegin(GL_POLYGON);
for(i = 0; i < n; i++)
glVertex3f(pts[i].x, pts[i].y, pts[i].z+.01);
glEnd();
// outline
glBegin(GL_LINE_LOOP);
glColor3f(0, 0, 0);
for(i = 0; i < n; i++)
glVertex3f(pts[i].x, pts[i].y, pts[i].z+.02);
glEnd();
}
//----------------------------------------------------------------------------
void FloorPlan::drawEdge(Point3F p1, Point3F p2)
{
glBegin(GL_LINES);
glColor3f(0, 1, 0);
glVertex3f(p1.x, p1.y, (p1.z+.2));
glVertex3f(p2.x, p2.y, (p2.z+.2));
glEnd();
}
//----------------------------------------------------------------------------
void FloorPlan::drawNode(DPoint pos)
{
glPointSize(10);
glBegin(GL_POINTS);
glColor3f(0, 0, 1);
glVertex3f(pos.x, pos.y, pos.z+.2);
glEnd();
}
//----------------------------------------------------------------------------
void FloorPlan::drawConnections(InteriorDetail *d)
{
if(!sDrawConnections)
return;
DPoint p1, p2;
U32 i;
for(i = 0; i < d->mConnections.size(); i++)
{
p1 = d->mConPoints[d->mConnections[i].start];
p1.z += .1;
p2 = d->mConPoints[d->mConnections[i].end];
p2.z += .1;
drawNode(p1);
drawNode(p2);
drawEdge(p1, p2);
}
}
//----------------------------------------------------------------------------
void FloorPlan::drawVolumes(InteriorDetail *d)
{
U32 i;
for(i = 0; i < d->mVolumes.size(); i++)
{
//drawEdge(d->mVolumes[i].surfacePtr->mCntr, d->mVolumes[i].capPt);
}
}
//----------------------------------------------------------------------------
void GroundPlan::render(Point3F &camPos, bool drawClipped)
{
U32 i;
S32 dataBaseSize = mGridDatabase.size();
if( ! dataBaseSize )
return;
// 2 methods for render
if(drawClipped)
{
Point2I gPos;
S32 index;
worldToGrid(camPos, gPos);
if(gPos.x < mGridOrigin.x || gPos.x > (mGridOrigin.x + mGridDims.x))
return;
index = gridToIndex(gPos);
S32 start, end;
S32 xspan, yspan;
getClippedRegion(index, start, end, xspan, yspan);
// begin rendering
S32 x, y;
for(y = 0; y < yspan; y++)
{
for(x = start + (y * mGridDims.x); x < ((start + (y * mGridDims.x)) + xspan); x++)
{
index = x;
// don't draw invalid node indexes
if(index > dataBaseSize || index < 0)
continue;
GridDetail &grid = mGridDatabase[index];
// clear node
if(grid.mNavFlag == GridDetail::clear)
{
Point3F pos1 = grid.mWorld[0][0];
pos1.z += 1;
wireCube(1, pos1);
}
else if(grid.mNavFlag == GridDetail::shadowed)
{
Point3F pos2 = grid.mWorld[0][0];
pos2.z += 1;
wireCube(1, pos2);
drawShadow(grid, index);
}
drawObstruct(grid, index);
// draw a network of connections for this node
drawNeighbor(grid, index);
}
}
}
// draw the whole graph
else
{
for(i = 0; i < dataBaseSize; i++)
{
GridDetail &grid = mGridDatabase[i];
// clear node
/*if(grid.mNavFlag == GridDetail::clear)
{
Point3F pos1 = grid.mWorld[0][0];
pos1.z += 1;
wireCube(1, pos1);
}
else if(grid.mNavFlag == GridDetail::shadowed)
{
Point3F pos2 = grid.mWorld[0][0];
pos2.z += 1;
wireCube(1, pos2);
drawShadow(grid, i);
}*/
drawObstruct(grid, i);
//drawNeighbor(grid, i);
}
}
}
//----------------------------------------------------------------------------
// these were more for testing
void GroundPlan::drawShadow(GridDetail &grid, S32 index)
{
Point2I *gPos;
Point2I cPos;
Point3F wMin;
Point3F wMax;
gPos = getPosition(index);
if(!gPos)
return;
cPos = *gPos;
gridToWorld(cPos, wMin);
gridToWorld(cPos + gridOffset[7], wMax);
glBegin(GL_LINES);
glColor3f(1.0, 0.0, 0.0);
glVertex3f(grid.mWorld[0][0].x, grid.mWorld[0][0].y, grid.mWorld[0][0].z);
glVertex3f(grid.mWorld[0][0].x, grid.mWorld[0][0].y, grid.mWorld[0][0].z+grid.mShadowHt);
glEnd();
}
//----------------------------------------------------------------------------
void GroundPlan::drawObstruct(GridDetail &grid, S32 index)
{
Point2I *gPos;
Point2I cPos;
Point3F wMin;
Point3F wMax;
gPos = getPosition(index);
if(!gPos)
return;
cPos = *gPos;
gridToWorld(cPos, wMin);
gridToWorld(cPos + gridOffset[7], wMax);
for(U16 i = 0; i < 2; i++)
{
if(grid.mNavFlags[i] == GridDetail::obstructed)
{
glBegin(GL_TRIANGLES);
glColor3f(1, 0, 0);
glVertex3f(grid.mWorld[i][0].x, grid.mWorld[i][0].y, grid.mWorld[i][0].z+.5);
glVertex3f(grid.mWorld[i][1].x, grid.mWorld[i][1].y, grid.mWorld[i][1].z+.5);
glVertex3f(grid.mWorld[i][2].x, grid.mWorld[i][2].y, grid.mWorld[i][2].z+.5);
glEnd();
}
}
}
//----------------------------------------------------------------------------
void GroundPlan::drawNeighbor(GridDetail &grid, S32 i)
{
Point3F to, nodeP;
nodeP = grid.mWorld[0][0];
nodeP.z += 1;
GridDetail *g;
bool isBottomRow = false;
bool isLeftEdge = false;
bool isRightEdge = false;
bool isTopRow = false;
if(i % mGridDims.x == 0)
isLeftEdge = true;
if(i < mGridDims.x)
isBottomRow = true;
if(((i+1) % mGridDims.x) == 0)
isRightEdge = true;
if(i >= ((mGridDims.x * mGridDims.y) - mGridDims.x))
isTopRow = true;
if(!isLeftEdge && !isBottomRow)
{
if(!isTopRow && !isRightEdge)
{
if(grid.mNeighbors.test(BIT(0)))
{
g = &(mGridDatabase[getNeighborDetails(i, 0)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(1)))
{
g = &(mGridDatabase[getNeighborDetails(i, 1)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(2)))
{
g = &(mGridDatabase[getNeighborDetails(i, 2)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(3)))
{
g = &(mGridDatabase[getNeighborDetails(i, 3)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(4)))
{
g = &(mGridDatabase[getNeighborDetails(i, 4)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(5)))
{
g = &(mGridDatabase[getNeighborDetails(i, 5)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(7)))
{
g = &(mGridDatabase[getNeighborDetails(i, 7)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
else if(!isTopRow)
{
if(grid.mNeighbors.test(BIT(0)))
{
g = &(mGridDatabase[getNeighborDetails(i, 0)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(1)))
{
g = &(mGridDatabase[getNeighborDetails(i, 1)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(2)))
{
g = &(mGridDatabase[getNeighborDetails(i, 2)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(4)))
{
g = &(mGridDatabase[getNeighborDetails(i, 4)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
else if(!isRightEdge)
{
if(grid.mNeighbors.test(BIT(0)))
{
g = &(mGridDatabase[getNeighborDetails(i, 0)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(1)))
{
g = &(mGridDatabase[getNeighborDetails(i, 1)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(2)))
{
g = &(mGridDatabase[getNeighborDetails(i, 2)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(3)))
{
g = &(mGridDatabase[getNeighborDetails(i, 3)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(5)))
{
g = &(mGridDatabase[getNeighborDetails(i, 5)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
else
{
if(grid.mNeighbors.test(BIT(0)))
{
g = &(mGridDatabase[getNeighborDetails(i, 0)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(1)))
{
g = &(mGridDatabase[getNeighborDetails(i, 1)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(2)))
{
g = &(mGridDatabase[getNeighborDetails(i, 2)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
}
else if(!isBottomRow)
{
if(!isTopRow)
{
if(grid.mNeighbors.test(BIT(1)))
{
g = &(mGridDatabase[getNeighborDetails(i, 1)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(3)))
{
g = &(mGridDatabase[getNeighborDetails(i, 3)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(5)))
{
g = &(mGridDatabase[getNeighborDetails(i, 5)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(7)))
{
g = &(mGridDatabase[getNeighborDetails(i, 7)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
else
{
if(grid.mNeighbors.test(BIT(3)))
{
g = &(mGridDatabase[getNeighborDetails(i, 3)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(7)))
{
g = &(mGridDatabase[getNeighborDetails(i, 7)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
}
else if(!isLeftEdge)
{
if(!isRightEdge)
{
if(grid.mNeighbors.test(BIT(2)))
{
g = &(mGridDatabase[getNeighborDetails(i, 2)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(4)))
{
g = &(mGridDatabase[getNeighborDetails(i, 4)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(5)))
{
g = &(mGridDatabase[getNeighborDetails(i, 5)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(7)))
{
g = &(mGridDatabase[getNeighborDetails(i, 7)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
else
{
if(grid.mNeighbors.test(BIT(2)))
{
g = &(mGridDatabase[getNeighborDetails(i, 2)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(4)))
{
g = &(mGridDatabase[getNeighborDetails(i, 4)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
}
else
{
if(grid.mNeighbors.test(BIT(5)))
{
g = &(mGridDatabase[getNeighborDetails(i, 5)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(6)))
{
g = &(mGridDatabase[getNeighborDetails(i, 6)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
if(grid.mNeighbors.test(BIT(7)))
{
g = &(mGridDatabase[getNeighborDetails(i, 7)]);
to = g->mWorld[0][0];
to.z += 1;
drawConnection(nodeP, to);
}
}
}
//----------------------------------------------------------------------------
void GroundPlan::drawConnection(Point3F &st, Point3F &ed)
{
glBegin(GL_LINES);
glColor3f(0.0, 0.0, 1.0);
glVertex3f(st.x, st.y, st.z);
glVertex3f(ed.x, ed.y, ed.z);
glEnd();
}
//----------------------------------------------------------------------------

162
ai/graphForceField.cc Normal file
View file

@ -0,0 +1,162 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#define ForceFieldCheckFrequency 2.0f
#define ForceFieldCheckDelay U32(1000.0f / ForceFieldCheckFrequency)
//-------------------------------------------------------------------------------------
MonitorForceFields::MonitorForceFields()
{
mFFList = NULL;
mCount = 0;
mSaveTimeMS = 0;
for (S32 i = 0; i < GraphMaxTeams; i++)
mTeamPartitions[i].setType(GraphPartition::ForceField);
}
MonitorForceFields::~MonitorForceFields()
{
delete [] mFFList;
}
//-------------------------------------------------------------------------------------
static S32 forceFieldTeam(ForceFieldBare * ff)
{
if (ff)
return (ff->isTeamControlled() ? ff->getSensorGroup() : 0);
return 0;
}
static bool forceFieldUp(ForceFieldBare * ff)
{
if (ff)
return dAtob(Con::executef(ff, 1, "isPowered"));
return false;
}
//-------------------------------------------------------------------------------------
MonitorForceFields::ForceField::ForceField()
{
mActive = false;
mTeam = 0;
}
void MonitorForceFields::ForceField::updateEdges()
{
// Want this quick, avoid functions calls in loop in debug exe.
register GraphEdgePtrs::iterator e = mEffects.begin();
register S32 count = mEffects.size();
U32 team = (mActive ? mTeam : 0);
while( --count >= 0 )
(* e++)->setTeam(team);
}
void MonitorForceFields::atMissionStart()
{
SimpleQueryList sql;
gServerContainer.findObjects(ForceFieldObjectType,
SimpleQueryList::insertionCallback, S32(&sql));
delete [] mFFList;
mFFList = new ForceField [ mCount = sql.mList.size() ];
// Initialze array, and disable LOS intersections on all the force fields.
S32 i;
for (i = 0; i < mCount; i++) {
ForceFieldBare * ffPtr = dynamic_cast<ForceFieldBare*>(sql.mList[i]);
AssertFatal(ffPtr, "MonitorForceFields query didn't work");
mFFList[i].mFF = ffPtr;
mFFList[i].mTeam = forceFieldTeam(ffPtr);
mFFList[i].mActive = forceFieldUp(ffPtr);
ffPtr->disableCollision();
}
// Do the collision work, only enabling the current object-
for (i = 0; i < mCount; i++) {
ForceFieldBare * ffPtr = mFFList[i].mFF;
ffPtr->enableCollision();
mFFList[i].mEffects = gNavGraph->getBlockedEdges(ffPtr, ForceFieldObjectType);
ffPtr->disableCollision();
}
// Restore collision enablement state- perform initial update-
for (i = 0; i < mCount; i++) {
mFFList[i].mFF->enableCollision();
mFFList[i].updateEdges();
}
}
// Clear partitions on all teams except one supplied.
void MonitorForceFields::clearPartitions(U32 exceptTeam)
{
for (S32 i = 0; i < GraphMaxTeams; i++)
if (i != exceptTeam)
mTeamPartitions[i].clear();
}
// This team had a search fail (which we assure is only due to force fields), and so we
// update their FF partition list- we mark all nodes visited on search.
void MonitorForceFields::informSearchFailed(GraphSearch * searcher, U32 team)
{
AssertFatal(team >= 0 && team < GraphMaxTeams, "Bad team index in FF monitor");
mTeamPartitions[team].pushPartition(searcher->getPartition());
}
GraphPartition::Answer MonitorForceFields::reachable(U32 team, S32 from, S32 to)
{
return mTeamPartitions[team].reachable(from, to);
}
// Check them periodically. Just call this often.
void MonitorForceFields::monitor()
{
U32 T = Sim::getCurrentTime();
if ((T - mSaveTimeMS) > ForceFieldCheckDelay)
{
mSaveTimeMS = T;
// Check for changes to team or active status.
for (S32 i = 0; i < mCount; i++)
{
ForceField & ff = mFFList[i];
// if (ForceFieldBare * forceFieldObject = (ForceFieldBare*)ff.mFF)
{
U32 team = forceFieldTeam(ff.mFF);
bool active = forceFieldUp((ForceFieldBare*)ff.mFF);
if (team == ff.mTeam && active == ff.mActive)
continue;
// Here we only invalidate the old team and the new team, unless either
// of them is team zero (which means everybody's partition gets changed).
if (team != ff.mTeam) {
if (team && ff.mTeam) {
mTeamPartitions[team].clear();
mTeamPartitions[ff.mTeam].clear();
}
else {
clearPartitions(0);
}
ff.mTeam = team;
}
if (active != ff.mActive) {
clearPartitions(ff.mTeam);
ff.mActive = active;
}
mFFList[i].updateEdges();
}
}
}
}

45
ai/graphForceField.h Normal file
View file

@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHFORCEFIELD_H_
#define _GRAPHFORCEFIELD_H_
#ifndef _FORCEFIELDBARE_H_
#include "game/forceFieldBare.h"
#endif
class MonitorForceFields
{
public:
struct ForceField
{
ForceField();
void updateEdges();
SimObjectPtr<ForceFieldBare> mFF;
GraphEdgePtrs mEffects;
bool mActive;
U32 mTeam;
};
protected:
ForceField * mFFList;
S32 mCount;
U32 mSaveTimeMS;
PartitionList mTeamPartitions[GraphMaxTeams];
public:
MonitorForceFields();
~MonitorForceFields();
GraphPartition::Answer reachable(U32 team, S32 from, S32 to);
void clearPartitions(U32 allBut);
void informSearchFailed(GraphSearch * searcher, U32 team);
S32 count() const {return mCount;}
void atMissionStart();
void monitor();
};
#endif

212
ai/graphGenUtils.cc Normal file
View file

@ -0,0 +1,212 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graphGenUtils.h"
//----------------------------------------------------------------
//
// EdgeTable Implementation
//
//----------------------------------------------------------------
EdgeTable::EdgeTable()
{
hashTable = NULL;
init();
}
//----------------------------------------------------------------
EdgeTable::~EdgeTable()
{
}
//----------------------------------------------------------------
S32 EdgeTable::hash(DEdge edge)
{
S32 value = ((edge.start + edge.end) % 1009);// <- prime #
return value;
}
//----------------------------------------------------------------
void EdgeTable::init()
{
hashTable = new DEdge *[DefaultTableSize];
hashTableSize = DefaultTableSize;
hashEntryCount = 0;
S32 i;
for(i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
}
//----------------------------------------------------------------
void EdgeTable::clear()
{
DEdge *walk, *temp;
U32 i;
for(i = 0; i < hashTableSize; i++)
{
walk = hashTable[i];
while(walk)
{
temp = walk->next;
delete walk;
walk = temp;
}
hashTable[i] = NULL;
}
delete [] hashTable;
}
//----------------------------------------------------------------
void EdgeTable::insert(DEdge edge)
{
DEdge *entry = new DEdge();
entry->start = edge.start;
entry->end = edge.end;
entry->midPoint = edge.midPoint;
entry->next = NULL;
entry->right = NULL;
entry->left = NULL;
entry->length = edge.length;
entry->flags = edge.flags;
// entry->totalBelongsTo = edge.totalBelongsTo;
S32 i;
// for(i = 0; i < edge.totalBelongsTo; i++)
// entry->shared[i] = edge.shared[i];
S32 idx = (hash(*entry) % hashTableSize);
entry->next = hashTable[idx];
hashTable[idx] = entry;
hashEntryCount++;
if(hashEntryCount > hashTableSize)
{
// resize the hash table
DEdge *head = NULL, *walk, *temp;
for(i = 0; i < hashTableSize; i++)
{
walk = hashTable[i];
while(walk)
{
temp = walk->next;
walk->next = head;
head = walk;
walk = temp;
}
}
delete [] hashTable;
hashTableSize = hashTableSize * 2 + 1;
hashTable = new DEdge *[hashTableSize];
for(i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
while(head)
{
temp = head->next;
idx = (hash(*head) % hashTableSize);
head->next = hashTable[idx];
hashTable[idx] = head;
head = temp;
}
}
}
//----------------------------------------------------------------
bool EdgeTable::contains(DEdge edge)
{
DEdge **walk = &hashTable[hash(edge) % hashTableSize];
while(*walk)
{
if(**walk == edge)
return true;
walk = &((*walk)->next);
}
return false;
}
//----------------------------------------------------------------
DEdge *EdgeTable::find(DEdge edge)
{
DEdge **walk = &hashTable[hash(edge) % hashTableSize];
while(*walk)
{
if(**walk == edge)
return *walk;
walk = &((*walk)->next);
}
return NULL;
}
//-------------------------------------------------------------------------------------
DSurf::DSurf()
{
mEdges = NULL;
mLongest = NULL;
mNumEdges = 0;
mSubSurfCount = 0;
mDivLevel = 0;
mMaxRadius = 0.0;
mMinRadius = 0.0;
parent = NULL;
dMemset(mSubSurfs, 0, sizeof(mSubSurfs));
dMemset(fullWinding, 0, sizeof(fullWinding));
mNormal.set(0,0,0);
mCtrIdx = 0;
volIdx = 0;
mZone = -1;
}
//-------------------------------------------------------------------------------------
DVolume::DVolume()
{
mNumPlanes = 0;
dMemset(mPlanes, 0, sizeof(mPlanes));
surfIdx = 0;
ht = 0.0;
capPt.set(0,0,0);
}
// See if point is inside the planes-
bool DVolume::checkInside(const Point3F& point)
{
for (S32 i = 0; i < mNumPlanes; i++)
if (mPlanes[i].distToPlane(point) >= 0.1)
return false;
return true;
}
//-------------------------------------------------------------------------------------
// Interior Detail
InteriorDetail::InteriorDetail()
{
mIndex = -1;
mNumUnObstructed = 0;
numPolyLists = 0;
}
bool InteriorDetail::haveSurfaceNear(const Point3F& pos, F32 rad)
{
rad *= rad; // use square of length
for (S32 i = 0; i < mSurfaces.size(); i++)
if ((pos - mSurfaces[i].mCntr).lenSquared() < rad)
return true;
return false;
}

304
ai/graphGenUtils.h Normal file
View file

@ -0,0 +1,304 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHGENUTILS_H_
#define _GRAPHGENUTILS_H_
#ifndef _TVECTOR_H_
#include "Core/tVector.h"
#endif
#ifndef _BITSET_H_
#include "Core/bitSet.h"
#endif
#ifndef _MPOINT_H_
#include "Math/mPoint.h"
#endif
#ifndef _MPLANE_H_
#include "Math/mPlane.h"
#endif
#ifndef _MBOX_H_
#include "Math/mBox.h"
#endif
#ifndef _GRAPHDATA_H_
#include "ai/graphData.h"
#endif
//----------------------------------------------------------------
class DEdge
{
public:
enum
{
divGenerated = BIT(0),
wall = BIT(1),
divided = BIT(2),
drawn = BIT(3),
traversable = BIT(4),
};
public:
U32 start, end, midPoint;
DEdge *right, *left;
DEdge *next;
BitSet32 flags;
F32 length;
public:
bool operator==(const DEdge &edge);
};
inline bool DEdge::operator==(const DEdge &edge)
{
return (((start == edge.start) && (end == edge.end)) ||
((end == edge.start) && (start == edge.end)));
}
//----------------------------------------------------------------
// hashtable to make graph edge building very quick.
class EdgeTable
{
public:
enum
{
DefaultTableSize = 32,
};
private:
DEdge **hashTable;
U32 hashTableSize;
U32 hashEntryCount;
public:
EdgeTable();
~EdgeTable();
void insert(DEdge edge);
bool contains(DEdge edge);
DEdge *find(DEdge edge);
DEdge *getBucket(U32 bucket);
U32 size() const;
U32 tableSize() const;
void clear();
private:
void init();
S32 hash(DEdge edge);
};
//----------------------------------------------------------------
inline DEdge *EdgeTable::getBucket(U32 bucket)
{
return hashTable[bucket];
}
//----------------------------------------------------------------
inline U32 EdgeTable::size() const
{
return hashEntryCount;
}
//----------------------------------------------------------------
inline U32 EdgeTable::tableSize() const
{
return hashTableSize;
}
//----------------------------------------------------------------
class DSpecNode
{
public:
enum
{
chuteNode = BIT(0),
};
public:
BitSet32 flags;
Point3F pos;
StringTableEntry name;
};
//----------------------------------------------------------------
class DPoint : public Point3F
{
public:
enum
{
divGenerated = BIT(0),
wall = BIT(1),
center = BIT(2),
inventory = BIT(3),
};
public:
BitSet32 flags;
DEdge *e;
U32 surfIdx;
public:
//operator Point3F &(){ return *this; }
};
//----------------------------------------------------------------
class DSurf
{
public:
enum
{
isWall = BIT(0),
shouldBeCulled = BIT(1),
divided = BIT(2),
split = BIT(3),
unObstructed = BIT(4),
specialSplit = BIT(5),
tooSmall = BIT(6),
};
DEdge **mEdges;
DEdge *mLongest;
BitSet32 mFlags;
BitSet32 mFlipStates;
U32 mNumEdges;
U16 mSubSurfs[32];
U16 mSubSurfCount;
VectorF mNormal;
S16 mDivLevel;
F32 mMaxRadius;
F32 mMinRadius;
U32 fullWinding[32];
DSurf *parent;
DPoint mCntr;
U32 mCtrIdx;
U32 volIdx;
S32 mZone;
DSurf();
};
//----------------------------------------------------------------
class DConnection
{
public:
U32 start;
U32 end;
// interface points
Point3F segNodes[2];
public:
bool operator==(const DConnection &con);
};
//----------------------------------------------------------------
inline bool DConnection::operator==(const DConnection &con)
{
return (((start == con.start) && (end == con.end)) ||
((end == con.start) && (start == con.end)));
}
//----------------------------------------------------------------
class DVolume
{
public:
U32 mNumPlanes;
PlaneF mPlanes[32];
U32 surfIdx;
F32 ht;
Point3F capPt;
DVolume();
bool checkInside(const Point3F& pt);
};
class DPortal
{
public:
Point3F center;
};
//----------------------------------------------------------------
class InteriorDetail
{
public:
Vector<DSurf> mSurfaces;
Vector<DPoint> mPoints;
Vector<DConnection> mConnections;
Vector<DPoint> mConPoints;
Vector<DVolume> mVolumes;
Vector<U32> mTraversable;
Vector<U32> mUnobstructed;
Vector<DSpecNode> mSpecialNodes;
Vector<Point3F> polyTestPoints;
Vector<DPortal> mPortals;
Vector<U32> sortedSurfList;
//ClippedPolyList *polyLists;
EdgeTable mEdgeTable;
S16 mIndex;
S32 mNumUnObstructed;
Box3F mBbox;
S32 numPolyLists;
InteriorDetail();
bool haveSurfaceNear(const Point3F& pos, F32 rad);
};
class GraphDetails
{
public:
EdgeInfoList edges;
NodeInfoList nodes;
EdgeInfoList segs;
GraphVolumeList volumes;
Vector<Point3F> chutes;
GraphDetails() { }
};
//----------------------------------------------------------------
class GridDetail
{
public:
enum {
clear = 0,
shadowed = 1,
obstructed = 2,
submerged = 3,
};
// these are at the node level
U8 mNavFlag;
BitSet32 mNeighbors;
F32 mShadowHt;
// these are at the triangle level
U8 mNavFlags[2];
F32 mShadowHts[2];
Point3F mWorld[2][3];
};
struct DSide
{
S32 closePts;
F32 bestDist;
DSide() { closePts = 0; bestDist = 0.f; }
};
#endif

1067
ai/graphGroundPlan.cc Normal file

File diff suppressed because it is too large Load diff

185
ai/graphGroundPlan.h Normal file
View file

@ -0,0 +1,185 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHGROUNDPLAN_H_
#define _GRAPHGROUNDPLAN_H_
#ifndef _TERRDATA_H_
#include "terrain/terrData.h"
#endif
#ifndef _CLIPPEDPOLYLIST_H_
#include "Collision/clippedPolyList.h"
#endif
#ifndef _GRAPHGENUTILS_H_
#include "ai/graphGenUtils.h"
#endif
class WaterBlock;
class NavigationGraph;
//---------------------------------------------------------------
class GroundPlan: public SimObject
{
friend class InspectionVisitor;
// members
typedef SimObject Parent;
TerrainBlock *mTerrainBlock;
Vector<GridDetail> mGridDatabase;
Point2I mGridOrigin;
Point2I mGridDims;
S32 mTotalVisited;
// static members
static Point2I gridOffset[8];
static bool renderMe;
static bool drawclipped;
// constructors/destructors
public:
GroundPlan();
~GroundPlan();
DECLARE_CONOBJECT(GroundPlan);
static void consoleInit();
static void initPersistFields();
// specific to SceneObject
protected:
bool onAdd();
void onRemove();
void drawObstruct(GridDetail &grid, S32 index);
void drawShadow(GridDetail &grid, S32 index);
void drawNeighbor(GridDetail &grid, S32 i);
void drawConnection(Point3F &st, Point3F &ed);
// terrain block details
protected:
void gridToWorld(const Point2I &gPos, Point3F &wPos);
void worldToGrid(const Point3F &wPos, Point2I &gPos);
void gridToCenter(const Point2I &gPos, Point2I &cPos);
F32 getGridHeight(const Point2I &gPos);
S32 gridToIndex(const Point2I &gPos);
void getClippedRegion(S32 index, S32 &st, S32 &ed, S32 &xspan, S32 &yspan);
bool isSplit45(const Point2I &gPos);
bool isOnBoundary(S32 i);
bool missionHasWater();
void getAllWaterBlocks(Vector<WaterBlock*> &blocks);
// inspection details
private:
void inspectSquare(const GridArea &gridArea, GridDetail &square);
S32 getNeighborDetails(S32 index, S32 key);
void packTriangleBits(const GridDetail &g, BitSet32 &bits, S32 bitType, S32 index, bool isLeftEdge, bool isBottomRow);
F32 lowestShadowHt(const GridDetail &g, BitSet32 &bits, S32 index);
GridArea alignTheArea(const GridArea& areaIn);
void findNeighbors();
void computeNavFlags();
bool setupPolyList(const Box3F& bBox, const Point3F& min, const Point3F& mid,
const Point3F& max, const VectorF& norm1, const VectorF& norm2,
const VectorF& norm3, F32* height);
GridDetail defaultDetail();
// interface
public:
Point2I getOrigin() const;
Point2I *getPosition(S32 index);
bool inspect(GridArea &area);
S32 getNavigable(Vector<U8> &navFlags, Vector<F32> &shadowHts);
Vector<U8> &getNeighbors(Vector<U8> &neighbors);
static TerrainBlock *getTerrainObj();
S32 getGridDimWidth() const;
S32 getGridDimHeight() const;
void render(Point3F &camPos, bool drawClipped);
bool setTerrainGraphInfo(TerrainGraphInfo* info);
void genExterior(Point2I &min, Point2I &max, NavigationGraph *nav);
};
extern GroundPlan *gGroundPlanTest;
//----------------------------------------------------------------------------
inline S32 GroundPlan::getGridDimWidth() const
{
return mGridDims.x;
}
//----------------------------------------------------------------------------
inline S32 GroundPlan::getGridDimHeight() const
{
return mGridDims.y;
}
//----------------------------------------------------------------------------
inline Point2I GroundPlan::getOrigin() const
{
return mGridOrigin;
}
//----------------------------------------------------------------------------
inline Point2I *GroundPlan::getPosition(S32 index)
{
static Point2I sPoint;
sPoint.x = mGridOrigin.x + index % mGridDims.x;
sPoint.y = mGridOrigin.y + index / mGridDims.x;
return &sPoint;
}
//----------------------------------------------------------------------------
inline bool GroundPlan::isSplit45(const Point2I &gPos)
{
GridSquare *gs = mTerrainBlock->findSquare(0, gPos);
if(gs->flags & GridSquare::Split45)
return true;
else
return false;
}
//----------------------------------------------------------------------------
inline S32 GroundPlan::getNeighborDetails(S32 index, S32 key)
{
if(key < 0 || key > 7)
return index;
switch(key)
{
case 0:
index = (index - mGridDims.x) - 1;
break;
case 1:
index -= mGridDims.x;
break;
case 2:
index--;
break;
case 3:
index = (index - mGridDims.x) + 1;
break;
case 4:
index = (index + mGridDims.x) - 1;
break;
case 5:
index++;
break;
case 6:
index = index + mGridDims.x;
break;
case 7:
index = (index + mGridDims.x) + 1;
break;
}
AssertFatal((index <= mGridDatabase.size() - 1) || (index > 0), "GroundPlan::getNeighborDetails() - index out of bounds!");
return index;
}
#endif

93
ai/graphGroundVisit.h Normal file
View file

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHGROUNDVISIT_H_
#define _GRAPHGROUNDVISIT_H_
#ifndef _GRAPHMATH_H_
#include "ai/graphMath.h"
#endif
class GroundPlan;
class GridDetail;
class InspectionVisitor : public GridVisitor
{
protected:
GroundPlan& mGPlan;
Vector<SceneObject*> mObjects;
const U32 mMask;
public:
//==> Might want to pass in mask later-
InspectionVisitor(const GridArea &toVisit, GroundPlan &p) :
mMask(InteriorObjectType|GameBaseObjectType|TurretObjectType),
GridVisitor(toVisit), mGPlan(p) { }
bool beforeDivide(const GridArea& R, S32 level);
bool atLevelZero(const GridArea& R);
};
//--------------------------------------------------------------------------
static void findObjectsCallback(SceneObject *obj, S32 val)
{
Vector<SceneObject*> *list = (Vector<SceneObject *> *)val;
list->push_back(obj);
}
//--------------------------------------------------------------------------
bool InspectionVisitor::beforeDivide(const GridArea& R, S32)
{
Box3F wBox;
// construct a world box based on these grid squares
mGPlan.gridToWorld(R.point, wBox.min);
mGPlan.gridToWorld((R.point+R.extent), wBox.max);
wBox.min.z = -(wBox.max.z = 5000);
// Clear the list and fetch objects
mObjects.clear();
gServerContainer.findObjects(-1, findObjectsCallback, S32(&mObjects));
// Scan list-
for (Vector<SceneObject*>::iterator i = mObjects.begin(); i != mObjects.end(); i++)
{
SceneObject * obj = (* i);
if (obj->getTypeMask() & mMask)
{
const Box3F & objBox = obj->getWorldBox();
if (wBox.isOverlapped(objBox))
{
if (obj->getName() && dStricmp("SmallRock", obj->getName()) == 0)
continue;
return true;
}
}
}
return false;
}
//--------------------------------------------------------------------------
bool InspectionVisitor::atLevelZero(const GridArea& R)
{
AssertFatal(R.extent.x == 1 && R.extent.y == 1, "inspection visitor error!");
mGPlan.mTotalVisited++;
S32 index = mGPlan.gridToIndex(R.point);
GridDetail &detail = mGPlan.mGridDatabase[index];
mGPlan.inspectSquare(R, detail);
return true;
}
//--------------------------------------------------------------------------
#endif

267
ai/graphIndoors.cc Normal file
View file

@ -0,0 +1,267 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
// Interior Node Methods
NodeProximity InteriorNode::containment(const Point3F& loc) const
{
if (gNavGraph->haveVolumes())
return gNavGraph->getContainment(mIndex, loc);
else
return Parent::containment(loc);
}
void InteriorNode::init(const IndoorNodeInfo & g, S32 index, const Point3F& normal)
{
mIndex = index;
mLoc = g.pos;
mNormal = normal;
mFlags.set(Inventory, g.isInventory());
mFlags.set(Algorithmic, g.isAlgorithmic());
mFlags.set(BelowPortal, g.isBelowPortal());
mFlags.set(PotentialSeed, g.isSeed());
}
GraphEdge& InteriorNode::pushOneWay(const GraphEdgeInfo::OneWay & edgeIn)
{
GraphEdge edgeOn;
edgeOn.mDest = edgeIn.dest;
if(edgeIn.isJetting())
edgeOn.setJetting();
mEdges.push_back(edgeOn);
return mEdges.last();
}
InteriorNode::InteriorNode()
{
mFlags.set(Indoor);
mMinDim = 0.5;
mArea = (mMinDim * mMinDim);
}
//-------------------------------------------------------------------------------------
// Must destruct the nodes.
void IndoorNodeList::clear()
{
for (iterator it = begin(); it != end(); it++)
it->~InteriorNode();
Parent::clear();
}
void IndoorNodeList::init(S32 size)
{
clear();
setSizeAndConstruct(*this, size);
}
// Must destruct elements.
IndoorNodeList::~IndoorNodeList()
{
clear();
}
//-------------------------------------------------------------------------------------
GraphBoundary::GraphBoundary(const GraphEdgeInfo& edgeInfo)
{
seg[0] = edgeInfo.segPoints[0];
seg[1] = edgeInfo.segPoints[1];
normal.x = seg[0].y - seg[1].y;
normal.y = seg[1].x - seg[0].x;
normal.z = 0;
F32 len = normal.len();
AssertFatal(len > 0.001, "GraphBoundry has small border");
normal *= (1.0f / len);
}
//==> Note - we may want to just have one boundary segment per edge - if so
// we can still compute both and then merge them. I think we may need both though
// since we will want to define a complete segment that they can cross over
// at some point.
// Some computations needed to set it up to know how to navigate through there.
void GraphBoundary::setItUp(S32 nodeIndex, const NodeInfoList& nodes,
const GraphVolumeList& volumes)
{
Point3F midpoint = scaleBetween(seg[0], seg[1], 0.5f);
// Set normal to point in the direction (outbound) we need-
Point3F nodeCenter = nodes[nodeIndex].pos;
Point3F outbound = (midpoint - nodeCenter);
if (mDot(outbound, normal) < 0)
normal *= -1.0;
// Find minimum of the midpoint from all planes, skipping current one.
//==> SKIP??
//==> This all needs to be better smoothed.
F32 D, minDist = 1e9;
S32 numWalls = volumes.planeCount(nodeIndex) - 2;
const PlaneF* planes = volumes.planeArray(nodeIndex);
AssertFatal(numWalls > 2, "Graph node has no meaningful volume");
while (numWalls--)
if ((D = -planes[numWalls].distToPlane(midpoint)) > 0.01)
if (D < minDist)
minDist = D;
distIn = getMin(minDist, 1.2f);
// Now set up the point we need to go through.
seekPt = midpoint - (distIn * normal);
}
//-------------------------------------------------------------------------------------
bool NavigationGraph::initInteriorNodes(const EdgeInfoList & edges,
const NodeInfoList & nodes, S32 startIndex)
{
// This allocation must happen before (in caller) for edge pool purposes-
mBoundaries.clear();
mHaveVolumes = (mNodeVolumes.size() == nodes.size());
// Build node list
Vector<Point3F> utilityBuffer;
for (S32 i = 0; i < nodes.size(); i++) {
Point3F normal(0,0,1);
F32 minDim = 1.0;
F32 area = 1.0;
if (mHaveVolumes) {
Point3F floorVec = mNodeVolumes.floorPlane(i);
if (floorVec.z != 0)
normal = -floorVec;
else
warning("Graph needs regeneration (missing floor plane)");
minDim = mNodeVolumes.getMinExt(i, utilityBuffer);
}
mIndoorNodes[i].init(nodes[i], startIndex + i, normal);
mIndoorNodes[i].setDims(minDim, area);
}
for (S32 j = 0; j < edges.size(); j++)
{
const GraphEdgeInfo& E = edges[j];
for (U32 k = 0; k < 2; k++)
{
GraphEdgeInfo::OneWay edgeOneWay = E.to[k^1];
S32 dstIndex = edgeOneWay.dest;
S32 srcIndex = E.to[k].dest;
edgeOneWay.dest += startIndex;
GraphEdge& edge = mIndoorNodes[srcIndex].pushOneWay(edgeOneWay);
// set up the boundary information
if (mHaveVolumes)
{
edge.mBorder = mBoundaries.size();
GraphBoundary boundary(E);
boundary.setItUp(srcIndex, nodes, mNodeVolumes);
mBoundaries.push_back(boundary);
AssertFatal(mBoundaries.size() < (1 << 15), "Maximum Boundaries = 32K");
}
}
}
return nodes.size();
}
//-------------------------------------------------------------------------------------
// Given a list of interior node indices to get rid of, compact those nodes (and any
// edges left hanging) out of the lists (mEdgeInfoList & mNodeInfoList). Also
// compacts out of the volume list if such is present.
S32 NavigationGraph::compactIndoorData(const Vector<S32>& cullList, S32 numOutdoor)
{
// On balance, it's more convenient to map into the indoor node list 'space' here...
S32 i;
Vector<S32> axeList(cullList.size());
for (i = 0; i < cullList.size(); i++)
axeList.push_back(cullList[i] - numOutdoor);
// Minimize fragmentation by pre-reserving.
NodeInfoList culledNodes(mNodeInfoList.size());
EdgeInfoList culledEdges(mEdgeInfoList.size());
BridgeDataList culledBridges;
GraphVolumeList culledVolumes;
culledVolumes.reserve(mNodeVolumes.size());
culledBridges.reserve(mBridgeList.size());
// ==> This looks wasteful, these need to be culled
if (mHaveVolumes)
culledVolumes.mPlanes = mNodeVolumes.mPlanes;
// Mark nodes-to-axe with -1, the rest at zero.
Vector<S32> remap;
for (i = 0, setSizeAndClear(remap,mNodeInfoList.size()); i < axeList.size(); i++)
{
S32 removedNode = axeList[i];
AssertFatal(removedNode >= 0, "NavigationGraph::compactIndoorData()");
remap[removedNode] = -1;
}
// Fetch nodes to keep into new list and build remap indices for remaining
for (i = 0; i < mNodeInfoList.size(); i++) if (remap[i] == 0)
{
remap[i] = culledNodes.size();
culledNodes.push_back(mNodeInfoList[i]);
if (mHaveVolumes)
culledVolumes.push_back(mNodeVolumes[i]);
}
// Copy down culled edge list and remap the "to" pointers.
for (i = 0; i < mEdgeInfoList.size(); i++)
{
GraphEdgeInfo edge = mEdgeInfoList[i];
if ((edge.to[0].dest = remap[edge.to[0].dest]) >= 0)
{
edge.to[1].dest = remap[edge.to[1].dest];
culledEdges.push_back(edge);
}
}
// Remap bridge indices, and remove any invalid. Note that outdoor nodes don't get
// culled, even if they have a stranded pocket.
for (i = 0; i < mBridgeList.size(); i++)
{
GraphBridgeData bridge = mBridgeList[i];
bool stillGood = true;
for (S32 j = 0; stillGood && (j < 2); j++)
{
// Remap indoor nodes, or see if it's been culled-
S32 nodeInd = bridge.nodes[j];
if (nodeInd >= numOutdoor)
{
S32 mappedNode = remap[nodeInd - numOutdoor];
if (mappedNode >= 0)
bridge.nodes[j] = (mappedNode + numOutdoor);
else
stillGood = false;
}
}
if (stillGood)
culledBridges.push_back(bridge);
}
// install the new lists:
mEdgeInfoList = culledEdges;
mNodeInfoList = culledNodes;
mNodeVolumes = culledVolumes;
mNodeVolumes.cullUnusedPlanes();
mBridgeList = culledBridges;
// whatever-
return culledNodes.size();
}

95
ai/graphIsland.cc Normal file
View file

@ -0,0 +1,95 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
// Mark islands using the visitation callback of GraphSearch, set up mIslandPtrs
// list. Happens on a freshly made graph (island mark bit on nodes is clear).
class IslandMarker : public GraphSearch
{
public:
S32 mCurIsland;
void onQExtraction();
F32 getEdgeTime(const GraphEdge*);
};
void IslandMarker::onQExtraction()
{
extractedNode()->setIsland(mCurIsland);
}
// We now have worlds where the island filler won't flood up, but will flood
// down, which results in an error. The default searcher stops when an
// "infinite" distance is encountered, so we need to override it here.
F32 IslandMarker::getEdgeTime(const GraphEdge*)
{
return 1.0;
}
S32 NavigationGraph::markIslands()
{
mIslandPtrs.clear();
mIslandSizes.clear();
mNonTransient.clearFlags(GraphNode::GotIsland);
mLargestIsland = -1;
S32 i, N, maxCount = -1;
// Do island-mark expansions until we have no more unmarked nodes
IslandMarker islandMarker;
islandMarker.mCurIsland = 0;
for (i = 0; i < mNonTransient.size(); i++)
{
if (GraphNode * node = mNonTransient[i])
{
if (!node->gotIsland())
{
N = islandMarker.performSearch(node, NULL);
if (N > maxCount)
{
maxCount = N;
mLargestIsland = islandMarker.mCurIsland;
}
mIslandSizes.push_back(N);
mIslandPtrs.push_back(node);
islandMarker.mCurIsland++;
}
if (RegularNode * regular = dynamic_cast<RegularNode*>(node))
regular->transientReserve();
}
}
// return total island count
return mIslandPtrs.size();
}
//-------------------------------------------------------------------------------------
//
// Culls all indoor nodes that are not part of the largest island.
//
S32 NavigationGraph::cullIslands()
{
S32 originalCount = mNonTransient.size();
Vector<S32> cullList(originalCount);
for(S32 i = 0; i < mNonTransient.size(); i++)
if (mNonTransient[i]->indoor() && mNonTransient[i]->island() != mLargestIsland)
cullList.push_back(i);
// remap indices and do whatever culling was found-
if (cullList.size())
compactIndoorData(cullList, mNumOutdoor);
Con::printf("Original count was %d nodes", originalCount);
Con::printf("%d nodes were culled", cullList.size());
return cullList.size();
}

485
ai/graphJetting.cc Normal file
View file

@ -0,0 +1,485 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#define RatingsCloseEnough 0.1
JetManager::JetManager()
{
mFloodInds[0] = NULL;
mFloodInds[1] = NULL;
mArmorPart = NULL;
}
JetManager::~JetManager()
{
clear();
}
void JetManager::clear()
{
for (S32 i = 0; i < AbsMaxBotCount; i++)
mPartitions[i].doConstruct();
delete mArmorPart;
delete [] mFloodInds[0];
delete [] mFloodInds[1];
mArmorPart = NULL;
mFloodInds[0] = NULL;
mFloodInds[1] = NULL;
}
// Used for both passes of the armor partitioning. Basically do a flood fill expansion
// with the armor partition telling us what has been "extracted". We use two flipping
// lists for this, each containing the last round of extractions. This routine is then
// used twice - reused for finding downhill.
S32 JetManager::floodPartition(U32 seed, bool needBoth, F32 ratings[2])
{
// Set up the loop-
S32 numFound = 0;
S32 floodSizes[2] = {1, 0};
S32 curExtract = 0, curDeposit;
mArmorPart->set(mFloodInds[0][0] = seed);
// Keep extracting until nothing gets put into next time list
while (floodSizes[curExtract])
{
U16 * extractFrom = mFloodInds[curExtract];
U16 * depositInto = mFloodInds[curDeposit = (curExtract ^ 1)];
for (S32 i = floodSizes[curExtract] - 1; i >= 0; i--)
{
GraphNode * node = gNavGraph->lookupNode(extractFrom[i]);
GraphEdgeArray edges = node->getEdges(NULL);
while (GraphEdge * edge = edges++) {
if (!mArmorPart->test(edge->mDest)) {
if (!edge->isJetting() || edge->canJet(ratings, needBoth)) {
mArmorPart->set(edge->mDest);
depositInto[floodSizes[curDeposit]++] = edge->mDest;
}
}
}
}
numFound += floodSizes[curExtract];
floodSizes[curExtract] = 0;
// Here's the flip. Note the previous line clears deposit size for next loop.
curExtract = curDeposit;
}
return numFound;
}
// Code to find a partition for one armor / energy configuration.
S32 JetManager::partitionOneArmor(JetPartition& list)
{
U32 memUsedBefore = Memory::getMemoryUsed();
S32 nodeCount = gNavGraph->numNodes();
// First time allocations. Also, newIncarnation() will clear them. Partition size
// spends a couple bytes to include transients so search code doesn't have to check.
if (! mArmorPart) {
mArmorPart = new GraphPartition();
mArmorPart->setSize(gNavGraph->numNodesAll());
mFloodInds[0] = new U16 [nodeCount];
mFloodInds[1] = new U16 [nodeCount];
}
S32 seedNode = 0;
S32 total = 0;
Vector<S32> saveSeedNodes;
// PASS 1
//
// First pass finds the 'stranded' component of each partition- this floods along
// only those edges that can be traversed BOTH WAYS.
//
list.clear();
mArmorPart->clear();
while (seedNode < nodeCount)
{
if (!mArmorPart->test(seedNode))
{
total += floodPartition (seedNode, true, list.ratings);
// Add the partition to the list, and remember the seed for next pass.
list.pushPartition(* mArmorPart);
saveSeedNodes.push_back(seedNode);
if (total == nodeCount)
break;
}
seedNode++;
}
// PASS 2
//
// This pass finds the downhill component of each partition, if different. Note that
// setDownhill() only adds a separate partition if the downhill one is different.
//
for (S32 i = 0; i < list.size(); i++)
{
mArmorPart->clear();
floodPartition (saveSeedNodes[i], false, list.ratings);
list[i].setDownhill(* mArmorPart);
}
U32 memUsedAfter = Memory::getMemoryUsed();
if (memUsedAfter > memUsedBefore)
NavigationGraph::sLoadMemUsed += (memUsedAfter - memUsedBefore);
return list.size();
}
//-------------------------------------------------------------------------------------
JetManager::Ability::Ability()
{
acc = dur = v0 = -111;
}
// Check for close enough abilities. So far the only thing I've seen change is the
// duration number.
bool JetManager::Ability::operator==(const Ability& ability)
{
return( mFabs(acc - ability.acc) < 0.01 &&
mFabs(dur - ability.dur) < 0.5 &&
mFabs(v0 - ability.v0) < 0.1
);
}
JetManager::ID::ID()
{
id = -1;
incarnation = -1;
}
void JetManager::ID::reset()
{
if (NavigationGraph::gotOneWeCanUse())
gNavGraph->jetManager().decRefCount(* this);
id = -1;
}
JetManager::ID::~ID()
{
// Could probably just use reset() here...
if (NavigationGraph::gotOneWeCanUse())
gNavGraph->jetManager().decRefCount(* this);
}
void JetManager::JetPartition::doConstruct()
{
ratings[0] = 0.0;
ratings[1] = 0.0;
users = 0;
used = false;
time = 0;
}
JetManager::JetPartition::JetPartition()
{
doConstruct();
}
//-------------------------------------------------------------------------------------
void JetManager::decRefCount(const ID& id)
{
if (id.id >= 0) {
JetPartition & jetPartition = mPartitions[id.id];
AssertFatal(jetPartition.users > 0, "JetManager ID management is not in sync");
// This is when it starts to grow old-
if (--jetPartition.users == 0)
jetPartition.time = Sim::getCurrentTime();
}
}
// Users update this with an id and a rating. Return true if a new one is created,
// telling consumer (nav path) to try to put off slow operations a little bit.
bool JetManager::update(ID& id, const Ability& ability)
{
// If missions cycles, or graph is rebuilt, id is out of date and should be reset-
if (id.incarnation != gNavGraph->incarnation())
{
id.incarnation = gNavGraph->incarnation();
id.id = -1;
}
// If new entry or no longer valid, find match or create new.
if (id.id < 0 || !(mPartitions[id.id].ability == ability))
{
decRefCount(id);
// Look for existing partition that matches this ability. We keep unused ones-
// If all have had abilities set on them, then take oldest one.
U32 oldAge = U32(-1);
JetPartition * slot = NULL;
JetPartition * oldest = NULL;
JetPartition * j = mPartitions;
for (S32 i = 0; i < AbsMaxBotCount; i++, j++)
{
if (j->ability == ability) {
j->users++;
id.id = i;
return false;
}
else {
// Look for free entry in case it's needed. First choice are those not yet
// in use. Second choice is the oldest one (smallest creation timestamp).
if (!slot && !j->users) {
if (!j->used)
slot = j;
else if (j->time < oldAge)
oldAge = j->time, oldest = j;
}
}
}
// No match found- create a new one with the slot found.
if (!slot)
slot = oldest;
AssertFatal(slot != NULL, "JetManager couldn't find empty slot");
id.id = (slot - mPartitions);
slot->ability = ability;
slot->users = 1;
slot->used = true;
calcJetRatings(slot->ratings, ability);
slot->setType(GraphPartition::Armor);
// Do the work-
partitionOneArmor(* slot);
// Inform caller that we did some slow work-
return true;
}
return false;
}
//-------------------------------------------------------------------------------------
// Here's the math. We've done the phsyics math accurately for the vertical distance,
// but we're just tweaking it for the lateral travel distance, which looks in practice
// to be about the same. That is, a bot can jet a flat XY distance that is close to
// how high straight up it can jet. But we scale down the lateral (increase what it
// _thinks_ it has to do) since this is just a heuristic. I'm not sure what the exact
// math is for estimating how far a bot can jet in any arc...
#define LateralExtra 2.00f
#define LateralScale 1.28f
F32 JetManager::modifiedLateral(F32 lateralDist)
{
return (lateralDist * LateralScale + LateralExtra);
}
F32 JetManager::modifiedDistance(F32 lateralDist, F32 verticalDist)
{
return verticalDist + modifiedLateral(lateralDist);
}
// In one case the public needs the actual XY from the lateral field, so this function
// is the inverse of modifiedLateral().
F32 JetManager::invertLateralMod(F32 lateralField)
{
F32 result = (lateralField - LateralExtra) * (1.0 / LateralScale);
return getMax(0.0f, result);
}
//-------------------------------------------------------------------------------------
// Public function that the aiNavJetting module uses for knowing when it has enough
// energy to launch. That code must use the same adjusted distance that
// the jetting edges use.
F32 JetManager::jetDistance(const Point3F& from, const Point3F& to) const
{
F32 vertical = (to.z - from.z);
Point2F lateral(to.x - from.x, to.y - from.y);
if (vertical > 0)
return modifiedDistance(lateral.len(), vertical);
else
return modifiedLateral(lateral.len());
}
//-------------------------------------------------------------------------------------
//
// All edge initialization comes through here, especially required since the
// partitioning needs at least one invariants holding on the edges, namely that
// the mDist field of a pair of edges between two nodes must be exactly the same.
//
void JetManager::initEdge(GraphEdge& edge, const GraphNode* src, const GraphNode* dst)
{
// The jet scale probably needs a little bit of work (and it seems like it really
// depends on the bot's energy levels. Any hop that uses less than half the
// bot's energy would probably be a good one to take, all else being equal.
if (!edge.isJetting())
edge.setInverse(src->edgeScale(dst));
else
edge.setInverse(calcJetScale(src, dst));
// Must assure that distances are the same both ways, so just to be sure we
// sort the locations. Partitioning flooding relies on this.
const Point3F& srcLoc = src->location();
const Point3F& dstLoc = dst->location();
Point3F high;
Point3F low;
if (srcLoc.z > dstLoc.z)
{
high = srcLoc;
low = dstLoc;
edge.setDown(edge.isJetting());
}
else
{
high = dstLoc;
low = srcLoc;
}
// Distance on edge is sum of horizontal + some extra + vertical. The extra
// is a buffer so they can get the lateral velicity going.
F32 absHeight = (high.z - low.z);
(high -= low).z = 0;
F32 lateral = high.len();
// edge.mDist = (lateral + absHeight + 2.0);
edge.mDist = modifiedDistance(lateral, absHeight);
// Get lateral distance for hops downward, little extra for buffer.
if (edge.isDown())
edge.setLateral(U8(modifiedLateral(lateral)));
// Set flatness. Must be conservative with it so that edges evaluate the
// same both ways for partioning purposes.
if (src->flat() && dst->flat())
edge.setJump(true);
}
// Ok - the indices are backwards from what's intuitive, but it's not a bug
// per se. We'll change to the intuitive if there's a convenient rebuild time...
// cf. The GraphBridgeData constructor.
void JetManager::replaceEdge(GraphBridgeData & B)
{
GraphNode * src = gNavGraph->lookupNode(B.nodes[1]);
GraphNode * dst = gNavGraph->lookupNode(B.nodes[0]);
GraphEdge * edge = src->getEdgeTo(dst);
AssertFatal(src && dst && edge, "JetManager::replaceEdge()");
if (B.isUnreachable())
{
edge->setSteep(true);
edge->setJetting();
edge->setImpossible();
}
else
{
edge->setJetting();
edge->setHop(B.jetClear);
initEdge(* edge, src, dst);
}
}
// Calculates scale factor on jetting edges for deciding when to do them.
F32 JetManager::calcJetScale(const GraphNode* from, const GraphNode* to) const
{
F32 scale;
F32 zdiff = (to->location().z - from->location().z);
if (zdiff > 0) {
// Jetting up adds to factor. Base amount is 3.0, then we
// also add 1 for every 10 meters going up beyond 20.
F32 per10 = mapValueLinear(zdiff, 20.0f, 120.0f, 0.0f, 10.0f);
scale = 3.0 + per10;
}
else {
// We'll make this number better once we have detection of
// which are walkable (walk-off-able) and which aren't.
scale = 1.4;
}
return scale;
}
#define GravityNum 0.625
// Given thrust and duration, see how far we can go.
F32 JetManager::calcJetRating(F32 thrust, F32 duration)
{
F32 tSqrd = duration * duration;
thrust -= GravityNum;
// How far we travel while applying the continuous force-
F32 pureJetDist = (0.5 * thrust * tSqrd * TickSec);
// Once jet is gone- final velocity carries up. More jet remains, but that's our
// reserve so we can be sure to cover the distance.
F32 finalVel = (thrust * duration);
F32 driftUp = (0.5 * finalVel / GravityNum) * finalVel;
return (pureJetDist + driftUp * TickSec);
}
// Calculate both ratings:
// 0 - without jumping
// 1 - can jump
//
// The ratings are a little bit conservative, but we need that buffer since the math
// isn't perfect for long hops. Mainly, we assume duration lasts as long as
// energy, whereas you actually get a little bit more from recharge- that's our buffer.
//
void JetManager::calcJetRatings(F32 * ratings, const Ability& ability)
{
ratings[0] = calcJetRating(ability.acc, ability.dur);
ratings[1] = ratings[0] + (ability.v0 * ability.dur * TickSec);
}
const F32 * JetManager::getRatings(const ID& jetCaps)
{
JetPartition & J = mPartitions[jetCaps.id];
AssertFatal(J.users > 0, "Bad call to JetManager::getRatings()");
return J.ratings;
}
GraphPartition::Answer JetManager::reachable(const ID& jetCaps, S32 from, S32 to)
{
AssertFatal(jetCaps.id >= 0, "Uninitialized ID given to JetManager::reachable()");
return mPartitions[jetCaps.id].reachable(from, to);
}
//-------------------------------------------------------------------------------------
// Estimate what percentage of energy is needed to make the given hop. Actually going
// from distance/ability to energy required is not performed anywhere, and instead of
// trying to invert the math, we'll just do a quick binary search. This only happens
// once when an edge is encountered in a path. And we save square roots! Ok I'm lazy.
F32 JetManager::estimateEnergy(const ID& jetCaps, const GraphEdge * edge)
{
JetPartition & J = mPartitions[jetCaps.id];
AssertFatal(J.users > 0, "JetManager::estimateEnergy()");
Ability interpAbility = J.ability;
F32 lower = 0.0;
F32 upper = 1.0;
F32 ratings[2];
// Eight iterations will get us within 1%
for (S32 i = 0; i < 8; i++)
{
F32 interpPercent = ((lower + upper) * 0.5);
interpAbility.dur = (interpPercent * J.ability.dur);
calcJetRatings(ratings, interpAbility);
if (edge->canJet(ratings))
upper = interpPercent;
else
lower = interpPercent;
}
// This is the lowest percentage we found which still works (or it's 1.0).
return upper;
}

69
ai/graphJetting.h Normal file
View file

@ -0,0 +1,69 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHJETTING_H_
#define _GRAPHJETTING_H_
class JetManager
{
public:
class ID
{
friend class JetManager;
S32 id;
S32 incarnation;
public:
ID();
~ID();
void reset();
};
struct Ability{ F32 acc; F32 dur; F32 v0; Ability(); bool operator==(const Ability&); };
friend class ID;
static F32 modifiedLateral(F32 xyDist);
static F32 modifiedDistance(F32 xyDist, F32 zDist);
static F32 invertLateralMod(F32 lateralField);
protected:
struct JetPartition : public PartitionList
{
F32 ratings[2];
Ability ability;
S32 users;
bool used;
U32 time;
JetPartition();
void doConstruct();
};
U16 * mFloodInds[2];
GraphPartition * mArmorPart;
JetPartition mPartitions[AbsMaxBotCount];
protected:
S32 floodPartition(U32 seed, bool needBoth, F32 ratings[2]);
void decRefCount(const ID& id);
public:
JetManager();
~JetManager();
S32 partitionOneArmor(JetPartition& list);
GraphPartition::Answer reachable(const ID& jetCaps, S32 from, S32 to);
const F32* getRatings(const ID& jetCaps);
F32 jetDistance(const Point3F& from, const Point3F& to) const;
F32 calcJetScale(const GraphNode* from, const GraphNode* to) const;
void initEdge(GraphEdge& edge, const GraphNode* src, const GraphNode* dst);
void replaceEdge(GraphBridgeData& replaceInfo);
F32 estimateEnergy(const ID& jetCaps, const GraphEdge * edge);
F32 calcJetRating(F32 thrust, F32 duration);
void calcJetRatings(F32 * ratings, const Ability& ability);
bool update(ID& id, const Ability& ability);
void clear();
};
#endif

225
ai/graphLOS.cc Normal file
View file

@ -0,0 +1,225 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "ai/graphLOS.h"
U32 Loser::mCasts = 0; // Just for informal profiling, counting casts.
static const F32 scGraphStepCheck = 0.75;
Loser::Loser(U32 mask, bool checkFF)
: mCheckingFF(checkFF),
mMask(mask)
{
mHitForceField = false;
}
bool Loser::haveLOS(const Point3F& src, const Point3F& dst)
{
if (mCheckingFF)
{
// Keep track if a force field is encountered. Walkable connections allow,
// jettable connections can't go through them.
U32 maskWithFF = (mMask | ForceFieldObjectType);
mCasts++;
mColl.object = NULL; // (Not sure if system clears)
if (gServerContainer.castRay(src, dst, maskWithFF, &mColl))
{
if (mColl.object->getTypeMask() & ForceFieldObjectType)
mHitForceField = true; // and fall through do normal LOS.
else
return false;
}
}
mCasts++;
mColl.object = NULL;
return !gServerContainer.castRay(src, dst, mMask, &mColl);
}
// Drop the point down to collision (if any) below. If no collision, we don't
// change the drop point, and return false.
bool Loser::hitBelow(Point3F& drop, F32 down)
{
Point3F below(drop.x, drop.y, drop.z - down);
if (gServerContainer.castRay(drop, below, mMask, &mColl))
{
drop = mColl.point;
return true;
}
return false;
}
// Height above, capped at UpHeightMax.
F32 Loser::heightUp(const Point3F& from, F32 maxUp)
{
Point3F up(from.x, from.y, from.z + maxUp);
if (haveLOS(from, up))
return maxUp;
else
return (mColl.point.z - from.z);
}
// Do LOS fanning one of the points. Just do a bunch of lines. We pre-increment
// and pre-decrement since the S-to-D los has already been checked.
bool Loser::fannedLOS1(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N)
{
Point3F D0 = D, D1 = D;
while(N--)
if(!haveLOS(S, D0 += inc) || !haveLOS(S, D1 -= inc))
return false;
return true;
}
// Do LOS fanning both points.
bool Loser::fannedLOS2(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N)
{
Point3F D0 = D, D1 = D, S0 = S, S1 = S;
while(N--)
if(!haveLOS(S0 += inc, D0 += inc) || !haveLOS(S1 -= inc, D1 -= inc))
return false;
return true;
}
// Do LOS checks downward to look for changes greater than step height.
bool Loser::walkOverBumps(Point3F S, Point3F D, F32 inc)
{
VectorF step = (D - S);
S32 N = getMax(S32(step.len() / inc), 2);
// Note D becomes our stepper directly under S.
F32 downDist = 140;
(D = S).z -= downDist;
if (haveLOS(S, D))
return false;
// Set up initial Z down.
F32 zdown = mColl.point.z;
step *= (1.0f / F32(N));
while (N--)
{
if (haveLOS(S += step, D += step))
return false;
if (mColl.object->getTypeMask() & WaterObjectType)
return false;
F32 getZ = mColl.point.z;
if (mFabs(getZ - zdown) > scGraphStepCheck)
return false;
// Save the Z, unless we're not walkable, in which case accumulate.
//==> Use proper slope values from PlayerData.
if (mColl.normal.z > gNavGlobs.mWalkableDot)
zdown = getZ;
}
return true;
}
// Fan the walk - this is SLOW - we'll use less resolution. Note we could try:
// ==> Walk from outside in (on all fanned checks), which will probably
// ==> result in earlier exits whenever false is the result.
// ==> Note we have to profile this whole process.
bool Loser::fannedBumpWalk(const Point3F& S, const Point3F& D, VectorF inc, S32 N)
{
Point3F D0 = D, D1 = D, S0 = S, S1 = S;
inc *= 2.0f;
while ((N -= 2) >= 0)
if (!walkOverBumps(S0 += inc, D0 += inc) || !walkOverBumps(S1 -= inc, D1 -= inc))
return false;
return true;
}
// (If both are outdoor, we'll look for a larger hop amount).
static const F32 sHopLookInc = 0.2;
// Move lines up, looking for a free line. We also want to assure that while there's
// not a free line, we at least have a minimum amount of breathing room - about 1.5m.
bool Loser::findHopLine(Point3F S, Point3F D, F32 maxUp, F32& freeHt)
{
F32 tThresh = (1.5f / (S - D).len());
F32 total = 0;
while (total < maxUp)
{
S.z += sHopLookInc;
D.z += sHopLookInc;
total += sHopLookInc;
if (haveLOS(S, D))
{
freeHt = total;
return true;
}
else
{
if (mColl.t < tThresh) // look for minimum amount in
break;
haveLOS(D, S); // ... from both directions
if (mColl.t < tThresh)
break;
}
}
return false;
}
#define GapWalkInc 0.15f
bool Loser::walkOverGaps(Point3F S, Point3F D, F32 allowedGap)
{
VectorF incVec = (D - S);
VectorF step2D(incVec.x, incVec.y, 0);
F32 inc2D = step2D.len();
S32 N = getMax(S32(inc2D / GapWalkInc) + 1, 2);
F32 bandWid = gNavGlobs.mStepHeight;
F32 saveL = 0, total2D = 0;
bool wasBelow = false;
// D serves as our stepper to do LOS down to
(D = S).z -= 100;
incVec *= (1.0f / F32(N));
inc2D *= (1.0f / F32(N));
while (N--)
{
bool los = !gServerContainer.castRay(S, D, mMask, &mColl);
// Must handle water-
if (!los)
if (mColl.object->getTypeMask() & WaterObjectType)
return false;
// check if we're below the allowed band-
if (los || mColl.point.z < (S.z - bandWid))
{
if (wasBelow)
{
if (total2D - saveL > allowedGap)
return false;
}
else
{
wasBelow = true;
saveL = total2D;
}
}
else
{
wasBelow = false;
saveL = total2D;
}
S += incVec;
D += incVec;
total2D += inc2D;
}
return true;
}

32
ai/graphLOS.h Normal file
View file

@ -0,0 +1,32 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHLOS_H_
#define _GRAPHLOS_H_
struct Loser
{
static U32 mCasts;
const bool mCheckingFF;
const U32 mMask;
RayInfo mColl;
bool mHitForceField;
Loser(U32 mask, bool checkFF = false);
bool haveLOS(const Point3F& src, const Point3F& dst);
bool fannedLOS1(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N);
bool fannedLOS2(const Point3F& S, const Point3F& D, const VectorF& inc, S32 N);
bool walkOverBumps(Point3F S, Point3F D, F32 inc = 0.2);
bool fannedBumpWalk(const Point3F& S, const Point3F& D, VectorF inc, S32 N);
bool findHopLine(Point3F S, Point3F D, F32 maxUp, F32& freeHt);
bool walkOverGaps(Point3F S, Point3F D, F32 allowedGap);
bool hitBelow(Point3F& dropPoint, F32 castDown);
F32 heightUp(const Point3F& from, F32 max);
};
#endif

529
ai/graphLocate.cc Normal file
View file

@ -0,0 +1,529 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#define WaitAfterNearbyFound 11
#define WaitAfterNothingFound 16
// Just want to construct a really good proximity for forcing sorting first below-
static struct BestProx: public NodeProximity{ BestProx(){makeGood();} } sBestProximity;
//-------------------------------------------------------------------------------------
bool NodeProximity::possible() const
{
return (mLateral < 4.0);
}
ProximateNode::ProximateNode(const NodeProximity& p, GraphNode* n)
{
mProximity = p;
mNode = n;
}
static S32 QSORT_CALLBACK varCompare(const void* a,const void* b)
{
ProximateNode * proxA = (ProximateNode*)a;
ProximateNode * proxB = (ProximateNode*)b;
if (proxA->mProximity < proxB->mProximity)
return -1;
else if (proxA->mProximity > proxB->mProximity)
return 1;
else
return 0;
}
void ProximateList::sort()
{
dQsort((void *)address(), size(), sizeof(ProximateNode), varCompare);
}
//-------------------------------------------------------------------------------------
// Ok - "dist" searcher is now a misnomer. It's really a depth searcher, and it's
// only used for the locator.
GraphSearchDist::GraphSearchDist()
{
mBestMatch = NULL;
mPoint.set(0,0,0);
mMaxSearchDist = 100;
setOnDepth(false);
}
void GraphSearchDist::setDistCap(F32 d)
{
mMaxSearchDist = d;
}
// For the graph locator, we actually just want to search out to a certain DEPTH,
// rather than on distance (go a few connections removed).
F32 GraphSearchDist::getEdgeTime(const GraphEdge* edge)
{
if (mSearchOnDepth)
return 1.001f;
else
return edge->mDist;
}
// The tracker needs to know travel distance if the location has left it's last node.
void GraphSearchDist::onQExtraction()
{
GraphNode * extractNode = extractedNode();
if (extractNode->indoor())
{
NodeProximity metric = extractNode->containment(mPoint);
if (metric.inside())
{
mBestMatch = extractNode;
mBestMetric = metric;
mProximate.clear();
setEarlyOut(true);
}
else
{
// We need to track if we've had any really close matches, and then
// disregard the high ones. Note that the definition of "really close"
// must probably account for whether the destination has a bounding box
// or not.
if (metric.possible())
if (metric < mCurThreshold)
{
// Add to the list of possibilities.
ProximateNode candidate(metric, extractNode);
mProximate.push_back(candidate);
if (metric < mBestMetric)
{
mBestMetric = metric;
mCurThreshold = getMax(F32(metric), 1.0f);
}
}
}
F32 thusFar = (mSearchOnDepth ? timeSoFar() : distSoFar());
if (thusFar > mMaxSearchDist)
setEarlyOut(true);
}
}
// Find the best indoor node to connect to, return metric for how good.
// The lower the number the better (using distance from planes).
NodeProximity GraphSearchDist::findBestMatch(GraphNode * current, const Point3F& loc)
{
// Set threshold based on how good the initial match is.
mBestMatch = NULL;
mBestMetric.makeBad();
mProximate.clear();
if (!current)
current = gNavGraph->closestNode(loc);
if (current)
{
setEarlyOut(false);
mPoint = loc;
mCurThreshold = 1e13;
if (current->indoor())
setDistCap(3.3);
else
setDistCap(2.2);
setOnDepth(true);
performSearch(current);
setOnDepth(false);
}
return mBestMetric;
}
//-------------------------------------------------------------------------------------
GraphLocate::GraphLocate()
{
reset();
mMounted = false;
}
void GraphLocate::reset()
{
mLocation.set(1e13,1e13,1e13);
mLoc2D = mLocation;
mTraveled = 1e13;
mClosest = NULL;
mCounter = 0;
mTerrain = false;
mUpInAir = false;
}
void GraphLocate::forceCheck()
{
mCounter = 0;
mTraveled = 1e13;
}
//-------------------------------------------------------------------------------------
static const U32 sMask = InteriorObjectType;
static bool haveLOS(Point3F src, Point3F dst, bool indoor)
{
static RayInfo coll;
src.z += 0.13;
dst.z += 0.13;
// Probably don't usually need collide with terrain at all, but there are some cases
// on proximity check to indoor nodes.
U32 mask = indoor ? (sMask|TerrainObjectType) : sMask;
if (!gServerContainer.castRay(src, dst, mask, &coll))
return true;
else {
// check vehicle collision- ?
return false;
}
}
// We allow a slight increase on the lower height, up to step height (see Generators
// down in Masada bunker - top of steps, fail LOS).
static bool checkLOS(const Point3F& src, const Point3F& dst, bool indoor)
{
if (!haveLOS(src, dst, indoor))
{
// Do more liberal check, raising up the lower point by up to 0.7 (~step height).
Point3F high, low;
if (src.z < dst.z)
low = src, high = dst;
else
low = dst, high = src;
low.z += getMin(0.7f, high.z - low.z);
return haveLOS(low, high, indoor);
}
return true;
}
//-------------------------------------------------------------------------------------
bool GraphLocate::canHookTo(const GraphLocate& other, bool& mustJet) const
{
bool canHook = false;
if (mClosest && other.mClosest)
{
if (onTerrain() && other.onTerrain())
{
canHook = (mClosest == other.mClosest || mClosest->neighbors(other.mClosest));
}
else
{
// might want more checking here (which is why the clause is separate...)
canHook = (mClosest == other.mClosest);
}
}
if (canHook)
mustJet = (mUpInAir || other.mUpInAir);
return canHook;
}
void GraphLocate::setMounted(bool b)
{
if (mMounted != b)
{
mMounted = b;
mClosest = NULL;
forceCheck();
}
}
// I think we can get away with doing the 2D check here since it's established that
// we're Ok in 3D and we're not looking at jet connections. (The reason it's needed
// is because guys (especially heavies) are actually far off the edge in Z on slopes!).
bool GraphLocate::checkRegularEdge(Point3F fromLoc, GraphEdge* edge)
{
GraphNode * destNode = gNavGraph->lookupNode(edge->mDest);
Point3F destLoc = destNode->location();
destLoc.z = fromLoc.z = 0.0f;
LineSegment edgeSeg(fromLoc, destLoc);
//==> If we can preprocess a walking width onto these edges, that could be good.
//==> For now the value is hard coded.
F32 dist = edgeSeg.distance(mLoc2D);
return (dist <= 0.8);
}
ProximateNode * GraphLocate::examineCandidates(ProximateList& proximate)
{
ProximateNode * bestIndoor = NULL;
ProximateNode * bestOutdoor = NULL;
mConnections.clear();
if (proximate.size())
{
proximate.sort();
for (ProximateList::iterator i = proximate.begin(); i != proximate.end(); i++)
{
bool isIndoor = i->mNode->indoor();
if (checkLOS(i->mNode->location(), mLocation, isIndoor))
{
pushEdge(i->mNode);
if (bestIndoor == NULL) // check for return value
{
if (isIndoor)
bestIndoor = i;
else
bestOutdoor = i;
}
}
if (mConnections.size() > 3)
break;
}
}
else
{
#if _GRAPH_WARNINGS_
NavigationGraph::warning("No proximate nodes found in graph locator");
#endif
}
// See if we want final jetting connection, and set that only for the best match.
if (mMounted && mConnections.size())
{
mConnections[0].mBorder = -1;
mConnections[0].setJetting();
}
if (bestIndoor)
return bestIndoor;
else
return bestOutdoor;
}
// Fetch neighbors onto list. Be careful with all the different edge types (jet,
// border edges, walkable straight connections). Routine is only called when we're
// inside the node, so we can connect to all except certain straight line edges.
void GraphLocate::getNeighbors(GraphNode* ofNode, bool outdoor, bool makeJet)
{
static GraphEdge edgeBuffer[MaxOnDemandEdges];
GraphEdgeArray edgeList = ofNode->getEdges(edgeBuffer);
S32 outdoorNumber = gNavGraph->numOutdoor();
Point3F fromLoc = ofNode->location();
if (outdoor) {
while(GraphEdge * edge = edgeList++) {
if(edge->mDest < outdoorNumber) {
GraphEdge connection = * edge;
if (!connection.isSteep())
{
if (makeJet || connection.isJetting())
connection.setJetting();
mConnections.push_back(connection);
}
}
else {
// Check if we're near the edge. Note we know this isn't a 'border' edge
// because terrains nodes don't have border edges going out.
if (!edge->isJetting() && checkRegularEdge(fromLoc, edge))
mConnections.push_back(* edge);
}
}
}
else {
// From indoor node we're inside.
while (GraphEdge * edge = edgeList++) {
if (edge->mDest >= outdoorNumber) { // (indoor dest)
if (makeJet) {
// If we're inside the neighbor's top and bottom planes, we can make
// a jet connection.
if (gNavGraph->verticallyInside(edge->mDest, mLocation)) {
GraphNode * destNode = gNavGraph->lookupNode(edge->mDest);
if (gNavGraph->possibleToJet(mLocation, destNode->location())) {
GraphEdge jetEdge;
jetEdge.mDest = edge->mDest;
jetEdge.setJetting();
jetEdge.mDist = edge->mDist;
jetEdge.copyInverse(edge);
mConnections.push_back(jetEdge);
}
}
}
else {
if (!edge->isJetting())
if (edge->isBorder() || checkRegularEdge(fromLoc, edge))
mConnections.push_back(* edge);
}
}
else { // Neighbor is outdoor-
if (makeJet) {
// Since this indoor node has a terrain neighbor, we know the location
// is above the terain. However the loc could be under top of node
// in the case of some shaded nodes.
GraphNode * destNode = gNavGraph->lookupNode(edge->mDest);
Point3F destLoc = destNode->location();
F32 topZ = (destLoc.z + destNode->terrHeight());
if (mLocation.z < topZ) {
if (gNavGraph->possibleToJet(mLocation, destLoc)) {
mConnections.push_back(* edge);
mConnections.last().setJetting();
}
}
}
else {
// Can check near edge here on other types of connections.
if (!edge->isJetting() && checkRegularEdge(fromLoc, edge))
mConnections.push_back(* edge);
}
}
}
}
}
void GraphLocate::pushEdge(GraphNode * to, F32 * getCosine)
{
GraphEdge edge;
edge.mDest = to->getIndex();
Point3F toLoc = to->location();
edge.mDist = (toLoc -= mLocation).len();
// Added for steepness detection (LH 11/12/00).
if (getCosine)
{
if (edge.mDist < 0.01)
* getCosine = 1.0;
else
{
toLoc.z = 0;
* getCosine = (toLoc.len() / edge.mDist);
}
}
mConnections.push_back(edge);
}
// We're inside this one, so we can hook to it and all neighbors.
void GraphLocate::beLikeNode(GraphNode* ourNode)
{
mConnections.clear();
bool tryJet = false;
bool outdoor = !ourNode->indoor();
if (mMounted) {
if (outdoor) {
F32 height;
if (gNavGraph->terrainHeight(mLocation, &height))
tryJet = (mLocation.z - height) > 2.0;
}
else {
// Handle indoor with volume checks. Since we know we're inside, just
// check the height above the floor.
if (gNavGraph->heightAboveFloor(ourNode->getIndex(), mLocation) > 2.0)
tryJet = true;
}
}
// Get all neighbors of the node we're inside of
getNeighbors(mClosest = ourNode, outdoor, tryJet);
F32 cosAngle = 1.0;
pushEdge(mClosest, outdoor ? &cosAngle : NULL);
if (tryJet || cosAngle < gNavGlobs.mPrettySteepDot)
mConnections.last().setJetting();
mTraveled = 0.0f;
mCounter = 0;
mUpInAir = tryJet;
}
void GraphLocate::update(const Point3F& newLoc)
{
bool searchIsReady = ((--mCounter < 0) && (mTraveled > 0));
F32 moveDist = (mLocation - newLoc).len();
// Update the traveled total
mTraveled += moveDist;
// Check for big jumps - redo search.
// if (moveDist > 30.0)
if (moveDist > 5.0)
mClosest = NULL;
if ((moveDist > 0.1) || searchIsReady)
{
mLocation = newLoc;
mLoc2D.set(mLocation.x, mLocation.y, 0.0);
mUpInAir = false;
if (GraphNode * onTerr = gNavGraph->findTerrainNode(mLocation))
{
mTerrain = true;
mMetric.makeBad();
beLikeNode(onTerr);
}
else
{
mTerrain = false;
NodeProximity newMetric;
// If the node match is good, it means we can afford to search as soon as
// the location leaves that node. Note we can move inside a node that
// we were just near to before, so the connections should be remade.
if (mClosest && (newMetric = mClosest->containment(mLocation)).inside())
{
if (!mMetric.inside())
beLikeNode(mClosest);
else if (mCounter < 0)
{
// Want to re-evaluate 'straight-line' edge connections every so often.
if (mCounter < -4)
beLikeNode(mClosest);
}
else
mCounter = 0;
mMetric = newMetric;
}
else if (searchIsReady)
{
GraphSearchDist * searcher = gNavGraph->getDistSearcher();
mMetric = searcher->findBestMatch(mClosest, mLocation);
if (searcher->bestMatch())
{
beLikeNode(searcher->bestMatch());
}
else
{
// Check proximate list.
ProximateList& nearby = searcher->getProximate();
if (GraphNode * terr = gNavGraph->nearbyTerrainNode(mLocation))
{
ProximateNode willSortFirst(sBestProximity, terr);
nearby.push_back(willSortFirst);
}
if (ProximateNode * bestBet = examineCandidates(nearby))
{
mClosest = bestBet->mNode;
mCounter = WaitAfterNearbyFound;
mTraveled = getMax( static_cast<F32>( bestBet->mProximity ), 0.0f );
}
else
{
// When nothing found, we don't reset the travel distance - this
// will increase the search distance gradually in the case where
// we somehow miss a match, get out of system. Uh, should work..
mClosest = NULL;
mCounter = WaitAfterNothingFound;
}
mCounter += (gRandGen.randI() & 3); // Little bit of stagger
}
}
}
}
}

76
ai/graphLocate.h Normal file
View file

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHLOCATE_H_
#define _GRAPHLOCATE_H_
struct ProximateNode
{
GraphNode * mNode;
NodeProximity mProximity;
ProximateNode(const NodeProximity& p, GraphNode* n);
};
class ProximateList : public Vector<ProximateNode>
{
public:
void sort();
};
class GraphLocate : public GraphNodeList
{
protected:
Point3F mLocation;
Point3F mLoc2D;
F32 mTraveled;
S32 mCounter;
bool mUpInAir;
bool mMounted;
GraphNode * mClosest;
bool mTerrain;
NodeProximity mMetric;
GraphEdgeList mConnections;
protected:
void getNeighbors(GraphNode* of, bool outdoor, bool makeJet=false);
bool checkRegularEdge(Point3F from, GraphEdge* to);
ProximateNode * examineCandidates(ProximateList& proximate);
void beLikeNode(GraphNode* ourNode);
void pushEdge(GraphNode* to, F32* getCos=NULL);
public:
GraphLocate();
void update(const Point3F& loc);
bool canHookTo(const GraphLocate&, bool& jet) const;
void reset();
void setMounted(bool b);
void forceCheck();
const GraphEdgeList& getEdges() const {return mConnections;}
GraphNode * bestMatch() const {return mClosest;}
NodeProximity bestMetric() const {return mMetric;}
bool onTerrain() const {return mTerrain;}
bool isMounted() const {return mMounted;}
void cleanup() {reset(); mMounted=false;}
};
class FindGraphNode // graphFind.cc
{
private:
GraphNode * mClosest;
Point3F mPoint;
U32 calcHash(const Point3F& point);
public:
enum {HashTableSize = 307};
FindGraphNode();
FindGraphNode(const Point3F& pt, GraphNode* hint = 0);
void setPoint(const Point3F& pt, GraphNode * hint = 0);
GraphNode * closest() const {return mClosest;}
void init();
};
#endif

363
ai/graphMake.cc Normal file
View file

@ -0,0 +1,363 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "terrain/terrRender.h"
//-------------------------------------------------------------------------------------
#ifdef DEBUG
#define DebugCheckHanging 1
#else
#define DebugCheckHanging 0
#endif
//-------------------------------------------------------------------------------------
static bool cullOneEdgeDup(GraphEdgeList& edges, BitVector& mark)
{
GraphEdgeList::iterator it, cull = NULL;
for (it = edges.begin(); it != edges.end(); it++)
if (mark.test(it->mDest)) {
cull = it;
break;
}
else
mark.set(it->mDest);
for (it = edges.begin(); it != edges.end(); it++)
mark.clear(it->mDest);
if (cull)
edges.erase_fast(cull);
return (cull != NULL);
}
//-------------------------------------------------------------------------------------
// This is where distances are calculated. Also used to verify that all edges have a
// counterpart coming back. Other final stuff has found its way here as well...
S32 NavigationGraph::doFinalFixups()
{
S32 numberHanging = 0, i;
GraphEdge edgeBuffOuter[MaxOnDemandEdges];
GraphEdgeArray edgeListOuter;
sTotalEdgeCount = 0;
for (i = 0; i < mNodeList.size(); i++)
{
if (GraphNode * node = mNodeList[i])
{
edgeListOuter = node->getEdges(edgeBuffOuter);
sTotalEdgeCount += edgeListOuter.numEdges();
while (GraphEdge * edgePtrOuter = edgeListOuter++)
{
GraphNode * neighbor = mNodeList[edgePtrOuter->mDest];
AssertFatal(neighbor, "graph has bad neighbor indices");
// Main edge setup method-
mJetManager.initEdge(*edgePtrOuter, node, neighbor);
#if DebugCheckHanging
if (edgePtrOuter->mDest == i)
Con::errorf("Node (%d) has edge pointing to self!", i);
// This debugging check only needed when we're modifying generation tools.
// Otherwise it's slow, and the generation is pretty solid anyway.
static GraphEdge sEdgeBuffInner[MaxOnDemandEdges];
bool found = false;
GraphEdgeArray edgeListInner = neighbor->getEdges(sEdgeBuffInner);
while (GraphEdge * edgePtrInner = edgeListInner++)
if (edgePtrInner->mDest == i){
found = true;
break;
}
if (!found)
{
numberHanging++;
Point3F P = node->location();
Point3F N = neighbor->location();
S32 D = edgePtrOuter->mDest;
Con::errorf("Hanging edge from %d (%f, %f, %f) to %d (%f, %f, %f)",
i, P.x, P.y, P.z, D, N.x, N.y, N.z);
}
#endif
}
}
}
// Keep separate list of the non-transients.
mTransientStart = mNodeList.size();
mNonTransient.reserve(mNodeList.size() + mMaxTransients);
mNonTransient = mNodeList;
for (i = 0; i < mMaxTransients; i++ )
mNodeList.push_back(NULL);
// Trim duplicate edges. This is rare (interior generator can get a strange
// boundary with parallel edges), and causes problems. Not the best fix for
// this problem (see graphTransient.cc attempt), but reasonable.
BitVector marker(mNodeList.size());
S32 numDups = 0;
marker.clear();
for (i = 0; i < mNodeList.size(); i++)
if (GraphNode * node = mNodeList[i])
while (cullOneEdgeDup(node->mEdges, marker))
numDups++;
if (numDups)
Con::printf("%d duplicate edges found on nodes.", numDups);
// Good idea to mark islands AFTER checking for hanging.
markIslands();
// See navGraph.cc-
newIncarnation();
mValidLOSTable = (mLOSTable && mLOSTable->valid(numNodes()));
mValidPathTable = false; // this has been dropped...
return numberHanging;
}
//-------------------------------------------------------------------------------------
//
// Assemble the graph from data that has been stored on it, computed, etc.
//
bool NavigationGraph::makeGraph(bool needEdgePool)
{
mPushedBridges = 0;
makeRunTimeNodes(needEdgePool); // (graphOutdoors.cc)
Con::printf("MakeGraph: %d indoor, %d outdoor", mNumIndoor, mNumOutdoor);
mIndoorPtrs.clear(); // convenience list
mIndoorTree.clear(); // BSP for it
if (mNumIndoor)
{
mIndoorPtrs.reserve(mNumIndoor);
for (S32 i = 0; i < mNumIndoor; i++)
mIndoorPtrs.push_back(mNodeList[i + mNumOutdoor]);
// Set up tree for quickly finding indoor nodes-
mIndoorTree.makeTree(mIndoorPtrs);
}
return true;
}
//-------------------------------------------------------------------------------------
// For now this just makes the roaming distance table.
// We compute these data separately since they take a while.
S32 NavigationGraph::makeTables()
{
// Get a table of ordered offsets.
mTerrainInfo.computeRoamRadii();
return 0;
}
//-------------------------------------------------------------------------------------
// Initialize the terrain info data from the ground plan.
bool NavigationGraph::setGround(GroundPlan * gp)
{
mTerrainInfo.haveGraph = true;
if (gp->setTerrainGraphInfo(&mTerrainInfo))
{
mTerrainInfo.doFinalDataSetup();
mTerrainInfo.consolidateData(mConjoin);
}
else
{
mTerrainInfo.haveGraph = false;
}
return mTerrainInfo.haveGraph;
}
//-------------------------------------------------------------------------------------
#define LOOK_AT_SPECIFIC_NODE 0
#if LOOK_AT_SPECIFIC_NODE
Point3F gSpecificNodeLoc(-149, -131.828, 163);
F32 gSpecificNodeRad = 9.0;
S32 findSpecificNode(const NodeInfoList& nodes)
{
S32 found = -1;
for (S32 i = 0; i < nodes.size(); i++)
{
Point3F point = nodes[i].pos;
if ((point - gSpecificNodeLoc).len() < gSpecificNodeRad)
Con::printf("Node %d is where it's at", found = i);
}
return found;
}
#endif
//-------------------------------------------------------------------------------------
// Get / Set interior node data. This doesn't affect the graph (building
// the graph from indoor and outdoor databases is a step that must be
// called explicitly).
S32 NavigationGraph::getNodesAndEdges(EdgeInfoList& edges, NodeInfoList& nodes)
{
edges = mEdgeInfoList;
nodes = mNodeInfoList;
return nodes.size();
}
// Set the system up with the given list of edges and nodes. Resets / hooks up
// nodes indoors.
S32 NavigationGraph::setNodesAndEdges(const EdgeInfoList& edges, const NodeInfoList& nodes)
{
mEdgeInfoList = edges;
mNodeInfoList = nodes;
return nodes.size();
}
//-------------------------------------------------------------------------------------
S32 NavigationGraph::setEdgesAndNodes(const EdgeInfoList& edges, const NodeInfoList& nodes)
{
return setAlgorithmic(edges, nodes);
}
//-------------------------------------------------------------------------------------
// Interior node generator calls this to add data to graph, plus these lists
// then become hand-editable. We always clean out existing algorithmic first.
S32 NavigationGraph::setAlgorithmic(const EdgeInfoList& edges, const NodeInfoList& nodes)
{
#if LOOK_AT_SPECIFIC_NODE
findSpecificNode(nodes);
#endif
clearAlgorithmic();
// Add the edges with remapped dest index, and marked as algorithmic.
S32 indexAdd = mNodeInfoList.size();
for (EdgeInfoList::const_iterator e = edges.begin(); e != edges.end(); e++) {
GraphEdgeInfo edge = * e;
edge.setAlgorithmic();
edge.to[0].dest += indexAdd;
edge.to[1].dest += indexAdd;
mEdgeInfoList.push_back(edge);
}
for (NodeInfoList::const_iterator n = nodes.begin(); n != nodes.end(); n++) {
IndoorNodeInfo node = * n;
node.setAlgorithmic();
mNodeInfoList.push_back(node);
}
return 0;
}
// Clear algorithmic nodes out of main list, plus edges that connect to any.
S32 NavigationGraph::clearAlgorithmic()
{
Vector<S32> mapIndices;
NodeInfoList keepNodes;
EdgeInfoList keepEdges;
setSizeAndClear(mapIndices, mNodeInfoList.size(), 0xff);
// get which nodes we're keeping and set up remap
for (S32 i = 0; i < mNodeInfoList.size(); i++)
if (mNodeInfoList[i].isAlgorithmic() == false) {
mapIndices[i] = keepNodes.size();
keepNodes.push_back(mNodeInfoList[i]);
}
// get the edges we're keeping and remap their destination indices
EdgeInfoList::iterator edge;
for (edge = mEdgeInfoList.begin(); edge != mEdgeInfoList.end(); edge++)
if (edge->isAlgorithmic() == false)
if (mNodeInfoList[edge->to[0].dest].isAlgorithmic() == false)
if (mNodeInfoList[edge->to[1].dest].isAlgorithmic() == false) {
for (S32 j = 0; j < 2; j++)
edge->to[j].dest = mapIndices[edge->to[j].dest];
keepEdges.push_back(*edge);
}
// copy back the culled lists
mNodeInfoList = keepNodes;
mEdgeInfoList = keepEdges;
return mNodeInfoList.size();
}
//-------------------------------------------------------------------------------------
S32 NavigationGraph::setNodeVolumes(const GraphVolumeList& volumes)
{
mNodeVolumes = volumes;
mNodeVolumes.nudgeVolumesOut();
return mNodeVolumes.size();
}
//-------------------------------------------------------------------------------------
S32 NavigationGraph::setChuteHints(const Vector<Point3F>& hints)
{
S32 numHints = hints.size();
mChutes.init(hints);
return numHints;
}
//-------------------------------------------------------------------------------------
static const U32 sMask = InteriorObjectType|StaticShapeObjectType|TerrainObjectType;
// The path logic as it pertains to jetting connections (often) needs to know for sure
// that the player can jump (makes a big difference in achievable height). This method
// must figure this out. It's Ok if we miss a few where it's possible to jump, but we
// don't want to err the other way (SOMETIMES can't jump, but think we can).
S32 NavigationGraph::findJumpableNodes()
{
S32 total = 0;
if (!mIsSpawnGraph)
{
F32 check45 = mSqrt(0.5);
RayInfo coll;
S32 i, j;
Point3F down(0, 0, -6);
// ==> As an optimization, we should only do this with nodes that you jet out of.
// Um, because of where this is called, we can't use numNodes()-
S32 numNodes = (mNumIndoor + mNumOutdoor);
for (i = 0; i < numNodes; i++) {
if (GraphNode * node = lookupNode(i)) {
if (node->getNormal().z > check45) {
// Cast several rays down and check their normals.
Point3F loc = node->location();
loc += Point3F(-0.8, -0.9, 2.0);
for (j = 0; j < 4; j++) {
Point3F corner((j & 1 != 0) * 1.6, (j & 2 != 0) * 1.7, 0.0);
corner += loc;
if (gServerContainer.castRay(corner, corner + down, sMask, &coll))
if (coll.normal.z < check45)
break;
}
if (j == 4) {
node->set(GraphNode::Flat);
total++;
}
}
}
}
}
return total;
}

341
ai/graphMath.cc Normal file
View file

@ -0,0 +1,341 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graphMath.h"
#include "Core/realComp.h"
#include "ai/tBinHeap.h"
//-------------------------------------------------------------------------------------
F32 solveForZ(const PlaneF& plane, const Point3F& point)
{
AssertFatal(mFabs(plane.z) > 0.0001, "solveForZ() expects non-vertical plane");
return (-plane.d - plane.x * point.x - plane.y * point.y) / plane.z;
}
//-------------------------------------------------------------------------------------
// Simple "iterator", used as in-
// for (mArea.start(step); mArea.pointInRect(step); mArea.step(step))
// ...
bool GridArea::start(Point2I& steppingPoint) const
{
if (isValidRect())
{
steppingPoint = point;
return true;
}
return false;
}
bool GridArea::step(Point2I& steppingPoint) const
{
if (++steppingPoint.x >= (point.x + extent.x))
{
steppingPoint.x = point.x;
if (++steppingPoint.y >= (point.y + extent.y))
return false;
}
return true;
}
//-------------------------------------------------------------------------------------
GridVisitor::GridVisitor(const GridArea & area)
: mArea(area.point, area.extent)
{
mPostCheck = mPreCheck = true;
}
// Default versions of virtuals. The Before and After callbacks just remove themselves:
bool GridVisitor::beforeDivide(const GridArea&,S32) {return !(mPreCheck = false);}
bool GridVisitor::atLevelZero(const GridArea&) {return true; }
bool GridVisitor::afterDivide(const GridArea&,S32,bool) {return !(mPostCheck= false);}
// Recursive region traversal.
// R is a box of width 2^L, and aligned on like boundary (L low bits all zero)
bool GridVisitor::recurse(GridArea R, S32 L)
{
bool success = true;
if (! R.overlaps(mArea)) {
success = false;
}
else if (L == 0) {
success = atLevelZero(R);
}
else if (mPreCheck && mArea.contains(R) && !beforeDivide(R, L)) { // early out?
success = false;
}
else {
// Made it to sub-division!
S32 half = 1 << (L - 1);
for (S32 y = half; y >= 0; y -= half)
for (S32 x = half; x >= 0; x -= half)
if (!recurse(GridArea(R.point.x + x, R.point.y + y, half, half), L - 1))
success = false;
// Post order stuff-
if (mPostCheck && mArea.contains(R) && !afterDivide(R, L, success))
success = false;
}
return success;
}
bool GridVisitor::traverse()
{
S32 level = 1;
GridArea powerTwoRect (-1, -1, 2, 2);
// Find the power-of-two-sized rect that encloses user-supplied grid.
while (!powerTwoRect.contains(mArea))
if (++level < 31) {
powerTwoRect.point *= 2;
powerTwoRect.extent *= 2;
}
else
return false;
// We have our rect and starting level, perform the recursive traversal:
return recurse(powerTwoRect, level);
}
//-------------------------------------------------------------------------------------
// Find distance of point from the segment. If the point's projection onto
// line isn't within the segment, then take distance from the endpoints.
//
// This also leaves the closest point set in soln.
//
F32 LineSegment::distance(const Point3F & p)
{
VectorF vec1 = b - a, vec2 = p - a;
F32 len = vec1.len(), dist = vec2.len();
soln = a; // good starting default.
// First check for case where A and B are basically same (avoid overflow) -
// can return distance from either point.
if( len > 0.001 )
{
// normalize vector from A to B and get projection of AP along it.
F32 dot = mDot( vec1 *= (1/len), vec2 );
if(dot >= 0)
{
if(dot <= len)
{
F32 sideSquared = (dist * dist) - (dot * dot);
if (sideSquared <= 0) // slight imprecision led to NaN
dist = sideSquared = 0;
else
dist = mSqrt(sideSquared);
soln += (vec1 * dot);
}
else
{
soln = b;
dist = (b - p).len();
}
}
}
return dist;
}
// Special check which does different math in 3D and 2D. If the test is passed
// in 3d, then do a 2d check.
bool LineSegment::botDistCheck(const Point3F& p, F32 dist3, F32 dist2)
{
F32 d3 = distance(p);
if (d3 < dist3)
{
Point2F proj2d(soln.x - p.x, soln.y - p.y);
F32 d2 = proj2d.lenSquared();
dist2 *= dist2;
return (d2 < dist2);
}
return false;
}
//-------------------------------------------------------------------------------------
// Get a list of grid offsets that "spirals" out in order of closeness.
// This is used by the navigation (preprocess) code to find nearest
// obstructed grid location, and it uses this for its search order.
S32 getGridSpiral(Vector<Point2I> & spiral, Vector<F32> & dists, S32 radius)
{
S32 x, y, i;
Vector<Point2I> pool;
BinHeap<F32> heap;
// Build all the points that we'll want to consider. We're going to
// leave off those points that are outside of circle.
for( y = -radius; y <= radius; y++ )
for( x = -radius; x <= radius; x++ )
{
F32 dist = Point2F( F32(x), F32(y) ).len();
if( dist <= F32(radius) + 0.00001 )
{
pool.push_back( Point2I(x,y) );
heap.insert( -dist ); // negate for sort order
}
}
// Get the elements out in order.
heap.buildHeap();
while( (i = heap.headIndex()) >= 0 )
{
spiral.push_back( pool[i] );
dists.push_back( -heap[i] ); // get the (sign-restored) distance
heap.removeHead();
}
return spiral.size();
}
//-----------------------------------------------------------------
// Line Stepper class.
void LineStepper::init(const Point3F & a, const Point3F & b)
{
solution = A = a, B = b;
total = (dir1 = B - A).len();
soFar = advance = 0.0f;
if( total > 0.00001 )
{
dir1 *= (1 / total);
}
else
{
total = 0.0f;
dir1.set(0,0,0);
}
}
//
// Finds the intersection of the line with the sphere on the way out. If there
// are two intersections, and we're starting outside, this should find the
// second.
//
F32 LineStepper::getOutboundIntersection(const SphereF & S)
{
// get projection of sphere center along our line
F32 project = mDot( S.center - A, dir1 );
// find point nearest to center of sphere
Point3F nearPoint = A + (dir1 * project);
// if this isn't in the sphere, then there's no intersection. negative return flags this.
if( ! S.isContained(nearPoint) )
return -1.0f;
// there is an intersection, figure how far from nearest point it is
F32 length = mSqrt( S.radius*S.radius - (nearPoint-S.center).lenSquared() );
// find the solution point and advance amount (which is return value)
solution = nearPoint + dir1 * length;
return( advance = length + project );
}
const Point3F& LineStepper::advanceToSolution()
{
if( advance != 0.0f )
{
soFar += advance;
A = solution;
advance = 0.0f;
}
return solution;
}
//---------------------------------------------------------------
// Finds the center of Mass of a CONVEX polygon.
// Verts should contain an array of Point2F's, in order,
// and of size n, that will represent the polygon.
//---------------------------------------------------------------
bool polygonCM(S32 n, Point2F *verts, Point2F *CM)
{
if(!verts || n < 3)
return false;
F32 area, area2 = 0.0;
Point2F cent;
CM->x = 0;
CM->y = 0;
U32 i;
for(i = 1; i < (n-1); i++)
{
triCenter(verts[0], verts[i], verts[i+1], cent);
area = triArea(verts[0], verts[i], verts[i+1]);
CM->x += area * cent.x;
CM->y += area * cent.y;
area2 += area;
}
CM->x /= 3 * area2;
CM->y /= 3 * area2;
return true;
}
//-------------------------------------------------------------------------------------
// Lifted from tools/morian/CSGBrush.cc. This converts to doubles for insurance, which
// shouldn't pose a speed problem since this is mainly used in graph preprocess.
bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut)
{
Point3D p1(p.x, p.y, p.z);
Point3D p2(q.x, q.y, q.z);
Point3D p3(r.x, r.y, r.z);
F64 d1 = p.d, d2 = q.d, d3 = r.d;
F64 bc = (p2.y * p3.z) - (p3.y * p2.z);
F64 ac = (p2.x * p3.z) - (p3.x * p2.z);
F64 ab = (p2.x * p3.y) - (p3.x * p2.y);
F64 det = (p1.x * bc) - (p1.y * ac) + (p1.z * ab);
// Parallel planes
if (mFabsD(det) < 1e-5)
return false;
F64 dc = (d2 * p3.z) - (d3 * p2.z);
F64 db = (d2 * p3.y) - (d3 * p2.y);
F64 ad = (d3 * p2.x) - (d2 * p3.x);
F64 detInv = 1.0 / det;
pOut->x = ((p1.y * dc) - (d1 * bc) - (p1.z * db)) * detInv;
pOut->y = ((d1 * ac) - (p1.x * dc) - (p1.z * ad)) * detInv;
pOut->z = ((p1.y * ad) + (p1.x * db) - (d1 * ab)) * detInv;
return true;
}
bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result)
{
if (v1.isZero() || v2.isZero() || (result == NULL))
return false;
Point3F v1Norm = v1, v2Norm = v2;
v1Norm.normalize();
v2Norm.normalize();
if ((! isZero(1.0f - v1Norm.len())) || (! isZero(1.0f - v2Norm.len())))
return false;
//make sure the vectors are non-colinear
F32 dot = mDot(v1Norm, v2Norm);
if (dot > 0.999f || dot < -0.999f)
return false;
//find the cross product, and normalize the result
mCross(v1Norm, v2Norm, result);
result->normalize();
return true;
}

349
ai/graphMath.h Normal file
View file

@ -0,0 +1,349 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHMATH_H_
#define _GRAPHMATH_H_
#ifndef _TVECTOR_H_
#include "Core/tVector.h"
#endif
#ifndef _MSPHERE_H_
#include "Math/mSphere.h"
#endif
#ifndef _MATHIO_H_
#include "Math/mathIO.h"
#endif
//-------------------------------------------------------------------------------------
// One-line convenience functions:
inline bool validArrayIndex(S32 index, S32 arrayLength)
{
return U32(index) < U32(arrayLength); // (Unsigned compare handles < 0 case too)
}
inline F32 triArea(const Point2F& a, const Point2F& b, const Point2F& c)
{
return mFabs((b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y));
}
inline void triCenter(const Point2F& p1, const Point2F& p2, const Point2F& p3, Point2F &c)
{
c.x = p1.x + p2.x + p3.x;
c.y = p1.y + p2.y + p3.y;
}
// prototype for finding the CM(center of mass) of a convex poly
bool polygonCM(S32 n, Point2F *verts, Point2F *CM);
// Find point at intersection of three planes if such exists (returns true if so).
bool intersectPlanes(const PlaneF& p, const PlaneF& q, const PlaneF& r, Point3F* pOut);
// Lookup into NxN table in which one entry is stored for each pair of unique indices.
inline S32 triangularTableIndex(S32 i1, S32 i2) {
if (i1 > i2)
return (((i1 - 1) * i1) >> 1) + i2;
else
return (((i2 - 1) * i2) >> 1) + i1;
}
//-------------------------------------------------------------------------------------
// Where vertical line through point hits plane (whose normal.z != 0):
F32 solveForZ(const PlaneF& plane, const Point3F& point);
// Cross product, but with a check for bad input (parallel vectors):
bool findCrossVector(const Point3F &v1, const Point3F &v2, Point3F *result);
//-------------------------------------------------------------------------------------
// Encapsulate some common operations on the grid rectangles
class GridArea : public RectI
{
public:
GridArea() { }
GridArea(const Point2I& pt,const Point2I& ext) : RectI(pt,ext) {}
GridArea(S32 L, S32 T, S32 W, S32 H) : RectI(L,T,W,H) {}
GridArea(const GridArea & g) : RectI(g.point,g.extent) {}
// grid to index, and vice versa:
S32 getIndex(Point2I pos) const;
Point2I getPos(S32 index) const;
// step through grid in index order (Y outer loop):
bool start(Point2I &p) const;
bool step(Point2I &p) const;
};
inline S32 GridArea::getIndex(Point2I p) const
{
p -= point;
if( validArrayIndex(p.x, extent.x) )
if( validArrayIndex(p.y, extent.y) )
return p.y * extent.x + p.x;
return -1;
}
inline Point2I GridArea::getPos(S32 index) const
{
Point2I P;
P.x = point.x + index % extent.x;
P.y = point.y + index / extent.x;
return P;
}
//-------------------------------------------------------------------------------------
// Linear range mapping.
// Get value that is pct way between first and last. Note this is probably a better
// way of getting midpoints than (A+B)*.5 due to rounding.
template <class T> T scaleBetween(T first, T last, F32 pct)
{
return first + (pct * (last - first));
}
// Get percent of way of value between min and max, clamping return to range [0,1]
template <class T> F32 getPercentBetween(T value, T min, T max)
{
if(mFabs((max - min)) < 0.0001)
return 0.0f;
else if(min < max){
if (value <= min) return 0.0f;
else if (value >= max) return 1.0f;
}
else if (min > max){
if (value >= min) return 0.0f;
else if (value <= max) return 1.0f;
}
return F32((value - min) / (max - min));
}
// Map value from domain into the given range, capping on ends-
template <class T> T mapValueLinear(T value, T dmin, T dmax, T rmin, T rmax)
{
return scaleBetween(rmin, rmax, getPercentBetween(value, dmin, dmax));
}
template <class T> T mapValueQuadratic(T value, T dmin, T dmax, T rmin, T rmax)
{
F32 pct = getPercentBetween(value, dmin, dmax);
return scaleBetween(rmin, rmax, pct * pct);
}
template <class T> T mapValueSqrt(T value, T dmin, T dmax, T rmin, T rmax)
{
F32 pct = getPercentBetween(value, dmin, dmax);
return scaleBetween(rmin, rmax, mSqrt(pct));
}
//-------------------------------------------------------------------------------------
// An object to do a quad-tree traversal of a grid region.
class GridVisitor
{
protected:
bool mPreCheck;
bool mPostCheck;
bool recurse(GridArea rect, S32 level);
public:
const GridArea mArea;
GridVisitor(const GridArea & area);
// virtuals - how you get visited:
virtual bool beforeDivide(const GridArea& R, S32 level);
virtual bool atLevelZero(const GridArea& R);
virtual bool afterDivide(const GridArea& R, S32 level, bool success);
// call the traversal:
bool traverse();
};
//-------------------------------------------------------------------------------------
// Return if the two points are within the given threshold distance.
template <class PointType, class DistType>
bool within(PointType p1, const PointType & p2, DistType thresh)
{
return (p1 -= p2).lenSquared() <= (thresh * thresh);
}
inline bool within_2D(const Point3F & A, const Point3F & B, F32 D)
{
Point2F A2d( A.x, A.y );
Point2F B2d( B.x, B.y );
return within( A2d, B2d, D );
}
//-------------------------------------------------------------------------------------
// Get a list of grid offsets that "spirals" out in order of closeness.
S32 getGridSpiral(Vector<Point2I> & spiral, Vector<F32> & dists, S32 radius);
//-------------------------------------------------------------------------------------
// Given a point, used to find closest distance/point on segment.
class LineSegment
{
Point3F a, b;
Point3F soln;
public:
LineSegment(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;}
LineSegment() {soln=a=b=Point3F(0,0,0);}
public:
void set(const Point3F& _a, const Point3F& _b) {soln=a =_a;b =_b;}
F32 distance(const Point3F& p);
bool botDistCheck(const Point3F& p, F32 d3, F32 d2);
Point3F solution() {return soln;}
Point3F getEnd(bool which) {return which ? b : a;}
};
//-------------------------------------------------------------------------------------
class LineStepper
{
Point3F A, B;
Point3F dir1; // unit direction vector from A to B.
F32 total; // total dist from A to B.
F32 soFar; // how far we've come.
F32 advance; // solutions to queries are kept until the user
Point3F solution; // tells us to "officially" advance.
public:
LineStepper(const Point3F & a, const Point3F & b) { init(a,b); }
const Point3F & getSolution() { return solution; }
F32 distSoFar() { return soFar; }
F32 totalDist() { return total; }
F32 remainingDist() { return total-soFar; }
// Methods that actually do stuff:
void init(const Point3F & a, const Point3F & b);
F32 getOutboundIntersection(const SphereF & s);
const Point3F & advanceToSolution();
};
//-------------------------------------------------------------------------------------
// reverse elements in place
template <class T> Vector<T> & reverseVec( Vector<T> & vector )
{
for(S32 halfWay = (vector.size() >> 1); halfWay; /* dec'd in loop */ )
{
T & farOne = * (vector.end() - halfWay--);
T & nearOne = * (vector.begin() + halfWay);
T tmp=farOne; farOne=nearOne; nearOne=tmp; // swap
}
return vector;
}
//--------------------------------------------------------------------------
// Read/Write a vector of items, using T.read(stream), T.write(stream).
// Skip function reads same amount as readVector1(), bypassing data.
template <class T> bool readVector1(Stream & stream, Vector<T> & vec)
{
S32 num, i;
bool Ok = stream.read( & num );
for( i = 0, vec.setSize( num ); i < num && Ok; i++ )
Ok = vec[i].read( stream );
return Ok;
}
template <class T> bool writeVector1(Stream & stream, const Vector<T> & vec)
{
bool Ok = stream.write( vec.size() );
for( U32 i = 0; i < vec.size() && Ok; i++ )
Ok = vec[i].write( stream );
return Ok;
}
//-------------------------------------------------------------------------------------
// Read/Write a vector of items, using stream.read(T), stream.write(T).
template <class T> bool readVector2(Stream & stream, Vector<T> & vec)
{
S32 num, i;
bool Ok = stream.read( & num );
for( i = 0, vec.setSize( num ); i < num && Ok; i++ )
Ok = stream.read(&vec[i]);
return Ok;
}
template <class T> bool writeVector2(Stream & stream, const Vector<T> & vec)
{
bool Ok = stream.write( vec.size() );
for( S32 i = 0; i < vec.size() && Ok; i++ )
Ok = stream.write(vec[i]);
return Ok;
}
//-------------------------------------------------------------------------------------
// Functions to read / write a vector of items that define mathRead() / mathWrite().
template <class T> bool mathReadVector(Vector<T>& vec, Stream& stream)
{
S32 num, i;
bool Ok = stream.read(&num);
for (i = 0, vec.setSize(num); i < num && Ok; i++ )
Ok = mathRead(stream, &vec[i]);
return Ok;
}
template <class T> bool mathWriteVector(const Vector<T>& vec, Stream& stream)
{
bool Ok = stream.write(vec.size());
for (S32 i = 0; i < vec.size() && Ok; i++)
Ok = mathWrite(stream, vec[i]);
return Ok;
}
//-------------------------------------------------------------------------------------
// Perform setSize() and force construction of all the elements. Done to address some
// difficulty experienced in getting virtual function table pointer constructed.
template <class T>
void setSizeAndConstruct(Vector<T> & vector, S32 size)
{
vector.setSize(size);
T * tList = new T [size];
S32 memSize = size * sizeof(T);
dMemcpy(vector.address(), tList, memSize);
delete [] tList;
}
template <class T>
void destructAndClear(Vector<T> & vector)
{
for (S32 i = vector.size() - 1; i >= 0; i--)
vector[i].~T();
vector.clear();
}
template <class T>
void setSizeAndClear(Vector<T> & vector, S32 size, S32 value=0)
{
vector.setSize( size );
dMemset(vector.address(), value, vector.memSize());
}
//--------------------------------------------------------------------------
// Read a vector with construction
template <class T> bool constructVector(Stream & stream, Vector<T> & vec)
{
S32 num, i;
bool Ok = stream.read(&num);
if(num && Ok)
{
setSizeAndConstruct(vec, num);
for (i = 0; i < num && Ok; i++)
Ok = vec[i].read(stream);
}
return Ok;
}
#endif

93
ai/graphNodes.h Normal file
View file

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHNODES_H_
#define _GRAPHNODES_H_
class GraphSearch;
class NavigationGraph;
class RegularNode : public GraphNode // graphNodeBase.cc
{
friend class GraphSearch;
protected:
Point3F mNormal;
GraphEdge * pushTransientEdge(S32);
void popTransientEdge();
public:
RegularNode();
GraphEdge& pushEdge(GraphNode * node);
GraphEdgeArray getEdges(GraphEdge*) const;
const Point3F& getNormal() const;
// one-liners:
void transientReserve() {mEdges.reserve(mEdges.size()+2);}
const Point3F& location() const {return mLoc;}
const Point3F& fetchLoc(Point3F&) const {return mLoc;}
const GraphEdge* getEdgePtr() const {return mEdges.address();}
GraphEdgeArray getEdges() const {return getEdges(NULL);}
S32 getIndex() const {return mIndex;}
S32 edgeUsage() const {return mEdges.memSize();}
F32 radius() const {return 1.0;}
};
struct GraphBoundary // graphIndoors.cc
{
Point3F seg[2];
Point3F normal;
Point3F seekPt;
F32 distIn;
GraphBoundary(const GraphEdgeInfo&);
void setItUp(S32 N, const NodeInfoList&, const GraphVolumeList&);
Point3F midpoint() const {return (seg[0] + seg[1]) * 0.5;}
};
typedef Vector<GraphBoundary> GraphBoundaries;
class InteriorNode : public RegularNode // graphIndoors.cc
{
typedef RegularNode Parent;
F32 mMinDim, mArea;
public:
InteriorNode();
void init(const IndoorNodeInfo& data, S32 index, const Point3F& floor);
void setDims(F32 minDim, F32 area) {mMinDim=minDim; mArea=area;}
F32 minDim() const {return mMinDim;}
F32 area() const {return mArea;}
GraphEdge& pushOneWay(const GraphEdgeInfo::OneWay&);
NodeProximity containment(const Point3F& loc) const;
};
class OutdoorNode : public RegularNode // graphOutdoors.cc
{
friend class NavigationGraph;
protected:
F32 mHeight;
public:
OutdoorNode();
S32 getLevel() const;
Point3F getRenderPos() const;
Point3F randomLoc() const;
F32 terrHeight() const {return mHeight;}
F32 radius() const {return F32(1<<mLevel)*gNavGlobs.mSquareRadius;}
F32 minDim() const {return F32(1<<mLevel)*gNavGlobs.mSquareWidth;}
};
class IndoorNodeList : public Vector<InteriorNode>
{
typedef Vector<InteriorNode> Parent;
public:
~IndoorNodeList();
void clear();
void init(S32 sz);
};
#endif

530
ai/graphOutdoors.cc Normal file
View file

@ -0,0 +1,530 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
// Visitor for making run-time node list.
class UnrollOutdoorList : public GridVisitor
{
protected:
const GraphNodeList& mGrid;
GraphNodeList& mListOut;
S32 getIndex(const GridArea& area);
public:
UnrollOutdoorList(const GridArea& world, const GraphNodeList& grid, GraphNodeList& list)
: GridVisitor(world),
mGrid(grid),
mListOut(list)
{
}
bool beforeDivide(const GridArea& R, S32 level);
bool atLevelZero(const GridArea& R);
};
S32 UnrollOutdoorList::getIndex(const GridArea& R)
{
S32 index = mArea.getIndex( R.point );
AssertFatal( validArrayIndex(index, mGrid.size()), "Node unroll bad index" );
return index;
}
bool UnrollOutdoorList::beforeDivide(const GridArea& R, S32 level)
{
S32 index = getIndex(R);
if (GraphNode * node = mGrid[index])
{
if (node->getLevel() == level)
{
mListOut.push_back(node);
return false; // stop the sub-divide
}
}
return true; // recurse further
}
bool UnrollOutdoorList::atLevelZero(const GridArea& R)
{
if (GraphNode * node = mGrid[getIndex(R)])
mListOut.push_back(node);
return true; // (N/A)
}
//-------------------------------------------------------------------------------------
// Make the grid of node pointers in the given area. Makes the grid of
// empty nodes, and fills in the list, and sets indices.
S32 NavigationGraph::setupOutdoorNodes(const GridArea& area_in, const Consolidated& cons,
GraphNodeList& grid_out, GraphNodeList& list_out)
{
S32 i;
mOutdoorNodes = new OutdoorNode [cons.size()];
// Set up grid with pointers to outdoor nodes. Consolidated areas have the whole
// square within the grid set to point at them.
setSizeAndClear(grid_out, area_in.len_x() * area_in.len_y());
for (i = 0; i < cons.size(); i++)
{
const OutdoorNodeInfo & nodeInfo = cons[i];
OutdoorNode * outdoorNode = (mOutdoorNodes + i);
Point2I gridPoint = nodeInfo.getPoint();
mTerrainInfo.posToLoc(outdoorNode->mLoc, gridPoint);
S32 level = nodeInfo.getLevel();
outdoorNode->mLevel = level;
S32 ind = mTerrainInfo.posToIndex(gridPoint);
AssertFatal(ind >= 0, "setupOutdoorNodes: bad pos");
if (mTerrainInfo.shadowed(ind)) {
outdoorNode->mHeight = mTerrainInfo.shadowHeight(ind);
outdoorNode->set(GraphNode::Shadowed);
}
else {
outdoorNode->mHeight = 1e17;
if (mTerrainInfo.submerged(ind))
outdoorNode->set(GraphNode::Submerged);
}
//==> Make this get the average normal.
S32 gridShift = gNavGlobs.mSquareShift;
Point2F terrGridLoc(gridPoint.x << gridShift, gridPoint.y << gridShift);
if (!mTerrainBlock->getNormal(terrGridLoc, &outdoorNode->mNormal))
outdoorNode->mNormal.set(0,0,1);
AssertFatal(level > -2, "Need level > -2 for outdoor nodes");
if (level < 0)
level = 0;
// fill in the grid with pointers to this node
S32 gridWidth = 1 << level;
for (S32 y = 0; y < gridWidth; y++)
for (S32 x = 0; x < gridWidth; x++)
{
Point2I P = Point2I(x,y) + gridPoint;
grid_out[ area_in.getIndex( P ) ] = outdoorNode;
}
// Construct the outdoor node type with position and level information.
// Fill in the grid here.
// if (level > 0)
{
Point3F middleOff (gridWidth << gridShift-1, gridWidth << gridShift-1, 0);
if (level == 0) //==> Need to handle -1, 0 differently.
middleOff *= 0.0;
outdoorNode->mLoc += middleOff;
if (!terrainHeight(outdoorNode->mLoc, & outdoorNode->mLoc.z))
{
// This was put here to make obvious a bug a while ago that seems
// to have been gone for quite a while.
outdoorNode->mLoc.z = 120;
warning("graphOutdoors.cc: No terrain found in middle of grid node");
}
}
}
// Do the unroll loop to put into the node list proper.
UnrollOutdoorList listUnroller(area_in, grid_out, list_out);
listUnroller.traverse();
// Make sure it matches our data:
AssertFatal(list_out.size() == cons.size(), "setupOutdoorNodes: data doesn't jibe");
// Set indices. Our approach is desgined to have larger squares first in the list.
// Uh, there was a potential reason for that at some point, not utilized I think...
for (i = 0; i < list_out.size(); i++)
(dynamic_cast<OutdoorNode *>(list_out[i]))->mIndex = i;
return list_out.size();
}
//-------------------------------------------------------------------------------------
// Visitor to hook up the nodes.
class HookOutdoorNodes : public GridVisitor
{
protected:
const GraphNodeList& mGrid;
const Vector<U8>& mNeighbors;
U16 * const mCounting;
bool hookBothWays(GraphNode* node, const Point2I& off);
S32 getIndex(const GridArea& area);
public:
HookOutdoorNodes(const GridArea& world, const GraphNodeList& grid,
const Vector<U8>& neighbors, U16 * const counts = NULL)
: GridVisitor(world), mGrid(grid), mNeighbors(neighbors), mCounting(counts) {}
bool beforeDivide(const GridArea& R, S32 level);
bool atLevelZero(const GridArea& R);
};
// Points for hooking to 12 neighbors. Offsets are in multiples of half the
// square width- further going across. See cornerStep & sideStep variables
// in loop below for order of these. Basically Left->Right, and then Up.
static const Point2I hookCorners[4] = {
Point2I( 0, 0 ),
Point2I( 2, 0 ),
Point2I( 0, 2 ),
Point2I( 2, 2 )
};
static const Point2I hookSides[8] = {
Point2I( 0, 0 ), Point2I( 1, 0 ),
Point2I( 0, 0 ), Point2I( 0, 1 ),
Point2I( 2, 0 ), Point2I( 2, 1 ),
Point2I( 0, 2 ), Point2I( 1, 2 )
};
S32 HookOutdoorNodes::getIndex(const GridArea& R)
{
S32 index = mArea.getIndex( R.point );
AssertFatal( validArrayIndex(index, mGrid.size()), "Node unroll bad index" );
return index;
}
// Hook the node, and return true if it was successful and it was a DOWNWARD hook.
// (i.e. down by ONE level). For downward hooks we also hook self into their
// list. Also do single hook ACROSS, and return false.
bool HookOutdoorNodes::hookBothWays(GraphNode* node, const Point2I& where)
{
S32 index = mArea.getIndex( where );
if (index >= 0)
{
if (GraphNode * neighbor = mGrid[ index ])
{
S32 srcLevel = node->getLevel();
S32 dstLevel = neighbor->getLevel();
OutdoorNode * from = dynamic_cast<OutdoorNode *>(node);
OutdoorNode * to = dynamic_cast<OutdoorNode *>(neighbor);
// check our assumptions-
AssertFatal (from && to && srcLevel>-2 && dstLevel>-2, "hookBothWays- ASKEW");
AssertFatal (mAbs(srcLevel - dstLevel) <= 1, "hookBothWays- UNSMOOTH BORDER");
// consider -1 and 0 to be same level for purposes of this routine-
if (srcLevel < 0) srcLevel = 0;
if (dstLevel < 0) dstLevel = 0;
// Hook to down-by-1 level, or across. Former case hooks both & returns true.
// 10/12/2000: Added counting mode which doesn't push (for pool allocation).
if( srcLevel == dstLevel + 1 )
{
if (mCounting) {
mCounting[from->getIndex()]++;
mCounting[to->getIndex()]++;
}
else {
from->pushEdge(to);
to->pushEdge(from);
}
return true;
}
else if( srcLevel == dstLevel )
{
if (mCounting)
mCounting[from->getIndex()]++;
else
from->pushEdge(to);
// fall through return false
}
}
}
return false;
}
// Hook to all neighbors of lower or equal level. When the level is equal, then
// we don't do the hook back (they'll do it themselves).
//
// Assert that level difference is preserved.
//
bool HookOutdoorNodes::beforeDivide(const GridArea& R, S32 level)
{
GraphNode * node = mGrid[ getIndex (R) ];
if( node && node->getLevel() == level )
{
// Look at all 12 neighbors - two on each side, and the four corners.
S32 cornerStep = 0, sideStep = 0;
S32 halfWidth = (1 << level-1);
S32 x, y;
// Loop through sides and corners, skipping middle-
for (y = -1; y <= 1; y++) for (x = -1; x <= 1; x++) if (x || y)
{
// Get offset from the boundaries computed above. We only offset for
// negative components, hence the strange math:
Point2I gridOffset((x < 0) * x, (y < 0) * y);
if (x && y)
{
Point2I cornerOff = hookCorners[cornerStep++];
(cornerOff *= halfWidth) += gridOffset;
hookBothWays(node, cornerOff += R.point);
}
else // (x XOR y)
{
for (S32 adjacent = 0; adjacent < 2; adjacent++)
{
Point2I sideOff = hookSides[sideStep * 2 + adjacent];
sideOff *= halfWidth;
sideOff += gridOffset;
sideOff += R.point;
if( ! hookBothWays(node, sideOff) )
break;
}
sideStep++;
}
}
// Stop the Visitor subdivision-
return false;
}
// Tell Visitor to recurse further-
return true;
}
bool HookOutdoorNodes::atLevelZero(const GridArea& R)
{
// Hook to all neighbors. At this level we don't hook back (each hooks itself).
S32 index = getIndex(R);
if (GraphNode * node = mGrid[index])
for (S32 dir = 0; dir < 8; dir++)
if (mNeighbors[index] & (1 << dir))
{
S32 x = TerrainGraphInfo::gridOffs[dir].x;
S32 y = TerrainGraphInfo::gridOffs[dir].y;
bool didBoth = true;
if(x && y) {
// diagonal hooks must gaurantee that there are valid nodes on either
// side of the line we're proposing to connect.
Point2I acrossDiagP1(x,0);
Point2I acrossDiagP2(0,y);
S32 ind1 = mArea.getIndex(R.point + acrossDiagP1);
S32 ind2 = mArea.getIndex(R.point + acrossDiagP2);
if (ind1 >= 0 && ind2 >= 0 && mGrid[ind1] && mGrid[ind2])
didBoth = hookBothWays(node, Point2I(x,y) + R.point);
else
didBoth = false;
}
else
didBoth = hookBothWays(node, Point2I(x,y) + R.point);
AssertFatal(!didBoth, "HookOutdoor: only lateral hook at level zero");
}
return true;
}
//-------------------------------------------------------------------------------------
// Create our run time consolidated nodes from the data that has been generated.
void NavigationGraph::makeRunTimeNodes(bool useEdgePool)
{
GridArea worldArea(mTerrainInfo.originGrid, mTerrainInfo.gridDimensions);
HookOutdoorNodes hookingVisitor(worldArea, mNodeGrid, mTerrainInfo.neighborFlags);
U16 * edgeCounts = NULL;
S32 totalNodeCount = mTerrainInfo.consolidated.size() + mNodeInfoList.size();
delete [] mOutdoorNodes;
mOutdoorNodes = NULL;
mNumOutdoor = 0;
mNodeList.clear();
mNodeGrid.clear();
mNodeList.reserve(mTerrainInfo.consolidated.size() + mNodeInfoList.size());
if (haveTerrain())
{
mNodeGrid.reserve(worldArea.extent.x * worldArea.extent.y);
// Makes outdoor grid and starts the node list-
mNumOutdoor = setupOutdoorNodes(worldArea, mTerrainInfo.consolidated, mNodeGrid, mNodeList);
}
// Need to allocate these here for edge pool-
mIndoorNodes.init(mNodeInfoList.size());
// Here is where we precompute edges if we're using a pool. To know outdoor counts,
// we run the traverser in a different mode. Then roll in stored edges and bridges.
delete [] mEdgePool;
mEdgePool = NULL;
if (useEdgePool)
{
edgeCounts = new U16 [totalNodeCount];
// For now it reserves space for Transient push, will change (since that is
// a wasteful (though simple) approach).
for (S32 i = 0; i < totalNodeCount; i++)
edgeCounts[i] = 2;
// Count outdoor edges-
if (haveTerrain())
{
HookOutdoorNodes counter(worldArea, mNodeGrid, mTerrainInfo.neighborFlags, edgeCounts);
counter.traverse();
}
// Count indoor-
for (S32 e = 0; e < mEdgeInfoList.size(); e++)
for (S32 dir = 0; dir < 2; dir++)
edgeCounts[mNumOutdoor + mEdgeInfoList[e].to[dir].dest]++;
// Count bridge edges-
mBridgeList.accumEdgeCounts(edgeCounts);
// Get the total size needed-
U32 totalEdgeCount = 0;
for (S32 t = 0; t < totalNodeCount; t++)
totalEdgeCount += edgeCounts[t];
// Allocate the pool (a NavigationGraph member)
mEdgePool = new GraphEdge[totalEdgeCount];
Con::printf("Allocated edge pool of size %d", totalEdgeCount);
// Set the outdoor pointers into the pool-
U32 offset = 0;
for (S32 o = 0; o < mNumOutdoor; o++)
{
mNodeList[o]->mEdges.setOwned(&mEdgePool[offset], edgeCounts[o]);
offset += edgeCounts[o];
}
// Set the indoor edge pool pointers.
for (S32 x = 0; x < mIndoorNodes.size(); x++)
{
U16 count = edgeCounts[x + mNumOutdoor];
mIndoorNodes[x].mEdges.setOwned(&mEdgePool[offset], count);
offset += count;
}
AssertFatal(offset == totalEdgeCount, "makeRunTimeNodes()");
}
// hooks up the edges.
hookingVisitor.traverse();
// Set up the interior nodes. Last param gives starting index-
initInteriorNodes(mEdgeInfoList, mNodeInfoList, mNodeList.size());
mNumIndoor = mIndoorNodes.size();
// Put the indoor nodes on our pointer list.
IndoorNodeList::iterator in;
for (in = mIndoorNodes.begin(); in != mIndoorNodes.end(); in++)
mNodeList.push_back(in);
findJumpableNodes();
// Set up the shoreline. Push it back more for Lava so bots give it wider berth.
expandShoreline(0);
if (mDeadlyLiquid)
expandShoreline(1);
S32 numHanging = doFinalFixups();
delete [] edgeCounts;
// AssertFatal(numHanging < 1, "Run time consolidate hookup making hanging nodes");
}
//-------------------------------------------------------------------------------------
// This sets the shoreline bit, which will affect edge scaling. Note that this method
// can be called multiple times to expand what is considered shoreline. This only
// operates on outdoor nodes and their outdoor neighbors.
void NavigationGraph::expandShoreline(U32 wave)
{
AssertFatal(wave < 3, "Only three shoreline bits available");
U32 extendFromMask = (GraphNode::ShoreLine << wave);
U32 extendToMask = (extendFromMask << 1);
for (S32 i = 0; i < mNumOutdoor; i++) {
if (GraphNode * node = lookupNode(i)) {
if (node->test(extendFromMask)) {
GraphEdgeArray edges = node->getEdges(mEdgeBuffer);
while (GraphEdge * edge = edges++) {
if (!edge->isJetting() && (edge->mDest < mNumOutdoor)) {
GraphNode * neighbor = lookupNode(edge->mDest);
if (!neighbor->test(extendFromMask))
neighbor->set(extendToMask);
}
}
}
}
}
}
//-------------------------------------------------------------------------------------
// Outdoor Node Methods
S32 OutdoorNode::getLevel() const
{
return S32(mLevel);
}
OutdoorNode::OutdoorNode()
{
mIndex = -1;
mLoc.set(-1,-1,-1);
mFlags.set(Outdoor);
}
Point3F OutdoorNode::getRenderPos() const
{
Point3F adjustPos = location();
if( mLevel > 0 ){
F32 up = F32(mLevel);
adjustPos.z += (up * 0.7);
}
else
adjustPos.z += 0.2;
return adjustPos;
}
// Randomize a location within an outdoor node. Choose random offsets that are at least 1
// unit away from grid axes, and at least 0.5 units in from sides-
Point3F OutdoorNode::randomLoc() const
{
Point3F loc = location();
F32 *xy = loc;
F32 R = radius();
for (S32 i = 0; i < 2; i++)
{
F32 off = gRandGen.randF() * (R - 1.5) + 1.0;
if (gRandGen.randI() & 1)
xy[i] += off;
else
xy[i] -= off;
}
gNavGraph->terrainHeight(loc, &loc.z);
loc.z += 1.0; // ===> Extra should depend on slope (if issue now w/ preprocess)?
return loc;
}

127
ai/graphPartition.cc Normal file
View file

@ -0,0 +1,127 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
GraphPartition::GraphPartition()
{
mType = ForceField;
mCanJetDown = false;
// mPartition.setSize(gNavGraph->numNodesAll());
// mPartition.clear();
}
void GraphPartition::install(const GraphPartition& p)
{
mPartition.copy(p.mPartition);
}
// Allocate the bit vector and clear it.
void GraphPartition::setSize(S32 N)
{
mPartition.setSize(N);
}
// Consumer uses a regular partition to build the downhill partition. We check here to
// see if it's any different from the regular partition.
void GraphPartition::setDownhill(const GraphPartition& downhill)
{
U32 bytes = mPartition.getByteSize();
AssertFatal(mType == Armor, "Only armor types can have a downhill component");
AssertFatal(bytes == downhill.mPartition.getByteSize(), "Unequal partition sizes");
if (dMemcmp(mPartition.getBits(), downhill.mPartition.getBits(), bytes)) {
mCanJetDown = true;
mDownhill.copy(downhill.mPartition);
}
else {
mCanJetDown = false;
}
}
// See if partition can answer question of whether or not we can get from A to B.
GraphPartition::Answer GraphPartition::reachable(S32 A, S32 B)
{
if (mPartition.test(A))
{
if (mPartition.test(B) || (mCanJetDown && mDownhill.test(B)))
return CanReach;
else
return CannotReach;
}
else if (mPartition.test(B))
return CannotReach;
else
return Ambiguous;
}
//-------------------------------------------------------------------------------------
PartitionList::PartitionList()
{
mType = GraphPartition::ForceField;
}
PartitionList::~PartitionList()
{
clear();
}
//-------------------------------------------------------------------------------------
void PartitionList::clear()
{
while (size()) {
last().~GraphPartition();
pop_back();
}
}
// This is called after searches that have failed to reach their target to register the
// new partition that has been discovered. ie. when a force field has changed states,
// then partition lists are invalid and get rebuilt as searches happen.
void PartitionList::pushPartition(const GraphPartition& partition)
{
GraphPartition empty;
// I'd like to construct the entry in the vector, but can't get it to go...
// i.e increment(); last().GraphPartition();
// But this should work, given that BitVector doesn't override assignment... ugh.
push_back(empty);
last().install(partition);
last().setType(mType);
}
// Our partition lists will probably never have the complete answer to this. Need an
// ambiguous return value.
GraphPartition::Answer PartitionList::reachable(S32 A, S32 B)
{
for (iterator partition = begin(); partition != end(); partition++)
{
GraphPartition::Answer answer = partition->reachable(A, B);
if (answer != GraphPartition::Ambiguous)
return answer;
}
AssertFatal(mType != GraphPartition::Armor, "Armor partitions are never ambiguous");
return GraphPartition::Ambiguous;
}
// When partitions are built at mission start, it has to check and see which nodes have
// entries after finishing each pass. It goes until graph is filled.
S32 PartitionList::haveEntry(S32 forNode)
{
for (iterator partition = begin(); partition != end(); partition++)
if (partition->test(forNode))
return true;
return false;
}

56
ai/graphPartition.h Normal file
View file

@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHPARTITION_H_
#define _GRAPHPARTITION_H_
class GraphSearch;
class GraphPartition
{
public:
enum Types {Armor, ForceField};
enum Answer {CannotReach, CanReach, Ambiguous};
protected:
Types mType;
bool mCanJetDown;
BitVector mPartition;
BitVector mDownhill;
public:
GraphPartition();
void install (const GraphPartition& p);
void setDownhill(const GraphPartition& downhill);
void setPartition(GraphSearch * searcher);
Answer reachable(S32 from, S32 to);
void setSize(S32 N);
void setType(Types t) {mType = t;}
bool test(S32 i) {return mPartition.test(i);}
void set(S32 i) {mPartition.set(i);}
void clear() {mPartition.clear();}
};
class PartitionList : public Vector<GraphPartition>
{
protected:
GraphPartition::Types mType;
public:
PartitionList();
~PartitionList();
void pushPartition(const GraphPartition& partition);
void setType(GraphPartition::Types t) {mType = t;}
GraphPartition::Answer reachable(S32 from, S32 to);
S32 haveEntry(S32 forNode);
void clear();
};
#endif

831
ai/graphPath.cc Normal file
View file

@ -0,0 +1,831 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "platform/profiler.h"
//-------------------------------------------------------------------------------------
// Go through the construct function since we want to reconstruct on mission cycle.
NavigationPath::NavigationPath()
{
constructThis();
}
void NavigationPath::constructThis()
{
mState.constructThis();
mCurEdge = NULL;
mTimeSlice = 0;
mSaveSeekNode = -1;
mSearchDist = 0.0f;
mUserStuck = true;
mForceSearch = true;
mRepathCounter = 10000;
mSaveDest.set(1e7, 1e7, 1e7);
setRedoDist();
mPctThreshSqrd = 1.0;
mPathIndoors = mAreIndoors = false;
mAwaitingSearch = false;
mTeam = 0;
mBusyJetting = false;
mSearchWasValid = true;
mAdjustSeek.set(0,0,0);
dMemset(mSavedEndpoints, 0, sizeof(mSavedEndpoints));
GraphEdge newEdge;
mSaveLastEdge = newEdge;
mStopCounter = 0;
mCastZ = mCastAng = 0.0;
mCastAverage.set(0,0,0);
mEstimatedEdge = NULL;
mEstimatedEnergy = 0.0;
mFindHere.init();
}
// Since some of the code is in elsewhere (graphSmooth), we should package up the
// relevant state for it... Doesn't seem like the best organization, but the idea is
// that the volume code "knows" more about smoothing paths indoor than we do here,
// and so that code should be separated...
void NavigationPath::State::constructThis()
{
thisEdgeJetting = false;
nextEdgeJetting = false;
nextEdgePrecise = false;
curSeekNode = 0;
seekLoc.set(0,0,0);
path.clear();
visit.clear();
edges.clear();
hereLoc.set(0,0,0);
destLoc.set(0,0,0);
}
// Fetch the node along the path, undoing any bit-flipped Transient indices.
GraphNode * NavigationPath::getNode(S32 idx)
{
S32 node = mState.path[idx];
S32 toggle = -S32(node < 0);
return gNavGraph->lookupNode(node ^ toggle);
}
// Get location in path, handling case where endpoints were transients.
Point3F NavigationPath::getLoc(S32 idx)
{
S32 node = mState.path[idx];
if(node >= 0)
return gNavGraph->lookupNode(node)->location();
else
return mSavedEndpoints[idx > 0];
}
// Save endpoints of search (flagged with negative indices). A NULL dstNode
// is passed for searches where destination is a node in graph.
void NavigationPath::saveEndpoints(TransientNode* srcNode, TransientNode* dstNode)
{
mSavedEndpoints[0] = srcNode->location();
mState.path.first() ^= S32(-1);
if (dstNode)
{
mSavedEndpoints[1] = dstNode->location();
mState.path.last() ^= S32(-1);
}
// These edges are used for the path advancing. Must take care with edges
// coming off of the transients - the last one must be saved since it is popped
// after the search (source connection INTO graph remains though)
setSizeAndClear(mState.edges, getMax(mState.path.size()-1, 0));
for (S32 i = mState.edges.size(); i > 0; i--)
{
GraphEdge * edgePtr = getNode(i-1)->getEdgeTo(getNode(i));
AssertFatal(edgePtr, "All edges should exist in path");
mState.edges[i - 1] = edgePtr;
}
if (mState.edges.size())
{
mSaveLastEdge = * mState.edges.last();
mState.edges.last() = & mSaveLastEdge;
}
}
// This is for renderer - though we'll use a similar node marking scheme for
// implementing avoidance / randomization of paths.
void NavigationPath::markRenderPath()
{
for(S32 i = getMax(mState.curSeekNode - 1, 0); i < mState.path.size(); i++)
getNode(i)->setOnPath();
}
// Revised path search to go off of the transient nodes.
void NavigationPath::computePath(TransientNode& srcNode, TransientNode& dstNode)
{
mSearchDist = 0.0f;
mSearchWasValid = false;
// Perform the search if the push indicates these two can (probably) reach.
if (gNavGraph->pushTransientPair(srcNode, dstNode, mTeam, mJetCaps))
{
PROFILE_START(PathComputation);
mState.path.clear();
mState.curSeekNode = 0;
GraphSearch * searcher = gNavGraph->getMainSearcher();
searcher->setAStar(true);
searcher->setTeam(mTeam);
searcher->setThreats(gNavGraph->getThreatSet(mTeam));
searcher->setRandomize(true);
#if _GRAPH_PART_
searcher->setRatings(gNavGraph->jetManager().getRatings(mJetCaps));
#endif
searcher->performSearch(&srcNode, &dstNode);
// Path fetcher tells us if search failed.
if (searcher->getPathIndices(mState.path))
{
mSearchDist = searcher->searchDist();
setSizeAndClear(mState.visit, mState.path.size());
saveEndpoints(&srcNode, &dstNode);
mState.curSeekNode = 1;
setEdgeConstraints();
mAwaitingSearch = false;
mSearchWasValid = true;
}
else
{
// Note - we need to know if the transient connections into the graph were
// all make-able by the bot (they should all go through the same evaluation
// to check the connections). I think this assert arose because a bot had
// transient connection that couldn't be made.
// // A search should only fail due to force fields. Armor capabilities should be
// // adequately predicted in advance.
// AssertFatal(gNavGraph->haveForceFields(), "Failed to predict failure");
//
// // Force field management will update this team's partition accordingly.
// gNavGraph->newPartition(searcher, mTeam);
if (gNavGraph->haveForceFields())
{
// Force field management will update this team's partition accordingly.
gNavGraph->newPartition(searcher, mTeam);
}
}
PROFILE_END(); // PathComputation
}
else {
#if _GRAPH_WARNINGS_
NavigationGraph::warning("Search tried across islands or partitions");
#endif
}
// Unconnects from graph.
gNavGraph->popTransientPair(srcNode, dstNode);
}
// Just want all the post-pathCompute stuff separated out:
void NavigationPath::afterCompute()
{
mSaveDest = mState.destLoc;
mForceSearch = false;
mRepathCounter = 0;
if ( mRedoMode == OnPercent ) {
F32 pctThresh = (mRedoPercent * mSearchDist);
mPctThreshSqrd = (pctThresh * pctThresh);
}
mUserStuck = false;
}
bool NavigationPath::updateTransients(TransientNode& hereNode,
TransientNode& destNode, bool forceRedo)
{
// Nodes could be invalid-
if (mHook.iGrowOld())
mLocateHere.reset(), mLocateDest.reset();
if (forceRedo)
mLocateHere.forceCheck(), mLocateDest.forceCheck();
// This does all the work of figuring out how to connect-
mLocateHere.update(mState.hereLoc);
mLocateDest.update(mState.destLoc);
// Install connections found, and update locs-
hereNode.setEdges(mLocateHere.getEdges());
destNode.setEdges(mLocateDest.getEdges());
hereNode.setClosest(mLocateHere.bestMatch());
destNode.setClosest(mLocateDest.bestMatch());
hereNode.setLoc(mState.hereLoc);
destNode.setLoc(mState.destLoc);
// NOTE! Only hook one way due to how Push-Search-Pop works (computePath() above).
bool needToJet;
if (mLocateHere.canHookTo(mLocateDest, needToJet)) {
GraphEdge& edge = hereNode.pushEdge(&destNode);
if (needToJet)
edge.setJetting();
gNavGraph->jetManager().initEdge(edge, &hereNode, &destNode);
}
return forceRedo; // not used
}
//-------------------------------------------------------------------------------------
// Periodic path updates. Return true if there are nodes to traverse.
#define SearchWaitTicks 20
#define AllowVisitTicks 21
bool NavigationPath::checkPathUpdate(TransientNode& hereNode, TransientNode& destNode)
{
bool recompute = mForceSearch;
// bool recompute = false;
bool changedSeekNode = (mSaveSeekNode != mState.curSeekNode);
mSaveSeekNode = mState.curSeekNode;
if (!recompute) {
F32 threshold = (mRedoMode == OnDist ? mRedoDistSqrd : mPctThreshSqrd);
bool canSearch = (++mRepathCounter > SearchWaitTicks) && !userMustJet();
bool needSearch = ((mState.destLoc - mSaveDest).lenSquared() > threshold);
if (mUserStuck)
needSearch = true;
if (needSearch) {
// We need to make the canSearch check a little more strict and wait a little
// extra time, or search when the node has been updated.
if (canSearch && (changedSeekNode || mRepathCounter > AllowVisitTicks))
recompute = true;
else
mAwaitingSearch = true;
}
}
if (recompute)
mAwaitingSearch = true;
updateTransients(hereNode, destNode, recompute);
if (recompute) {
computePath(hereNode, destNode);
afterCompute();
}
if (weAreIndoors())
mAreIndoors = true;
else {
if(checkOutsideAdvance())
advanceByOne();
}
// Update path
if (mState.curSeekNode < mState.path.size())
{
GraphNode * fromNode = (mState.curSeekNode > 0 ? getNode(mState.curSeekNode-1) : NULL);
if (fromNode && gNavGraph->useVolumeTraverse(fromNode, mCurEdge))
{
S32 adv = gNavGraph->checkIndoorSkip(mState);
if (adv)
{
// Con::printf("Advanced through %d indoor nodes", adv);
while (adv--)
{
AssertFatal(canAdvance(), "Nav path tried illegal advance");
advanceByOne();
}
}
else
{
if (gNavGraph->volumeTraverse(fromNode, mCurEdge, mState))
{
if (canAdvance()) //==> Shouldn't it always be possible to advance?
advanceByOne();
}
}
}
else
{
mState.seekLoc = getLoc(mState.curSeekNode);
F32 threshold = visitRadius() + 0.22;
if(within_2D(mState.hereLoc, mState.seekLoc, threshold) && canAdvance()) {
advanceByOne();
if (mState.curSeekNode < mState.path.size())
mState.seekLoc = getLoc(mState.curSeekNode); // re-fetch!
}
}
}
return (mState.curSeekNode < mState.path.size());
}
//-------------------------------------------------------------------------------------
// The consumer of the NavigationPath must call this every frame.
bool NavigationPath::updateLocations(const Point3F& here, const Point3F& there)
{
// This addition to the Z is important because we don't match to indoor nodes that
// we are below. Not pretty, but the alternative is most extra LOS calls, or other
// potential ambiguities in the node matching process. Also see the wall scanning
// code below, which is 'cognizant' of these numbers.
(mState.hereLoc = here).z += 0.4;
(mState.destLoc = there).z += 0.4;
mAreIndoors = false; // (default - get's changed if indoors)
if( !NavigationGraph::gotOneWeCanUse()) {
mState.seekLoc = mState.destLoc;
mSearchWasValid = false;
}
else {
// Threat manager needs to be called frequently-
gNavGraph->threats()->monitorThreats();
gNavGraph->monitorForceFields();
// Note hook nodes must be fetched every frame (in case of graph revisions).
if (!checkPathUpdate(mHook.getHook1(), mHook.getHook2()))
mState.seekLoc = mState.destLoc;
// Debug info-
markRenderPath();
// This just walks the graph looking for malfeasance
// gNavGraph->patrolForProblems();
}
// Movement code sets while jetting, each set lasts for one frame
mBusyJetting = false;
// Busy-dec the stop counter, and check wall avoidance when non-zero.
if (--mStopCounter <= 0)
mStopCounter = 0;
else
checkWallAvoid();
// Return status of the last search performed-
return mSearchWasValid;
}
//-------------------------------------------------------------------------------------
// Management of path advancing.
// This assumes we have a graph and are seeking a node within the list.
S32 NavigationPath::checkOutsideAdvance()
{
if(canAdvance())
{
S32 seekNode = mState.curSeekNode + 1;
if (seekNode < mState.path.size() && !mState.nextEdgeJetting)
{ // See if we can get to the next one. We're doing this in 2D, which may be Ok
// in the long run too, though we might track maximum heights along the way.
// First we check for registered threats along this segment.
Point3F srcLoc(mState.hereLoc);
Point3F dstLoc(getLoc(seekNode));
if (gNavGraph->threats()->sanction(srcLoc, dstLoc, mTeam))
{
srcLoc.z = dstLoc.z = 0.0f;
F32 pct = gNavGraph->mTerrainInfo.checkOpenTerrain(srcLoc, dstLoc);
if (pct == 1.0)
return 1;
}
}
}
return 0;
}
bool NavigationPath::canAdvance()
{
if (mState.curSeekNode < mState.path.size())
if (!mState.thisEdgeJetting)
return true;
return false;
}
F32 NavigationPath::visitRadius()
{
// if (mState.nextEdgeJetting || mAreIndoors || mPathIndoors)
if (mState.nextEdgePrecise || mAreIndoors || mPathIndoors)
return 0.4;
else
return 2.6;
}
// See if the next destination is a Jetting edge. An assumption of this routine
// is that it is only called when a new path is computed, or when the path is
// advanced. Else problems - such as transient source having new connections
// which don't relate to the current path, etc.
void NavigationPath::setEdgeConstraints()
{
mState.thisEdgeJetting = false;
mState.nextEdgeJetting = false;
mState.nextEdgePrecise = false;
mPathIndoors = false;
mCurEdge = NULL;
if (mState.curSeekNode > 0 && mState.curSeekNode < mState.path.size())
{
GraphNode * curSeekNode = getNode(mState.curSeekNode);
GraphNode * prevSeekNode = getNode(mState.curSeekNode-1);
mCurEdge = mState.edges[mState.curSeekNode - 1];
if (curSeekNode->indoor() || prevSeekNode->indoor())
mPathIndoors = true;
mState.thisEdgeJetting = mCurEdge->isJetting();
// Configure the edge. Hop over is a U8 with 3 bits of precision.
if (mState.thisEdgeJetting)
{
mNavJetting.init();
if (mCurEdge->hasHop())
mNavJetting.mHopOver = mCurEdge->getHop();
}
if (mState.thisEdgeJetting)
mState.seekLoc = getLoc(mState.curSeekNode);
if (mState.curSeekNode+1 < mState.path.size())
{
GraphEdge * nextEdge = mState.edges[mState.curSeekNode];
mState.nextEdgeJetting = nextEdge->isJetting();
// This flags that we need to go to the node location.
mState.nextEdgePrecise = (mState.nextEdgeJetting || !nextEdge->isBorder());
}
}
}
// Called when a node has been visited. Mark it for later avoidance (path randomizing)
// if such is enabled and reasonable. Don't mark large terrain nodes or transients.
void NavigationPath::randomization()
{
GraphNode * node = getNode(mState.curSeekNode);
if (!node->transient() && node->getLevel() <= 2)
node->setAvoid(60000);
}
// The path is advanced through only two interface functions, this one and the next.
void NavigationPath::advanceByOne()
{
AssertFatal(canAdvance(), "advanceByOne() called incorrectly");
randomization();
mState.curSeekNode++;
setEdgeConstraints();
}
// When user is done - they inform the system so it can advance.
void NavigationPath::informJetDone()
{
AssertFatal(mState.thisEdgeJetting, "informJetDone() only called when jetting");
randomization();
mState.curSeekNode++;
setEdgeConstraints();
}
// User queries to see if they now must jet.
bool NavigationPath::userMustJet() const
{
return (mState.path.size() > 0 && mState.thisEdgeJetting);
}
void NavigationPath::forceSearch()
{
if (userMustJet() && _GRAPH_WARNINGS_)
NavigationGraph::warning("Searched forced while user is jetting");
mForceSearch = true;
}
// Here we use the path randomization to hopefully get the guy off the path. We back up
// through the list of any that were skipped - since we may not have even come to those
// yet. And then we go forward by one.
void NavigationPath::informStuck(const Point3F& stuckLoc, const Point3F& stuckDest)
{
stuckLoc; stuckDest;
S32 start = getMax(mState.curSeekNode - 1, 1);
S32 end = getMin(mState.curSeekNode + 1, mState.path.size() - 1);
while (start > 1 && mState.visit[start].test(State::Skipped))
start--;
while (start < end) {
GraphNode * node = getNode(start++);
AssertFatal(!node->transient(), "informStuck() didn't skip transients");
node->setAvoid(15 * 1000, true);
}
mUserStuck = true;
}
//-------------------------------------------------------------------------------------
U32 gCountStoppedTicks = 0;
// F32 gVelSquaredThresh = 0.1;
F32 gVelSquaredThresh = -1.0; // Take out for moment - needs more testing.
// When not moving but should be, this gets called - when in Express/Walk mode.
void NavigationPath::informProgress(const VectorF& vel)
{
F32 velSquared = vel.lenSquared();
if (velSquared < gVelSquaredThresh)
{
if ((mStopCounter += 2) > 10)
gCountStoppedTicks++;
}
else
{
// Once not stopped, then limit how much we have to count down. But we do
// want that time as well for fading the running mCastAverage below.
mStopCounter = getMin(mStopCounter, 7);
}
}
// A nice angle for scanning around with good coverage and consistent change. These
// are relative prime numbers a little over 3/8 of the cycle points (360.0, 1.90000)
static const F64 sCycleAngle = mDegToRad(F64(137.3));
static const F64 sCycleHeight = 0.37747;
static const U32 sCycleMask = (InteriorObjectType|TerrainObjectType);
static const F32 sAverageTheNew = (1.0 / 8.0);
static const F32 sAverageTheOld = (1.0 - sAverageTheOld);
// Monitor the stop counter. When it persists, look for walls and such to move
// away from. We do this with rotating LOS checks, which also have to move up and
// down to have the best chance of finding obstacles. Cycling on 1.9 height covers
// range from 0.4 to 2.3 (see above addition to supplied here loc z).
void NavigationPath::checkWallAvoid()
{
if (mStopCounter > 3)
{
if ((mCastAng += sCycleAngle) > M_2PI)
mCastAng -= M_2PI;
if ((mCastZ += sCycleHeight) > 1.9)
mCastAng -= 1.9;
F32 angle32 = F32(mCastAng);
Point3F startPoint( mState.hereLoc.x, mState.hereLoc.y, mState.hereLoc.z + F32(mCastZ) );
Point3F lineOut( mCos(angle32), mSin(angle32), 0);
// Get the endpoint out a little ways. Find intersection and convert back
// to an offset which we'll roll into running average of offsets.
(lineOut *= 7.0) += startPoint;
RayInfo coll;
gServerContainer.castRay(startPoint, lineOut, sCycleMask, &coll);
coll.point -= startPoint;
// Get the running average-
coll.point *= sAverageTheNew;
mCastAverage *= sAverageTheOld;
mCastAverage += coll.point;
}
else
{
// In first few frames free, or first frames stuck, fade down the average.
mCastAverage *= (2.0 / 3.0);
}
}
static Point3F reignIn(const Point3F& S, Point3F D, F32 cap)
{
D -= S;
if (D.lenSquared() > (cap * cap))
D.normalize(cap);
return D += S;
}
// This is where user fetches location to seek. We now do additional checks to
// reign in this distance when near steep things.
const Point3F& NavigationPath::getSeekLoc(const VectorF&)
{
// if (NavigationGraph::gotOneWeCanUse())
// mAdjustSeek = reignIn(mState.hereLoc, mState.seekLoc, 10.0);
// else
if (mStopCounter)
{
// Seek away from collision point if that exists-
mAdjustSeek = mCastAverage;
mAdjustSeek *= F32(mStopCounter);
mAdjustSeek += mState.hereLoc;
}
else
mAdjustSeek = mState.seekLoc;
return mAdjustSeek;
}
//-------------------------------------------------------------------------------------
// Convey the jetting ability to the jet manager. We let the aiConnection go ahead
// and retrieve this data from Player since we've so far kept from #including Player.h.
void NavigationPath::setJetAbility(const JetManager::Ability & ability)
{
if (gNavGraph) {
if (_GRAPH_PART_ && gNavGraph->jetManager().update(mJetCaps, ability)) {
// A bunch of work just happened, let's forestall searches a little-
mRepathCounter = getMin(SearchWaitTicks >> 1, mRepathCounter);
}
}
}
//-------------------------------------------------------------------------------------
// How far across open terrain user can go from src to dst. dstLoc parameter is
// updated accordingly, and a percentage from 0 to 1 is returned.
F32 NavigationPath::checkOpenTerrain(const Point3F& srcLoc, Point3F& dstLoc)
{
if (NavigationGraph::gotOneWeCanUse())
return gNavGraph->mTerrainInfo.checkOpenTerrain(srcLoc, dstLoc);
dstLoc = srcLoc;
return 0.0;
}
//-------------------------------------------------------------------------------------
// A couple of path lookahead functions-
// Get a point that is dist along the path.
Point3F NavigationPath::getLocOnPath(F32 dist)
{
Point3F currLoc(mState.hereLoc);
for (S32 i = mState.curSeekNode; i < mState.path.size() && dist > 0.01; i++)
{
Point3F nextLoc = getLoc(i);
VectorF diffVec = nextLoc;
F32 len = (diffVec -= currLoc).len();
if (len < dist)
{
currLoc = nextLoc;
dist -= len;
}
else
{
currLoc += (diffVec *= (dist / len));
break;
}
}
return currLoc;
}
// Find distance remaining on path by stepping through it - stop if user has given a
// threshold dist outside of which they don't care (defaults to huge).
F32 NavigationPath::distRemaining(F32 maxCare)
{
Point3F currLoc(mState.hereLoc);
F32 dist = 0.0f;
for (S32 i = mState.curSeekNode; i < mState.path.size(); i++)
{
Point3F nextLoc = getLoc(i);
if ((dist += (nextLoc - currLoc).len()) > maxCare)
return maxCare;
currLoc = nextLoc;
}
return dist;
}
//-------------------------------------------------------------------------------------
// Find next edge that requires jetting if it's within maxDist. If found, then we
// estimate the needed energy to complete the hop. Since the estimation is a little
// lengthy - we remember which edge we computed for and only do it once.
F32 NavigationPath::jetWillNeedEnergy(F32 maxDist)
{
if (!mState.thisEdgeJetting)
{
F32 accumDist = 0.0f;
for (S32 i = mState.curSeekNode; i < mState.edges.size(); i++)
{
// Let's only do a len() calculation to the first node, otherwise use distance
// on the edge. (First len() required because the bot's moving).
F32 legDist;
if (i == mState.curSeekNode)
legDist = (getLoc(i) - mState.hereLoc).len();
else
legDist = mState.edges[i - 1]->mDist;
if ((accumDist += legDist) < maxDist)
{
GraphEdge * nextEdge = mState.edges[i];
if (nextEdge->isJetting())
return estimateEnergy(nextEdge);
}
}
}
return 0.0;
}
// Get the jet manager's estimation, and remember that we did this work for this
// edge since JetManager::estimateEnergy() does a bit o' math.
F32 NavigationPath::estimateEnergy(const GraphEdge * edge)
{
if (mEstimatedEdge != edge)
{
mEstimatedEnergy = gNavGraph->jetManager().estimateEnergy(mJetCaps, edge);
mEstimatedEdge = edge;
}
return mEstimatedEnergy;
}
//-------------------------------------------------------------------------------------
// Return true if we are outside, and set the roam radius (in param) if so.
bool NavigationPath::locationIsOutdoors(const Point3F &location, F32* roamRadPtr)
{
F32 roamRadius = 1e12; // Default is outdoors, with unlimited roam room.
if (NavigationGraph::gotOneWeCanUse())
{
if (!gNavGraph->haveTerrain())
return false;
Point3F tempLocation(location.x, location.y, 0.0f);
if (gNavGraph->mTerrainInfo.inGraphArea(tempLocation))
{
SphereF sphere;
if (gNavGraph->mTerrainInfo.locToIndexAndSphere(sphere, tempLocation) >= 0)
roamRadius = sphere.radius; // fall through to true
else
return false;
}
}
if (roamRadPtr)
*roamRadPtr = roamRadius;
return true;
}
// By default (with no graph), this is false.
bool NavigationPath::weAreIndoors() const
{
if (NavigationGraph::gotOneWeCanUse())
if (!gNavGraph->haveTerrain())
return true;
else if (gNavGraph->mTerrainInfo.inGraphArea(mState.hereLoc))
return !gNavGraph->findTerrainNode(mState.hereLoc);
return false;
}
bool NavigationPath::getPathNodeLoc(S32 ahead, Point3F& loc)
{
S32 dest = mState.curSeekNode + ahead;
if (dest < mState.path.size() && dest > 0)
{
GraphEdge * beforeEdge = mState.edges[dest - 1];
if (!beforeEdge->isJetting())
{
if (beforeEdge->isBorder())
loc = gNavGraph->getBoundary(beforeEdge->mBorder).seekPt;
else
loc = getNode(dest)->location();
return true;
}
}
return false;
}
// Does the bot have to jet into a vehicle?
bool NavigationPath::intoMount() const
{
if (mLocateDest.isMounted())
if (mState.curSeekNode == mState.path.size() - 1)
return true;
return false;
}
bool NavigationPath::canReachLoc(const Point3F& dst)
{
if (NavigationGraph::gotOneWeCanUse())
{
// mFindHere is a FindGraphNode which remembers results of closest node searches.
mFindHere.setPoint(mState.hereLoc, mFindHere.closest());
FindGraphNode findDest(dst);
return gNavGraph->canReachLoc(mFindHere, findDest, mTeam, mJetCaps);
}
else
return true;
}
void NavigationPath::missionCycleCleanup()
{
constructThis();
mJetCaps.reset();
mNavJetting.init();
mHook.reset();
mFindHere.init();
mLocateHere.cleanup();
mLocateDest.cleanup();
}

133
ai/graphPath.h Normal file
View file

@ -0,0 +1,133 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHPATH_H_
#define _GRAPHPATH_H_
struct NavJetting
{
NavJetting() {init();}
void init() {mHopOver = 0.0f; mChuteUp = mChuteDown = false;}
F32 mHopOver;
bool mChuteUp;
bool mChuteDown;
};
class NavigationPath
{
public:
typedef Vector<BitSet32> VisitState;
struct State
{
enum {Visited = (1 << 0),
Outbound = (1 << 1),
Skipped = (1 << 2)};
void constructThis();
Vector<S32> path;
VisitState visit;
GraphEdgePtrs edges;
Point3F seekLoc;
Point3F hereLoc;
Point3F destLoc;
bool thisEdgeJetting;
bool nextEdgeJetting;
bool nextEdgePrecise;
S32 curSeekNode;
};
private:
enum HowToRedo {OnDist, OnPercent};
void constructThis();
bool checkPathUpdate(TransientNode& here, TransientNode& there);
bool updateTransients(TransientNode& src, TransientNode& dst, bool redo);
void saveEndpoints(TransientNode* from, TransientNode* to);
void computePath(TransientNode& from, TransientNode& to);
F32 estimateEnergy(const GraphEdge * edge);
void checkWallAvoid();
void randomization();
void markRenderPath();
void afterCompute();
bool canAdvance();
void setEdgeConstraints();
void advanceByOne();
S32 checkOutsideAdvance();
GraphNode * getNode(S32 idx);
Point3F getLoc(S32 index);
State mState;
Point3F mHereLoc;
Point3F mDestLoc;
Point3F mAdjustSeek;
HowToRedo mRedoMode;
bool mForceSearch;
bool mAreIndoors;
bool mPathIndoors;
bool mAwaitingSearch;
bool mBusyJetting;
F32 mPctThreshSqrd;
F32 mRedoDistSqrd;
F32 mRedoPercent;
F32 mSearchDist;
U32 mTeam;
Point3F mSaveDest;
S32 mRepathCounter, mTimeSlice;
S32 mStopCounter;
S32 mSaveSeekNode;
bool mUserStuck;
bool mSearchWasValid;
GraphEdge * mCurEdge;
GraphLocate mLocateHere;
GraphLocate mLocateDest;
GraphHookRequest mHook;
Point3F mSavedEndpoints[2];
GraphEdge mSaveLastEdge;
JetManager::ID mJetCaps;
NavJetting mNavJetting;
FindGraphNode mFindHere;
F64 mCastAng, mCastZ;
Point3F mCastAverage;
const GraphEdge * mEstimatedEdge;
F32 mEstimatedEnergy;
public:
NavigationPath();
bool updateLocations(const Point3F& src, const Point3F& dst);
F32 checkOpenTerrain(const Point3F& from, Point3F& to);
bool locationIsOutdoors(const Point3F &loc, F32* roamDist=0);
bool weAreIndoors() const;
bool userMustJet() const;
bool intoMount() const;
void informJetDone();
void setJetAbility(const JetManager::Ability& ability);
void informStuck(const Point3F& stuckLoc, const Point3F& stuckDest);
void informProgress(const VectorF& vel);
F32 jetWillNeedEnergy(F32 maxDist);
Point3F getLocOnPath(F32 dist);
F32 distRemaining(F32 maxCare=1e9);
bool getPathNodeLoc(S32 ahead, Point3F& loc);
const Point3F& getSeekLoc(const VectorF& vel);
bool canReachLoc(const Point3F& dst);
void missionCycleCleanup();
F32 visitRadius();
void forceSearch();
// One-liners-
NavJetting* getJetInfo() {return &mNavJetting;}
F32 searchDist() const {return mSearchDist;}
bool isPathCurrent() const {return !mAwaitingSearch;}
void getLastPath(Vector<S32>& L) {L=mState.path;}
void setTeam(U32 whichTeam) {mTeam = whichTeam;}
void setRedoDist(F32 D=0.8f) {mRedoDistSqrd=D*D; mRedoMode=OnDist;}
void setRedoPercent(F32 P=0.15f) {mRedoPercent=P; mRedoMode=OnPercent;}
void setDestMounted(bool b) {mLocateDest.setMounted(b);}
void setSourceMounted(bool b) {mLocateHere.setMounted(b);}
void informJetBusy() {mBusyJetting = true;}
};
#endif

148
ai/graphQueries.cc Normal file
View file

@ -0,0 +1,148 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
// Get fast travel distance if we have a graph with a path XRef table. First method is
// static and checks for presence of graph, second does the path table walk.
F32 NavigationGraph::fastDistance(const Point3F& p1, const Point3F& p2)
{
if(gotOneWeCanUse())
return gNavGraph->distancef(p1, p2);
else
{
warning("fastDistance() called without usable graph present");
return (p1 - p2).len();
}
}
F32 NavigationGraph::distancef(const Point3F& p1, const Point3F& p2)
{
// REMOVED TABLE SEARCHES
// if (mValidPathTable) {
// GraphNode * src = closestNode(p1);
// GraphNode * dst = closestNode(p2);
// Vector<S32> nodes;
//
// if (src && dst)
// if (src->island() == dst->island())
// return walkPathTable(src, dst, nodes);
// else
// warning("distancef() attempted across separate islands");
// else
// warning("distancef() failed to find closest node(s)");
// }
return (p1 - p2).len();
}
//-------------------------------------------------------------------------------------
void NavigationGraph::chokePoints(const Point3F& srcPoint, Vector<Point3F>& points,
F32 minHideD, F32 stopSearchD)
{
if (GraphNode * srcNode = closestNode(srcPoint))
getLOSSearcher()->findChokePoints(srcNode, points, minHideD, stopSearchD);
else
warning("getChokePoints() failed to find a closest node");
}
// Static function:
S32 NavigationGraph::getChokePoints(const Point3F& srcPoint, Vector<Point3F>& points,
F32 minHideDist, F32 stopSearchDist)
{
points.clear();
if (gotOneWeCanUse())
if (gNavGraph->validLOSXref())
gNavGraph->chokePoints(srcPoint, points, minHideDist, stopSearchDist);
else
warning("getChokePoints() called without valid LOS XRef table");
else
warning("getChokePoints() called without (usable) graph in place");
return points.size();
}
//-------------------------------------------------------------------------------------
Point3F NavigationGraph::hideOnSlope(const Point3F& from, const Point3F& avoid, F32 rad, F32 deg)
{
const char * warnMsg = NULL;
if (gotOneWeCanUse())
{
if (gNavGraph->validLOSXref())
{
GraphSearchLOS * searcher = gNavGraph->getLOSSearcher();
return searcher->hidingPlace(from, avoid, rad, mDegToRad(90-deg), true);
}
else
warnMsg = "hideOnSlope() called without valid LOS XRef table";
}
else
warnMsg = "hideOnSlope() called without (usable) graph in place";
if (warnMsg && _GRAPH_WARNINGS_)
warning(warnMsg);
return from;
}
//-------------------------------------------------------------------------------------
Point3F NavigationGraph::hideOnDistance(const Point3F& from, const Point3F& avoid, F32 rad, F32 hideLen)
{
const char * warnMsg = NULL;
if (gotOneWeCanUse())
{
if (gNavGraph->validLOSXref())
{
GraphSearchLOS * searcher = gNavGraph->getLOSSearcher();
return searcher->hidingPlace(from, avoid, rad, hideLen, false);
}
else
warnMsg = "hideOnDistance() called without valid LOS XRef table";
}
else
warnMsg = "hideOnDistance() called without (usable) graph in place";
if (warnMsg && _GRAPH_WARNINGS_)
warning(warnMsg);
return from;
}
//-------------------------------------------------------------------------------------
Point3F NavigationGraph::findLOSLocation(const Point3F& from, const Point3F& wantToSee,
F32 minDist, const SphereF& getCloseTo, F32 capDist)
{
const char * warnMsg = NULL;
if (gotOneWeCanUse())
{
if (gNavGraph->validLOSXref())
{
GraphSearchLOS * searcher = gNavGraph->getLOSSearcher();
return searcher->findLOSLoc(from, wantToSee, minDist, getCloseTo, capDist);
}
else
warnMsg = "findLOSLocation() called without valid LOS XRef table";
}
else
warnMsg = "findLOSLocation() called without (usable) graph in place";
if (warnMsg && _GRAPH_WARNINGS_)
warning(warnMsg);
return from;
}

420
ai/graphRender.cc Normal file
View file

@ -0,0 +1,420 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "dgl/dgl.h"
#include "Core/color.h"
//-------------------------------------------------------------------------------------
static bool sShowFieldEdges = false;
static bool sFilterDownEdges = false;
static S32 sShowFFTeam = 0;
static S32 sEdgesDrawn = 0;
//-------------------------------------------------------------------------------------
struct SortedNode
{
GraphNode * node;
F32 dist;
SortedNode(GraphNode* n=NULL, F32 d=0.0) {node = n; dist = d;}
};
class SortedList : public Vector<SortedNode>
{
static S32 QSORT_CALLBACK cmpSortedNodes(const void* , const void* );
public:
void init(const GraphNodeList& list, const Point3F& camLoc);
void sort();
};
S32 QSORT_CALLBACK SortedList::cmpSortedNodes(const void * a,const void * b)
{
F32 A = ((SortedNode*)a)->dist;
F32 B = ((SortedNode*)b)->dist;
return (A < B ? -1 : (A > B ? 1 : 0));
}
void SortedList::sort()
{
dQsort((void* )(this->address()), this->size(), sizeof(SortedNode), cmpSortedNodes);
}
// Set up the list with distances (squared (faster)), and sort it.
void SortedList::init(const GraphNodeList& list, const Point3F& camLoc)
{
clear();
for (S32 i = 0; i < list.size(); i++)
{
GraphNode * node = list[i];
SortedNode sortNode(node, (node->location() - camLoc).lenSquared());
push_back(sortNode);
}
sort();
}
//-------------------------------------------------------------------------------------
extern void wireCube(F32 size, Point3F pos);
static void drawNode(const Point3F& pos, const ColorF& color, U32 whichType = 0)
{
glPointSize(whichType ? 9 : 4);
glBegin(GL_POINTS);
if (whichType)
glColor3f(1, 1, 1);
else
glColor3f(color.red, color.green, color.blue);
glVertex3f(pos.x, pos.y, pos.z);
glEnd();
}
static void drawNode(const Point3F& pos, U32 whichType = 0)
{
glPointSize(whichType ? 9 : 4);
glBegin(GL_POINTS);
if (whichType)
glColor3f(1, 1, 1);
else
glColor3f(1, 0, 0);
glVertex3f(pos.x, pos.y, pos.z);
glEnd();
}
// Draw node size to indicate roam radius amount. Try square root relationship.
static void drawOutdoorNode(const Point3F& pos, F32 roamRad, bool highlight)
{
F32 mapRoamRad = mSqrt(roamRad * 2.0);
S32 numPoints = mFloor(mapRoamRad) + 1;
glPointSize(numPoints);
glBegin(GL_POINTS);
if (highlight)
glColor3f(1, 1, 1);
else
glColor3f(1, 0, 0);
glVertex3f(pos.x, pos.y, pos.z);
glEnd();
}
static void renderEdge(const Point3F& src, const Point3F& dst,
const ColorF* col = 0, bool wide=false, bool flip = false)
{
const Point3F& from = (flip ? dst : src);
const Point3F& to = (flip ? src : dst);
Point3F extra = (to - from);
F32 len = extra.len();
if (len > 0.01)
{
extra *= (0.04 / len);
extra += to;
glLineWidth(wide ? 3 : 1);
glBegin(GL_LINES);
if(col)
glColor3f(col->red, col->green, col->blue);
else
glColor3f(0, 0.3, 1);
glVertex3f(from.x, from.y, from.z);
glVertex3f(extra.x, extra.y, extra.z);
glEnd();
glLineWidth(1);
sEdgesDrawn++;
}
}
//-------------------------------------------------------------------------------------
static void renderSegments(Vector<LineSegment>& segments)
{
static Point3F morph(0.0007,0.0037,0.0013);
static Point3F a(0,0,0);
for (S32 i = 0; i < segments.size(); i++)
{
ColorF color(mFabs(mCos(a.x)), mFabs(mCos(a.y)), mFabs(mCos(a.z)));
a += morph;
if (a.x > M_2PI) a.x -= M_2PI;
if (a.y > M_2PI) a.y -= M_2PI;
if (a.z > M_2PI) a.z -= M_2PI;
LineSegment& seg = segments[i];
renderEdge(seg.getEnd(0), seg.getEnd(1), &color);
}
}
static void renderBoxes(Vector<Point3F>& boxLocs)
{
for (S32 i = 0; i < boxLocs.size(); i++) {
Point3F loc = boxLocs[i];
wireCube(0.22f, loc);
}
}
void NavigationGraph::pushRenderSeg(const LineSegment& lineSeg)
{
if (mRenderThese.size() < 800)
mRenderThese.push_back(lineSeg);
}
void NavigationGraph::pushRenderBox(Point3F boxLoc, F32 zadd)
{
if (mRenderBoxes.size() < 100) {
boxLoc.z += zadd;
mRenderBoxes.push_back(boxLoc);
}
}
//-------------------------------------------------------------------------------------
// RENDERING
void NavigationGraph::render(Point3F &camPos, bool)
{
GridArea w = getWorldRect();
if (mNodeList.size() && gotOneWeCanUse())
{
U32 filters = Con::getIntVariable("$GraphRenderFilter");
sFilterDownEdges = (filters & 1);
sShowFFTeam = Con::getIntVariable("$GraphShowFFTeam");
// Signal fields with flashing-
sShowFieldEdges = !(Sim::getCurrentTime() & 0x600);
// Render the last path query, avoid transients on ends. Note we need to switch
// to something which uses a bit on the edge instead of counter on node.
for (S32 k = mTempNodeBuf.size() - 1; k >= 0; k--)
if (GraphNode * tempNode = lookupNode(mTempNodeBuf[k]))
if (!tempNode->transient())
tempNode->setOnPath();
if (haveTerrain() && sDrawOutdoorNodes)
{
GridArea c = getGridRectangle(camPos, 13);
if (w.intersect(c))
{
GraphEdge edgeBuffer[MaxOnDemandEdges];
GraphNodeList nodesInArea;
SortedList sortedList;
S32 numNodes = getNodesInArea(nodesInArea, w);
sortedList.init(nodesInArea, camPos);
sEdgesDrawn = 0;
for (S32 i = 0; i < numNodes && sEdgesDrawn < sEdgeRenderMaxOutdoor; i++)
{
bool highlight;
GraphNode * node = sortedList[i].node;
if (sShowThreatened < 0)
highlight = (node->render0() || node->submerged());
else
highlight = (node->threats() & showWhichThreats());
Point3F nodeLoc = node->getRenderPos();
F32 roamRad = getRoamRadius(nodeLoc);
drawOutdoorNode(nodeLoc, roamRad, highlight);
drawNeighbors(node, node->getEdges(edgeBuffer), camPos);
}
}
}
// if (sDrawTransients)
if (sDrawOutdoorNodes || sDrawIndoorNodes)
renderTransientNodes();
if (sDrawIndoorNodes)
renderInteriorNodes(camPos);
if (mRenderThese.size() > 0) {
renderSegments(mRenderThese);
if (mRenderThese.size() > 9) {
for (S32 i = mRenderThese.size() / 8; i >= 0; i--)
mRenderThese.erase_fast((gRandGen.randI()&0xFFFFFF) % mRenderThese.size());
}
}
if (mRenderBoxes.size() > 0) {
renderBoxes(mRenderBoxes);
if (mRenderBoxes.size() > 3) {
for (S32 i = mRenderBoxes.size() / 3; i >= 0; i--)
mRenderBoxes.erase_fast((gRandGen.randI()&0xFFFFFF) % mRenderBoxes.size());
}
}
}
}
#define IndoorRenderThresh 49.0
#define MinIndoorBoxWidth 7.0
#define MaxIndoorBoxWidth 57.0
#define MaxIndoorRender 100
void NavigationGraph::renderInteriorNodes(Point3F camPos)
{
static F32 boxW = 24;
GraphEdge edgeBuffer[MaxOnDemandEdges];
ColorF color(0, 1, 0);
GraphNodeList renderList;
//==> Would of course be better to clip to box in FRONT of camera...
Point3F boxOff(boxW, boxW, 111);
Box3F clipBox(camPos - boxOff, camPos + boxOff);
mIndoorTree.getIntersecting(renderList, clipBox);
S32 numNodes = renderList.size();
SortedList sortedList;
sortedList.init(renderList, camPos);
sEdgesDrawn = 0;
// Resize box based on density. Note we always want to go tall though.
// if (numNodes <= MaxIndoorRender)
// boxW = getMin(MaxIndoorBoxWidth, boxW + 0.5);
// else
// boxW = getMax(MinIndoorBoxWidth, boxW - 0.5);
for (S32 i = 0; i < numNodes && sEdgesDrawn < sEdgeRenderMaxIndoor; i++)
{
bool highlight;
GraphNode * node = sortedList[i].node;
Point3F pos = node->getRenderPos();
GraphEdgeArray edges = node->getEdges(edgeBuffer);
if (sShowThreatened < 0)
highlight = node->render0();
else
highlight = (node->threats() & showWhichThreats());
drawNode(pos, color, highlight);
drawNeighbors(node, edges, camPos, getMin(boxW + 40.0, 80.0));
}
}
void NavigationGraph::renderTransientNodes()
{
GraphEdge edgeBuffer[MaxOnDemandEdges];
for (S32 i = 0; i < mMaxTransients; i++)
if (GraphNode * node = mNodeList[mTransientStart + i]) {
Point3F pos = node->getRenderPos();
drawNode(pos);
drawNeighbors(node, node->getEdges(edgeBuffer), pos);
}
}
// Find the two intermediate points to connect to between node volumes. We are given
// that the edge has a border definition.
void NavigationGraph::getCrossingPoints(S32 from, Point3F* twoPoints, GraphEdge* edge)
{
GraphBoundary & B = mBoundaries[edge->mBorder];
twoPoints[0] = B.seekPt;
if (GraphEdge * edgeBack = lookupNode(edge->mDest)->getEdgeTo(from))
twoPoints[1] = mBoundaries[edgeBack->mBorder].seekPt;
else
twoPoints[1] = B.seekPt + (B.normal * (B.distIn * 2));
twoPoints[0].z += 0.22;
twoPoints[1].z += 0.22;
}
static const ColorF borderColor(0.0, 0.9, 0.11); // green
static const ColorF steepColor(0.5, 0.5, 0.5); // grey
static const ColorF jettingColor(0.9, 0.9, 0.0); // yellow
static const ColorF pathColor(1.0, 1.0, 1.0); // white
// Note that index is only used for comparison to eliminate drawing both ways.
// The transient draw routine above forces the draw by passing zero. The others
// use their proper index.
void NavigationGraph::drawNeighbors(GraphNode* fromNode, GraphEdgeArray edges,
const Point3F& camPos, F32 within)
{
Point3F fromLoc = fromNode->getRenderPos();
S32 fromIndex = fromNode->getIndex();
bool isTransient = fromNode->transient();
within *= within;
while (GraphEdge * edge = edges++) {
// if (edge->mDest > fromIndex || isTransient) {
if (1) { // Think we need to render both ways...
// Flash field edges-
if (edge->getTeam() && !sShowFieldEdges)
{
// Console variable $graphShowFFTeam will make it only flash those edges
// owned by the specified team-
if (!sShowFFTeam || edge->getTeam() == sShowFFTeam)
continue;
}
GraphNode * toNode = mNodeList[edge->mDest];
Point3F toLoc = toNode->getRenderPos(), mid, low, high;
ColorF color(0.07, 0.07, 0.98);
F32 shadePct = F32(toNode->onPath()) * (1.0 / F32(GraphMaxOnPath));
bool whitenPath = (shadePct > 0.0 && fromNode->onPath());
bool isJetting = edge->isJetting();
bool isSteep = edge->isSteep();
bool isBorder = edge->isBorder();
if (sFilterDownEdges && (toLoc.z < fromLoc.z))
continue;
if (isSteep) color = steepColor;
else if (isJetting) color = jettingColor;
else if (isBorder) color = borderColor;
if (whitenPath) {
color.red = scaleBetween(color.red, 1.0f, shadePct);
color.green = scaleBetween(color.green, 1.0f, shadePct);
color.blue = scaleBetween(color.blue, 1.0f, shadePct);
toNode->decOnPath();
fromNode->decOnPath();
}
if (isJetting) {
if (sDrawJetConnections && (toLoc-camPos).lenSquared() < within) {
bool flip;
if(fromLoc.z > toLoc.z) {
flip = true;
(mid = low = toLoc).z = (high = fromLoc).z;
}
else {
flip = false;
(mid = low = fromLoc).z = (high = toLoc).z;
}
if (edge->hasHop()) {
Point3F aboveH(high.x, high.y, high.z + edge->getHop());
mid.z += edge->getHop();
renderEdge(low, mid, &color, whitenPath, flip);
renderEdge(mid, aboveH, &color, whitenPath, flip);
renderEdge(aboveH, high, &color, whitenPath, flip);
}
else {
renderEdge(fromLoc, mid, &color, whitenPath);
renderEdge(mid, toLoc, &color, whitenPath);
}
}
}
else if (isBorder && mHaveVolumes) {
Point3F connectPts[3+1];
connectPts[0] = fromLoc;
connectPts[3] = toLoc;
getCrossingPoints(fromIndex, &connectPts[1], edge);
for (S32 c = 0; c < 3;) {
Point3F end = connectPts[c++];
Point3F start = connectPts[c];
if (isTransient && !whitenPath) {
end.z += 0.28, start.z += 0.28;
ColorF invertColor(1-color.red, 1-color.green, 1-color.blue);
renderEdge(start, end, &invertColor, whitenPath);
}
else {
renderEdge(start, end, &color, whitenPath);
}
}
}
else {
renderEdge(fromLoc, toLoc, &color, whitenPath);
}
}
}//edge loop
}

331
ai/graphSearchLOS.cc Normal file
View file

@ -0,0 +1,331 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
// Searcher
// This "visitor" callback does much of the work for three different types of LOS-based
// queries: hiding (two types), acquiring LOS, and a "choke point" finder.
void GraphSearchLOS::onQExtraction()
{
GraphNode * extNode = extractedNode();
mExtractLoc = extNode->location();
mExtractInd = extNode->getIndex();
setOnRelax(false);
if (mLOSTable && !extNode->transient()) {
if (mSeekingLOS) {
if (!mNeedInside || (mLOSLoc - mExtractLoc).lenSquared() < mFarDist) {
// Check for LOS if we're looking for it. Note table doesn't store LOS
// with same entry. We save the best entry relative to the search
// sphere if the user wants that.
if (mExtractInd != mLOSKey) {
if (mLOSTable->muzzleLOS(mLOSKey, mExtractInd)) {
if (mNearSphere) {
// If we're in band outside of this sphere, take it, else
// keep looking but record best one found.
if (mOuterSphere.isContained(mExtractLoc)) {
setEarlyOut(true);
setTarget(mExtractInd);
}
else {
F32 dSqrd = (mExtractLoc - mNearSphere->center).lenSquared();
if (dSqrd < mBestDistSqrd) {
setTarget(mExtractInd);
mBestDistSqrd = dSqrd;
}
}
}
else {
// No sphere, so we take first LOS point found
setEarlyOut(true);
setTarget(mExtractInd);
}
}
}
}
}
else { // LOS Avoid (find hidden / choke point query)-
if (mExtractInd != mLOSKey) {
if (mAvoidingLOS) {
if (mLOSTable->hidden(mLOSKey, mExtractInd)) {
F32 distHidden = getSearchRef(mExtractInd)->mUser.f;
if (mDistanceBased) {
if (distHidden > mMinHidden) {
setEarlyOut(true);
setTarget(mExtractInd);
}
else {
// For hidden nodes that aren't the final solution, propogate
// distance - which happens in the relaxation virtual below...
setOnRelax(true);
if (distHidden > mBestHidden) {
mBestHidden = distHidden;
setTarget(mExtractInd);
}
}
}
else {
// Hide based on slope. Look for better than supplied minimum
// but track the best we've found. Note that small 2d lengths
// can happen indoors- we ignore as being indeterminate.
Point3F V(mExtractLoc.x-mLOSLoc.x, mExtractLoc.y-mLOSLoc.y, 0);
F32 L = V.len();
F32 dot = (L > 0.001 ? mDot(extNode->getNormal(), V /= L) : -1);
#ifdef _GRAPH_DEBUG_
Point3F nodeNormal = extNode->getNormal();
nodeNormal *= 10;
LineSegment lineSeg(mExtractLoc, mExtractLoc + nodeNormal);
mDebugSegs.push_back(lineSeg);
#endif
if (dot > mMinSlopeDot) {
setEarlyOut(true);
setTarget(mExtractInd);
}
else if (dot > mBestSlopeDot) {
mBestSlopeDot = dot;
setTarget(mExtractInd);
}
}
}
}
else {// Choke point query
if (distSoFar() < mMaxChokeDist) {
// Choke point query. We're interested in first time obstructed nodes
// which have lots of other obstructed nodes that descend from them.
// When we extract a descendant, we see about a new best distance.
if (mLOSTable->hidden(mLOSKey, mExtractInd)) {
setOnRelax(true);
if (mHead.mPrev >= 0) {
mChokePoints.push_back(mExtractInd);
}
else {
mExtractInd = ~mHead.mPrev; // Ones compliment has its uses!
SearchRef * ancestor = getSearchRef(mExtractInd);
if (mHead.mUser.f > ancestor->mUser.f)
ancestor->mUser.f = mHead.mUser.f;
}
}
}
else {
setEarlyOut(true);
}
}
}
}//(LOS Avoid Queries)
}
}
// Here we propogate hidden distance to neighbors that are also hidden.
void GraphSearchLOS::onRelaxEdge(const GraphEdge* edge)
{
if (edge->mDest != mLOSKey)
{
if (mLOSTable->hidden(mLOSKey, edge->mDest))
{
SearchRef * sref = getSearchRef(edge->mDest);
F32 hiddenDistSoFar = mHead.mUser.f;
sref->mUser.f = (hiddenDistSoFar + edge->mDist);
// For choke point query, use mPrev field for ancestor propogation-
if (mChokePtQuery)
sref->mPrev = ~mExtractInd;
}
}
}
U32 gGraphSearchLOSCalls = 0;
// The choke point query requires that we filter out edges going down. We only
// do this on those down edges which have LOS to the key point since we want the
// ones that don't have LOS.
F32 GraphSearchLOS::getEdgeTime(const GraphEdge* edge)
{
if (mChokePtQuery && edge->isJetting() && edge->isDown())
if (edge->mDest != mLOSKey)
if (!mLOSTable->hidden(mLOSKey, edge->mDest))
{
// Probably won't happen often, but there could be a hop down that doesn't
// wind up as a choke point (maybe partial LOS), which will result in some
// point further along the path (that goes through this down hop) being
// a choke point, and the hop down won't get filtered.
return SearchFailureAssure;
}
else
{
// We want short hops from edges to take priority over longer lateral
// hops downward. So we scale up any lateral over a few.
gGraphSearchLOSCalls++;
F32 lateral = JetManager::invertLateralMod(F32(edge->getLateral()));
F32 truncLat = getMax(lateral - 4.0f, 0.0f);
return (truncLat * truncLat);
}
return Parent::getEdgeTime(edge);
}
//-------------------------------------------------------------------------------------
void GraphSearchLOS::setNullDefaults()
{
setEarlyOut(false);
mLOSTable = NULL;
mLOSLoc.set(-1,-1,-1);
mAvoidingLOS = mSeekingLOS = mNeedInside = mChokePtQuery = false;
mDistanceBased = true;
mMinSlopeDot = -1.0;
mBestSlopeDot = -1.0;
mFarDist = 1e22;
mThreatCount = 0;
mMaxChokeDist = 500;
mBestHidden = 0.0f;
mMinHidden = 22.0f;
mNearSphere = NULL;
mDebugSegs.clear();
setOnRelax(false);
}
//-------------------------------------------------------------------------------------
Point3F GraphSearchLOS::findLOSLoc(const Point3F& from, const Point3F& wantToSee,
F32 minDist, const SphereF& getCloseTo, F32 capDist)
{
setNullDefaults();
if (GraphNode * losNode = gNavGraph->closestNode(wantToSee)) {
if (GraphNode * sourceNode = gNavGraph->closestNode(from)) {
// Configure the search for LOS seeking
mLOSKey = losNode->getIndex();
mLOSLoc = losNode->location();
mLOSTable = gNavGraph->getLOSXref();
mNeedInside = (mFarDist = capDist) < 1e6;
mFarDist *= mFarDist;
(mOuterSphere = * (mNearSphere = & getCloseTo)).radius += (mNearWidth = 10);
mBestDistSqrd = 1e13;
mSeekingLOS = true;
gNavGraph->threats()->pushTempThreat(losNode, wantToSee, minDist);
S32 iters = GraphSearch::performSearch(sourceNode, NULL);
gNavGraph->threats()->popTempThreat();
if (getTarget() >= 0) {
getPathIndices(gNavGraph->tempNodeBuff());
return gNavGraph->lookupNode(getTarget())->location();
}
}
}
return from;
}
//-------------------------------------------------------------------------------------
Point3F GraphSearchLOS::hidingPlace(const Point3F& from, const Point3F& avoid,
F32 avoidRad, F32 basisForAvoidance, bool avoidOnSlope)
{
if (GraphNode * losNode = gNavGraph->closestNode(avoid)) {
if (GraphNode * sourceNode = gNavGraph->closestNode(from)) {
// Clear out search machinery and set up what we want to do
setNullDefaults();
mLOSKey = losNode->getIndex();
mLOSLoc = losNode->location();
mLOSTable = gNavGraph->getLOSXref();
mAvoidingLOS = true;
// Can avoid on distance (beyond first hidden point), or on slope.
if (avoidOnSlope) {
mDistanceBased = false;
mMinSlopeDot = mCos(basisForAvoidance);
}
else {
mDistanceBased = true;
mMinHidden = basisForAvoidance;
}
// Run the search with avoid center temporarily affected.
gNavGraph->threats()->pushTempThreat(losNode, avoid, avoidRad);
S32 iters = GraphSearch::performSearch(sourceNode, NULL);
gNavGraph->threats()->popTempThreat();
// Return if something found-
if (getTarget() >= 0) {
getPathIndices(gNavGraph->tempNodeBuff()); // (debug rendering)
return gNavGraph->lookupNode(getTarget())->location();
}
}
}
else {
#if _GRAPH_WARNINGS_
NavigationGraph::warning("hidingPlace() didn't find what it wanted");
#endif
}
return from;
}
//-------------------------------------------------------------------------------------
// Special purpose search which makes use of mPrev field in SearchRef to
// accomplish what it wants- relies on that field not being needed because we're
// not finding a path. A choke point is a point just barely out of LOS from a given
// source, but beyond which there exists a long unbroken hidden string of nodes. This
// condition keeps the bots from finding choke points around poles or into alcoves.
S32 GraphSearchLOS::findChokePoints(GraphNode* S, Vector<Point3F>& points,
F32 minHideDist, F32 maxSearchDist)
{
setNullDefaults();
points.clear();
if ((mLOSTable = gNavGraph->getLOSXref()) != NULL)
{
// Configure the machinery-
mLOSKey = S->getIndex();
mLOSLoc = S->location();
mMaxChokeDist = (maxSearchDist + minHideDist);
mMinHidden = minHideDist;
mChokePoints.clear();
mChokePtQuery = true;
// Do the work-
GraphSearch::performSearch(S, NULL);
GraphNodeList downList;
for (S32 i = 0; i < mChokePoints.size(); i++)
{
S32 nodeInd = mChokePoints[i];
SearchRef * searchRef = getSearchRef(nodeInd);
if (searchRef->mUser.f > minHideDist)
{
// We filter out those nodes which are down a jet connection EXCEPT that if
// very few choke points have been found, then we may use some of the nodes
// that are the top of such jet connections, so these are saved in downList
S32 beforeIndex = lookupSearchRef(searchRef->mPrev)->mIndex;
GraphNode * beforeNode = gNavGraph->lookupNode(beforeIndex);
GraphEdge * edgeTo = beforeNode->getEdgeTo(nodeInd);
if (edgeTo->isJetting() && edgeTo->isDown())
downList.addUnique(beforeNode);
else
points.push_back(gNavGraph->lookupNode(nodeInd)->location());
}
}
// If not very many points were found, then use some of the downList of locations
// that are just prior to a downward jet connection (to a hidden choke point).
// ===> Might want to select a random element of downList per iteration here...
while (downList.size() && points.size() < 4)
{
points.push_back(downList.last()->location());
downList.pop_back();
}
}
return points.size();
}

170
ai/graphSearches.h Normal file
View file

@ -0,0 +1,170 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHSEARCHES_H_
#define _GRAPHSEARCHES_H_
#ifndef _TBINHEAP_H_
#include "ai/tBinHeap.h"
#endif
#define SearchFailureThresh 1e17
#define SearchFailureAssure 1e19
struct SearchRef
{
SearchRef(S32 x, F32 d, F32 t) {mIndex=x; mDist=d; mSort=t; mPrev=-1;}
union UserData {F32 f; U32 u; S32 s; UserData() {f=0;} };
S32 operator<(const SearchRef& sr2) {return mSort > sr2.mSort;}
S16 mIndex;
S16 mPrev;
F32 mDist;
UserData mUser;
F32 mTime;
F32 mSort;
};
typedef Vector<GraphQIndex> QIndexList;
typedef BinHeap<SearchRef> GraphQueue;
class GraphSearch // graphDijkstra.cc
{
GraphQueue & mQueue;
QIndexList & mQIndices;
Vector<F32> & mHeuristicsVec;
GraphPartition & mPartition;
// ^^^^ Shared lists ^^^^
Point3F mTargetLoc;
F32 * mHeuristicsPtr;
S32 mTargetNode, mSourceNode;
F32 mSearchDist;
bool mInProgress;
S32 mIterations;
S32 mRelaxCount;
S32 mTransientStart;
U32 mCurrentSimTime;
U32 mTeam, mInformTeam;
const F32 * mInformRatings, * mJetRatings;
GraphThreatSet mThreatSet, mInformThreats;
GraphEdge mEdgeBuffer[MaxOnDemandEdges];
bool mVisitOnExtract, mVisitOnRelax;
bool mEarlyOut, mAStar, mInformAStar, mRandomize;
bool initializeIndices();
void doSmallReset();
void doLargeReset();
void resetIndices();
SearchRef * insertSearchRef(S32 idx, F32 dist, F32 time);
S32 getAvoidFactor();
F32 calcHeuristic(const GraphEdge* to);
void initSearch(GraphNode * S, GraphNode * D = NULL);
bool runDijkstra();
protected:
GraphNode * mExtractNode;
SearchRef mHead;
void setDone() {mInProgress=false;}
F32 distSoFar() const {return mHead.mDist;}
F32 timeSoFar() const {return mHead.mTime;}
GraphNode * extractedNode() const {return mExtractNode;}
SearchRef * getSearchRef(S32 nodeInd);
SearchRef * lookupSearchRef(S32 qInd);
virtual void onQExtraction() {mVisitOnExtract = false;}
virtual void onRelaxEdge(const GraphEdge*) {mVisitOnRelax = false;}
virtual bool earlyOut() {return (mEarlyOut=false);}
virtual F32 getEdgeTime(const GraphEdge* e);
public:
GraphSearch();
F32 doTableSearch(GraphNode* S, GraphNode* D, Vector<S32>& nodes);
S32 performSearch(GraphNode* S, GraphNode* D = 0);
bool getPathIndices(Vector<S32>& nodeIndexList, S32 target = -1);
bool runSearch(GraphNode* S, GraphNode* D = 0);
void markVisited(BitVector& emptyVec);
void setThreats(GraphThreatSet T) {mInformThreats = T;}
void setRatings(const F32* r) {mInformRatings = r;}
void setTeam(U32 team) {mInformTeam = team;}
const GraphPartition& getPartition() const {return mPartition;}
bool inProgress() const {return mInProgress;}
F32 searchDist() const {return mSearchDist;}
S32 getTarget() const {return mTargetNode;}
S32 relaxCount() const {return mRelaxCount;}
void setRandomize(bool b) {mRandomize = b;}
void setTarget(S32 T) {mTargetNode = T;}
void setEarlyOut(bool b) {mEarlyOut = b;}
void setOnRelax(bool b) {mVisitOnRelax = b;}
void setAStar(bool b = 1) {mInformAStar = b;}
class Globals {
friend class GraphSearch;
GraphQueue searchQ;
QIndexList qIndices;
Vector<F32> heuristics;
GraphPartition partition;
};
};
class GraphSearchLOS : public GraphSearch // graphSearchLOS.cc
{
typedef GraphSearch Parent;
const SearchThreat * * mConsider;
const LOSTable * mLOSTable;
const SphereF* mNearSphere;
Vector<LineSegment> mDebugSegs;
S32 mThreatCount;
S32 mLOSKey, mExtractInd;
Point3F mExtractLoc, mLOSLoc;
SphereF mOuterSphere;
Vector<S32> mChokePoints;
F32 mNearWidth, mFarDist, mMaxChokeDist;
F32 mBestDistSqrd, mBestHidden, mMinHidden;
F32 mMinSlopeDot, mBestSlopeDot;
bool mSeekingLOS, mAvoidingLOS;
bool mDistanceBased;
bool mChokePtQuery;
bool mNeedInside;
void setNullDefaults();
protected:
void onQExtraction();
void onRelaxEdge(const GraphEdge*);
F32 getEdgeTime(const GraphEdge* e);
bool earlyOut() {return true;}
public:
S32 performSearch(GraphNode* S, GraphNode* D, const SearchThreats& T);
S32 findChokePoints(GraphNode* S, Vector<Point3F>& pts, F32 hideD, F32 maxD);
Point3F hidingPlace(const Point3F& from, const Point3F& avoid,
F32 avoidRad, F32 avoidBasis, bool avoidOnSlope);
Point3F findLOSLoc(const Point3F& from, const Point3F& wantToSee, F32 minDist,
const SphereF& getCloseTo, F32 capDist);
};
class GraphSearchDist : public GraphSearch // graphLocate.cc
{
GraphNode * mBestMatch;
NodeProximity mBestMetric;
Point3F mPoint;
ProximateList mProximate;
F32 mCurThreshold;
F32 mMaxSearchDist;
bool mSearchOnDepth;
protected:
F32 getEdgeTime(const GraphEdge* edge);
void onQExtraction();
public:
GraphSearchDist();
NodeProximity findBestMatch(GraphNode* cur, const Point3F& loc);
GraphNode * bestMatch() const {return mBestMatch;}
void setOnDepth(bool b) {mSearchOnDepth = b;}
bool earlyOut() {return true;}
ProximateList& getProximate() {return mProximate;}
void setDistCap(F32 d);
};
#endif

270
ai/graphSmooth.cc Normal file
View file

@ -0,0 +1,270 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
#ifdef _GRAPH_DEBUG_
static void debugDrawBorderSeg(const GraphBoundary& border);
static void debugDrawParaNormal(const Point3F& midpoint, VectorF paraNormal);
static void debugDrawSeekVector(Point3F curLoc, Point3F seekLoc);
#else
#define debugDrawBorderSeg(border)
#define debugDrawParaNormal(midpoint,paranormal)
#define debugDrawSeekVector(curloc,seekloc)
#endif
//-------------------------------------------------------------------------------------
// define SegCheck3D (2.0f)
#define SegCheck3D (4.0f)
#define SegCheck2D (0.5f)
#define RoomNeeded (0.5f)
#define InsideThreshSquared (RoomNeeded * RoomNeeded)
// User has to call this this first-
bool NavigationGraph::useVolumeTraverse(const GraphNode* from, const GraphEdge* to)
{
if (mHaveVolumes)
if (from->canHaveBorders()) // i.e (indoor | transient)
if (from->volumeIndex() >= 0)
if (to && !to->isJetting() && to->isBorder())
return true;
return false;
}
//
// See if line from currLoc to seekLoc can pass through the border.
//
// Ok, I'm beginning to see the wisdom of pursuing a BSP-based approach to the
// NAV graphs. We're doing a lot of work here and still not getting nearly enough
// free-form movement as could/should be had.
//
//==> This should probably account for different bot sizes.
//
static bool canFitThrough(const Point3F& currLoc, const Point3F& seekLoc,
const GraphBoundary& border)
{
// We rely on fact that boundary normal is parallel to ground, and just do 2D math
// here. The source and destination locations should be on opposite sides. These
// dot products then also give us an interpolation value to find the crossing point.
VectorF seek2D = seekLoc, curr2D = currLoc;
(seek2D -= border.seg[0]).z = 0;
(curr2D -= border.seg[0]).z = 0;
F32 seekDot = mDot(seek2D, border.normal);
F32 currDot = mDot(curr2D, border.normal);
if (seekDot > 0.03 && currDot < -0.03)
{
F32 interp = -currDot / (seekDot - currDot);
Point3F crossPt = scaleBetween(currLoc, seekLoc, interp);
// Want to make sure the crossing point is well inside. We've done plenty of
// math already, let's avoid square roots and just check 2 sufficient conditions:
// Dist from endpoints is enough, vectors to endpoints point away from each other
VectorF from0 = crossPt, from1 = crossPt;
(from0 -= border.seg[0]).z = 0;
(from1 -= border.seg[1]).z = 0;
if (from0.lenSquared() > InsideThreshSquared)
if (from1.lenSquared() > InsideThreshSquared)
if (mDot(from0, from1) < -0.9)
return true;
}
return false;
}
//-------------------------------------------------------------------------------------
bool NavigationGraph::volumeTraverse(const GraphNode* from, const GraphEdge* to,
NavigationPath::State & S)
{
bool canAdvance = false;
const GraphBoundary & border = mBoundaries[to->mBorder];
Point3F midpoint = (border.seg[0] + border.seg[1]) * 0.5f;
BitSet32 & visit = S.visit[S.curSeekNode-1];
debugDrawBorderSeg(border);
// See if the node advancer below skipped this fromNode.
if (visit.test(NavigationPath::State::Skipped))
{
// If so then we just want to get on the other side of it.
F32 dot = mDot(S.hereLoc - border.seg[0], border.normal);
if (dot > 0.05)
canAdvance = true;
}
else
{
if (inNodeVolume(from, S.hereLoc))
{
visit.set(NavigationPath::State::Visited);
// Get "line" to check proximity to (just use a really large segment)
// When near, head across, else seek the proper point.
Point3F paraNormal = (border.normal * 33.0);
LineSegment crossingLine(midpoint - paraNormal, midpoint + paraNormal);
debugDrawParaNormal(midpoint, paraNormal);
// This state was flipping on some small nodes near walls - should work to
// just only head out once in this case.
if (!visit.test(NavigationPath::State::Outbound))
{
if (crossingLine.botDistCheck(S.hereLoc, SegCheck3D, SegCheck2D))
visit.set(NavigationPath::State::Outbound);
else
S.seekLoc = border.seekPt;
}
if (visit.test(NavigationPath::State::Outbound))
S.seekLoc = midpoint + (border.normal * 2.0);
}
else
{
// The source node has been visited, but we're in the next node and want to
// decide if we can begin tracking next segment (if that's what's next).
if (visit.test(NavigationPath::State::Visited))
{
LineSegment borderSeg(border.seg[0], border.seg[1]);
//==> THIS CHECK NEEDS TO LOOK AT SEGMENT WHEN LARGE (use projected pulled in loc on seg)
// We could modifiy this pull-in a little based on player distance, and it would
// then traverse a little differently.
// This has a problem in a certain case- they might not be able to achieve getting
// inside the volume for narrow ones. Need to handle being here for a couple of
// frames, plus we should make the approach to the border be better - based
// on where you want to go.
if (!borderSeg.botDistCheck(S.hereLoc, SegCheck3D, 0.2))
{
// When the next edge is either jetting or laid walk connection, then
// we need to actually go to the node location before advancing.
if (S.nextEdgePrecise)
{
S.seekLoc = lookupNode(to->mDest)->location();
if (within_2D(S.hereLoc, S.seekLoc, 0.3))
canAdvance = true;
}
else
{
mRenderThese.clear();
canAdvance = true;
}
}
else
{
S.seekLoc = (midpoint + (border.normal * 2.0));
}
}
else
{
Point3F inVec = (border.seg[1] - border.seg[0]);
F32 len = inVec.len();
AssertFatal(len > 0.01, "There is a way-too-short border segment");
inVec.normalize(len > 1.2 ? 0.4 : len / 3); // get 'pulled in' segment
LineSegment borderSeg(border.seg[0]+inVec, border.seg[1]-inVec);
if (borderSeg.botDistCheck(S.hereLoc, SegCheck3D, SegCheck2D))
{
mRenderThese.clear();
canAdvance = true;
}
else
{
S.seekLoc = from->location();
}
}
}
}
debugDrawSeekVector(S.hereLoc, S.seekLoc);
return canAdvance;
}
// This just tries to do further skips from the current location. The above code
// performs advancement in the case of skipped nodes. Like the outdoor advance,
// we only attempt one at a time, but unlike it the advancement doesn't happen
// right away.
S32 NavigationGraph::checkIndoorSkip(NavigationPath::State & S)
{
S32 startingFrom = (S.curSeekNode - 1);
S32 numberSkipped = 0;
if (!S.visit[startingFrom].test(NavigationPath::State::Skipped))
{
// See if we can skip ahead through outbound segments.
for (S32 from = startingFrom; from < S.edges.size() - 1; from++)
{
GraphEdge * fromOut = S.edges[from];
GraphEdge * toOut = S.edges[from+1];
if (fromOut->isBorder() && toOut->isBorder())
{
AssertFatal(!fromOut->isJetting() && !toOut->isJetting(), "Indoor skip");
const GraphBoundary & toBorder = mBoundaries[toOut->mBorder];
const GraphBoundary & fromBorder = mBoundaries[fromOut->mBorder];
// See if segment to the TO outbound place passes Ok through FROM outbound
if (canFitThrough(S.hereLoc, toBorder.seekPt, fromBorder))
{
// The first from has already been visited....
if (from > startingFrom)
{
S.seekLoc = toBorder.seekPt;
S.visit[from].set(NavigationPath::State::Skipped);
numberSkipped++;
continue;
}
}
}
break;
}
}
return numberSkipped;
}
//-------------------------------------------------------------------------------------
#ifdef _GRAPH_DEBUG_
static void debugDrawBorderSeg(const GraphBoundary& border)
{
Point3F bseg1(border.seg[0]);
Point3F bseg2(border.seg[1]);
bseg1.z += 1.0f;
bseg2.z += 1.0f;
LineSegment borderSeg(bseg1, bseg2);
gNavGraph->pushRenderSeg(borderSeg);
}
static void debugDrawParaNormal(const Point3F& midpoint, VectorF paraNormal)
{
paraNormal.normalize(4.0);
Point3F p1(midpoint - paraNormal);
Point3F p2(midpoint + paraNormal);
p1.z += 1.4f;
p2.z += 1.4f;
LineSegment paraNormalSeg(p1, p2);
gNavGraph->pushRenderSeg(paraNormalSeg);
}
static void debugDrawSeekVector(Point3F curLoc, Point3F seekLoc)
{
curLoc.z += 1.8f;
seekLoc.z += 1.8f;
LineSegment seekVecSeg(curLoc, seekLoc);
gNavGraph->pushRenderSeg(seekVecSeg);
}
#endif

372
ai/graphSpawn.cc Normal file
View file

@ -0,0 +1,372 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#include "game/missionMarker.h"
//-------------------------------------------------------------------------------------
// Utility routine for below- Get nodes in sphere, separated into indoor/outdoor
void NavigationGraph::getInSphere(const SphereF& S, GraphNodeList& I, GraphNodeList& O)
{
// Find all the candidate nodes.
for (S32 i = 0; i < numNodes(); i++)
if (GraphNode * node = lookupNode(i))
if (S.isContained(node->location()))
(node->outdoor() ? O : I).push_back(node);
}
static void findSpawnSpheres(Vector<SpawnSphere* > & list)
{
for (SimSetIterator itr(Sim::getRootGroup()); *itr; ++itr)
if (SpawnSphere * ss = dynamic_cast<SpawnSphere *>(*itr))
if (!ss->isClientObject())
for (S32 i = list.size() - 1; i > -2; i--) // Check if it's already in our
if (i < 0) // list since the spawn spheres
list.push_back(ss); // are kept in a couple of sets
else if (ss == list[i])
break;
}
// Build the spawn data out from the node list. We're basically making a pre-
// randomized list per spawn sphere so the spawn lookups happen fast, plus
// we control the amount of data we're using for this system.
void NavigationGraph::makeSpawnList()
{
mSpawnList.reset();
Vector<SpawnSphere* > spawnSpheres;
findSpawnSpheres(spawnSpheres);
if (spawnSpheres.size() == 0)
{
Con::printf("Warning! No spawn sphere markers found in mission file!");
return;
}
// For holding lists for each sphere-
GraphNodeList indoor;
GraphNodeList outdoor;
indoor.reserve(mNumIndoor);
outdoor.reserve(mNumOutdoor);
// List to build-
SpawnLocations spawnList;
// Build our list of spheres for indoor / outdoor.
for (S32 s = 0; s < spawnSpheres.size(); s++)
{
outdoor.clear();
indoor.clear();
// Get the lists-
SpawnSphere * ss = spawnSpheres[s];
SpawnLocations::Sphere sphereList(ss->getPosition(), ss->mRadius);
getInSphere(sphereList.mSphere, indoor, outdoor);
// Add spheres for outdoor and indoor if wanted and available.
if (ss->mIndoorWeight) {
if (indoor.size()) {
sphereList.mInside = true;
spawnList.mSpheres.push_back(sphereList);
}
else
Con::printf("Warning- Spawn sphere with no INDOOR nodes found!");
}
if (ss->mOutdoorWeight) {
if (outdoor.size()) {
sphereList.mInside = false;
spawnList.mSpheres.push_back(sphereList);
}
else
Con::printf("Warning- Spawn sphere with no OUTDOOR nodes found!");
}
}
// Figure out max budget per spawn sphere. Try to keep it to 300 total, but each one
// should have at least 20.
S32 maxPer = 50;
if (spawnList.mSpheres.size() > 1)
maxPer = getMin(maxPer, 300 / spawnList.mSpheres.size());
maxPer = getMax(20, maxPer);
// Create pre-randomized location list for each sphere.
for (S32 i = 0; i < spawnList.mSpheres.size(); i++)
{
SpawnLocations::Sphere & sphereList = spawnList.mSpheres[i];
outdoor.clear();
indoor.clear();
getInSphere(sphereList.mSphere, indoor, outdoor);
S32 maxPointsHere = maxPer;
// Get the list of points. If it's indoor and the # of nodes is smaller
// than the budget, then just scan all of them. Otherwise ... randomize.
sphereList.mOffset = spawnList.size();
if (sphereList.mInside && indoor.size() < maxPointsHere)
{
for (S32 j = 0; j < indoor.size(); j++)
{
GraphNode * node = indoor[j];
Point3F point = node->location();
if (sanctionSpawnLoc(node, point))
spawnList.push_back(point);
}
}
else
{
GraphNodeList & nodeList = (sphereList.mInside ? indoor : outdoor);
U32 nodeListSize = nodeList.size();
S32 numPushed = 0;
// Go for a while longer in case a lot are filtered-
for (S32 j = 0; j < (maxPointsHere * 10) && numPushed < maxPointsHere; j++)
{
U32 randIndex = (gRandGen.randI() % nodeListSize);
GraphNode * node = nodeList[randIndex];
Point3F point = node->randomLoc();
if (sanctionSpawnLoc(node, point))
{
spawnList.push_back(point);
numPushed++;
}
}
}
// Set how many actually got added-
sphereList.mCount = (spawnList.size() - sphereList.mOffset);
// Hopefully some got added-
if (sphereList.mCount == 0)
Con::printf("Warning: Spawn sphere didn't have any valid nodes within it.");
}
// List is made, copy it over-
mSpawnList = spawnList;
}
//-------------------------------------------------------------------------------------
// Spawn node functions. Old system operates if there's a NAV, else the new one
// if it's just a spawn graph.
const Point3F * NavigationGraph::getSpawnLoc(S32 nodeIndex)
{
if (mSpawnList.empty())
{
if (validArrayIndex(nodeIndex, numNodes()))
if (GraphNode * node = lookupNode(nodeIndex))
return & node->location();
}
else
{
if (validArrayIndex(nodeIndex, mSpawnList.size()))
return & mSpawnList[nodeIndex];
}
return NULL;
}
const Point3F * NavigationGraph::getRandSpawnLoc(S32 nodeIndex)
{
if (mSpawnList.empty())
{
if (validArrayIndex(nodeIndex, numNodes()))
if (GraphNode * node = lookupNode(nodeIndex))
{
static Point3F sPoint;
sPoint = node->randomLoc();
return & sPoint;
}
}
else
{
if (validArrayIndex(nodeIndex, mSpawnList.size()))
return & mSpawnList[nodeIndex];
}
return NULL;
}
//-------------------------------------------------------------------------------------
#define CastShift 6
#define NumCasts (1 << CastShift)
#define MaskOff (NumCasts - 1)
#define Periphery (NumCasts / 4 + 1)
#define AngleInc ((M_PI * 2.0) / F32(NumCasts))
#define CapDistance 60.0
// Do NumCasts LOS casts and save the distances (squared) and angles. Return minimum
// of the dists array. Used by the spawn function to find where to look, and by the
// code that pre-generates the random spawn locations.
static F32 lookAround(Point3F P, F32 dists[NumCasts], F32 qAngles[NumCasts])
{
F32 minDist = (CapDistance * CapDistance);
RayInfo coll;
// Maybe get eye-height here, though midsection level would work better for
// guaranteeing a nicer view, in most cases.
P.z += 1.0;
// Gather distances.
for (S32 i = 0; i < NumCasts; i++)
{
// Get quat angle, and do 90 degree conversion
F32 ang = F32(i) * AngleInc;
qAngles[i] = ang;
ang += (M_PI / 2.0);
// Get Direction vector- convert to Dest vector.
VectorF D(mCos(ang), mSin(ang), 0);
D *= CapDistance;
D += P;
// Find distances squared, and the min-
// const U32 mask = (InteriorObjectType|StaticShapeObjectType|TerrainObjectType);
if (gServerContainer.castRay(P, D, U32(-1), &coll))
minDist = getMin( dists[i] = (coll.point -= P).lenSquared(), minDist );
else
dists[i] = (CapDistance * CapDistance);
}
return minDist;
}
// This really isn't a graph function per se, but since this is affiliated with the
// spawn graph stuff, might as well go in the graph code.
F32 NavigationGraph::whereToLook(Point3F P)
{
F32 dists[NumCasts];
F32 qAngles[NumCasts];
// Do circle of LOS casts-
lookAround(P, dists, qAngles);
// Now find out what looks the best. Maximize difference of squares form forward
// to side. Note that side angle is a little more than 90 deg so that we angle
// away from wall a little bit.
S32 bestIndex = 0;
F32 bestMetric = -1e13, sideDist, metric;
const U32 wrap = (NumCasts - 1);
for (U32 j = 0; j < NumCasts; j++)
{
// Take smaller off two off to the side distances-
sideDist = getMin(dists[(j - Periphery) & wrap], dists[(j + Periphery) & wrap]);
// Metric is (forward dist squared) - ((smaller) side distance squared)
if ((metric = (dists[j] - sideDist)) > bestMetric)
bestMetric = metric, bestIndex = j;
}
// Return the angle need for get/set transform.
return qAngles[bestIndex];
}
//-------------------------------------------------------------------------------------
#define SpawnSlopeLimit 0.866 // cos 30
static bool checkSlopeBelow(const Point3F& point)
{
RayInfo coll;
Point3F below(point.x, point.y, point.z - 10.0);
if (gServerContainer.castRay(point, below, U32(-1), &coll))
if (coll.normal.z > SpawnSlopeLimit)
return true;
return false;
}
// A spawn location is only generated if, looking from waist out in a circle, no
// collisions found within these thresholds.
#define IndoorSpawnExtraDist 1.8
#define OutdoorSpawnExtraDist 7.2
bool NavigationGraph::sanctionSpawnLoc(GraphNode * node, const Point3F& point) const
{
if (!node->liquidZone() && !node->inventory() && checkSlopeBelow(point))
{
F32 dists[NumCasts];
F32 angles[NumCasts];
F32 thresh = (node->indoor() ? IndoorSpawnExtraDist : OutdoorSpawnExtraDist);
F32 minDistSqrd = lookAround(point, dists, angles);
return minDistSqrd > (thresh * thresh);
}
return false;
}
//-------------------------------------------------------------------------------------
// The parameters are from randI(), make a 64 bit return value in range [0,1]
static F64 getRandom64(U64 r1, U64 r2)
{
r1 <<= 31;
r1 |= r2;
F64 frand = F64(r1);
F64 fnorm = frand * (1.0 / 4611686018427387904.0);
return fnorm;
}
S32 NavigationGraph::randNode(const Point3F& P, F32 R, bool indoor, bool outdoor)
{
SphereF sphere(P, R);
// If there's a valid spawn list, that's what we use. This will occcur when
// it's a SPN file, or a NAV file from which only the spawn data was loaded.
if (!mSpawnList.empty())
{
return mSpawnList.getRandom(sphere, indoor, gRandGen.randI());
}
// Use graph utility lists to save on realloc calls.
mUtilityNodeList1.clear();
mUtilityNodeList2.clear();
// Note terrain should be done first since getNodesInArea() clears list...
if (outdoor && haveTerrain())
{
S32 gridRad = S32(R / gNavGlobs.mSquareWidth) + 1;
GridArea gridArea = getGridRectangle(P, gridRad);
getNodesInArea(mUtilityNodeList1, gridArea);
}
// ... and indoor second since getIntersecting() appends to list.
if (indoor)
{
Box3F wBox(P, P, true);
Point3F ext(R, R, R);
wBox.min -= ext;
wBox.max += ext;
mIndoorTree.getIntersecting(mUtilityNodeList1, wBox);
}
S32 count = mUtilityNodeList2.searchSphere(sphere, mUtilityNodeList1);
#if 1
if (count > 0)
return mUtilityNodeList2[gRandGen.randI() % count]->getIndex();
#else
// Stuff to do weighted average-
// Get total area, along with a cutout point that should give us a
// weighted random of the nodes based on their estimated area-
F64 cutoffArea = 0.0f;
for (S32 pass = 1; pass <= 2; pass++)
{
F64 totalArea = 0.0f;
for (S32 i = 0; i < count; i++)
{
F64 R = insideSphere[i]->radius();
F64 area = (R * R);
totalArea += area;
if (pass == 2 && totalArea >= cutoffArea)
return insideSphere[i]->getIndex();
}
if (pass == 1)
cutoffArea = totalArea * getRandom64(gRandGen.randI(), gRandGen.randI());
}
#endif
return -1;
}

245
ai/graphThreats.cc Normal file
View file

@ -0,0 +1,245 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
#define ThreatCheckFrequency 1.0f
#define ThreatCheckDelay U32( 1000.0f / ThreatCheckFrequency )
//-------------------------------------------------------------------------------------
// Search Threat Object
SearchThreat::SearchThreat()
{
mActive = false;
mTeam = 0;
}
SearchThreat::SearchThreat(ShapeBase * threat, F32 R, S32 team)
{
mThreat = threat;
MatrixF const& threatTransform = threat->getTransform();
threatTransform.getColumn(3, &center);
radius = R;
mActive = (threat->getDamageState() == ShapeBase::Enabled);
mTeam = team;
}
// Update the threat bit set on the nodes that this threat effects. Want it to
// be fast, so the two loops are separate.
void SearchThreat::updateNodes()
{
GraphNodeList::iterator n = mEffects.begin();
GraphThreatSet threatSetOn = 1;
GraphThreatSet threatSetOff = ~(threatSetOn <<= mSlot);
S32 count = mEffects.size();
if (mActive)
while( --count >= 0 )
(* n++)->threats() |= threatSetOn;
else
while( --count >= 0 )
(* n++)->threats() &= threatSetOff;
}
// Check for changes to team or enabled status. If the enabled status changes, then
// bunch of edges change - let caller know there was a big change.
bool SearchThreat::checkEnableChange()
{
bool enabled = (mThreat->getDamageState() == ShapeBase::Enabled);
if (enabled != mActive) {
mActive = enabled;
updateNodes();
return true;
}
return false;
}
//-------------------------------------------------------------------------------------
// Threat List Management / Configuration
SearchThreats::SearchThreats()
{
dMemset(mTeamCaresAbout, 0, sizeof(mTeamCaresAbout));
// Team zero is reserved for threats temporarily installed for the purpose of the
// LOS queries. (These queries temporarily flag edges, and then unflags them).
// All teams "worry" about threat zero.
mCheck = mCount = 1;
informOtherTeams(-1, 0, true);
mThreats[0].mActive = false;
mSaveTimeMS = Sim::getCurrentTime();
}
// Inform all other teams of this threat's active status (update their care-about set).
void SearchThreats::informOtherTeams(S32 thisTeam, S32 thisThreat, bool isOn)
{
GraphThreatSet threatSet = 1;
threatSet <<= thisThreat;
for (S32 i = 0; i < GraphMaxTeams; i++)
if (i != thisTeam && isOn)
mTeamCaresAbout[i] |= threatSet;
else
mTeamCaresAbout[i] &= ~threatSet;
}
GraphThreatSet SearchThreats::getThreatSet(S32 team) const
{
AssertFatal(validArrayIndex(team, GraphMaxTeams), "Bad team # to getThreatSet()");
return mTeamCaresAbout[team];
}
void SearchThreats::pushTempThreat(GraphNode* losNode, const Point3F& avoidPt, F32 avoidRad)
{
AssertFatal(mThreats[0].mEffects.size() == 0, "Temp threat push not balanced");
mThreats[0].mEffects = gNavGraph->getVisibleNodes(losNode, avoidPt, avoidRad);
mThreats[0].mActive = true;
mThreats[0].updateNodes();
}
void SearchThreats::popTempThreat()
{
mThreats[0].mActive = false;
mThreats[0].updateNodes();
mThreats[0].mEffects.clear();
}
// Add the threat. This is called once per MIS file threat at mission startup.
bool SearchThreats::add(const SearchThreat& threat)
{
if (mCount < MaxThreats) {
S32 X = mCount++;
mThreats[X] = threat;
informOtherTeams(threat.mTeam, mThreats[X].mSlot = X, true);
mThreats[X].mEffects = gNavGraph->getVisibleNodes(threat.center, threat.radius);
mThreats[X].updateNodes();
return true;
}
return false;
}
// Fetch into autoclass buffer, meant for immediate use.
S32 SearchThreats::getThreatPtrs(const SearchThreat* list[MaxThreats], GraphThreatSet mask) const
{
S32 numActive = 0;
GraphThreatSet bit = 1;
for (S32 i = 0; i < mCount; i++, bit <<= 1)
if ((mask & bit) && mThreats[i].mActive)
list[numActive++] = &mThreats[i];
return numActive;
}
// This is called every tick to efficiently keep the list clean of threats
// that have gone away, or to monitor status of the threat.
//
// We return true if we can afford to do some more stuff and we're not at list's
// end. We can't afford to do more stuff if some threat had to be rescanned.
bool SearchThreats::checkOne()
{
bool canCheckMore = true;
if (mCheck < mCount) {
if (!bool(mThreats[mCheck].mThreat)) {
// AssertFatal(0, "Can static threats go away?");
// if (mCheck < --mCount) // fast erase
// mThreats[mCheck] = mThreats[mCount];
// mThreats[mCount].mThreat = NULL;
// mThreats[mCount].mActive = false;
}
else {
// getSensorGroup() has problem - for now just leave it at assigned team in order
// to get it tested.
// S32 team = mThreats[mCheck].mThreat->getSensorGroup();
// if (mThreats[mCheck].mTeam != team) {
// informOtherTeams(mThreats[mCheck].mTeam, mCheck, false);
// informOtherTeams(team, mCheck, mThreats[mCheck].mActive);
// mThreats[mCheck].mTeam = team;
// }
if (mThreats[mCheck].checkEnableChange()) {
canCheckMore = false;
informOtherTeams(mThreats[mCheck].mTeam, mCheck, mThreats[mCheck].mActive);
}
}
mCheck++;
}
else {
// Skip the reserved first threat-
mCheck = 1;
canCheckMore = false;
}
return canCheckMore;
}
// Called frequently - but only do at most ThreatCheckFrequency times a second.
void SearchThreats::monitorThreats()
{
U32 T = Sim::getCurrentTime();
if ( (T - mSaveTimeMS) > ThreatCheckDelay )
{
mSaveTimeMS = T;
while (checkOne())
;
}
}
// The path advancer uses this to see if a leg it wants to advance along
// goes through any registered threats.
bool SearchThreats::sanction(const Point3F& from, const Point3F& to, S32 team) const
{
if (mCount)
{
const SearchThreat * threatList[MaxThreats];
if (S32 N = getThreatPtrs(threatList, getThreatSet(team)))
{
LineSegment travelLine(from, to);
while (--N >= 0)
{
const SphereF * sphere = threatList[N];
if (sphere->isContained(from) || sphere->isContained(to))
return false;
if (travelLine.distance(sphere->center) < sphere->radius)
return false;
}
}
}
return true;
}
// Just used for some debug rendering of those nodes in view of threats specified
// by console debug vars.
GraphThreatSet NavigationGraph::showWhichThreats() const
{
if (sShowThreatened >= 0)
if (S32 N = mThreats.numThreats())
return GraphThreatSet(1) << (sShowThreatened % N);
return 0;
}
bool NavigationGraph::installThreat(ShapeBase * threat, S32 team, F32 rad)
{
if (mIsSpawnGraph)
{
// Just a spawn graph...
return true;
}
else
{
U32 memUsedBefore = Memory::getMemoryUsed();
SearchThreat newThreat(threat, rad, team);
bool success = mThreats.add(newThreat);
sLoadMemUsed += (Memory::getMemoryUsed() - memUsedBefore);
return success;
}
}

56
ai/graphThreats.h Normal file
View file

@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHTHREATS_H_
#define _GRAPHTHREATS_H_
#ifndef _SHAPEBASE_H_
#include "game/shapeBase.h"
#endif
struct SearchThreat : public SphereF
{
SearchThreat();
SearchThreat(ShapeBase * threat, F32 R, S32 team);
void updateNodes();
bool checkEnableChange();
SimObjectPtr<ShapeBase> mThreat;
GraphNodeList mEffects;
bool mActive;
S16 mTeam;
S16 mSlot;
};
class SearchThreats
{
public:
enum{ MaxThreats = (sizeof(GraphThreatSet) << 3) };
protected:
void informOtherTeams(S32 onTeam, S32 thisThreat, bool isOn);
S32 getThreatPtrs(const SearchThreat* L[MaxThreats], GraphThreatSet S) const;
protected:
SearchThreat mThreats[MaxThreats];
GraphThreatSet mTeamCaresAbout[GraphMaxTeams];
S32 mCheck, mCount;
U32 mSaveTimeMS;
public:
SearchThreats();
void pushTempThreat(GraphNode* node, const Point3F& loc, F32 rad);
void popTempThreat();
bool checkOne();
bool add(const SearchThreat&);
GraphThreatSet getThreatSet(S32 forTeam) const;
bool sanction(const Point3F& from, const Point3F& to, S32 team) const;
void monitorThreats();
S32 numThreats() const {return mCount;}
};
#endif

318
ai/graphTransient.cc Normal file
View file

@ -0,0 +1,318 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
//-------------------------------------------------------------------------------------
GraphEdge * GraphNode::pushTransientEdge(S32 dest)
{
// if (mEdges.isOwned())
// {
// AssertFatal(!mOwnedPtr, "GraphNode::pushTransientEdge()");
// mOwnedPtr = mEdges.address();
// mOwnedCnt = mEdges.size();
// mEdges.clearOwned();
// mEdges.reserve(mOwnedCnt + 4);
// mEdges.setSize(mOwnedCnt);
// dMemcpy(mEdges.address(), mOwnedPtr, mOwnedCnt * sizeof(GraphEdge));
// }
// Hook back to the supplied destination-
GraphEdge edge;
edge.mDest = dest;
edge.mDist = (mLoc - gNavGraph->lookupNode(dest)->location()).len();
mEdges.push_back(edge);
return & mEdges.last();
}
void GraphNode::popTransientEdge()
{
// if (mOwnedPtr)
// {
// AssertFatal(!mEdges.isOwned(), "GraphNode::popTransientEdge()");
// mEdges.setOwned(mOwnedPtr, mOwnedCnt, true);
// mOwnedPtr = NULL;
// mOwnedCnt = 0;
// }
// if (!mEdges.isOwned())
// mEdges.pop_back();
mEdges.pop_back();
}
//-------------------------------------------------------------------------------------
TransientNode::TransientNode(S32 index)
{
mGroundStart = -1;
mFlags.clear();
mFlags.set(Transient);
mIndex = index;
mClosest = NULL;
mSaveClosest = NULL;
}
Point3F TransientNode::getRenderPos() const
{
Point3F buff, adjusted;
adjusted = fetchLoc(buff);
adjusted.z += 0.2;
return adjusted;
}
// When asked for its list of edges, the transient node must return the list
// it used in the last search, not whatever it might currently think is it's
// best connection (it maintains these separately). This is a little confusing,
// might want to re-think if it's the best way to handle it.
GraphEdgeArray TransientNode::getEdges(GraphEdge*) const
{
return GraphEdgeArray(mSearchEdges.size(), mSearchEdges.address());
}
// This method is used below to get the current best hooks.
GraphEdgeArray TransientNode::getHookedEdges() const
{
return GraphEdgeArray(mEdges.size(), mEdges.address());
}
// This is weird, but I'm avoiding casts in the middle of the processing. Allows
// node traverser to handle transients Ok.
S32 TransientNode::volumeIndex() const
{
if (mSaveClosest && mSaveClosest->indoor())
return mSaveClosest->volumeIndex();
return -1;
}
//-------------------------------------------------------------------------------------
// Interface function for pairs of dynamic nodes
// Remove the two hook nodes referenced by the index. Should always be nodes there
// if proper version management (using 'incarnation' variables) is happening.
void NavigationGraph::destroyPairOfHooks(S32 pairLookup)
{
AssertFatal(validArrayIndex(pairLookup,mNodeList.size()-1) &&
mNodeList[pairLookup] && mNodeList[pairLookup + 1],
"Poor GraphHookRequest communication" );
for(S32 i = 0; i < 2; i++) {
delete mNodeList[pairLookup + i];
mNodeList[pairLookup + i] = NULL;
}
}
// Note that NULL entries are fleshed out at the end of graphMake (in doFinalFixups()).
S32 NavigationGraph::createPairOfHooks()
{
S32 findPair = mMaxTransients;
while( (findPair -= 2) >= 0 )
if(!mNodeList[ mTransientStart + findPair])
break;
AssertISV(findPair >= 0, "Out of dynamic graph connections (bot max exceeded?)");
findPair += mTransientStart;
AssertFatal(!mNodeList[findPair+1], "Free graph hooks must come in pairs");
// allocate 'em
for (S32 i = 0; i < 2; i++)
mNodeList[findPair + i] = new TransientNode(findPair + i);
return findPair;
}
//-------------------------------------------------------------------------------------
void mayBeFineButTrapIt()
{
}
// Transient nodes point INTO the graph, but the graph doesn't need or maintain hooks
// back, except when a search is performed - see navPath.cc.
//
// We also set the search edges here - we copy over those nodes that had LOS. If
// none have LOS, we keep at least the closest one.
//
S32 NavigationGraph::hookTransient(TransientNode& transient)
{
S32 retIsland = -7;
S32 thisIndex = transient.getIndex();
GraphEdgeArray edgeList = transient.getHookedEdges();
Point3F thisLoc = transient.mLoc;
transient.mSearchEdges.clear();
transient.mSaveClosest = transient.mClosest;
transient.mFirstHook = NULL;
while (GraphEdge * edge = edgeList++)
{
GraphNode * hookTo = lookupNode(edge->mDest);
S32 hookIsland = hookTo->mIsland;
Point3F destLoc = hookTo->location();
if((retIsland != -7) && (hookIsland != retIsland))
mayBeFineButTrapIt();
else
retIsland = hookIsland;
// The first hook is used as a representative node to determine reachability
// across partitions.
if (!transient.mFirstHook && !hookTo->transient())
transient.mFirstHook = hookTo;
if (GraphEdge * edgeBack = hookTo->pushTransientEdge(thisIndex))
{
// Last edge of path may contain a border. Note we have to reflect the
// border, which is easy since they come in pairs :)
if (edge->isBorder())
edgeBack->mBorder = (edge->mBorder ^ 1);
if (edge->isJetting())
edgeBack->setJetting();
if (edge->getTeam())
edgeBack->setTeam(edge->getTeam());
}
transient.mSearchEdges.push_back(*edge);
}
// restore the regular list.
transient.mEdges = transient.mSearchEdges;
return (transient.mIsland = retIsland);
}
void NavigationGraph::unhookTransient(TransientNode & transient)
{
GraphEdgeArray edgeList = transient.getEdges(NULL);
while (GraphEdge * edge = edgeList++)
lookupNode(edge->mDest)->popTransientEdge();
}
//-------------------------------------------------------------------------------------
// Balanced calls for hooking in pair of transients. Hook returns if the two locations
// can find each other (or if it's ambiguous). Determined by islands and partitions.
bool NavigationGraph::pushTransientPair(TransientNode& srcNode, TransientNode& dstNode,
U32 team, const JetManager::ID& jetCaps)
{
S32 island0 = hookTransient(dstNode);
S32 island1 = hookTransient(srcNode);
if (island0 == island1 && island0 >= 0)
{
// Islands Ok- check partitions-
S32 srcInd = srcNode.mFirstHook->getIndex();
S32 dstInd = dstNode.mFirstHook->getIndex();
GraphPartition::Answer answer;
jetCaps;
// First check armor partition- it never gives ambiguous answers (since they are
// computed in full when a new energy/armor configuration occurs).
#if _GRAPH_PART_
answer = mJetManager.reachable(jetCaps, srcInd, dstInd);
if (answer == GraphPartition::CanReach)
#endif
{
// Then check force fields. Answer can be ambiguous (in which case the search
// will try, and add to the partition list if it fails).
answer = mForceFields.reachable(team, srcInd, dstInd);
if (answer == GraphPartition::CanReach || answer == GraphPartition::Ambiguous)
return true;
}
}
return false;
}
void NavigationGraph::popTransientPair(TransientNode& srcNode, TransientNode& dstNode)
{
unhookTransient(srcNode);
unhookTransient(dstNode);
}
//-------------------------------------------------------------------------------------
bool NavigationGraph::canReachLoc(const FindGraphNode& src, const FindGraphNode& dst,
U32 team, const JetManager::ID& jetCaps)
{
GraphNode * srcNode = src.closest();
GraphNode * dstNode = dst.closest();
if (srcNode && dstNode) {
if (srcNode->island() == dstNode->island()) {
// Islands Ok- check partitions-
S32 srcInd = srcNode->getIndex();
S32 dstInd = dstNode->getIndex();
GraphPartition::Answer ans;
// First check armor partition- it never gives ambiguous answers-
#if _GRAPH_PART_
ans = mJetManager.reachable(jetCaps, srcInd, dstInd);
if (ans == GraphPartition::CanReach)
#endif
{
// Then check force fields. Answer can be ambiguous-
ans = mForceFields.reachable(team, srcInd, dstInd);
if (ans == GraphPartition::CanReach || ans == GraphPartition::Ambiguous)
return true;
}
}
}
return false;
}
//-------------------------------------------------------------------------------------
// A path searcher goes through this interface to get a handle to a transient node.
GraphHookRequest::GraphHookRequest()
{
mIncarnation = -1;
mPairLookup = -1;
}
GraphHookRequest::~GraphHookRequest()
{
if (NavigationGraph::gotOneWeCanUse())
if (gNavGraph->incarnation() == mIncarnation)
gNavGraph->destroyPairOfHooks(mPairLookup);
}
// Called on mission cycle - basically a re-construct.
void GraphHookRequest::reset()
{
mIncarnation = -1;
mPairLookup = -1;
}
// There CAN be GraphHookRequest objects constructed without there being a graph, but NOT
// actual requests for nodes. This is why we don't assert above, but do in the following.
TransientNode & GraphHookRequest::getHook(S32 firstOrSecond)
{
AssertFatal(NavigationGraph::gotOneWeCanUse(), "Called GraphHookRequest w/o graph");
if(mIncarnation != gNavGraph->incarnation()) {
mPairLookup = gNavGraph->createPairOfHooks();
mIncarnation = gNavGraph->incarnation();
}
GraphNode * theNode = gNavGraph->lookupNode(mPairLookup + firstOrSecond);
AssertFatal(mPairLookup >= 0 && theNode != NULL, "Couldn not alloc transient node");
AssertFatal(theNode->transient(), "Node must be transient!");
TransientNode * t = static_cast<TransientNode *>(theNode);
return *t;
}
bool GraphHookRequest::iGrowOld() const
{
AssertFatal( NavigationGraph::gotOneWeCanUse(),
"I hear the mermaids singing, each to each... "
"I do not think they sing to me... " );
return gNavGraph->incarnation() != mIncarnation;
}

49
ai/graphTransient.h Normal file
View file

@ -0,0 +1,49 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _GRAPHTRANSIENT_H_
#define _GRAPHTRANSIENT_H_
class TransientNode : public RegularNode
{
friend class NavigationGraph;
GraphEdgeList mSearchEdges;
S32 mGroundStart;
const GraphNode * mClosest, * mSaveClosest;
const GraphNode * mFirstHook;
public:
TransientNode(S32 index);
GraphEdgeArray getEdges(GraphEdge*) const;
GraphEdgeArray getHookedEdges() const;
void informPushed();
void makeEdgeList(const Point3F& loc, const GraphNodeList& list);
void connectOutdoor(GraphNode* outdoorNode, bool all=true);
void setLoc(const Point3F& loc) {mLoc = loc;}
void setEdges(const GraphEdgeList& edges) {mEdges=edges;}
void setClosest(const GraphNode* closest) {mClosest=closest;}
Point3F getRenderPos() const;
S32 volumeIndex() const;
};
class GraphHookRequest
{
private:
TransientNode& getHook(S32 N);
S32 mIncarnation;
S32 mPairLookup;
public:
GraphHookRequest();
~GraphHookRequest();
TransientNode& getHook1() { return getHook(0); }
TransientNode& getHook2() { return getHook(1); }
bool iGrowOld() const;
void reset();
};
#endif

387
ai/graphVolume.cc Normal file
View file

@ -0,0 +1,387 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "ai/graph.h"
#define PlaneAdjErr 0.014f
#define MapIndoorIndex(i) ((i)-mNumOutdoor)
//-------------------------------------------------------------------------------------
// General query for volume info - package up everything user might want, including
// list of corners. Bool parameter tells if ceiling corners should be fetched too.
const GraphVolume& NavigationGraph::fetchVolume(const GraphNode* node, bool above)
{
S32 Index = MapIndoorIndex(node->getIndex());
mTempVolumeBuf.mFloor = mNodeVolumes.floorPlane(Index);
mTempVolumeBuf.mCeiling = mNodeVolumes.abovePlane(Index);
mTempVolumeBuf.mPlanes = mNodeVolumes.planeArray(Index);
mTempVolumeBuf.mCount = mNodeVolumes.planeCount(Index);
mNodeVolumes.getCorners(Index, mTempVolumeBuf.mCorners, !above);
return mTempVolumeBuf;
}
//-------------------------------------------------------------------------------------
static const NodeProximity sFarProximity;
// Points inside the volume return a negative distance from planes, so we take the
// maximum of these, and then "good" containment means that the maximum of these
// numbers is still pretty small. The check with floor is either/or though. We start
// with ceiling distance as the beginning metric.
NodeProximity NavigationGraph::getContainment(S32 indoorIndex, const Point3F& point)
{
NodeProximity metric;
PlaneF floor = mNodeVolumes.floorPlane(indoorIndex = MapIndoorIndex(indoorIndex));
// Below floor -> not a candidate, return low containment (far)
metric.mHeight = floor.distToPlane(point);
if (metric.mHeight > PlaneAdjErr)
return sFarProximity;
F32 ceilingMetric = mNodeVolumes.abovePlane(indoorIndex).distToPlane(point);
metric.mAboveC = ceilingMetric;
if (ceilingMetric > 0.0) {
// Tricky number here - probably don't want to make huge because it's sometimes
// important that we're near the top of volumes - other times not. Really
// need more info about the volume... This fixes problem in Beggar's Run.
ceilingMetric = getMax(ceilingMetric, 7.0f);
}
// Find the closest wall, use ceiling as start (if we're below, else larger #).
metric.mLateral = ceilingMetric;
const PlaneF * planes = mNodeVolumes.planeArray(indoorIndex);
S32 numWalls = mNodeVolumes.planeCount(indoorIndex) - 2;
while (--numWalls >= 0)
{
F32 D = (planes++)->distToPlane(point);
if (D > metric.mLateral)
metric.mLateral = D;
}
return metric;
}
//-------------------------------------------------------------------------------------
// Find closest point and store result in soln if it exists.
bool NavigationGraph::closestPointOnVol(GraphNode * node, const Point3F& point, Point3F& soln) const
{
S32 indoorIndex = MapIndoorIndex(node->getIndex());
return mNodeVolumes.closestPoint(indoorIndex, point, soln);
}
bool GraphVolumeList::closestPoint(S32 ind, const Point3F& point, Point3F& soln) const
{
Vector<Point3F> corners;
bool foundIt = false;
PlaneF floor = floorPlane(ind);
if (intersectWalls(ind, floor, corners))
{
S32 numCorners = corners.size();
F32 minDist = 1e13;
corners.push_back(corners[0]);
for (S32 i = 0; i < numCorners; i++)
{
LineSegment segment(corners[i], corners[i+1]);
F32 D = segment.distance(point);
if (D < minDist)
{
foundIt = true;
minDist = D;
soln = segment.solution();
soln.z = point.z;
}
}
}
return foundIt;
}
//-------------------------------------------------------------------------------------
PlaneF NavigationGraph::getFloorPlane(GraphNode * node)
{
return mNodeVolumes.floorPlane(MapIndoorIndex(node->getIndex()));
}
//-------------------------------------------------------------------------------------
// Are we between floor and ceiling of this node?
bool NavigationGraph::verticallyInside(S32 indoorIndex, const Point3F& point)
{
PlaneF floor = mNodeVolumes.floorPlane(indoorIndex = MapIndoorIndex(indoorIndex));
if (floor.distToPlane(point) <= PlaneAdjErr)
if (mNodeVolumes.abovePlane(indoorIndex).distToPlane(point) <= PlaneAdjErr)
return true;
return false;
}
F32 NavigationGraph::heightAboveFloor(S32 indoorIndex, const Point3F& point)
{
PlaneF floor = mNodeVolumes.floorPlane(MapIndoorIndex(indoorIndex));
return -(floor.distToPlane(point) + PlaneAdjErr);
}
//-------------------------------------------------------------------------------------
bool GraphVolumeList::intersectWalls(S32 i, const PlaneF& with, Vector<Point3F>& list) const
{
bool noneWereParallel = true;
const PlaneF* planes = planeArray(i);
S32 N = planeCount(i) - 2;
for (S32 w = 0; w < N; w++) {
Point3F point;
if (intersectPlanes(with, planes[w], planes[(w+1)%N], &point))
list.push_back(point);
else {
// Parallel planes occasionally happen - since intersection points are
// not vital to system (just used to find bounding box) - we just
// continue, but return false;
//
noneWereParallel = false;
}
}
return noneWereParallel;
}
//-------------------------------------------------------------------------------------
// Get intersections of the volume.
S32 GraphVolumeList::getCorners(S32 ind, Vector<Point3F>& points, bool justFloor) const
{
points.clear();
PlaneF floor = floorPlane(ind);
intersectWalls(ind, floor, points);
if (!justFloor)
{
PlaneF ceiling = abovePlane(ind);
intersectWalls(ind, ceiling, points);
}
return points.size();
}
//-------------------------------------------------------------------------------------
// Get minimum extent of the volume. Want minimum of max distance taken from
// each wall plane.
// The point buffer is just passed in to avoid vector reallocs since this method is
// called a lot at startup, and Point3F vecs are showing up on memory profiles.
F32 GraphVolumeList::getMinExt(S32 ind, Vector<Point3F>& ptBuffer)
{
ptBuffer.clear();
if (intersectWalls(ind, floorPlane(ind), ptBuffer))
{
const PlaneF* planes = planeArray(ind);
S32 N = planeCount(ind) - 2;
F32 minD = 1e9;
for (S32 w = 0; w < N; w++)
{
F32 maxD = -10, d;
for (S32 p = 0; p < ptBuffer.size(); p++)
if ((d = (-planes[w].distToPlane(ptBuffer[p]))) > maxD)
maxD = d;
if (maxD < minD)
minD = maxD;
}
return minD;
}
else {
return 0.5;
}
}
//-------------------------------------------------------------------------------------
// Push a box of given height and width at P.
void GraphVolumeList::addVolume(Point3F pos, F32 boxw, F32 boxh)
{
GraphVolInfo entry;
entry.mPlaneCount = 6;
entry.mPlaneIndex = mPlanes.size();
static const VectorF walls[4]=
{VectorF(-1,0,0), VectorF(0,1,0), VectorF(1,0,0), VectorF(0,-1,0)};
// Add the walls-
for (S32 i = 0; i < 4; i++)
{
Point3F point = pos + walls[i] * boxw;
PlaneF wall(point, walls[i]);
mPlanes.push_back(wall);
}
PlaneF floor(pos, VectorF(0, 0, -1));
mPlanes.push_back(floor);
pos.z += boxh;
PlaneF ceiling(pos, VectorF(0, 0, 1));
mPlanes.push_back(ceiling);
push_back(entry);
}
//-------------------------------------------------------------------------------------
// When islands are culled, lists are compacted, and this service is needed. It's
// called after the GraphVolInfo list is culled down.
void GraphVolumeList::cullUnusedPlanes()
{
// Copy down the planes and remap the indices into the plane list.
Vector<PlaneF> keepers(mPlanes.size());
for (iterator it = begin(); it != end(); it++)
{
S32 newIndex = keepers.size();
for (S32 p = 0; p < it->mPlaneCount; p++)
keepers.push_back(mPlanes[it->mPlaneIndex + p]);
it->mPlaneIndex = newIndex;
}
// Replace the plane list with the keepers-
mPlanes = keepers;
}
//-------------------------------------------------------------------------------------
// Ideally always go through this for remapping to the indoor list:
S32 NavigationGraph::indoorIndex(const GraphNode* node)
{
return MapIndoorIndex(node->getIndex());
}
//-------------------------------------------------------------------------------------
bool NavigationGraph::inNodeVolume(const GraphNode* node, const Point3F& point)
{
S32 nodeIndex = MapIndoorIndex(node->volumeIndex());
AssertFatal(validArrayIndex(nodeIndex, mNodeVolumes.size()), "nonmatching volume");
S32 numWalls = mNodeVolumes.planeCount(nodeIndex) - 2;
const PlaneF* planes = mNodeVolumes.planeArray(nodeIndex);
while (numWalls--)
// if (planes[numWalls].whichSide(point) != PlaneF::Back)
if (planes[numWalls].distToPlane(point) > PlaneAdjErr)
return false;
return true;
}
//-------------------------------------------------------------------------------------
void NavigationGraph::drawNodeInfo(GraphNode * node)
{
if (mHaveVolumes) {
clearRenderBoxes();
Vector<Point3F> corners;
mNodeVolumes.getCorners(indoorIndex(node), corners, false);
for (S32 c = 0; c < corners.size(); c++)
pushRenderBox(corners[c], 0);
}
}
// This is a debugging method- see how well we match nodes and such.
const char * NavigationGraph::drawNodeInfo(const Point3F& loc)
{
const char * info = NULL;
if (GraphNode * node = closestNode(loc)) {
if (node->indoor()) {
// Put boxes around the node
drawNodeInfo(node);
info = "Found indoor node";
}
else
info = "Found outdoor node";
}
else
info = "Couldn't find a closest node to this location";
return info;
}
//-------------------------------------------------------------------------------------
static const U32 sMask = InteriorObjectType|StaticShapeObjectType|StaticObjectType;
static bool intersectsAnything(const Box3F& bounds, ClippedPolyList& polyList)
{
if (gServerContainer.buildPolyList(bounds, sMask, &polyList))
return (polyList.mPolyList.size() != 0);
return false;
}
//-------------------------------------------------------------------------------------
#define JustUnshrinkIt (NodeVolumeShrink + 0.002)
#define ExtrudeALittle (NodeVolumeShrink + 0.22)
//
// We want to slightly oversize node volumes which can. This accomplishes two things:
// 1. The containment metrics are better. Example is when you're staning on a ledge
// but the node from below (which is along a wall) matches you better.
// 2. It should help with the indoor lookahead smoothing. It makes sure we are
// always inside nodes.
//
// This ALSO moves the volumes out by the amount that Greg's system pulls it in.
// (We do this here because of the extra checks which want them still shrunk).
//
void GraphVolumeList::nudgeVolumesOut()
{
Point3F outSize(1.0, 1.0, 1.0);
Vector<F32> nudgeAmounts;
Vector<Point3F> corners;
for (S32 i = 0; i < size(); i++)
{
// Calculate a bounding box of the node volume-
getCorners(i, corners, false);
Box3F bounds(Point3F(1e9, 1e9, 1e9), Point3F(-1e9, -1e9, -1e9), true);
for (Vector<Point3F>::iterator c = corners.begin(); c != corners.end(); c++)
bounds.min.setMin(*c), bounds.max.setMax(*c);
// Perform our extrusion checks on all the walls (_separately_, hence bool array)
S32 numPlanes = planeCount(i);
PlaneF* planeList = getPlaneList(i);
nudgeAmounts.setSize(numPlanes);
bounds.min -= outSize;
bounds.max += outSize;
for (S32 n = 0; n < numPlanes; n++)
{
if (n == numPlanes - 2) {
// Floor- don't extrude this one
nudgeAmounts[n] = JustUnshrinkIt;
}
else {
// Build poly list extruded in one direction-
ClippedPolyList polyList;
polyList.mPlaneList.clear();
polyList.mNormal.set(0, 0, 0);
for (S32 p = 0; p < numPlanes; p++) {
PlaneF plane = planeList[p];
if (p == n)
plane.d -= ExtrudeALittle;
polyList.mPlaneList.push_back(plane);
}
// See if we nudged without hitting anything.
if (intersectsAnything(bounds, polyList))
nudgeAmounts[n] = JustUnshrinkIt;
else
nudgeAmounts[n] = ExtrudeALittle;
}
}
// Now move the planes
for (S32 j = 0; j < numPlanes; j++)
planeList[j].d -= nudgeAmounts[j];
}
}

195
ai/oVector.h Normal file
View file

@ -0,0 +1,195 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _OVECTOR_H_
#define _OVECTOR_H_
#ifndef _PLATFORM_H_
#include "Platform/platform.h"
#endif
#ifdef DEBUG_GUARD
extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize,
const char* fileName,
const U32 lineNum);
#else
extern bool VectorResize(U32 *aSize, U32 *aCount, void **arrayPtr, U32 newCount, U32 elemSize);
#endif
template<class T> class OVector
{
protected:
U16 mElementCount;
U16 mArraySize;
T * mArray;
// Could probably use the low bit as flag and double our max size here...
U16 userOwned() const {return (mArraySize & 0x8000);}
U16 arraySize() const {return (mArraySize & 0x7fff);}
bool resize(U32 ecount) {
bool Ok = true;
AssertFatal(ecount < (1<<15), "OVector: 32K maximum exceeded");
if (!userOwned()) {
U32 size = arraySize(); // Want to use existing VectorResize(),
U32 count = mElementCount; // so convert to U32s for it...
#ifdef DEBUG_GUARD
Ok = VectorResize(&size, &count, (void**) &mArray, ecount, sizeof(T), __FILE__, __LINE__);
#else
Ok = VectorResize(&size, &count, (void**) &mArray, ecount, sizeof(T));
#endif
mArraySize = size | userOwned();
mElementCount = count;
}
else {
AssertISV(ecount <= arraySize(), "OVector: overgrown owned vector");
mElementCount = ecount;
}
return Ok;
}
public:
OVector() {
mArray = NULL;
mElementCount = mArraySize = 0;
}
~OVector() {
if (!userOwned())
dFree(mArray);
}
typedef T* iterator;
typedef const T* const_iterator;
// One-liners-
iterator begin() {return mArray;}
iterator end() {return mArray + mElementCount;}
T& front() {return * begin();}
T& back() {return * end();}
T& first() {return mArray[0];}
T& last() {return mArray[mElementCount - 1];}
T& operator[](S32 i) {return operator[](U32(i));}
T& operator[](U32 i) {return mArray[i];}
const T& first() const {return mArray[0];}
const T& last() const {return mArray[mElementCount - 1];}
const_iterator begin() const {return mArray;}
const_iterator end() const {return mArray + mElementCount;}
const T& front() const {return * begin();}
const T& back() const {return * end();}
const T& operator[](U32 i) const {return mArray[i];}
const T& operator[](S32 i) const {return operator[](U32(i));}
void clear() {mElementCount = 0;}
void compact() {resize(mElementCount);}
bool empty() const {return (mElementCount == 0);}
bool isOwned() const {return (userOwned() != 0);}
S32 memSize() const {return capacity() * sizeof(T);}
U32 capacity() const {return arraySize();}
T * address() const {return mArray;}
S32 size() const {return S32(mElementCount);}
// This is where user sets their own data. We then allow all other operations to
// go on as normal - errors will be caught in resize(), which everything uses.
void setOwned(T * data, U16 available, bool setSize = false) {
if (!userOwned())
dFree(mArray);
AssertFatal(available < (1<<15), "OVector: can only hold 32K");
mElementCount = (setSize ? available : 0);
mArraySize = (available | 0x8000);
mArray = data;
}
void clearOwned() {
if (userOwned()) {
mElementCount = 0;
mArraySize = 0;
mArray = 0;
}
}
S32 setSize(U32 size) {
if (size > arraySize())
resize(size);
else
mElementCount = size;
return mElementCount;
}
void increment(U32 delta=1) {
if ((mElementCount += delta) > arraySize())
resize(mElementCount);
}
void decrement(U32 delta = 1) {
if (mElementCount > delta)
mElementCount -= delta;
else
mElementCount = 0;
}
void insert(U32 i) {
increment();
dMemmove(&mArray[i + 1], &mArray[i], (mElementCount - i - 1) * sizeof(T));
}
void erase(U32 i) {
dMemmove(&mArray[i], &mArray[i + 1], (mElementCount - i - 1) * sizeof(T));
decrement();
}
void erase_fast(U32 i) { // CAUTION: this does not maintain list order
if (i < (mElementCount - 1)) // Copies the last element into the deleted hole
dMemmove(&mArray[i], &mArray[mElementCount - 1], sizeof(T));
decrement();
}
void erase_fast(iterator q) {
erase_fast(U32(q - mArray));
}
void push_back(const T& x) {
increment();
mArray[mElementCount - 1] = x;
}
void push_front(const T & x) {
insert(0);
mArray[0] = x;
}
void pop_front() {
erase(U32(0));
}
void pop_back() {
decrement();
}
void reserve(U32 size) {
if (size > arraySize()) {
S32 ec = S32(mElementCount);
if (resize(size))
mElementCount = U32(ec);
}
}
void operator=(const OVector& p) {
resize(p.mElementCount);
if (p.mElementCount)
dMemcpy(mArray,p.mArray,mElementCount * sizeof(T));
}
void merge(const OVector& p) {
if (p.size()) {
S32 oldsize = size();
resize(oldsize + p.size());
dMemcpy( &mArray[oldsize], p.address(), p.size() * sizeof(T) );
}
}
};
#endif //_OVECTOR_H_

415
ai/tBinHeap.h Normal file
View file

@ -0,0 +1,415 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
/*
BinHeap.h
jan 17, 2000
*/
#ifndef BINHEAP_H
#define BINHEAP_H
#ifndef _TVECTOR_H_
#include "Core/tVector.h"
#endif
#define BinHeapInline inline
#define BinHeapParent(child) (((child) - 1) >> 1)
#define BinHeapRight(parent) (((parent) + 1) << 1)
#define BinHeapLeft(parent) (((parent) << 1) + 1)
// Much perfomed operation for shifting in the heap
#define BinHeapMove(src, dst) (mBack[ mHeap[dst] = mHeap[src] ] = dst)
//
// BinaryHeap Class
//
template <class T>
class BinHeap
{
protected:
T * mPool;
S16 * mHeap;
S16 * mBack;
Vector<T> mPoolVec;
Vector<S16> mHeapVec;
Vector<S16> mBackVec;
bool mIsHeapified;
S32 mHeapCount;
protected:
void setPointers();
void keyChange(S32 heapIndex);
void keyImprove(S32 heapIndex);
void shiftDown(S32 parent, S32 child);
void shiftUp(S32 parent, S32 child);
public:
BinHeap();
~BinHeap();
void changeKey(S32 indexInArray);
void improveKey(S32 indexInArray);
void clear();
void removeHead();
void insert(const T &elem);
T *head();
S32 headIndex();
S32 count();
S32 size();
void buildHeap();
void heapify(S32 heapIndex);
T &operator[](U32 index);
void reserve(S32 amount);
bool validateBack();
bool validateHeap();
};
// inlines
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline BinHeap<T>::BinHeap()
{
mHeapCount = 0;
mIsHeapified = false;
setPointers();
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline S32 BinHeap<T>::count()
{
return mHeapCount;
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline S32 BinHeap<T>::size()
{
return mPoolVec.size();
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline S32 BinHeap<T>::headIndex()
{
return (mHeapCount > 0 ? mHeap[0] : -1);
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline T * BinHeap<T>::head()
{
if(mHeapCount > 0)
return & mPool[mHeap[0]] ;
else
return NULL;
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline void BinHeap<T>::setPointers()
{
mPool = mPoolVec.address();
mHeap = mHeapVec.address();
mBack = mBackVec.address();
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline void BinHeap<T>::shiftDown(S32 parent, S32 child)
{
mHeap[child] = mHeap[parent];
mBack[mHeap[child]] = child;
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline void BinHeap<T>::shiftUp(S32 parent, S32 child)
{
mHeap[parent] = mHeap[child];
mBack[mHeap[parent]] = parent;
}
// implementation
//--------------------------------------------------------------------------------
template <class T>
BinHeap<T>::~BinHeap()
{
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline void BinHeap<T>::changeKey(S32 indexInArray)
{
S32 indexInHeap = mBack[indexInArray];
keyChange(indexInHeap);
}
//--------------------------------------------------------------------------------
template <class T>
BinHeapInline void BinHeap<T>::improveKey(S32 index)
{
keyImprove(mBack[index]);
}
//--------------------------------------------------------------------------------
template <class T>
void BinHeap<T>::keyChange(S32 heapIndex)
{
S32 i = heapIndex;
S32 tempHeap2Vec = mHeap[heapIndex];
mIsHeapified = false;
while(i > 0)
{
if(mPool[mHeap[BinHeapParent(i)]] < mPool[tempHeap2Vec])
{
mIsHeapified = true;
shiftDown(BinHeapParent(i), i);
i = BinHeapParent(i);
}
else
break;
}
mHeap[i] = tempHeap2Vec;
mBack[mHeap[i]] = i;
if(!mIsHeapified)
heapify(heapIndex);
}
//--------------------------------------------------------------------------------
// This version of keyChange() is for values known only to improve - and thus move
// towards head of queue. Dijkstra() knows this, so we remove many wasted calls
// to heapify() for case where the key didn't percolate up.
template <class T>
void BinHeap<T>::keyImprove(S32 heapIndex)
{
S32 i = heapIndex;
S32 tempHeap2Vec = mHeap[heapIndex];
while(i > 0)
{
S32 parent = BinHeapParent(i);
if(mPool[mHeap[parent]] < mPool[tempHeap2Vec])
{
// shiftDown(parent, i);
BinHeapMove(parent, i);
i = parent;
}
else
{
// BinHeapMove(tempHeap2Vec, i);
mHeap[i] = tempHeap2Vec;
mBack[mHeap[i]] = i;
return;
}
}
}
//--------------------------------------------------------------------------------
template <class T>
void BinHeap<T>::clear()
{
mPoolVec.clear();
mHeapVec.clear();
mBackVec.clear();
setPointers();
mIsHeapified = false;
mHeapCount = 0;
}
//--------------------------------------------------------------------------------
template <class T>
void BinHeap<T>::insert(const T &elem)
{
S32 indexInArray = mPoolVec.size();
mPoolVec.push_back(elem);
mHeapVec.increment(1);
mBackVec.push_back(mHeapCount);
setPointers();
mHeap[mHeapCount++] = indexInArray;
if(mIsHeapified)
{
register S32 i = mHeapCount - 1;
register S32 tempHeap2Vec = mHeap[i];
while(i > 0)
{
if(mPool[mHeap[BinHeapParent(i)]] < elem)
{
shiftDown(BinHeapParent(i), i);
i = BinHeapParent(i);
}
else
break;
}
mHeap[i] = tempHeap2Vec;
mBack[tempHeap2Vec] = i;
}
}
//---------------------------------------------------------------------------------
template <class T>
void BinHeap<T>::heapify(S32 parent)
{
S32 l, r;
S32 largest = parent;
S32 tempHeap2Vec = mHeap[parent];
while(1)
{
if( (l = BinHeapLeft(parent)) < mHeapCount) // only carry further if left exists.
{
if(mPool[tempHeap2Vec] < mPool[mHeap[l]])
largest = l;
if( (r = BinHeapRight(parent)) < mHeapCount ) // don't do below work if no right
{
if(largest == parent && mHeap[parent] != tempHeap2Vec)
{
if( mPool[tempHeap2Vec] < mPool[mHeap[r]] )
largest = r;
}
else
{
if( mPool[mHeap[largest]] < mPool[mHeap[r]] )
largest = r;
}
}
}
if(largest != parent)
{
shiftUp(parent, largest);
parent = largest;
}
else
{
mHeap[parent] = tempHeap2Vec;
mBack[tempHeap2Vec] = parent;
break;
}
}
mIsHeapified = true;
}
//--------------------------------------------------------------------------------
template <class T>
bool BinHeap<T>::validateBack()
{
bool valid = true;
for(S32 i = 0; i < mHeapCount; i++)
{
if(mBack[i] == -1)
continue;
if(mHeap[mBack[i]] != i || mBack[mHeap[i]] != i)
valid = false;
}
return valid;
}
//--------------------------------------------------------------------------------
template <class T> void BinHeap<T>::reserve(S32 amount)
{
mPoolVec.reserve(amount);
mHeapVec.reserve(amount);
mBackVec.reserve(amount);
}
//--------------------------------------------------------------------------------
template <class T>
bool BinHeap<T>::validateHeap()
{
if(!mIsHeapified)
buildHeap();
bool valid = true;
S32 parents = (mHeapCount-1) >> 1;
for(S32 i = parents; i >= 0; i--)
{
S32 l = BinHeapLeft(i);
S32 r = BinHeapRight(i);
if(l < mHeapCount && mPool[mHeap[i]] < mPool[mHeap[l]])
{
printf("error: (%d < l)parent with lower key than child!\n", i);
valid = false;
}
if(r < mHeapCount && mPool[mHeap[i]] < mPool[mHeap[r]])
{
printf("Error: (%d < r)parent with lower key than child!\n", i);
valid = false;
}
}
return valid;
}
//---------------------------------------------------------------------------------
template <class T>
void BinHeap<T>::buildHeap()
{
mIsHeapified = true;
for(S32 j = (mHeapCount >> 1) - 1; j >= 0; j--)
heapify(j);
}
//---------------------------------------------------------------------------------
template <class T>
void BinHeap<T>::removeHead()
{
if(mHeapCount < 1)
return;
if(!mIsHeapified)
buildHeap();
mBack[mHeap[0]] = -1;
mBack[mHeap[0]=mHeap[--mHeapCount]] = 0;
if(mHeapCount)
heapify(0);
}
//-----------------------------------------------------------------------------------
template <class T>
BinHeapInline T & BinHeap<T>::operator[](U32 index)
{
return mPool[index];
}
//-----------------------------------------------------------------------------------
#endif

23
ai/texturePreload.h Normal file
View file

@ -0,0 +1,23 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _TEXTUREPRELOAD_H_
#define _TEXTUREPRELOAD_H_
class PreloadTextures
{
enum {MaxHandles = 512};
TextureHandle mTextures[MaxHandles];
S32 mNext;
public:
PreloadTextures();
~PreloadTextures();
void load(const char * name, bool clamp);
};
#endif

3372
audio/audio.cc Normal file

File diff suppressed because it is too large Load diff

21
audio/audio.h Normal file
View file

@ -0,0 +1,21 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIO_H_
#define _AUDIO_H_
#ifndef _PLATFORMAL_H_
#include "PlatformWin32/platformAL.h"
#endif
#ifndef _PLATFORMAUDIO_H_
#include "Platform/platformAudio.h"
#endif
#ifndef _AUDIODATABLOCK_H_
#include "audio/audioDataBlock.h"
#endif
#endif // _H_AUDIO_

119
audio/audioBuffer.cc Normal file
View file

@ -0,0 +1,119 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioBuffer.h"
#include "Core/stream.h"
#include "console/console.h"
#include "Core/fileStream.h"
#include "audio/audioThread.h"
//--------------------------------------
AudioBuffer::AudioBuffer(StringTableEntry filename)
{
AssertFatal(StringTable->lookup(filename), "AudioBuffer:: filename is not a string table entry");
mFilename = filename;
mLoading = false;
malBuffer = AL_INVALID;
}
AudioBuffer::~AudioBuffer()
{
if( malBuffer != AL_INVALID ) {
alDeleteBuffers( 1, &malBuffer );
}
}
//--------------------------------------
Resource<AudioBuffer> AudioBuffer::find(const char *filename)
{
Resource<AudioBuffer> buffer = ResourceManager->load(filename);
if (bool(buffer) == false)
{
char buf[512];
dSprintf(buf, sizeof(buf), "audio/%s", filename);
// see if the file exists
if (ResourceManager->getPathOf(buf))
{
AudioBuffer *temp = new AudioBuffer(StringTable->insert(filename));
ResourceManager->add(filename, temp);
buffer = ResourceManager->load(filename);
}
}
return buffer;
}
ResourceInstance* AudioBuffer::construct(Stream &)
{
return NULL;
}
//-----------------------------------------------------------------
ALuint AudioBuffer::getALBuffer(bool block)
{
// jff: block until thread is completely stable (resourcemanager is not
// thread safe yet (though it never seems to crash there...) )
block = true;
// sol: if the above "block = true" is removed, uncomment the following
// error so that the Linux build maintainer knows to re-enable the audio
// thread. It's disabled at the moment as an optimization.
//#ifdef __linux
//#error Linux version needs to re-enable audio thread in platformLinux/audio.cc
//#endif
// clear the error state
alGetError();
if (alIsBuffer(malBuffer))
return malBuffer;
alGenBuffers(1, &malBuffer);
if(alGetError() != AL_NO_ERROR)
return(AL_INVALID);
char buffer[512];
dSprintf(buffer, sizeof(buffer), "audio/%s", mFilename);
ResourceObject * obj = ResourceManager->find(buffer);
if(obj)
{
if(block)
{
bool readWavSuccess = readWAV(obj);
if(readWavSuccess)
return(malBuffer);
}
else if(gAudioThread)
{
gAudioThread->loadResource(obj, this);
return(malBuffer);
}
}
alDeleteBuffers(1, &malBuffer);
return(AL_INVALID);
}
bool AudioBuffer::readWAV(ResourceObject *obj)
{
if(Audio::doesSupportDynamix())
{
U32 size = obj->fileSize;
Stream *str = ResourceManager->openStream(obj);
void * data = dMalloc(obj->fileSize);
str->read(obj->fileSize, data);
ResourceManager->closeStream(str);
if(alBufferSyncData_EXT(malBuffer, AL_FORMAT_WAVE_EXT, data, obj->fileSize, 0))
return(true);
dFree(data);
}
return(false);
}

47
audio/audioBuffer.h Normal file
View file

@ -0,0 +1,47 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOBUFFER_H_
#define _AUDIOBUFFER_H_
#ifndef _PLATFORM_H_
#include "Platform/platform.h"
#endif
#ifndef _PLATFORMAL_H_
#include "PlatformWin32/platformAL.h"
#endif
#ifndef _RESMANAGER_H_
#include "Core/resManager.h"
#endif
//--------------------------------------------------------------------------
class AudioBuffer: public ResourceInstance
{
friend class AudioThread;
private:
StringTableEntry mFilename;
bool mLoading;
ALuint malBuffer;
bool readRIFFchunk(Stream &s, const char *seekLabel, U32 *size);
bool readWAV(ResourceObject *obj);
public:
AudioBuffer(StringTableEntry filename);
~AudioBuffer();
ALuint getALBuffer(bool block = false);
bool isLoading() {return(mLoading);}
static Resource<AudioBuffer> find(const char *filename);
static ResourceInstance* construct(Stream& stream);
};
#endif // _H_AUDIOBUFFER_

311
audio/audioCodec.cc Normal file
View file

@ -0,0 +1,311 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioCodec.h"
//#include "audio/audioCodecGSM.h"
#ifndef __linux
#include "audio/audioCodecMiles.h"
#endif
//-------------------------------------------------------------------------
// Class AudioCodecManager:
//-------------------------------------------------------------------------
AudioCodecManager::CodecInfo AudioCodecManager::smCodecTable[] =
{
#ifndef __linux // miles only exists on Win32!
{ AUDIO_CODEC_V12, MilesEncoderCodec::create, MilesDecoderCodec::create, 0, 0},
{ AUDIO_CODEC_V24, MilesEncoderCodec::create, MilesDecoderCodec::create, 0, 0},
{ AUDIO_CODEC_V29, MilesEncoderCodec::create, MilesDecoderCodec::create, 0, 0},
#else
{ AUDIO_CODEC_V12, 0, 0, 0, 0 },
{ AUDIO_CODEC_V24, 0, 0, 0, 0 },
{ AUDIO_CODEC_V29, 0, 0, 0, 0 },
#endif
// { AUDIO_CODEC_GSM, GSMEncoderCodec::create, GSMDecoderCodec::create, 0, 0 },
};
//-------------------------------------------------------------------------
void AudioCodecManager::destroy()
{
for(U32 i = 0; i < sizeof(smCodecTable) / sizeof(smCodecTable[0]); i++)
{
delete smCodecTable[i].mEncoder;
delete smCodecTable[i].mDecoder;
smCodecTable[i].mEncoder = 0;
smCodecTable[i].mDecoder = 0;
}
}
VoiceEncoderCodec * AudioCodecManager::createEncoderCodec(S32 codecId)
{
for(U32 i = 0; i < sizeof(smCodecTable) / sizeof(smCodecTable[0]); i++)
if((smCodecTable[i].mId == codecId) && smCodecTable[i].mCreateEncoder)
{
// already created?
if(smCodecTable[i].mEncoder)
return(smCodecTable[i].mEncoder);
VoiceCodec * codec = smCodecTable[i].mCreateEncoder(codecId);
if(!dynamic_cast<VoiceEncoderCodec*>(codec))
{
delete codec;
return(0);
}
else
{
smCodecTable[i].mEncoder = static_cast<VoiceEncoderCodec*>(codec);
return(smCodecTable[i].mEncoder);
}
}
return(0);
}
VoiceDecoderCodec * AudioCodecManager::createDecoderCodec(S32 codecId)
{
for(U32 i = 0; i < sizeof(smCodecTable) / sizeof(smCodecTable[0]); i++)
if((smCodecTable[i].mId == codecId) && smCodecTable[i].mCreateDecoder)
{
// already created?
if(smCodecTable[i].mDecoder)
return(smCodecTable[i].mDecoder);
VoiceCodec * codec = smCodecTable[i].mCreateDecoder(codecId);
if(!dynamic_cast<VoiceDecoderCodec*>(codec))
{
delete codec;
return(0);
}
else
{
smCodecTable[i].mDecoder = static_cast<VoiceDecoderCodec*>(codec);
return(smCodecTable[i].mDecoder);
}
}
return(0);
}
//-------------------------------------------------------------------------
// Class VoiceEncoderStream:
//-------------------------------------------------------------------------
VoiceEncoderStream::VoiceEncoderStream()
{
mEncoder = 0;
mEncoderId = AUDIO_CODEC_NONE;
mConnection = 0;
mStreamId = 0;
mSequence = 0;
mStream = 0;
mQueue.setSize(VOICE_CHANNELS * VOICE_FREQUENCY * (VOICE_BITS >> 3) * VOICE_LENGTH);
}
VoiceEncoderStream::~VoiceEncoderStream()
{
close();
}
//-------------------------------------------------------------------------
bool VoiceEncoderStream::setConnection(GameConnection * con)
{
mStreamId++;
mSequence = 0;
mConnection = con;
mQueue.clear();
return(true);
}
//-------------------------------------------------------------------------
bool VoiceEncoderStream::setCodec(S32 codecId)
{
close();
mEncoder = 0;
mEncoderId = AUDIO_CODEC_NONE;
if(codecId == AUDIO_CODEC_NONE)
return(true);
mEncoder = AudioCodecManager::createEncoderCodec(codecId);
if(mEncoder)
{
if(dynamic_cast<VoiceEncoderCodec*>(mEncoder))
{
mEncoderId = codecId;
return(true);
}
else
{
delete mEncoder;
mEncoder = 0;
}
}
return(false);
}
//-------------------------------------------------------------------------
bool VoiceEncoderStream::open()
{
close();
if(mEncoder)
mStream = mEncoder->openStream();
return(bool(mStream));
}
void VoiceEncoderStream::close()
{
if(mEncoder && mStream)
{
mEncoder->closeStream(mStream);
mStream = 0;
}
}
//-------------------------------------------------------------------------
bool VoiceEncoderStream::setBuffer(const U8 * data, U32 size)
{
AssertFatal(data, "VoiceEncoderStream::setBuffer: invalid buffer ptr");
if(size > mQueue.getFree())
return(false);
mQueue.enqueue(data, size);
return(true);
}
//-------------------------------------------------------------------------
void VoiceEncoderStream::process(bool flush)
{
if(!mEncoder || !mStream)
return;
while(flush || (mQueue.getUsed() >= 1800))
{
SimVoiceStreamEvent * mEvent = new SimVoiceStreamEvent(mStreamId, mSequence++, mEncoderId);
U32 amount = mEncoder->process(mStream, &mQueue, mEvent->getData(), SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE);
mEvent->setDataSize(amount);
mConnection->postNetEvent(mEvent);
if(flush && (amount < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE))
break;
}
}
void VoiceEncoderStream::flush()
{
process(true);
mQueue.clear();
}
//-------------------------------------------------------------------------
// Class VoiceEncoder:
//-------------------------------------------------------------------------
VoiceDecoderStream::VoiceDecoderStream()
{
mDecoder = 0;
mDecoderId = AUDIO_CODEC_NONE;
mInQueue.setSize(VOICE_CHANNELS * VOICE_FREQUENCY * (VOICE_BITS >> 3) * VOICE_LENGTH);
mOutQueue.setSize(VOICE_CHANNELS * VOICE_FREQUENCY * (VOICE_BITS >> 3) * VOICE_LENGTH);
mStream = 0;
}
VoiceDecoderStream::~VoiceDecoderStream()
{
close();
}
//-------------------------------------------------------------------------
bool VoiceDecoderStream::setCodec(S32 codecId)
{
close();
mDecoder = 0;
mDecoderId = AUDIO_CODEC_NONE;
if(codecId == AUDIO_CODEC_NONE)
return(true);
mDecoder = AudioCodecManager::createDecoderCodec(codecId);
if(mDecoder)
{
if(dynamic_cast<VoiceDecoderCodec*>(mDecoder))
{
mDecoderId = codecId;
return(true);
}
else
{
delete mDecoder;
mDecoder = 0;
}
}
return(false);
}
//-------------------------------------------------------------------------
bool VoiceDecoderStream::open()
{
if(!mDecoder)
return(false);
close();
if(mDecoder)
mStream = mDecoder->openStream();
return(bool(mStream));
}
void VoiceDecoderStream::close()
{
if(mDecoder && mStream)
{
mDecoder->closeStream(mStream);
mStream = 0;
}
}
//-------------------------------------------------------------------------
bool VoiceDecoderStream::setBuffer(U8 * data, U32 size)
{
AssertFatal(data, "VoiceDecoderStream::setBuffer: invalid data ptr");
if(size > mInQueue.getFree())
return(false);
mInQueue.enqueue(data, size);
return(true);
}
U32 VoiceDecoderStream::getBuffer(U8 ** data, U32 * size)
{
*data = mOutQueue.getHead();
*size = mOutQueue.getContiguousUsed();
mOutQueue.dequeue(*size);
return(*size);
}
void VoiceDecoderStream::process(bool flush)
{
if(!mDecoder || !mStream)
return;
while( flush || (mInQueue.getUsed() && !mOutQueue.isFull()) )
{
U32 amount = mDecoder->process(mStream, &mInQueue, mOutQueue.getTail(), mOutQueue.getContiguousFree());
mOutQueue.enqueue(amount);
if(flush && (amount == 0))
break;
}
}

147
audio/audioCodec.h Normal file
View file

@ -0,0 +1,147 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOCODEC_H_
#define _AUDIOCODEC_H_
#ifndef _AUDIONET_H_
#include "audio/audioNet.h"
#endif
#ifndef _GAMECONNECTION_H_
#include "game/gameConnection.h"
#endif
#ifndef _FILESTREAM_H_
#include "core/fileStream.h"
#endif
#ifndef _BUFFERQUEUE_H_
#include "audio/bufferQueue.h"
#endif
//--------------------------------------------------------------------------
#define VOICE_FREQUENCY 8000
#define VOICE_BITS 16
#define VOICE_CHANNELS 1
#define VOICE_LENGTH 5 // in seconds
enum {
AUDIO_CODEC_V12 = 0,
AUDIO_CODEC_V24,
AUDIO_CODEC_V29,
AUDIO_CODEC_GSM,
AUDIO_NUM_CODECS,
AUDIO_CODEC_NONE = -1
};
//--------------------------------------------------------------------------
// Class VoiceCodec:
//--------------------------------------------------------------------------
class VoiceCodec
{
public:
VoiceCodec() {};
virtual ~VoiceCodec() {};
virtual bool open() = 0;
virtual void close() = 0;
virtual void * openStream() = 0;
virtual void closeStream(void * stream) = 0;
virtual U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen) = 0;
};
class VoiceDecoderCodec : public VoiceCodec {};
class VoiceEncoderCodec : public VoiceCodec {};
typedef VoiceCodec * (*CODEC_CREATE_PROC)(U32 codecId);
//-------------------------------------------------------------------------
// Struct AudioCodecManager:
//-------------------------------------------------------------------------
struct AudioCodecManager
{
struct CodecInfo
{
S32 mId;
CODEC_CREATE_PROC mCreateEncoder;
CODEC_CREATE_PROC mCreateDecoder;
VoiceEncoderCodec * mEncoder;
VoiceDecoderCodec * mDecoder;
};
static CodecInfo smCodecTable[];
static VoiceEncoderCodec * createEncoderCodec(S32 codecId);
static VoiceDecoderCodec * createDecoderCodec(S32 codecId);
static void destroy();
};
//-------------------------------------------------------------------------
// Class VoiceEncoderStream:
//-------------------------------------------------------------------------
class VoiceEncoderStream
{
private:
BufferQueue mQueue;
VoiceEncoderCodec * mEncoder;
S32 mEncoderId;
GameConnection * mConnection;
U8 mStreamId;
U8 mSequence;
void * mStream;
public:
VoiceEncoderStream();
~VoiceEncoderStream();
S32 getCodec() { return(mEncoderId); }
bool setCodec(S32 codecId);
bool open();
void close();
bool setConnection(GameConnection *);
bool setBuffer(const U8 * data, U32 size);
void process(bool flush=false);
void flush();
};
//-------------------------------------------------------------------------
// Class VoiceDecoderStream:
//-------------------------------------------------------------------------
class VoiceDecoderStream
{
private:
VoiceDecoderCodec * mDecoder;
S32 mDecoderId;
BufferQueue mInQueue;
BufferQueue mOutQueue;
void * mStream;
public:
VoiceDecoderStream();
~VoiceDecoderStream();
S32 getCodec() { return(mDecoderId); }
bool setCodec(S32 codecId);
bool open();
void close();
bool setBuffer(U8 * data, U32 size);
U32 getBuffer(U8 ** data, U32 * size);
void process(bool flush=false);
};
#endif // _INC_AUDIOCODEC

195
audio/audioCodecGSM.cc Normal file
View file

@ -0,0 +1,195 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include <stdio.h>
extern "C"
{
#include "inc/gsm.h"
}
#include "audio/audioCodecGSM.h"
// The number of samples encoded at once in the GSM spec
#define GSM_FRAMESIZE 160
#define GSM_FRAMESIZE_BYTES GSM_FRAMESIZE*sizeof(gsm_signal)
//--------------------------------------------------------------------------
// Class GSMEncoderCodec:
//--------------------------------------------------------------------------
GSMEncoderCodec::GSMEncoderCodec()
{
}
GSMEncoderCodec::~GSMEncoderCodec()
{
close();
}
VoiceCodec * GSMEncoderCodec::create(U32 codecId)
{
if(codecId != AUDIO_CODEC_GSM)
return(0);
// create and open the codec
GSMEncoderCodec * codec = new GSMEncoderCodec();
if(!codec->open())
{
delete codec;
codec = 0;
}
return(codec);
}
//--------------------------------------------------------------------------
bool GSMEncoderCodec::open()
{
return true;
}
void GSMEncoderCodec::close()
{
}
//--------------------------------------------------------------------------
void * GSMEncoderCodec::openStream()
{
return gsm_create();
}
void GSMEncoderCodec::closeStream(void * stream)
{
if ( stream )
gsm_destroy((gsm)stream);
}
U32 GSMEncoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen)
{
gsm_frame frame;
gsm_signal gsmdata[ GSM_FRAMESIZE];
gsm_signal samples[2*GSM_FRAMESIZE];
U32 encoded;
U8 *output;
const U32 framesize = (sizeof frame);
// Sanity check
if ( !stream || !queue ) {
return 0;
}
output = const_cast<U8*>(data);
encoded = 0;
while ( maxLen >= framesize ) {
if ( queue->getUsed() < (sizeof samples) ) {
break;
}
queue->dequeue((U8*)samples, (sizeof samples));
// Convert the samples to 4000 Hz
for ( int i=0, j=0; i<GSM_FRAMESIZE; i += 1, j += 2 ) {
gsmdata[i] = ((S32)samples[j]+samples[j+1])/2;
}
gsm_encode((gsm)stream, gsmdata, frame);
dMemcpy(output, frame, framesize);
encoded += framesize;
output += framesize;
maxLen -= framesize;
}
return encoded;
}
//--------------------------------------------------------------------------
// Class GSMDecoderCodec:
//--------------------------------------------------------------------------
GSMDecoderCodec::GSMDecoderCodec()
{
}
GSMDecoderCodec::~GSMDecoderCodec()
{
close();
}
VoiceCodec * GSMDecoderCodec::create(U32 codecId)
{
if(codecId != AUDIO_CODEC_GSM)
return(0);
// create and open the codec
GSMDecoderCodec * codec = new GSMDecoderCodec();
if(!codec->open())
{
delete codec;
codec = 0;
}
return(codec);
}
//--------------------------------------------------------------------------
bool GSMDecoderCodec::open()
{
return(true);
}
void GSMDecoderCodec::close()
{
}
//--------------------------------------------------------------------------
void * GSMDecoderCodec::openStream()
{
return gsm_create();
}
void GSMDecoderCodec::closeStream(void * stream)
{
if ( stream )
gsm_destroy((gsm)stream);
}
U32 GSMDecoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen)
{
gsm_frame frame;
gsm_signal gsmdata[GSM_FRAMESIZE];
U32 decoded;
U8 *output;
const U32 framesize = 2*GSM_FRAMESIZE_BYTES;
// Sanity check
if ( !stream || !queue ) {
return 0;
}
output = const_cast<U8*>(data);
decoded = 0;
while ( maxLen >= framesize ) {
gsm_signal *samples = (gsm_signal *)output;
// Decode another frame of data
if ( queue->getUsed() < (sizeof frame) ) {
break;
}
queue->dequeue((U8*)frame, (sizeof frame));
gsm_decode((gsm)stream, frame, gsmdata);
// Convert the samples from 4000 Hz
for ( int i=0, j=0; i<GSM_FRAMESIZE; i += 1, j += 2 ) {
samples[j] = gsmdata[i];
if ( i == (GSM_FRAMESIZE-1) ) {
samples[j+1] = gsmdata[i];
} else {
samples[j+1] = ((S32)gsmdata[i]+gsmdata[i+1])/2;
}
}
decoded += framesize;
output += framesize;
maxLen -= framesize;
}
return decoded;
}

52
audio/audioCodecGSM.h Normal file
View file

@ -0,0 +1,52 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOCODECGSM_H_
#define _AUDIOCODECGSM_H_
#ifndef _AUDIOCODEC_H_
#include "audio/audioCodec.h"
#endif
class GSMEncoderCodec : public VoiceEncoderCodec
{
private:
GSMEncoderCodec();
~GSMEncoderCodec();
public:
static VoiceCodec * create(U32 codecId);
bool open();
void close();
void * openStream();
void closeStream(void * stream);
U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen);
};
class GSMDecoderCodec : public VoiceDecoderCodec
{
private:
GSMDecoderCodec();
~GSMDecoderCodec();
public:
static VoiceCodec * create(U32 codecId);
bool open();
void close();
void * openStream();
void closeStream(void * stream);
U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen);
};
#endif // _INC_AUDIOCODECGSM

276
audio/audioCodecMiles.cc Normal file
View file

@ -0,0 +1,276 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioCodecMiles.h"
//--------------------------------------------------------------------------
// Class MilesEncoderCodec:
//--------------------------------------------------------------------------
MilesEncoderCodec::MilesEncoderCodec(const char * ext) : VoiceEncoderCodec()
{
mProvider = 0;
mProcStreamOpen = 0;
mProcStreamProcess = 0;
mProcStreamClose = 0;
mQueue = 0;
mExtension = dStrdup(ext);
}
MilesEncoderCodec::~MilesEncoderCodec()
{
close();
dFree(mExtension);
}
VoiceCodec * MilesEncoderCodec::create(U32 codecId)
{
const char * ext = 0;
switch(codecId)
{
case AUDIO_CODEC_V12: ext = ".v12"; break;
case AUDIO_CODEC_V24: ext = ".v24"; break;
case AUDIO_CODEC_V29: ext = ".v29"; break;
default:
return(0);
}
// create and open the codec
MilesEncoderCodec * codec = new MilesEncoderCodec(ext);
if(!codec->open())
{
delete codec;
codec = 0;
}
return(codec);
}
//--------------------------------------------------------------------------
bool MilesEncoderCodec::open()
{
if(mProvider)
return(true);
RIB_INTERFACE_ENTRY ENCODER_REQUEST[] = {
{ RIB_FUNCTION, "ASI_stream_open", (U32)&mProcStreamOpen, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_close", (U32)&mProcStreamClose, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_process", (U32)&mProcStreamProcess, RIB_NONE } };
mProvider = RIB_find_file_provider("ASI codec", "Output file types", mExtension);
if(mProvider && (RIB_request(mProvider, "ASI stream", ENCODER_REQUEST) == RIB_NOERR))
return(true);
return(false);
}
void MilesEncoderCodec::close()
{
if(mProvider)
{
RIB_free_provider_handle(mProvider);
mProvider = 0;
}
}
//--------------------------------------------------------------------------
void * MilesEncoderCodec::openStream()
{
HASISTREAM handle = mProcStreamOpen((U32)this, callback_router, 0);
if(!handle)
return(0);
HASISTREAM * stream = new HASISTREAM;
*stream = handle;
return(static_cast<void *>(stream));
}
void MilesEncoderCodec::closeStream(void * stream)
{
AssertFatal(stream, "MilesEncoderCodec::closeStream: invalid stream ptr");
HASISTREAM * pHandle = static_cast<HASISTREAM*>(stream);
if(mProvider)
mProcStreamClose(*pHandle);
delete pHandle;
}
//--------------------------------------------------------------------------
S32 AILCALLBACK MilesEncoderCodec::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset)
{
return(((MilesEncoderCodec*)user)->callback(dest, bytesRequested, offset));
}
S32 MilesEncoderCodec::callback(void * dest, S32 bytesRequested, S32 offset)
{
offset;
return(mQueue->dequeue((U8*)dest, bytesRequested));
}
U32 MilesEncoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen)
{
if(!stream || !queue)
return(0);
mQueue = queue;
U32 amount = U32(mProcStreamProcess(*static_cast<HASISTREAM * >(stream), static_cast<void*>(const_cast<U8*>(data)), S32(maxLen)));
mQueue = 0;
return(amount);
}
//--------------------------------------------------------------------------
// Class MilesDecoderCodec:
//--------------------------------------------------------------------------
MilesDecoderCodec::MilesDecoderCodec(const char * ext) : VoiceDecoderCodec()
{
mProvider = 0;
mProcStreamOpen = 0;
mProcStreamProcess = 0;
mProcStreamSeek = 0;
mProcStreamClose = 0;
mProcStreamAttribute = 0;
mProcStreamSetPreference = 0;
mAttribOutputSampleRate = 0;
mAttribOutputBits = 0;
mAttribOutputChannels = 0;
mAttribRequestedRate = 0;
mQueue = 0;
mExtension = dStrdup(ext);
}
MilesDecoderCodec::~MilesDecoderCodec()
{
close();
dFree(mExtension);
}
VoiceCodec * MilesDecoderCodec::create(U32 codecId)
{
const char * ext = 0;
switch(codecId)
{
case AUDIO_CODEC_V12: ext = ".v12"; break;
case AUDIO_CODEC_V24: ext = ".v24"; break;
case AUDIO_CODEC_V29: ext = ".v29"; break;
default:
return(0);
}
// create and open the codec
MilesDecoderCodec * codec = new MilesDecoderCodec(ext);
if(!codec->open())
{
delete codec;
codec = 0;
}
return(codec);
}
//--------------------------------------------------------------------------
bool MilesDecoderCodec::open()
{
if(mProvider)
return(true);
RIB_INTERFACE_ENTRY DECODER_REQUEST[] = {
{ RIB_FUNCTION, "ASI_stream_attribute", (U32) &mProcStreamAttribute, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_open", (U32) &mProcStreamOpen, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_seek", (U32) &mProcStreamSeek, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_close", (U32) &mProcStreamClose, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_process", (U32) &mProcStreamProcess, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_set_preference", (U32) &mProcStreamSetPreference, RIB_NONE },
{ RIB_ATTRIBUTE, "Output sample rate", (U32) &mAttribOutputSampleRate, RIB_NONE },
{ RIB_ATTRIBUTE, "Output sample width", (U32) &mAttribOutputBits, RIB_NONE },
{ RIB_ATTRIBUTE, "Output channels", (U32) &mAttribOutputChannels, RIB_NONE },
{ RIB_PREFERENCE, "Requested sample rate", (U32) &mAttribRequestedRate, RIB_NONE } };
mProvider = RIB_find_file_provider("ASI codec", "Input file types", mExtension);
if(mProvider && (RIB_request(mProvider, "ASI stream", DECODER_REQUEST) == RIB_NOERR))
return(true);
return(false);
}
void MilesDecoderCodec::close()
{
if(mProvider)
{
RIB_free_provider_handle(mProvider);
mProvider = 0;
}
}
//--------------------------------------------------------------------------
void * MilesDecoderCodec::openStream()
{
if(!mProvider)
return(0);
HASISTREAM handle = mProcStreamOpen((U32)this, callback_router, 0);
if(!handle)
return(0);
// properties...
U32 requestedRate = VOICE_FREQUENCY;
mProcStreamSetPreference(handle, mAttribRequestedRate, &requestedRate);
U32 chan = mProcStreamAttribute(handle, mAttribOutputChannels);
U32 rate = mProcStreamAttribute(handle, mAttribOutputSampleRate);
U32 bits = mProcStreamAttribute(handle, mAttribOutputBits);
if((chan != VOICE_CHANNELS) || (rate != VOICE_FREQUENCY) || (bits != VOICE_BITS))
{
mProcStreamClose(handle);
return(0);
}
HASISTREAM * stream = new HASISTREAM;
*stream = handle;
return(static_cast<void *>(stream));
}
void MilesDecoderCodec::closeStream(void * stream)
{
AssertFatal(stream, "MilesDecoderCodec::closeStream: invalid stream ptr");
HASISTREAM * pHandle = static_cast<HASISTREAM*>(stream);
if(mProvider)
mProcStreamClose(*pHandle);
delete pHandle;
}
//--------------------------------------------------------------------------
S32 AILCALLBACK MilesDecoderCodec::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset)
{
return(((MilesDecoderCodec*)user)->callback(dest, bytesRequested, offset));
}
S32 MilesDecoderCodec::callback(void * dest, S32 bytesRequested, S32 offset)
{
offset;
return(mQueue->dequeue((U8*)dest, bytesRequested));
}
U32 MilesDecoderCodec::process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen)
{
if(!stream || !queue)
return(0);
mQueue = queue;
U32 amount = U32(mProcStreamProcess(*static_cast<HASISTREAM * >(stream), static_cast<void*>(const_cast<U8*>(data)), S32(maxLen)));
mQueue = 0;
return(amount);
}

87
audio/audioCodecMiles.h Normal file
View file

@ -0,0 +1,87 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOCODECMILES_H_
#define _AUDIOCODECMILES_H_
#ifndef _AUDIOCODEC_H_
#include "audio/audioCodec.h"
#endif
#ifndef _MSS_H_
#include "mss.h"
#endif
class MilesEncoderCodec : public VoiceEncoderCodec
{
private:
MilesEncoderCodec(const char * ext);
~MilesEncoderCodec();
char * mExtension;
HPROVIDER mProvider;
ASI_STREAM_OPEN mProcStreamOpen;
ASI_STREAM_PROCESS mProcStreamProcess;
ASI_STREAM_CLOSE mProcStreamClose;
BufferQueue * mQueue;
static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset);
S32 callback(void * dest, S32 bytesRequested, S32 offset);
public:
static VoiceCodec * create(U32 codecId);
bool open();
void close();
void * openStream();
void closeStream(void * stream);
U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen);
};
class MilesDecoderCodec : public VoiceDecoderCodec
{
private:
MilesDecoderCodec(const char * ext);
~MilesDecoderCodec();
char * mExtension;
HPROVIDER mProvider;
ASI_STREAM_OPEN mProcStreamOpen;
ASI_STREAM_PROCESS mProcStreamProcess;
ASI_STREAM_SEEK mProcStreamSeek;
ASI_STREAM_CLOSE mProcStreamClose;
ASI_STREAM_ATTRIBUTE mProcStreamAttribute;
ASI_STREAM_SET_PREFERENCE mProcStreamSetPreference;
HATTRIB mAttribOutputSampleRate;
HATTRIB mAttribOutputBits;
HATTRIB mAttribOutputChannels;
HATTRIB mAttribRequestedRate;
BufferQueue * mQueue;
static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset);
S32 callback(void * dest, S32 bytesRequested, S32 offset);
public:
static VoiceCodec * create(U32 codecId);
bool open();
void close();
void * openStream();
void closeStream(void * stream);
U32 process(void * stream, BufferQueue * queue, const U8 * data, U32 maxLen);
};
#endif // _INC_AUDIOCODEMILES

512
audio/audioDataBlock.cc Normal file
View file

@ -0,0 +1,512 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioDataBlock.h"
#include "console/consoleTypes.h"
#include "PlatformWin32/platformAL.h"
//--------------------------------------------------------------------------
namespace
{
void writeRangedF32(BitStream * bstream, F32 val, F32 min, F32 max, U32 numBits)
{
val = (mClampF(val, min, max) - min) / (max - min);
bstream->writeInt(val * ((1 << numBits) - 1), numBits);
}
F32 readRangedF32(BitStream * bstream, F32 min, F32 max, U32 numBits)
{
return(min + (F32(bstream->readInt(numBits)) / F32((1 << numBits) - 1)) * (max - min));
}
void writeRangedS32(BitStream * bstream, S32 val, S32 min, S32 max)
{
bstream->writeRangedU32((val - min), 0, (max - min));
}
S32 readRangedS32(BitStream * bstream, S32 min, S32 max)
{
return(bstream->readRangedU32(0, (max - min)) + min);
}
};
//--------------------------------------------------------------------------
// Class AudioEnvironment:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioEnvironment);
AudioEnvironment::AudioEnvironment()
{
mUseRoom = true;
mRoom = AL_ENVIRONMENT_GENERIC;
mRoomHF = 0;
mReflections = 0;
mReverb = 0;
mRoomRolloffFactor = 0.1f;
mDecayTime = 0.1f;
mDecayHFRatio = 0.1f;
mReflectionsDelay = 0.f;
mReverbDelay = 0.f;
mRoomVolume = 0;
mEffectVolume = 0.f;
mDamping = 0.f;
mEnvironmentSize = 10.f;
mEnvironmentDiffusion = 1.f;
mAirAbsorption = 0.f;
mFlags = 0;
}
static EnumTable::Enums roomEnums[] =
{
{ AL_ENVIRONMENT_GENERIC, "GENERIC" }, // 0
{ AL_ENVIRONMENT_PADDEDCELL, "PADDEDCELL" },
{ AL_ENVIRONMENT_ROOM, "ROOM" },
{ AL_ENVIRONMENT_BATHROOM, "BATHROOM" },
{ AL_ENVIRONMENT_LIVINGROOM, "LIVINGROOM" },
{ AL_ENVIRONMENT_STONEROOM, "STONEROOM" }, // 5
{ AL_ENVIRONMENT_AUDITORIUM, "AUDITORIUM" },
{ AL_ENVIRONMENT_CONCERTHALL, "CONCERTHALL" },
{ AL_ENVIRONMENT_CAVE, "CAVE" },
{ AL_ENVIRONMENT_ARENA, "ARENA" },
{ AL_ENVIRONMENT_HANGAR, "HANGAR" }, // 10
{ AL_ENVIRONMENT_CARPETEDHALLWAY, "CARPETEDHALLWAY" },
{ AL_ENVIRONMENT_HALLWAY, "HALLWAY" },
{ AL_ENVIRONMENT_STONECORRIDOR, "STONECORRIDOR" },
{ AL_ENVIRONMENT_ALLEY, "ALLEY" },
{ AL_ENVIRONMENT_FOREST, "FOREST" }, // 15
{ AL_ENVIRONMENT_CITY, "CITY" },
{ AL_ENVIRONMENT_MOUNTAINS, "MOUNTAINS" },
{ AL_ENVIRONMENT_QUARRY, "QUARRY" },
{ AL_ENVIRONMENT_PLAIN, "PLAIN" },
{ AL_ENVIRONMENT_PARKINGLOT, "PARKINGLOT" }, // 20
{ AL_ENVIRONMENT_SEWERPIPE, "SEWERPIPE" },
{ AL_ENVIRONMENT_UNDERWATER, "UNDERWATER" },
{ AL_ENVIRONMENT_DRUGGED, "DRUGGED" },
{ AL_ENVIRONMENT_DIZZY, "DIZZY" },
{ AL_ENVIRONMENT_PSYCHOTIC, "PSYCHOTIC" }, // 25
};
static EnumTable gAudioEnvironmentRoomTypes(sizeof(roomEnums) / sizeof(roomEnums[0]), &roomEnums[0]);
//--------------------------------------------------------------------------
IMPLEMENT_GETDATATYPE(AudioEnvironment)
IMPLEMENT_SETDATATYPE(AudioEnvironment)
void AudioEnvironment::consoleInit()
{
addField("useRoom", TypeBool, Offset(mUseRoom, AudioEnvironment));
addField("room", TypeEnum, Offset(mRoom, AudioEnvironment), 1, &gAudioEnvironmentRoomTypes);
addField("roomHF", TypeS32, Offset(mRoomHF, AudioEnvironment));
addField("reflections", TypeS32, Offset(mReflections, AudioEnvironment));
addField("reverb", TypeS32, Offset(mReverb, AudioEnvironment));
addField("roomRolloffFactor", TypeF32, Offset(mRoomRolloffFactor, AudioEnvironment));
addField("decayTime", TypeF32, Offset(mDecayTime, AudioEnvironment));
addField("decayHFRatio", TypeF32, Offset(mDecayHFRatio, AudioEnvironment));
addField("reflectionsDelay", TypeF32, Offset(mReflectionsDelay, AudioEnvironment));
addField("reverbDelay", TypeF32, Offset(mReverbDelay, AudioEnvironment));
addField("roomVolume", TypeS32, Offset(mRoomVolume, AudioEnvironment));
addField("effectVolume", TypeF32, Offset(mEffectVolume, AudioEnvironment));
addField("damping", TypeF32, Offset(mDamping, AudioEnvironment));
addField("environmentSize", TypeF32, Offset(mEnvironmentSize, AudioEnvironment));
addField("environmentDiffusion", TypeF32, Offset(mEnvironmentDiffusion, AudioEnvironment));
addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioEnvironment));
addField("flags", TypeS32, Offset(mFlags, AudioEnvironment));
Con::registerType(TypeAudioEnvironmentPtr, sizeof(AudioEnvironment*),
REF_GETDATATYPE(AudioEnvironment),
REF_SETDATATYPE(AudioEnvironment));
}
void AudioEnvironment::packData(BitStream* stream)
{
Parent::packData(stream);
if(stream->writeFlag(mUseRoom))
stream->writeRangedU32(mRoom, AL_ENVIRONMENT_GENERIC, AL_ENVIRONMENT_COUNT);
else
{
writeRangedS32(stream, mRoomHF, -10000, 0);
writeRangedS32(stream, mReflections, -10000, 10000);
writeRangedS32(stream, mReverb, -10000, 2000);
writeRangedF32(stream, mRoomRolloffFactor, 0.1f, 10.f, 8);
writeRangedF32(stream, mDecayTime, 0.1f, 20.f, 8);
writeRangedF32(stream, mDecayHFRatio, 0.1f, 20.f, 8);
writeRangedF32(stream, mReflectionsDelay, 0.f, 0.3f, 9);
writeRangedF32(stream, mReverbDelay, 0.f, 0.1f, 7);
writeRangedS32(stream, mRoomVolume, -10000, 0);
writeRangedF32(stream, mEffectVolume, 0.f, 1.f, 8);
writeRangedF32(stream, mDamping, 0.f, 2.f, 9);
writeRangedF32(stream, mEnvironmentSize, 1.f, 100.f, 10);
writeRangedF32(stream, mEnvironmentDiffusion, 0.f, 1.f, 8);
writeRangedF32(stream, mAirAbsorption, -100.f, 0.f, 10);
stream->writeInt(mFlags, 6);
}
}
void AudioEnvironment::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mUseRoom = stream->readFlag();
if(mUseRoom)
mRoom = stream->readRangedU32(AL_ENVIRONMENT_GENERIC, AL_ENVIRONMENT_COUNT);
else
{
mRoomHF = readRangedS32(stream, -10000, 0);
mReflections = readRangedS32(stream, -10000, 10000);
mReverb = readRangedS32(stream, -10000, 2000);
mRoomRolloffFactor = readRangedF32(stream, 0.1f, 10.f, 8);
mDecayTime = readRangedF32(stream, 0.1f, 20.f, 8);
mDecayHFRatio = readRangedF32(stream, 0.1f, 20.f, 8);
mReflectionsDelay = readRangedF32(stream, 0.f, 0.3f, 9);
mReverbDelay = readRangedF32(stream, 0.f, 0.1f, 7);
mRoomVolume = readRangedS32(stream, -10000, 0);
mEffectVolume = readRangedF32(stream, 0.f, 1.f, 8);
mDamping = readRangedF32(stream, 0.f, 2.f, 9);
mEnvironmentSize = readRangedF32(stream, 1.f, 100.f, 10);
mEnvironmentDiffusion = readRangedF32(stream, 0.f, 1.f, 8);
mAirAbsorption = readRangedF32(stream, -100.f, 0.f, 10);
mFlags = stream->readInt(6);
}
}
//--------------------------------------------------------------------------
// Class AudioEnvironmentProfile:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioSampleEnvironment);
AudioSampleEnvironment::AudioSampleEnvironment()
{
mDirect = 0;
mDirectHF = 0;
mRoom = 0;
mRoomHF = 0;
mObstruction = 0.f;
mObstructionLFRatio = 0.f;
mOcclusion = 0.f;
mOcclusionLFRatio = 0.f;
mOcclusionRoomRatio = 0.f;
mRoomRolloff = 0.f;
mAirAbsorption = 0.f;
mOutsideVolumeHF = 0.f;
mFlags = 0;
}
//--------------------------------------------------------------------------
IMPLEMENT_GETDATATYPE(AudioSampleEnvironment)
IMPLEMENT_SETDATATYPE(AudioSampleEnvironment)
void AudioSampleEnvironment::consoleInit()
{
addField("direct", TypeS32, Offset(mDirect, AudioSampleEnvironment));
addField("directHF", TypeS32, Offset(mDirectHF, AudioSampleEnvironment));
addField("room", TypeS32, Offset(mRoom, AudioSampleEnvironment));
addField("obstruction", TypeF32, Offset(mObstruction, AudioSampleEnvironment));
addField("obstructionLFRatio", TypeF32, Offset(mObstructionLFRatio, AudioSampleEnvironment));
addField("occlusion", TypeF32, Offset(mOcclusion, AudioSampleEnvironment));
addField("occlusionLFRatio", TypeF32, Offset(mOcclusionLFRatio, AudioSampleEnvironment));
addField("occlusionRoomRatio", TypeF32, Offset(mOcclusionRoomRatio, AudioSampleEnvironment));
addField("roomRolloff", TypeF32, Offset(mRoomRolloff, AudioSampleEnvironment));
addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioSampleEnvironment));
addField("outsideVolumeHF", TypeS32, Offset(mOutsideVolumeHF, AudioSampleEnvironment));
addField("flags", TypeS32, Offset(mFlags, AudioSampleEnvironment));
Con::registerType(TypeAudioSampleEnvironmentPtr,
sizeof(AudioSampleEnvironment*),
REF_GETDATATYPE(AudioSampleEnvironment),
REF_SETDATATYPE(AudioSampleEnvironment));
}
void AudioSampleEnvironment::packData(BitStream* stream)
{
Parent::packData(stream);
writeRangedS32(stream, mDirect, -10000, 1000);
writeRangedS32(stream, mDirectHF, -10000, 0);
writeRangedS32(stream, mRoom, -10000, 1000);
writeRangedS32(stream, mRoomHF, -10000, 0);
writeRangedF32(stream, mObstruction, 0.f, 1.f, 9);
writeRangedF32(stream, mObstructionLFRatio, 0.f, 1.f, 8);
writeRangedF32(stream, mOcclusion, 0.f, 1.f, 9);
writeRangedF32(stream, mOcclusionLFRatio, 0.f, 1.f, 8);
writeRangedF32(stream, mOcclusionRoomRatio, 0.f, 10.f, 9);
writeRangedF32(stream, mRoomRolloff, 0.f, 10.f, 9);
writeRangedF32(stream, mAirAbsorption, 0.f, 10.f, 9);
writeRangedS32(stream, mOutsideVolumeHF, -10000, 0);
stream->writeInt(mFlags, 3);
}
void AudioSampleEnvironment::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mDirect = readRangedS32(stream, -10000, 1000);
mDirectHF = readRangedS32(stream, -10000, 0);
mRoom = readRangedS32(stream, -10000, 1000);
mRoomHF = readRangedS32(stream, -10000, 0);
mObstruction = readRangedF32(stream, 0.f, 1.f, 9);
mObstructionLFRatio = readRangedF32(stream, 0.f, 1.f, 8);
mOcclusion = readRangedF32(stream, 0.f, 1.f, 9);
mOcclusionLFRatio = readRangedF32(stream, 0.f, 1.f, 8);
mOcclusionRoomRatio = readRangedF32(stream, 0.f, 10.f, 9);
mRoomRolloff = readRangedF32(stream, 0.f, 10.f, 9);
mAirAbsorption = readRangedF32(stream, 0.f, 10.f, 9);
mOutsideVolumeHF = readRangedS32(stream, -10000, 0);
mFlags = stream->readInt(3);
}
//--------------------------------------------------------------------------
// Class AudioDescription:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioDescription);
AudioDescription::AudioDescription()
{
mDescription.mVolume = 1.0f;
mDescription.mIsLooping = false;
mDescription.mIs3D = false;
mDescription.mMinDistance = 1.0f;
mDescription.mMaxDistance = 100.0f;
mDescription.mConeInsideAngle = 360;
mDescription.mConeOutsideAngle = 360;
mDescription.mConeOutsideVolume = 1.0f;
mDescription.mConeVector.set(0, 0, 1);
mDescription.mEnvironmentLevel = 0.f;
mDescription.mLoopCount = -1;
mDescription.mMinLoopGap = 0;
mDescription.mMaxLoopGap = 0;
mDescription.mType = Audio::DefaultAudioType;
}
//--------------------------------------------------------------------------
IMPLEMENT_GETDATATYPE(AudioDescription)
IMPLEMENT_SETDATATYPE(AudioDescription)
void AudioDescription::consoleInit()
{
addField("volume", TypeF32, Offset(mDescription.mVolume, AudioDescription));
addField("isLooping", TypeBool, Offset(mDescription.mIsLooping, AudioDescription));
addField("is3D", TypeBool, Offset(mDescription.mIs3D, AudioDescription));
addField("minDistance", TypeF32, Offset(mDescription.mMinDistance, AudioDescription));
addField("maxDistance", TypeF32, Offset(mDescription.mMaxDistance, AudioDescription));
addField("coneInsideAngle", TypeS32, Offset(mDescription.mConeInsideAngle, AudioDescription));
addField("coneOutsideAngle", TypeS32, Offset(mDescription.mConeOutsideAngle, AudioDescription));
addField("coneOutsideVolume", TypeF32, Offset(mDescription.mConeOutsideVolume, AudioDescription));
addField("coneVector", TypePoint3F, Offset(mDescription.mConeVector, AudioDescription));
addField("environmentLevel", TypeF32, Offset(mDescription.mEnvironmentLevel, AudioDescription));
addField("loopCount", TypeS32, Offset(mDescription.mLoopCount, AudioDescription));
addField("minLoopGap", TypeS32, Offset(mDescription.mMinLoopGap, AudioDescription));
addField("maxLoopGap", TypeS32, Offset(mDescription.mMaxLoopGap, AudioDescription));
addField("type", TypeS32, Offset(mDescription.mType, AudioDescription));
Con::registerType(TypeAudioDescriptionPtr, sizeof(AudioDescription*),
REF_GETDATATYPE(AudioDescription),
REF_SETDATATYPE(AudioDescription));
}
//--------------------------------------------------------------------------
bool AudioDescription::onAdd()
{
if (!Parent::onAdd())
return false;
// validate the data
mDescription.mVolume = mClampF(mDescription.mVolume, 0.0f, 1.0f);
mDescription.mLoopCount = mClamp(mDescription.mLoopCount, -1, mDescription.mLoopCount);
mDescription.mMaxLoopGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap);
mDescription.mMinLoopGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap);
if (mDescription.mIs3D)
{
// validate the data
mDescription.mMinDistance = mClampF(mDescription.mMinDistance, 0.f, mDescription.mMinDistance);
mDescription.mMaxDistance = (mDescription.mMaxDistance > mDescription.mMinDistance) ? mDescription.mMaxDistance : (mDescription.mMinDistance+0.01f);
mDescription.mConeInsideAngle = mClamp(mDescription.mConeInsideAngle, 0, 360);
mDescription.mConeOutsideAngle = mClamp(mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle, 360);
mDescription.mConeOutsideVolume = mClampF(mDescription.mConeOutsideVolume, 0.0f, 1.0f);
mDescription.mConeVector.normalize();
mDescription.mEnvironmentLevel = mClampF(mDescription.mEnvironmentLevel, 0.f, 1.f);
}
if(mDescription.mType >= Audio::NumAudioTypes)
mDescription.mType = Audio::DefaultAudioType;
return true;
}
//--------------------------------------------------------------------------
void AudioDescription::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFloat(mDescription.mVolume, 6);
if(stream->writeFlag(mDescription.mIsLooping))
{
stream->write(mDescription.mLoopCount);
stream->write(mDescription.mMinLoopGap);
stream->write(mDescription.mMaxLoopGap);
}
stream->writeFlag(mDescription.mIs3D);
if (mDescription.mIs3D)
{
stream->write(mDescription.mMinDistance);
stream->write(mDescription.mMaxDistance);
stream->writeInt(mDescription.mConeInsideAngle, 9);
stream->writeInt(mDescription.mConeOutsideAngle, 9);
stream->writeInt(mDescription.mConeOutsideVolume, 6);
stream->writeNormalVector(mDescription.mConeVector, 8);
stream->write(mDescription.mEnvironmentLevel);
}
stream->writeInt(mDescription.mType, 3);
}
//--------------------------------------------------------------------------
void AudioDescription::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mDescription.mVolume = stream->readFloat(6);
mDescription.mIsLooping = stream->readFlag();
if(mDescription.mIsLooping)
{
stream->read(&mDescription.mLoopCount);
stream->read(&mDescription.mMinLoopGap);
stream->read(&mDescription.mMaxLoopGap);
}
mDescription.mIs3D = stream->readFlag();
if ( mDescription.mIs3D )
{
stream->read(&mDescription.mMinDistance);
stream->read(&mDescription.mMaxDistance);
mDescription.mConeInsideAngle = stream->readInt(9);
mDescription.mConeOutsideAngle = stream->readInt(9);
mDescription.mConeOutsideVolume = stream->readFloat(6);
stream->readNormalVector(&mDescription.mConeVector, 8);
stream->read(&mDescription.mEnvironmentLevel);
}
mDescription.mType = stream->readInt(3);
}
//--------------------------------------------------------------------------
// Class AudioProfile:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioProfile);
AudioProfile::AudioProfile()
{
mFilename = NULL;
mDescriptionObject = NULL;
mSampleEnvironment = 0;
mPreload = false;
}
//--------------------------------------------------------------------------
IMPLEMENT_GETDATATYPE(AudioProfile)
IMPLEMENT_SETDATATYPE(AudioProfile)
void AudioProfile::consoleInit()
{
addField("filename", TypeString, Offset(mFilename, AudioProfile));
addField("description", TypeAudioDescriptionPtr, Offset(mDescriptionObject, AudioProfile));
addField("environment", TypeAudioSampleEnvironmentPtr, Offset(mSampleEnvironment, AudioProfile));
addField("preload", TypeBool, Offset(mPreload, AudioProfile));
Con::registerType(TypeAudioProfilePtr, sizeof(AudioProfile*),
REF_GETDATATYPE(AudioProfile),
REF_SETDATATYPE(AudioProfile));
}
//--------------------------------------------------------------------------
bool AudioProfile::onAdd()
{
if (!Parent::onAdd())
return false;
// if this is client side, make sure that description is as well
if(mDescriptionObject)
{ // client side dataBlock id's are not in the dataBlock id range
if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast)
{
SimObjectId pid = mDescriptionObject->getId();
if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast)
{
Con::errorf(ConsoleLogEntry::General,"AudioProfile: data dataBlock not networkable (use datablock to create).");
return false;
}
}
}
if(mPreload && mFilename != NULL)
{
Resource<AudioBuffer> buffer = AudioBuffer::find(mFilename);
if(bool(buffer))
{
ALuint bufferId = buffer->getALBuffer(true);
alBufferi_EXT(bufferId, AL_BUFFER_KEEP_RESIDENT, AL_TRUE);
}
}
return(true);
}
//--------------------------------------------------------------------------
void AudioProfile::packData(BitStream* stream)
{
Parent::packData(stream);
// audio description:
if (stream->writeFlag(mDescriptionObject))
stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
// environmental info:
if (stream->writeFlag(mSampleEnvironment))
stream->writeRangedU32(mSampleEnvironment->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
//
char buffer[256];
if(!mFilename)
buffer[0] = 0;
else
dStrcpy(buffer, mFilename);
S32 len = dStrlen(buffer);
if(len > 3 && !dStricmp(buffer + len - 4, ".wav"))
buffer[len-4] = 0;
stream->writeString(buffer);
}
//--------------------------------------------------------------------------
void AudioProfile::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
// audio datablock:
if (stream->readFlag()) {
SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
Sim::findObject(id, mDescriptionObject);
}
// sample environment:
if (stream->readFlag()) {
SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
Sim::findObject(id, mSampleEnvironment);
}
char buffer[256];
stream->readString(buffer);
dStrcat(buffer, ".wav");
mFilename = StringTable->insert(buffer);
}

132
audio/audioDataBlock.h Normal file
View file

@ -0,0 +1,132 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIODATABLOCK_H_
#define _AUDIODATABLOCK_H_
#ifndef _PLATFORMAUDIO_H_
#include "Platform/platformAudio.h"
#endif
#ifndef _AUDIOBUFFER_H_
#include "audio/audioBuffer.h"
#endif
#ifndef _BITSTREAM_H_
#include "Core/bitStream.h"
#endif
#ifndef _NETOBJECT_H_
#include "Sim/netObject.h"
#endif
//--------------------------------------------------------------------------
class AudioEnvironment : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
bool mUseRoom;
S32 mRoom;
S32 mRoomHF;
S32 mReflections;
S32 mReverb;
F32 mRoomRolloffFactor;
F32 mDecayTime;
F32 mDecayHFRatio;
F32 mReflectionsDelay;
F32 mReverbDelay;
S32 mRoomVolume;
F32 mEffectVolume;
F32 mDamping;
F32 mEnvironmentSize;
F32 mEnvironmentDiffusion;
F32 mAirAbsorption;
S32 mFlags;
AudioEnvironment();
static void consoleInit();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(AudioEnvironment);
};
//--------------------------------------------------------------------------
class AudioSampleEnvironment : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
S32 mDirect;
S32 mDirectHF;
S32 mRoom;
S32 mRoomHF;
F32 mObstruction;
F32 mObstructionLFRatio;
F32 mOcclusion;
F32 mOcclusionLFRatio;
F32 mOcclusionRoomRatio;
F32 mRoomRolloff;
F32 mAirAbsorption;
S32 mOutsideVolumeHF;
S32 mFlags;
AudioSampleEnvironment();
static void consoleInit();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(AudioSampleEnvironment);
};
//--------------------------------------------------------------------------
class AudioDescription: public SimDataBlock
{
typedef SimDataBlock Parent;
public:
// field info
Audio::Description mDescription;
AudioDescription();
DECLARE_CONOBJECT(AudioDescription);
static void consoleInit();
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
const Audio::Description* getDescription() const { return &mDescription; }
};
//----------------------------------------------------------------------------
class AudioProfile: public SimDataBlock
{
typedef SimDataBlock Parent;
public:
// field info
AudioDescription * mDescriptionObject;
AudioSampleEnvironment * mSampleEnvironment;
StringTableEntry mFilename;
bool mPreload;
AudioProfile();
DECLARE_CONOBJECT(AudioProfile);
static void consoleInit();
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
const Audio::Description* getDescription() const { return mDescriptionObject ? mDescriptionObject->getDescription() : NULL; }
bool isPreload() { return mPreload; }
};
#endif // _H_AUDIODATABLOCK_

773
audio/audioFunctions.cc Normal file
View file

@ -0,0 +1,773 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/platformAudio.h"
#include "console/simBase.h"
#include "audio/audioDataBlock.h"
extern F32 mAudioTypeVolume[Audio::NumAudioTypes];
//--------------------------------------------------------------------------
// Expose all al get/set methods...
//--------------------------------------------------------------------------
enum {
Source = BIT(0),
Listener = BIT(1),
Context = BIT(2),
Environment = BIT(3),
Get = BIT(4),
Set = BIT(5),
Int = BIT(6),
Float = BIT(7),
Float3 = BIT(8),
Float6 = BIT(9)
};
static ALenum getEnum(const char * name, U32 flags)
{
AssertFatal(name, "getEnum: bad param");
static struct {
char * mName;
ALenum mAlenum;
U32 mFlags;
} table[] = {
//-----------------------------------------------------------------------------------------------------------------
// "name" ENUM Flags
//-----------------------------------------------------------------------------------------------------------------
//{ "AL_SOURCE_TYPE", AL_SOURCE_TYPE, (Source|Get|Set|Float) },
{ "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) },
{ "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) },
{ "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) },
//{ "AL_MIN_DISTANCE", AL_MIN_DISTANCE, (Source|Get|Set|Float) },
{ "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) },
{ "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) },
{ "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) },
{ "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) },
{ "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) },
{ "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) },
{ "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) },
{ "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) },
//{ "AL_SOURCE_LOOPING", AL_SOURCE_LOOPING, (Source|Get|Set|Int) },
//{ "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) },
//{ "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) },
//{ "AL_SOURCE_AMBIENT", AL_SOURCE_AMBIENT, (Source|Get|Set|Int) },
{ "AL_VENDOR", AL_VENDOR, (Context|Get) },
{ "AL_VERSION", AL_VERSION, (Context|Get) },
{ "AL_RENDERER", AL_RENDERER, (Context|Get) },
{ "AL_EXTENSIONS", AL_EXTENSIONS, (Context|Get) },
//{ "ALC_PROVIDER", ALC_PROVIDER, (Context|Get|Set|Int) },
//{ "ALC_PROVIDER_COUNT", ALC_PROVIDER_COUNT, (Context|Get|Int) },
//{ "ALC_PROVIDER_NAME", ALC_PROVIDER_NAME, (Context|Get|Int) },
//{ "ALC_SPEAKER", ALC_SPEAKER, (Context|Get|Set|Int) },
//{ "ALC_SPEAKER_COUNT", ALC_SPEAKER_COUNT, (Context|Get|Int) },
//{ "ALC_SPEAKER_NAME", ALC_SPEAKER_NAME, (Context|Get|Int) },
//{ "ALC_BUFFER_DYNAMIC_MEMORY_SIZE", ALC_BUFFER_DYNAMIC_MEMORY_SIZE, (Context|Get|Set|Int) },
//{ "ALC_BUFFER_DYNAMIC_MEMORY_USAGE",ALC_BUFFER_DYNAMIC_MEMORY_USAGE, (Context|Get|Int) },
//{ "ALC_BUFFER_DYNAMIC_COUNT", ALC_BUFFER_DYNAMIC_COUNT, (Context|Get|Int) },
//{ "ALC_BUFFER_MEMORY_USAGE", ALC_BUFFER_MEMORY_USAGE, (Context|Get|Int) },
//{ "ALC_BUFFER_COUNT", ALC_BUFFER_COUNT, (Context|Get|Int) },
//{ "ALC_BUFFER_LATENCY", ALC_BUFFER_LATENCY, (Context|Get|Int) },
/*
// environment
{ "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) },
// sample environment
{ "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) },
*/
};
for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++)
{
if((table[i].mFlags & flags) != flags)
continue;
if(dStricmp(table[i].mName, name) == 0)
return(table[i].mAlenum);
}
return(AL_INVALID);
}
//-----------------------------------------------
ConsoleFunction(OpenALInitDriver, bool, 1, 1, "OpenALInitDriver()")
{
if(Audio::OpenALInit())
{
ResourceManager->registerExtension(".wav", AudioBuffer::construct);
Con::setIntVariable("DefaultAudioType", Audio::DefaultAudioType);
Con::setIntVariable("ChatAudioType", Audio::ChatAudioType);
Con::setIntVariable("GuiAudioType", Audio::GuiAudioType);
Con::setIntVariable("EffectAudioType", Audio::EffectAudioType);
return true;
}
return false;
}
//-----------------------------------------------
ConsoleFunction(OpenALShutdownDriver, void, 1, 1, "OpenALShutdownDriver()")
{
Audio::OpenALShutdown();
}
//-----------------------------------------------
ConsoleFunction(alGetString, const char *, 2, 2, "alGetString(enum)")
{
argc;
ALenum e = getEnum(argv[1], (Context|Get));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetString: invalid enum name '%s'", argv[1]);
return "";
}
return (const char*)alGetString(e);
}
//--------------------------------------------------------------------------
// Source
//--------------------------------------------------------------------------
ConsoleFunction(alxCreateSource, S32, 2, 6, "alxCreateSource(profile, {x,y,z} | description, filename, {x,y,z})")
{
AudioDescription *description = NULL;
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
description = dynamic_cast<AudioDescription*>( Sim::findObject( argv[1] ) );
if (description == NULL)
{
Con::printf("Unable to locate audio profile/description '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
}
if (profile)
{
if (argc == 2)
return alxCreateSource(profile);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) ));
return alxCreateSource(profile, &transform);
}
if (description)
{
if (argc == 3)
return alxCreateSource(description, argv[2]);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) ));
return alxCreateSource(description, argv[2], &transform);
}
return NULL_AUDIOHANDLE;
}
//-----------------------------------------------
ConsoleFunction(alxSourcef, void, 4, 4, "alxSourcef(handle, ALenum, value)")
{
ALenum e = getEnum(argv[2], (Source|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]);
return;
}
alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3]));
}
//-----------------------------------------------
ConsoleFunction(alxSource3f, void, 3, 6, "alxSource3f(handle, ALenum, \"x y z\" | x, y, z)")
{
ALenum e = getEnum(argv[2], (Source|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]);
return;
}
if(argc != 3 || argc != 6)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args");
return;
}
Point3F pos;
if(argc == 3)
dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[1]);
pos.y = dAtof(argv[2]);
pos.z = dAtof(argv[3]);
}
alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z);
}
//-----------------------------------------------
ConsoleFunction(alxSourcei, void, 4, 4, "alxSourcei(handle, ALenum, value)")
{
ALenum e = getEnum(argv[2], (Source|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]);
return;
}
alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3]));
}
//-----------------------------------------------
ConsoleFunction(alxGetSourcef, F32, 3, 3, "alxGetSourcef(handle, ALenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]);
return(0.f);
}
F32 value;
alxGetSourcef(dAtoi(argv[1]), e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxGetSource3f, const char *, 3, 3, "alxGetSource3f(handle, ALenum)" )
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]);
return("0 0 0");
}
F32 value1, value2, value3;
alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
//-----------------------------------------------
ConsoleFunction(alxGetSourcei, S32, 3, 3, "alxGetSourcei(handle, ALenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]);
return(0);
}
S32 value;
alxGetSourcei(dAtoi(argv[1]), e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxPlay, S32, 2, 5, "alxPlay(handle) | alxPlay(profile, {x,y,z})")
{
if (argc == 2)
{
AUDIOHANDLE handle = dAtoi(argv[1]);
return alxPlay(handle);
}
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
Con::printf("Unable to locate audio profile '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
Point3F pos(0.f, 0.f, 0.f);
if(argc == 3)
dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z);
else if(argc == 5)
pos.set(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
MatrixF transform;
transform.set(EulerF(0,0,0), pos);
return alxPlay(profile, &transform, NULL);
}
//-----------------------------------------------
ConsoleFunction(alxStop, void, 2, ,2, "alxStop(handle)")
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return;
alxStop(handle);
}
//-----------------------------------------------
ConsoleFunction(alxStopAll, void, 1, 1, "alxStopAll()")
{
alxStopAll();
}
//--------------------------------------------------------------------------
// Listener
//--------------------------------------------------------------------------
ConsoleFunction(alxListenerf, void, 2, 2, "alxListener(ALenum, value)")
{
ALenum e = getEnum(argv[1], (Listener|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxListenerf: invalid enum name '%s'", argv[1]);
return;
}
alxListenerf(e, dAtof(argv[2]));
}
//-----------------------------------------------
ConsoleFunction(alxListener3f, void, 3, 5, "alxListener3f(ALenum, \"x y z\" | x, y, z)")
{
ALenum e = getEnum(argv[1], (Listener|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxListener3f: invalid enum name '%s'", argv[1]);
return;
}
if(argc != 3 || argc != 5)
{
Con::errorf(ConsoleLogEntry::General, "alxListener3f: wrong number of arguments");
return;
}
Point3F pos;
if(argc == 3)
dSscanf(argv[2], "%f %f %f", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[2]);
pos.y = dAtof(argv[3]);
pos.z = dAtof(argv[4]);
}
alxListener3f(e, pos.x, pos.y, pos.z);
}
//-----------------------------------------------
ConsoleFunction(alxGetListenerf, F32, 2, 2, "alxGetListenerf(Alenum)")
{
ALenum e = getEnum(argv[1], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxGetListenerf: invalid enum name '%s'", argv[1]);
return(0.f);
}
F32 value;
alxGetListenerf(e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxGetListener3f, const char *, 2, 2, "alxGetListener3f(Alenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxGetListener3f: invalid enum name '%s'", argv[1]);
return("0 0 0");
}
F32 value1, value2, value3;
alxGetListener3f(e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
//-----------------------------------------------
ConsoleFunction(alxGetListeneri, S32, 2, 2, "alxGetListeneri(Alenum)")
{
ALenum e = getEnum(argv[1], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxGetListeneri: invalid enum name '%s'", argv[1]);
return(0);
}
S32 value;
alxGetListeneri(e, &value);
return(value);
}
//--------------------------------------------------------------------------
// Channel Volumes
//--------------------------------------------------------------------------
ConsoleFunction(alxGetChannelVolume, S32, 2, 2, "alxGetChannelVolume(channel_id)")
{
U32 type = dAtoi(argv[1]);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1]));
return(0.f);
}
return(mAudioTypeVolume[type]);
}
//-----------------------------------------------
ConsoleFunction(alxSetChannelVolume, bool, 3, 3, "alxGetChannelVolume(channel_id, volume 0.0-1.0)")
{
U32 type = dAtoi(argv[1]);
F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "alxSetChannelVolume: invalid channel '%d'", dAtoi(argv[1]));
return false;
}
mAudioTypeVolume[type] = volume;
#pragma message("todo") /*
alxUpdateTypeGain(1 << type);
*/
return true;
}
//-----------------------------------------------
//-----------------------------------------------
//-----------------------------------------------
//-----------------------------------------------
#if 0
ConsoleFunction(ExpandFilename, const char*, 2, 2, "ExpandFilename(filename)")
{
}
//--------------------------------------------------------------------------
void init()
{
Con::addCommand("alxIsEnabled", cAudio_isEnabled, "alxIsEnabled(name)", 2, 2);
Con::addCommand("alxIsExtensionPresent", cAudio_isExtensionPresent, "alxIsExtensionPresent(name)", 2, 2);
Con::addCommand("alxContexti", cAudio_alxContexti, "alxContexti(Alenum, value)", 3, 3);
Con::addCommand("alxGetContexti", cAudio_alxGetContexti, "alxGetContexti(Alenum)", 2, 2);
Con::addCommand("alxGetContextstr", cAudio_alxGetContextstr, "alxGetContextstr(Alenum, idx)", 3, 3);
Con::addCommand("alxEnvironmenti", cAudio_alxEnvironmenti, "alxEnvironmenti(Alenum, value)", 3, 3);
Con::addCommand("alxEnvironmentf", cAudio_alxEnvironmentf, "alxEnvironmentf(Alenum, value)", 3, 3);
Con::addCommand("alxGetEnvironmenti", cAudio_alxGetEnvironmenti, "alxGetEnvironmenti(Alenum)", 2, 2);
Con::addCommand("alxGetEnvironmentf", cAudio_alxGetEnvironmentf, "alxGetEnvironmentf(Alenum)", 2, 2);
Con::addCommand("alxSetEnvironment", cAudio_alxSetEnvironment, "alxSetEnvironment(AudioEnvironmentData)", 2, 2);
Con::addCommand("alxEnableEnvironmental", cAudio_alxEnableEnvironmental, "alxEnableEnvironmental(bool)", 2, 2 );
Con::addCommand("alxGetWaveLen", cAudio_alxGetWaveLen, "alxGetWaveLen(profile|filename)", 2, 2);
Con::addCommand("getAudioDriverList", cGetAudioDriverList, "getAudioDriverList();", 1, 1);
Con::addCommand("getAudioDriverInfo", cGetAudioDriverInfo, "getAudioDriverInfo();", 1, 1);
Con::addCommand("alxSetCaptureGainScale", cAudio_alxSetCaptureGainScale, "alxSetCaptureGainScale(scale)", 2, 2);
Con::addCommand("alxGetCaptureGainScale", cAudio_alxGetCaptureGainScale, "alxGetCaptureGainScale()", 1, 1);
Con::addCommand("alxDisableOuterFalloffs", cAudio_alxDisableOuterFalloffs, "alxDisableOuterFalloffs(bool)", 2, 2);
Con::addCommand("alxSetInnerFalloffScale", cAudio_alxSetInnerFalloffScale, "alxSetInnerFalloffScale(scale)", 2, 2);
Con::addCommand("alxGetInnerFalloffScale", cAudio_alxGetInnerFalloffScale, "alxGetInnerFalloffScale()", 1, 1);
Con::addCommand("alxForceMaxDistanceUpdate", cAudio_alxForceMaxDistanceUpdate, "alxForceMaxDistanceUpdate(bool)", 2, 2);
// default all channels to full gain
for(U32 i = 0; i < Audio::NumAudioTypes; i++)
mAudioTypeVolume[i] = 1.f;
}
//--------------------------------------------------------------------------
// Console functions
//--------------------------------------------------------------------------
static bool cAudio_setDriver(SimObject *, S32, const char *argv[])
{
return(Audio::setDriver(argv[1]));
}
//--------------------------------------------------------------------------
static void cAudio_alxEnvironmenti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmenti: invalid enum name '%s'", argv[1]);
return;
}
alxEnvironmenti(e, dAtoi(argv[2]));
}
static void cAudio_alxEnvironmentf(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxEnvironmentf: invalid enum name '%s'", argv[1]);
return;
}
alxEnvironmenti(e, dAtof(argv[2]));
}
static S32 cAudio_alxGetEnvironmenti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmenti: invalid enum name '%s'", argv[1]);
return(0);
}
S32 value;
alxGetEnvironmenti(e, &value);
return(value);
}
static F32 cAudio_alxGetEnvironmentf(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Environment|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetEnvironmentf: invalid enum name '%s'", argv[1]);
return(0.f);
}
F32 value;
alxGetEnvironmentf(e, &value);
return(value);
}
static void cAudio_alxSetEnvironment(SimObject *, S32, const char ** argv)
{
AudioEnvironment * environment = dynamic_cast<AudioEnvironment*>(Sim::findObject(argv[1]));
alxSetEnvironment(environment);
}
static void cAudio_alxEnableEnvironmental(SimObject *, S32, const char ** argv)
{
alxEnableEnvironmental(dAtob(argv[1]));
}
//--------------------------------------------------------------------------
// Misc
//--------------------------------------------------------------------------
static S32 cAudio_alxGetWaveLen(SimObject *, S32, const char ** argv)
{
// filename or profile?
AudioProfile * profile = 0;
const char * fileName = 0;
if(!dStrrchr(argv[1], '.'))
{
profile = dynamic_cast<AudioProfile*>(Sim::findObject(argv[1]));
if(!profile)
{
Con::errorf(ConsoleLogEntry::General, "Unable to locate audio profile: '%s'", argv[1]);
return(0);
}
fileName = profile->mFilename;
}
else
fileName = argv[1];
Resource<AudioBuffer> buffer = AudioBuffer::find(fileName);
if(!bool(buffer))
{
Con::errorf(ConsoleLogEntry::General, "Failed to find wave file: '%s'", fileName);
return(0);
}
ALuint alBuffer = buffer->getALBuffer(true);
if(alBuffer == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetWaveLen: invalid buffer");
return(0);
}
return(alxGetWaveLen(alBuffer));
}
static const char* cGetAudioDriverList( SimObject*, S32, const char** )
{
return( Audio::getDriverListString() );
}
static const char* cGetAudioDriverInfo( SimObject*, S32, const char** )
{
return( Audio::getCurrentDriverInfo() );
}
static void cAudio_alxSetCaptureGainScale(SimObject *, S32, const char ** argv)
{
mCaptureGainScale = mClampF(dAtof(argv[1]), MIN_CAPTURE_SCALE, MAX_CAPTURE_SCALE);
}
static F32 cAudio_alxGetCaptureGainScale(SimObject *, S32, const char **)
{
return(mCaptureGainScale);
}
static void cAudio_alxForceMaxDistanceUpdate(SimObject *, S32, const char ** argv)
{
mForceMaxDistanceUpdate = dAtob(argv[1]);
}
static void cAudio_alxDisableOuterFalloffs(SimObject *, S32, const char ** argv)
{
alxDisableOuterFalloffs(dAtob(argv[1]));
}
static void cAudio_alxSetInnerFalloffScale(SimObject *, S32, const char ** argv)
{
alxSetInnerFalloffScale(dAtof(argv[1]));
}
static F32 cAudio_alxGetInnerFalloffScale(SimObject *, S32, const char **)
{
return(alxGetInnerFalloffScale());
}
// Music: ----------------------------------------------------------------
static void cAudio_alxPlayMusic(SimObject *, S32, const char ** argv)
{
alxPlayMusicStream(StringTable->insert(argv[1]));
}
static void cAudio_alxStopMusic(SimObject *, S32, const char **)
{
alxStopMusicStream();
}
static void cAudio_alxContexti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Context|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxContexti: invalid enum name '%s'", argv[1]);
return;
}
alxContexti(e, dAtoi(argv[2]));
}
static S32 cAudio_alxGetContexti(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Context|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContexti: invalid enum name '%s'", argv[2]);
return(0);
}
ALint value = 0;
alxGetContexti(e, &value);
return(value);
}
static const char * cAudio_alxGetContextstr(SimObject *, S32, const char ** argv)
{
ALenum e = getEnum(argv[1], (Context|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetContextstr: invalid enum name '%s'", argv[2]);
return("");
}
ALubyte * str = (ALubyte *)"";
alxGetContextstr(e, dAtoi(argv[2]), &str);
return(StringTable->insert((const char *)str));
}
static bool cAudio_isEnabled(SimObject *, S32, const char ** argv)
{
if(!dStricmp(argv[1], "system"))
return(mInitialized);
if(!dStricmp(argv[1], "capture"))
return(mCaptureInitialized);
if(!dStricmp(argv[1], "environment_iasig"))
return(mEnvironment && mEnvironmentEnabled);
return(false);
}
static bool cAudio_isExtensionPresent(SimObject *, S32, const char ** argv)
{
#pragma message("todo") /*
return((bool)alIsExtensionPresent((const ALubyte *)argv[1]));
*/
return false;
}
#endif

352
audio/audioMss.cc Normal file
View file

@ -0,0 +1,352 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "audio/audioMss.h"
namespace {
const char * cVoiceCodecs [] = { ".v12", ".v24", ".v29" };
const U32 cNumVoiceCodecs = sizeof(cVoiceCodecs) / sizeof(cVoiceCodecs[0]);
};
HPROVIDER EncoderStream::hProvider = 0; // ASI provider used to encode data
U32 EncoderStream::mCodecLevel = 0;
ASI_STREAM_OPEN EncoderStream::streamOpen;
ASI_STREAM_PROCESS EncoderStream::streamProcess;
ASI_STREAM_CLOSE EncoderStream::streamClose;
//--------------------------------------
// - can encode using just one method (hence the statics)
EncoderStream::EncoderStream()
{
hStream = NULL;
mStreamId = 0;
mConnection = NULL;
}
//--------------------------------------
EncoderStream::~EncoderStream()
{
closeStream();
}
//--------------------------------------
bool EncoderStream::open(U32 codecLevel)
{
if(codecLevel >= cNumVoiceCodecs)
return(false);
mCodecLevel = codecLevel;
// there can be only one
if(hProvider)
return true;
RIB_INTERFACE_ENTRY ENCODER_REQUEST[] = {
{ RIB_FUNCTION, "ASI_stream_open", (U32) &streamOpen, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_close", (U32) &streamClose, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_process", (U32) &streamProcess, RIB_NONE } };
hProvider = RIB_find_file_provider("ASI codec", "Output file types", const_cast<char*>(cVoiceCodecs[codecLevel]));
if (hProvider != NULL)
if (RIB_request(hProvider, "ASI stream", ENCODER_REQUEST) == RIB_NOERR)
{
return true;
}
Con::printf("EncoderStream::open FAILED");
close();
return false;
}
//--------------------------------------
void EncoderStream::close()
{
if (hProvider)
{
RIB_free_provider_handle(hProvider);
hProvider = NULL;
}
}
//--------------------------------------
bool EncoderStream::openStream()
{
if (!hProvider)
return false;
closeStream();
mConnection = NULL;
hStream = streamOpen((U32)this, callback_router, 0);
mQueue.setSize(8000*2*5);
if (hStream)
Con::printf("EncoderStream::open %d", mStreamId);
else
Con::printf("EncoderStream::open FAILED");
return (hStream != NULL);
}
//--------------------------------------
void EncoderStream::closeStream()
{
if (hStream)
{
streamClose(hStream);
hStream = NULL;
}
mSequence = 0;
}
bool EncoderStream::setConnection(GameConnection *con)
{
mStreamId++;
mSequence = 0;
mConnection = con;
mQueue.clear();
return true;
}
//--------------------------------------
S32 AILCALLBACK EncoderStream::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset)
{
return ((EncoderStream*)user)->callback(dest, bytesRequested, offset);
}
//--------------------------------------------------------------------------
S32 EncoderStream::callback(void *dest, S32 bytesRequested, S32 offset)
{
offset; // unused
return mQueue.dequeue((U8*)dest, bytesRequested);
}
//--------------------------------------
void EncoderStream::setBuffer(const U8 *buffer, U32 size)
{
if (size > mQueue.getFree())
{
AssertWarn(0, "EncoderStream: queue full");
return;
}
mQueue.enqueue(buffer, size);
}
//--------------------------------------
void EncoderStream::process(bool flush)
{
if (!hStream)
return;
while ( flush || (mQueue.getUsed() >= 1800) )
{
SimVoiceStreamEvent *mEvent = new SimVoiceStreamEvent(mStreamId, mSequence++, mCodecLevel);
S32 amount = streamProcess(hStream, mEvent->getData(), SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE);
mEvent->setDataSize(amount);
mConnection->postNetEvent(mEvent);
if (flush && amount < SimVoiceStreamEvent::VOICE_PACKET_DATA_SIZE)
break;
}
}
//--------------------------------------
void EncoderStream::flush()
{
process(true);
mQueue.clear();
}
//--------------------------------------------------------------------------
// Class VoiceDecoder:
//--------------------------------------------------------------------------
VoiceDecoder::VoiceDecoder()
{
hProvider = 0;
mCodecLevel = U32(-1);
}
VoiceDecoder::~VoiceDecoder()
{
close();
}
bool VoiceDecoder::open(U32 codecLevel)
{
if(codecLevel >= cNumVoiceCodecs)
return(false);
mCodecLevel = codecLevel;
if(hProvider)
return true;
RIB_INTERFACE_ENTRY DECODER_REQUEST[] = {
{ RIB_FUNCTION, "ASI_stream_attribute", (U32) &streamAttribute, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_open", (U32) &streamOpen, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_seek", (U32) &streamSeek, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_close", (U32) &streamClose, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_process", (U32) &streamProcess, RIB_NONE },
{ RIB_FUNCTION, "ASI_stream_set_preference", (U32) &streamSetPreference, RIB_NONE },
{ RIB_ATTRIBUTE, "Output sample rate", (U32) &OUTPUT_SAMPLE_RATE, RIB_NONE },
{ RIB_ATTRIBUTE, "Output sample width", (U32) &OUTPUT_BITS, RIB_NONE },
{ RIB_ATTRIBUTE, "Output channels", (U32) &OUTPUT_CHANNELS, RIB_NONE },
{ RIB_PREFERENCE, "Requested sample rate", (U32) &REQUESTED_RATE, RIB_NONE } };
hProvider = RIB_find_file_provider("ASI codec", "Input file types", cVoiceCodecs[codecLevel]);
if (hProvider != NULL)
if (RIB_request(hProvider, "ASI stream", DECODER_REQUEST) == RIB_NOERR)
return true;
Con::printf("VoiceDecoder::open FAILED");
close();
return false;
}
void VoiceDecoder::close()
{
if (hProvider)
{
AssertWarn(hProvider, "No Provider?")
RIB_free_provider_handle(hProvider);
hProvider = NULL;
}
}
//--------------------------------------------------------------------------
DecoderStream::DecoderStream()
{
hStream = NULL;
mDecoder = 0;
}
//--------------------------------------
DecoderStream::~DecoderStream()
{
close();
}
//--------------------------------------
bool DecoderStream::open()
{
if(!mDecoder)
return false;
close();
hStream = mDecoder->streamOpen((U32)this, callback_router, 0);
if (hStream)
Con::printf("DecoderStream::open");
else
{
Con::printf("DecoderStream::open FAILED");
return false;
}
mInQueue.setSize(8000*5);
mOutQueue.setSize(8000*2*5);
// request an output rate
U32 request = OUTPUT_RATE;
mDecoder->streamSetPreference(hStream, mDecoder->REQUESTED_RATE, &request);
U32 nch = mDecoder->streamAttribute(hStream, mDecoder->OUTPUT_CHANNELS);
U32 rate = mDecoder->streamAttribute(hStream, mDecoder->OUTPUT_SAMPLE_RATE);
U32 bits = mDecoder->streamAttribute(hStream, mDecoder->OUTPUT_BITS);
// make a few assumptions
if (nch != 1 || rate != OUTPUT_RATE || bits != 16)
{
close();
return false;
}
return true;
}
//--------------------------------------
void DecoderStream::close()
{
if(hStream && mDecoder)
mDecoder->streamClose(hStream);
hStream = 0;
}
void DecoderStream::setDecoder(VoiceDecoder * decoder)
{
if(mDecoder && mDecoder != decoder)
close();
mDecoder = decoder;
}
//--------------------------------------
S32 AILCALLBACK DecoderStream::callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset)
{
return ((DecoderStream*)user)->callback(dest, bytesRequested, offset);
}
//--------------------------------------------------------------------------
S32 DecoderStream::callback(void *dest, S32 bytesRequested, S32 offset)
{
offset; // unused
return mInQueue.dequeue((U8*)dest, bytesRequested);
}
//--------------------------------------
U32 DecoderStream::getBuffer(U8 **data, U32 *size)
{
*data = mOutQueue.getHead();
*size = mOutQueue.getContiguousUsed();
mOutQueue.dequeue(*size);
return *size;
}
//--------------------------------------
void DecoderStream::setBuffer(U8 *data, U32 size)
{
if (size > mInQueue.getFree())
{
AssertWarn(0, "DecoderStream: queue full");
return;
}
mInQueue.enqueue(data, size);
}
//--------------------------------------
void DecoderStream::process(bool flush)
{
if (!hStream)
return;
while ( flush || (mInQueue.getUsed() && !mOutQueue.isFull()) )
{
S32 amount = mDecoder->streamProcess(hStream, mOutQueue.getTail(), mOutQueue.getContiguousFree());
mOutQueue.enqueue(amount);
if (flush && amount == 0)
break;
}
}

128
audio/audioMss.h Normal file
View file

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOMSS_H_
#define _AUDIOMSS_H_
#ifndef _AUDIONET_H_
#include "audio/audioNet.h"
#endif
#ifndef _GAMECONNECTION_H_
#include "game/gameConnection.h"
#endif
#ifndef _MSS_H_
#include "mss.h"
#endif
#ifndef _FILESTREAM_H_
#include "core/fileStream.h"
#endif
#ifndef _BUFFERQUEUE_H_
#include "audio/bufferQueue.h"
#endif
//--------------------------------------------------------------------------
// we can only have one encoder... but multiple decoders can be installed
class EncoderStream
{
private:
static HPROVIDER hProvider; // ASI provider used to encode data
static U32 mCodecLevel;
static ASI_STREAM_OPEN streamOpen;
static ASI_STREAM_PROCESS streamProcess;
static ASI_STREAM_CLOSE streamClose;
HASISTREAM hStream;
BufferQueue mQueue;
GameConnection *mConnection;
U8 mStreamId;
U8 mSequence;
S32 callback(void *dest, S32 bytesRequested, S32 offset);
static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset);
void sendPacket(bool flush=false);
public:
EncoderStream();
~EncoderStream();
static bool open(U32 codecLevel);
static void close();
bool openStream();
void closeStream();
bool setConnection(GameConnection *con);
void setBuffer(const U8 *buffer, U32 size);
void process(bool flush=false);
void flush();
};
//--------------------------------------------------------------------------
class VoiceDecoder
{
friend class DecoderStream;
private:
HPROVIDER hProvider; // ASI provider used to decode data
ASI_STREAM_OPEN streamOpen;
ASI_STREAM_PROCESS streamProcess;
ASI_STREAM_SEEK streamSeek;
ASI_STREAM_CLOSE streamClose;
ASI_STREAM_ATTRIBUTE streamAttribute;
ASI_STREAM_SET_PREFERENCE streamSetPreference;
HATTRIB OUTPUT_SAMPLE_RATE;
HATTRIB OUTPUT_BITS;
HATTRIB OUTPUT_CHANNELS;
HATTRIB REQUESTED_RATE;
U32 mCodecLevel;
public:
VoiceDecoder();
~VoiceDecoder();
U32 getCodecLevel() {return(mCodecLevel);}
bool open(U32 codecLevel);
void close();
};
class DecoderStream
{
HASISTREAM hStream;
GameConnection *mConnection;
BufferQueue mInQueue;
BufferQueue mOutQueue;
enum { OUTPUT_RATE = 8000 };
VoiceDecoder * mDecoder;
//--------------------------------------
S32 callback(void *dest, S32 bytesRequested, S32 offset);
static S32 AILCALLBACK callback_router(U32 user, void FAR *dest, S32 bytesRequested, S32 offset);
public:
DecoderStream();
~DecoderStream();
void setDecoder(VoiceDecoder * decoder);
bool open();
void close();
void setBuffer(U8 *data, U32 size);
U32 getBuffer(U8 **data, U32 *size);
void process(bool flush=false);
};
#endif // _H_AUDIOMSS_

197
audio/audioNet.cc Normal file
View file

@ -0,0 +1,197 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "platform/platformAudio.h"
#include "audio/audioNet.h"
#include "core/bitStream.h"
#include "game/shapeBase.h"
#include "game/gameConnection.h"
#include "audio/audioCodec.h"
#define SIZE_BITS 5
#define SEQUENCE_BITS 6
// GSM:
//#define SIZE_BITS 6
//#define SEQUENCE_BITS 7
//--------------------------------------------------------------------------
SimVoiceStreamEvent::SimVoiceStreamEvent(U8 streamId, U32 seq, U8 codecId)
{
mGuaranteeType = Unguaranteed;
mSize = VOICE_PACKET_DATA_SIZE;
mData = new U8[VOICE_PACKET_DATA_SIZE+1];
mCodecId = codecId;
mStreamId = streamId & STREAM_MASK;
mSequence = seq;
mClientId = 0;
mObjectId = 0;
// NOTE: the first byte in the data is used as a lock count
// this will allow us to pass the data from object to object
// without making multiple copies of it.
if(mData)
(*mData) = 1;
}
SimVoiceStreamEvent::SimVoiceStreamEvent(const SimVoiceStreamEvent *event)
{
mGuaranteeType = Unguaranteed;
mData = event->mData;
mSize = event->mSize;
mCodecId = event->mCodecId;
mClientId = event->mClientId;
mStreamId = event->mStreamId;
mSequence = event->mSequence;
mObjectId = event->mObjectId;
// NOTE: the first byte in the data is used as a lock count
// this will allow us to pass the data from object to object
// without making multiple copies of it.
if (mData)
(*mData)++; // increment lock count
}
//--------------------------------------
SimVoiceStreamEvent::~SimVoiceStreamEvent()
{
if (mData)
{
(*mData)--; // decrement lock ount
if (*mData == 0) // if zero we are responsible for deleting the data
delete [] mData;
}
}
//--------------------------------------
void SimVoiceStreamEvent::pack(NetConnection *con, BitStream *bstream)
{
AssertFatal((1<<SIZE_BITS) >= VOICE_PACKET_DATA_SIZE, "SimVoiceStreamEvent::pack: insuffecient bits to encode size.");
AssertFatal((1<<SEQUENCE_BITS) >= mSequence, "SimVoiceStreamEvent::pack: insuffecient bits to encode sequence.");
AssertFatal((1<<2 >= AUDIO_NUM_CODECS), "SimVoiceStreamEvent::pack: blah");
bstream->writeInt(mStreamId, 5);
bstream->writeInt(mSequence, SEQUENCE_BITS);
bstream->writeInt(mCodecId, 2);
if(con->isServerConnection())
{
// client side
}
else
{
// server side
bstream->write(mClientId);
if(mSequence == 0)
bstream->writeInt(mObjectId, NetConnection::GhostIdBitSize);
}
// only the EOS packets are not VOICE_PACKET_DATA_SIZE bytes long.
if(bstream->writeFlag(mSize != VOICE_PACKET_DATA_SIZE))
bstream->writeInt(mSize, SIZE_BITS);
bstream->_write(mSize, mData+1);
}
//--------------------------------------
void SimVoiceStreamEvent::write(NetConnection *con, BitStream *bstream)
{
pack(con, bstream);
}
//--------------------------------------
void SimVoiceStreamEvent::unpack(NetConnection *con, BitStream *bstream)
{
mSize = VOICE_PACKET_DATA_SIZE;
mStreamId = bstream->readInt(5);
mSequence = bstream->readInt(SEQUENCE_BITS);
mCodecId = bstream->readInt(2);
if(con->isServerConnection())
{
// client side
bstream->read(&mClientId);
if (mSequence == 0)
mObjectId = bstream->readInt(NetConnection::GhostIdBitSize);
}
else
{
// server side
mClientId = con->getId();
if(mSequence == 0)
{
ShapeBase *base = ((GameConnection*)con)->getControlObject();
mObjectId = base ? con->getGhostIndex(base) : 0;
}
}
// not a full packet?
if(bstream->readFlag())
mSize = getMin(bstream->readInt(SIZE_BITS), VOICE_PACKET_DATA_SIZE);
// read data (skip lock byte)
bstream->_read(mSize, mData+1);
}
//--------------------------------------
void SimVoiceStreamEvent::process(NetConnection *con)
{
if (con->isServerConnection())
processClient(con);
else
processServer(con);
}
//--------------------------------------
void SimVoiceStreamEvent::processClient(NetConnection *con)
{
con;
alxReceiveVoiceStream(this);
}
//--------------------------------------
void SimVoiceStreamEvent::processServer(NetConnection *con)
{
if(con->getProtocolVersion() < MIN_PROTOCOL_VERSION)
return;
GameConnection *itr = static_cast<GameConnection*>(con->getConnectionList());
GameConnection *gc = dynamic_cast<GameConnection*>(con);
if(!gc)
return;
if(U32(gc->getVoiceEncodingLevel()) != mCodecId)
return;
while (itr != NULL)
{
if(!itr->isServerConnection() && (itr->getProtocolVersion() >= MIN_PROTOCOL_VERSION))
{
if(itr->canListen(gc) || itr->isListening(gc->getVoiceID()))
{
if (mSequence == 0)
itr->willListen(gc->getVoiceID());
if ( itr->isListening(gc->getVoiceID()) )
itr->postNetEvent( new SimVoiceStreamEvent(this) );
if (mSize < VOICE_PACKET_DATA_SIZE)
itr->stopListening(gc->getVoiceID());
}
}
itr = static_cast<GameConnection*>(itr->getNext());
}
}
IMPLEMENT_CO_NETEVENT_V1(SimVoiceStreamEvent);

65
audio/audioNet.h Normal file
View file

@ -0,0 +1,65 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIONET_H_
#define _AUDIONET_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _NETCONNECTION_H_
#include "sim/netConnection.h"
#endif
class GameConnection;
//--------------------------------------
struct SimVoiceStreamEvent: public NetEvent
{
private:
void processClient(NetConnection *);
void processServer(NetConnection *);
enum { MIN_PROTOCOL_VERSION = 34 };
enum { STREAM_MASK = 0x1f }; // 5 bits
public:
U8 *mData;
U8 mSize;
U32 mClientId;
U8 mStreamId;
U8 mSequence;
U8 mCodecId;
SimObjectId mObjectId;
enum { VOICE_PACKET_DATA_SIZE = 30 };
// // GSM:
// enum { VOICE_PACKET_DATA_SIZE = 33 };
SimVoiceStreamEvent(U8 streamId=0, U32 seq=0, U8 codecId=0);
SimVoiceStreamEvent(const SimVoiceStreamEvent *event);
~SimVoiceStreamEvent();
void pack(NetConnection *, BitStream *bstream);
void write(NetConnection *, BitStream *bstream);
void unpack(NetConnection *, BitStream *bstream);
void process(NetConnection *);
DECLARE_CONOBJECT(SimVoiceStreamEvent);
U8* getData() { return mData+1; }
U32 getSize() { return mSize; }
void setDataSize(U32 size)
{
mSize = size;
if (mSize < VOICE_PACKET_DATA_SIZE) // if end of stream make sure we notify
mGuaranteeType = Guaranteed;
}
};
#endif // _H_AUDIONET_

218
audio/audioThread.cc Normal file
View file

@ -0,0 +1,218 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioThread.h"
// AudioResourceQueue: -----------------------------------------------------
AudioResourceQueue::AudioResourceQueue()
{
mHead = mTail = 0;
}
void AudioResourceQueue::enqueue(AudioResourceEntry * entry)
{
AssertFatal(entry, "AudioResourceQueue::enqueue: invalid entry");
entry->mNext = 0;
if(!mHead)
mHead = mTail = entry;
else
{
mTail->mNext = entry;
mTail = entry;
}
}
AudioResourceEntry * AudioResourceQueue::dequeue()
{
AudioResourceEntry * entry = mHead;
if(mHead == mTail)
mHead = mTail = 0;
else
mHead = mHead->mNext;
return(entry);
}
// AudioThread: ------------------------------------------------------------
AudioThread * gAudioThread = 0;
AudioThread::AudioThread() : Thread(0, 0, false)
{
mStopSemaphore = Semaphore::createSemaphore(0);
mWakeSemaphore = Semaphore::createSemaphore();
mMutex = Mutex::createMutex();
// Now that the semaphores are created, start up the thread
start();
}
AudioThread::~AudioThread()
{
Semaphore::destroySemaphore(mStopSemaphore);
Semaphore::destroySemaphore(mWakeSemaphore);
Mutex::destroyMutex(mMutex);
}
void AudioThread::wake()
{
if(!isAlive())
return;
Semaphore::releaseSemaphore(mWakeSemaphore);
}
void AudioThread::stop()
{
if(!isAlive())
return;
Semaphore::releaseSemaphore(mStopSemaphore);
wake();
join();
}
void AudioThread::run(S32)
{
while(1)
{
Semaphore::acquireSemaphore(mWakeSemaphore);
if(Semaphore::acquireSemaphore(mStopSemaphore, false))
return;
lock();
AudioResourceEntry * entry = mLoadingQueue.mHead;
if(!entry)
{
unlock();
continue;
}
// load it in
Stream * stream = ResourceManager->openStream(entry->mResourceObj);
stream->read(entry->mResourceObj->fileSize, entry->mData);
ResourceManager->closeStream(stream);
mLoadedQueue.enqueue(mLoadingQueue.dequeue());
unlock();
}
}
//--------------------------------------------------------------------------
void AudioThread::loadResource(ResourceObject * obj, AudioBuffer * buffer)
{
AssertFatal(!buffer->mLoading, "AudioThread::loadResource: buffer already loading");
AudioResourceEntry * entry = new AudioResourceEntry();
entry->mResourceObj = obj;
entry->mBuffer = buffer;
entry->mData = dMalloc(obj->fileSize);
dMemset(entry->mData, 0, obj->fileSize);
buffer->mLoading = true;
lock();
mLoadingQueue.enqueue(entry);
unlock();
Semaphore::acquireSemaphore(mWakeSemaphore, false);
Semaphore::releaseSemaphore(mWakeSemaphore);
}
void AudioThread::setBufferPlayHandle(AudioBuffer * buffer, AUDIOHANDLE handle)
{
lock();
// search the loading list
AudioResourceEntry * entry = mLoadingQueue.mHead;
bool found = false;
while(entry && !found)
{
if(entry->mBuffer == buffer)
{
entry->mPlayHandle = handle;
found = true;
}
entry = entry->mNext;
}
// search the loaded list
if(!found)
{
entry = mLoadedQueue.mHead;
while(entry && !found)
{
if(entry->mBuffer == buffer)
{
entry->mPlayHandle = handle;
found = true;
}
entry = entry->mNext;
}
}
unlock();
}
AudioResourceEntry * AudioThread::getLoadedList()
{
lock();
AudioResourceEntry * list = mLoadedQueue.mHead;
mLoadedQueue.mHead = mLoadedQueue.mTail = 0;
unlock();
return(list);
}
// static methods: --------------------------------------------------------
void AudioThread::create()
{
if(gAudioThread)
return;
gAudioThread = new AudioThread();
}
void AudioThread::destroy()
{
if(!gAudioThread)
return;
gAudioThread->stop();
delete gAudioThread;
gAudioThread = 0;
}
void AudioThread::process()
{
if(!gAudioThread)
return;
AudioResourceEntry * entry = gAudioThread->getLoadedList();
// sync all the loaded buffers and play those marked
while(entry)
{
entry->mBuffer->mLoading = false;
if(alBufferSyncData_EXT(entry->mBuffer->malBuffer, AL_FORMAT_WAVE_EXT, entry->mData,
entry->mResourceObj->fileSize, 0))
{
if(entry->mPlayHandle != NULL_AUDIOHANDLE)
alxPlay(entry->mPlayHandle);
}
AudioResourceEntry * next = entry->mNext;
delete entry;
entry = next;
}
}

90
audio/audioThread.h Normal file
View file

@ -0,0 +1,90 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOTHREAD_H_
#define _AUDIOTHREAD_H_
#ifndef _PLATFORMTHREAD_H_
#include "Platform/platformThread.h"
#endif
#ifndef _PLATFORMSEMAPHORE_H_
#include "Platform/platformSemaphore.h"
#endif
#ifndef _PLATFORMMUTEX_H_
#include "Platform/platformMutex.h"
#endif
#ifndef _AUDIO_H_
#include "audio/audio.h"
#endif
struct AudioResourceEntry
{
ResourceObject * mResourceObj;
AudioBuffer * mBuffer;
void * mData;
AUDIOHANDLE mPlayHandle;
AudioResourceEntry * mNext;
AudioResourceEntry()
{
mResourceObj = 0;
mBuffer = 0;
mData = 0;
mPlayHandle = NULL_AUDIOHANDLE;
mNext = 0;
}
};
struct AudioResourceQueue
{
AudioResourceEntry * mHead;
AudioResourceEntry * mTail;
AudioResourceQueue();
void enqueue(AudioResourceEntry * entry);
AudioResourceEntry * dequeue();
};
class AudioThread : public Thread
{
private:
void * mStopSemaphore;
void * mWakeSemaphore;
void * mMutex;
// accessed in both threads. memory must be managed in main thread
AudioResourceQueue mLoadingQueue;
AudioResourceQueue mLoadedQueue;
public:
AudioThread();
~AudioThread();
void wake();
void stop();
void lock() { Mutex::lockMutex(mMutex); }
void unlock() { Mutex::unlockMutex(mMutex); }
void run(S32 arg);
void setBufferPlayHandle(AudioBuffer * buffer, AUDIOHANDLE handle);
void loadResource(ResourceObject * resourceObj, AudioBuffer * buffer);
AudioResourceEntry * getLoadedList();
// static methods
static void create();
static void destroy();
static void process();
};
extern AudioThread * gAudioThread;
#endif

111
audio/bufferQueue.cc Normal file
View file

@ -0,0 +1,111 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "audio/bufferQueue.h"
//--------------------------------------------------------------------------
// Class BufferQueue:
//--------------------------------------------------------------------------
BufferQueue::BufferQueue()
{
mQueue = NULL;
mSize = 0;
mHead = NULL;
mTail = NULL;
mIsFull= false;
}
BufferQueue::~BufferQueue()
{
setSize(0);
}
//--------------------------------------------------------------------------
void BufferQueue::setSize(U32 size)
{
mIsFull = false;
if (size == mSize)
{
clear();
return;
}
mSize = size;
if (mQueue)
{
delete [] mQueue;
mQueue = NULL;
}
if (mSize)
mQueue = new U8[mSize];
mTail = mQueue;
mHead = mQueue;
}
//--------------------------------------------------------------------------
void BufferQueue::enqueue(U32 size)
{
AssertFatal(size <= getContiguousFree(), "BufferQueue: enqueue overflow.");
if (size == 0)
return;
advanceTail(size);
}
void BufferQueue::enqueue(const U8* data, U32 size)
{
AssertFatal(size <= getFree(), "BufferQueue: enqueue overflow.");
if (size == 0)
return;
U32 con= getContiguousFree();
if (size <= con)
dMemcpy(mTail, data, size);
else
{
dMemcpy(mTail, data, con);
dMemcpy(mQueue, &data[con], size-con);
}
advanceTail(size);
}
//--------------------------------------------------------------------------
U32 BufferQueue::dequeue(U32 request)
{
if (request == 0)
return 0;
request = getMin(request, getContiguousUsed());
advanceHead(request);
return request;
}
U32 BufferQueue::dequeue(U8* data, U32 request)
{
if (request == 0)
return 0;
request = getMin(request, getUsed());
U32 con=getContiguousUsed();
if (con >= request)
dMemcpy(data, mHead, request);
else
{
dMemcpy(data, mHead, con);
dMemcpy(data+con, mQueue, request-con);
}
advanceHead(request);
return request;
}

106
audio/bufferQueue.h Normal file
View file

@ -0,0 +1,106 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _BUFFERQUEUE_H_
#define _BUFFERQUEUE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
class BufferQueue
{
private:
U8 *mQueue;
U8 *mHead;
U8 *mTail;
U32 mSize;
bool mIsFull;
void advanceHead(U32 n);
void advanceTail(U32 n);
public:
BufferQueue();
~BufferQueue();
void setSize(U32 size);
bool isFull() { return mIsFull; }
bool isEmpty() { return !mIsFull && mHead == mTail; }
void clear() { mHead = mTail = mQueue; mIsFull = false; }
U8* getTail() { return mIsFull ? NULL : mTail; }
U8* getHead() { return mHead; }
// inlined
U32 getFree();
U32 getUsed();
U32 getContiguousFree();
U32 getContiguousUsed();
void enqueue(U32 size);
void enqueue(const U8* data, U32 size);
U32 dequeue(U32 size);
U32 dequeue(U8* data, U32 size);
};
//--------------------------------------------------------------------------
// BufferQueue: inlined functions
//--------------------------------------------------------------------------
inline void BufferQueue::advanceHead(U32 n)
{
mHead += n; // advance head
if (mHead >= (mQueue + mSize)) // wrap around the queue
mHead -= mSize;
mIsFull = false;
}
inline void BufferQueue::advanceTail(U32 n)
{
mTail += n; // advance tail
if (mTail >= (mQueue + mSize)) // wrap around the queue
mTail -= mSize;
mIsFull = (mTail == mHead);
}
inline U32 BufferQueue::getFree()
{
if (mIsFull == false)
return (mTail < mHead) ? (mHead - mTail) : (mSize - (mTail-mHead));
else
return 0;
}
inline U32 BufferQueue::getUsed()
{
if (mIsFull == false)
return (mHead <= mTail) ? (mTail - mHead) : (mSize - (mHead-mTail));
else
return mSize;
}
inline U32 BufferQueue::getContiguousFree()
{
if (mIsFull == false)
return (mTail < mHead) ? (mHead - mTail) : (mSize - (mTail-mQueue));
else
return 0;
}
inline U32 BufferQueue::getContiguousUsed()
{
if (mIsFull == false)
return (mHead <= mTail) ? (mTail - mHead) : (mSize - (mHead-mQueue));
else
return mSize;
}
#endif // _INC_BUFFERQUEUE

View file

@ -0,0 +1,99 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#include "Collision/abstractPolyList.h"
//----------------------------------------------------------------------------
AbstractPolyList::~AbstractPolyList()
{
mInterestNormalRegistered = false;
}
static U32 PolyFace[6][4] = {
{ 3, 2, 1, 0 },
{ 7, 4, 5, 6 },
{ 0, 5, 4, 3 },
{ 6, 5, 0, 1 },
{ 7, 6, 1, 2 },
{ 4, 7, 2, 3 },
};
void AbstractPolyList::addBox(const Box3F &box)
{
Point3F pos = box.min;
F32 dx = box.max.x - box.min.x;
F32 dy = box.max.y - box.min.y;
F32 dz = box.max.z - box.min.z;
U32 base = addPoint(pos);
pos.y += dy; addPoint(pos);
pos.x += dx; addPoint(pos);
pos.y -= dy; addPoint(pos);
pos.z += dz; addPoint(pos);
pos.x -= dx; addPoint(pos);
pos.y += dy; addPoint(pos);
pos.x += dx; addPoint(pos);
for (S32 i = 0; i < 6; i++) {
begin(0,i);
S32 v1 = base + PolyFace[i][0];
S32 v2 = base + PolyFace[i][1];
S32 v3 = base + PolyFace[i][2];
S32 v4 = base + PolyFace[i][3];
vertex(v1);
vertex(v2);
vertex(v3);
vertex(v4);
plane(v1,v2,v3);
end();
}
}
bool AbstractPolyList::getMapping(MatrixF *, Box3F *)
{
// return list transform and bounds in list space...optional
return false;
}
bool AbstractPolyList::isInterestedInPlane(const PlaneF& plane)
{
if (mInterestNormalRegistered == false) {
return true;
}
else {
PlaneF xformed;
mPlaneTransformer.transform(plane, xformed);
if (mDot(xformed, mInterestNormal) >= 0.0f)
return false;
else
return true;
}
}
bool AbstractPolyList::isInterestedInPlane(const U32 index)
{
if (mInterestNormalRegistered == false) {
return true;
}
else {
const PlaneF& rPlane = getIndexedPlane(index);
if (mDot(rPlane, mInterestNormal) >= 0.0f)
return false;
else
return true;
}
}
void AbstractPolyList::setInterestNormal(const Point3F& normal)
{
mInterestNormalRegistered = true;
mInterestNormal = normal;
}

View file

@ -0,0 +1,118 @@
//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, Inc.
//-----------------------------------------------------------------------------
#ifndef _ABSTRACTPOLYLIST_H_
#define _ABSTRACTPOLYLIST_H_
#ifndef _MMATH_H_
#include "Math/mMath.h"
#endif
#ifndef _MPLANETRANSFORMER_H_
#include "Math/mPlaneTransformer.h"
#endif
#ifndef _TVECTOR_H_
#include "Core/tVector.h"
#endif
class SceneObject;
//----------------------------------------------------------------------------
class AbstractPolyList
{
protected:
// User set state
SceneObject* mCurrObject;
MatrixF mBaseMatrix;
MatrixF mMatrix;
Point3F mScale;
PlaneTransformer mPlaneTransformer;
bool mInterestNormalRegistered;
Point3F mInterestNormal;
public:
AbstractPolyList();
virtual ~AbstractPolyList();
// Common functionality
void setBaseTransform(const MatrixF& mat);
void setTransform(const MatrixF* mat, const Point3F& scale);
void getTransform(MatrixF* mat, Point3F * scale);
void setObject(SceneObject*);
void addBox(const Box3F &box);
void doConstruct();
// Interface functions
virtual bool isEmpty() const = 0;
virtual U32 addPoint(const Point3F& p) = 0;
virtual U32 addPlane(const PlaneF& plane) = 0;
virtual void begin(U32 material,U32 surfaceKey) = 0;
virtual void plane(U32 v1,U32 v2,U32 v3) = 0;
virtual void plane(const PlaneF& p) = 0;
virtual void plane(const U32 index) = 0;
virtual void vertex(U32 vi) = 0;
virtual void end() = 0;
virtual bool getMapping(MatrixF *, Box3F *);
// Interest functionality
void setInterestNormal(const Point3F& /*normal*/);
void clearInterestNormal() { mInterestNormalRegistered = false; }
virtual bool isInterestedInPlane(const PlaneF& /*plane*/);
virtual bool isInterestedInPlane(const U32 index);
protected:
virtual const PlaneF& getIndexedPlane(const U32 index) = 0;
};
inline AbstractPolyList::AbstractPolyList()
{
doConstruct();
}
inline void AbstractPolyList::doConstruct()
{
mCurrObject = NULL;
mBaseMatrix.identity();
mMatrix.identity();
mScale.set(1, 1, 1);
mPlaneTransformer.setIdentity();
mInterestNormalRegistered = false;
}
inline void AbstractPolyList::setBaseTransform(const MatrixF& mat)
{
mBaseMatrix = mat;
}
inline void AbstractPolyList::setTransform(const MatrixF* mat, const Point3F& scale)
{
mMatrix = mBaseMatrix;
mMatrix.mul(*mat);
mScale = scale;
mPlaneTransformer.set(mMatrix, mScale);
}
inline void AbstractPolyList::getTransform(MatrixF* mat, Point3F * scale)
{
*mat = mMatrix;
*scale = mScale;
}
inline void AbstractPolyList::setObject(SceneObject* obj)
{
mCurrObject = obj;
}
#endif

Some files were not shown because too many files have changed in this diff Show more