Created
January 11, 2025 15:40
-
-
Save mrkybe/75cdb05645a2199400aa61ea9cb1ae66 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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