import Vector from '../../math/Vector';
import Location from '../../utils/Location';
import Limiter from '../Limiter';
import Steerable from '../Steerable';
import SteeringAcceleration from '../SteeringAcceleration';
import SteeringBehavior from '../SteeringBehavior';
/**
* {@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.
*
* {@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.
*
* 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.
*
* @param Type of vector, either 2D or 3D, implementing the {@link Vector} interface
*
* @author davebaol
*/
class Arrive> extends SteeringBehavior {
/** The target to arrive to. */
protected target: Location;
/**
* 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.
*/
protected arrivalTolerance: number = 0;
/** The radius for beginning to slow down */
protected decelerationRadius: number = 0;
/** The time over which to achieve target speed */
protected timeToTarget: number = 0.1;
/**
* 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
*/
constructor(owner: Steerable, target: Location = null) {
super(owner);
this.target = target;
}
/** Returns the target to arrive to. */
public getTarget(): Location {
return this.target;
}
/**
* Sets the target to arrive to.
* @return this behavior for chaining.
*/
public setTarget(target: Location): Arrive {
this.target = target;
return this;
}
/**
* Returns 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 getArrivalTolerance(): number {
return this.arrivalTolerance;
}
/**
* Sets 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.
* @return this behavior for chaining.
*/
public setArrivalTolerance(arrivalTolerance: number): Arrive {
this.arrivalTolerance = arrivalTolerance;
return this;
}
/** Returns the radius for beginning to slow down. */
public getDecelerationRadius(): number {
return this.decelerationRadius;
}
/**
* Sets the radius for beginning to slow down.
* @return this behavior for chaining.
*/
public setDecelerationRadius(decelerationRadius: number): Arrive {
this.decelerationRadius = decelerationRadius;
return this;
}
/** Returns the time over which to achieve target speed. */
public getTimeToTarget(): number {
return this.timeToTarget;
}
/**
* Sets the time over which to achieve target speed.
* @return this behavior for chaining.
*/
public setTimeToTarget(timeToTarget: number): Arrive {
this.timeToTarget = timeToTarget;
return this;
}
//
// Setters overridden in order to fix the correct return type for chaining
//
public setOwner(owner: Steerable): Arrive {
this.owner = owner;
return this;
}
public setEnabled(enabled: boolean): Arrive {
this.enabled = enabled;
return this;
}
/**
* Sets the limiter of this steering behavior. The given limiter must at least take care of the maximum linear speed and
* acceleration.
* @return this behavior for chaining.
*/
public setLimiter(limiter: Limiter): Arrive {
this.limiter = limiter;
return this;
}
protected calculateRealSteering(steering: SteeringAcceleration ): SteeringAcceleration {
return this.arrive(steering, this.target.getPosition());
}
protected arrive(steering: SteeringAcceleration, targetPosition: T): SteeringAcceleration {
// Get the direction and distance to the target
const toTarget: T = steering.linear.copy(targetPosition).sub(this.owner.getPosition());
const distance: number = toTarget.len();
// Check if we are there, return no steering
if (distance <= this.arrivalTolerance) return steering.setZero();
const actualLimiter: Limiter = this.getActualLimiter();
// Go max speed
let targetSpeed: number = actualLimiter.getMaxLinearSpeed();
// If we are inside the slow down radius calculate a scaled speed
if (distance <= this.decelerationRadius) targetSpeed *= distance / this.decelerationRadius;
// Target velocity combines speed and direction
// Optimized code for: toTarget.nor().scl(targetSpeed)
const targetVelocity: T = toTarget.scale(targetSpeed / distance);
// Acceleration tries to get to the target velocity without exceeding max acceleration
// Notice that steering.linear and targetVelocity are the same vector
targetVelocity
.sub(this.owner.getLinearVelocity())
.scale(1 / this.timeToTarget)
.limit(actualLimiter.getMaxLinearAcceleration());
// No angular acceleration
steering.angular = 0;
// Output the steering
return steering;
}
}
export default Arrive;