Skip to content

Instantly share code, notes, and snippets.

@mrkybe
Created January 11, 2025 15:40
Show Gist options
  • Save mrkybe/75cdb05645a2199400aa61ea9cb1ae66 to your computer and use it in GitHub Desktop.
Save mrkybe/75cdb05645a2199400aa61ea9cb1ae66 to your computer and use it in GitHub Desktop.
using Assets.Scripts.AI;
using MoreLinq;
using UnityEngine;
namespace Assets.AI
{
public class ContextMap
{
public Vector2 Position;
public SteerDirection[] Directions { get; private set; }
private RaycastHit2D[] RaycastHits;
public Vector2[] Attractors;
public Vector2[] Repulsors;
private int NumberOfDirections = 16;
private IRandomGenerator randomGenerator;
public ContextMap()
{
Directions = new SteerDirection[NumberOfDirections];
RaycastHits = new RaycastHit2D[NumberOfDirections];
Attractors = new Vector2[12];
Repulsors = new Vector2[12];
for (int i = 0; i < NumberOfDirections; i++)
{
Directions[i] = new SteerDirection(ExtensionMethods.GetVector2FromAngle(i * (360f / NumberOfDirections)));
}
randomGenerator = new StandardRandomGenerator(new System.Random());
}
public void UpdateFromSingleVector(Vector2 v)
{
for (int i = 0; i < NumberOfDirections; i++)
{
float dot = Vector2.Dot(v, Directions[i].Direction);
dot = ((1 * dot + 1f) / 2f);
Directions[i].Magnitude = dot;
}
}
public void UpdateFromAttractors(float MaxDistance)
{
int total = 0;
for (int i = 0; i < NumberOfDirections; i++)
{
Directions[i].Magnitude = 0;
}
for (int j = 0; j < Attractors.Length; j++)
{
if (Attractors[j].Equals(Vector2.zero))
{
continue;
}
for (int i = 0; i < NumberOfDirections; i++)
{
float dot = Vector2.Dot(Attractors[j], Directions[i].Direction);
dot = dot.Remap(-1, 1, 0, 1);
Directions[i].Magnitude += ((dot * (MaxDistance - (Attractors[j].magnitude))) * 1.5f);
Directions[i].Magnitude *= 1.25f;
}
total++;
}
for (int i = 0; i < NumberOfDirections; i++)
{
Directions[i].Magnitude /= total;
}
for (int j = 0; j < Attractors.Length; j++)
{
Attractors[j] = Vector2.zero;
}
}
public void UpdateFromRepulsors(float MaxDistance)
{
int total = 0;
for (int i = 0; i < NumberOfDirections; i++)
{
Directions[i].Magnitude = 0;
}
for (int j = 0; j < Repulsors.Length; j++)
{
if (Repulsors[j] == Vector2.zero)
{
continue;
}
for (int i = 0; i < NumberOfDirections; i++)
{
float dot = Vector2.Dot(Repulsors[j], Directions[i].Direction);
dot = dot.Remap(-1, 1, 1, 0);
Directions[i].Magnitude += ((dot * (MaxDistance - (Repulsors[j].magnitude))) * 1.5f);
Directions[i].Magnitude *= 1.25f;
}
total++;
}
for (int i = 0; i < NumberOfDirections; i++)
{
Directions[i].Magnitude /= total;
}
for (int j = 0; j < Repulsors.Length; j++)
{
Repulsors[j] = Vector2.zero;
}
}
public void UpdateFromRaycastCircleBool(ContactFilter2D contactFilter, float radius)
{
RaycastHit2D[] results = new RaycastHit2D[10];
for (int i = 0; i < NumberOfDirections; i++)
{
Ray2D ray = new Ray2D(Position, Directions[i].Direction);
bool hit = Physics2D.Raycast(ray.origin, ray.direction, contactFilter, results, radius) != 0;
if (hit)
{
Directions[i].Magnitude = 0;
}
else
{
Directions[i].Magnitude = 1;
}
}
}
public void UpdateFromCircleRaycastCircleBool(ContactFilter2D contactFilter, float thickness, float radius)
{
RaycastHit2D[] results = new RaycastHit2D[10];
for (int i = 0; i < NumberOfDirections; i++)
{
Ray2D ray = new Ray2D(Position, Directions[i].Direction);
bool hit = Physics2D.CircleCast(ray.origin, thickness, ray.direction, contactFilter, results, radius) != 0;
if (hit)
{
Directions[i].Magnitude = 0;
}
else
{
Directions[i].Magnitude = 1;
}
}
}
public void UpdateFromRaycastCircleHitDistance(ContactFilter2D contactFilter, float radius)
{
RaycastHit2D[] results = new RaycastHit2D[10];
for (int i = 0; i < NumberOfDirections; i++)
{
Ray2D ray = new Ray2D(Position, Directions[i].Direction);
bool hit = Physics2D.Raycast(ray.origin, ray.direction, contactFilter, results, radius) != 0;
if (hit)
{
Directions[i].Magnitude = results[0].distance.Remap(0.125f, radius, 0, 1);
}
else
{
Directions[i].Magnitude = 1;
}
}
}
/// <summary>
/// The average of all directions, scaled by magnitude and then normalized.
/// </summary>
/// <returns></returns>
public Vector2 Average()
{
Vector2 average = Vector2.zero;
for (int i = 0; i < NumberOfDirections; i++)
{
average += Directions[i].Magnitude * Directions[i].Direction;
}
return (average / NumberOfDirections).normalized;
}
/// <summary>
/// The direction with a non 0 magnitude, with a dot product most closely matching v.
/// </summary>
/// <returns></returns>
public Vector2 NonZeroAndClosestTo(Vector2 v)
{
int idx = 0;
float tmp = 0f;
float best = -1f;
for (int i = 0; i < NumberOfDirections; i++)
{
if (Directions[i].Magnitude < 0.1f)
{
continue;
}
tmp = Vector2.Dot(v, Directions[i].Direction);
if (tmp > best)
{
idx = i;
best = tmp;
}
}
return Directions[idx].Direction;
}
/// <summary>
/// One of the directions with the minimum value, chosen at random.
/// </summary>
/// <returns></returns>
public Vector2 Minimum()
{
var m = Directions.MinBy(s => s.Magnitude).Random(randomGenerator);
if (float.IsNaN(m.Magnitude))
{
return Vector2.zero;
}
return m.Direction * m.Magnitude;
}
/// <summary>
/// One of the directions with the maximum value, chosen at random.
/// </summary>
/// <returns></returns>
public Vector2 Maximum()
{
var m = Directions.MaxBy(s => s.Magnitude).Random(randomGenerator);
if (float.IsNaN(m.Magnitude))
{
return Vector2.zero;
}
return m.Direction * m.Magnitude;
}
/// <summary>
/// Applies 'mask' to this CM.
/// </summary>
/// <param name="mask">ContextMap to apply.</param>
/// <param name="threshold">Minimum value on 'mask' that counts as masking.</param>
/// <param name="maskedValue">Value to set for masked portions.</param>
public void MaskWith(ContextMap mask, float threshold = 0.01f, float maskedValue = 0f)
{
for (int i = 0; i < Directions.Length; i++)
{
Directions[i].Magnitude = mask.Directions[i].Magnitude > threshold ? Directions[i].Magnitude : maskedValue;
}
}
/// <summary>
/// Applies 'mask' to this CM.
/// </summary>
/// <param name="mask">ContextMap to apply.</param>
public void MultiplyWith(ContextMap mask)
{
for (int i = 0; i < Directions.Length; i++)
{
Directions[i].Magnitude = mask.Directions[i].Magnitude * Directions[i].Magnitude;
}
}
public void Smooth()
{
for (int i = 0; i < Directions.Length; i++)
{
Directions[i].OldMagnitude = Directions[i].Magnitude;
}
for (int i = 0; i < Directions.Length; i++)
{
int pos = (Directions.Length + i + 1) % Directions.Length;
int neg = (Directions.Length + i - 1) % Directions.Length;
Directions[i].Magnitude = (Directions[neg].OldMagnitude + Directions[i].OldMagnitude + Directions[pos].OldMagnitude) / 3;
}
}
public void DrawSteeringVectors(Vector2 position, Color color)
{
for (int i = 0; i < NumberOfDirections; i++)
{
Debug.DrawLine(position.ToV3XY(), position.ToV3XY() + Directions[i].Direction.ToV3XY() * Directions[i].Magnitude * 0.55f, color, 0, false);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment