12 Commits

7 changed files with 201 additions and 22 deletions

View File

@@ -19,6 +19,7 @@ public static class Vector2DExtensions
public static Vector2D FromTo(this Vector2D from, Vector2D to) => Vector2D.FromTo(from, to);
public static Vector2D Scale(this Vector2D vector, Vector2D scale) => Vector2D.Scale(vector, scale);
public static Vector2D Perpendicular(this Vector2D vector) => Vector2D.Perpendicular(vector);
public static Vector2D Rotate(this Vector2D vector, float angleInRadian) => Vector2D.Rotate(vector, angleInRadian);
public static Vector2D Min(this Vector2D left, Vector2D right) => Vector2D.Min(left, right);
public static Vector2D Max(this Vector2D left, Vector2D right) => Vector2D.Max(left, right);

View File

@@ -40,6 +40,7 @@ public record Vector2D(float X, float Y)
public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from;
public static Vector2D Scale(Vector2D vector, Vector2D scale) => new(vector.X * scale.X, vector.Y * scale.Y);
public static Vector2D Perpendicular(Vector2D vector) => new(-vector.Y, vector.X);
public static Vector2D Rotate(Vector2D vector, float angleInRadian) => new(MathF.Cos(angleInRadian) * vector.X - MathF.Sin(angleInRadian) * vector.Y, MathF.Sin(angleInRadian) * vector.X + MathF.Cos(angleInRadian) * vector.Y);
public static Vector2D Min(Vector2D left, Vector2D right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y);
public static Vector2D Max(Vector2D left, Vector2D right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y);

View File

@@ -39,13 +39,15 @@ public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D
BehaviourController.OnBehaviourAdded += OnBehaviourAddedToController;
BehaviourController.OnBehaviourRemoved += OnBehaviourRemovedFromController;
Transform.OnPositionChanged += OnPositionChanged;
Transform.OnPositionChanged += SetNeedsRecalculation;
Transform.OnRotationChanged += SetNeedsRecalculation;
Transform.OnScaleChanged += SetNeedsRecalculation;
}
private void OnBehaviourAddedToController(IBehaviourController _, IBehaviour behaviour)
{
if (behaviour is IRigidBody2D rigidbody)
_rigidBody2D = rigidbody;
if (behaviour is IRigidBody2D rigidBody)
_rigidBody2D = rigidBody;
}
private void OnBehaviourRemovedFromController(IBehaviourController _, IBehaviour behaviour)
@@ -54,13 +56,15 @@ public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D
_rigidBody2D = null;
}
private void OnPositionChanged(ITransform transform) => NeedsRecalculation = true;
private void SetNeedsRecalculation(ITransform transform) => NeedsRecalculation = true;
protected override void OnFinalize()
{
BehaviourController.OnBehaviourAdded -= OnBehaviourAddedToController;
BehaviourController.OnBehaviourRemoved -= OnBehaviourRemovedFromController;
Transform.OnScaleChanged -= SetNeedsRecalculation;
Transform.OnPositionChanged -= OnPositionChanged;
Transform.OnPositionChanged -= SetNeedsRecalculation;
Transform.OnRotationChanged -= SetNeedsRecalculation;
}
}

View File

@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core;
using Syntriax.Engine.Physics2D.Abstract;
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D;
@@ -30,36 +31,104 @@ public class CollisionDetector : ICollisionDetector
return false;
}
private bool DetectCircleShape(ICircleCollider2D circleCollider, IShapeCollider2D shapeCollider, out CollisionDetectionInformation? collisionInformation)
private static bool DetectCircleShape(ICircleCollider2D circleCollider, IShapeCollider2D shapeCollider, out CollisionDetectionInformation? collisionInformation)
{
throw new System.NotImplementedException();
return DetectShapeCircle(shapeCollider, circleCollider, out collisionInformation);
}
private static bool DetectShapeShape(IShapeCollider2D left, IShapeCollider2D right, out CollisionDetectionInformation? collisionInformation)
{
throw new System.NotImplementedException();
collisionInformation = default;
var vertices = left.ShapeWorld.Vertices;
int count = vertices.Count;
for (int indexProjection = 0; indexProjection < count; indexProjection++)
{
Vector2D projectionVector = vertices[indexProjection].FromTo(vertices[(indexProjection + 1) % count]).Perpendicular().Normalized;
Projection leftProjection = left.ShapeWorld.ToProjection(projectionVector);
Projection rightProjection = right.ShapeWorld.ToProjection(projectionVector);
if (!leftProjection.Overlaps(rightProjection, out float depth))
return false;
if (collisionInformation == default || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
collisionInformation = new(left, right, projectionVector, depth);
}
return true;
}
private static bool DetectShapeCircle(IShapeCollider2D shapeCollider, ICircleCollider2D circleCollider, out CollisionDetectionInformation? collisionInformation)
{
throw new System.NotImplementedException();
collisionInformation = default;
{
Vector2D shapeToCircleProjectionVector = shapeCollider.Transform.Position.FromTo(circleCollider.CircleWorld.Center).Normalized;
Projection shapeProjection = shapeCollider.ShapeWorld.ToProjection(shapeToCircleProjectionVector);
Projection circleProjection = circleCollider.CircleWorld.ToProjection(shapeToCircleProjectionVector);
if (!shapeProjection.Overlaps(circleProjection, out float depth))
return false;
if (collisionInformation == default || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
collisionInformation = new(shapeCollider, circleCollider, shapeToCircleProjectionVector, depth);
}
var vertices = shapeCollider.ShapeWorld.Vertices;
int count = vertices.Count;
for (int indexProjection = 0; indexProjection < count; indexProjection++)
{
Vector2D projectionVector = vertices[indexProjection].FromTo(vertices[(indexProjection + 1) % count]).Perpendicular().Normalized;
Projection shapeProjection = shapeCollider.ShapeWorld.ToProjection(projectionVector);
Projection circleProjection = circleCollider.CircleWorld.ToProjection(projectionVector);
if (!shapeProjection.Overlaps(circleProjection, out float depth))
return false;
if (collisionInformation == default || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
collisionInformation = new(shapeCollider, circleCollider, projectionVector, depth);
}
return true;
}
private static bool DetectCircleCircle(ICircleCollider2D left, ICircleCollider2D right, out CollisionDetectionInformation? collisionInformation)
{
collisionInformation = default;
Vector2D leftToRightCenter = left.CircleWorld.Center.FromTo(right.CircleWorld.Center);
float distanceCircleCenter = leftToRightCenter.Magnitude;
float radiusSum = left.CircleWorld.Radius + right.CircleWorld.Radius;
Vector2D leftToRightCenterProjectionVector = left.CircleWorld.Center.FromTo(right.CircleWorld.Center).Normalized;
float circleSurfaceDistance = distanceCircleCenter - radiusSum;
Projection leftProjection = left.CircleWorld.ToProjection(leftToRightCenterProjectionVector);
Projection rightProjection = right.CircleWorld.ToProjection(leftToRightCenterProjectionVector);
bool collision = circleSurfaceDistance <= 0f;
bool collision = leftProjection.Overlaps(rightProjection, out float depth);
if (collision)
collisionInformation = new(left, right, leftToRightCenter.Normalized, -circleSurfaceDistance);
collisionInformation = new(left, right, leftToRightCenterProjectionVector, depth);
return collision;
}
// private static bool DetectCircleCircle(ICircleCollider2D left, ICircleCollider2D right, out CollisionDetectionInformation? collisionInformation)
// {
// collisionInformation = default;
// Vector2D leftToRightCenter = left.CircleWorld.Center.FromTo(right.CircleWorld.Center);
// float distanceCircleCenter = leftToRightCenter.Magnitude;
// float radiusSum = left.CircleWorld.Radius + right.CircleWorld.Radius;
// float circleSurfaceDistance = distanceCircleCenter - radiusSum;
// bool collision = circleSurfaceDistance <= 0f;
// if (collision)
// collisionInformation = new(left, right, leftToRightCenter.Normalized, -circleSurfaceDistance);
// return collision;
// }
}

View File

@@ -13,6 +13,12 @@ public record Circle(Vector2D Center, float Radius)
public static Circle Displace(Circle circle, Vector2D displaceVector) => new(circle.Center + displaceVector, circle.Radius);
public static Projection Project(Circle circle, Vector2D projectionVector)
{
float projectedCenter = circle.Center.Dot(projectionVector);
return new(projectedCenter - circle.Radius, projectedCenter + circle.Radius);
}
public static Circle TransformCircle(ITransform transform, Circle circle)
=> new(transform.TransformVector2D(circle.Center), circle.Radius * transform.Scale.Magnitude);
@@ -27,6 +33,8 @@ public static class CircleExtensions
public static Circle Displace(this Circle circle, Vector2D displaceVector) => Circle.Displace(circle, displaceVector);
public static Projection ToProjection(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector);
public static Circle TransformCircle(this ITransform transform, Circle circle) => Circle.TransformCircle(transform, circle);
public static bool ApproximatelyEquals(this Circle left, Circle right) => Circle.ApproximatelyEquals(left, right);

View File

@@ -0,0 +1,45 @@
namespace Syntriax.Engine.Physics2D.Primitives;
public record Projection(float Min, float Max)
{
public static bool Overlaps(Projection left, Projection right) => Overlaps(left, right, out var _);
public static bool Overlaps(Projection left, Projection right, out float depth)
{
// TODO Try to improve this
bool rightMinInLeft = right.Min > left.Min && right.Min < left.Max;
if (rightMinInLeft)
{
depth = left.Max - right.Min;
return true;
}
bool rightMaxInLeft = right.Max < left.Max && right.Max > left.Min;
if (rightMaxInLeft)
{
depth = left.Min - right.Max;
return true;
}
bool leftMinInRight = left.Min > right.Min && left.Min < right.Max;
if (leftMinInRight)
{
depth = right.Max - left.Min;
return true;
}
bool leftMaxInRight = left.Max < right.Max && left.Max > right.Min;
if (leftMaxInRight)
{
depth = right.Min - left.Max;
return true;
}
depth = 0f;
return false;
}
}
public static class ProjectionExtensions
{
public static bool Overlaps(this Projection left, Projection right) => Projection.Overlaps(left, right);
public static bool Overlaps(this Projection left, Projection right, out float depth) => Projection.Overlaps(left, right, out depth);
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections;
using System.Collections.Generic;
@@ -9,7 +8,31 @@ namespace Syntriax.Engine.Physics2D.Primitives;
public record Shape(IList<Vector2D> Vertices) : IEnumerable<Vector2D>
{
public Vector2D this[Index index] => Vertices[index];
public static readonly Shape Triangle = CreateNgon(3, Vector2D.Up);
public static readonly Shape Box = CreateNgon(4, Vector2D.One);
public static readonly Shape Pentagon = CreateNgon(5, Vector2D.Up);
public static readonly Shape Hexagon = CreateNgon(6, Vector2D.Right);
public Vector2D this[System.Index index] => Vertices[index];
public static Shape CreateCopy(Shape shape) => new(new List<Vector2D>(shape.Vertices));
public static Shape CreateNgon(int vertexCount) => CreateNgon(vertexCount, Vector2D.Up);
public static Shape CreateNgon(int vertexCount, Vector2D positionToRotate)
{
if (vertexCount < 3)
throw new System.ArgumentException($"{nameof(vertexCount)} must have a value of more than 2.");
List<Vector2D> vertices = new(vertexCount);
float radiansPerVertex = 360f / vertexCount * Math.DegreeToRadian;
for (int i = 0; i < vertexCount; i++)
vertices.Add(positionToRotate.Rotate(i * radiansPerVertex));
return new(vertices);
}
public static Triangle GetSuperTriangle(Shape shape)
{
@@ -18,15 +41,15 @@ public record Shape(IList<Vector2D> Vertices) : IEnumerable<Vector2D>
foreach (Vector2D point in shape.Vertices)
{
minX = MathF.Min(minX, point.X);
minY = MathF.Min(minY, point.Y);
maxX = MathF.Max(maxX, point.X);
maxY = MathF.Max(maxY, point.Y);
minX = Math.Min(minX, point.X);
minY = Math.Min(minY, point.Y);
maxX = Math.Max(maxX, point.X);
maxY = Math.Max(maxY, point.Y);
}
float dx = maxX - minX;
float dy = maxY - minY;
float deltaMax = MathF.Max(dx, dy);
float deltaMax = Math.Max(dx, dy);
float midX = (minX + maxX) / 2;
float midY = (minY + maxY) / 2;
@@ -52,6 +75,30 @@ public record Shape(IList<Vector2D> Vertices) : IEnumerable<Vector2D>
return lines;
}
public static void Project(Shape shape, Vector2D projectionVector, IList<float> list)
{
list.Clear();
int count = shape.Vertices.Count;
for (int i = 0; i < count; i++)
list.Add(projectionVector.Dot(shape[i]));
}
public static Projection Project(Shape shape, Vector2D projectionVector)
{
float min = float.MaxValue;
float max = float.MinValue;
foreach (var vertex in shape)
{
float projectedLength = projectionVector.Dot(vertex);
min = Math.Min(projectedLength, min);
max = Math.Max(projectedLength, max);
}
return new(min, max);
}
public static Shape TransformShape(Shape shape, ITransform transform)
{
List<Vector2D> vertices = new(shape.Vertices.Count);
@@ -90,10 +137,14 @@ public record Shape(IList<Vector2D> Vertices) : IEnumerable<Vector2D>
public static class ShapeExtensions
{
public static Shape CreateCopy(Shape shape) => Shape.CreateCopy(shape);
public static Triangle ToSuperTriangle(this Shape shape) => Shape.GetSuperTriangle(shape);
public static void ToLines(this Shape shape, IList<Line> lines) => Shape.GetLines(shape, lines);
public static List<Line> ToLines(this Shape shape) => Shape.GetLines(shape);
public static void ToProjection(this Shape shape, Vector2D projectionVector, IList<float> list) => Shape.Project(shape, projectionVector, list);
public static Projection ToProjection(this Shape shape, Vector2D projectionVector) => Shape.Project(shape, projectionVector);
public static Shape TransformShape(this ITransform transform, Shape shape) => Shape.TransformShape(shape, transform);
public static void TransformShape(this ITransform transform, Shape from, ref Shape to) => Shape.TransformShape(from, transform, ref to);