Cleanup / Functioning formation
This commit is contained in:
@@ -1,63 +0,0 @@
|
||||
package mindustry.ai.ai.fma.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import mindustry.ai.ai.fma.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* The defensive circle posts members around the circumference of a circle, so their backs are to the center of the circle. The
|
||||
* circle can consist of any number of members. Although a huge number of members might look silly, this implementation doesn't
|
||||
* put any fixed limit.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class DefensiveCircleFormationPattern implements FormationPattern{
|
||||
/** The number of slots currently in the pattern. */
|
||||
int numberOfSlots;
|
||||
|
||||
/** The radius of one member. This is needed to determine how close we can pack a given number of members around circle. */
|
||||
float memberRadius;
|
||||
|
||||
/**
|
||||
* Creates a {@code DefensiveCircleFormationPattern}
|
||||
*/
|
||||
public DefensiveCircleFormationPattern(float memberRadius){
|
||||
this.memberRadius = memberRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNumberOfSlots(int numberOfSlots){
|
||||
this.numberOfSlots = numberOfSlots;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location calculateSlotLocation(Location outLocation, int slotNumber){
|
||||
if(numberOfSlots > 1){
|
||||
// Place the slot around the circle based on its slot number
|
||||
float angleAroundCircle = (Mathf.PI2 * slotNumber) / numberOfSlots;
|
||||
|
||||
// The radius depends on the radius of the member,
|
||||
// and the number of members in the circle:
|
||||
// we want there to be no gap between member's shoulders.
|
||||
float radius = memberRadius / (float)Math.sin(Math.PI / numberOfSlots);
|
||||
|
||||
// Fill location components based on the angle around circle.
|
||||
outLocation.angleToVector(outLocation.getPosition(), angleAroundCircle).scl(radius);
|
||||
|
||||
// The members should be facing out
|
||||
outLocation.setOrientation(angleAroundCircle);
|
||||
}else{
|
||||
outLocation.getPosition().setZero();
|
||||
outLocation.setOrientation(Mathf.PI2 * slotNumber);
|
||||
}
|
||||
|
||||
// Return the slot location
|
||||
return outLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSlots(int slotCount){
|
||||
// In this case we support any number of slots.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package mindustry.ai.ai.fma.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* The offensive circle posts members around the circumference of a circle, so their fronts are to the center of the circle. The
|
||||
* circle can consist of any number of members. Although a huge number of members might look silly, this implementation doesn't
|
||||
* put any fixed limit.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class OffensiveCircleFormationPattern extends DefensiveCircleFormationPattern{
|
||||
|
||||
/**
|
||||
* Creates a {@code OffensiveCircleFormationPattern}
|
||||
*/
|
||||
public OffensiveCircleFormationPattern(float memberRadius){
|
||||
super(memberRadius);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location calculateSlotLocation(Location outLocation, int slotNumber){
|
||||
super.calculateSlotLocation(outLocation, slotNumber);
|
||||
outLocation.setOrientation(outLocation.getOrientation() + Mathf.PI);
|
||||
return outLocation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
import mindustry.ai.ai.steer.Proximity.*;
|
||||
|
||||
/**
|
||||
* {@code GroupBehavior} is the base class for the steering behaviors that take into consideration the agents in the game world
|
||||
* that are within the immediate area of the owner. This immediate area is defined by a {@link Proximity} that is in charge of
|
||||
* finding and processing the owner's neighbors through the given {@link ProximityCallback}.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class GroupBehavior extends SteeringBehavior{
|
||||
/** The proximity decides which agents are considered neighbors. */
|
||||
protected Proximity proximity;
|
||||
|
||||
/**
|
||||
* Creates a GroupBehavior for the specified owner and proximity.
|
||||
* @param owner the owner of this behavior.
|
||||
* @param proximity the proximity to detect the owner's neighbors
|
||||
*/
|
||||
public GroupBehavior(Steerable owner, Proximity proximity){
|
||||
super(owner);
|
||||
this.proximity = proximity;
|
||||
}
|
||||
|
||||
/** Returns the proximity of this group behavior */
|
||||
public Proximity getProximity(){
|
||||
return proximity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the proximity of this group behavior
|
||||
* @param proximity the proximity to set
|
||||
*/
|
||||
public void setProximity(Proximity proximity){
|
||||
this.proximity = proximity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
/**
|
||||
* A {@code Limiter} provides the maximum magnitudes of speed and acceleration for both linear and angular components.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface Limiter{
|
||||
|
||||
/**
|
||||
* Returns the threshold below which the linear speed can be considered zero. It must be a small positive value near to zero.
|
||||
* Usually it is used to avoid updating the orientation when the velocity vector has a negligible length.
|
||||
*/
|
||||
default float getZeroLinearSpeedThreshold(){
|
||||
return 0.001f;
|
||||
}
|
||||
|
||||
/** Returns the maximum linear speed. */
|
||||
default float getMaxLinearSpeed(){
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
/** Returns the maximum linear acceleration. */
|
||||
default float getMaxLinearAcceleration(){
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
/** Returns the maximum angular speed. */
|
||||
default float getMaxAngularSpeed(){
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
|
||||
/** Returns the maximum angular acceleration. */
|
||||
default float getMaxAngularAcceleration(){
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
import mindustry.ai.ai.steer.behaviors.*;
|
||||
import mindustry.ai.ai.utils.Timepiece;
|
||||
|
||||
/**
|
||||
* A {@code Proximity} defines an area that is used by group behaviors to find and process the owner's neighbors.
|
||||
* <p>
|
||||
* Typically (but not necessarily) different group behaviors share the same {@code Proximity} for a given owner. This allows you to
|
||||
* combine group behaviors so as to get a more complex behavior also known as emergent behavior. Emergent behavior is behavior
|
||||
* that looks complex and/or purposeful to the observer but is actually derived spontaneously from fairly simple rules. The
|
||||
* lower-level agents following the rules have no idea of the bigger picture; they are only aware of themselves and maybe a few of
|
||||
* their neighbors. A typical example of emergence is flocking behavior which is a combination of three group behaviors:
|
||||
* {@link Separation separation}, {@link Alignment alignment}, and {@link Cohesion cohesion}. The three behaviors are typically
|
||||
* combined through a {@link BlendedSteering blended steering}. This works okay but, because of the limited view distance of a
|
||||
* character, it's possible for an agent to become isolated from its flock. If this happens, it will just sit still and do
|
||||
* nothing. To prevent this from happening, you usually add in the {@link Wander wander} behavior too. This way, all the agents
|
||||
* keep moving all the time. Tweaking the magnitudes of each of the contributing behaviors will give you different effects such as
|
||||
* shoals of fish, loose swirling flocks of birds, or bustling close-knit herds of sheep.
|
||||
* <p>
|
||||
* Before a steering acceleration can be calculated for a combination of group behaviors, the neighbors must be determined and
|
||||
* processed. This is done by the {@link #findNeighbors} method and its callback argument.
|
||||
* <p>
|
||||
* Notes:
|
||||
* <ul>
|
||||
* <li>Sharing a {@code Proximity} instance among group behaviors having the same owner can save a little time determining the
|
||||
* neighbors only once from inside the {@code findNeighbors} method. Especially, {@code Proximity} implementation classes can use
|
||||
* {@link mindustry.ai.ai.utils.Timepiece#getTime() GdxAI.getTimepiece().getTime()} to calculate neighbors only once per frame (assuming delta time is
|
||||
* always greater than 0, if time has changed the frame has changed too). This means that
|
||||
* <ul>
|
||||
* <li>if you forget to {@link Timepiece#update(float) update the timepiece} on each frame the proximity instance will be
|
||||
* calculated only the very first time, which is not what you want of course.</li>
|
||||
* <li>ideally the timepiece should be updated before the proximity is updated by the {@link #findNeighbors(ProximityCallback)}
|
||||
* method.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>If you want to make sure a Proximity doesn't use as a neighbor a given agent from the list, for example the evader or the
|
||||
* owner itself, you have to implement a callback that prevents it from being considered by returning {@code false} from the method
|
||||
* {@link ProximityCallback#report(Steerable) reportNeighbor}.</li>
|
||||
* <li>If there is some efficient way of pruning potential neighbors before they are processed, the overall performance in time
|
||||
* will improve. Spatial data structures such as multi-resolution maps, quad-trees, oct-trees, and binary space partition (BSP)
|
||||
* trees can be used to get potential neighbors more efficiently. Spatial partitioning techniques are crucial when you have to
|
||||
* deal with lots of agents. Especially, if you're using Bullet or Box2d in your game, it's recommended to implement proximities
|
||||
* that exploit their methods to query the world. Both Bullet and Box2d internally use some kind of spatial partitioning.</li>
|
||||
* </ul>
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface Proximity{
|
||||
|
||||
/** Returns the owner of this proximity. */
|
||||
Steerable getOwner();
|
||||
|
||||
/** Sets the owner of this proximity. */
|
||||
void setOwner(Steerable owner);
|
||||
|
||||
/**
|
||||
* Finds the agents that are within the immediate area of the owner. Each of those agents is passed to the
|
||||
* {@link ProximityCallback#report(Steerable) reportNeighbor} method of the specified callback.
|
||||
* @return the number of neighbors found.
|
||||
*/
|
||||
int findNeighbors(ProximityCallback callback);
|
||||
|
||||
/**
|
||||
* The callback object used by a proximity to report the owner's neighbor.
|
||||
* @author davebaol
|
||||
*/
|
||||
interface ProximityCallback{
|
||||
|
||||
/**
|
||||
* The callback method used to report a neighbor.
|
||||
* @param neighbor the reported neighbor.
|
||||
* @return {@code true} if the given neighbor is valid; {@code false} otherwise.
|
||||
*/
|
||||
boolean report(Steerable neighbor);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* A {@code Steerable} is a {@link Location} that gives access to the character's data required by steering system.
|
||||
* <p>
|
||||
* Notice that there is nothing to connect the direction that a Steerable is moving and the direction it is facing. For
|
||||
* instance, a character can be oriented along the x-axis but be traveling directly along the y-axis.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface Steerable extends Location, Limiter{
|
||||
|
||||
/** Returns the vector indicating the linear velocity of this Steerable. */
|
||||
Vec2 getLinearVelocity();
|
||||
|
||||
/** Returns the float value indicating the the angular velocity in radians of this Steerable. */
|
||||
float getAngularVelocity();
|
||||
|
||||
/** Returns the bounding radius of this Steerable. */
|
||||
float getBoundingRadius();
|
||||
|
||||
/** Returns {@code true} if this Steerable is tagged; {@code false} otherwise. */
|
||||
boolean isTagged();
|
||||
|
||||
/**
|
||||
* Tag/untag this Steerable. This is a generic flag utilized in a variety of ways.
|
||||
* @param tagged the boolean value to set
|
||||
*/
|
||||
void setTagged(boolean tagged);
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* An adapter class for {@link Steerable}. You can derive from this and only override what you are interested in. For example,
|
||||
* this comes in handy when you have to create on the fly a target for a particular behavior.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class SteerableAdapter implements Steerable{
|
||||
|
||||
@Override
|
||||
public float getMaxLinearSpeed(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLinearSpeed(float maxLinearSpeed){
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxLinearAcceleration(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxLinearAcceleration(float maxLinearAcceleration){
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxAngularSpeed(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxAngularSpeed(float maxAngularSpeed){
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMaxAngularAcceleration(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxAngularAcceleration(float maxAngularAcceleration){
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getPosition(){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getOrientation(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrientation(float orientation){
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getLinearVelocity(){
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getAngularVelocity(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getBoundingRadius(){
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTagged(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTagged(boolean tagged){
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* {@code SteeringAcceleration} is a movement requested by the steering system. It is made up of two components, linear and angular
|
||||
* acceleration.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class SteeringAcceleration{
|
||||
/** The linear component of this steering acceleration. */
|
||||
public Vec2 linear;
|
||||
|
||||
/** The angular component of this steering acceleration. */
|
||||
public float angular;
|
||||
|
||||
/**
|
||||
* Creates a {@code SteeringAcceleration} with the given linear acceleration and zero angular acceleration.
|
||||
* @param linear The initial linear acceleration to give this SteeringAcceleration.
|
||||
*/
|
||||
public SteeringAcceleration(Vec2 linear){
|
||||
this(linear, 0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SteeringAcceleration} with the given linear and angular components.
|
||||
* @param linear The initial linear acceleration to give this SteeringAcceleration.
|
||||
* @param angular The initial angular acceleration to give this SteeringAcceleration.
|
||||
*/
|
||||
public SteeringAcceleration(Vec2 linear, float angular){
|
||||
if(linear == null) throw new IllegalArgumentException("Linear acceleration cannot be null");
|
||||
this.linear = linear;
|
||||
this.angular = angular;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if both linear and angular components of this steering acceleration are zero; {@code false} otherwise. */
|
||||
public boolean isZero(){
|
||||
return angular == 0 && linear.isZero();
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeros the linear and angular components of this steering acceleration.
|
||||
* @return this steering acceleration for chaining
|
||||
*/
|
||||
public SteeringAcceleration setZero(){
|
||||
linear.setZero();
|
||||
angular = 0f;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given steering acceleration to this steering acceleration.
|
||||
* @param steering the steering acceleration
|
||||
* @return this steering acceleration for chaining
|
||||
*/
|
||||
public SteeringAcceleration add(SteeringAcceleration steering){
|
||||
linear.add(steering.linear);
|
||||
angular += steering.angular;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scales this steering acceleration by the specified scalar.
|
||||
* @param scalar the scalar
|
||||
* @return this steering acceleration for chaining
|
||||
*/
|
||||
public SteeringAcceleration scl(float scalar){
|
||||
linear.scl(scalar);
|
||||
angular *= scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* First scale a supplied steering acceleration, then add it to this steering acceleration.
|
||||
* @param steering the steering acceleration
|
||||
* @param scalar the scalar
|
||||
* @return this steering acceleration for chaining
|
||||
*/
|
||||
public SteeringAcceleration mulAdd(SteeringAcceleration steering, float scalar){
|
||||
linear.mulAdd(steering.linear, scalar);
|
||||
angular += steering.angular * scalar;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the square of the magnitude of this steering acceleration. This includes the angular component. */
|
||||
public float calculateSquareMagnitude(){
|
||||
return linear.len2() + angular * angular;
|
||||
}
|
||||
|
||||
/** Returns the magnitude of this steering acceleration. This includes the angular component. */
|
||||
public float calculateMagnitude(){
|
||||
return (float)Math.sqrt(calculateSquareMagnitude());
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package mindustry.ai.ai.steer;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* A {@code SteeringBehavior} calculates the linear and/or angular accelerations to be applied to its owner.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class SteeringBehavior{
|
||||
|
||||
/** The owner of this steering behavior */
|
||||
protected Steerable owner;
|
||||
|
||||
/** The limiter of this steering behavior */
|
||||
protected Limiter limiter;
|
||||
|
||||
/** A flag indicating whether this steering behavior is enabled or not. */
|
||||
protected boolean enabled;
|
||||
|
||||
/**
|
||||
* Creates a {@code SteeringBehavior} for the specified owner. The behavior is enabled and has no explicit limiter, meaning
|
||||
* that the owner is used instead.
|
||||
* @param owner the owner of this steering behavior
|
||||
*/
|
||||
public SteeringBehavior(Steerable owner){
|
||||
this(owner, null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SteeringBehavior} for the specified owner and limiter. The behavior is enabled.
|
||||
* @param owner the owner of this steering behavior
|
||||
* @param limiter the limiter of this steering behavior
|
||||
*/
|
||||
public SteeringBehavior(Steerable owner, Limiter limiter){
|
||||
this(owner, limiter, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SteeringBehavior} for the specified owner and activation flag. The behavior has no explicit limiter,
|
||||
* meaning that the owner is used instead.
|
||||
* @param owner the owner of this steering behavior
|
||||
* @param enabled a flag indicating whether this steering behavior is enabled or not
|
||||
*/
|
||||
public SteeringBehavior(Steerable owner, boolean enabled){
|
||||
this(owner, null, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code SteeringBehavior} for the specified owner, limiter and activation flag.
|
||||
* @param owner the owner of this steering behavior
|
||||
* @param limiter the limiter of this steering behavior
|
||||
* @param enabled a flag indicating whether this steering behavior is enabled or not
|
||||
*/
|
||||
public SteeringBehavior(Steerable owner, Limiter limiter, boolean enabled){
|
||||
this.owner = owner;
|
||||
this.limiter = limiter;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this behavior is enabled calculates the steering acceleration and writes it to the given steering output. If it is
|
||||
* disabled the steering output is set to zero.
|
||||
* @param steering the steering acceleration to be calculated.
|
||||
* @return the calculated steering acceleration for chaining.
|
||||
*/
|
||||
public SteeringAcceleration calculateSteering(SteeringAcceleration steering){
|
||||
return isEnabled() ? calculateRealSteering(steering) : steering.setZero();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the steering acceleration produced by this behavior and writes it to the given steering output.
|
||||
* <p>
|
||||
* This method is called by {@link #calculateSteering(SteeringAcceleration)} when this steering behavior is enabled.
|
||||
* @param steering the steering acceleration to be calculated.
|
||||
* @return the calculated steering acceleration for chaining.
|
||||
*/
|
||||
protected abstract SteeringAcceleration calculateRealSteering(SteeringAcceleration steering);
|
||||
|
||||
/** Returns the owner of this steering behavior. */
|
||||
public Steerable getOwner(){
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the owner of this steering behavior.
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public SteeringBehavior setOwner(Steerable owner){
|
||||
this.owner = owner;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the limiter of this steering behavior. */
|
||||
public Limiter getLimiter(){
|
||||
return limiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the limiter of this steering behavior.
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public SteeringBehavior setLimiter(Limiter limiter){
|
||||
this.limiter = limiter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns true if this steering behavior is enabled; false otherwise. */
|
||||
public boolean isEnabled(){
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this steering behavior on/off.
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public SteeringBehavior setEnabled(boolean enabled){
|
||||
this.enabled = enabled;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the actual limiter of this steering behavior. */
|
||||
protected Limiter getActualLimiter(){
|
||||
return limiter == null ? owner : limiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that creates a new vector.
|
||||
* <p>
|
||||
* This method is used internally to instantiate vectors of the correct type parameter {@code T}. This technique keeps the API
|
||||
* simple and makes the API easier to use with the GWVec2 backend because avoids the use of reflection.
|
||||
* @param location the location whose position is used to create the new vector
|
||||
* @return the newly created vector
|
||||
*/
|
||||
protected Vec2 newVector(Location location){
|
||||
return location.getPosition().cpy().setZero();
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.Proximity.*;
|
||||
|
||||
/**
|
||||
* {@code Alignment} is a group behavior producing a linear acceleration that attempts to keep the owner aligned with the agents in
|
||||
* its immediate area defined by the given {@link Proximity}. The acceleration is calculated by first iterating through all the
|
||||
* neighbors and averaging their linear velocity vectors. This value is the desired direction, so we just subtract the owner's
|
||||
* linear velocity to get the steering output.
|
||||
* <p>
|
||||
* Cars moving along roads demonstrate {@code Alignment} type behavior. They also demonstrate {@link Separation} as they try to
|
||||
* keep a minimum distance from each other.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Alignment extends GroupBehavior implements ProximityCallback{
|
||||
private Vec2 averageVelocity;
|
||||
|
||||
/**
|
||||
* Creates an {@code Alignment} behavior for the specified owner and proximity.
|
||||
* @param owner the owner of this behavior
|
||||
* @param proximity the proximity
|
||||
*/
|
||||
public Alignment(Steerable owner, Proximity proximity){
|
||||
super(owner, proximity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
steering.setZero();
|
||||
|
||||
averageVelocity = steering.linear;
|
||||
|
||||
int neighborCount = proximity.findNeighbors(this);
|
||||
|
||||
if(neighborCount > 0){
|
||||
// Average the accumulated velocities
|
||||
averageVelocity.scl(1f / neighborCount);
|
||||
|
||||
// Match the average velocity.
|
||||
// Notice that steering.linear and averageVelocity are the same vector here.
|
||||
averageVelocity.sub(owner.getLinearVelocity()).limit(getActualLimiter().getMaxLinearAcceleration());
|
||||
}
|
||||
|
||||
return steering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean report(Steerable neighbor){
|
||||
// Accumulate neighbor velocity
|
||||
averageVelocity.add(neighbor.getLinearVelocity());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code Arrive} behavior moves the agent towards a target position. It is similar to seek but it attempts to arrive at the target
|
||||
* position with a zero velocity.
|
||||
* <p>
|
||||
* {@code Arrive} behavior uses two radii. The {@code arrivalTolerance} lets the owner get near enough to the target without
|
||||
* letting small errors keep it in motion. The {@code decelerationRadius}, usually much larger than the previous one, specifies
|
||||
* when the incoming character will begin to slow down. The algorithm calculates an ideal speed for the owner. At the slowing-down
|
||||
* radius, this is equal to its maximum linear speed. At the target point, it is zero (we want to have zero speed when we arrive).
|
||||
* In between, the desired speed is an interpolated intermediate value, controlled by the distance from the target.
|
||||
* <p>
|
||||
* The direction toward the target is calculated and combined with the desired speed to give a target velocity. The algorithm
|
||||
* looks at the current velocity of the character and works out the acceleration needed to turn it into the target velocity. We
|
||||
* can't immediately change velocity, however, so the acceleration is calculated based on reaching the target velocity in a fixed
|
||||
* time scale known as {@code timeToTarget}. This is usually a small value; it defaults to 0.1 seconds which is a good starting
|
||||
* point.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Arrive extends SteeringBehavior{
|
||||
/** The target to arrive to. */
|
||||
public Location target;
|
||||
/**
|
||||
* The tolerance for arriving at the target. It lets the owner get near enough to the target without letting small errors keep
|
||||
* it in motion.
|
||||
*/
|
||||
public float arrivalTolerance;
|
||||
/** The radius for beginning to slow down */
|
||||
public float decelerationRadius;
|
||||
/** The time over which to achieve target speed */
|
||||
public float timeToTarget = 0.1f;
|
||||
|
||||
/**
|
||||
* Creates an {@code Arrive} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior
|
||||
*/
|
||||
public Arrive(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code Arrive} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior
|
||||
*/
|
||||
public Arrive(Steerable owner, Location target){
|
||||
super(owner);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
return arrive(steering, target.getPosition());
|
||||
}
|
||||
|
||||
protected SteeringAcceleration arrive(SteeringAcceleration steering, Vec2 targetPosition){
|
||||
// Get the direction and distance to the target
|
||||
Vec2 toTarget = steering.linear.set(targetPosition).sub(owner.getPosition());
|
||||
float distance = toTarget.len();
|
||||
|
||||
// Check if we are there, return no steering
|
||||
if(distance <= arrivalTolerance) return steering.setZero();
|
||||
|
||||
Limiter actualLimiter = getActualLimiter();
|
||||
// Go max speed
|
||||
float targetSpeed = actualLimiter.getMaxLinearSpeed();
|
||||
|
||||
// If we are inside the slow down radius calculate a scaled speed
|
||||
if(distance <= decelerationRadius) targetSpeed *= distance / decelerationRadius;
|
||||
|
||||
// Target velocity combines speed and direction
|
||||
Vec2 targetVelocity = toTarget.scl(targetSpeed / distance); // Optimized code for: toTarget.nor().scl(targetSpeed)
|
||||
|
||||
// Acceleration tries to get to the target velocity without exceeding max acceleration
|
||||
// Notice that steering.linear and targetVelocity are the same vector
|
||||
targetVelocity.sub(owner.getLinearVelocity()).scl(1f / timeToTarget).limit(actualLimiter.getMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0f;
|
||||
|
||||
// Output the steering
|
||||
return steering;
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* This combination behavior simply sums up all the behaviors, applies their weights, and truncates the result before returning.
|
||||
* There are no constraints on the blending weights; they don't have to sum to one, for example, and rarely do. Don't think of
|
||||
* {@code BlendedSteering} as a weighted mean, because it's not.
|
||||
* <p>
|
||||
* With {@code BlendedSteering} you can combine multiple behaviors to get a more complex behavior. It can work fine, but the
|
||||
* trade-off is that it comes with a few problems:
|
||||
* <ul>
|
||||
* <li>Since every active behavior is calculated every time step, it can be a costly method to process.</li>
|
||||
* <li>Behavior weights can be difficult to tweak. There have been research projects that have tried to evolve the steering
|
||||
* weights using genetic algorithms or neural networks. Results have not been encouraging, however, and manual experimentation
|
||||
* still seems to be the most sensible approach.</li>
|
||||
* <li>It's problematic with conflicting forces. For instance, a common scenario is where an agent is backed up against a wall by
|
||||
* several other agents. In this example, the separating forces from the neighboring agents can be greater than the repulsive
|
||||
* force from the wall and the agent can end up being pushed through the wall boundary. This is almost certainly not going to be
|
||||
* favorable. Sure you can make the weights for the wall avoidance huge, but then your agent may behave strangely next time it
|
||||
* finds itself alone and next to a wall.</li>
|
||||
* </ul>
|
||||
* @author davebaol
|
||||
*/
|
||||
public class BlendedSteering extends SteeringBehavior{
|
||||
|
||||
/** The list of behaviors and their corresponding blending weights. */
|
||||
protected Array<BehaviorAndWeight> list = new Array<>();
|
||||
|
||||
private SteeringAcceleration steering;
|
||||
|
||||
/**
|
||||
* Creates a {@code BlendedSteering} for the specified {@code owner}, {@code maxLinearAcceleration} and
|
||||
* {@code maxAngularAcceleration}.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public BlendedSteering(Steerable owner){
|
||||
super(owner);
|
||||
|
||||
this.list = new Array<>();
|
||||
this.steering = new SteeringAcceleration(newVector(owner));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a steering behavior and its weight to the list.
|
||||
* @param behavior the steering behavior to add
|
||||
* @param weight the weight of the behavior
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public BlendedSteering add(SteeringBehavior behavior, float weight){
|
||||
return add(new BehaviorAndWeight(behavior, weight));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a steering behavior and its weight to the list.
|
||||
* @param item the steering behavior and its weight
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public BlendedSteering add(BehaviorAndWeight item){
|
||||
item.behavior.setOwner(owner);
|
||||
list.add(item);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a steering behavior from the list.
|
||||
* @param item the steering behavior to remove
|
||||
*/
|
||||
public void remove(BehaviorAndWeight item){
|
||||
list.remove(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a steering behavior from the list.
|
||||
* @param behavior the steering behavior to remove
|
||||
*/
|
||||
public void remove(SteeringBehavior behavior){
|
||||
for(int i = 0; i < list.size; i++){
|
||||
if(list.get(i).behavior == behavior){
|
||||
list.remove(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the weighted behavior at the specified index.
|
||||
* @param index the index of the weighted behavior to return
|
||||
*/
|
||||
public BehaviorAndWeight get(int index){
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration blendedSteering){
|
||||
// Clear the output to start with
|
||||
blendedSteering.setZero();
|
||||
|
||||
// Go through all the behaviors
|
||||
int len = list.size;
|
||||
for(int i = 0; i < len; i++){
|
||||
BehaviorAndWeight bw = list.get(i);
|
||||
|
||||
// Calculate the behavior's steering
|
||||
bw.behavior.calculateSteering(steering);
|
||||
|
||||
// Scale and add the steering to the accumulator
|
||||
blendedSteering.mulAdd(steering, bw.weight);
|
||||
}
|
||||
|
||||
Limiter actualLimiter = getActualLimiter();
|
||||
|
||||
// Crop the result
|
||||
blendedSteering.linear.limit(actualLimiter.getMaxLinearAcceleration());
|
||||
if(blendedSteering.angular > actualLimiter.getMaxAngularAcceleration())
|
||||
blendedSteering.angular = actualLimiter.getMaxAngularAcceleration();
|
||||
|
||||
return blendedSteering;
|
||||
}
|
||||
|
||||
//
|
||||
// Nested classes
|
||||
//
|
||||
|
||||
public static class BehaviorAndWeight{
|
||||
|
||||
protected SteeringBehavior behavior;
|
||||
protected float weight;
|
||||
|
||||
public BehaviorAndWeight(SteeringBehavior behavior, float weight){
|
||||
this.behavior = behavior;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public SteeringBehavior getBehavior(){
|
||||
return behavior;
|
||||
}
|
||||
|
||||
public void setBehavior(SteeringBehavior behavior){
|
||||
this.behavior = behavior;
|
||||
}
|
||||
|
||||
public float getWeight(){
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(float weight){
|
||||
this.weight = weight;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.Proximity.*;
|
||||
|
||||
/**
|
||||
* {@code Cohesion} is a group behavior producing a linear acceleration that attempts to move the agent towards the center of mass
|
||||
* of the agents in its immediate area defined by the given {@link Proximity}. The acceleration is calculated by first iterating
|
||||
* through all the neighbors and averaging their position vectors. This gives us the center of mass of the neighbors, the place
|
||||
* the agents wants to get to, so it seeks to that position.
|
||||
* <p>
|
||||
* A sheep running after its flock is demonstrating cohesive behavior. Use this behavior to keep a group of agents together.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Cohesion extends GroupBehavior implements ProximityCallback{
|
||||
|
||||
private Vec2 centerOfMass;
|
||||
|
||||
/**
|
||||
* Creates a {@code Cohesion} for the specified owner and proximity.
|
||||
* @param owner the owner of this behavior.
|
||||
* @param proximity the proximity to detect the owner's neighbors
|
||||
*/
|
||||
public Cohesion(Steerable owner, Proximity proximity){
|
||||
super(owner, proximity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
|
||||
steering.setZero();
|
||||
|
||||
centerOfMass = steering.linear;
|
||||
|
||||
int neighborCount = proximity.findNeighbors(this);
|
||||
|
||||
if(neighborCount > 0){
|
||||
|
||||
// The center of mass is the average of the sum of positions
|
||||
centerOfMass.scl(1f / neighborCount);
|
||||
|
||||
// Now seek towards that position.
|
||||
centerOfMass.sub(owner.getPosition()).nor().scl(getActualLimiter().getMaxLinearAcceleration());
|
||||
}
|
||||
|
||||
return steering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean report(Steerable neighbor){
|
||||
// Accumulate neighbor position
|
||||
centerOfMass.add(neighbor.getPosition());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.Proximity.*;
|
||||
|
||||
/**
|
||||
* {@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).
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* This algorithm works well with small and/or moving obstacles whose shape can be approximately represented by a center and a
|
||||
* radius.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class CollisionAvoidance extends GroupBehavior implements ProximityCallback{
|
||||
private float shortestTime;
|
||||
private Steerable firstNeighbor;
|
||||
private float firstMinSeparation;
|
||||
private float firstDistance;
|
||||
private Vec2 firstRelativePosition;
|
||||
private Vec2 firstRelativeVelocity;
|
||||
private Vec2 relativePosition;
|
||||
private Vec2 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
|
||||
//
|
||||
// NOTE: You might think that the condition below always evaluates to true since
|
||||
// firstNeighbor has been set to null when entering this method. In fact, we have just
|
||||
// executed findNeighbors(this) that has possibly set firstNeighbor to a non null value
|
||||
// through the method reportNeighbor defined below.
|
||||
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 report(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 timeToCollision 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* {@code Evade} behavior is almost the same as {@link Pursue} except that the agent flees from the estimated future position of
|
||||
* the pursuer. Indeed, reversing the acceleration is all we have to do.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Evade extends Pursue{
|
||||
|
||||
/**
|
||||
* Creates a {@code Evade} behavior for the specified owner and target. Maximum prediction time defaults to 1 second.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior, typically a pursuer.
|
||||
*/
|
||||
public Evade(Steerable owner, Steerable target){
|
||||
this(owner, target, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Evade} behavior for the specified owner and pursuer.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior, typically a pursuer
|
||||
* @param maxPredictionTime the max time used to predict the pursuer's position assuming it continues to move with its current
|
||||
* velocity.
|
||||
*/
|
||||
public Evade(Steerable owner, Steerable target, float maxPredictionTime){
|
||||
super(owner, target, maxPredictionTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getActualMaxLinearAcceleration(){
|
||||
// Simply return the opposite of the max linear acceleration so to evade the target
|
||||
return -getActualLimiter().getMaxLinearAcceleration();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code Face} behavior makes the owner look at its target. It delegates to the {@link ReachOrientation} behavior to perform the
|
||||
* rotation but calculates the target orientation first based on target and owner position.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Face extends ReachOrientation{
|
||||
|
||||
/**
|
||||
* Creates a {@code Face} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public Face(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Face} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior.
|
||||
*/
|
||||
public Face(Steerable owner, Location target){
|
||||
super(owner, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
return face(steering, target.getPosition());
|
||||
}
|
||||
|
||||
protected SteeringAcceleration face(SteeringAcceleration steering, Vec2 targetPosition){
|
||||
// Get the direction to target
|
||||
Vec2 toTarget = steering.linear.set(targetPosition).sub(owner.getPosition());
|
||||
|
||||
// Check for a zero direction, and return no steering if so
|
||||
if(toTarget.isZero(getActualLimiter().getZeroLinearSpeedThreshold())) return steering.setZero();
|
||||
|
||||
// Calculate the orientation to face the target
|
||||
float orientation = owner.vectorToAngle(toTarget);
|
||||
|
||||
// Delegate to ReachOrientation
|
||||
return reachOrientation(steering, orientation);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code Flee} behavior does the opposite of {@link Seek}. It produces a linear steering force that moves the agent away from a
|
||||
* target position.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Flee extends Seek{
|
||||
|
||||
/**
|
||||
* Creates a {@code Flee} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public Flee(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Flee} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target agent of this behavior.
|
||||
*/
|
||||
public Flee(Steerable owner, Location target){
|
||||
super(owner, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// We just do the opposite of seek, i.e. (owner.getPosition() - target.getPosition())
|
||||
// instead of (target.getPosition() - owner.getPosition())
|
||||
steering.linear.set(owner.getPosition()).sub(target.getPosition()).nor().scl(getActualLimiter().getMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
// Output steering acceleration
|
||||
return steering;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* The {@code FollowFlowField} behavior produces a linear acceleration that tries to align the motion of the owner with the local
|
||||
* tangent of a flow field. The flow field defines a mapping from a location in space to a flow vector. Any flow field can be used
|
||||
* as the basis of this steering behavior, although it is sensitive to discontinuities in the field.
|
||||
* <p>
|
||||
* For instance, flow fields can be used for simulating various effects, such as magnetic fields, an irregular gust of wind or the
|
||||
* meandering path of a river. They can be generated by a simple random algorithm, a Perlin noise or a complicated image
|
||||
* processing. And of course flow fields can be dynamic. The only limit is your imagination.
|
||||
* <p>
|
||||
* Like {@link FollowPath}, this behavior can work in a predictive manner when its {@code predictionTime} is greater than 0.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class FollowFlowField extends SteeringBehavior{
|
||||
/** The flow field to follow. */
|
||||
public FlowField flowField;
|
||||
/** The time in the future to predict the owner's position. Set it to 0 for non-predictive flow field following. */
|
||||
public float predictionTime;
|
||||
|
||||
/**
|
||||
* Creates a non-predictive {@code FollowFlowField} for the specified owner.
|
||||
* @param owner the owner of this behavior
|
||||
*/
|
||||
public FollowFlowField(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-predictive {@code FollowFlowField} for the specified owner and flow field. Prediction time defaults to 0.
|
||||
* @param owner the owner of this behavior
|
||||
* @param flowField the flow field to follow
|
||||
*/
|
||||
public FollowFlowField(Steerable owner, FlowField flowField){
|
||||
this(owner, flowField, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FollowFlowField} with the specified owner, flow field and prediction time.
|
||||
* @param owner the owner of this behavior
|
||||
* @param flowField the flow field to follow
|
||||
* @param predictionTime the time in the future to predict the owner's position. Can be 0 for non-predictive flow field
|
||||
* following.
|
||||
*/
|
||||
public FollowFlowField(Steerable owner, FlowField flowField, float predictionTime){
|
||||
super(owner);
|
||||
this.flowField = flowField;
|
||||
this.predictionTime = predictionTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Predictive or non-predictive behavior?
|
||||
Vec2 location = (predictionTime == 0) ?
|
||||
// Use the current position of the owner
|
||||
owner.getPosition()
|
||||
:
|
||||
// Calculate the predicted future position of the owner. We're reusing steering.linear here.
|
||||
steering.linear.set(owner.getPosition()).mulAdd(owner.getLinearVelocity(), predictionTime);
|
||||
|
||||
// Retrieve the flow vector at the specified location
|
||||
Vec2 flowVector = flowField.lookup(location);
|
||||
|
||||
// Clear both linear and angular components
|
||||
steering.setZero();
|
||||
|
||||
if(flowVector != null && !flowVector.isZero()){
|
||||
Limiter actualLimiter = getActualLimiter();
|
||||
|
||||
// Calculate linear acceleration
|
||||
steering.linear.mulAdd(flowVector, actualLimiter.getMaxLinearSpeed()).sub(owner.getLinearVelocity())
|
||||
.limit(actualLimiter.getMaxLinearAcceleration());
|
||||
}
|
||||
|
||||
// Output steering
|
||||
return steering;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code FlowField} defines a mapping from a location in space to a flow vector. Typically flow fields are implemented as a
|
||||
* multidimensional array representing a grid of cells. In each cell of the grid lives a flow vector.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface FlowField{
|
||||
/**
|
||||
* Returns the flow vector for the specified position in space.
|
||||
* @param position the position to map
|
||||
*/
|
||||
Vec2 lookup(Vec2 position);
|
||||
}
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.utils.Path;
|
||||
import mindustry.ai.ai.steer.utils.Path.*;
|
||||
|
||||
/**
|
||||
* {@code FollowPath} behavior produces a linear acceleration that moves the agent along the given path. First it calculates the
|
||||
* agent location based on the specified prediction time. Then it works out the position of the internal target based on the
|
||||
* location just calculated and the shape of the path. It finally uses {@link Seek seek} behavior to move the owner towards the
|
||||
* internal target position. However, if the path is open {@link Arrive arrive} behavior is used to approach path's extremities
|
||||
* when they are far less than the {@link FollowPath#decelerationRadius deceleration radius} from the internal target position.
|
||||
* <p>
|
||||
* For complex paths with sudden changes of direction the predictive behavior (i.e., with prediction time greater than 0) can
|
||||
* appear smoother than the non-predictive one (i.e., with no prediction time). However, predictive path following has the
|
||||
* downside of cutting corners when some sections of the path come close together. This cutting-corner attitude can make the
|
||||
* character miss a whole section of the path. This might not be what you want if, for example, the path represents a patrol
|
||||
* route.
|
||||
* @param <P> Type of path parameter implementing the {@link PathParam} interface
|
||||
* @author davebaol
|
||||
*/
|
||||
public class FollowPath<P extends PathParam> extends Arrive{
|
||||
/** The path to follow */
|
||||
public Path<P> path;
|
||||
/** The distance along the path to generate the target. Can be negative if the owner has to move along the reverse direction. */
|
||||
public float pathOffset;
|
||||
/** The current position on the path */
|
||||
public P pathParam;
|
||||
/** The flag indicating whether to use {@link Arrive} behavior to approach the end of an open path. It defaults to {@code true}. */
|
||||
public boolean arriveEnabled;
|
||||
/** The time in the future to predict the owner's position. Set it to 0 for non-predictive path following. */
|
||||
public float predictionTime;
|
||||
|
||||
private Vec2 internalTargetPosition;
|
||||
|
||||
/**
|
||||
* Creates a non-predictive {@code FollowPath} behavior for the specified owner and path.
|
||||
* @param owner the owner of this behavior
|
||||
* @param path the path to be followed by the owner.
|
||||
*/
|
||||
public FollowPath(Steerable owner, Path<P> path){
|
||||
this(owner, path, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-predictive {@code FollowPath} behavior for the specified owner, path and path offset.
|
||||
* @param owner the owner of this behavior
|
||||
* @param path the path to be followed by the owner
|
||||
* @param pathOffset the distance along the path to generate the target. Can be negative if the owner is to move along the
|
||||
* reverse direction.
|
||||
*/
|
||||
public FollowPath(Steerable owner, Path<P> path, float pathOffset){
|
||||
this(owner, path, pathOffset, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FollowPath} behavior for the specified owner, path, path offset, maximum linear acceleration and prediction
|
||||
* time.
|
||||
* @param owner the owner of this behavior
|
||||
* @param path the path to be followed by the owner
|
||||
* @param pathOffset the distance along the path to generate the target. Can be negative if the owner is to move along the
|
||||
* reverse direction.
|
||||
* @param predictionTime the time in the future to predict the owner's position. Can be 0 for non-predictive path following.
|
||||
*/
|
||||
public FollowPath(Steerable owner, Path<P> path, float pathOffset, float predictionTime){
|
||||
super(owner);
|
||||
this.path = path;
|
||||
this.pathParam = path.createParam();
|
||||
this.pathOffset = pathOffset;
|
||||
this.predictionTime = predictionTime;
|
||||
|
||||
this.arriveEnabled = true;
|
||||
|
||||
this.internalTargetPosition = newVector(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
|
||||
// Predictive or non-predictive behavior?
|
||||
Vec2 location = (predictionTime == 0) ?
|
||||
// Use the current position of the owner
|
||||
owner.getPosition()
|
||||
:
|
||||
// Calculate the predicted future position of the owner. We're reusing steering.linear here.
|
||||
steering.linear.set(owner.getPosition()).mulAdd(owner.getLinearVelocity(), predictionTime);
|
||||
|
||||
// Find the distance from the start of the path
|
||||
float distance = path.calculateDistance(location, pathParam);
|
||||
|
||||
// Offset it
|
||||
float targetDistance = distance + pathOffset;
|
||||
|
||||
// Calculate the target position
|
||||
path.calculateTargetPosition(internalTargetPosition, pathParam, targetDistance);
|
||||
|
||||
if(arriveEnabled && path.isOpen()){
|
||||
if(pathOffset >= 0){
|
||||
// Use Arrive to approach the last point of the path
|
||||
if(targetDistance > path.getLength() - decelerationRadius) return arrive(steering, internalTargetPosition);
|
||||
}else{
|
||||
// Use Arrive to approach the first point of the path
|
||||
if(targetDistance < decelerationRadius) return arrive(steering, internalTargetPosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Seek the target position
|
||||
steering.linear.set(internalTargetPosition).sub(owner.getPosition()).nor()
|
||||
.scl(getActualLimiter().getMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
// Output steering acceleration
|
||||
return steering;
|
||||
}
|
||||
|
||||
/** Returns the current position of the internal target. This method is useful for debug purpose. */
|
||||
public Vec2 getInternalTargetPosition(){
|
||||
return internalTargetPosition;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.Proximity.*;
|
||||
import mindustry.ai.ai.steer.proximities.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* This behavior attempts to position a owner so that an obstacle is always between itself and the agent (the hunter) it's trying
|
||||
* to hide from. First the distance to each of these obstacles is determined. Then the owner uses the arrive behavior to steer
|
||||
* toward the closest one. If no appropriate obstacles can be found, no steering is returned.
|
||||
* <p>
|
||||
* You can use this behavior not only for situations where you require a non-player character (NPC) to hide from the player, like
|
||||
* find cover when fired at, but also in situations where you would like an NPC to sneak up on a player. For example, you can
|
||||
* create an NPC capable of stalking a player through a gloomy forest, darting from tree to tree.
|
||||
* <p>
|
||||
* It's worth mentioning that since this behavior can produce no steering acceleration it is commonly used with
|
||||
* {@link PrioritySteering}. For instance, to make the owner go away from the target if there are no obstacles nearby to hide
|
||||
* behind, just use {@link Hide} and {@link Evade} behaviors with this priority order.
|
||||
* <p>
|
||||
* There are a few interesting modifications you might want to make to this behavior:
|
||||
* <ul>
|
||||
* <li>With {@link FieldOfViewProximity} you can allow the owner to hide only if the target is within its field of view. This
|
||||
* tends to produce unsatisfactory performance though, because the owner starts to act like a child hiding from monsters beneath
|
||||
* the bed sheets, something like "if you can't see it, then it can't see you" effect making the owner look dumb. This can be
|
||||
* countered slightly though by adding in a time effect so that the owner will hide if the target is visible or if it has seen the
|
||||
* target within the last {@code N} seconds. This gives it a sort of memory and produces reasonable-looking behavior.</li>
|
||||
* <li>The same as above, but this time the owner only tries to hide if the owner can see the target and the target can see the
|
||||
* owner.
|
||||
* <li>It might be desirable to produce a force that steers the owner so that it always favors hiding positions that are to the
|
||||
* side or rear of the pursuer. This can be achieved easily using the dot product to bias the distances returned from the method
|
||||
* {@link #getHidingPosition}.</li>
|
||||
* <li>At the beginning of any of the methods a check can be made to test if the target is within a "threat distance" before
|
||||
* proceeding with any further calculations. If the target is not a threat, then the method can return immediately with zero
|
||||
* steering.</li>
|
||||
* </ul>
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Hide extends Arrive implements ProximityCallback{
|
||||
/** The proximity to find nearby obstacles. */
|
||||
public Proximity proximity;
|
||||
/** The distance from the boundary of the obstacle behind which to hide. */
|
||||
public float distanceFromBoundary;
|
||||
|
||||
private Vec2 toObstacle;
|
||||
private Vec2 bestHidingSpot;
|
||||
private float distance2ToClosest;
|
||||
|
||||
/**
|
||||
* Creates an {@code Hide} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior
|
||||
*/
|
||||
public Hide(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Hide} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior
|
||||
*/
|
||||
public Hide(Steerable owner, Location target){
|
||||
this(owner, target, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Hide} behavior for the specified owner, target and proximity.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior
|
||||
* @param proximity the proximity to find nearby obstacles
|
||||
*/
|
||||
public Hide(Steerable owner, Location target, Proximity proximity){
|
||||
super(owner, target);
|
||||
this.proximity = proximity;
|
||||
|
||||
this.bestHidingSpot = newVector(owner);
|
||||
this.toObstacle = null; // Set to null since we'll reuse steering.linear for this vector
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Initialize member variables used by the callback
|
||||
this.distance2ToClosest = Float.POSITIVE_INFINITY;
|
||||
this.toObstacle = steering.linear;
|
||||
|
||||
// Find neighbors (the obstacles) using this behavior as callback
|
||||
int neighborsCount = proximity.findNeighbors(this);
|
||||
|
||||
// If no suitable obstacles found return no steering otherwise use Arrive on the hiding spot
|
||||
return neighborsCount == 0 ? steering.setZero() : arrive(steering, bestHidingSpot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean report(Steerable neighbor){
|
||||
// Calculate the position of the hiding spot for this obstacle
|
||||
Vec2 hidingSpot = getHidingPosition(neighbor.getPosition(), neighbor.getBoundingRadius(), target.getPosition());
|
||||
|
||||
// Work in distance-squared space to find the closest hiding
|
||||
// spot to the owner
|
||||
float distance2 = hidingSpot.dst2(owner.getPosition());
|
||||
if(distance2 < distance2ToClosest){
|
||||
distance2ToClosest = distance2;
|
||||
bestHidingSpot.set(hidingSpot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the position of a target and the position and radius of an obstacle, this method calculates a position
|
||||
* {@code distanceFromBoundary} away from the object's bounding radius and directly opposite the target. It does this by scaling
|
||||
* the normalized "to obstacle" vector by the required distance away from the center of the obstacle and then adding the result
|
||||
* to the obstacle's position.
|
||||
* @return the hiding position behind the obstacle.
|
||||
*/
|
||||
protected Vec2 getHidingPosition(Vec2 obstaclePosition, float obstacleRadius, Vec2 targetPosition){
|
||||
// Calculate how far away the agent is to be from the chosen
|
||||
// obstacle's bounding radius
|
||||
float distanceAway = obstacleRadius + distanceFromBoundary;
|
||||
|
||||
// Calculate the normalized vector toward the obstacle from the target
|
||||
toObstacle.set(obstaclePosition).sub(targetPosition).nor();
|
||||
|
||||
// Scale it to size and add to the obstacle's position to get
|
||||
// the hiding spot.
|
||||
return toObstacle.scl(distanceAway).add(obstaclePosition);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* {@code Interpose} behavior produces a steering force that moves the owner to a point along the imaginary line connecting two
|
||||
* other agents. A bodyguard taking a bullet for his employer or a soccer player intercepting a pass are examples of this type of
|
||||
* behavior. Like {@code Pursue}, the owner must estimate where the two agents are going to be located at a time {@code t} in the
|
||||
* future. It can then steer toward that position using the {@link Arrive} behavior. But how do we know what the best value of
|
||||
* {@code t} is to use? The answer is, we don't, so we make a calculated guess instead.
|
||||
* <p>
|
||||
* The first step is to determine a point along the imaginary line connecting the positions of the agents at the current time
|
||||
* step. This point is found taking into account the {@code interpositionRatio}, a number between 0 and 1 where 0 is the position
|
||||
* of the first agent (agentA) and 1 is the position of the second agent (agentB). Values in between are interpolated intermediate
|
||||
* locations.
|
||||
* <p>
|
||||
* Then the distance from this point is computed and the value divided by the owner's maximum speed to give the time {@code t}
|
||||
* required to travel the distance.
|
||||
* <p>
|
||||
* Using the time {@code t}, the agents' positions are extrapolated into the future. The target position in between of these
|
||||
* predicted positions is determined and finally the owner uses the Arrive behavior to steer toward that point.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Interpose extends Arrive{
|
||||
public Steerable agentA;
|
||||
public Steerable agentB;
|
||||
public float interpositionRatio;
|
||||
|
||||
private Vec2 internalTargetPosition;
|
||||
|
||||
/**
|
||||
* Creates an {@code Interpose} behavior for the specified owner and agents using the midpoint between agents as the target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param agentA the first agent
|
||||
* @param agentB the other agent
|
||||
*/
|
||||
public Interpose(Steerable owner, Steerable agentA, Steerable agentB){
|
||||
this(owner, agentA, agentB, 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@code Interpose} behavior for the specified owner and agents using the the given interposing ratio.
|
||||
* @param owner the owner of this behavior
|
||||
* @param agentA the first agent
|
||||
* @param agentB the other agent
|
||||
* @param interpositionRatio a number between 0 and 1 indicating the percentage of the distance between the 2 agents that the
|
||||
* owner should reach, where 0 is agentA position and 1 is agentB position.
|
||||
*/
|
||||
public Interpose(Steerable owner, Steerable agentA, Steerable agentB, float interpositionRatio){
|
||||
super(owner);
|
||||
this.agentA = agentA;
|
||||
this.agentB = agentB;
|
||||
this.interpositionRatio = interpositionRatio;
|
||||
|
||||
this.internalTargetPosition = newVector(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// First we need to figure out where the two agents are going to be at
|
||||
// time Vec2 in the future. This is approximated by determining the time
|
||||
// taken by the owner to reach the desired point between the 2 agents
|
||||
// at the current time at the max speed. This desired point P is given by
|
||||
// P = posA + interpositionRatio * (posB - posA)
|
||||
internalTargetPosition.set(agentB.getPosition()).sub(agentA.getPosition()).scl(interpositionRatio)
|
||||
.add(agentA.getPosition());
|
||||
|
||||
float timeToTargetPosition = owner.getPosition().dst(internalTargetPosition) / getActualLimiter().getMaxLinearSpeed();
|
||||
|
||||
// Now we have the time, we assume that agent A and agent B will continue on a
|
||||
// straight trajectory and extrapolate to get their future positions.
|
||||
// Note that here we are reusing steering.linear vector as agentA future position
|
||||
// and targetPosition as agentB future position.
|
||||
steering.linear.set(agentA.getPosition()).mulAdd(agentA.getLinearVelocity(), timeToTargetPosition);
|
||||
internalTargetPosition.set(agentB.getPosition()).mulAdd(agentB.getLinearVelocity(), timeToTargetPosition);
|
||||
|
||||
// Calculate the target position between these predicted positions
|
||||
internalTargetPosition.sub(steering.linear).scl(interpositionRatio).add(steering.linear);
|
||||
|
||||
// Finally delegate to Arrive
|
||||
return arrive(steering, internalTargetPosition);
|
||||
}
|
||||
|
||||
/** Returns the current position of the internal target. This method is useful for debug purpose. */
|
||||
public Vec2 getInternalTargetPosition(){
|
||||
return internalTargetPosition;
|
||||
}
|
||||
}
|
||||
@@ -1,362 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* First the {@code Jump} behavior calculates the linear velocity required to achieve the jump. If the calculated velocity doesn't
|
||||
* exceed the maximum linear velocity the jump is achievable; otherwise it's not. In either cases, the given callback gets
|
||||
* informed through the {@link JumpCallback#reportAchievability(boolean) reportAchievability} method. Also, if the jump is
|
||||
* achievable the run up phase begins and the {@code Jump} behavior will start to produce the linear acceleration required to match
|
||||
* the calculated velocity. Once the jump point and the linear velocity are reached with a precision within the given tolerance
|
||||
* the callback is told to jump through the {@link JumpCallback#takeoff(float, float) takeoff} method.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Jump extends MatchVelocity{
|
||||
|
||||
/** The jump descriptor to use */
|
||||
protected JumpDescriptor jumpDescriptor;
|
||||
|
||||
/**
|
||||
* The gravity vector to use. Notice that this behavior only supports gravity along a single axis, which must be the one
|
||||
* returned by the {@link GravityComponentHandler#getComponent(Vec2)} method.
|
||||
*/
|
||||
protected Vec2 gravity;
|
||||
|
||||
protected GravityComponentHandler gravityComponentHandler;
|
||||
|
||||
protected JumpCallback callback;
|
||||
|
||||
protected float takeoffPositionTolerance;
|
||||
protected float takeoffVelocityTolerance;
|
||||
|
||||
/** The maximum vertical component of jump velocity, where "vertical" stands for the axis where gravity operates. */
|
||||
protected float maxVerticalVelocity;
|
||||
|
||||
/** Keeps track of whether the jump is achievable */
|
||||
private boolean isJumpAchievable;
|
||||
|
||||
protected float airborneTime = 0;
|
||||
|
||||
private JumpTarget jumpTarget;
|
||||
private Vec2 planarVelocity;
|
||||
|
||||
/**
|
||||
* Creates a {@code Jump} behavior.
|
||||
* @param owner the owner of this behavior
|
||||
* @param jumpDescriptor the descriptor of the jump to make
|
||||
* @param gravity the gravity vector
|
||||
* @param gravityComponentHandler the handler giving access to the vertical axis
|
||||
* @param callback the callback that gets informed about jump achievability and when to jump
|
||||
*/
|
||||
public Jump(Steerable owner, JumpDescriptor jumpDescriptor, Vec2 gravity,
|
||||
GravityComponentHandler gravityComponentHandler, JumpCallback callback){
|
||||
super(owner);
|
||||
this.gravity = gravity;
|
||||
this.gravityComponentHandler = gravityComponentHandler;
|
||||
setJumpDescriptor(jumpDescriptor);
|
||||
this.callback = callback;
|
||||
|
||||
this.jumpTarget = new JumpTarget(owner);
|
||||
this.planarVelocity = newVector(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Check if we have a trajectory, and create one if not.
|
||||
if(target == null){
|
||||
target = calculateTarget();
|
||||
callback.reportAchievability(isJumpAchievable);
|
||||
}
|
||||
|
||||
// If the trajectory is zero, return no steering acceleration
|
||||
if(!isJumpAchievable) return steering.setZero();
|
||||
|
||||
// Check if the owner has reached target position and velocity with acceptable tolerance
|
||||
if(owner.getPosition().epsilonEquals(target.getPosition(), takeoffPositionTolerance)){
|
||||
if(owner.getLinearVelocity().epsilonEquals(target.getLinearVelocity(), takeoffVelocityTolerance)){
|
||||
isJumpAchievable = false;
|
||||
// Perform the jump, and return no steering (the owner is airborne, no need to steer).
|
||||
callback.takeoff(maxVerticalVelocity, airborneTime);
|
||||
return steering.setZero();
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate to MatchVelocity
|
||||
return super.calculateRealSteering(steering);
|
||||
}
|
||||
|
||||
/** Works out the trajectory calculation. */
|
||||
private Steerable calculateTarget(){
|
||||
this.jumpTarget.position = jumpDescriptor.takeoffPosition;
|
||||
this.airborneTime = calculateAirborneTimeAndVelocity(jumpTarget.linearVelocity, jumpDescriptor, getActualLimiter()
|
||||
.getMaxLinearSpeed());
|
||||
this.isJumpAchievable = airborneTime >= 0;
|
||||
return jumpTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the airborne time and sets the {@code outVelocity} vector to the airborne planar velocity required to achieve the
|
||||
* jump. If the jump is not achievable -1 is returned and the {@code outVelocity} vector remains unchanged.
|
||||
* <p>
|
||||
* Be aware that you should avoid using unlimited or very high max velocity, because this might produce a time of flight close
|
||||
* to 0. Actually, the motion equation for Vec2 has 2 solutions and Jump always try to use the fastest time.
|
||||
* @param outVelocity the output vector where the airborne planar velocity is calculated
|
||||
* @param jumpDescriptor the jump descriptor
|
||||
* @param maxLinearSpeed the maximum linear speed that can be used to achieve the jump
|
||||
* @return the time of flight or -1 if the jump is not achievable using the given max linear speed.
|
||||
*/
|
||||
public float calculateAirborneTimeAndVelocity(Vec2 outVelocity, JumpDescriptor jumpDescriptor, float maxLinearSpeed){
|
||||
float g = gravityComponentHandler.getComponent(gravity);
|
||||
|
||||
// Calculate the first jump time, see time of flight at http://hyperphysics.phy-astr.gsu.edu/hbase/traj.html
|
||||
// Notice that the equation has 2 solutions. We'd ideally like to achieve the jump in the fastest time
|
||||
// possible, so we want to use the smaller of the two values. However, this time value might give us
|
||||
// an impossible launch velocity (required speed greater than the max), so we need to check and
|
||||
// use the higher value if necessary.
|
||||
float sqrtTerm = (float)Math.sqrt(2f * g * gravityComponentHandler.getComponent(jumpDescriptor.delta)
|
||||
+ maxVerticalVelocity * maxVerticalVelocity);
|
||||
float time = (-maxVerticalVelocity + sqrtTerm) / g;
|
||||
|
||||
// Check if we can use it
|
||||
if(!checkAirborneTimeAndCalculateVelocity(outVelocity, time, jumpDescriptor, maxLinearSpeed)){
|
||||
// Otherwise try the other time
|
||||
time = (-maxVerticalVelocity - sqrtTerm) / g;
|
||||
if(!checkAirborneTimeAndCalculateVelocity(outVelocity, time, jumpDescriptor, maxLinearSpeed)){
|
||||
return -1f; // Unachievable jump
|
||||
}
|
||||
}
|
||||
return time; // Achievable jump
|
||||
}
|
||||
|
||||
private boolean checkAirborneTimeAndCalculateVelocity(Vec2 outVelocity, float time, JumpDescriptor jumpDescriptor,
|
||||
float maxLinearSpeed){
|
||||
// Calculate the planar velocity
|
||||
planarVelocity.set(jumpDescriptor.delta).scl(1f / time);
|
||||
gravityComponentHandler.setComponent(planarVelocity, 0f);
|
||||
|
||||
// Check the planar linear speed
|
||||
if(planarVelocity.len2() < maxLinearSpeed * maxLinearSpeed){
|
||||
// We have a valid solution, so store it by merging vertical and non-vertical axes
|
||||
float verticalValue = gravityComponentHandler.getComponent(outVelocity);
|
||||
gravityComponentHandler.setComponent(outVelocity.set(planarVelocity), verticalValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the jump descriptor. */
|
||||
public JumpDescriptor getJumpDescriptor(){
|
||||
return jumpDescriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the jump descriptor to use.
|
||||
* @param jumpDescriptor the jump descriptor to set
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public Jump setJumpDescriptor(JumpDescriptor jumpDescriptor){
|
||||
this.jumpDescriptor = jumpDescriptor;
|
||||
this.target = null;
|
||||
this.isJumpAchievable = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the gravity vector. */
|
||||
public Vec2 getGravity(){
|
||||
return gravity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the gravity vector.
|
||||
* @param gravity the gravity to set
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public Jump setGravity(Vec2 gravity){
|
||||
this.gravity = gravity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the maximum vertical component of jump velocity, where "vertical" stands for the axis where gravity operates. */
|
||||
public float getMaxVerticalVelocity(){
|
||||
return maxVerticalVelocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum vertical component of jump velocity, where "vertical" stands for the axis where gravity operates.
|
||||
* @param maxVerticalVelocity the maximum vertical velocity to set
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public Jump setMaxVerticalVelocity(float maxVerticalVelocity){
|
||||
this.maxVerticalVelocity = maxVerticalVelocity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the tolerance used to check if the owner has reached the takeoff location. */
|
||||
public float getTakeoffPositionTolerance(){
|
||||
return takeoffPositionTolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tolerance used to check if the owner has reached the takeoff location.
|
||||
* @param takeoffPositionTolerance the takeoff position tolerance to set
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public Jump setTakeoffPositionTolerance(float takeoffPositionTolerance){
|
||||
this.takeoffPositionTolerance = takeoffPositionTolerance;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the tolerance used to check if the owner has reached the takeoff velocity. */
|
||||
public float getTakeoffVelocityTolerance(){
|
||||
return takeoffVelocityTolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tolerance used to check if the owner has reached the takeoff velocity.
|
||||
* @param takeoffVelocityTolerance the takeoff velocity tolerance to set
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public Jump setTakeoffVelocityTolerance(float takeoffVelocityTolerance){
|
||||
this.takeoffVelocityTolerance = takeoffVelocityTolerance;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the the tolerance used to check if the owner has reached the takeoff location with the required velocity.
|
||||
* @param takeoffTolerance the takeoff tolerance for both position and velocity
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public Jump setTakeoffTolerance(float takeoffTolerance){
|
||||
setTakeoffPositionTolerance(takeoffTolerance);
|
||||
setTakeoffVelocityTolerance(takeoffTolerance);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static class JumpTarget extends SteerableAdapter{
|
||||
|
||||
Vec2 position;
|
||||
Vec2 linearVelocity;
|
||||
|
||||
public JumpTarget(Steerable other){
|
||||
this.position = null;
|
||||
this.linearVelocity = other.getPosition().cpy().setZero();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getPosition(){
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getLinearVelocity(){
|
||||
return linearVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code JumpDescriptor} contains jump information like the take-off and the landing position.
|
||||
* @author davebaol
|
||||
*/
|
||||
public static class JumpDescriptor{
|
||||
/** The position of the takeoff pad */
|
||||
public Vec2 takeoffPosition;
|
||||
|
||||
/** The position of the landing pad */
|
||||
public Vec2 landingPosition;
|
||||
|
||||
/** The change in position from takeoff to landing. This is calculated from the other values. */
|
||||
public Vec2 delta;
|
||||
|
||||
/**
|
||||
* Creates a {@code JumpDescriptor} with the given takeoff and landing positions.
|
||||
* @param takeoffPosition the position of the takeoff pad
|
||||
* @param landingPosition the position of the landing pad
|
||||
*/
|
||||
public JumpDescriptor(Vec2 takeoffPosition, Vec2 landingPosition){
|
||||
this.takeoffPosition = takeoffPosition;
|
||||
this.landingPosition = landingPosition;
|
||||
this.delta = landingPosition.cpy();
|
||||
set(takeoffPosition, landingPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this {@code JumpDescriptor} from the given takeoff and landing positions.
|
||||
* @param takeoffPosition the position of the takeoff pad
|
||||
* @param landingPosition the position of the landing pad
|
||||
*/
|
||||
public void set(Vec2 takeoffPosition, Vec2 landingPosition){
|
||||
this.takeoffPosition.set(takeoffPosition);
|
||||
this.landingPosition.set(landingPosition);
|
||||
this.delta.set(landingPosition).sub(takeoffPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code GravityComponentHandler} is aware of the axis along which the gravity acts.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface GravityComponentHandler{
|
||||
|
||||
/**
|
||||
* Returns the component of the given vector along which the gravity operates.
|
||||
* <p>
|
||||
* Assuming a 3D coordinate system where the gravity is acting along the y-axis, this method will be implemented as follows:
|
||||
*
|
||||
* <pre>
|
||||
* public float getComponent (Vector3 vector) {
|
||||
* return vector.y;
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Of course, the equivalent 2D implementation will use Vector2 instead of Vector3.
|
||||
* @param vector the vector
|
||||
* @return the value of the component affected by gravity.
|
||||
*/
|
||||
float getComponent(Vec2 vector);
|
||||
|
||||
/**
|
||||
* Sets the component of the given vector along which the gravity operates.
|
||||
* <p>
|
||||
* Assuming a 3D coordinate system where the gravity is acting along the y-axis, this method will be implemented as follows:
|
||||
*
|
||||
* <pre>
|
||||
* public void setComponent (Vector3 vector, float value) {
|
||||
* vector.y = value;
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* Of course, the equivalent 2D implementation will use Vector2 instead of Vector3.
|
||||
* @param vector the vector
|
||||
* @param value the value of the component affected by gravity
|
||||
*/
|
||||
void setComponent(Vec2 vector, float value);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@code JumpCallback} allows you to know whether a jump is achievable and when to jump.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface JumpCallback{
|
||||
|
||||
/**
|
||||
* Reports whether the jump is achievable or not.
|
||||
* <p>
|
||||
* A jump is not achievable when the character's maximum linear velocity is not enough, in which case the jump behavior
|
||||
* won't produce any acceleration; you might want to use pathfinding to plan a new path.
|
||||
* <p>
|
||||
* If the jump is achievable the run up phase will start immediately and the character will try to match the target velocity
|
||||
* toward the takeoff point. This is the right moment to start the run up animation, if needed.
|
||||
* @param achievable whether the jump is achievable or not.
|
||||
*/
|
||||
void reportAchievability(boolean achievable);
|
||||
|
||||
/**
|
||||
* This method is called to notify that both the position and velocity of the character are good enough to jump.
|
||||
* @param maxVerticalVelocity the velocity to set along the vertical axis to achieve the jump
|
||||
* @param time the duration of the jump
|
||||
*/
|
||||
void takeoff(float maxVerticalVelocity, float time);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* The entire steering framework assumes that the direction a character is facing does not have to be its direction of motion. In
|
||||
* many cases, however, you would like the character to face in the direction it is moving. To do this you can manually align the
|
||||
* orientation of the character to its linear velocity on each frame update or you can use the {@code LookWhereYouAreGoing}
|
||||
* behavior.
|
||||
* <p>
|
||||
* {@code LookWhereYouAreGoing} behavior gives the owner angular acceleration to make it face in the direction it is moving. In
|
||||
* this way the owner changes facing gradually, which can look more natural, especially for aerial vehicles such as helicopters or
|
||||
* for human characters that can move sideways.
|
||||
* <p>
|
||||
* This is a process similar to the {@code Face} behavior. The target orientation is calculated using the current velocity of the
|
||||
* owner. If there is no velocity, then the target orientation is set to the current orientation. We have no preference in this
|
||||
* situation for any orientation.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class LookWhereYouAreGoing extends ReachOrientation{
|
||||
|
||||
/**
|
||||
* Creates a {@code LookWhereYouAreGoing} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public LookWhereYouAreGoing(Steerable owner){
|
||||
super(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Check for a zero direction, and return no steering if so
|
||||
if(owner.getLinearVelocity().isZero(getActualLimiter().getZeroLinearSpeedThreshold())) return steering.setZero();
|
||||
|
||||
// Calculate the orientation based on the velocity of the owner
|
||||
float orientation = owner.vectorToAngle(owner.getLinearVelocity());
|
||||
|
||||
// Delegate to ReachOrientation
|
||||
return reachOrientation(steering, orientation);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* This steering behavior produces a linear acceleration trying to match target's velocity. It does not produce any angular
|
||||
* acceleration.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class MatchVelocity extends SteeringBehavior{
|
||||
/** The target of this behavior */
|
||||
public Steerable target;
|
||||
/** The time over which to achieve target speed */
|
||||
public float timeToTarget;
|
||||
|
||||
/**
|
||||
* Creates a {@code MatchVelocity} behavior for the given owner. No target is set. The maxLinearAcceleration is set to 100. The
|
||||
* timeToTarget is set to 0.1 seconds.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public MatchVelocity(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code MatchVelocity} behavior for the given owner and target. The timeToTarget is set to 0.1 seconds.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior.
|
||||
*/
|
||||
public MatchVelocity(Steerable owner, Steerable target){
|
||||
this(owner, target, 0.1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code MatchVelocity} behavior for the given owner, target and timeToTarget.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior
|
||||
* @param timeToTarget the time over which to achieve target speed.
|
||||
*/
|
||||
public MatchVelocity(Steerable owner, Steerable target, float timeToTarget){
|
||||
super(owner);
|
||||
this.target = target;
|
||||
this.timeToTarget = timeToTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Acceleration tries to get to the target velocity without exceeding max acceleration
|
||||
steering.linear.set(target.getLinearVelocity()).sub(owner.getLinearVelocity()).scl(1f / timeToTarget)
|
||||
.limit(getActualLimiter().getMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
// Output steering acceleration
|
||||
return steering;
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* The {@code PrioritySteering} behavior iterates through the behaviors and returns the first non zero steering. It makes sense
|
||||
* since certain steering behaviors only request an acceleration in particular conditions. Unlike {@link Seek} or {@link Evade},
|
||||
* which always produce an acceleration, {@link RaycastObstacleAvoidance}, {@link CollisionAvoidance}, {@link Separation},
|
||||
* {@link Hide} and {@link Arrive} will suggest no acceleration in many cases. But when these behaviors do suggest an
|
||||
* acceleration, it is unwise to ignore it. An obstacle avoidance behavior, for example, should be honored immediately to avoid
|
||||
* the crash.
|
||||
* <p>
|
||||
* Typically the behaviors of a {@code PrioritySteering} are arranged in groups with regular blending weights, see
|
||||
* {@link BlendedSteering}. These groups are then placed in priority order to let the steering system consider each group in turn.
|
||||
* It blends the steering behaviors in the current group together. If the total result is very small (less than some small, but
|
||||
* adjustable, parameter), then it is ignored and the next group is considered. It is best not to check against zero directly,
|
||||
* because numerical instability in calculations can mean that a zero value is never reached for some steering behaviors. Using a
|
||||
* small constant value (conventionally called {@code epsilon}) avoids this problem. When a group is found with a result that isn't
|
||||
* small, its result is used to steer the agent.
|
||||
* <p>
|
||||
* For instance, a pursuing agent working in a team may have three priorities:
|
||||
* <ul>
|
||||
* <li>a collision avoidance group that contains behaviors for obstacle avoidance, wall avoidance, and avoiding other characters.</li>
|
||||
* <li>a separation behavior used to avoid getting too close to other members of the chasing pack.</li>
|
||||
* <li>a pursuit behavior to chase the target.</li>
|
||||
* </ul>
|
||||
* If the character is far from any interference, the collision avoidance group will return with no desired acceleration. The
|
||||
* separation behavior will then be considered but will also return with no action. Finally, the pursuit behavior will be
|
||||
* considered, and the acceleration needed to continue the chase will be used. If the current motion of the character is perfect
|
||||
* for the pursuit, this behavior may also return with no acceleration. In this case, there are no more behaviors to consider, so
|
||||
* the character will have no acceleration, just as if they'd been exclusively controlled by the pursuit behavior.
|
||||
* <p>
|
||||
* In a different scenario, if the character is about to crash into a wall, the first group will return an acceleration that will
|
||||
* help avoid the crash. The character will carry out this acceleration immediately, and the steering behaviors in the other
|
||||
* groups won't be considered.
|
||||
* <p>
|
||||
* Usually {@code PrioritySteering} gives you a good compromise between speed and accuracy.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class PrioritySteering extends SteeringBehavior{
|
||||
/** The threshold of the steering acceleration magnitude below which a steering behavior is considered to have given no output. */
|
||||
public float epsilon;
|
||||
/**
|
||||
* The list of steering behaviors in priority order. The first item in the list is tried first, the subsequent entries are only
|
||||
* considered if the first one does not return a result.
|
||||
*/
|
||||
public Array<SteeringBehavior> behaviors = new Array<>();
|
||||
/** The index of the behavior whose acceleration has been returned by the last evaluation of this priority steering. */
|
||||
public int selectedBehaviorIndex;
|
||||
|
||||
/**
|
||||
* Creates a {@code PrioritySteering} behavior for the specified owner. The threshold is set to 0.001.
|
||||
* @param owner the owner of this behavior
|
||||
*/
|
||||
public PrioritySteering(Steerable owner){
|
||||
this(owner, 0.001f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PrioritySteering} behavior for the specified owner and threshold.
|
||||
* @param owner the owner of this behavior
|
||||
* @param epsilon the threshold of the steering acceleration magnitude below which a steering behavior is considered to have
|
||||
* given no output
|
||||
*/
|
||||
public PrioritySteering(Steerable owner, float epsilon){
|
||||
super(owner);
|
||||
this.epsilon = epsilon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the specified behavior to the priority list.
|
||||
* @param behavior the behavior to add
|
||||
* @return this behavior for chaining.
|
||||
*/
|
||||
public PrioritySteering add(SteeringBehavior behavior){
|
||||
behaviors.add(behavior);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// We'll need epsilon squared later.
|
||||
float epsilonSquared = epsilon * epsilon;
|
||||
|
||||
// Go through the behaviors until one has a large enough acceleration
|
||||
int n = behaviors.size;
|
||||
selectedBehaviorIndex = -1;
|
||||
for(int i = 0; i < n; i++){
|
||||
selectedBehaviorIndex = i;
|
||||
|
||||
SteeringBehavior behavior = behaviors.get(i);
|
||||
|
||||
// Calculate the behavior's steering
|
||||
behavior.calculateSteering(steering);
|
||||
|
||||
// If we're above the threshold return the current steering
|
||||
if(steering.calculateSquareMagnitude() > epsilonSquared) return steering;
|
||||
}
|
||||
|
||||
// If we get here, it means that no behavior had a large enough acceleration,
|
||||
// so return the small acceleration from the final behavior or zero if there are
|
||||
// no behaviors in the list.
|
||||
return n > 0 ? steering : steering.setZero();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* {@code Pursue} behavior produces a force that steers the agent towards the evader (the target). Actually it predicts where an
|
||||
* agent will be in time @{code t} and seeks towards that point to intercept it. We did this naturally playing tag as children,
|
||||
* which is why the most difficult tag players to catch were those who kept switching direction, foiling our predictions.
|
||||
* <p>
|
||||
* This implementation performs the prediction by assuming the target will continue moving with the same velocity it currently
|
||||
* has. This is a reasonable assumption over short distances, and even over longer distances it doesn't appear too stupid. The
|
||||
* algorithm works out the distance between character and target and works out how long it would take to get there, at maximum
|
||||
* speed. It uses this time interval as its prediction lookahead. It calculates the position of the target if it continues to move
|
||||
* with its current velocity. This new position is then used as the target of a standard seek behavior.
|
||||
* <p>
|
||||
* If the character is moving slowly, or the target is a long way away, the prediction time could be very large. The target is
|
||||
* less likely to follow the same path forever, so we'd like to set a limit on how far ahead we aim. The algorithm has a
|
||||
* {@code maxPredictionTime} for this reason. If the prediction time is beyond this, then the maximum time is used.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Pursue extends SteeringBehavior{
|
||||
/** The target */
|
||||
public Steerable target;
|
||||
/** The maximum prediction time */
|
||||
public float maxPredictionTime;
|
||||
|
||||
/**
|
||||
* Creates a {@code Pursue} behavior for the specified owner and target. Maximum prediction time defaults to 1 second.
|
||||
* @param owner the owner of this behavior.
|
||||
* @param target the target of this behavior.
|
||||
*/
|
||||
public Pursue(Steerable owner, Steerable target){
|
||||
this(owner, target, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Pursue} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target of this behavior
|
||||
* @param maxPredictionTime the max time used to predict the target's position assuming it continues to move with its current
|
||||
* velocity.
|
||||
*/
|
||||
public Pursue(Steerable owner, Steerable target, float maxPredictionTime){
|
||||
super(owner);
|
||||
this.target = target;
|
||||
this.maxPredictionTime = maxPredictionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the actual linear acceleration to be applied. This method is overridden by the {@link Evade} behavior to invert the
|
||||
* maximum linear acceleration in order to evade the target.
|
||||
*/
|
||||
protected float getActualMaxLinearAcceleration(){
|
||||
return getActualLimiter().getMaxLinearAcceleration();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
Vec2 targetPosition = target.getPosition();
|
||||
|
||||
// Get the square distance to the evader (the target)
|
||||
float squareDistance = steering.linear.set(targetPosition).sub(owner.getPosition()).len2();
|
||||
|
||||
// Work out our current square speed
|
||||
float squareSpeed = owner.getLinearVelocity().len2();
|
||||
|
||||
float predictionTime = maxPredictionTime;
|
||||
|
||||
if(squareSpeed > 0){
|
||||
// Calculate prediction time if speed is not too small to give a reasonable value
|
||||
float squarePredictionTime = squareDistance / squareSpeed;
|
||||
if(squarePredictionTime < maxPredictionTime * maxPredictionTime)
|
||||
predictionTime = (float)Math.sqrt(squarePredictionTime);
|
||||
}
|
||||
|
||||
// Calculate and seek/flee the predicted position of the target
|
||||
steering.linear.set(targetPosition).mulAdd(target.getLinearVelocity(), predictionTime).sub(owner.getPosition()).nor()
|
||||
.scl(getActualMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
// Output steering acceleration
|
||||
return steering;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.utils.*;
|
||||
import mindustry.ai.ai.steer.utils.rays.*;
|
||||
import mindustry.ai.ai.utils.Ray;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* With the {@code RaycastObstacleAvoidance} the moving agent (the owner) casts one or more rays out in the direction of its
|
||||
* motion. If these rays collide with an obstacle, then a target is created that will avoid the collision, and the owner does a
|
||||
* basic seek on this target. Typically, the rays extend a short distance ahead of the character (usually a distance corresponding
|
||||
* to a few seconds of movement).
|
||||
* <p>
|
||||
* This behavior is especially suitable for large-scale obstacles like walls.
|
||||
* <p>
|
||||
* You should use the {@link RayConfiguration} more suitable for your game environment. Some basic ray configurations are provided
|
||||
* by the framework: {@link SingleRayConfiguration}, {@link ParallelSideRayConfiguration} and
|
||||
* {@link CentralRayWithWhiskersConfiguration}. There are no hard and fast rules as to which configuration is better. Each has its
|
||||
* own particular idiosyncrasies. A single ray with short whiskers is often the best initial configuration to try but can make it
|
||||
* impossible for the character to move down tight passages. The single ray configuration is useful in concave environments but
|
||||
* grazes convex obstacles. The parallel configuration works well in areas where corners are highly obtuse but is very susceptible
|
||||
* to the corner trap.
|
||||
* <p>
|
||||
* <a name="cornerTrap">
|
||||
* <h2>The corner trap</h2></a> All the basic configurations for multi-ray obstacle avoidance can suffer from a crippling problem
|
||||
* with acute angled corners (any convex corner, in fact, but it is more prevalent with acute angles). Consider a character with
|
||||
* two parallel rays that is going towards a corner. As soon as its left ray is colliding with the wall near the corner, the
|
||||
* steering behavior will turn it to the left to avoid the collision. Immediately, the right ray will then be colliding the other
|
||||
* side of the corner, and the steering behavior will turn the character to the right. The character will repeatedly collide both
|
||||
* sides of the corner in rapid succession. It will appear to home into the corner directly, until it slams into the wall. It will
|
||||
* be unable to free itself from the trap.
|
||||
* <p>
|
||||
* The fan structure, with a wide enough fan angle, alleviates this problem. Often, there is a trade-off, however, between
|
||||
* avoiding the corner trap with a large fan angle and keeping the angle small to allow the character to access small passages. At
|
||||
* worst, with a fan angle near PI radians, the character will not be able to respond quickly enough to collisions detected on its
|
||||
* side rays and will still graze against walls. There are two approaches that work well and represent the most practical
|
||||
* solutions to the problem:
|
||||
* <ul>
|
||||
* <li><b>Adaptive fan angles:</b> If the character is moving successfully without a collision, then the fan angle is narrowed. If
|
||||
* a collision is detected, then the fan angle is widened. If the character detects many collisions on successive frames, then the
|
||||
* fan angle will continue to widen, reducing the chance that the character is trapped in a corner.</li>
|
||||
* <li><b>Winner ray:</b> If a corner trap is detected, then one of the rays is considered to have won, and the collisions
|
||||
* detected by other rays are ignored for a while.</li>
|
||||
* </ul>
|
||||
* It seems that the most practical solution is to use adaptive fan angles, with one long ray cast and two shorter whiskers.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class RaycastObstacleAvoidance extends SteeringBehavior{
|
||||
/** The inputRay configuration */
|
||||
public RayConfiguration rayConfiguration;
|
||||
/** The collision detector */
|
||||
public RaycastCollisionDetector raycastCollisionDetector;
|
||||
/** The minimum distance to a wall, i.e. how far to avoid collision. */
|
||||
public float distanceFromBoundary;
|
||||
|
||||
private Collision outputCollision;
|
||||
private Collision minOutputCollision;
|
||||
|
||||
/**
|
||||
* Creates a {@code RaycastObstacleAvoidance} behavior.
|
||||
* @param owner the owner of this behavior
|
||||
*/
|
||||
public RaycastObstacleAvoidance(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code RaycastObstacleAvoidance} behavior.
|
||||
* @param owner the owner of this behavior
|
||||
* @param rayConfiguration the ray configuration
|
||||
*/
|
||||
public RaycastObstacleAvoidance(Steerable owner, RayConfiguration rayConfiguration){
|
||||
this(owner, rayConfiguration, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code RaycastObstacleAvoidance} behavior.
|
||||
* @param owner the owner of this behavior
|
||||
* @param rayConfiguration the ray configuration
|
||||
* @param raycastCollisionDetector the collision detector
|
||||
*/
|
||||
public RaycastObstacleAvoidance(Steerable owner, RayConfiguration rayConfiguration,
|
||||
RaycastCollisionDetector raycastCollisionDetector){
|
||||
this(owner, rayConfiguration, raycastCollisionDetector, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code RaycastObstacleAvoidance} behavior.
|
||||
* @param owner the owner of this behavior
|
||||
* @param rayConfiguration the ray configuration
|
||||
* @param raycastCollisionDetector the collision detector
|
||||
* @param distanceFromBoundary the minimum distance to a wall (i.e., how far to avoid collision).
|
||||
*/
|
||||
public RaycastObstacleAvoidance(Steerable owner, RayConfiguration rayConfiguration,
|
||||
RaycastCollisionDetector raycastCollisionDetector, float distanceFromBoundary){
|
||||
super(owner);
|
||||
this.rayConfiguration = rayConfiguration;
|
||||
this.raycastCollisionDetector = raycastCollisionDetector;
|
||||
this.distanceFromBoundary = distanceFromBoundary;
|
||||
|
||||
this.outputCollision = new Collision(newVector(owner), newVector(owner));
|
||||
this.minOutputCollision = new Collision(newVector(owner), newVector(owner));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
Vec2 ownerPosition = owner.getPosition();
|
||||
float minDistanceSquare = Float.POSITIVE_INFINITY;
|
||||
|
||||
// Get the updated rays
|
||||
Ray[] inputRays = rayConfiguration.updateRays();
|
||||
|
||||
// Process rays
|
||||
for(Ray inputRay : inputRays){
|
||||
// Find the collision with current ray
|
||||
boolean collided = raycastCollisionDetector.findCollision(outputCollision, inputRay);
|
||||
|
||||
if(collided){
|
||||
float distanceSquare = ownerPosition.dst2(outputCollision.point);
|
||||
if(distanceSquare < minDistanceSquare){
|
||||
minDistanceSquare = distanceSquare;
|
||||
// Swap collisions
|
||||
Collision tmpCollision = outputCollision;
|
||||
outputCollision = minOutputCollision;
|
||||
minOutputCollision = tmpCollision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return zero steering if no collision has occurred
|
||||
if(minDistanceSquare == Float.POSITIVE_INFINITY) return steering.setZero();
|
||||
|
||||
// Calculate and seek the target position
|
||||
steering.linear.set(minOutputCollision.point)
|
||||
.mulAdd(minOutputCollision.normal, owner.getBoundingRadius() + distanceFromBoundary).sub(owner.getPosition()).nor()
|
||||
.scl(getActualLimiter().getMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
// Output steering acceleration
|
||||
return steering;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code ReachOrientation} tries to align the owner to the target. It pays no attention to the position or velocity of the owner
|
||||
* or target. This steering behavior does not produce any linear acceleration; it only responds by turning.
|
||||
* <p>
|
||||
* {@code ReachOrientation} behaves in a similar way to {@link Arrive} since it tries to reach the target orientation and tries to
|
||||
* have zero rotation when it gets there. Like arrive, it uses two radii: {@code decelerationRadius} for slowing down and
|
||||
* {@code alignTolerance} to make orientations near the target acceptable without letting small errors keep the owner swinging.
|
||||
* Because we are dealing with a single scalar value, rather than a 2D or 3D vector, the radius acts as an interval.
|
||||
* <p>
|
||||
* Similarly to {@code Arrive}, there is a {@code timeToTarget} that defaults to 0.1 seconds.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class ReachOrientation extends SteeringBehavior{
|
||||
/** The target to align to. */
|
||||
public Location target;
|
||||
/** The tolerance for aligning to the target without letting small errors keep the owner swinging. */
|
||||
public float alignTolerance;
|
||||
/** The radius for beginning to slow down */
|
||||
public float decelerationRadius;
|
||||
/** The time over which to achieve target rotation speed */
|
||||
public float timeToTarget = 0.1f;
|
||||
|
||||
/**
|
||||
* Creates a {@code ReachOrientation} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public ReachOrientation(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ReachOrientation} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target.
|
||||
*/
|
||||
public ReachOrientation(Steerable owner, Location target){
|
||||
super(owner);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
return reachOrientation(steering, target.getOrientation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a steering that tries to align the owner to the target orientation. This method is called by subclasses that want
|
||||
* to align to a certain orientation.
|
||||
* @param steering the steering to be calculated.
|
||||
* @param targetOrientation the target orientation you want to align to.
|
||||
* @return the calculated steering for chaining.
|
||||
*/
|
||||
protected SteeringAcceleration reachOrientation(SteeringAcceleration steering, float targetOrientation){
|
||||
// Get the rotation direction to the target wrapped to the range [-PI, PI]
|
||||
float rotation = Mathf.wrapAngleAroundZero(targetOrientation - owner.getOrientation());
|
||||
|
||||
// Absolute rotation
|
||||
float rotationSize = rotation < 0f ? -rotation : rotation;
|
||||
|
||||
// Check if we are there, return no steering
|
||||
if(rotationSize <= alignTolerance) return steering.setZero();
|
||||
|
||||
Limiter actualLimiter = getActualLimiter();
|
||||
|
||||
// Use maximum rotation
|
||||
float targetRotation = actualLimiter.getMaxAngularSpeed();
|
||||
|
||||
// If we are inside the slow down radius, then calculate a scaled rotation
|
||||
if(rotationSize <= decelerationRadius) targetRotation *= rotationSize / decelerationRadius;
|
||||
|
||||
// The final target rotation combines
|
||||
// speed (already in the variable) and direction
|
||||
targetRotation *= rotation / rotationSize;
|
||||
|
||||
// Acceleration tries to get to the target rotation
|
||||
steering.angular = (targetRotation - owner.getAngularVelocity()) / timeToTarget;
|
||||
|
||||
// Check if the absolute acceleration is too great
|
||||
float angularAcceleration = steering.angular < 0f ? -steering.angular : steering.angular;
|
||||
if(angularAcceleration > actualLimiter.getMaxAngularAcceleration())
|
||||
steering.angular *= actualLimiter.getMaxAngularAcceleration() / angularAcceleration;
|
||||
|
||||
// No linear acceleration
|
||||
steering.linear.setZero();
|
||||
|
||||
// Output the steering
|
||||
return steering;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code Seek} behavior moves the owner towards the target position. Given a target, this behavior calculates the linear steering
|
||||
* acceleration which will direct the agent towards the target as fast as possible.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Seek extends SteeringBehavior{
|
||||
/** The target to seek */
|
||||
public Location target;
|
||||
|
||||
/**
|
||||
* Creates a {@code Seek} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public Seek(Steerable owner){
|
||||
this(owner, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Seek} behavior for the specified owner and target.
|
||||
* @param owner the owner of this behavior
|
||||
* @param target the target agent of this behavior.
|
||||
*/
|
||||
public Seek(Steerable owner, Location target){
|
||||
super(owner);
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Try to match the position of the character with the position of the target by calculating
|
||||
// the direction to the target and by moving toward it as fast as possible.
|
||||
steering.linear.set(target.getPosition()).sub(owner.getPosition()).nor().scl(getActualLimiter().getMaxLinearAcceleration());
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
// Output steering acceleration
|
||||
return steering;
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.Proximity.*;
|
||||
|
||||
/**
|
||||
* {@code Separation} is a group behavior producing a steering acceleration repelling from the other neighbors which are the agents
|
||||
* in the immediate area defined by the given {@link Proximity}. The acceleration is calculated by iterating through all the
|
||||
* neighbors, examining each one. The vector to each agent under consideration is normalized, multiplied by a strength decreasing
|
||||
* according to the inverse square law in relation to distance, and accumulated.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Separation extends GroupBehavior implements ProximityCallback{
|
||||
/**
|
||||
* The constant coefficient of decay for the inverse square law force. It controls how fast the separation strength decays with
|
||||
* distance.
|
||||
*/
|
||||
public float decayCoefficient = 1f;
|
||||
|
||||
private Vec2 toAgent;
|
||||
private Vec2 linear;
|
||||
|
||||
/**
|
||||
* Creates a {@code Separation} behavior for the specified owner and proximity.
|
||||
* @param owner the owner of this behavior
|
||||
* @param proximity the proximity to detect the owner's neighbors
|
||||
*/
|
||||
public Separation(Steerable owner, Proximity proximity){
|
||||
super(owner, proximity);
|
||||
|
||||
this.toAgent = newVector(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
steering.setZero();
|
||||
|
||||
linear = steering.linear;
|
||||
|
||||
proximity.findNeighbors(this);
|
||||
|
||||
return steering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean report(Steerable neighbor){
|
||||
|
||||
toAgent.set(owner.getPosition()).sub(neighbor.getPosition());
|
||||
float distanceSqr = toAgent.len2();
|
||||
|
||||
if(distanceSqr == 0) return true;
|
||||
|
||||
float maxAcceleration = getActualLimiter().getMaxLinearAcceleration();
|
||||
|
||||
// Calculate the strength of repulsion through inverse square law decay
|
||||
float strength = decayCoefficient / distanceSqr;
|
||||
if(strength > maxAcceleration) strength = maxAcceleration;
|
||||
|
||||
// Add the acceleration
|
||||
// Optimized code for linear.mulAdd(toAgent.nor(), strength);
|
||||
linear.mulAdd(toAgent, strength / (float)Math.sqrt(distanceSqr));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
package mindustry.ai.ai.steer.behaviors;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code Wander} behavior is designed to produce a steering acceleration that will give the impression of a random walk through
|
||||
* the agent's environment. You'll often find it a useful ingredient when creating an agent's behavior.
|
||||
* <p>
|
||||
* There is a circle in front of the owner (where front is determined by its current facing direction) on which the target is
|
||||
* constrained. Each time the behavior is run, we move the target around the circle a little, by a random amount. Now there are 2
|
||||
* ways to implement wander behavior:
|
||||
* <ul>
|
||||
* <li>The owner seeks the target, using the {@link Seek} behavior, and performs a {@link LookWhereYouAreGoing} behavior to
|
||||
* correct its orientation.</li>
|
||||
* <li>The owner tries to face the target in each frame, using the {@link Face} behavior to align to the target, and applies full
|
||||
* linear acceleration in the direction of its current orientation.</li>
|
||||
* </ul>
|
||||
* In either case, the orientation of the owner is retained between calls (so smoothing the changes in orientation). The angles
|
||||
* that the edges of the circle subtend to the owner determine how fast it will turn. If the target is on one of these extreme
|
||||
* points, it will turn quickly. The target will twitch and jitter around the edge of the circle, but the owner's orientation will
|
||||
* change smoothly.
|
||||
* <p>
|
||||
* This implementation uses the second approach. However, if you manually align owner's orientation to its linear velocity on each
|
||||
* time step, {@link Face} behavior should not be used (which is the default case). On the other hand, if the owner has
|
||||
* independent facing you should explicitly call {@link #setFaceEnabled(boolean) setFaceEnabled(true)} before using Wander
|
||||
* behavior.
|
||||
* <p>
|
||||
* Note that this behavior internally calls the {@link Timepiece#getTime() GdxAI.getTimepiece().getTime()} method to get the
|
||||
* current AI time and make the {@link #wanderRate} FPS independent. This means that
|
||||
* <ul>
|
||||
* <li>if you forget to {@link Timepiece#update(float) update the timepiece} the wander orientation won't change.</li>
|
||||
* <li>ideally the timepiece should be always updated before this steering behavior runs.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This steering behavior can be used to produce a whole range of random motion, from very smooth undulating turns to wild
|
||||
* Strictly Ballroom type whirls and pirouettes depending on the size of the circle, its distance from the agent, and the amount
|
||||
* of random displacement each frame.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Wander extends Face{
|
||||
/** The forward offset of the wander circle */
|
||||
public float wanderOffset;
|
||||
/** The radius of the wander circle */
|
||||
public float wanderRadius;
|
||||
/** The rate, expressed in radian per second, at which the wander orientation can change */
|
||||
public float wanderRate;
|
||||
/** The last time the orientation of the wander target has been updated */
|
||||
public float lastTime;
|
||||
/** The current orientation of the wander target */
|
||||
public float wanderOrientation;
|
||||
/**
|
||||
* The flag indicating whether to use {@link Face} behavior or not. This should be set to {@code true} when independent facing
|
||||
* is used.
|
||||
*/
|
||||
public boolean faceEnabled;
|
||||
|
||||
private Vec2 internalTargetPosition;
|
||||
private Vec2 wanderCenter;
|
||||
|
||||
/**
|
||||
* Creates a {@code Wander} behavior for the specified owner.
|
||||
* @param owner the owner of this behavior.
|
||||
*/
|
||||
public Wander(Steerable owner){
|
||||
super(owner);
|
||||
|
||||
this.internalTargetPosition = newVector(owner);
|
||||
this.wanderCenter = newVector(owner);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SteeringAcceleration calculateRealSteering(SteeringAcceleration steering){
|
||||
// Update the wander orientation
|
||||
float now = Timepiece.time;
|
||||
if(lastTime > 0){
|
||||
float delta = now - lastTime;
|
||||
wanderOrientation += Mathf.randomTriangular(wanderRate * delta);
|
||||
}
|
||||
lastTime = now;
|
||||
|
||||
// Calculate the combined target orientation
|
||||
float targetOrientation = wanderOrientation + owner.getOrientation();
|
||||
|
||||
// Calculate the center of the wander circle
|
||||
wanderCenter.set(owner.getPosition()).mulAdd(owner.angleToVector(steering.linear, owner.getOrientation()), wanderOffset);
|
||||
|
||||
// Calculate the target location
|
||||
// Notice that we're using steering.linear as temporary vector
|
||||
internalTargetPosition.set(wanderCenter).mulAdd(owner.angleToVector(steering.linear, targetOrientation), wanderRadius);
|
||||
|
||||
float maxLinearAcceleration = getActualLimiter().getMaxLinearAcceleration();
|
||||
|
||||
if(faceEnabled){
|
||||
// Delegate to face
|
||||
face(steering, internalTargetPosition);
|
||||
|
||||
// Set the linear acceleration to be at full
|
||||
// acceleration in the direction of the orientation
|
||||
owner.angleToVector(steering.linear, owner.getOrientation()).scl(maxLinearAcceleration);
|
||||
}else{
|
||||
// Seek the internal target position
|
||||
steering.linear.set(internalTargetPosition).sub(owner.getPosition()).nor().scl(maxLinearAcceleration);
|
||||
|
||||
// No angular acceleration
|
||||
steering.angular = 0;
|
||||
|
||||
}
|
||||
|
||||
return steering;
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package mindustry.ai.ai.steer.proximities;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.Timepiece;
|
||||
|
||||
/**
|
||||
* {@code FieldOfViewProximity} emulates the peripheral vision of the owner as if it had eyes. Any agents contained in the
|
||||
* specified list that are within the field of view of the owner are considered owner's neighbors. The field of view is determined
|
||||
* by a radius and an angle in degrees.
|
||||
* <p>
|
||||
* Note that this implementation checks the AI time of the current frame through the {@link mindustry.ai.ai.utils.Timepiece#getTime()
|
||||
* GdxAI.getTimepiece().getTime()} method in order to calculate neighbors only once per frame (assuming delta time is always
|
||||
* greater than 0, if time has changed the frame has changed too). This means that
|
||||
* <ul>
|
||||
* <li>if you forget to {@link mindustry.ai.ai.utils.Timepiece#update(float) update the timepiece} on each frame the proximity instance will be
|
||||
* calculated only the very first time, which is not what you want of course.</li>
|
||||
* <li>ideally the timepiece should be updated before the proximity is updated by the {@link #findNeighbors(ProximityCallback)}
|
||||
* method.</li>
|
||||
* </ul>
|
||||
* @author davebaol
|
||||
*/
|
||||
public class FieldOfViewProximity extends ProximityBase{
|
||||
|
||||
/** The radius of this proximity. */
|
||||
protected float radius;
|
||||
|
||||
/** The angle in radians of this proximity. */
|
||||
protected float angle;
|
||||
|
||||
private float coneThreshold;
|
||||
private float lastTime;
|
||||
private Vec2 ownerOrientation;
|
||||
private Vec2 toAgent;
|
||||
|
||||
/**
|
||||
* Creates a {@code FieldOfViewProximity} for the specified owner, agents and cone area defined by the given radius and angle
|
||||
* in radians.
|
||||
* @param owner the owner of this proximity
|
||||
* @param agents the agents
|
||||
* @param radius the radius of the cone area
|
||||
* @param angle the angle in radians of the cone area
|
||||
*/
|
||||
public FieldOfViewProximity(Steerable owner, Iterable<? extends Steerable> agents, float radius, float angle){
|
||||
super(owner, agents);
|
||||
this.radius = radius;
|
||||
setAngle(angle);
|
||||
this.lastTime = 0;
|
||||
this.ownerOrientation = owner.getPosition().cpy().setZero();
|
||||
this.toAgent = owner.getPosition().cpy().setZero();
|
||||
}
|
||||
|
||||
/** Returns the radius of this proximity. */
|
||||
public float getRadius(){
|
||||
return radius;
|
||||
}
|
||||
|
||||
/** Sets the radius of this proximity. */
|
||||
public void setRadius(float radius){
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
/** Returns the angle of this proximity in radians. */
|
||||
public float getAngle(){
|
||||
return angle;
|
||||
}
|
||||
|
||||
/** Sets the angle of this proximity in radians. */
|
||||
public void setAngle(float angle){
|
||||
this.angle = angle;
|
||||
this.coneThreshold = (float)Math.cos(angle * 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findNeighbors(ProximityCallback callback){
|
||||
int neighborCount = 0;
|
||||
|
||||
// If the frame is new then avoid repeating calculations
|
||||
// when this proximity is used by multiple group behaviors.
|
||||
float currentTime = Timepiece.time;
|
||||
if(this.lastTime != currentTime){
|
||||
// Save the current time
|
||||
this.lastTime = currentTime;
|
||||
|
||||
Vec2 ownerPosition = owner.getPosition();
|
||||
|
||||
// Transform owner orientation to a Vector
|
||||
owner.angleToVector(ownerOrientation, owner.getOrientation());
|
||||
|
||||
// Scan the agents searching for neighbors
|
||||
for(Steerable currentAgent : agents){
|
||||
|
||||
// Make sure the agent being examined isn't the owner
|
||||
if(currentAgent != owner){
|
||||
|
||||
toAgent.set(currentAgent.getPosition()).sub(ownerPosition);
|
||||
|
||||
// The bounding radius of the current agent is taken into account
|
||||
// by adding it to the radius proximity
|
||||
float range = radius + currentAgent.getBoundingRadius();
|
||||
|
||||
float toAgentLen2 = toAgent.len2();
|
||||
|
||||
// Make sure the current agent is within the range.
|
||||
// Notice we're working in distance-squared space to avoid square root.
|
||||
if(toAgentLen2 < range * range){
|
||||
|
||||
// If the current agent is within the field of view of the owner,
|
||||
// report it to the callback and tag it for further consideration.
|
||||
if(ownerOrientation.dot(toAgent) > coneThreshold){
|
||||
if(callback.report(currentAgent)){
|
||||
currentAgent.setTagged(true);
|
||||
neighborCount++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the tag
|
||||
currentAgent.setTagged(false);
|
||||
}
|
||||
}else{
|
||||
// Scan the agents searching for tagged neighbors
|
||||
for(Steerable currentAgent : agents){
|
||||
|
||||
// Make sure the agent being examined isn't the owner and that
|
||||
// it's tagged.
|
||||
if(currentAgent != owner && currentAgent.isTagged()){
|
||||
|
||||
if(callback.report(currentAgent)){
|
||||
neighborCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return neighborCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package mindustry.ai.ai.steer.proximities;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* {@code InfiniteProximity} is likely the simplest type of Proximity one can imagine. All the agents contained in the specified
|
||||
* list are considered neighbors of the owner, excluded the owner itself (if it is part of the list).
|
||||
* @author davebaol
|
||||
*/
|
||||
public class InfiniteProximity extends ProximityBase{
|
||||
|
||||
/**
|
||||
* Creates a {@code InfiniteProximity} for the specified owner and list of agents.
|
||||
* @param owner the owner of this proximity
|
||||
* @param agents the list of agents
|
||||
*/
|
||||
public InfiniteProximity(Steerable owner, Iterable<? extends Steerable> agents){
|
||||
super(owner, agents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findNeighbors(ProximityCallback callback){
|
||||
int neighborCount = 0;
|
||||
for(Steerable currentAgent : agents){
|
||||
// Make sure the agent being examined isn't the owner
|
||||
if(currentAgent != owner){
|
||||
if(callback.report(currentAgent)){
|
||||
neighborCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return neighborCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package mindustry.ai.ai.steer.proximities;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
|
||||
/**
|
||||
* {@code ProximityBase} is the base class for any concrete proximity based on an iterable collection of agents.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class ProximityBase implements Proximity{
|
||||
/** The owner of this proximity. */
|
||||
protected Steerable owner;
|
||||
/** The collection of the agents handled by this proximity. */
|
||||
public Iterable<? extends Steerable> agents;
|
||||
|
||||
/**
|
||||
* Creates a {@code ProximityBase} for the specified owner and list of agents.
|
||||
* @param owner the owner of this proximity
|
||||
* @param agents the list of agents
|
||||
*/
|
||||
public ProximityBase(Steerable owner, Iterable<? extends Steerable> agents){
|
||||
this.owner = owner;
|
||||
this.agents = agents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Steerable getOwner(){
|
||||
return owner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOwner(Steerable owner){
|
||||
this.owner = owner;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package mindustry.ai.ai.steer.proximities;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.Timepiece;
|
||||
|
||||
/**
|
||||
* A {@code RadiusProximity} elaborates any agents contained in the specified list that are within the radius of the owner.
|
||||
* <p>
|
||||
* Note that this implementation checks the AI time of the current frame through the {@link mindustry.ai.ai.utils.Timepiece#getTime()
|
||||
* GdxAI.getTimepiece().getTime()} method in order to calculate neighbors only once per frame (assuming delta time is always
|
||||
* greater than 0, if time has changed the frame has changed too). This means that
|
||||
* <ul>
|
||||
* <li>if you forget to {@link mindustry.ai.ai.utils.Timepiece#update(float) update the timepiece} on each frame the proximity instance will be
|
||||
* calculated only the very first time, which is not what you want of course.</li>
|
||||
* <li>ideally the timepiece should be updated before the proximity is updated by the {@link #findNeighbors(ProximityCallback)}
|
||||
* method.</li>
|
||||
* </ul>
|
||||
* @author davebaol
|
||||
*/
|
||||
public class RadiusProximity extends ProximityBase{
|
||||
/** The radius of this proximity. */
|
||||
protected float radius;
|
||||
|
||||
private float lastTime;
|
||||
|
||||
/**
|
||||
* Creates a {@code RadiusProximity} for the specified owner, agents and radius.
|
||||
* @param owner the owner of this proximity
|
||||
* @param agents the agents
|
||||
* @param radius the radius of the cone area
|
||||
*/
|
||||
public RadiusProximity(Steerable owner, Iterable<? extends Steerable> agents, float radius){
|
||||
super(owner, agents);
|
||||
this.radius = radius;
|
||||
this.lastTime = 0;
|
||||
}
|
||||
|
||||
/** Returns the radius of this proximity. */
|
||||
public float getRadius(){
|
||||
return radius;
|
||||
}
|
||||
|
||||
/** Sets the radius of this proximity. */
|
||||
public void setRadius(float radius){
|
||||
this.radius = radius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int findNeighbors(ProximityCallback callback){
|
||||
int neighborCount = 0;
|
||||
|
||||
// If the frame is new then avoid repeating calculations
|
||||
// when this proximity is used by multiple group behaviors.
|
||||
float currentTime = Timepiece.time;
|
||||
if(this.lastTime != currentTime){
|
||||
// Save the current time
|
||||
this.lastTime = currentTime;
|
||||
|
||||
Vec2 ownerPosition = owner.getPosition();
|
||||
|
||||
// Scan the agents searching for neighbors
|
||||
for(Steerable currentAgent : agents){
|
||||
// Make sure the agent being examined isn't the owner
|
||||
if(currentAgent != owner){
|
||||
float squareDistance = ownerPosition.dst2(currentAgent.getPosition());
|
||||
|
||||
// The bounding radius of the current agent is taken into account
|
||||
// by adding it to the range
|
||||
float range = radius + currentAgent.getBoundingRadius();
|
||||
|
||||
// If the current agent is within the range, report it to the callback
|
||||
// and tag it for further consideration.
|
||||
if(squareDistance < range * range){
|
||||
if(callback.report(currentAgent)){
|
||||
currentAgent.setTagged(true);
|
||||
neighborCount++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the tag
|
||||
currentAgent.setTagged(false);
|
||||
}
|
||||
}else{
|
||||
// Scan the agents searching for tagged neighbors
|
||||
for(Steerable currentAgent : agents){
|
||||
// Make sure the agent being examined isn't the owner and that
|
||||
// it's tagged.
|
||||
if(currentAgent != owner && currentAgent.isTagged()){
|
||||
|
||||
if(callback.report(currentAgent)){
|
||||
neighborCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return neighborCount;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.utils.Path.*;
|
||||
|
||||
/**
|
||||
* The {@code Path} for an agent having path following behavior. A path can be shared by multiple path following behaviors because
|
||||
* its status is maintained in a {@link PathParam} local to each behavior.
|
||||
* <p>
|
||||
* The most common type of path is made up of straight line segments, which usually gives reasonably good results while keeping
|
||||
* the math simple. However, some driving games use splines to get smoother curved paths, which makes the math more complex.
|
||||
* @param <P> Type of path parameter implementing the {@link PathParam} interface
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface Path<P extends PathParam>{
|
||||
|
||||
/** Returns a new instance of the path parameter. */
|
||||
P createParam();
|
||||
|
||||
/** Returns {@code true} if this path is open; {@code false} otherwise. */
|
||||
boolean isOpen();
|
||||
|
||||
/** Returns the length of this path. */
|
||||
float getLength();
|
||||
|
||||
/** Returns the first point of this path. */
|
||||
Vec2 getStartPoint();
|
||||
|
||||
/** Returns the last point of this path. */
|
||||
Vec2 getEndPoint();
|
||||
|
||||
/**
|
||||
* Maps the given position to the nearest point along the path using the path parameter to ensure coherence and returns the
|
||||
* distance of that nearest point from the start of the path.
|
||||
* @param position a location in game space
|
||||
* @param param the path parameter
|
||||
* @return the distance of the nearest point along the path from the start of the path itself.
|
||||
*/
|
||||
float calculateDistance(Vec2 position, P param);
|
||||
|
||||
/**
|
||||
* Calculates the target position on the path based on its distance from the start and the path parameter.
|
||||
* @param out the target position to calculate
|
||||
* @param param the path parameter
|
||||
* @param targetDistance the distance of the target position from the start of the path
|
||||
*/
|
||||
void calculateTargetPosition(Vec2 out, P param, float targetDistance);
|
||||
|
||||
/**
|
||||
* A path parameter used by path following behaviors to keep the path status.
|
||||
* @author davebaol
|
||||
*/
|
||||
interface PathParam{
|
||||
|
||||
/** Returns the distance from the start of the path */
|
||||
float getDistance();
|
||||
|
||||
/**
|
||||
* Sets the distance from the start of the path
|
||||
* @param distance the distance to set
|
||||
*/
|
||||
void setDistance(float distance);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils;
|
||||
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* A {@code RayConfiguration} is a collection of rays typically used for collision avoidance.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface RayConfiguration{
|
||||
Ray[] updateRays();
|
||||
}
|
||||
@@ -1,265 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils.paths;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.ai.ai.steer.utils.Path;
|
||||
import mindustry.ai.ai.steer.utils.paths.LinePath.*;
|
||||
|
||||
/**
|
||||
* A {@code LinePath} is a path for path following behaviors that is made up of a series of waypoints. Each waypoint is connected
|
||||
* to the successor with a {@link Segment}.
|
||||
* @author davebaol
|
||||
* @author Daniel Holderbaum
|
||||
*/
|
||||
public class LinePath implements Path<LinePathParam>{
|
||||
|
||||
private Array<Segment> segments;
|
||||
private boolean isOpen;
|
||||
private float pathLength;
|
||||
private Vec2 nearestPointOnCurrentSegment;
|
||||
private Vec2 nearestPointOnPath;
|
||||
private Vec2 tmpB;
|
||||
private Vec2 tmpC;
|
||||
|
||||
/**
|
||||
* Creates a closed {@code LinePath} for the specified {@code waypoints}.
|
||||
* @param waypoints the points making up the path
|
||||
* @throws IllegalArgumentException if {@code waypoints} is {@code null} or has less than two (2) waypoints.
|
||||
*/
|
||||
public LinePath(Array waypoints){
|
||||
this(waypoints, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code LinePath} for the specified {@code waypoints}.
|
||||
* @param waypoints the points making up the path
|
||||
* @param isOpen a flag indicating whether the path is open or not
|
||||
* @throws IllegalArgumentException if {@code waypoints} is {@code null} or has less than two (2) waypoints.
|
||||
*/
|
||||
public LinePath(Array<Vec2> waypoints, boolean isOpen){
|
||||
this.isOpen = isOpen;
|
||||
createPath(waypoints);
|
||||
nearestPointOnCurrentSegment = waypoints.first().cpy();
|
||||
nearestPointOnPath = waypoints.first().cpy();
|
||||
tmpB = waypoints.first().cpy();
|
||||
tmpC = waypoints.first().cpy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen(){
|
||||
return isOpen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getLength(){
|
||||
return pathLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getStartPoint(){
|
||||
return segments.first().begin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec2 getEndPoint(){
|
||||
return segments.peek().end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the square distance of the nearest point on line segment {@code a-b}, from point {@code c}. Also, the {@code out}
|
||||
* vector is assigned to the nearest point.
|
||||
* @param out the output vector that contains the nearest point on return
|
||||
* @param a the start point of the line segment
|
||||
* @param b the end point of the line segment
|
||||
* @param c the point to calculate the distance from
|
||||
*/
|
||||
public float calculatePointSegmentSquareDistance(Vec2 out, Vec2 a, Vec2 b, Vec2 c){
|
||||
out.set(a);
|
||||
tmpB.set(b);
|
||||
tmpC.set(c);
|
||||
|
||||
Vec2 ab = tmpB.sub(a);
|
||||
float abLen2 = ab.len2();
|
||||
if(abLen2 != 0){
|
||||
float t = (tmpC.sub(a)).dot(ab) / abLen2;
|
||||
out.mulAdd(ab, Mathf.clamp(t, 0, 1));
|
||||
}
|
||||
|
||||
return out.dst2(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinePathParam createParam(){
|
||||
return new LinePathParam();
|
||||
}
|
||||
|
||||
// We pass the last parameter value to the path in order to calculate the current
|
||||
// parameter value. This is essential to avoid nasty problems when lines are close together.
|
||||
// We should limit the algorithm to only considering areas of the path close to the previous
|
||||
// parameter value. The character is unlikely to have moved far, after all.
|
||||
// This technique, assuming the new value is close to the old one, is called coherence, and it is a
|
||||
// feature of many geometric algorithms.
|
||||
// TODO: Currently coherence is not implemented.
|
||||
@Override
|
||||
public float calculateDistance(Vec2 agentCurrPos, LinePathParam parameter){
|
||||
// Find the nearest segment
|
||||
float smallestDistance2 = Float.POSITIVE_INFINITY;
|
||||
Segment nearestSegment = null;
|
||||
for(int i = 0; i < segments.size; i++){
|
||||
Segment segment = segments.get(i);
|
||||
float distance2 = calculatePointSegmentSquareDistance(nearestPointOnCurrentSegment, segment.begin, segment.end,
|
||||
agentCurrPos);
|
||||
|
||||
// first point
|
||||
if(distance2 < smallestDistance2){
|
||||
nearestPointOnPath.set(nearestPointOnCurrentSegment);
|
||||
smallestDistance2 = distance2;
|
||||
nearestSegment = segment;
|
||||
parameter.segmentIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Distance from path start
|
||||
float lengthOnPath = nearestSegment.cumulativeLength - nearestPointOnPath.dst(nearestSegment.end);
|
||||
|
||||
parameter.setDistance(lengthOnPath);
|
||||
|
||||
return lengthOnPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculateTargetPosition(Vec2 out, LinePathParam param, float targetDistance){
|
||||
if(isOpen){
|
||||
// Open path support
|
||||
if(targetDistance < 0){
|
||||
// Clamp target distance to the min
|
||||
targetDistance = 0;
|
||||
}else if(targetDistance > pathLength){
|
||||
// Clamp target distance to the max
|
||||
targetDistance = pathLength;
|
||||
}
|
||||
}else{
|
||||
// Closed path support
|
||||
if(targetDistance < 0){
|
||||
// Backwards
|
||||
targetDistance = pathLength + (targetDistance % pathLength);
|
||||
}else if(targetDistance > pathLength){
|
||||
// Forward
|
||||
targetDistance = targetDistance % pathLength;
|
||||
}
|
||||
}
|
||||
|
||||
// Walk through lines to see on which line we are
|
||||
Segment desiredSegment = null;
|
||||
for(int i = 0; i < segments.size; i++){
|
||||
Segment segment = segments.get(i);
|
||||
if(segment.cumulativeLength >= targetDistance){
|
||||
desiredSegment = segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// begin-------targetPos-------end
|
||||
float distance = desiredSegment.cumulativeLength - targetDistance;
|
||||
|
||||
out.set(desiredSegment.begin).sub(desiredSegment.end).scl(distance / desiredSegment.length).add(desiredSegment.end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up this {@link Path} using the given way points.
|
||||
* @param waypoints The way points of this path.
|
||||
* @throws IllegalArgumentException if {@code waypoints} is {@code null} or empty.
|
||||
*/
|
||||
public void createPath(Array<Vec2> waypoints){
|
||||
if(waypoints == null || waypoints.size < 2)
|
||||
throw new IllegalArgumentException("waypoints cannot be null and must contain at least two (2) waypoints");
|
||||
|
||||
segments = new Array<>(waypoints.size);
|
||||
pathLength = 0;
|
||||
Vec2 curr = waypoints.first();
|
||||
Vec2 prev = null;
|
||||
for(int i = 1; i <= waypoints.size; i++){
|
||||
prev = curr;
|
||||
if(i < waypoints.size)
|
||||
curr = waypoints.get(i);
|
||||
else if(isOpen)
|
||||
break; // keep the path open
|
||||
else
|
||||
curr = waypoints.first(); // close the path
|
||||
Segment segment = new Segment(prev, curr);
|
||||
pathLength += segment.length;
|
||||
segment.cumulativeLength = pathLength;
|
||||
segments.add(segment);
|
||||
}
|
||||
}
|
||||
|
||||
public Array<Segment> getSegments(){
|
||||
return segments;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code LinePathParam} contains the status of a {@link LinePath}.
|
||||
* @author davebaol
|
||||
*/
|
||||
public static class LinePathParam implements Path.PathParam{
|
||||
int segmentIndex;
|
||||
float distance;
|
||||
|
||||
@Override
|
||||
public float getDistance(){
|
||||
return distance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDistance(float distance){
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
/** Returns the index of the current segment along the path */
|
||||
public int getSegmentIndex(){
|
||||
return segmentIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@code Segment} connects two consecutive waypoints of a {@link LinePath}.
|
||||
* @author davebaol
|
||||
*/
|
||||
public static class Segment{
|
||||
Vec2 begin;
|
||||
Vec2 end;
|
||||
float length;
|
||||
float cumulativeLength;
|
||||
|
||||
/**
|
||||
* Creates a {@code Segment} for the 2 given points.
|
||||
*/
|
||||
Segment(Vec2 begin, Vec2 end){
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
this.length = begin.dst(end);
|
||||
}
|
||||
|
||||
/** Returns the start point of this segment. */
|
||||
public Vec2 getBegin(){
|
||||
return begin;
|
||||
}
|
||||
|
||||
/** Returns the end point of this segment. */
|
||||
public Vec2 getEnd(){
|
||||
return end;
|
||||
}
|
||||
|
||||
/** Returns the length of this segment. */
|
||||
public float getLength(){
|
||||
return length;
|
||||
}
|
||||
|
||||
/** Returns the cumulative length from the first waypoint of the {@link LinePath} this segment belongs to. */
|
||||
public float getCumulativeLength(){
|
||||
return cumulativeLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils.rays;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.Ray;
|
||||
|
||||
/**
|
||||
* A {@code CentralRayWithWhiskersConfiguration} uses a long central ray and two shorter whiskers.
|
||||
* <p>
|
||||
* A central ray with short whiskers is often the best initial configuration to try but can make it impossible for the character
|
||||
* to move down tight passages. Also, it is still susceptible to the <a
|
||||
* href="../behaviors/RaycastObstacleAvoidance.html#cornerTrap">corner trap</a>, far less than the parallel configuration though.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class CentralRayWithWhiskersConfiguration extends RayConfigurationBase{
|
||||
|
||||
private float rayLength;
|
||||
private float whiskerLength;
|
||||
private float whiskerAngle;
|
||||
|
||||
/**
|
||||
* Creates a {@code CentralRayWithWhiskersConfiguration} for the given owner where the central ray has the specified length and
|
||||
* the two whiskers have the specified length and angle.
|
||||
* @param owner the owner of this configuration
|
||||
* @param rayLength the length of the central ray
|
||||
* @param whiskerLength the length of the two whiskers (usually shorter than the central ray)
|
||||
* @param whiskerAngle the angle in radians of the whiskers from the central ray
|
||||
*/
|
||||
public CentralRayWithWhiskersConfiguration(Steerable owner, float rayLength, float whiskerLength, float whiskerAngle){
|
||||
super(owner, 3);
|
||||
this.rayLength = rayLength;
|
||||
this.whiskerLength = whiskerLength;
|
||||
this.whiskerAngle = whiskerAngle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ray[] updateRays(){
|
||||
Vec2 ownerPosition = owner.getPosition();
|
||||
Vec2 ownerVelocity = owner.getLinearVelocity();
|
||||
|
||||
float velocityAngle = owner.vectorToAngle(ownerVelocity);
|
||||
|
||||
// Update central ray
|
||||
rays[0].start.set(ownerPosition);
|
||||
rays[0].end.set(ownerVelocity).nor().scl(rayLength).add(ownerPosition);
|
||||
|
||||
// Update left ray
|
||||
rays[1].start.set(ownerPosition);
|
||||
owner.angleToVector(rays[1].end, velocityAngle - whiskerAngle).scl(whiskerLength).add(ownerPosition);
|
||||
|
||||
// Update right ray
|
||||
rays[2].start.set(ownerPosition);
|
||||
owner.angleToVector(rays[2].end, velocityAngle + whiskerAngle).scl(whiskerLength).add(ownerPosition);
|
||||
|
||||
return rays;
|
||||
}
|
||||
|
||||
/** Returns the length of the central ray. */
|
||||
public float getRayLength(){
|
||||
return rayLength;
|
||||
}
|
||||
|
||||
/** Sets the length of the central ray. */
|
||||
public void setRayLength(float rayLength){
|
||||
this.rayLength = rayLength;
|
||||
}
|
||||
|
||||
/** Returns the length of the two whiskers. */
|
||||
public float getWhiskerLength(){
|
||||
return whiskerLength;
|
||||
}
|
||||
|
||||
/** Sets the length of the two whiskers. */
|
||||
public void setWhiskerLength(float whiskerLength){
|
||||
this.whiskerLength = whiskerLength;
|
||||
}
|
||||
|
||||
/** Returns the angle in radians of the whiskers from the central ray. */
|
||||
public float getWhiskerAngle(){
|
||||
return whiskerAngle;
|
||||
}
|
||||
|
||||
/** Sets the angle in radians of the whiskers from the central ray. */
|
||||
public void setWhiskerAngle(float whiskerAngle){
|
||||
this.whiskerAngle = whiskerAngle;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils.rays;
|
||||
|
||||
import arc.math.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* A {@code ParallelSideRayConfiguration} uses two rays parallel to the direction of motion. The rays have the same length and
|
||||
* opposite side offset.
|
||||
* <p>
|
||||
* The parallel configuration works well in areas where corners are highly obtuse but is very susceptible to the <a
|
||||
* href="../behaviors/RaycastObstacleAvoidance.html">corner trap</a>.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class ParallelSideRayConfiguration extends RayConfigurationBase{
|
||||
|
||||
private static final float HALF_PI = Mathf.PI * 0.5f;
|
||||
|
||||
private float length;
|
||||
private float sideOffset;
|
||||
|
||||
/**
|
||||
* Creates a {@code ParallelSideRayConfiguration} for the given owner where the two rays have the specified length and side
|
||||
* offset.
|
||||
* @param owner the owner of this ray configuration
|
||||
* @param length the length of the rays.
|
||||
* @param sideOffset the side offset of the rays.
|
||||
*/
|
||||
public ParallelSideRayConfiguration(Steerable owner, float length, float sideOffset){
|
||||
super(owner, 2);
|
||||
this.length = length;
|
||||
this.sideOffset = sideOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ray[] updateRays(){
|
||||
float velocityAngle = owner.vectorToAngle(owner.getLinearVelocity());
|
||||
|
||||
// Update ray 0
|
||||
owner.angleToVector(rays[0].start, velocityAngle - HALF_PI).scl(sideOffset).add(owner.getPosition());
|
||||
rays[0].end.set(owner.getLinearVelocity()).nor().scl(length); // later we'll add rays[0].start;
|
||||
|
||||
// Update ray 1
|
||||
owner.angleToVector(rays[1].start, velocityAngle + HALF_PI).scl(sideOffset).add(owner.getPosition());
|
||||
rays[1].end.set(rays[0].end).add(rays[1].start);
|
||||
|
||||
// add start position to ray 0
|
||||
rays[0].end.add(rays[0].start);
|
||||
|
||||
return rays;
|
||||
}
|
||||
|
||||
/** Returns the length of the rays. */
|
||||
public float getLength(){
|
||||
return length;
|
||||
}
|
||||
|
||||
/** Sets the length of the rays. */
|
||||
public void setLength(float length){
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/** Returns the side offset of the rays. */
|
||||
public float getSideOffset(){
|
||||
return sideOffset;
|
||||
}
|
||||
|
||||
/** Sets the side offset of the rays. */
|
||||
public void setSideOffset(float sideOffset){
|
||||
this.sideOffset = sideOffset;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils.rays;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.steer.utils.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* {@code RayConfigurationBase} is the base class for concrete ray configurations having a fixed number of rays.
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class RayConfigurationBase implements RayConfiguration{
|
||||
|
||||
protected Steerable owner;
|
||||
protected Ray[] rays;
|
||||
|
||||
/**
|
||||
* Creates a {@code RayConfigurationBase} for the given owner and the specified number of rays.
|
||||
* @param owner the owner of this configuration
|
||||
* @param numRays the number of rays used by this configuration
|
||||
*/
|
||||
public RayConfigurationBase(Steerable owner, int numRays){
|
||||
this.owner = owner;
|
||||
this.rays = new Ray[numRays];
|
||||
for(int i = 0; i < numRays; i++)
|
||||
this.rays[i] = new Ray(owner.getPosition().cpy().setZero(), owner.getPosition().cpy().setZero());
|
||||
}
|
||||
|
||||
/** Returns the owner of this configuration. */
|
||||
public Steerable getOwner(){
|
||||
return owner;
|
||||
}
|
||||
|
||||
/** Sets the owner of this configuration. */
|
||||
public void setOwner(Steerable owner){
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
/** Returns the rays of this configuration. */
|
||||
public Ray[] getRays(){
|
||||
return rays;
|
||||
}
|
||||
|
||||
/** Sets the rays of this configuration. */
|
||||
public void setRays(Ray[] rays){
|
||||
this.rays = rays;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package mindustry.ai.ai.steer.utils.rays;
|
||||
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* As the name suggests, a {@code SingleRayConfiguration} uses just one ray cast.
|
||||
* <p>
|
||||
* This configuration is useful in concave environments but grazes convex obstacles. It is not susceptible to the <a
|
||||
* href="../behaviors/RaycastObstacleAvoidance.html#cornerTrap">corner trap</a>, though.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class SingleRayConfiguration extends RayConfigurationBase{
|
||||
|
||||
private float length;
|
||||
|
||||
/**
|
||||
* Creates a {@code SingleRayConfiguration} for the given owner where the ray has the specified length.
|
||||
* @param owner the owner of this configuration
|
||||
* @param length the length of the ray
|
||||
*/
|
||||
public SingleRayConfiguration(Steerable owner, float length){
|
||||
super(owner, 1);
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Ray[] updateRays(){
|
||||
rays[0].start.set(owner.getPosition());
|
||||
rays[0].end.set(owner.getLinearVelocity()).nor().scl(length).add(rays[0].start);
|
||||
return rays;
|
||||
}
|
||||
|
||||
/** Returns the length of the ray. */
|
||||
public float getLength(){
|
||||
return length;
|
||||
}
|
||||
|
||||
/** Sets the length of the ray. */
|
||||
public void setLength(float length){
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package mindustry.ai.ai.utils;
|
||||
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* A {@code Collision} is made up of a collision point and the normal at that point of collision.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Collision{
|
||||
/** The collision point. */
|
||||
public Vec2 point;
|
||||
/** The normal of this collision. */
|
||||
public Vec2 normal;
|
||||
|
||||
/**
|
||||
* Creates a {@code Collision} with the given {@code point} and {@code normal}.
|
||||
* @param point the point where this collision occurred
|
||||
* @param normal the normal of this collision
|
||||
*/
|
||||
public Collision(Vec2 point, Vec2 normal){
|
||||
this.point = point;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this collision from the given collision.
|
||||
* @param collision The collision
|
||||
* @return this collision for chaining.
|
||||
*/
|
||||
public Collision set(Collision collision){
|
||||
this.point.set(collision.point);
|
||||
this.normal.set(collision.normal);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this collision from the given point and normal.
|
||||
* @param point the collision point
|
||||
* @param normal the normal of this collision
|
||||
* @return this collision for chaining.
|
||||
*/
|
||||
public Collision set(Vec2 point, Vec2 normal){
|
||||
this.point.set(point);
|
||||
this.normal.set(normal);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package mindustry.ai.ai.utils;
|
||||
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* The {@code Location} interface represents any game object having a position and an orientation.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface Location{
|
||||
|
||||
/** Returns the vector indicating the position of this location. */
|
||||
Vec2 getPosition();
|
||||
|
||||
/**
|
||||
* Returns the float value indicating the orientation of this location. The orientation is the angle in radians representing
|
||||
* the direction that this location is facing.
|
||||
*/
|
||||
float getOrientation();
|
||||
|
||||
/**
|
||||
* Sets the orientation of this location, i.e. the angle in radians representing the direction that this location is facing.
|
||||
* @param orientation the orientation in radians
|
||||
*/
|
||||
void setOrientation(float orientation);
|
||||
|
||||
/**
|
||||
* Returns the angle in radians pointing along the specified vector.
|
||||
* @param vector the vector
|
||||
*/
|
||||
default float vectorToAngle(Vec2 vector){
|
||||
return Mathf.atan2(-vector.x, vector.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unit vector in the direction of the specified angle expressed in radians.
|
||||
* @param outVector the output vector.
|
||||
* @param angle the angle in radians.
|
||||
* @return the output vector for chaining.
|
||||
*/
|
||||
default Vec2 angleToVector(Vec2 outVector, float angle){
|
||||
return outVector.set(-Mathf.sin(angle), Mathf.cos(angle));
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package mindustry.ai.ai.utils;
|
||||
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* A {@code Ray} is made up of a starting point and an ending point.
|
||||
* @author davebaol
|
||||
*/
|
||||
public class Ray{
|
||||
/** The starting point of this ray. */
|
||||
public Vec2 start;
|
||||
/** The ending point of this ray. */
|
||||
public Vec2 end;
|
||||
|
||||
/**
|
||||
* Creates a {@code Ray} with the given {@code start} and {@code end} points.
|
||||
* @param start the starting point of this ray
|
||||
* @param end the starting point of this ray
|
||||
*/
|
||||
public Ray(Vec2 start, Vec2 end){
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this ray from the given ray.
|
||||
* @param ray The ray
|
||||
* @return this ray for chaining.
|
||||
*/
|
||||
public Ray set(Ray ray){
|
||||
start.set(ray.start);
|
||||
end.set(ray.end);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this Ray from the given start and end points.
|
||||
* @param start the starting point of this ray
|
||||
* @param end the starting point of this ray
|
||||
* @return this ray for chaining.
|
||||
*/
|
||||
public Ray set(Vec2 start, Vec2 end){
|
||||
this.start.set(start);
|
||||
this.end.set(end);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package mindustry.ai.ai.utils;
|
||||
|
||||
|
||||
/**
|
||||
* A {@code RaycastCollisionDetector} finds the closest intersection between a ray and any object in the game world.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface RaycastCollisionDetector{
|
||||
|
||||
/**
|
||||
* Casts the given ray to test if it collides with any objects in the game world.
|
||||
* @param ray the ray to cast.
|
||||
* @return {@code true} in case of collision; {@code false} otherwise.
|
||||
*/
|
||||
boolean collides(Ray ray);
|
||||
|
||||
/**
|
||||
* Find the closest collision between the given input ray and the objects in the game world. In case of collision,
|
||||
* {@code outputCollision} will contain the collision point and the normal vector of the obstacle at the point of collision.
|
||||
* @param outputCollision the output collision.
|
||||
* @param inputRay the ray to cast.
|
||||
* @return {@code true} in case of collision; {@code false} otherwise.
|
||||
*/
|
||||
boolean findCollision(Collision outputCollision, Ray inputRay);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package mindustry.ai.ai.utils;
|
||||
|
||||
/** All units are in seconds. */
|
||||
public class Timepiece{
|
||||
public static float time;
|
||||
public static float deltaTime;
|
||||
|
||||
public void update(float deltaTime){
|
||||
Timepiece.deltaTime = deltaTime;
|
||||
Timepiece.time = time + deltaTime;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package mindustry.ai.ai.utils;
|
||||
|
||||
import arc.math.geom.*;
|
||||
|
||||
public class VecLocation implements Location{
|
||||
float orientation;
|
||||
Vec2 position = new Vec2();
|
||||
|
||||
@Override
|
||||
public Vec2 getPosition(){
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getOrientation(){
|
||||
return orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOrientation(float orientation){
|
||||
this.orientation = orientation;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
import arc.struct.*;
|
||||
@@ -1,10 +1,8 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.ai.ai.steer.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* A {@code Formation} coordinates the movement of a group of characters so that they retain some group organization. Characters
|
||||
@@ -24,54 +22,51 @@ public class Formation{
|
||||
Array<SlotAssignment> slotAssignments;
|
||||
|
||||
/** The anchor point of this formation. */
|
||||
protected Location anchor;
|
||||
|
||||
public Vec3 anchor;
|
||||
/** The formation pattern */
|
||||
protected FormationPattern pattern;
|
||||
|
||||
public FormationPattern pattern;
|
||||
/** The strategy used to assign a member to his slot */
|
||||
protected SlotAssignmentStrategy slotAssignmentStrategy;
|
||||
|
||||
public SlotAssignmentStrategy slotAssignmentStrategy;
|
||||
/** The formation motion moderator */
|
||||
protected FormationMotionModerator motionModerator;
|
||||
public FormationMotionModerator motionModerator;
|
||||
|
||||
private final Vec2 positionOffset;
|
||||
private final Mat orientationMatrix = new Mat();
|
||||
|
||||
/** The location representing the drift offset for the currently filled slots. */
|
||||
private final Location driftOffset;
|
||||
private final Vec3 driftOffset;
|
||||
|
||||
/**
|
||||
* Creates a {@code Formation} for the specified {@code pattern} using a {@link FreeSlotAssignmentStrategy} and no motion
|
||||
* moderator.
|
||||
* @param anchor the anchor point of this formation, usually a {@link Steerable}. Cannot be {@code null}.
|
||||
* @param anchor the anchor point of this formation, Cannot be {@code null}.
|
||||
* @param pattern the pattern of this formation
|
||||
* @throws IllegalArgumentException if the anchor point is {@code null}
|
||||
*/
|
||||
public Formation(Location anchor, FormationPattern pattern){
|
||||
public Formation(Vec3 anchor, FormationPattern pattern){
|
||||
this(anchor, pattern, new FreeSlotAssignmentStrategy(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Formation} for the specified {@code pattern} and {@code slotAssignmentStrategy} using no motion moderator.
|
||||
* @param anchor the anchor point of this formation, usually a {@link Steerable}. Cannot be {@code null}.
|
||||
* @param anchor the anchor point of this formation, Cannot be {@code null}.
|
||||
* @param pattern the pattern of this formation
|
||||
* @param slotAssignmentStrategy the strategy used to assign a member to his slot
|
||||
* @throws IllegalArgumentException if the anchor point is {@code null}
|
||||
*/
|
||||
public Formation(Location anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy){
|
||||
public Formation(Vec3 anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy){
|
||||
this(anchor, pattern, slotAssignmentStrategy, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code Formation} for the specified {@code pattern}, {@code slotAssignmentStrategy} and {@code moderator}.
|
||||
* @param anchor the anchor point of this formation, usually a {@link Steerable}. Cannot be {@code null}.
|
||||
* @param anchor the anchor point of this formation, Cannot be {@code null}.
|
||||
* @param pattern the pattern of this formation
|
||||
* @param slotAssignmentStrategy the strategy used to assign a member to his slot
|
||||
* @param motionModerator the motion moderator. Can be {@code null} if moderation is not needed
|
||||
* @throws IllegalArgumentException if the anchor point is {@code null}
|
||||
*/
|
||||
public Formation(Location anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy,
|
||||
public Formation(Vec3 anchor, FormationPattern pattern, SlotAssignmentStrategy slotAssignmentStrategy,
|
||||
FormationMotionModerator motionModerator){
|
||||
if(anchor == null) throw new IllegalArgumentException("The anchor point cannot be null");
|
||||
this.anchor = anchor;
|
||||
@@ -80,64 +75,8 @@ public class Formation{
|
||||
this.motionModerator = motionModerator;
|
||||
|
||||
this.slotAssignments = new Array<>();
|
||||
this.driftOffset = new VecLocation();
|
||||
this.positionOffset = anchor.getPosition().cpy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current anchor point of the formation. This can be the location (i.e. position and orientation) of a leader
|
||||
* member, a modified center of mass of the members in the formation, or an invisible but steered anchor point for a two-level
|
||||
* steering system.
|
||||
*/
|
||||
public Location getAnchorPoint(){
|
||||
return anchor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the anchor point of the formation.
|
||||
* @param anchor the anchor point to set
|
||||
*/
|
||||
public void setAnchorPoint(Location anchor){
|
||||
this.anchor = anchor;
|
||||
}
|
||||
|
||||
/** @return the pattern of this formation */
|
||||
public FormationPattern getPattern(){
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the pattern of this formation
|
||||
* @param pattern the pattern to set
|
||||
*/
|
||||
public void setPattern(FormationPattern pattern){
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
/** @return the slot assignment strategy of this formation */
|
||||
public SlotAssignmentStrategy getSlotAssignmentStrategy(){
|
||||
return slotAssignmentStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the slot assignment strategy of this formation
|
||||
* @param slotAssignmentStrategy the slot assignment strategy to set
|
||||
*/
|
||||
public void setSlotAssignmentStrategy(SlotAssignmentStrategy slotAssignmentStrategy){
|
||||
this.slotAssignmentStrategy = slotAssignmentStrategy;
|
||||
}
|
||||
|
||||
/** @return the motion moderator of this formation */
|
||||
public FormationMotionModerator getMotionModerator(){
|
||||
return motionModerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the motion moderator of this formation
|
||||
* @param motionModerator the motion moderator to set
|
||||
*/
|
||||
public void setMotionModerator(FormationMotionModerator motionModerator){
|
||||
this.motionModerator = motionModerator;
|
||||
this.driftOffset = new Vec3();
|
||||
this.positionOffset = new Vec2(anchor.x, anchor.y).cpy();
|
||||
}
|
||||
|
||||
/** Updates the assignment of members to slots */
|
||||
@@ -146,7 +85,7 @@ public class Formation{
|
||||
slotAssignmentStrategy.updateSlotAssignments(slotAssignments);
|
||||
|
||||
// Set the newly calculated number of slots
|
||||
pattern.setNumberOfSlots(slotAssignmentStrategy.calculateNumberOfSlots(slotAssignments));
|
||||
pattern.slots = slotAssignmentStrategy.calculateNumberOfSlots(slotAssignments);
|
||||
|
||||
// Update the drift offset if a motion moderator is set
|
||||
if(motionModerator != null) motionModerator.calculateDriftOffset(driftOffset, slotAssignments, pattern);
|
||||
@@ -164,7 +103,7 @@ public class Formation{
|
||||
|
||||
// Check if the pattern supports one more slot
|
||||
if(pattern.supportsSlots(occupiedSlots)){
|
||||
setPattern(pattern);
|
||||
this.pattern = pattern;
|
||||
|
||||
// Update the slot assignments and return success
|
||||
updateSlotAssignments();
|
||||
@@ -236,38 +175,33 @@ public class Formation{
|
||||
|
||||
/** Writes new slot locations to each member */
|
||||
public void updateSlots(){
|
||||
// Find the anchor point
|
||||
Location anchor = getAnchorPoint();
|
||||
|
||||
positionOffset.set(anchor.getPosition());
|
||||
float orientationOffset = anchor.getOrientation();
|
||||
positionOffset.set(anchor);
|
||||
float orientationOffset = anchor.z;
|
||||
if(motionModerator != null){
|
||||
positionOffset.sub(driftOffset.getPosition());
|
||||
orientationOffset -= driftOffset.getOrientation();
|
||||
positionOffset.sub(driftOffset);
|
||||
orientationOffset -= driftOffset.z;
|
||||
}
|
||||
|
||||
// Get the orientation of the anchor point as a matrix
|
||||
orientationMatrix.idt().rotateRad(anchor.getOrientation());
|
||||
orientationMatrix.idt().rotate(anchor.z);
|
||||
|
||||
// Go through each member in turn
|
||||
for(int i = 0; i < slotAssignments.size; i++){
|
||||
SlotAssignment slotAssignment = slotAssignments.get(i);
|
||||
|
||||
// Retrieve the location reference of the formation member to calculate the new value
|
||||
Location relativeLoc = slotAssignment.member.getTargetLocation();
|
||||
Vec3 relativeLoc = slotAssignment.member.formationPos();
|
||||
float z = relativeLoc.z;
|
||||
|
||||
// Ask for the location of the slot relative to the anchor point
|
||||
pattern.calculateSlotLocation(relativeLoc, slotAssignment.slotNumber);
|
||||
|
||||
Vec2 relativeLocPosition = relativeLoc.getPosition();
|
||||
|
||||
// Transform it by the anchor point's position and orientation
|
||||
//relativeLocPosition.mul(orientationMatrix).add(anchor.position);
|
||||
relativeLocPosition.mul(orientationMatrix);
|
||||
relativeLoc.mul(orientationMatrix);
|
||||
|
||||
// Add the anchor and drift components
|
||||
relativeLocPosition.add(positionOffset);
|
||||
relativeLoc.setOrientation(relativeLoc.getOrientation() + orientationOffset);
|
||||
relativeLoc.add(positionOffset.x, positionOffset.y, 0);
|
||||
relativeLoc.z = z + orientationOffset;
|
||||
}
|
||||
|
||||
// Possibly reset the anchor point if a moderator is set
|
||||
@@ -1,6 +1,6 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import mindustry.ai.ai.utils.*;
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* Game characters coordinated by a {@link Formation} must implement this interface. Any {@code FormationMember} has a target
|
||||
@@ -9,7 +9,6 @@ import mindustry.ai.ai.utils.*;
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface FormationMember{
|
||||
|
||||
/** Returns the target location of this formation member. */
|
||||
Location getTargetLocation();
|
||||
Vec3 formationPos();
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.math.geom.*;
|
||||
import arc.struct.*;
|
||||
import mindustry.ai.ai.utils.*;
|
||||
|
||||
/**
|
||||
* A {@code FormationMotionModerator} moderates the movement of the formation based on the current positions of the members in its
|
||||
@@ -11,14 +10,13 @@ import mindustry.ai.ai.utils.*;
|
||||
* @author davebaol
|
||||
*/
|
||||
public abstract class FormationMotionModerator{
|
||||
|
||||
private Location tempLocation;
|
||||
private Vec3 tempLocation;
|
||||
|
||||
/**
|
||||
* Update the anchor point to moderate formation motion. This method is called at each frame.
|
||||
* @param anchor the anchor point
|
||||
*/
|
||||
public abstract void updateAnchorPoint(Location anchor);
|
||||
public abstract void updateAnchorPoint(Vec3 anchor);
|
||||
|
||||
/**
|
||||
* Calculates the drift offset when members are in the given set of slots for the specified pattern.
|
||||
@@ -27,31 +25,26 @@ public abstract class FormationMotionModerator{
|
||||
* @param pattern the pattern
|
||||
* @return the given location for chaining.
|
||||
*/
|
||||
public Location calculateDriftOffset(Location centerOfMass, Array<SlotAssignment> slotAssignments,
|
||||
FormationPattern pattern){
|
||||
|
||||
public Vec3 calculateDriftOffset(Vec3 centerOfMass, Array<SlotAssignment> slotAssignments, FormationPattern pattern){
|
||||
// Clear the center of mass
|
||||
centerOfMass.getPosition().setZero();
|
||||
centerOfMass.x = centerOfMass.y = 0;
|
||||
float centerOfMassOrientation = 0;
|
||||
|
||||
// Make sure tempLocation is instantiated
|
||||
if(tempLocation == null) tempLocation = new VecLocation();
|
||||
|
||||
Vec2 centerOfMassPos = centerOfMass.getPosition();
|
||||
Vec2 tempLocationPos = tempLocation.getPosition();
|
||||
if(tempLocation == null) tempLocation = new Vec3();
|
||||
|
||||
// Go through each assignment and add its contribution to the center
|
||||
float numberOfAssignments = slotAssignments.size;
|
||||
for(int i = 0; i < numberOfAssignments; i++){
|
||||
pattern.calculateSlotLocation(tempLocation, slotAssignments.get(i).slotNumber);
|
||||
centerOfMassPos.add(tempLocationPos);
|
||||
centerOfMassOrientation += tempLocation.getOrientation();
|
||||
centerOfMass.add(tempLocation);
|
||||
centerOfMassOrientation += tempLocation.z;
|
||||
}
|
||||
|
||||
// Divide through to get the drift offset.
|
||||
centerOfMassPos.scl(1f / numberOfAssignments);
|
||||
centerOfMass.scl(1f / numberOfAssignments);
|
||||
centerOfMassOrientation /= numberOfAssignments;
|
||||
centerOfMass.setOrientation(centerOfMassOrientation);
|
||||
centerOfMass.z = centerOfMassOrientation;
|
||||
|
||||
return centerOfMass;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import mindustry.ai.ai.utils.*;
|
||||
import arc.math.geom.*;
|
||||
|
||||
/**
|
||||
* The {@code FormationPattern} interface represents the shape of a formation and generates the slot offsets, relative to its
|
||||
@@ -10,21 +10,18 @@ import mindustry.ai.ai.utils.*;
|
||||
* {@code FormationPattern} interface.
|
||||
* @author davebaol
|
||||
*/
|
||||
public interface FormationPattern{
|
||||
|
||||
/**
|
||||
* Sets the number of slots.
|
||||
* @param numberOfSlots the number of slots to set
|
||||
*/
|
||||
void setNumberOfSlots(int numberOfSlots);
|
||||
public abstract class FormationPattern{
|
||||
public int slots;
|
||||
|
||||
/** Returns the location of the given slot index. */
|
||||
Location calculateSlotLocation(Location outLocation, int slotNumber);
|
||||
public abstract Vec3 calculateSlotLocation(Vec3 out, int slot);
|
||||
|
||||
/**
|
||||
* Returns true if the pattern can support the given number of slots
|
||||
* @param slotCount the number of slots
|
||||
* @return {@code true} if this pattern can support the given number of slots; {@code false} othervwise.
|
||||
*/
|
||||
boolean supportsSlots(int slotCount);
|
||||
public boolean supportsSlots(int slotCount){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
import arc.struct.*;
|
||||
@@ -1,4 +1,4 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
/**
|
||||
@@ -1,4 +1,4 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package mindustry.ai.ai.fma;
|
||||
package mindustry.ai.formations;
|
||||
|
||||
|
||||
import arc.struct.*;
|
||||
@@ -0,0 +1,32 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class CircleFormation extends FormationPattern{
|
||||
/** The radius of one member. This is needed to determine how close we can pack a given number of members around circle. */
|
||||
public float memberRadius;
|
||||
/** Angle offset. */
|
||||
public float angleOffset = 0;
|
||||
|
||||
public CircleFormation(float memberRadius){
|
||||
this.memberRadius = memberRadius;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 outLocation, int slotNumber){
|
||||
if(slots > 1){
|
||||
float angle = (360f * slotNumber) / slots;
|
||||
float radius = memberRadius / (float)Math.sin(180f / slots * Mathf.degRad);
|
||||
outLocation.set(Angles.trnsx(angle, radius), Angles.trnsy(angle, radius), angle);
|
||||
}else{
|
||||
outLocation.set(0, 0, 360f * slotNumber);
|
||||
}
|
||||
|
||||
outLocation.z += angleOffset;
|
||||
|
||||
return outLocation;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package mindustry.ai.formations.patterns;
|
||||
|
||||
import arc.math.*;
|
||||
import arc.math.geom.*;
|
||||
import mindustry.ai.formations.*;
|
||||
|
||||
public class SquareFormation extends FormationPattern{
|
||||
public float spacing = 20;
|
||||
|
||||
@Override
|
||||
public Vec3 calculateSlotLocation(Vec3 out, int slot){
|
||||
//side of each square of formation
|
||||
int side = Mathf.ceil(Mathf.sqrt(slots + 1));
|
||||
int cx = slot % side, cy = slot / side;
|
||||
|
||||
//don't hog the middle spot
|
||||
if(cx == side /2 && cy == side/2 && (side%2)==1){
|
||||
slot = slots;
|
||||
|
||||
cx = slot % side;
|
||||
cy = slot / side;
|
||||
}
|
||||
|
||||
return out.set(cx - (side/2f - 0.5f), cy - (side/2f - 0.5f), 0).scl(spacing);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,72 @@
|
||||
package mindustry.ai.types;
|
||||
|
||||
import arc.*;
|
||||
import arc.math.geom.*;
|
||||
import arc.util.ArcAnnotate.*;
|
||||
import mindustry.ai.formations.*;
|
||||
import mindustry.ai.formations.patterns.*;
|
||||
import mindustry.entities.units.*;
|
||||
import mindustry.gen.*;
|
||||
|
||||
public class FormationAI extends AIController{
|
||||
public @Nullable Unitc control;
|
||||
public class FormationAI extends AIController implements FormationMember{
|
||||
public @Nullable Unitc leader;
|
||||
|
||||
public FormationAI(@Nullable Unitc control){
|
||||
this.control = control;
|
||||
private transient Vec3 target = new Vec3();
|
||||
|
||||
public FormationAI(@Nullable Unitc leader){
|
||||
this.leader = leader;
|
||||
}
|
||||
|
||||
public FormationAI(){
|
||||
static Formation formation;
|
||||
static Vec2 vec = new Vec2();
|
||||
|
||||
@Override
|
||||
public void init(){
|
||||
if(formation == null){
|
||||
Vec3 vec = new Vec3();
|
||||
|
||||
formation = new Formation(vec, new SquareFormation());
|
||||
Core.app.addListener(new ApplicationListener(){
|
||||
@Override
|
||||
public void update(){
|
||||
formation.updateSlots();
|
||||
vec.set(leader.x(), leader.y(), leader.rotation());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
formation.addMember(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(){
|
||||
if(control != null){
|
||||
if(leader != null){
|
||||
|
||||
unit.controlWeapons(control.isRotate(), control.isShooting());
|
||||
unit.controlWeapons(leader.isRotate(), leader.isShooting());
|
||||
// unit.moveAt(Tmp.v1.set(deltaX, deltaY).limit(unit.type().speed));
|
||||
if(control.isShooting()){
|
||||
unit.aimLook(control.aimX(), control.aimY());
|
||||
if(leader.isShooting()){
|
||||
unit.aimLook(leader.aimX(), leader.aimY());
|
||||
}else{
|
||||
unit.lookAt(unit.vel().angle());
|
||||
|
||||
unit.lookAt(leader.rotation());
|
||||
if(!unit.vel().isZero(0.001f)){
|
||||
// unit.lookAt(unit.vel().angle());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
unit.moveAt(vec.set(target).sub(unit).limit2(unit.type().speed));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFollowing(Playerc player){
|
||||
return control == player.unit();
|
||||
return leader == player.unit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vec3 formationPos(){
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import static mindustry.Vars.*;
|
||||
|
||||
@Component
|
||||
abstract class UnitComp implements Healthc, Physicsc, Hitboxc, Statusc, Teamc, Itemsc, Rotc, Unitc, Weaponsc, Drawc, Boundedc, Syncc, Shieldc{
|
||||
|
||||
@Import float x, y, rotation, elevation, maxHealth;
|
||||
|
||||
private UnitController controller;
|
||||
|
||||
@@ -44,9 +44,14 @@ public class AIController implements UnitController{
|
||||
}
|
||||
}
|
||||
|
||||
protected void init(){
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unit(Unitc unit){
|
||||
this.unit = unit;
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -277,6 +277,8 @@ public class UnitType extends UnlockableContent{
|
||||
}
|
||||
|
||||
public void drawLegs(Legsc unit){
|
||||
Draw.reset();
|
||||
|
||||
Draw.mixcol(Color.white, unit.hitTime());
|
||||
|
||||
float ft = Mathf.sin(unit.walkTime(), 6f, 2f + unit.hitSize() / 15f);
|
||||
|
||||
Reference in New Issue
Block a user