205 lines
6.9 KiB
C#
205 lines
6.9 KiB
C#
using FarseerPhysics.Dynamics;
|
|
using Microsoft.Xna.Framework;
|
|
using System;
|
|
using FarseerPhysics;
|
|
using Barotrauma.Extensions;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
class SteeringManager
|
|
{
|
|
protected const float CircleDistance = 2.5f;
|
|
protected const float CircleRadius = 0.3f;
|
|
|
|
protected const float RayCastInterval = 0.5f;
|
|
|
|
protected ISteerable host;
|
|
|
|
private Vector2 steering;
|
|
|
|
private Vector2? avoidObstaclePos;
|
|
private float rayCastTimer;
|
|
|
|
private float wanderAngle;
|
|
|
|
public float WanderAngle
|
|
{
|
|
get { return wanderAngle; }
|
|
set { wanderAngle = value; }
|
|
}
|
|
|
|
public SteeringManager(ISteerable host)
|
|
{
|
|
this.host = host;
|
|
|
|
wanderAngle = Rand.Range(0.0f, MathHelper.TwoPi);
|
|
}
|
|
|
|
public void SteeringSeek(Vector2 targetSimPos, float weight = 1)
|
|
{
|
|
steering += DoSteeringSeek(targetSimPos, weight);
|
|
}
|
|
|
|
public void SteeringWander(float weight = 1)
|
|
{
|
|
steering += DoSteeringWander(weight);
|
|
}
|
|
|
|
public void SteeringAvoid(float deltaTime, float lookAheadDistance, float weight = 1, Vector2? heading = null)
|
|
{
|
|
steering += DoSteeringAvoid(deltaTime, lookAheadDistance, weight, heading);
|
|
}
|
|
|
|
public void SteeringManual(float deltaTime, Vector2 velocity)
|
|
{
|
|
steering += velocity;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
steering = Vector2.Zero;
|
|
}
|
|
|
|
public void ResetX()
|
|
{
|
|
steering.X = 0.0f;
|
|
}
|
|
|
|
public void ResetY()
|
|
{
|
|
steering.Y = 0.0f;
|
|
}
|
|
|
|
public virtual void Update(float speed)
|
|
{
|
|
if (steering == Vector2.Zero || !MathUtils.IsValid(steering))
|
|
{
|
|
steering = Vector2.Zero;
|
|
host.Steering = Vector2.Zero;
|
|
return;
|
|
}
|
|
if (steering.LengthSquared() > speed * speed)
|
|
{
|
|
steering = Vector2.Normalize(steering) * Math.Abs(speed);
|
|
}
|
|
host.Steering = steering;
|
|
}
|
|
|
|
protected virtual Vector2 DoSteeringSeek(Vector2 target, float weight)
|
|
{
|
|
Vector2 targetVel = target - host.SimPosition;
|
|
|
|
if (targetVel.LengthSquared() < 0.00001f) return Vector2.Zero;
|
|
|
|
targetVel = Vector2.Normalize(targetVel) * weight;
|
|
Vector2 newSteering = targetVel - host.Steering;
|
|
|
|
if (newSteering == Vector2.Zero) return Vector2.Zero;
|
|
|
|
float steeringSpeed = (newSteering + host.Steering).Length();
|
|
if (steeringSpeed > Math.Abs(weight))
|
|
{
|
|
newSteering = Vector2.Normalize(newSteering) * Math.Abs(weight);
|
|
}
|
|
|
|
return newSteering;
|
|
}
|
|
|
|
protected virtual Vector2 DoSteeringWander(float weight)
|
|
{
|
|
Vector2 circleCenter = (host.Steering == Vector2.Zero) ? Rand.Vector(weight) : host.Steering;
|
|
circleCenter = Vector2.Normalize(circleCenter) * CircleDistance;
|
|
|
|
Vector2 displacement = new Vector2(
|
|
(float)Math.Cos(wanderAngle),
|
|
(float)Math.Sin(wanderAngle));
|
|
displacement = displacement * CircleRadius;
|
|
|
|
float angleChange = 1.5f;
|
|
|
|
wanderAngle += Rand.Range(0.0f, 1.0f) * angleChange - angleChange * 0.5f;
|
|
|
|
Vector2 newSteering = circleCenter + displacement;
|
|
float steeringSpeed = (newSteering + host.Steering).Length();
|
|
if (steeringSpeed > weight)
|
|
{
|
|
newSteering = Vector2.Normalize(newSteering) * weight;
|
|
}
|
|
|
|
return newSteering;
|
|
}
|
|
|
|
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float weight, Vector2? heading = null)
|
|
{
|
|
if (steering == Vector2.Zero || host.Steering == Vector2.Zero)
|
|
{
|
|
return Vector2.Zero;
|
|
}
|
|
float maxDistance = lookAheadDistance;
|
|
if (rayCastTimer <= 0.0f)
|
|
{
|
|
Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance;
|
|
rayCastTimer = RayCastInterval;
|
|
Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead);
|
|
if (closestBody == null)
|
|
{
|
|
avoidObstaclePos = null;
|
|
return Vector2.Zero;
|
|
}
|
|
else
|
|
{
|
|
// TODO: Doesn't take items into account (like turrets)
|
|
if (closestBody.UserData is Structure closestStructure)
|
|
{
|
|
Vector2 obstaclePosition = Submarine.LastPickedPosition;
|
|
if (closestStructure.IsHorizontal)
|
|
{
|
|
obstaclePosition.Y = closestStructure.SimPosition.Y;
|
|
}
|
|
else
|
|
{
|
|
obstaclePosition.X = closestStructure.SimPosition.X;
|
|
}
|
|
avoidObstaclePos = obstaclePosition;
|
|
}
|
|
else
|
|
{
|
|
avoidObstaclePos = Submarine.LastPickedPosition;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rayCastTimer -= deltaTime;
|
|
}
|
|
if (!avoidObstaclePos.HasValue)
|
|
{
|
|
return Vector2.Zero;
|
|
}
|
|
Vector2 diff = avoidObstaclePos.Value - host.SimPosition;
|
|
float dist = diff.Length();
|
|
|
|
if (dist > maxDistance)
|
|
{
|
|
return Vector2.Zero;
|
|
}
|
|
if (heading.HasValue)
|
|
{
|
|
var f = heading ?? host.Steering;
|
|
// Avoid to left or right depending on the current heading
|
|
Vector2 relativeVector = Vector2.Normalize(diff) - Vector2.Normalize(f);
|
|
var dir = relativeVector.X > 0 ? diff.Right() : diff.Left();
|
|
float factor = 1.0f - Math.Min(dist / maxDistance, 1);
|
|
return dir * factor * weight;
|
|
}
|
|
else
|
|
{
|
|
// Doesn't work right because it effectively just slows down or reverses the movement, where as we'd like to go right or left to avoid the target.
|
|
// There's also another issue, which also affects going right or left: the raycast doesn't hit anything if we turn too much -> avoiding doesn't work well.
|
|
// Could probably "remember" the avoidance a bit longer so that the avoid steering is not immedieately disgarded, but kept for a while and reduced gradually?
|
|
return -diff * (1.0f - dist / maxDistance) * weight;
|
|
}
|
|
}
|
|
}
|
|
}
|