feat: added raycasting support for physics engine 2D
This commit is contained in:
parent
adfa6c6ba0
commit
6a41407005
@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
using Syntriax.Engine.Core;
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
namespace Syntriax.Engine.Physics2D;
|
namespace Syntriax.Engine.Physics2D;
|
||||||
@ -35,6 +37,26 @@ public interface IPhysicsEngine2D
|
|||||||
/// <param name="deltaTime">The time step.</param>
|
/// <param name="deltaTime">The time step.</param>
|
||||||
void StepIndividual(IRigidBody2D rigidBody, float deltaTime);
|
void StepIndividual(IRigidBody2D rigidBody, float deltaTime);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Casts a <see cref="Ray2D"/> into the scene and returns the closest <see cref="RaycastResult"/> it hits, if any.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ray">The <see cref="Ray2D"/> to cast.</param>
|
||||||
|
/// <param name="length">The maximum distance to check for intersections. Defaults to <see cref="float.MaxValue"/>.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="RaycastResult"/> containing information about the hit, or <see cref="null"/> if no hit was detected.
|
||||||
|
/// </returns>
|
||||||
|
RaycastResult? Raycast(Ray2D ray, float length = float.MaxValue);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Casts a <see cref="Ray2D"/> into the scene and stores all hit results in the provided list.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ray">The <see cref="Ray2D"/> to cast.</param>
|
||||||
|
/// <param name="results">
|
||||||
|
/// A list to which all <see cref="RaycastResult"/>s will be added. The list will be populated with zero or more <see cref="RaycastResult"/> objects.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="length">The maximum distance to check for intersections. Defaults to <see cref="float.MaxValue"/>.</param>
|
||||||
|
void Raycast(Ray2D ray, IList<RaycastResult> results, float length = float.MaxValue);
|
||||||
|
|
||||||
readonly record struct PhysicsIterationArguments(IPhysicsEngine2D Sender, float IterationDeltaTime);
|
readonly record struct PhysicsIterationArguments(IPhysicsEngine2D Sender, float IterationDeltaTime);
|
||||||
readonly record struct PhysicsStepArguments(IPhysicsEngine2D Sender, float StepDeltaTime);
|
readonly record struct PhysicsStepArguments(IPhysicsEngine2D Sender, float StepDeltaTime);
|
||||||
}
|
}
|
||||||
|
23
Engine.Physics2D/Abstract/IRaycastResolver2D.cs
Normal file
23
Engine.Physics2D/Abstract/IRaycastResolver2D.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Physics2D;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a 2D raycast resolver.
|
||||||
|
/// </summary>
|
||||||
|
public interface IRaycastResolver2D
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Casts a <see cref="Ray2D"/> against a specific <see cref="ICollider2D"/> shape and returns the first hit, if any.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the <see cref="ICollider2D"/>, which must implement <see cref="ICollider2D"/>.</typeparam>
|
||||||
|
/// <param name="shape">The <see cref="ICollider2D"/> shape to test against.</param>
|
||||||
|
/// <param name="ray">The <see cref="Ray2D"/> to cast.</param>
|
||||||
|
/// <param name="length">
|
||||||
|
/// The maximum distance to check along the <see cref="Ray2D"/>. Defaults to <see cref="float.MaxValue"/>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// A <see cref="RaycastResult"/> containing information about the intersection, or <see cref="null"/> if there was no hit.
|
||||||
|
/// </returns>
|
||||||
|
RaycastResult? RaycastAgainst<T>(T shape, Ray2D ray, float length = float.MaxValue) where T : ICollider2D;
|
||||||
|
}
|
14
Engine.Physics2D/Abstract/RaycastResult.cs
Normal file
14
Engine.Physics2D/Abstract/RaycastResult.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Physics2D;
|
||||||
|
|
||||||
|
public readonly struct RaycastResult(Ray2D ray, ICollider2D collider2D, Vector2D position, Vector2D normal)
|
||||||
|
{
|
||||||
|
public readonly Ray2D Ray = ray;
|
||||||
|
|
||||||
|
public readonly Vector2D Position = position;
|
||||||
|
public readonly Vector2D Normal = normal;
|
||||||
|
|
||||||
|
public readonly ICollider2D Collider2D = collider2D;
|
||||||
|
public readonly RigidBody2D? RigidBody2D = collider2D.BehaviourController.GetBehaviourInParent<RigidBody2D>();
|
||||||
|
}
|
@ -15,6 +15,7 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
|
|||||||
|
|
||||||
protected readonly ICollisionDetector2D collisionDetector = null!;
|
protected readonly ICollisionDetector2D collisionDetector = null!;
|
||||||
protected readonly ICollisionResolver2D collisionResolver = null!;
|
protected readonly ICollisionResolver2D collisionResolver = null!;
|
||||||
|
protected readonly IRaycastResolver2D raycastResolver = null!;
|
||||||
|
|
||||||
private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority));
|
private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority));
|
||||||
protected ActiveBehaviourCollectorSorted<IPrePhysicsUpdate> physicsPreUpdateCollector = new() { SortBy = SortByPriority() };
|
protected ActiveBehaviourCollectorSorted<IPrePhysicsUpdate> physicsPreUpdateCollector = new() { SortBy = SortByPriority() };
|
||||||
@ -27,6 +28,35 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
|
|||||||
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
|
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
|
||||||
public float IterationPeriod { get => _iterationPeriod; set => _iterationPeriod = value.Max(0.0001f); }
|
public float IterationPeriod { get => _iterationPeriod; set => _iterationPeriod = value.Max(0.0001f); }
|
||||||
|
|
||||||
|
public RaycastResult? Raycast(Ray2D ray, float length = float.MaxValue)
|
||||||
|
{
|
||||||
|
RaycastResult? closestResult = null;
|
||||||
|
float closestDistance = float.MaxValue;
|
||||||
|
|
||||||
|
for (int i = 0; i < colliderCollector.Count; i++)
|
||||||
|
if (raycastResolver.RaycastAgainst(colliderCollector[i], ray, length) is RaycastResult raycastResult)
|
||||||
|
{
|
||||||
|
float magnitudeSquared = raycastResult.Position.FromTo(ray.Origin).MagnitudeSquared;
|
||||||
|
|
||||||
|
if (magnitudeSquared > closestDistance)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
closestDistance = magnitudeSquared;
|
||||||
|
closestResult = raycastResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Raycast(Ray2D ray, IList<RaycastResult> results, float length = float.MaxValue)
|
||||||
|
{
|
||||||
|
results.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < colliderCollector.Count; i++)
|
||||||
|
if (raycastResolver.RaycastAgainst(colliderCollector[i], ray, length) is RaycastResult raycastResult)
|
||||||
|
results.Add(raycastResult);
|
||||||
|
}
|
||||||
|
|
||||||
public void Step(float deltaTime)
|
public void Step(float deltaTime)
|
||||||
{
|
{
|
||||||
float intervalDeltaTime = deltaTime / IterationPerStep;
|
float intervalDeltaTime = deltaTime / IterationPerStep;
|
||||||
@ -216,13 +246,15 @@ public class PhysicsEngine2D : Behaviour, IPreUpdate, IPhysicsEngine2D
|
|||||||
{
|
{
|
||||||
collisionDetector = new CollisionDetector2D();
|
collisionDetector = new CollisionDetector2D();
|
||||||
collisionResolver = new CollisionResolver2D();
|
collisionResolver = new CollisionResolver2D();
|
||||||
|
raycastResolver = new RaycastResolver2D();
|
||||||
Priority = int.MaxValue;
|
Priority = int.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhysicsEngine2D(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
|
public PhysicsEngine2D(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver, IRaycastResolver2D raycastResolver)
|
||||||
{
|
{
|
||||||
this.collisionDetector = collisionDetector;
|
this.collisionDetector = collisionDetector;
|
||||||
this.collisionResolver = collisionResolver;
|
this.collisionResolver = collisionResolver;
|
||||||
|
this.raycastResolver = raycastResolver;
|
||||||
Priority = int.MaxValue;
|
Priority = int.MaxValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
|
|||||||
|
|
||||||
private readonly ICollisionDetector2D collisionDetector = null!;
|
private readonly ICollisionDetector2D collisionDetector = null!;
|
||||||
private readonly ICollisionResolver2D collisionResolver = null!;
|
private readonly ICollisionResolver2D collisionResolver = null!;
|
||||||
|
private readonly IRaycastResolver2D raycastResolver = null!;
|
||||||
|
|
||||||
public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
|
public int IterationPerStep { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
|
||||||
|
|
||||||
@ -43,6 +44,34 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
|
|||||||
rigidBody.BehaviourController.OnBehaviourAdded.RemoveListener(delegateOnBehaviourAdded);
|
rigidBody.BehaviourController.OnBehaviourAdded.RemoveListener(delegateOnBehaviourAdded);
|
||||||
rigidBody.BehaviourController.OnBehaviourRemoved.RemoveListener(delegateOnBehaviourRemoved);
|
rigidBody.BehaviourController.OnBehaviourRemoved.RemoveListener(delegateOnBehaviourRemoved);
|
||||||
}
|
}
|
||||||
|
public RaycastResult? Raycast(Ray2D ray, float length = float.MaxValue)
|
||||||
|
{
|
||||||
|
RaycastResult? closestResult = null;
|
||||||
|
float closestDistance = float.MaxValue;
|
||||||
|
|
||||||
|
for (int i = 0; i < colliders.Count; i++)
|
||||||
|
if (raycastResolver.RaycastAgainst(colliders[i], ray, length) is RaycastResult raycastResult)
|
||||||
|
{
|
||||||
|
float magnitudeSquared = raycastResult.Position.FromTo(ray.Origin).MagnitudeSquared;
|
||||||
|
|
||||||
|
if (magnitudeSquared > closestDistance)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
closestDistance = magnitudeSquared;
|
||||||
|
closestResult = raycastResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return closestResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Raycast(Ray2D ray, IList<RaycastResult> results, float length = float.MaxValue)
|
||||||
|
{
|
||||||
|
results.Clear();
|
||||||
|
|
||||||
|
for (int i = 0; i < colliders.Count; i++)
|
||||||
|
if (raycastResolver.RaycastAgainst(colliders[i], ray, length) is RaycastResult raycastResult)
|
||||||
|
results.Add(raycastResult);
|
||||||
|
}
|
||||||
|
|
||||||
public void Step(float deltaTime)
|
public void Step(float deltaTime)
|
||||||
{
|
{
|
||||||
@ -203,15 +232,17 @@ public class PhysicsEngine2DStandalone : IPhysicsEngine2D
|
|||||||
{
|
{
|
||||||
collisionDetector = new CollisionDetector2D();
|
collisionDetector = new CollisionDetector2D();
|
||||||
collisionResolver = new CollisionResolver2D();
|
collisionResolver = new CollisionResolver2D();
|
||||||
|
raycastResolver = new RaycastResolver2D();
|
||||||
|
|
||||||
delegateOnBehaviourAdded = OnBehaviourAdded;
|
delegateOnBehaviourAdded = OnBehaviourAdded;
|
||||||
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PhysicsEngine2DStandalone(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
|
public PhysicsEngine2DStandalone(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver, IRaycastResolver2D raycastResolver)
|
||||||
{
|
{
|
||||||
this.collisionDetector = collisionDetector;
|
this.collisionDetector = collisionDetector;
|
||||||
this.collisionResolver = collisionResolver;
|
this.collisionResolver = collisionResolver;
|
||||||
|
this.raycastResolver = raycastResolver;
|
||||||
|
|
||||||
delegateOnBehaviourAdded = OnBehaviourAdded;
|
delegateOnBehaviourAdded = OnBehaviourAdded;
|
||||||
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
delegateOnBehaviourRemoved = OnBehaviourRemoved;
|
||||||
|
102
Engine.Physics2D/RaycastResolver2D.cs
Normal file
102
Engine.Physics2D/RaycastResolver2D.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Physics2D;
|
||||||
|
|
||||||
|
public class RaycastResolver2D : IRaycastResolver2D
|
||||||
|
{
|
||||||
|
private readonly Pool<List<Line2D>> lineCacheQueue = new(() => new List<Line2D>(4));
|
||||||
|
|
||||||
|
RaycastResult? IRaycastResolver2D.RaycastAgainst<T>(T shape, Ray2D ray, float length)
|
||||||
|
{
|
||||||
|
if (shape is IShapeCollider2D shapeCollider)
|
||||||
|
return RaycastAgainstShape(shapeCollider, ray, length);
|
||||||
|
else if (shape is ICircleCollider2D circleCollider)
|
||||||
|
return RaycastAgainstCircle(circleCollider, ray, length);
|
||||||
|
|
||||||
|
throw new System.NotSupportedException($"{shape.GetType().FullName} is not supported by {GetType().FullName}. Please implement a {nameof(IRaycastResolver2D)} and use it as the raycast resolver.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaycastResult? RaycastAgainstShape(IShapeCollider2D shapeCollider, Ray2D ray, float length)
|
||||||
|
{
|
||||||
|
List<Line2D> line2Ds = lineCacheQueue.Get();
|
||||||
|
line2Ds.Clear();
|
||||||
|
|
||||||
|
RaycastResult? raycastResult = null;
|
||||||
|
float closestRaycastResultSquared = float.MaxValue;
|
||||||
|
|
||||||
|
shapeCollider.ShapeWorld.ToLines(line2Ds);
|
||||||
|
|
||||||
|
Line2D rayLine = ray.ToLine(length);
|
||||||
|
|
||||||
|
foreach (Line2D line in line2Ds)
|
||||||
|
{
|
||||||
|
if (line.Intersects(rayLine))
|
||||||
|
{
|
||||||
|
Vector2D hitPosition = line.IntersectionPoint(rayLine);
|
||||||
|
|
||||||
|
float hitDistanceSquared = ray.Origin.FromTo(hitPosition).MagnitudeSquared;
|
||||||
|
|
||||||
|
if (closestRaycastResultSquared < hitDistanceSquared)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
closestRaycastResultSquared = hitDistanceSquared;
|
||||||
|
|
||||||
|
Vector2D lineDirection = line.Direction;
|
||||||
|
|
||||||
|
Vector2D normal1 = lineDirection.Perpendicular();
|
||||||
|
Vector2D normal2 = -lineDirection.Perpendicular();
|
||||||
|
|
||||||
|
float normalDot1 = rayLine.Direction.Dot(normal1);
|
||||||
|
float normalDot2 = rayLine.Direction.Dot(normal2);
|
||||||
|
|
||||||
|
Vector2D hitNormal = normalDot1 < normalDot2 ? normal1 : normal2;
|
||||||
|
|
||||||
|
if (shapeCollider.ShapeWorld.Overlaps(ray.Origin))
|
||||||
|
hitNormal = hitNormal.Reversed;
|
||||||
|
|
||||||
|
raycastResult = new(ray, shapeCollider, hitPosition, hitNormal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
line2Ds.Clear();
|
||||||
|
lineCacheQueue.Return(line2Ds);
|
||||||
|
|
||||||
|
return raycastResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RaycastResult? RaycastAgainstCircle(ICircleCollider2D circleCollider, Ray2D ray, float length)
|
||||||
|
{
|
||||||
|
Circle circle = circleCollider.CircleWorld;
|
||||||
|
|
||||||
|
if (circle.Overlaps(ray.Origin, out _, out float depth))
|
||||||
|
{
|
||||||
|
Vector2D insideNormal = circle.Center.FromTo(ray.Origin).Normalized;
|
||||||
|
Vector2D insidePosition = circle.Center + insideNormal * circle.Radius;
|
||||||
|
|
||||||
|
return new(ray, circleCollider, insidePosition, insideNormal);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2D originToCircle = ray.Origin.FromTo(circle.Center);
|
||||||
|
float distanceToClosest = ray.Direction.Dot(originToCircle);
|
||||||
|
|
||||||
|
Vector2D closestPoint = ray.Origin + ray.Direction * distanceToClosest;
|
||||||
|
Vector2D closestPointToCircle = closestPoint.FromTo(circle.Center);
|
||||||
|
|
||||||
|
float closestToCircleDistanceSquared = closestPointToCircle.MagnitudeSquared;
|
||||||
|
|
||||||
|
if (closestToCircleDistanceSquared > circle.RadiusSquared)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
float distanceToHit = distanceToClosest - (circle.Radius - closestToCircleDistanceSquared.Sqrt());
|
||||||
|
|
||||||
|
if (distanceToHit > length || distanceToHit < 0f)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Vector2D hitPosition = ray.Evaluate(distanceToHit);
|
||||||
|
Vector2D hitNormal = circle.Center.FromTo(hitPosition).Normalized;
|
||||||
|
|
||||||
|
return new(ray, circleCollider, hitPosition, hitNormal);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user