mirror of
https://github.com/tribes2/engine.git
synced 2026-03-07 06:20:30 +00:00
t2 engine svn checkout
This commit is contained in:
commit
ff569bd2ae
988 changed files with 394180 additions and 0 deletions
3133
Engine.dsp
Normal file
3133
Engine.dsp
Normal file
File diff suppressed because it is too large
Load diff
34
Engine.plg
Normal file
34
Engine.plg
Normal 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
34
Makefile
Normal 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
2975
ai/aiConnection.cc
Normal file
File diff suppressed because it is too large
Load diff
403
ai/aiConnection.h
Normal file
403
ai/aiConnection.h
Normal 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
766
ai/aiConsole.cc
Normal 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
246
ai/aiDebug.cc
Normal 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
341
ai/aiMath.cc
Normal 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
349
ai/aiMath.h
Normal 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
485
ai/aiNavJetting.cc
Normal 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
79
ai/aiNavJetting.h
Normal 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
28
ai/aiNavStep.cc
Normal 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
32
ai/aiNavStep.h
Normal 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
183
ai/aiObjective.cc
Normal 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
105
ai/aiObjective.h
Normal 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
446
ai/aiPlayer.cc
Normal 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
92
ai/aiPlayer.h
Normal 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
805
ai/aiStep.cc
Normal 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
143
ai/aiStep.h
Normal 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
235
ai/aiTask.cc
Normal 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
55
ai/aiTask.h
Normal 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
1528
ai/graph.cc
Normal file
File diff suppressed because it is too large
Load diff
341
ai/graph.h
Normal file
341
ai/graph.h
Normal 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
220
ai/graphBSP.h
Normal 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
388
ai/graphBase.cc
Normal 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
228
ai/graphBase.h
Normal 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
974
ai/graphBridge.cc
Normal 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
96
ai/graphBridge.h
Normal 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
208
ai/graphBuildLOS.cc
Normal 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
660
ai/graphConjoin.cc
Normal 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
1283
ai/graphData.cc
Normal file
File diff suppressed because it is too large
Load diff
531
ai/graphData.h
Normal file
531
ai/graphData.h
Normal 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
117
ai/graphDebug.cc
Normal 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
102
ai/graphDefines.h
Normal 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
377
ai/graphDijkstra.cc
Normal 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
562
ai/graphFind.cc
Normal 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(¢er);
|
||||
|
||||
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
2392
ai/graphFloorPlan.cc
Normal file
File diff suppressed because it is too large
Load diff
231
ai/graphFloorPlan.h
Normal file
231
ai/graphFloorPlan.h
Normal 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 ¢er);
|
||||
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
761
ai/graphFloorRender.cc
Normal 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
162
ai/graphForceField.cc
Normal 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
45
ai/graphForceField.h
Normal 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
212
ai/graphGenUtils.cc
Normal 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
304
ai/graphGenUtils.h
Normal 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
1067
ai/graphGroundPlan.cc
Normal file
File diff suppressed because it is too large
Load diff
185
ai/graphGroundPlan.h
Normal file
185
ai/graphGroundPlan.h
Normal 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
93
ai/graphGroundVisit.h
Normal 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
267
ai/graphIndoors.cc
Normal 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
95
ai/graphIsland.cc
Normal 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
485
ai/graphJetting.cc
Normal 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
69
ai/graphJetting.h
Normal 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
225
ai/graphLOS.cc
Normal 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
32
ai/graphLOS.h
Normal 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
529
ai/graphLocate.cc
Normal 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
76
ai/graphLocate.h
Normal 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
363
ai/graphMake.cc
Normal 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
341
ai/graphMath.cc
Normal 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
349
ai/graphMath.h
Normal 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
93
ai/graphNodes.h
Normal 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
530
ai/graphOutdoors.cc
Normal 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
127
ai/graphPartition.cc
Normal 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
56
ai/graphPartition.h
Normal 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
831
ai/graphPath.cc
Normal 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
133
ai/graphPath.h
Normal 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
148
ai/graphQueries.cc
Normal 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
420
ai/graphRender.cc
Normal 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
331
ai/graphSearchLOS.cc
Normal 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
170
ai/graphSearches.h
Normal 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
270
ai/graphSmooth.cc
Normal 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
372
ai/graphSpawn.cc
Normal 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
245
ai/graphThreats.cc
Normal 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, ¢er);
|
||||
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
56
ai/graphThreats.h
Normal 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
318
ai/graphTransient.cc
Normal 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
49
ai/graphTransient.h
Normal 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
387
ai/graphVolume.cc
Normal 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
195
ai/oVector.h
Normal 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
415
ai/tBinHeap.h
Normal 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
23
ai/texturePreload.h
Normal 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
3372
audio/audio.cc
Normal file
File diff suppressed because it is too large
Load diff
21
audio/audio.h
Normal file
21
audio/audio.h
Normal 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
119
audio/audioBuffer.cc
Normal 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
47
audio/audioBuffer.h
Normal 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
311
audio/audioCodec.cc
Normal 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
147
audio/audioCodec.h
Normal 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
195
audio/audioCodecGSM.cc
Normal 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
52
audio/audioCodecGSM.h
Normal 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
276
audio/audioCodecMiles.cc
Normal 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
87
audio/audioCodecMiles.h
Normal 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
512
audio/audioDataBlock.cc
Normal 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
132
audio/audioDataBlock.h
Normal 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
773
audio/audioFunctions.cc
Normal 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
352
audio/audioMss.cc
Normal 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
128
audio/audioMss.h
Normal 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
197
audio/audioNet.cc
Normal 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
65
audio/audioNet.h
Normal 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
218
audio/audioThread.cc
Normal 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
90
audio/audioThread.h
Normal 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
111
audio/bufferQueue.cc
Normal 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
106
audio/bufferQueue.h
Normal 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
|
||||
99
collision/abstractPolyList.cc
Normal file
99
collision/abstractPolyList.cc
Normal 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;
|
||||
}
|
||||
|
||||
118
collision/abstractPolyList.h
Normal file
118
collision/abstractPolyList.h
Normal 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
Loading…
Add table
Add a link
Reference in a new issue