com.badlogic.gdx.ai.steer.behaviors.CollisionAvoidance Maven / Gradle / Ivy
/*******************************************************************************
* Copyright 2014 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.ai.steer.behaviors;
import com.badlogic.gdx.ai.steer.GroupBehavior;
import com.badlogic.gdx.ai.steer.Limiter;
import com.badlogic.gdx.ai.steer.Proximity;
import com.badlogic.gdx.ai.steer.Proximity.ProximityCallback;
import com.badlogic.gdx.ai.steer.Steerable;
import com.badlogic.gdx.ai.steer.SteeringAcceleration;
import com.badlogic.gdx.math.Vector;
/** {@code CollisionAvoidance} behavior steers the owner to avoid obstacles lying in its path. An obstacle is any object that can be
* approximated by a circle (or sphere, if you are working in 3D).
*
* This implementation uses collision prediction working out the closest approach of two agents and determining if their distance
* at this point is less than the sum of their bounding radius. For avoiding groups of characters, averaging positions and
* velocities do not work well with this approach. Instead, the algorithm needs to search for the character whose closest approach
* will occur first and to react to this character only. Once this imminent collision is avoided, the steering behavior can then
* react to more distant characters.
*
* This algorithm works well with small and/or moving obstacles whose shape can be approximately represented by a center and a
* radius.
*
* @param Type of vector, either 2D or 3D, implementing the {@link Vector} interface
*
* @author davebaol */
public class CollisionAvoidance> extends GroupBehavior implements ProximityCallback {
private float shortestTime;
private Steerable firstNeighbor;
private float firstMinSeparation;
private float firstDistance;
private T firstRelativePosition;
private T firstRelativeVelocity;
private T relativePosition;
private T relativeVelocity;
/** Creates a {@code CollisionAvoidance} behavior for the specified owner and proximity.
* @param owner the owner of this behavior
* @param proximity the proximity of this behavior. */
public CollisionAvoidance (Steerable owner, Proximity proximity) {
super(owner, proximity);
this.firstRelativePosition = newVector(owner);
this.firstRelativeVelocity = newVector(owner);
this.relativeVelocity = newVector(owner);
}
@Override
protected SteeringAcceleration calculateRealSteering (SteeringAcceleration steering) {
shortestTime = Float.POSITIVE_INFINITY;
firstNeighbor = null;
firstMinSeparation = 0;
firstDistance = 0;
relativePosition = steering.linear;
// Take into consideration each neighbor to find the most imminent collision.
int neighborCount = proximity.findNeighbors(this);
// If we have no target, then return no steering acceleration
if (neighborCount == 0 || firstNeighbor == null) return steering.setZero();
// If we're going to hit exactly, or if we're already
// colliding, then do the steering based on current position.
if (firstMinSeparation <= 0 || firstDistance < owner.getBoundingRadius() + firstNeighbor.getBoundingRadius()) {
relativePosition.set(firstNeighbor.getPosition()).sub(owner.getPosition());
} else {
// Otherwise calculate the future relative position
relativePosition.set(firstRelativePosition).mulAdd(firstRelativeVelocity, shortestTime);
}
// Avoid the target
// Notice that steerling.linear and relativePosition are the same vector
relativePosition.nor().scl(-getActualLimiter().getMaxLinearAcceleration());
// No angular acceleration
steering.angular = 0f;
// Output the steering
return steering;
}
@Override
public boolean reportNeighbor (Steerable neighbor) {
// Calculate the time to collision
relativePosition.set(neighbor.getPosition()).sub(owner.getPosition());
relativeVelocity.set(neighbor.getLinearVelocity()).sub(owner.getLinearVelocity());
float relativeSpeed2 = relativeVelocity.len2();
// Collision can't happen when the agents have the same linear velocity.
// Also, note that timeToTarget would be NaN due to the indeterminate form 0/0 and,
// since any comparison involving NaN returns false, it would become the shortestTime,
// so defeating the algorithm.
if (relativeSpeed2 == 0) return false;
float timeToCollision = -relativePosition.dot(relativeVelocity) / relativeSpeed2;
// If timeToCollision is negative, i.e. the owner is already moving away from the the neighbor,
// or it's not the most imminent collision then no action needs to be taken.
if (timeToCollision <= 0 || timeToCollision >= shortestTime) return false;
// Check if it is going to be a collision at all
float distance = relativePosition.len();
float minSeparation = distance - (float)Math.sqrt(relativeSpeed2) * timeToCollision /* shortestTime */;
if (minSeparation > owner.getBoundingRadius() + neighbor.getBoundingRadius()) return false;
// Store most imminent collision data
shortestTime = timeToCollision;
firstNeighbor = neighbor;
firstMinSeparation = minSeparation;
firstDistance = distance;
firstRelativePosition.set(relativePosition);
firstRelativeVelocity.set(relativeVelocity);
return true;
}
//
// Setters overridden in order to fix the correct return type for chaining
//
@Override
public CollisionAvoidance setOwner (Steerable owner) {
this.owner = owner;
return this;
}
@Override
public CollisionAvoidance setEnabled (boolean enabled) {
this.enabled = enabled;
return this;
}
/** Sets the limiter of this steering behavior. The given limiter must at least take care of the maximum linear acceleration.
* @return this behavior for chaining. */
@Override
public CollisionAvoidance setLimiter (Limiter limiter) {
this.limiter = limiter;
return this;
}
}