Mercurial > projects > openmelee
diff ai/steer.d @ 18:7f74e064dad5
refactored code
author | zzzzrrr <mason.green@gmail.com> |
---|---|
date | Wed, 25 Mar 2009 11:28:25 -0400 |
parents | steer.d@8e6a9e390cba |
children | 08ddf9e71b88 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ai/steer.d Wed Mar 25 11:28:25 2009 -0400 @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2009, Mason Green (zzzzrrr) + * http://www.dsource.org/projects/openmelee + * Based on OpenSteer, Copyright (c) 2002-2003, Sony Computer Entertainment America + * Original author: Craig Reynolds + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the polygonal nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +module openmelee.ai.steer; + +import blaze.common.bzMath: bzDot, bzClamp, bzVec2; +import blaze.dynamics.bzBody: bzBody; + +import openmelee.ships.ship : Ship, State; +import openmelee.ai.utilities; + +class Steer +{ + // Constructor: initializes state + this (Ship ship) + { + m_ship = ship; + } + + struct PathIntersection + { + bool intersect; + float distance; + bzVec2 surfacePoint; + bzVec2 surfaceNormal; + bzBody obstacle; + } + + // reset state + void reset () { + // initial state of wander behavior + m_wanderSide = 0; + m_wanderUp = 0; + } + + void update() { + m_position = m_ship.state.position; + m_velocity = m_ship.state.velocity; + m_speed = m_ship.state.speed; + m_maxForce = m_ship.state.maxForce; + m_forward = m_ship.state.forward; + } + + // -------------------------------------------------- steering behaviors + + bzVec2 steerForWander (float dt) { + // random walk m_wanderSide and m_wanderUp between -1 and +1 + float speed = 12 * dt; // maybe this (12) should be an argument? + m_wanderSide = scalarRandomWalk (m_wanderSide, speed, -1, +1); + m_wanderUp = scalarRandomWalk (m_wanderUp, speed, -1, +1); + + // return a pure lateral steering vector: (+/-Side) + (+/-Up) + return (m_side * m_wanderSide) + (m_up * m_wanderUp); + } + + // Seek behavior + bzVec2 steerForSeek (bzVec2 target) { + bzVec2 desiredVelocity = target - m_position; + return desiredVelocity - m_velocity; + } + + // Flee behavior + bzVec2 steerForFlee (bzVec2 target) { + bzVec2 desiredVelocity = m_position - target; + return desiredVelocity - m_velocity; + } + + /* + // xxx proposed, experimental new seek/flee [cwr 9-16-02] + bzVec2 xxxsteerForFlee (bzVec2 target) { + bzVec2 offset = m_position - target; + bzVec2 desiredVelocity = bzClamp(offset.truncateLength (maxSpeed ()); + return desiredVelocity - m_velocity; + } + + bzVec2 xxxsteerForSeek (bzVec2 target) { + // bzVec2 offset = target - position; + bzVec2 offset = target - m_position; + bzVec2 desiredVelocity = offset.truncateLength (maxSpeed ()); //xxxnew + return desiredVelocity - m_velocity; + } + */ + + /* + // ------------------------------------------------------------------------ + // Obstacle Avoidance behavior + // + // Returns a steering force to avoid a given obstacle. The purely + // lateral steering force will turn our vehicle towards a silhouette edge + // of the obstacle. Avoidance is required when (1) the obstacle + // intersects the vehicle's current path, (2) it is in front of the + // vehicle, and (3) is within minTimeToCollision seconds of travel at the + // vehicle's current velocity. Returns a zero vector value (bzVec2::zero) + // when no avoidance is required. + bzVec2 steerToAvoidObstacle (float minTimeToCollision, Obstacle obstacle) { + + bzVec2 avoidance = obstacle.steerToAvoid (this, minTimeToCollision); + return avoidance; + } + + // avoids all obstacles in an ObstacleGroup + */ + + bzVec2 steerToAvoidObstacles (float minTimeToCollision, bzBody obstacles) { + + bzVec2 avoidance; + PathIntersection nearest, next; + float minDistanceToCollision = minTimeToCollision * m_speed; + + next.intersect = false; + nearest.intersect = false; + + // test all obstacles for intersection with my forward axis, + // select the one whose point of intersection is nearest + for (bzBody o; o; o = o.next) { + // This code which presumes the obstacle is spherical + //findNextIntersectionWithSphere (o, next); + + if (!nearest.intersect || (next.intersect && next.distance < nearest.distance)) { + nearest = next; + } + } + + // when a nearest intersection was found + if (nearest.intersect && (nearest.distance < minDistanceToCollision)) { + // compute avoidance steering force: take offset from obstacle to me, + // take the component of that which is lateral (perpendicular to my + // forward direction), set length to maxForce, add a bit of forward + // component (in capture the flag, we never want to slow down) + bzVec2 offset = m_position - nearest.obstacle.position; + //avoidance = Vector3Helpers.PerpendicularComponent(offset, this.Forward); + avoidance.normalize; + avoidance *= m_maxForce; + avoidance += m_forward * m_maxForce * 0.75f; + } + + return avoidance; + } + + /* + // ------------------------------------------------------------------------ + // Unaligned collision avoidance behavior: avoid colliding with other + // nearby vehicles moving in unconstrained directions. Determine which + // (if any) other other vehicle we would collide with first, then steers + // to avoid the site of that potential collision. Returns a steering + // force vector, which is zero length if there is no impending collision. + + bzVec2 steerToAvoidNeighbors (float minTimeToCollision, AVGroup others) { + + // first priority is to prevent immediate interpenetration + bzVec2 separation = steerToAvoidCloseNeighbors (0, others); + if (separation != bzVec2::zero) return separation; + + // otherwise, go on to consider potential future collisions + float steer = 0; + Ship threat; + + // 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 + bzVec2 xxxThreatPositionAtNearestApproach; + bzVec2 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++) + { + Ship other = i; + if (other !is this) + { + // avoid when future positions are this close (or less) + float collisionDangerThreshold = radius * 2; + + // predicted time until nearest approach of "this" and "other" + float time = predictNearestApproachTime (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 (other, time) + < collisionDangerThreshold) + { + minTime = time; + threat = other; + xxxThreatPositionAtNearestApproach + = hisPositionAtNearestApproach; + xxxOurPositionAtNearestApproach + = ourPositionAtNearestApproach; + } + } + } + } + + // if a potential collision was found, compute steering to avoid + if (threat) + { + // parallel: +1, perpendicular: 0, anti-parallel: -1 + float parallelness = m_forward.dot(threat.forward); + float angle = 0.707f; + + if (parallelness < -angle) + { + // anti-parallel "head on" paths: + // steer away from future threat position + bzVec2 offset = xxxThreatPositionAtNearestApproach - m_position; + float sideDot = offset.dot(m_side()); + steer = (sideDot > 0) ? -1.0f : 1.0f; + } + else + { + if (parallelness > angle) + { + // parallel paths: steer away from threat + bzVec2 offset = threat.position - m_position; + float sideDot = bzDot(offset, m_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() <= speed) + { + float sideDot = bzDot(m_side, threat.velocity); + steer = (sideDot > 0) ? -1.0f : 1.0f; + } + } + } + } + + return m_side() * steer; + } + */ + + // Given two vehicles, based on their current positions and velocities, + // determine the time until nearest approach + float predictNearestApproachTime (State other) { + + // imagine we are at the origin with no velocity, + // compute the relative velocity of the other vehicle + bzVec2 myVelocity = m_velocity; + bzVec2 otherVelocity = other.velocity; + bzVec2 relVelocity = otherVelocity - myVelocity; + float relSpeed = relVelocity.length; + + // 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 + bzVec2 relTangent = relVelocity / relSpeed; + + // find distance from its path to origin (compute offset from + // other to us, find length of projection onto path) + bzVec2 relPosition = m_position - other.position; + float projection = bzDot(relTangent, relPosition); + + return projection / relSpeed; + } + + // Given the time until nearest approach (predictNearestApproachTime) + // determine position of each vehicle at that time, and the distance + // between them + float computeNearestApproachPositions (State other, float time) { + + bzVec2 myTravel = m_forward * m_speed * time; + bzVec2 otherTravel = other.forward * other.speed * time; + + bzVec2 myFinal = m_position + myTravel; + bzVec2 otherFinal = other.position + otherTravel; + + return (myFinal - otherFinal).length; + } + + // ------------------------------------------------------------------------ + // pursuit of another vehicle ( version with ceiling on prediction time) + + bzVec2 steerForPursuit (State quarry) { + return steerForPursuit (quarry, float.max); + } + + bzVec2 steerForPursuit (State quarry, float maxPredictionTime) { + + // offset from this to quarry, that distance, unit vector toward quarry + bzVec2 offset = quarry.position - m_position; + float distance = offset.length; + bzVec2 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 = bzDot(m_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 = bzDot(m_forward , unitOffset); + + float directTravelTime = distance / m_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 + + // 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; + break; + case 0: // ahead, perpendicular + timeFactor = 1.8f; + break; + case -1: // ahead, anti-parallel + timeFactor = 0.85f; + break; + } + break; + case 0: + switch (p) + { + case +1: // aside, parallel + timeFactor = 1; + break; + case 0: // aside, perpendicular + timeFactor = 0.8f; + break; + case -1: // aside, anti-parallel + timeFactor = 4; + break; + } + break; + case -1: + switch (p) + { + case +1: // behind, parallel + timeFactor = 0.5f; + break; + case 0: // behind, perpendicular + timeFactor = 2; + break; + case -1: // behind, anti-parallel + timeFactor = 2; + 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 + bzVec2 target = quarry.predictFuturePosition(etl); + + return target; //steerForSeek (target); + } + + // ------------------------------------------------------------------------ + // evasion of another vehicle + bzVec2 steerForEvasion (State menace, float maxPredictionTime) { + + // offset from this to menace, that distance, unit vector toward menace + bzVec2 offset = menace.position - m_position; + float distance = offset.length; + + float roughTime = distance / menace.speed; + float predictionTime = ((roughTime > maxPredictionTime) ? maxPredictionTime : roughTime); + bzVec2 target = menace.predictFuturePosition (predictionTime); + + return steerForFlee (target); + } + + + // ------------------------------------------------------------------------ + // tries to maintain a given speed, returns a maxForce-clipped steering + // force along the forward/backward axis + bzVec2 steerForTargetSpeed (float targetSpeed) { + float mf = m_maxForce; + float speedError = targetSpeed - m_speed; + return m_forward * bzClamp(speedError, -mf, +mf); + } + + + // ----------------------------------------------------------- utilities + bool isAhead (bzVec2 target) {return isAhead (target, 0.707f);}; + bool isAside (bzVec2 target) {return isAside (target, 0.707f);}; + bool isBehind (bzVec2 target) {return isBehind (target, -0.707f);}; + + bool isAhead (bzVec2 target, float cosThreshold) + { + bzVec2 targetDirection = target - m_position; + targetDirection.normalize(); + return bzDot(m_forward, targetDirection) > cosThreshold; + } + + bool isAside (bzVec2 target, float cosThreshold) + { + bzVec2 targetDirection = target - m_position; + targetDirection.normalize(); + float dp = bzDot(m_forward, targetDirection); + return (dp < cosThreshold) && (dp > -cosThreshold); + } + + bool isBehind (bzVec2 target, float cosThreshold) + { + bzVec2 targetDirection = target - m_position; + targetDirection.normalize(); + return bzDot(m_forward, targetDirection) < cosThreshold; + } + + private: + + Ship m_ship; + + bzVec2 m_position; + bzVec2 m_velocity; + bzVec2 m_up; + bzVec2 m_side; + bzVec2 m_forward; + + float m_speed = 0; + float m_maxForce = 0; + + // Wander behavior + float m_wanderSide; + float m_wanderUp; +}