From 0649494f9c5393b1daa00705fd3aa26f29a0555a Mon Sep 17 00:00:00 2001 From: Syntriax Date: Fri, 1 Dec 2023 17:42:07 +0300 Subject: [PATCH] feat: Basic Collider --- Engine | 2 +- Game/Game1.cs | 60 ++++-- Game/Physics2D/Abstract/ICollider2D.cs | 4 +- Game/Physics2D/Collider2DBehaviour.cs | 233 ++++++++++++++++++++++ Game/Physics2D/Collider2DBehaviourBase.cs | 41 ---- Game/Physics2D/Collider2DBox.cs | 16 -- Game/Physics2D/PhysicsEngine2D.cs | 14 +- 7 files changed, 288 insertions(+), 82 deletions(-) create mode 100644 Game/Physics2D/Collider2DBehaviour.cs delete mode 100644 Game/Physics2D/Collider2DBehaviourBase.cs delete mode 100644 Game/Physics2D/Collider2DBox.cs diff --git a/Engine b/Engine index bb6990a..c03d74d 160000 --- a/Engine +++ b/Engine @@ -1 +1 @@ -Subproject commit bb6990a80c499a93de9c23be7c8821d66c4c3377 +Subproject commit c03d74dbe0949d75411cd368c8b07dbafd871a20 diff --git a/Game/Game1.cs b/Game/Game1.cs index 8278dc5..eddfe0b 100644 --- a/Game/Game1.cs +++ b/Game/Game1.cs @@ -7,12 +7,14 @@ using Syntriax.Engine.Core; using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Graphics.TwoDimensional; using Syntriax.Engine.Input; +using Syntriax.Engine.Physics2D; namespace Pong; public class Game1 : Game { private GraphicsDeviceManager _graphics = null!; + private PhysicsEngine2D engine; private SpriteBatch _spriteBatch = null!; private GameManager gameManager = null!; @@ -40,9 +42,10 @@ public class Game1 : Game protected override void LoadContent() { + engine = new PhysicsEngine2D(); _spriteBatch = new SpriteBatch(GraphicsDevice); - Sprite spriteBox = new Sprite() { Texture2D = Content.Load("Sprites/Pixel") }; + // Sprite spriteBox = new Sprite() { Texture2D = Content.Load("Sprites/Pixel") }; Sprite spriteBall = new Sprite() { Texture2D = Content.Load("Sprites/Circle") }; IGameObject gameObjectCamera = gameManager.InstantiateGameObject(); @@ -52,32 +55,44 @@ public class Game1 : Game gameManager.Camera = gameObjectCamera.BehaviourController.AddBehaviour(); gameManager.Camera.Viewport = GraphicsDevice.Viewport; - IGameObject gameObjectPlayArea = gameManager.InstantiateGameObject(); - PlayAreaBehaviour playAreaBehaviour = gameObjectPlayArea.BehaviourController.AddBehaviour(); - playAreaBehaviour.PlayArea = new Vector2(_graphics.PreferredBackBufferWidth, _graphics.PreferredBackBufferHeight); + // IGameObject gameObjectPlayArea = gameManager.InstantiateGameObject(); + // PlayAreaBehaviour playAreaBehaviour = gameObjectPlayArea.BehaviourController.AddBehaviour(); + // playAreaBehaviour.PlayArea = new Vector2(_graphics.PreferredBackBufferWidth, _graphics.PreferredBackBufferHeight); IGameObject gameObjectBall = gameManager.InstantiateGameObject(); gameObjectBall.Name = "Ball"; gameObjectBall.Transform.Position = Vector2.Zero; gameObjectBall.Transform.Scale = new Vector2(1f / 51.2f, 1f / 51.2f); - gameObjectBall.BehaviourController.AddBehaviour(new Vector2(.1f, .1f), playAreaBehaviour, 100f); + engine.AddRigidBody(gameObjectBall.BehaviourController.AddBehaviour()); + gameObjectBall.BehaviourController.AddBehaviour((System.Collections.Generic.IList)[new Vector2(1, 1) * 5f, new Vector2(-1, 1) * 5f, new Vector2(1, -1) * 5f, new Vector2(-1, -1) * 5f]); + // gameObjectBall.BehaviourController.AddBehaviour(new Vector2(.1f, .1f), playAreaBehaviour, 100f); gameObjectBall.BehaviourController.AddBehaviour().Assign(spriteBall); + IGameObject gameObjectBall2 = gameManager.InstantiateGameObject(); + gameObjectBall2.Name = "Ball2"; + gameObjectBall2.Transform.Position = Vector2.UnitY * -40; + gameObjectBall2.Transform.Scale = new Vector2(1f / 51.2f, 1f / 51.2f); + RigidBody2D rigidBody = gameObjectBall2.BehaviourController.AddBehaviour(); + rigidBody.Velocity = Vector2.UnitY * 10f; + engine.AddRigidBody(rigidBody); + gameObjectBall2.BehaviourController.AddBehaviour((System.Collections.Generic.IList)[new Vector2(1, 1) * 5f, new Vector2(-1, 1) * 5f, new Vector2(1, -1) * 5f, new Vector2(-1, -1) * 5f]); + // gameObjectBall2.BehaviourController.AddBehaviour(new Vector2(.1f, .1f), playAreaBehaviour, 100f); + gameObjectBall2.BehaviourController.AddBehaviour().Assign(spriteBall); - IGameObject gameObjectLeft = gameManager.InstantiateGameObject(); - gameObjectLeft.Name = "Left"; - gameObjectLeft.Transform.Position = new Vector2(-452, 0f); - gameObjectLeft.Transform.Scale = new Vector2(10f, 40f); - gameObjectLeft.BehaviourController.AddBehaviour(); - gameObjectLeft.BehaviourController.AddBehaviour(Keys.W, Keys.S, 268f, -268f, 400f); - gameObjectLeft.BehaviourController.AddBehaviour().Assign(spriteBox); + // IGameObject gameObjectLeft = gameManager.InstantiateGameObject(); + // gameObjectLeft.Name = "Left"; + // gameObjectLeft.Transform.Position = new Vector2(-452, 0f); + // gameObjectLeft.Transform.Scale = new Vector2(10f, 40f); + // gameObjectLeft.BehaviourController.AddBehaviour(); + // gameObjectLeft.BehaviourController.AddBehaviour(Keys.W, Keys.S, 268f, -268f, 400f); + // gameObjectLeft.BehaviourController.AddBehaviour().Assign(spriteBox); - IGameObject gameObjectRight = gameManager.InstantiateGameObject(); - gameObjectRight.Name = "Right"; - gameObjectRight.Transform.Position = new Vector2(452, 0f); - gameObjectRight.Transform.Scale = new Vector2(10f, 40f); - gameObjectRight.BehaviourController.AddBehaviour(); - gameObjectRight.BehaviourController.AddBehaviour(Keys.Up, Keys.Down, 268f, -268f, 400f); - gameObjectRight.BehaviourController.AddBehaviour().Assign(spriteBox); + // IGameObject gameObjectRight = gameManager.InstantiateGameObject(); + // gameObjectRight.Name = "Right"; + // gameObjectRight.Transform.Position = new Vector2(452, 0f); + // gameObjectRight.Transform.Scale = new Vector2(10f, 40f); + // gameObjectRight.BehaviourController.AddBehaviour(); + // gameObjectRight.BehaviourController.AddBehaviour(Keys.Up, Keys.Down, 268f, -268f, 400f); + // gameObjectRight.BehaviourController.AddBehaviour().Assign(spriteBox); // TODO: use this.Content to load your game content here } @@ -114,10 +129,15 @@ public class Game1 : Game gameManager.Camera.Rotation -= gameTime.ElapsedGameTime.Nanoseconds * 0.000025f; // TODO: Add your update logic here + while (physicsTimer + 0.01f < gameTime.TotalGameTime.TotalMilliseconds * .001f) + { + physicsTimer += 0.01f; + engine.Step(.01f); + } gameManager.Update(gameTime); - base.Update(gameTime); } + static float physicsTimer = 0f; protected override void Draw(GameTime gameTime) { diff --git a/Game/Physics2D/Abstract/ICollider2D.cs b/Game/Physics2D/Abstract/ICollider2D.cs index 730d7d0..f458b40 100644 --- a/Game/Physics2D/Abstract/ICollider2D.cs +++ b/Game/Physics2D/Abstract/ICollider2D.cs @@ -14,10 +14,10 @@ public interface ICollider2D : IBehaviour, IAssignableTransform Vector2 OffsetPosition { get; set; } Vector2 OffsetScale { get; set; } float OffsetRotation { get; set; } - IReadOnlyList Vertices { get; } - bool CheckCollision(ICollider2D collider); + + bool CheckCollision(Vector2 point, ICollider2D otherCollider); void RecalculateVertices(); } diff --git a/Game/Physics2D/Collider2DBehaviour.cs b/Game/Physics2D/Collider2DBehaviour.cs new file mode 100644 index 0000000..94c0e4a --- /dev/null +++ b/Game/Physics2D/Collider2DBehaviour.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.Xna.Framework; + +using Syntriax.Engine.Core; +using Syntriax.Engine.Core.Abstract; +using Syntriax.Engine.Physics2D.Abstract; + +namespace Syntriax.Engine.Physics2D; + +public class Collider2DBehaviour(IList vertices) : BehaviourOverride, ICollider2D +{ + private List triangles = new List(32); + private readonly List _vertices = new List(32); + + public Action? OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; } + public Action? OnCollision { get; set; } = null; + + public Vector2 OffsetPosition { get; set; } = Vector2.Zero; + public Vector2 OffsetScale { get; set; } = Vector2.One; + public float OffsetRotation { get; set; } = 0f; + + ITransform IAssignableTransform.Transform => Transform; + + public bool Assign(ITransform transform) => GameObject.Assign(transform); + + public bool CheckCollision(Vector2 point, ICollider2D otherCollider) + { + foreach (var triangle in triangles) + { + if (!isInside(point, triangle)) + continue; + + OnCollision?.Invoke(this, otherCollider); + return true; + } + + return false; + } + + public void RecalculateVertices() + { + triangles.Clear(); + + Triangle superTriangle = GetSuperTriangle(Vertices); + triangles.Add(superTriangle); + + List badTriangles = new(32); + List polygon = new(32); + + _vertices.Clear(); + foreach (var vertex in VerticesOriginal) + _vertices.Add(vertex + Transform.Position); + + foreach (var vertex in _vertices) + { + badTriangles.Clear(); + polygon.Clear(); + + foreach (var triangle in triangles) + { + Circle circle = GetCircumCircle(triangle); + if (Vector2.DistanceSquared(circle.Center, vertex) <= circle.Radius * circle.Radius) + badTriangles.Add(triangle); + } + + foreach (var triangle in badTriangles) + foreach (var edge in GetEdges(triangle)) + { + if (DoesEdgeExistInTriangles(edge, badTriangles)) + polygon.Add(edge); + } + + foreach (var triangle in badTriangles) + triangles.Remove(triangle); + + foreach (var edge in polygon) + { + triangles.Add(new() + { + A = edge.A, + B = edge.B, + C = vertex + }); + } + } + + for (int i = triangles.Count - 1; i >= 0; i--) + { + Triangle triangle = triangles[i]; + if ( + triangle.A == superTriangle.A || triangle.A == superTriangle.B || triangle.A == superTriangle.C || + triangle.B == superTriangle.A || triangle.B == superTriangle.B || triangle.B == superTriangle.C || + triangle.C == superTriangle.A || triangle.C == superTriangle.B || triangle.C == superTriangle.C + ) + triangles.RemoveAt(i); + } + + // for (int i = 0; i < triangles.Count; i++) + // { + // Triangle triangle = triangles[i]; + + // triangle.A += Transform.Position; + // triangle.B += Transform.Position; + // triangle.C += Transform.Position; + + // triangles[i] = triangle; + // } + } + + private bool DoesEdgeExistInTriangles(Edge edge, List triangles) + { + foreach (var triangle in triangles) + foreach (var edgeOther in GetEdges(triangle)) + if (edge.A == edgeOther.A && edge.B == edgeOther.B) + return true; + else if (edge.A == edgeOther.B && edge.B == edgeOther.A) + return true; + return false; + } + + private List GetEdges(Triangle triangle) + => [ + new() { A = triangle.A, B = triangle.B }, + new() { A = triangle.B, B = triangle.C }, + new() { A = triangle.C, B = triangle.A } + ]; + + private Triangle GetSuperTriangle(IReadOnlyList vertices) + { + float minX = float.MaxValue, minY = float.MaxValue; + float maxX = float.MinValue, maxY = float.MinValue; + + foreach (Vector2 point in vertices) + { + 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 = Math.Max(dx, dy); + float midX = (minX + maxX) / 2; + float midY = (minY + maxY) / 2; + + Vector2 p1 = new Vector2(midX - 20 * deltaMax, midY - deltaMax); + Vector2 p2 = new Vector2(midX, midY + 20 * deltaMax); + Vector2 p3 = new Vector2(midX + 20 * deltaMax, midY - deltaMax); + + return new Triangle() { A = p1, B = p2, C = p3 }; + } + + private struct Triangle { public Vector2 A, B, C; } + + private double GetArea(Triangle triangle) + { + return Math.Abs((triangle.A.X * (triangle.B.Y - triangle.C.Y) + + triangle.B.X * (triangle.C.Y - triangle.A.Y) + + triangle.C.X * (triangle.A.Y - triangle.B.Y)) / 2.0); + } + + /* A function to check whether point P(x, y) lies + inside the triangle formed by A(x1, y1), + B(x2, y2) and C(x3, y3) */ + private bool isInside(Vector2 point, Triangle triangle) + { + double A = GetArea(triangle); + /* Calculate area of triangle ABC */ + // double A = area(x1, y1, x2, y2, x3, y3); + + double A1 = GetArea(new() { A = point, B = triangle.B, C = triangle.C }); + /* Calculate area of triangle PBC */ + // double A1 = area(x, y, x2, y2, x3, y3); + + /* Calculate area of triangle PAC */ + double A2 = GetArea(new() { A = triangle.A, B = point, C = triangle.C }); + // double A2 = area(x1, y1, x, y, x3, y3); + + /* Calculate area of triangle PAB */ + double A3 = GetArea(new() { A = triangle.A, B = triangle.B, C = point }); + // double A3 = area(x1, y1, x2, y2, x, y); + + /* Check if sum of A1, A2 and A3 is same as A */ + return A == A1 + A2 + A3; + } + + private struct Edge + { + public Vector2 A; + public Vector2 B; + } + + private struct Circle + { + public double Radius; + public Vector2 Center; + } + + private Circle GetCircumCircle(Triangle triangle) + { + Circle result = new(); + + Vector2 midAB = (triangle.A + triangle.B) / 2; + Vector2 midBC = (triangle.B + triangle.C) / 2; + + float slopeAB = (triangle.B.Y - triangle.A.Y) / (triangle.B.X - triangle.A.X); + float slopeBC = (triangle.C.Y - triangle.B.Y) / (triangle.C.X - triangle.B.X); + + // Check if the slopes are not parallel + if (Math.Abs(slopeAB - slopeBC) > float.Epsilon) + { + float x = (slopeAB * slopeBC * (triangle.A.Y - triangle.C.Y) + slopeBC * (triangle.A.X + triangle.B.X) - slopeAB * (triangle.B.X + triangle.C.X)) / (2 * (slopeBC - slopeAB)); + float y = -(x - (triangle.A.X + triangle.B.X) / 2) / slopeAB + (triangle.A.Y + triangle.B.Y) / 2; + + result.Center = new Vector2(x, y); + result.Radius = Vector2.Distance(result.Center, triangle.A); + } + else + { + // If slopes are parallel, use the midpoints of the sides as the circumcenter + result.Center = (midAB + midBC) / 2; + result.Radius = Vector2.Distance(result.Center, triangle.A); + } + + return result; + } + + public IList VerticesOriginal { get; } = vertices; + public IReadOnlyList Vertices => _vertices; +} diff --git a/Game/Physics2D/Collider2DBehaviourBase.cs b/Game/Physics2D/Collider2DBehaviourBase.cs deleted file mode 100644 index 6706751..0000000 --- a/Game/Physics2D/Collider2DBehaviourBase.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Collections.Generic; - -using Microsoft.Xna.Framework; - -using Syntriax.Engine.Core; -using Syntriax.Engine.Core.Abstract; -using Syntriax.Engine.Physics2D.Abstract; - -namespace Syntriax.Engine.Physics2D; - -public abstract class Collider2DBehaviourBase : BehaviourOverride, ICollider2D -{ - public Action? OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; } - public Action? OnCollision { get; set; } = null; - - public Vector2 OffsetPosition { get; set; } = Vector2.Zero; - public Vector2 OffsetScale { get; set; } = Vector2.One; - public float OffsetRotation { get; set; } = 0f; - - ITransform IAssignableTransform.Transform => Transform; - - - public bool Assign(ITransform transform) => GameObject.Assign(transform); - - public void RecalculateVertices() - { - throw new NotImplementedException(); - } - - public bool CheckCollision(ICollider2D collider) - { - return false; - - OnCollision?.Invoke(this, collider); - return true; - } - - - public abstract IReadOnlyList Vertices { get; } -} diff --git a/Game/Physics2D/Collider2DBox.cs b/Game/Physics2D/Collider2DBox.cs deleted file mode 100644 index 930604b..0000000 --- a/Game/Physics2D/Collider2DBox.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -using Syntriax.Engine.Physics2D.Abstract; - -namespace Syntriax.Engine.Physics2D; - -// public class Collider2DBox : Collider2DBehaviourBase -// { -// protected override bool ProcessCollision(ICollider2D collider) -// { -// if (collider is not Collider2DBox) -// return false; - - -// } -// } diff --git a/Game/Physics2D/PhysicsEngine2D.cs b/Game/Physics2D/PhysicsEngine2D.cs index 1cd722f..35209da 100644 --- a/Game/Physics2D/PhysicsEngine2D.cs +++ b/Game/Physics2D/PhysicsEngine2D.cs @@ -46,15 +46,25 @@ public class PhysicsEngine2D : IPhysicsEngine2D Vector2 nextPosition = rigidBody.Transform.Position; nextPosition += rigidBody.Velocity * intervalDeltaTime; - rigidBody.Transform.Position += nextPosition; + rigidBody.Transform.Position = nextPosition; } + + foreach (var collider in colliders) + collider.RecalculateVertices(); + for (int colliderIX = 0; colliderIX < colliders.Count; colliderIX++) { ICollider2D colliderX = colliders[colliderIX]; for (int colliderIY = colliderIX + 1; colliderIY < colliders.Count; colliderIY++) - if (colliderX.CheckCollision(colliders[colliderIY])) + foreach (var vertex in colliderX.Vertices) + { + if (!colliders[colliderIY].CheckCollision(vertex, colliderX)) + continue; + Console.WriteLine($"Collision"); + break; + } } } }