/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef OBJECT_FLOCKER_H
#define OBJECT_FLOCKER_H
#include "pnModifier/plSingleModifier.h"
class hsStream;
class hsResMgr;
class plRandom;
class pfObjectFlocker;
// Database tokens for our prox database
template
class pfTokenForProximityDatabase
{
public:
virtual ~pfTokenForProximityDatabase() {}
// call this when your position changes
virtual void UpdateWithNewPosition(const hsPoint3 &newPos) = 0;
// find all close-by objects (determined by center and radius)
virtual void FindNeighbors(const hsPoint3 ¢er, const float radius, std::vector &results) = 0;
};
// A basic prox database (might need to be optimized in the future)
template
class pfBasicProximityDatabase
{
public:
class tokenType;
typedef std::vector tokenVector;
typedef typename tokenVector::const_iterator tokenIterator;
// "token" to represent objects stored in the database
class tokenType: public pfTokenForProximityDatabase
{
private:
tokenVector& fTokens;
T fParent;
hsPoint3 fPosition;
public:
// constructor
tokenType(T parentObject, tokenVector& tokens) : fParent(parentObject), fTokens(tokens)
{
fTokens.push_back(this);
}
// destructor
virtual ~tokenType()
{
// remove this token from the database's vector
fTokens.erase(std::find(fTokens.begin(), fTokens.end(), this));
}
// call this when your position changes
void UpdateWithNewPosition(const hsPoint3 &newPosition) {fPosition = newPosition;}
// find all close-by objects (determined by center and radius)
void FindNeighbors(const hsPoint3 ¢er, const float radius, std::vector & results)
{
// take the slow way, loop and check every one
const float radiusSquared = radius * radius;
for (tokenIterator i = fTokens.begin(); i != fTokens.end(); i++)
{
const hsVector3 offset(¢er, &((**i).fPosition));
const float distanceSquared = offset.MagnitudeSquared();
// push onto result vector when within given radius
if (distanceSquared < radiusSquared)
results.push_back((**i).fParent);
}
}
};
private:
// STL vector containing all tokens in database
tokenVector fGroup;
public:
// constructor
pfBasicProximityDatabase(void) {}
// destructor
virtual ~pfBasicProximityDatabase() {}
// allocate a token to represent a given client object in this database
tokenType *MakeToken(T parentObject) {return TRACKED_NEW tokenType(parentObject, fGroup);}
// return the number of tokens currently in the database
int Size(void) {return group.size();}
};
// A basic vehicle class that handles accelleration, braking, and turning
class pfVehicle
{
private:
hsPoint3 fPos; // position in meters
hsPoint3 fLastPos; // the last position we had
hsPoint3 fSmoothedPosition;
hsVector3 fVel; // velocity in meters/second
hsVector3 fSmoothedAcceleration;
hsVector3 fForward; // forward vector (unit length)
hsVector3 fLastForward; // the last forward vector we had
hsVector3 fSide; // side vector (unit length)
hsVector3 fUp; // up vector (unit length)
float fSpeed; // speed (length of velocity vector)
float fMass; // mass of the object (defaults to 1)
float fMaxForce; // the maximum steering force that can be applied
float fMaxSpeed; // the maximum speed of this vehicle
float fCurvature;
float fSmoothedCurvature;
float fRadius;
// measure the path curvature (1/turning radius), maintain smoothed version
void IMeasurePathCurvature(const float elapsedTime);
public:
pfVehicle() {Reset();}
virtual ~pfVehicle() {}
void Reset();
// get/set attributes
float Mass() const {return fMass;}
float SetMass(float m) {return fMass = m;}
hsVector3 Forward() const {return fForward;}
hsVector3 SetForward(hsVector3 forward) {return fForward = forward;}
hsVector3 Side() const {return fSide;}
hsVector3 SetSide(hsVector3 side) {return fSide = side;}
hsVector3 Up() const {return fUp;}
hsVector3 SetUp(hsVector3 up) {return fUp = up;}
hsPoint3 Position() const {return fPos;}
hsPoint3 SetPosition(hsPoint3 pos) {return fPos = pos;}
hsVector3 Velocity() const {return Forward() * fSpeed;}
float Speed() const {return fSpeed;}
float SetSpeed(float speed) {return fSpeed = speed;}
float MaxForce() const {return fMaxForce;}
float SetMaxForce(float maxForce) {return fMaxForce = maxForce;}
float MaxSpeed() const {return fMaxSpeed;}
float SetMaxSpeed(float maxSpeed) {return fMaxSpeed = maxSpeed;}
float Curvature() const {return fCurvature;}
float SmoothedCurvature() {return fSmoothedCurvature;}
float ResetSmoothedCurvature(float value = 0);
hsVector3 SmoothedAcceleration() {return fSmoothedAcceleration;}
hsVector3 ResetSmoothedAcceleration(const hsVector3 &value = hsVector3(0,0,0));
hsPoint3 SmoothedPosition() {return fSmoothedPosition;}
hsPoint3 ResetSmoothedPosition(const hsPoint3 &value = hsPoint3(0,0,0));
float Radius() const {return fRadius;}
float SetRadius(float radius) {return fRadius = radius;}
// Basic geometry functions
// Reset local space to identity
void ResetLocalSpace();
// Set the side vector to a normalized cross product of forward and up
void SetUnitSideFromForwardAndUp();
// Regenerate orthonormal basis vectors given a new forward vector (unit length)
void RegenerateOrthonormalBasisUF(const hsVector3 &newUnitForward);
// If the new forward is NOT known to have unit length
void RegenerateOrthonormalBasis(const hsVector3 &newForward)
{hsVector3 temp = newForward; temp.Normalize(); RegenerateOrthonormalBasisUF(temp);}
// For supplying both a new forward, and a new up
void RegenerateOrthonormalBasis(const hsVector3 &newForward, const hsVector3 &newUp)
{fUp = newUp; RegenerateOrthonormalBasis(newForward);}
// Keep forward parallel to velocity, change up as little as possible
virtual void RegenerateLocalSpace(const hsVector3 &newVelocity, const float elapsedTime);
// Keep forward parallel to velocity, but "bank" the up vector
void RegenerateLocalSpaceForBanking(const hsVector3 &newVelocity, const float elapsedTime);
// Vehicle physics functions
// apply a steering force to our momentum and adjust our
// orientation to match our velocity vector
void ApplySteeringForce(const hsVector3 &force, const float deltaTime);
// adjust the steering force passed to ApplySteeringForce (so sub-classes can refine)
// by default, we won't allow backward-facing steering at a low speed
virtual hsVector3 AdjustRawSteeringForce(const hsVector3 &force, const float deltaTime);
// apply a braking force
void ApplyBrakingForce(const float rate, const float deltaTime);
// predict the position of the vehicle (assumes constant velocity)
hsPoint3 PredictFuturePosition(const float predictionTime);
};
// A goal object, basically keeps track of a scene object so we can get velocity from it
class pfBoidGoal
{
private:
hsPoint3 fLastPos;
hsPoint3 fCurPos;
hsVector3 fForward;
float fSpeed; // in meters/sec
hsBool fHasLastPos; // does the last position make sense?
public:
pfBoidGoal();
~pfBoidGoal() {}
void Update(plSceneObject *goal, float deltaTime);
hsPoint3 Position() const {return fCurPos;}
float Speed() const {return fSpeed;}
hsVector3 Forward() const {return fForward;}
hsPoint3 PredictFuturePosition(const float predictionTime);
};
typedef pfTokenForProximityDatabase pfProximityToken;
typedef pfBasicProximityDatabase pfProximityDatabase;
// The actual "flocking following" (not really a boid, but whatever)
class pfBoid: public pfVehicle
{
private:
plKey fObjKey;
float fWanderSide;
float fWanderUp;
float fGoalWeight;
float fRandomWeight;
float fSeparationRadius;
float fSeparationAngle;
float fSeparationWeight;
float fCohesionRadius;
float fCohesionAngle;
float fCohesionWeight;
pfProximityToken* fProximityToken;
std::vector fNeighbors;
// Set our flocking settings to default
void IFlockDefaults();
// Setup our prox database token
void ISetupToken(pfProximityDatabase &pd);
// Are we in the neighborhood of another boid?
hsBool IInBoidNeighborhood(const pfVehicle &other, const float minDistance, const float maxDistance, const float cosMaxAngle);
// Wander steering
hsVector3 ISteerForWander(float timeDelta);
// Seek the target point
hsVector3 ISteerForSeek(const hsPoint3 &target);
// Steer the boid toward our goal
hsVector3 ISteerToGoal(pfBoidGoal &goal, float maxPredictionTime);
// Steer to keep separation
hsVector3 ISteerForSeparation(const float maxDistance, const float cosMaxAngle, const std::vector &flock);
// Steer to keep the flock together
hsVector3 ISteerForCohesion(const float maxDistance, const float cosMaxAngle, const std::vector &flock);
public:
pfObjectFlocker *fFlockerPtr;
pfBoid(pfProximityDatabase &pd, pfObjectFlocker *flocker, plKey &key);
pfBoid(pfProximityDatabase &pd, pfObjectFlocker *flocker, plKey &key, hsPoint3 &pos);
pfBoid(pfProximityDatabase &pd, pfObjectFlocker *flocker, plKey &key, hsPoint3 &pos, float speed, hsVector3 &forward, hsVector3 &side, hsVector3 &up);
virtual ~pfBoid();
// Get/set functions
float GoalWeight() const {return fGoalWeight;}
float SetGoalWeight(float goalWeight) {return fGoalWeight = goalWeight;}
float WanderWeight() const {return fRandomWeight;}
float SetWanderWeight(float wanderWeight) {return fRandomWeight = wanderWeight;}
float SeparationWeight() const {return fSeparationWeight;}
float SetSeparationWeight(float weight) {return fSeparationWeight = weight;}
float SeparationRadius() const {return fSeparationRadius;}
float SetSeparationRadius(float radius) {return fSeparationRadius = radius;}
float CohesionWeight() const {return fCohesionWeight;}
float SetCohesionWeight(float weight) {return fCohesionWeight = weight;}
float CohesionRadius() const {return fCohesionRadius;}
float SetCohesionRadius(float radius) {return fCohesionRadius = radius;}
// Update the boid's data based on the goal and time delta
void Update(pfBoidGoal &goal, float deltaTime);
plKey &GetKey() {return fObjKey;}
// We're redirecting this to the "banking" function
virtual void RegenerateLocalSpace(const hsVector3 &newVelocity, const float elapsedTime);
};
class pfFlock
{
private:
std::vector fBoids;
pfBoidGoal fBoidGoal;
pfProximityDatabase *fDatabase;
// global values so when we add a boid we can set it's parameters
float fGoalWeight, fRandomWeight;
float fSeparationWeight, fSeparationRadius;
float fCohesionWeight, fCohesionRadius;
float fMaxForce; // max steering force
float fMaxSpeed, fMinSpeed;
public:
pfFlock();
~pfFlock();
// Get/set functions (affect the whole flock, and any new boids added)
float GoalWeight() const {return fGoalWeight;}
void SetGoalWeight(float goalWeight);
float WanderWeight() const {return fRandomWeight;}
void SetWanderWeight(float wanderWeight);
float SeparationWeight() const {return fSeparationWeight;}
void SetSeparationWeight(float weight);
float SeparationRadius() const {return fSeparationRadius;}
void SetSeparationRadius(float radius);
float CohesionWeight() const {return fCohesionWeight;}
void SetCohesionWeight(float weight);
float CohesionRadius() const {return fCohesionRadius;}
void SetCohesionRadius(float radius);
float MaxForce() const {return fMaxForce;}
void SetMaxForce(float force);
float MaxSpeed() const {return fMaxSpeed;}
void SetMaxSpeed(float speed);
float MinSpeed() const {return fMinSpeed;}
void SetMinSpeed(float minSpeed);
// setup/run functions
void AddBoid(pfObjectFlocker *flocker, plKey &key, hsPoint3 &pos);
void Update(plSceneObject *goal, float deltaTime);
pfBoid *GetBoid(int i);
friend class pfObjectFlocker;
};
class pfObjectFlocker : public plSingleModifier
{
public:
pfObjectFlocker();
~pfObjectFlocker();
CLASSNAME_REGISTER( pfObjectFlocker );
GETINTERFACE_ANY( pfObjectFlocker, plSingleModifier );
virtual void SetTarget(plSceneObject* so);
virtual hsBool MsgReceive(plMessage* msg);
virtual void Read(hsStream* stream, hsResMgr* mgr);
virtual void Write(hsStream* stream, hsResMgr* mgr);
void SetNumBoids(UInt8 val);
void SetBoidKey(plKey key) { fBoidKey = key; }
float GoalWeight() const {return fFlock.GoalWeight();}
void SetGoalWeight(float goalWeight) {fFlock.SetGoalWeight(goalWeight);}
float WanderWeight() const {return fFlock.WanderWeight();}
void SetWanderWeight(float wanderWeight) {fFlock.SetWanderWeight(wanderWeight);}
float SeparationWeight() const {return fFlock.SeparationWeight();}
void SetSeparationWeight(float weight) {fFlock.SetSeparationWeight(weight);}
float SeparationRadius() const {return fFlock.SeparationRadius();}
void SetSeparationRadius(float radius) {fFlock.SetSeparationRadius(radius);}
float CohesionWeight() const {return fFlock.CohesionWeight();}
void SetCohesionWeight(float weight) {fFlock.SetCohesionWeight(weight);}
float CohesionRadius() const {return fFlock.CohesionRadius();}
void SetCohesionRadius(float radius) {fFlock.SetCohesionRadius(radius);}
float MaxForce() const {return fFlock.MaxForce();}
void SetMaxForce(float force) {fFlock.SetMaxForce(force);}
float MaxSpeed() const {return fFlock.MaxSpeed();}
void SetMaxSpeed(float speed) {fFlock.SetMaxSpeed(speed);}
float MinSpeed() const {return fFlock.MinSpeed();}
void SetMinSpeed(float minSpeed) {fFlock.SetMinSpeed(minSpeed);}
hsBool RandomizeAnimStart() const {return fRandomizeAnimationStart;}
void SetRandomizeAnimStart(hsBool val) {fRandomizeAnimationStart = val;}
hsBool UseTargetRotation() const {return fUseTargetRotation;}
void SetUseTargetRotation(hsBool val) {fUseTargetRotation = val;}
protected:
const static int fFileVersion; // so we don't have to update the global version number when we change
pfFlock fFlock;
int fNumBoids;
plKey fBoidKey;
hsBool fUseTargetRotation;
hsBool fRandomizeAnimationStart;
virtual hsBool IEval(double secs, hsScalar del, UInt32 dirty);
};
#endif