float3 OpenSteer::SteerLibrary:: steerToAvoidCloseNeighbors (const AbstractVehicle& v, const float minSeparationDistance, const AVGroup& others) { // for each of the other vehicles... for (AVIterator i = others.begin(); i != others.end(); i++) { AbstractVehicle& other = **i; if (&other != &v) { const float sumOfRadii = v.radius() + other.radius(); const float minCenterToCenter = minSeparationDistance + sumOfRadii; const float3 offset = float3_subtract(make_float3(other.position()), make_float3(v.position())); const float currentDistance = float3_length(offset); if (currentDistance < minCenterToCenter) { annotateAvoidCloseNeighbor (other, minSeparationDistance); return float3_perpendicularComponent(float3_minus(offset), make_float3(v.forward())); } } } // otherwise return zero return float3_zero(); }
float3 OpenSteer::SteerLibrary:: steerForTargetSpeed (const AbstractVehicle& v, const float targetSpeed) { const float mf = v.maxForce (); const float speedError = targetSpeed - v.speed (); return float3_scalar_multiply(make_float3(v.forward ()), clip (speedError, -mf, +mf)); }
float OpenSteer::SteerLibrary:: computeNearestApproachPositions (const AbstractVehicle& v, AbstractVehicle& other, float time) { const float3 myTravel = float3_scalar_multiply(make_float3(v.forward()), v.speed () * time); const float3 otherTravel = float3_scalar_multiply(make_float3(other.forward()), other.speed () * time); const float3 myFinal = float3_add(make_float3(v.position()), myTravel); const float3 otherFinal = float3_add(make_float3(other.position()), otherTravel); // xxx for annotation ourPositionAtNearestApproach = myFinal; hisPositionAtNearestApproach = otherFinal; return float3_distance(myFinal, otherFinal); }
void OpenSteer::OpenSteerDemo::position3dCamera (AbstractVehicle& selected, float distance, float /*elevation*/) { selectedVehicle = &selected; if (&selected) { const Vec3 behind = selected.forward() * -distance; camera.setPosition (selected.position() + behind); camera.target = selected.position(); } }
//----------------------------------------------------------------------------- // called when steerToAvoidCloseNeighbors decides steering is required // (parameter names commented out to prevent compiler warning from "-W") void Pedestrian::annotateAvoidCloseNeighbor (const AbstractVehicle& other, const float /*additionalDistance*/) { // draw the word "Ouch!" above colliding vehicles const float headOn = forward().dot(other.forward()) < 0; const Color green (0.4f, 0.8f, 0.1f); const Color red (1, 0.1f, 0); const Color color = headOn ? red : green; const char* string = headOn ? "OUCH!" : "pardon me"; const osVector3 location = position() + osVector3 (0, 0.5f, 0); if (annotationIsOn()) draw2dTextAt3dLocation (string, location, color, drawGetWindowWidth(), drawGetWindowHeight()); }
void OpenSteer::OpenSteerDemo::position3dCamera (AbstractVehicle& selected, float distance, float /*elevation*/) { //selectedVehicle = &selected; if (&selected) { const float3 behind = float3_scalar_multiply(make_float3(selected.forward()), -distance); camera.setPosition ( make_float4(float3_add(make_float3(selected.position()), behind), 0.f) ); camera.target = make_float3(selected.position()); } }
void OpenSteer::OpenSteerDemo::position3dCamera (AbstractVehicle& selected, float distance, float /*elevation*/) { SteeringVehicle::setSelectedVehicle( &selected ); if (&selected) { const Vec3 behind = selected.forward() * -distance; OpenSteer::Camera::accessInstance().setPosition (selected.position() + behind); OpenSteer::Camera::accessInstance().target = selected.position(); } }
void OpenSteer:: PlaneObstacle:: findIntersectionWithVehiclePath (const AbstractVehicle& vehicle, PathIntersection& pi) const { // initialize pathIntersection object to "no intersection found" pi.intersect = false; const Vec3 lp = localizePosition (vehicle.position ()); const Vec3 ld = localizeDirection (vehicle.forward ()); // no obstacle intersection if path is parallel to XY (side/up) plane if (ld.dot (Vec3::forward) == 0.0f) return; // no obstacle intersection if vehicle is heading away from the XY plane if ((lp.z > 0.0f) && (ld.z > 0.0f)) return; if ((lp.z < 0.0f) && (ld.z < 0.0f)) return; // no obstacle intersection if obstacle "not seen" from vehicle's side if ((seenFrom () == outside) && (lp.z < 0.0f)) return; if ((seenFrom () == inside) && (lp.z > 0.0f)) return; // find intersection of path with rectangle's plane (XY plane) const float ix = lp.x - (ld.x * lp.z / ld.z); const float iy = lp.y - (ld.y * lp.z / ld.z); const Vec3 planeIntersection (ix, iy, 0.0f); // no obstacle intersection if plane intersection is outside 2d shape if (!xyPointInsideShape (planeIntersection, vehicle.radius ())) return; // otherwise, the vehicle path DOES intersect this rectangle const Vec3 localXYradial = planeIntersection.normalize (); const Vec3 radial = globalizeDirection (localXYradial); const float sideSign = (lp.z > 0.0f) ? +1.0f : -1.0f; const Vec3 opposingNormal = forward () * sideSign; pi.intersect = true; pi.obstacle = this; pi.distance = (lp - planeIntersection).length (); pi.steerHint = opposingNormal + radial; // should have "toward edge" term? pi.surfacePoint = globalizePosition (planeIntersection); pi.surfaceNormal = opposingNormal; pi.vehicleOutside = lp.z > 0.0f; }
bool OpenSteer::SteerLibrary:: inBoidNeighborhood (const AbstractVehicle& v, const AbstractVehicle& other, const float minDistance, const float maxDistance, const float cosMaxAngle) { if (&other == &v) { return false; } else { const float3 offset = float3_subtract(make_float3(other.position()), make_float3(v.position())); const float distanceSquared = float3_lengthSquared(offset); // definitely in neighborhood if inside minDistance sphere if (distanceSquared < (minDistance * minDistance)) { return true; } else { // definitely not in neighborhood if outside maxDistance sphere if (distanceSquared > (maxDistance * maxDistance)) { return false; } else { // otherwise, test angular offset from forward axis const float3 unitOffset = float3_scalar_divide(offset, sqrt(distanceSquared)); const float forwardness = float3_dot(make_float3(v.forward()), unitOffset); return forwardness > cosMaxAngle; } } } }
OpenSteer::Vec3 OpenSteer::Obstacle::PathIntersection:: steerToAvoidIfNeeded (const AbstractVehicle& vehicle, const float minTimeToCollision) const { // if nearby intersection found, steer away from it, otherwise no steering const float minDistanceToCollision = minTimeToCollision * vehicle.speed(); if (intersect && (distance < minDistanceToCollision)) { // compute avoidance steering force: take the component of // steerHint which is lateral (perpendicular to vehicle's // forward direction), set its length to vehicle's maxForce Vec3 lateral = steerHint.perpendicularComponent (vehicle.forward ()); if (lateral == Vec3::zero) lateral = vehicle.side (); return lateral.normalize () * vehicle.maxForce (); } else { return Vec3::zero; } }
float3 OpenSteer::SteerLibrary:: steerForPursuit (const AbstractVehicle& v, const AbstractVehicle& quarry, const float maxPredictionTime) { // offset from this to quarry, that distance, unit vector toward quarry const float3 offset = float3_subtract(make_float3(quarry.position()), make_float3(v.position())); const float distance = float3_length(offset); const float3 unitOffset = float3_scalar_divide(offset, distance); // how parallel are the paths of "this" and the quarry // (1 means parallel, 0 is pependicular, -1 is anti-parallel) const float parallelness = float3_dot(make_float3(v.forward()), make_float3(quarry.forward())); // how "forward" is the direction to the quarry // (1 means dead ahead, 0 is directly to the side, -1 is straight back) const float forwardness = float3_dot(make_float3(v.forward()), unitOffset); const float directTravelTime = distance / v.speed (); const int f = intervalComparison (forwardness, -0.707f, 0.707f); const int p = intervalComparison (parallelness, -0.707f, 0.707f); float timeFactor = 0; // to be filled in below float3 color; // to be filled in below (xxx just for debugging) // Break the pursuit into nine cases, the cross product of the // quarry being [ahead, aside, or behind] us and heading // [parallel, perpendicular, or anti-parallel] to us. switch (f) { case +1: switch (p) { case +1: // ahead, parallel timeFactor = 4; color = gBlack; break; case 0: // ahead, perpendicular timeFactor = 1.8f; color = gGray50; break; case -1: // ahead, anti-parallel timeFactor = 0.85f; color = gWhite; break; } break; case 0: switch (p) { case +1: // aside, parallel timeFactor = 1; color = gRed; break; case 0: // aside, perpendicular timeFactor = 0.8f; color = gYellow; break; case -1: // aside, anti-parallel timeFactor = 4; color = gGreen; break; } break; case -1: switch (p) { case +1: // behind, parallel timeFactor = 0.5f; color= gCyan; break; case 0: // behind, perpendicular timeFactor = 2; color= gBlue; break; case -1: // behind, anti-parallel timeFactor = 2; color = gMagenta; break; } break; } // estimated time until intercept of quarry const float et = directTravelTime * timeFactor; // xxx experiment, if kept, this limit should be an argument const float etl = (et > maxPredictionTime) ? maxPredictionTime : et; // estimated position of quarry at intercept const float3 target = quarry.predictFuturePosition (etl); // annotation annotationLine (make_float3(v.position()), target, gaudyPursuitAnnotation ? color : gGray40); return steerForSeek (v, target); }
float3 OpenSteer::SteerLibrary:: steerForAlignment (const AbstractVehicle& v, const float maxDistance, const float cosMaxAngle, const AVGroup& flock) { // steering accumulator and count of neighbors, both initially zero float3 steering = float3_zero(); int neighbors = 0; // for each of the other vehicles... for (AVIterator other = flock.begin(); other != flock.end(); other++) { if (inBoidNeighborhood (v, **other, v.radius() * 3, maxDistance, cosMaxAngle)) { // accumulate sum of neighbor's heading steering = float3_add(steering, make_float3((**other).forward())); // count neighbors neighbors++; } } // divide by neighbors, subtract off current heading to get error- // correcting direction, then normalize to pure direction if (neighbors > 0) steering = float3_normalize(float3_subtract(float3_scalar_divide(steering, (float)neighbors), make_float3(v.forward()))); return steering; }
float3 OpenSteer::SteerLibrary:: steerToAvoidNeighbors (const AbstractVehicle& v, const float minTimeToCollision, const AVGroup& others) { // first priority is to prevent immediate interpenetration const float3 separation = steerToAvoidCloseNeighbors (v, 0, others); if (!float3_equals(separation, float3_zero())) return separation; // otherwise, go on to consider potential future collisions float steer = 0; AbstractVehicle* threat = NULL; // Time (in seconds) until the most immediate collision threat found // so far. Initial value is a threshold: don't look more than this // many frames into the future. float minTime = minTimeToCollision; // xxx solely for annotation float3 xxxThreatPositionAtNearestApproach; float3 xxxOurPositionAtNearestApproach; // for each of the other vehicles, determine which (if any) // pose the most immediate threat of collision. for (AVIterator i = others.begin(); i != others.end(); i++) { AbstractVehicle& other = **i; if (&other != &v) { // avoid when future positions are this close (or less) const float collisionDangerThreshold = v.radius() * 2; // predicted time until nearest approach of "this" and "other" const float time = predictNearestApproachTime (v, other); // If the time is in the future, sooner than any other // threatened collision... if ((time >= 0) && (time < minTime)) { // if the two will be close enough to collide, // make a note of it if (computeNearestApproachPositions (v, other, time) < collisionDangerThreshold) { minTime = time; threat = &other; xxxThreatPositionAtNearestApproach = hisPositionAtNearestApproach; xxxOurPositionAtNearestApproach = ourPositionAtNearestApproach; } } } } // if a potential collision was found, compute steering to avoid if (threat != NULL) { // parallel: +1, perpendicular: 0, anti-parallel: -1 float parallelness = float3_dot(make_float3(v.forward()), make_float3(threat->forward())); float angle = 0.707f; if (parallelness < -angle) { // anti-parallel "head on" paths: // steer away from future threat position float3 offset = float3_subtract(xxxThreatPositionAtNearestApproach, make_float3(v.position())); float sideDot = float3_dot(offset, v.side()); steer = (sideDot > 0) ? -1.0f : 1.0f; } else { if (parallelness > angle) { // parallel paths: steer away from threat float3 offset = float3_subtract(make_float3(threat->position()), make_float3(v.position())); float sideDot = float3_dot(offset, v.side()); steer = (sideDot > 0) ? -1.0f : 1.0f; } else { // perpendicular paths: steer behind threat // (only the slower of the two does this) if (threat->speed() <= v.speed()) { float sideDot = float3_dot(v.side(), threat->velocity()); steer = (sideDot > 0) ? -1.0f : 1.0f; } } } annotateAvoidNeighbor (*threat, steer, xxxOurPositionAtNearestApproach, xxxThreatPositionAtNearestApproach); } return float3_scalar_multiply(v.side(), steer); }
void OpenSteer:: SphereObstacle:: findIntersectionWithVehiclePath (const AbstractVehicle& vehicle, PathIntersection& pi) const { // This routine is based on the Paul Bourke's derivation in: // Intersection of a Line and a Sphere (or circle) // http://www.swin.edu.au/astronomy/pbourke/geometry/sphereline/ // But the computation is done in the vehicle's local space, so // the line in question is the Z (Forward) axis of the space which // simplifies some of the calculations. float b, c, d, p, q, s; Vec3 lc; // initialize pathIntersection object to "no intersection found" pi.intersect = false; // find sphere's "local center" (lc) in the vehicle's coordinate space lc = vehicle.localizePosition (center); pi.vehicleOutside = lc.length () > radius; // if obstacle is seen from inside, but vehicle is outside, must avoid // (noticed once a vehicle got outside it ignored the obstacle 2008-5-20) if (pi.vehicleOutside && (seenFrom () == inside)) { pi.intersect = true; pi.distance = 0.0f; pi.steerHint = (center - vehicle.position()).normalize(); return; } // compute line-sphere intersection parameters const float r = radius + vehicle.radius(); b = -2 * lc.z; c = square (lc.x) + square (lc.y) + square (lc.z) - square (r); d = (b * b) - (4 * c); // when the path does not intersect the sphere if (d < 0) return; // otherwise, the path intersects the sphere in two points with // parametric coordinates of "p" and "q". (If "d" is zero the two // points are coincident, the path is tangent) s = sqrtXXX (d); p = (-b + s) / 2; q = (-b - s) / 2; // both intersections are behind us, so no potential collisions if ((p < 0) && (q < 0)) return; // at least one intersection is in front, so intersects our forward // path pi.intersect = true; pi.obstacle = this; pi.distance = ((p > 0) && (q > 0)) ? // both intersections are in front of us, find nearest one ((p < q) ? p : q) : // otherwise one is ahead and one is behind: we are INSIDE obstacle (seenFrom () == outside ? // inside a solid obstacle, so distance to obstacle is zero 0.0f : // hollow obstacle (or "both"), pick point that is in front ((p > 0) ? p : q)); pi.surfacePoint = vehicle.position() + (vehicle.forward() * pi.distance); pi.surfaceNormal = (pi.surfacePoint-center).normalize(); switch (seenFrom ()) { case outside: pi.steerHint = pi.surfaceNormal; break; case inside: pi.steerHint = -pi.surfaceNormal; break; case both: pi.steerHint = pi.surfaceNormal * (pi.vehicleOutside ? 1.0f : -1.0f); break; } }