Steeriously  0.1
 All Classes Namespaces Functions Variables Typedefs Enumerations Pages
Steeriously.hpp
1 #ifndef STEERIOUSLY_HPP
2 #define STEERIOUSLY_HPP
3 
36 #include <vector>
37 #include <memory>
38 #include <iostream>
39 
40 #include <steeriously/Agent.hpp>
41 #include <steeriously/BehaviorHelpers.hpp>
42 #include <steeriously/GeometryHelpers.hpp>
43 #include <steeriously/Path.hpp>
44 #include <steeriously/Transformations.hpp>
45 #include <steeriously/Utilities.hpp>
46 #include <steeriously/Vector2.hpp>
47 #include <steeriously/VectorMath.hpp>
48 #include <steeriously/Wall.hpp>
49 
66 namespace steer //begin steeriously namespace
67 {
81  template<class T, class N>
82  steer::Vector2 calculateTarget(const T& agent, const N& other)
83  {
84  steer::Vector2 to = other->getPosition() - agent->getPosition();
85 
86  //constrains processing to those cases where
87  //the other agent is within range
88  if (VectorMath::lengthSquared(to) > agent->getThreatRange() * agent->getThreatRange())
89  return steer::Vector2(0.0, 0.0);
90 
91  //find the time in the future to extrapolate to
92  //as a function of the distance between the agents
93  //and their speeds
94  float extrapolateTime = VectorMath::length(to) / (agent->getMaxSpeed() + other->getSpeed());
95 
96  //target acquired!
97  steer::Vector2 target = steer::Vector2(other->getPosition() + other->getVelocity() * extrapolateTime);
98  return target;
99  }
100 
109  template<class T, class N, class P>
110  steer::Vector2 calculateTarget(const T& agent, const N& otherA, const P& otherB)
111  {
112  //find the midpoint between the two other agents
113  steer::Vector2 mid = (otherA->getPosition() + otherB->getPosition()) / 2.f;
114 
115  //find the time in the future to extrapolate to
116  //as a function of the distance between the agent
117  //and the midpoint in terms of the max speed
118  float extrapolateTime = VectorMath::distance(agent->getPosition(), mid) / agent->getMaxSpeed();
119 
120  //extrapolate position based on time in the future
121  //assuming the other agents are moving in a straight line
122  steer::Vector2 posA = otherA->getPosition() + otherA->getVelocity() * extrapolateTime;
123  steer::Vector2 posB = otherB->getPosition() + otherB->getVelocity() * extrapolateTime;
124 
125  //midpoint found - voila!
126  mid = (posA + posB) / 2.f;
127 
128  return mid;
129  }
130 
137  template <class T>
138  steer::Vector2 Seek(const T& agent)
139  {
140  steer::Vector2 velocity = { 0.0, 0.0 };
141  if (agent != nullptr)
142  {
143  velocity = VectorMath::normalize(agent->getTarget() - agent->getPosition()) * agent->getMaxSpeed();
144  return (velocity - agent->getVelocity());
145  }
146  else
147  return velocity;
148  };
149 
156  template <class T>
157  steer::Vector2 Seek(const T& agent, steer::Vector2 target)
158  {
159  steer::Vector2 velocity = { 0.0, 0.0 };
160 
161  velocity = VectorMath::normalize(target - agent->getPosition()) * agent->getMaxSpeed();
162 
163  return (velocity - agent->getVelocity());
164  };
165 
173  template<class T, class conT>
174  steer::Vector2 Alignment(const T& agent, const conT& neighbors)
175  {
176  steer::Vector2 avg = steer::Vector2(0.0, 0.0);
177  int count = 0;
178 
179  if (agent != nullptr)
180  {
181  for (auto& i : neighbors)
182  {
183  //exclude agent of interest, make sure
184  //neighboring agents are tagged in the group
185  if (i != nullptr && i != agent && i->taggedInGroup())
186  {
187  //summing the heading vectors of
188  //agents tagged in the group
189  avg += i->getHeading();
190 
191  ++count;
192  }
193  }
194 
195  //only get avg if there are agents around
196  if (count > 0)
197  {
198  avg /= (float)count;
199 
200  avg -= agent->getHeading();
201  }
202  }
203 
204  return avg;
205  }
206 
214  template <class T, class conT>
215  steer::Vector2 Separation(const T& agent, const conT& neighbors)
216  {
217  steer::Vector2 force = steer::Vector2(0.0, 0.0);
218  steer::Vector2 toTarget = steer::Vector2(0.0, 0.0);
219  steer::Vector2 normal = steer::Vector2(0.0, 0.0);
220  float length = 0;
221 
222  if (agent != nullptr)
223  {
224  for (auto& i : neighbors)
225  {
226  //exclude agent of interest and make
227  //sure neighboring agents are tagged in the group
228  if (i != nullptr && i != agent && i->taggedInGroup())
229  {
230  toTarget = agent->getPosition() - i->getPosition();
231 
232  normal = VectorMath::normalize(toTarget);
233  length = VectorMath::length(toTarget);
234 
235  //calculate a force as a function
236  //of its distance from each neighboring agent
237  force += normal / length;
238  }
239  }
240  }
241 
242  return force;
243  }
244 
252  template <class T, class conT>
253  steer::Vector2 Cohesion(const T& agent, const conT& neighbors)
254  {
255  steer::Vector2 centerOfMass = steer::Vector2(0.0, 0.0);
256  steer::Vector2 force = steer::Vector2(0.0, 0.0);
257 
258  int count = 0;
259 
260  if (agent != nullptr)
261  {
262  for (auto& i : neighbors)
263  {
264  //exclude agent of interest and make sure
265  //nighboring agents are tagged in the group
266  if (i != nullptr && i != agent && i->taggedInGroup())
267  {
268  //sum their positions
269  centerOfMass += i->getPosition();
270 
271  ++count;
272  }
273  }
274 
275  if (count > 0)
276  {
277  //compute the center of mass
278  centerOfMass /= (float)count;
279 
280  //calculate the force with Seek(T& agent, steer::Vector2 target) overload
281  //internally, this does the same thing without
282  //setting m_target - which is undesirable
283  //when tuning flocking behavior
284  force = Seek(agent, centerOfMass);
285  }
286 
287  //normalize due to the fact that
288  //cohesion generally factors in
289  //higher than separation and alignment
290  force = VectorMath::normalize(force);
291  }
292 
293  return force;
294  };
295 
303  template<class T>
304  steer::Vector2 Arrive(const T& agent, Uint32 deceleration)
305  {
306  steer::Vector2 target = agent->getTarget() - agent->getPosition();
307 
308  //calculate the distance to the target
309  float distance = VectorMath::length(target);
310 
311  //bail if on target
312  if (distance > 0)
313  {
314  //speed is relative to distance and deceleration factors
315  float speed = distance / ((float)deceleration * agent->getDecelerationTweaker());
316 
317  //cap speed at agent's max
318  speed = std::min(speed, agent->getMaxSpeed());
319 
320  //voila!
321  steer::Vector2 velocity = target * speed / distance;
322 
323  return (velocity - agent->getVelocity());
324  }
325 
326  //nothing to do - stay the course!
327  return steer::Vector2(0.0, 0.0);
328  }
329 
337  template<class T, class N>
338  steer::Vector2 Pursuit(const T& agent, const N& evader)
339  {
340  steer::Vector2 to = evader->getPosition() - agent->getPosition();
341 
342  float heading = VectorMath::dotProduct(agent->getHeading(), evader->getHeading());
343 
344  //evading agent is ahead, so seek to it
345  if ((VectorMath::dotProduct(to, agent->getHeading()) > 0) && (heading < -0.95))//acos(0.95)=18 degs
346  {
347  agent->setTarget(evader->getPosition());
348  return Seek<T>(agent);
349  }
350 
351  //evading agent is not ahead
352  //...extrapolate its future position
353  float extrapolationTime = VectorMath::length(to) / (agent->getMaxSpeed() + evader->getSpeed());
354 
355  //seek...
356  agent->setTarget(evader->getPosition() + evader->getVelocity() * extrapolationTime);
357  return Seek<T>(agent);
358  }
359 
368  template<class T, class N>
369  steer::Vector2 OffsetPursuit(const T& agent, const N& leader, const steer::BehaviorParameters& parameters)
370  {
371  //thisAgent->setTarget(calculateTarget(thisAgent, leader));
372  agent->setTarget(leader->getPosition());
373 
374  steer::Vector2 to = agent->getTarget() - agent->getPosition();
375 
376  //distance to target
377  float distance = VectorMath::length(to);
378 
379  if (distance > 0)
380  {
381  //speed is relative to distance and deceleration factors
382  float speed = distance / ((float)parameters.deceleration * agent->getDecelerationTweaker());
383 
384  //cap velocity at max
385  speed = std::min(speed, agent->getMaxSpeed());
386 
387  //seek...
388  steer::Vector2 velocity = to * speed / distance;
389 
390  return (velocity - agent->getVelocity());
391  }
392 
393  return steer::Vector2(0.0, 0.0);
394  }
395 
402  template<class T>
403  steer::Vector2 Flee(const T& agent)
404  {
405  steer::Vector2 velocity = VectorMath::normalize(agent->getPosition() - agent->getTarget()) * agent->getMaxSpeed();
406  if (VectorMath::distanceSquared(agent->getPosition(), agent->getTarget()) > agent->getThreatRange()*agent->getThreatRange())
407  return steer::Vector2(0.0, 0.0);
408  else
409  return (velocity - agent->getVelocity());
410  }
411 
419  template<class T, class N>
420  steer::Vector2 Evade(const T& agent, const N& other)
421  {
422  agent->setTarget(calculateTarget<T,N>(agent, other));
423 
424  steer::Vector2 velocity = VectorMath::normalize(agent->getPosition() - agent->getTarget()) * agent->getMaxSpeed();
425  if (VectorMath::distanceSquared(agent->getPosition(), agent->getTarget()) > agent->getThreatRange()*agent->getThreatRange())
426  return steer::Vector2(0.0, 0.0);
427  else
428  return (velocity - agent->getVelocity());
429  }
430 
439  inline steer::Vector2 findPosition(const steer::Vector2& goalPosition, const float radius, const steer::Vector2& avoidPosition, float distanceBuffer)
440  {
441  //calculate the desired buffer between the goal position
442  //and the radius of the goal area
443  float away = radius + distanceBuffer;
444 
445  //calculate the heading toward the avoidance position
446  steer::Vector2 to = VectorMath::normalize(goalPosition - avoidPosition);
447 
448  //calculate the position relative to the goal and
449  //scaled according to the direction of the avoidance
450  //position and the distance *from* the goal
451  return (to * away) + goalPosition;
452  }
453 
463  template<class T, class N, class conT>
464  steer::Vector2 Hide(const T& agent, const N& other, const conT& obstacles, const steer::BehaviorParameters& parameters)
465  {
466  float closest = steer::MaxFloat;
467  steer::Vector2 best;
468 
469  for (auto& ob : obstacles)
470  {
471  //find a hiding spot, given each obstacle
472  steer::Vector2 spot = findPosition(ob->getPosition(), ob->getRadius(), other->getPosition(), agent->getDistanceBuffer());
473 
474  //determine the closest hiding spot
475  float distance = steer::VectorMath::distanceSquared(spot, agent->getPosition());
476 
477  if (distance < closest)
478  {
479  closest = distance;
480 
481  best = spot;
482  }
483 
484  }
485 
486  //no hiding spots...?
487  //...evade the other agent
488  if (closest == steer::MaxFloat)
489  {
490  return Evade<T, N>(agent, other);
491  }
492 
493  //otherwise, arrive at the hiding spot
494  agent->setTarget(best);
495  return Arrive<T>(agent, parameters.deceleration);
496  }
497 
507  template<class T, class N, class P>
508  steer::Vector2 Interpose(const T& agent, const N& otherA, const P& otherB, const steer::BehaviorParameters& parameters)
509  {
510  //calculate target given two agents
511  agent->setTarget(calculateTarget<T,N,P>(agent, otherA, otherB));
512 
513  steer::Vector2 to = agent->getTarget() - agent->getPosition();
514 
515  float distance = VectorMath::length(to);
516 
517  if (distance > 0)
518  {
519  float speed = distance / ((float)parameters.deceleration * agent->getDecelerationTweaker());
520 
521  //cap velocity at the max
522  speed = std::min(speed, agent->getMaxSpeed());
523 
524  steer::Vector2 velocity = to * speed / distance;
525 
526  return (velocity - agent->getVelocity());
527  }
528 
529  //otherwise, stay the course
530  return steer::Vector2(0.0, 0.0);
531  }
532 
538  template<class T>
539  steer::Vector2 Wander(const T& agent)
540  {
541  if (agent != nullptr)
542  {
543  //this behavior is dependent on the update rate, so this line must
544  //be included when using time independent framerate.
545  float JitterThisTimeSlice = agent->m_wanderJitter * agent->getElapsedTime();
546 
547  //first, add a small random vector to the target's position
548  agent->m_wanderTarget += steer::Vector2(RandomClamped() * JitterThisTimeSlice, RandomClamped() * JitterThisTimeSlice);
549 
550  //reproject this new vector back on to a unit circle
551  agent->m_wanderTarget = VectorMath::normalize(agent->m_wanderTarget);
552 
553  //increase the length of the vector to the same as the radius
554  //of the wander circle
555  agent->m_wanderTarget *= agent->m_wanderRadius;
556 
557  //move the target into a position WanderDist in front of the agent
558  steer::Vector2 target = agent->m_wanderTarget + steer::Vector2(agent->m_wanderDistance, 0.0);
559 
560  //project the target into world space
561  steer::Vector2 Target = PointToWorldSpace(target, agent->getHeading(), agent->getSide(), agent->getPosition());
562 
563  return Target - agent->getPosition();
564  }
565  }
566 
575  template <class T, class conT>
576  steer::Vector2 ObstacleAvoidance(const T& agent, const conT& obstacles, const steer::BehaviorParameters& parameters)
577  {
578  //the detection box length is proportional to the agent's velocity
579  agent->setBoxLength(parameters.MinDetectionBoxLength + (agent->getSpeed() / agent->getMaxSpeed()) * parameters.MinDetectionBoxLength);
580 
581  //tag obstacles...
582  steer::TagObstaclesWithinViewRange< T, conT >(agent, obstacles, agent->m_boxLength);
583 
584  //closest obstacle
585  steer::SphereObstacle* closest = nullptr;
586 
587  //track distance to closest obstacle
588  float distance = steer::MaxDouble;
589 
590  //track position of closest obstacle
591  steer::Vector2 position;
592 
593  for (auto& i : obstacles)
594  {
595  if (i != nullptr && i->taggedInGroup())
596  {
597  //get obstacle position
598  steer::Vector2 local = PointToLocalSpace(i->getPosition(), agent->getHeading(), agent->getSide(), agent->getPosition());
599 
600  //obstacle is ahead the agent
601  if (local.x >= 0)
602  {
603  //condition for potential intersection
604  float radius = i->getRadius() + agent->getBoundingRadius();
605 
606  if (fabs(local.y) < radius)
607  {
608  //formula x = local.x +/-sqrt(r^2-local.y^2) , y=0
609  //check for negative value indicating intersection
610  float subtrahend = sqrt(radius*radius - local.y*local.y);
611 
612  float difference = local.x - subtrahend;
613 
614  //if difference is less than 0, use positive
615  //subtrahend equation
616  if (difference <= 0.0)
617  {
618  difference = local.x + subtrahend;
619  }
620 
621  //update closest obstacle, its distance, and its position
622  if (difference < distance)
623  {
624  distance = difference;
625 
626  closest = i;
627 
628  position = local;
629  }
630  }
631  }
632  }
633  }
634 
635  steer::Vector2 force;
636 
637  if (closest != nullptr)
638  {
639  //scale avoidance force with respect
640  //to the agent's distance from the obstacle
641  float scale = 1.0 + (agent->boxLength() - position.x) / agent->boxLength();
642 
643  //calculate the lateral force
644  force.y = (closest->getRadius() - position.y) * scale;
645 
646  const float brake = 0.2;
647 
648  //apply braking factor to the force
649  force.x = (closest->getRadius() - position.x) * brake;
650  }
651 
652  //convert to world space
653  return VectorToWorldSpace(force, agent->getHeading(), agent->getSide());
654  }
655 
663  template<class T>
664  steer::Vector2 WallAvoidance(const T& agent, const std::vector<steer::Wall*>& walls)
665  {
666  //the feelers are contained in a std::vector
667  agent->createFeelers();
668 
669  float distance = 0.0;
670  float closestDistance = steer::MaxFloat;
671 
672  //this will hold an index into the vector of walls
673  int closest = -1;
674 
675  steer::Vector2 force = steer::Vector2(0.0, 0.0);
676  steer::Vector2 tempPoint = steer::Vector2(0.0, 0.0);
677  steer::Vector2 point = steer::Vector2(0.0, 0.0);
678 
679  for (unsigned int flr = 0; flr<agent->getFeelers().size(); ++flr)
680  {
681  //for each feeler, check each wall for an intersection point
682  for (unsigned int w = 0; w<walls.size(); ++w)
683  {
684  if (steer::LineIntersection2D(agent->getPosition(), agent->getFeelers()[flr], walls[w]->From(), walls[w]->To(), distance, tempPoint))
685  {
686  //keep a record of the intersection data
687  if (distance < closestDistance)
688  {
689  closestDistance = distance;
690 
691  closest = w;
692 
693  point = tempPoint;
694  }
695  }
696  }
697 
698  //calculate a force to steer away
699  //from wall if there is an intersection
700  if (closest >= 0)
701  {
702  //calculate the magnitude the agent
703  //will overshoot the wall by
704  steer::Vector2 over = agent->getFeelers()[flr] - point;
705 
706  //create a wall avoidance force
707  //scaled by the overshoot
708  force = walls[closest]->Normal() * steer::VectorMath::length(over);
709  }
710 
711  }
712 
713  return force;
714  }
715 
724  template<class T>
725  steer::Vector2 PathFollowing(const T& agent, steer::Path* path, const steer::BehaviorParameters& params)
726  {
727  if(path != nullptr)
728  {
729  //continue on to the next waypoint in the path
730  if (steer::VectorMath::distanceSquared(path->currentWaypoint(), agent->getPosition()) < params.waypointSeekDistanceSquared)
731  {
732  path->setNextWaypoint();
733  }
734 
735  if (!path->finished())
736  {
737  agent->setTarget(path->currentWaypoint());
738  return Seek< T >(agent);
739  }
740 
741  else
742  {
743  agent->setTarget(path->currentWaypoint());
744  return Arrive< T >(agent, agent->m_deceleration);
745  }
746  }
747  }
748 
749 } //end namespace steeriously
750 
751 #endif //STEERIOUSLY_HPP