// XXX 4-23-03: Temporary work around (see comment above) // // Checks for intersection of the given spherical obstacle with a // volume of "likely future vehicle positions": a cylinder along the // current path, extending minTimeToCollision seconds along the // forward axis from current position. // // If they intersect, a collision is imminent and this function returns // a steering force pointing laterally away from the obstacle's center. // // Returns a zero vector if the obstacle is outside the cylinder // // xxx couldn't this be made more compact using localizePosition? Vector3 steerToAvoid(Vehicle v, float minTimeToCollision) { // minimum distance to obstacle before avoidance is required float minDistanceToCollision = minTimeToCollision * v.Speed; float minDistanceToCenter = minDistanceToCollision + radius; // contact distance: sum of radii of obstacle and vehicle float totalRadius = radius + v.Radius; // obstacle center relative to vehicle position Vector3 localOffset = center - v.Position; // distance along vehicle's forward axis to obstacle's center float forwardComponent = Vector3.Dot(localOffset, v.Forward); Vector3 forwardOffset = forwardComponent * v.Forward; // offset from forward axis to obstacle's center Vector3 offForwardOffset = localOffset - forwardOffset; // test to see if sphere overlaps with obstacle-free corridor bool inCylinder = offForwardOffset.magnitude < totalRadius; bool nearby = forwardComponent < minDistanceToCenter; bool inFront = forwardComponent > 0; // if all three conditions are met, steer away from sphere center if (inCylinder && nearby && inFront) { return offForwardOffset * -1; } else { return Vector3.zero; } }
// constructor public tokenType(Vehicle parentObject, BruteForceProximityDatabase pd) { // store pointer to our associated database and the object this // token represents, and store this token on the database's vector bfpd = pd; tParentObject = parentObject; bfpd.group.Add(this); }
// type for the "tokens" manipulated by this spatial database //typedef AbstractTokenForProximityDatabase<ContentType> tokenType; // allocate a token to represent a given client object in this database public virtual AbstractTokenForProximityDatabase allocateToken(Vehicle parentObject) { return new AbstractTokenForProximityDatabase(); }
// ---------------------------------------------------------------------------- // evasion of another vehicle public Vector3 steerForEvasion ( Vehicle menace, float maxPredictionTime) { // offset from this to menace, that distance, unit vector toward menace Vector3 offset = menace.Position - Position; float distance = offset.magnitude; float roughTime = distance / menace.Speed; float predictionTime = ((roughTime > maxPredictionTime) ? maxPredictionTime : roughTime); Vector3 target = menace.predictFuturePosition (predictionTime); return steerForFlee (target); }
// allocate a token to represent a given client object in this database //public override tokenType allocateToken (Object parentObject) public override AbstractTokenForProximityDatabase allocateToken(Vehicle parentObject) { tokenType tToken=new tokenType (parentObject, this); return (AbstractTokenForProximityDatabase)tToken; }
// ---------------------------------------------------------------------------- // pursuit of another vehicle (& version with ceiling on prediction time) public Vector3 steerForPursuit (Vehicle quarry) { return steerForPursuit (quarry, float.MaxValue); }
public Vector3 steerForPursuit (Vehicle quarry, float maxPredictionTime) { // offset from this to quarry, that distance, unit vector toward quarry Vector3 offset = quarry.Position - Position; float distance = offset.magnitude; Vector3 unitOffset = offset / distance; // how parallel are the paths of "this" and the quarry // (1 means parallel, 0 is pependicular, -1 is anti-parallel) float parallelness = Vector3.Dot(Forward, quarry.Forward); // how "forward" is the direction to the quarry // (1 means dead ahead, 0 is directly to the side, -1 is straight back) float forwardness = Vector3.Dot(Forward, unitOffset); float directTravelTime = distance / Speed; int f = intervalComparison (forwardness, -0.707f, 0.707f); int p = intervalComparison (parallelness, -0.707f, 0.707f); float timeFactor = 0; // to be filled in below Color color = Color.black; // 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 = Color.black; break; case 0: // ahead, perpendicular timeFactor = 1.8f; color = Color.gray; break; case -1: // ahead, anti-parallel timeFactor = 0.85f; color = Color.white; break; } break; case 0: switch (p) { case +1: // aside, parallel timeFactor = 1; color = Color.red; break; case 0: // aside, perpendicular timeFactor = 0.8f; color = Color.yellow; break; case -1: // aside, anti-parallel timeFactor = 4; color = Color.green; break; } break; case -1: switch (p) { case +1: // behind, parallel timeFactor = 0.5f; color = Color.cyan; break; case 0: // behind, perpendicular timeFactor = 2; color = Color.blue; break; case -1: // behind, anti-parallel timeFactor = 2; color = Color.magenta; break; } break; } // estimated time until intercept of quarry float et = directTravelTime * timeFactor; // xxx experiment, if kept, this limit should be an argument float etl = (et > maxPredictionTime) ? maxPredictionTime : et; // estimated position of quarry at intercept Vector3 target = quarry.predictFuturePosition (etl); // annotation #if DEBUG annotationLine (Position, target, gaudyPursuitAnnotation ? color : Color.gray); #endif return steerForSeek (target); }
// ---------------------------------------------------------------------------- // used by boid behaviors: is a given vehicle within this boid's neighborhood? bool inBoidNeighborhood ( Vehicle other, float minDistance, float maxDistance, float cosMaxAngle) { if (other == this) { return false; } else { Vector3 offset = other.Position - Position; float distanceSquared = offset.sqrMagnitude; // 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 Vector3 unitOffset = offset / (float) System.Math.Sqrt (distanceSquared); float forwardness = Vector3.Dot(Forward, unitOffset); return forwardness > cosMaxAngle; } } } }
// Given the time until nearest approach (predictNearestApproachTime) // determine position of each vehicle at that time, and the distance // between them float computeNearestApproachPositions(Vehicle other, float time, ref Vector3 ourPosition, ref Vector3 hisPosition) { Vector3 myTravel = Forward * Speed * time; Vector3 otherTravel = other.Forward * other.Speed * time; ourPosition = Position + myTravel; hisPosition = other.Position + otherTravel; return Vector3.Distance(ourPosition, hisPosition); }
// Given two vehicles, based on their current positions and velocities, // determine the time until nearest approach // // XXX should this return zero if they are already in contact? float predictNearestApproachTime (Vehicle other) { // imagine we are at the origin with no velocity, // compute the relative velocity of the other vehicle Vector3 myVelocity = Velocity; Vector3 otherVelocity = other.Velocity; Vector3 relVelocity = otherVelocity - myVelocity; float relSpeed = relVelocity.magnitude; // for parallel paths, the vehicles will always be at the same distance, // so return 0 (aka "now") since "there is no time like the present" if (relSpeed == 0) return 0; // Now consider the path of the other vehicle in this relative // space, a line defined by the relative position and velocity. // The distance from the origin (our vehicle) to that line is // the nearest approach. // Take the unit tangent along the other vehicle's path Vector3 relTangent = relVelocity / relSpeed; // find distance from its path to origin (compute offset from // other to us, find length of projection onto path) Vector3 relPosition = Position - other.Position; float projection = Vector3.Dot(relTangent, relPosition); return projection / relSpeed; }
// called when steerToAvoidNeighbors decides steering is required // (default action is to do nothing, layered classes can overload it) public virtual void annotateAvoidNeighbor ( Vehicle vehicle, float steer, Vector3 position, Vector3 threatPosition) { Debug.DrawLine(Position, vehicle.Position, Color.red); // Neighbor position Debug.DrawLine(Position, position, Color.green); // Position we're aiming for }
// called when steerToAvoidCloseNeighbors decides steering is required // (default action is to do nothing, layered classes can overload it) public virtual void annotateAvoidCloseNeighbor(Vehicle otherVehicle, Vector3 component) { Debug.DrawLine(Position, otherVehicle.Position, Color.red); Debug.DrawRay (Position, component*3, Color.yellow); }