/*==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 .
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 fGroup.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