diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..104b544
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,484 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from `dotnet new gitignore`
+
+# dotenv files
+.env
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# Tye
+.tye/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
+.idea
+
+##
+## Visual studio for Mac
+##
+
+
+# globs
+Makefile.in
+*.userprefs
+*.usertasks
+config.make
+config.status
+aclocal.m4
+install-sh
+autom4te.cache/
+*.tar.gz
+tarballs/
+test-results/
+
+# Mac bundle stuff
+*.dmg
+*.app
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
+# Windows thumbnail cache files
+Thumbs.db
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Vim temporary swap files
+*.swp
diff --git a/Engine.Core/Abstract/IBehaviour.cs b/Engine.Core/Abstract/IBehaviour.cs
index 47c30bc..95d12d8 100644
--- a/Engine.Core/Abstract/IBehaviour.cs
+++ b/Engine.Core/Abstract/IBehaviour.cs
@@ -16,4 +16,9 @@ public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignab
/// Call priority of the .
///
int Priority { get; set; }
+
+ ///
+ /// If the is active.
+ ///
+ bool IsActive { get; }
}
diff --git a/Engine.Core/Abstract/ICamera.cs b/Engine.Core/Abstract/ICamera.cs
deleted file mode 100644
index bcab06c..0000000
--- a/Engine.Core/Abstract/ICamera.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using System;
-
-namespace Syntriax.Engine.Core.Abstract;
-
-public interface ICamera : IAssignableTransform
-{
- Action? OnZoomChanged { get; set; }
-
- float Zoom { get; set; }
-
- void Update();
-}
diff --git a/Engine.Core/Abstract/IGameManager.cs b/Engine.Core/Abstract/IGameManager.cs
new file mode 100644
index 0000000..6909fb2
--- /dev/null
+++ b/Engine.Core/Abstract/IGameManager.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+
+namespace Syntriax.Engine.Core.Abstract;
+
+public interface IGameManager : IEntity, IEnumerable
+{
+ Action? OnGameObjectRegistered { get; set; }
+ Action? OnGameObjectUnRegistered { get; set; }
+
+
+ IReadOnlyList GameObjects { get; }
+
+
+ void RegisterGameObject(IGameObject gameObject);
+ T InstantiateGameObject(params object?[]? args) where T : class, IGameObject;
+ IGameObject RemoveGameObject(IGameObject gameObject);
+
+ void Update(EngineTime time);
+ void PreDraw();
+}
diff --git a/Engine.Core/Behaviour.cs b/Engine.Core/Behaviour.cs
index 943d366..4b9241b 100644
--- a/Engine.Core/Behaviour.cs
+++ b/Engine.Core/Behaviour.cs
@@ -5,6 +5,7 @@ using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
+[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class Behaviour : IBehaviour
{
public Action? OnUnassigned { get; set; } = null;
@@ -25,6 +26,8 @@ public abstract class Behaviour : IBehaviour
public IStateEnable StateEnable => _stateEnable;
public IBehaviourController BehaviourController => _behaviourController;
+ public bool IsActive => StateEnable.Enabled && BehaviourController.GameObject.StateEnable.Enabled;
+
public bool Initialized
{
get => _initialized;
diff --git a/Engine.Core/BehaviourController.cs b/Engine.Core/BehaviourController.cs
index 98d4585..0a96572 100644
--- a/Engine.Core/BehaviourController.cs
+++ b/Engine.Core/BehaviourController.cs
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
+[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
public class BehaviourController : IBehaviourController
{
public Action? OnPreUpdate { get; set; }
@@ -55,17 +57,17 @@ public class BehaviourController : IBehaviourController
public IList GetBehaviours()
{
- IList behaviours = new List();
+ List? behaviours = null;
foreach (var behaviourItem in this.behaviours)
{
if (behaviourItem is not T behaviour)
continue;
- behaviours ??= new List();
+ behaviours ??= [];
behaviours.Add(behaviour);
}
- return behaviours;
+ return behaviours ?? Enumerable.Empty().ToList();
}
public void RemoveBehaviour(bool removeAll = false) where T : class, IBehaviour
diff --git a/Engine.Core/BehaviourOverride.cs b/Engine.Core/BehaviourOverride.cs
index 81a8d4c..6e8dcc9 100644
--- a/Engine.Core/BehaviourOverride.cs
+++ b/Engine.Core/BehaviourOverride.cs
@@ -39,13 +39,13 @@ public abstract class BehaviourOverride : Behaviour
OnFinalize();
}
- protected virtual void OnPreUpdatePreEnabledCheck() { }
+ protected virtual void OnPreUpdatePreActiveCheck() { }
protected virtual void OnPreUpdate() { }
private void PreUpdate(IBehaviourController _)
{
- OnPreUpdatePreEnabledCheck();
+ OnPreUpdatePreActiveCheck();
- if (!StateEnable.Enabled)
+ if (!IsActive)
return;
if (isInitializedThisFrame)
@@ -61,23 +61,23 @@ public abstract class BehaviourOverride : Behaviour
isInitializedThisFrame = false;
}
- protected virtual void OnUpdatePreEnabledCheck() { }
+ protected virtual void OnUpdatePreActiveCheck() { }
protected virtual void OnUpdate() { }
private void Update(IBehaviourController _)
{
- OnUpdatePreEnabledCheck();
+ OnUpdatePreActiveCheck();
- if (!StateEnable.Enabled)
+ if (!IsActive)
return;
OnUpdate();
}
- protected virtual void OnPreDrawPreEnabledCheck() { }
+ protected virtual void OnPreDrawPreActiveCheck() { }
protected virtual void OnPreDraw() { }
private void PreDraw(IBehaviourController _)
{
- OnPreDrawPreEnabledCheck();
+ OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
diff --git a/Engine.Core/Engine.Core.sln b/Engine.Core/Engine.Core.sln
deleted file mode 100644
index 5133cda..0000000
--- a/Engine.Core/Engine.Core.sln
+++ /dev/null
@@ -1,25 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.5.002.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Engine.Core", "Engine.Core.csproj", "{6C1AF4B1-60B0-4225-9A96-F597BC04E9D0}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {6C1AF4B1-60B0-4225-9A96-F597BC04E9D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6C1AF4B1-60B0-4225-9A96-F597BC04E9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6C1AF4B1-60B0-4225-9A96-F597BC04E9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6C1AF4B1-60B0-4225-9A96-F597BC04E9D0}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {CC449885-B99C-4C71-842D-3C19A35E786F}
- EndGlobalSection
-EndGlobal
diff --git a/Engine.Core/EngineTime.cs b/Engine.Core/EngineTime.cs
index 107748c..c9d5f87 100644
--- a/Engine.Core/EngineTime.cs
+++ b/Engine.Core/EngineTime.cs
@@ -2,8 +2,8 @@ using System;
namespace Syntriax.Engine.Core;
-public record EngineTime
-(
- TimeSpan Total,
- TimeSpan Elapsed
-);
+public readonly struct EngineTime(TimeSpan Total, TimeSpan Elapsed)
+{
+ public readonly TimeSpan Total { get; init; } = Total;
+ public readonly TimeSpan Elapsed { get; init; } = Elapsed;
+}
diff --git a/Engine.Core/Extensions/Abstract/TransformExtensions.cs b/Engine.Core/Extensions/Abstract/TransformExtensions.cs
new file mode 100644
index 0000000..24244a3
--- /dev/null
+++ b/Engine.Core/Extensions/Abstract/TransformExtensions.cs
@@ -0,0 +1,9 @@
+namespace Syntriax.Engine.Core.Abstract;
+
+public static class TransformExtensions
+{
+ public static Vector2D TransformVector2D(this ITransform transform, Vector2D vector)
+ => vector.Scale(transform.Scale)
+ .Rotate(transform.Rotation * Math.DegreeToRadian)
+ .Add(transform.Position);
+}
diff --git a/Engine.Core/Extensions/FloatExtensions.cs b/Engine.Core/Extensions/FloatExtensions.cs
new file mode 100644
index 0000000..019bc39
--- /dev/null
+++ b/Engine.Core/Extensions/FloatExtensions.cs
@@ -0,0 +1,22 @@
+namespace Syntriax.Engine.Core;
+
+public static class FloatExtensions
+{
+ public static bool ApproximatelyEquals(this float a, float b)
+ => ApproximatelyEquals(a, b, float.Epsilon);
+ public static bool ApproximatelyEquals(this float a, float b, float epsilon)
+ {
+ if (a == b)
+ return true;
+
+ const float floatNormal = (1 << 23) * float.Epsilon;
+ float absA = Math.Abs(a);
+ float absB = Math.Abs(b);
+ float diff = Math.Abs(a - b);
+
+ if (a == 0.0f || b == 0.0f || diff < floatNormal)
+ return diff < (epsilon * floatNormal);
+
+ return diff / Math.Min(absA + absB, float.MaxValue) < epsilon;
+ }
+}
diff --git a/Engine.Core/Extensions/Vector2DExtensions.cs b/Engine.Core/Extensions/Vector2DExtensions.cs
index 6207043..29995da 100644
--- a/Engine.Core/Extensions/Vector2DExtensions.cs
+++ b/Engine.Core/Extensions/Vector2DExtensions.cs
@@ -4,13 +4,23 @@ public static class Vector2DExtensions
{
public static float Length(this Vector2D vector) => Vector2D.Length(vector);
public static float LengthSquared(this Vector2D vector) => Vector2D.LengthSquared(vector);
+
public static float Distance(this Vector2D from, Vector2D to) => Vector2D.Distance(from, to);
+ public static Vector2D Invert(this Vector2D vector) => Vector2D.Invert(vector);
+ public static Vector2D Add(this Vector2D vector, Vector2D vectorToAdd) => Vector2D.Add(vector, vectorToAdd);
+ public static Vector2D Subtract(this Vector2D vector, Vector2D vectorToSubtract) => Vector2D.Subtract(vector, vectorToSubtract);
+ public static Vector2D Multiply(this Vector2D vector, float value) => Vector2D.Multiply(vector, value);
+ public static Vector2D Subdivide(this Vector2D vector, float value) => Vector2D.Subdivide(vector, value);
+
+ public static Vector2D Abs(this Vector2D vector) => Vector2D.Abs(vector);
public static Vector2D Reflect(this Vector2D vector, Vector2D normal) => Vector2D.Reflect(vector, normal);
public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector);
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);
public static Vector2D Clamp(this Vector2D vector, Vector2D min, Vector2D max) => Vector2D.Clamp(vector, min, max);
@@ -19,4 +29,7 @@ public static class Vector2DExtensions
public static float Cross(this Vector2D left, Vector2D right) => Vector2D.Cross(left, right);
public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right);
public static float Dot(this Vector2D left, Vector2D right) => Vector2D.Dot(left, right);
+
+
+ public static bool ApproximatelyEquals(this Vector2D left, Vector2D right, float epsilon = float.Epsilon) => Vector2D.ApproximatelyEquals(left, right, epsilon);
}
diff --git a/Engine.Core/GameManager.cs b/Engine.Core/GameManager.cs
index 8410bce..b9dd0c4 100644
--- a/Engine.Core/GameManager.cs
+++ b/Engine.Core/GameManager.cs
@@ -8,9 +8,9 @@ using Syntriax.Engine.Core.Factory;
namespace Syntriax.Engine.Core;
-public class GameManager : IEntity, IEnumerable
+[System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")]
+public class GameManager : IGameManager
{
- public Action? OnCameraChanged { get; set; } = null;
public Action? OnGameObjectRegistered { get; set; } = null;
public Action? OnGameObjectUnRegistered { get; set; } = null;
@@ -20,12 +20,11 @@ public class GameManager : IEntity, IEnumerable
public Action? OnStateEnableAssigned { get; set; } = null;
- private IList _gameObjects = new List(Constants.GAME_OBJECTS_SIZE_INITIAL);
+ private readonly List _gameObjects = new(Constants.GAME_OBJECTS_SIZE_INITIAL);
private IStateEnable _stateEnable = null!;
private GameObjectFactory _gameObjectFactory = null!;
private bool _initialized = false;
- private ICamera _camera = null!;
private GameObjectFactory GameObjectFactory
{
@@ -38,7 +37,8 @@ public class GameManager : IEntity, IEnumerable
}
public bool Initialized => _initialized;
- public IList GameObjects => _gameObjects;
+ public IReadOnlyList GameObjects => _gameObjects;
+
public IStateEnable StateEnable
{
get
@@ -54,19 +54,6 @@ public class GameManager : IEntity, IEnumerable
}
}
- public ICamera Camera
- {
- get => _camera;
- set
- {
- if (_camera == value)
- return;
-
- _camera = value;
- OnCameraChanged?.Invoke(this);
- }
- }
-
public void RegisterGameObject(IGameObject gameObject)
{
if (_gameObjects.Contains(gameObject))
@@ -101,6 +88,7 @@ public class GameManager : IEntity, IEnumerable
foreach (var gameObject in GameObjects)
gameObject.Initialize();
+ _initialized = true;
OnInitialized?.Invoke(this);
return true;
}
@@ -114,6 +102,7 @@ public class GameManager : IEntity, IEnumerable
GameObjects[i].Finalize();
OnFinalized?.Invoke(this);
+ _initialized = false;
return true;
}
diff --git a/Engine.Core/GameObject.cs b/Engine.Core/GameObject.cs
index 7423977..0e50c25 100644
--- a/Engine.Core/GameObject.cs
+++ b/Engine.Core/GameObject.cs
@@ -5,6 +5,7 @@ using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
+[System.Diagnostics.DebuggerDisplay("Name: {Name}, Initialized: {Initialized}")]
public class GameObject : IGameObject
{
public Action? OnStateEnableAssigned { get; set; } = null;
diff --git a/Engine.Core/Math.cs b/Engine.Core/Math.cs
new file mode 100644
index 0000000..eda310b
--- /dev/null
+++ b/Engine.Core/Math.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Numerics;
+
+namespace Syntriax.Engine.Core;
+
+public static class Math
+{
+ public const float RadianToDegree = 180f / PI;
+ public const float DegreeToRadian = PI / 180f;
+
+ public const float E = 2.718281828459045f;
+ public const float PI = 3.1415926535897932f;
+ public const float Tau = 2f * PI;
+
+ public static T Abs(T x) where T : INumber => x > T.Zero ? x : -x;
+ public static float Acos(float x) => MathF.Acos(x);
+ public static float Asin(float x) => MathF.Asin(x);
+ public static float Atan2(float y, float x) => MathF.Atan2(y, x);
+ public static float Atanh(float x) => MathF.Atanh(x);
+ public static T Clamp(this T x, T min, T max) where T : INumber => (x < min) ? min : (x > max) ? max : x;
+ public static float Ceiling(float x) => MathF.Ceiling(x);
+ public static float CopySign(float x, float y) => MathF.CopySign(x, y);
+ public static float Floor(float x) => MathF.Floor(x);
+ public static float IEEERemainder(float x, float y) => MathF.IEEERemainder(x, y);
+ public static float Log(float x, float y) => MathF.Log(x, y);
+ public static T Max(T x, T y) where T : INumber => (x > y) ? x : y;
+ public static float MaxMagnitude(float x, float y) => MathF.MaxMagnitude(x, y);
+ public static T Min(T x, T y) where T : INumber => (x < y) ? x : y;
+ public static float MinMagnitude(float x, float y) => MathF.MinMagnitude(x, y);
+ public static float Pow(float x, float y) => MathF.Pow(x, y);
+ public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode);
+ public static T Sqr(T x) where T : INumber => x * x;
+ public static float Sqrt(float x) => MathF.Sqrt(x);
+ public static float Truncate(float x) => MathF.Truncate(x);
+}
diff --git a/Engine.Core/Transform.cs b/Engine.Core/Transform.cs
index f4b28dd..cc0c32e 100644
--- a/Engine.Core/Transform.cs
+++ b/Engine.Core/Transform.cs
@@ -4,6 +4,7 @@ using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
+[System.Diagnostics.DebuggerDisplay("Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")]
public class Transform : ITransform
{
public Action? OnPositionChanged { get; set; } = null;
diff --git a/Engine.Core/Vector2D.cs b/Engine.Core/Vector2D.cs
index bd15af2..bbf9399 100644
--- a/Engine.Core/Vector2D.cs
+++ b/Engine.Core/Vector2D.cs
@@ -2,12 +2,15 @@ using System;
namespace Syntriax.Engine.Core;
-[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized}")]
-public record Vector2D(float X, float Y)
+[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
+public readonly struct Vector2D(float X, float Y)
{
- public float Magnitude => Length(this);
- public float MagnitudeSquared => LengthSquared(this);
- public Vector2D Normalized => Normalize(this);
+ public readonly float X { get; init; } = X;
+ public readonly float Y { get; init; } = Y;
+
+ public readonly float Magnitude => Length(this);
+ public readonly float MagnitudeSquared => LengthSquared(this);
+ public readonly Vector2D Normalized => Normalize(this);
public readonly static Vector2D Up = new(0f, 1f);
public readonly static Vector2D Down = new(0f, -1f);
@@ -22,17 +25,28 @@ public record Vector2D(float X, float Y)
public static Vector2D operator *(Vector2D vector, float value) => new(vector.X * value, vector.Y * value);
public static Vector2D operator *(float value, Vector2D vector) => new(vector.X * value, vector.Y * value);
public static Vector2D operator /(Vector2D vector, float value) => new(vector.X / value, vector.Y / value);
+ public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y;
+ public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y;
public static float Length(Vector2D vector) => MathF.Sqrt(LengthSquared(vector));
public static float LengthSquared(Vector2D vector) => vector.X * vector.X + vector.Y * vector.Y;
public static float Distance(Vector2D from, Vector2D to) => Length(FromTo(from, to));
+ public static Vector2D Invert(Vector2D vector) => -vector;
+ public static Vector2D Add(Vector2D left, Vector2D right) => left + right;
+ public static Vector2D Subtract(Vector2D left, Vector2D right) => left - right;
+ public static Vector2D Multiply(Vector2D vector, float value) => vector * value;
+ public static Vector2D Subdivide(Vector2D vector, float value) => vector / value;
+
+ public static Vector2D Abs(Vector2D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y));
public static Vector2D Normalize(Vector2D vector) => vector / Length(vector);
public static Vector2D Reflect(Vector2D vector, Vector2D normal) => vector - 2f * Dot(vector, normal) * normal;
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);
public static Vector2D Clamp(Vector2D vector, Vector2D min, Vector2D max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y));
@@ -42,5 +56,28 @@ public record Vector2D(float X, float Y)
public static float Angle(Vector2D left, Vector2D right) => MathF.Acos(Dot(left, right) / (Length(left) * Length(right)));
public static float Dot(Vector2D left, Vector2D right) => left.X * right.X + left.Y * right.Y;
+ ///
+ /// Finds the Orientation of 3 s
+ ///
+ /// 0 -> Collinear, 1 -> Clockwise, 2 -> Counterclockwise
+ public static int Orientation(Vector2D left, Vector2D middle, Vector2D right)
+ {
+ Vector2D leftToMiddle = left.FromTo(middle);
+ Vector2D middleToRight = middle.FromTo(right);
+
+ float value = leftToMiddle.Y * middleToRight.X -
+ leftToMiddle.X * middleToRight.Y;
+
+ if (value > 0) return 1;
+ if (value < 0) return 2;
+ return 0;
+ }
+
+ public static bool ApproximatelyEquals(Vector2D left, Vector2D right, float epsilon = float.Epsilon)
+ => left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon);
+
public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})";
+
+ public override bool Equals(object? obj) => obj is Vector2D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y);
+ public override int GetHashCode() => HashCode.Combine(X, Y);
}
diff --git a/Engine.Input/Engine.Input.sln b/Engine.Input/Engine.Input.sln
deleted file mode 100644
index c53c614..0000000
--- a/Engine.Input/Engine.Input.sln
+++ /dev/null
@@ -1,25 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.5.002.0
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Engine.Input", "Engine.Input.csproj", "{637B86E7-3699-4248-9A9F-C5CB09779B53}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {637B86E7-3699-4248-9A9F-C5CB09779B53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {637B86E7-3699-4248-9A9F-C5CB09779B53}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {637B86E7-3699-4248-9A9F-C5CB09779B53}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {637B86E7-3699-4248-9A9F-C5CB09779B53}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {FB9A6AA6-FAEF-4DE6-BDE0-C9CC4F02B29A}
- EndGlobalSection
-EndGlobal
diff --git a/Engine.Physics2D/Abstract/ICircleCollider2D.cs b/Engine.Physics2D/Abstract/ICircleCollider2D.cs
new file mode 100644
index 0000000..2391e53
--- /dev/null
+++ b/Engine.Physics2D/Abstract/ICircleCollider2D.cs
@@ -0,0 +1,9 @@
+using Syntriax.Engine.Physics2D.Primitives;
+
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface ICircleCollider2D : ICollider2D
+{
+ Circle CircleLocal { get; set; }
+ Circle CircleWorld { get; }
+}
diff --git a/Engine.Physics2D/Abstract/ICollider2D.cs b/Engine.Physics2D/Abstract/ICollider2D.cs
new file mode 100644
index 0000000..5d2a5ff
--- /dev/null
+++ b/Engine.Physics2D/Abstract/ICollider2D.cs
@@ -0,0 +1,18 @@
+using System;
+
+using Syntriax.Engine.Core.Abstract;
+
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface ICollider2D : IBehaviour, IAssignableTransform
+{
+ Action? OnCollisionDetected { get; set; }
+ Action? OnCollisionResolved { get; set; }
+
+ Action? OnTriggered { get; set; }
+
+ IRigidBody2D? RigidBody2D { get; }
+ bool IsTrigger { get; set; }
+
+ void Recalculate();
+}
diff --git a/Engine.Physics2D/Abstract/ICollisionDetector2D.cs b/Engine.Physics2D/Abstract/ICollisionDetector2D.cs
new file mode 100644
index 0000000..d76da2a
--- /dev/null
+++ b/Engine.Physics2D/Abstract/ICollisionDetector2D.cs
@@ -0,0 +1,8 @@
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+public interface ICollisionDetector2D
+{
+ bool TryDetect(T1 left, T2 right, out CollisionDetectionInformation collisionInformation) where T1 : ICollider2D where T2 : ICollider2D;
+}
diff --git a/Engine.Physics2D/Abstract/ICollisionResolver2D.cs b/Engine.Physics2D/Abstract/ICollisionResolver2D.cs
new file mode 100644
index 0000000..05672a0
--- /dev/null
+++ b/Engine.Physics2D/Abstract/ICollisionResolver2D.cs
@@ -0,0 +1,6 @@
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface ICollisionResolver2D
+{
+ void Resolve(CollisionDetectionInformation collisionInformation);
+}
diff --git a/Engine.Physics2D/Abstract/IPhysicsEngine2D.cs b/Engine.Physics2D/Abstract/IPhysicsEngine2D.cs
new file mode 100644
index 0000000..aed1d10
--- /dev/null
+++ b/Engine.Physics2D/Abstract/IPhysicsEngine2D.cs
@@ -0,0 +1,11 @@
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface IPhysicsEngine2D
+{
+ int IterationCount { get; set; }
+
+ void AddRigidBody(IRigidBody2D rigidBody);
+ void RemoveRigidBody(IRigidBody2D rigidBody);
+
+ void Step(float deltaTime);
+}
diff --git a/Engine.Physics2D/Abstract/IPhysicsMaterial2D.cs b/Engine.Physics2D/Abstract/IPhysicsMaterial2D.cs
new file mode 100644
index 0000000..c533cda
--- /dev/null
+++ b/Engine.Physics2D/Abstract/IPhysicsMaterial2D.cs
@@ -0,0 +1,7 @@
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface IPhysicsMaterial2D
+{
+ float Friction { get; }
+ float Restitution { get; }
+}
diff --git a/Engine.Physics2D/Abstract/IRigidBody2D.cs b/Engine.Physics2D/Abstract/IRigidBody2D.cs
new file mode 100644
index 0000000..ef8a5c2
--- /dev/null
+++ b/Engine.Physics2D/Abstract/IRigidBody2D.cs
@@ -0,0 +1,15 @@
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Core.Abstract;
+
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface IRigidBody2D : IBehaviour, IAssignableTransform
+{
+ IPhysicsMaterial2D Material { get; set; }
+
+ Vector2D Velocity { get; set; }
+ float AngularVelocity { get; set; }
+
+ float Mass { get; set; }
+ bool IsStatic { get; set; }
+}
diff --git a/Engine.Physics2D/Abstract/IShapeCollider2D.cs b/Engine.Physics2D/Abstract/IShapeCollider2D.cs
new file mode 100644
index 0000000..2cc5218
--- /dev/null
+++ b/Engine.Physics2D/Abstract/IShapeCollider2D.cs
@@ -0,0 +1,9 @@
+using Syntriax.Engine.Physics2D.Primitives;
+
+namespace Syntriax.Engine.Physics2D.Abstract;
+
+public interface IShapeCollider2D : ICollider2D
+{
+ Shape ShapeLocal { get; set; }
+ Shape ShapeWorld { get; }
+}
diff --git a/Engine.Physics2D/Collider2DBehaviourBase.cs b/Engine.Physics2D/Collider2DBehaviourBase.cs
new file mode 100644
index 0000000..e69096f
--- /dev/null
+++ b/Engine.Physics2D/Collider2DBehaviourBase.cs
@@ -0,0 +1,73 @@
+using System;
+
+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? OnCollisionDetected { get; set; } = null;
+ public Action? OnCollisionResolved { get; set; } = null;
+ public Action? OnTriggered { get; set; } = null;
+
+
+ protected bool NeedsRecalculation { get; private set; } = true;
+ protected IRigidBody2D? _rigidBody2D = null;
+
+ public IRigidBody2D? RigidBody2D => _rigidBody2D;
+ public bool IsTrigger { get; set; } = false;
+
+ ITransform IAssignableTransform.Transform => Transform;
+ Action? IAssignableTransform.OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; }
+
+ bool IAssignableTransform.Assign(ITransform transform) => GameObject.Assign(transform);
+
+ public void Recalculate()
+ {
+ if (!NeedsRecalculation)
+ return;
+
+ CalculateCollider();
+ NeedsRecalculation = false;
+ }
+
+ public abstract void CalculateCollider();
+
+ protected override void OnInitialize()
+ {
+ BehaviourController.TryGetBehaviour(out _rigidBody2D);
+
+ BehaviourController.OnBehaviourAdded += OnBehaviourAddedToController;
+ BehaviourController.OnBehaviourRemoved += OnBehaviourRemovedFromController;
+
+ Transform.OnPositionChanged += SetNeedsRecalculation;
+ Transform.OnRotationChanged += SetNeedsRecalculation;
+ Transform.OnScaleChanged += SetNeedsRecalculation;
+ }
+
+ private void OnBehaviourAddedToController(IBehaviourController _, IBehaviour behaviour)
+ {
+ if (behaviour is IRigidBody2D rigidBody)
+ _rigidBody2D = rigidBody;
+ }
+
+ private void OnBehaviourRemovedFromController(IBehaviourController _, IBehaviour behaviour)
+ {
+ if (behaviour is IRigidBody2D _)
+ _rigidBody2D = null;
+ }
+
+ private void SetNeedsRecalculation(ITransform transform) => NeedsRecalculation = true;
+
+ protected override void OnFinalize()
+ {
+ BehaviourController.OnBehaviourAdded -= OnBehaviourAddedToController;
+ BehaviourController.OnBehaviourRemoved -= OnBehaviourRemovedFromController;
+ Transform.OnScaleChanged -= SetNeedsRecalculation;
+
+ Transform.OnPositionChanged -= SetNeedsRecalculation;
+ Transform.OnRotationChanged -= SetNeedsRecalculation;
+ }
+}
diff --git a/Engine.Physics2D/Collider2DCircleBehaviour.cs b/Engine.Physics2D/Collider2DCircleBehaviour.cs
new file mode 100644
index 0000000..7314c02
--- /dev/null
+++ b/Engine.Physics2D/Collider2DCircleBehaviour.cs
@@ -0,0 +1,17 @@
+using Syntriax.Engine.Physics2D.Abstract;
+using Syntriax.Engine.Physics2D.Primitives;
+
+namespace Syntriax.Engine.Physics2D;
+
+public class Collider2DCircleBehaviour : Collider2DBehaviourBase, ICircleCollider2D
+{
+ public Circle CircleWorld { get; protected set; } = Circle.UnitCircle;
+ public Circle CircleLocal { get; set; } = Circle.UnitCircle;
+
+
+ public override void CalculateCollider() => CircleWorld = Transform.TransformCircle(CircleLocal);
+
+
+ public Collider2DCircleBehaviour() { }
+ public Collider2DCircleBehaviour(Circle circle) => CircleLocal = circle;
+}
diff --git a/Engine.Physics2D/Collider2DShapeBehaviour.cs b/Engine.Physics2D/Collider2DShapeBehaviour.cs
new file mode 100644
index 0000000..a658b14
--- /dev/null
+++ b/Engine.Physics2D/Collider2DShapeBehaviour.cs
@@ -0,0 +1,18 @@
+using Syntriax.Engine.Physics2D.Abstract;
+using Syntriax.Engine.Physics2D.Primitives;
+
+namespace Syntriax.Engine.Physics2D;
+
+public class Collider2DShapeBehaviour : Collider2DBehaviourBase, IShapeCollider2D
+{
+ public Shape ShapeWorld { get => _shapeWorld; protected set => _shapeWorld = value; }
+ public Shape ShapeLocal { get; set; } = Shape.Box;
+
+ private Shape _shapeWorld = Shape.Box.CreateCopy();
+
+ public override void CalculateCollider() => Transform.TransformShape(ShapeLocal, ref _shapeWorld);
+
+
+ public Collider2DShapeBehaviour() { }
+ public Collider2DShapeBehaviour(Shape shape) => ShapeLocal = shape;
+}
diff --git a/Engine.Physics2D/CollisionDetectionInformation.cs b/Engine.Physics2D/CollisionDetectionInformation.cs
new file mode 100644
index 0000000..453932c
--- /dev/null
+++ b/Engine.Physics2D/CollisionDetectionInformation.cs
@@ -0,0 +1,19 @@
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+[System.Diagnostics.DebuggerDisplay("Normal: {Normal.ToString(), nq}, Penetration: {Penetration}")]
+public readonly struct CollisionDetectionInformation
+(
+ ICollider2D Left,
+ ICollider2D Right,
+ Vector2D Normal,
+ float Penetration
+)
+{
+ public ICollider2D Left { get; init; } = Left;
+ public ICollider2D Right { get; init; } = Right;
+ public Vector2D Normal { get; init; } = Normal;
+ public float Penetration { get; init; } = Penetration;
+}
diff --git a/Engine.Physics2D/CollisionDetector2D.cs b/Engine.Physics2D/CollisionDetector2D.cs
new file mode 100644
index 0000000..1f9c332
--- /dev/null
+++ b/Engine.Physics2D/CollisionDetector2D.cs
@@ -0,0 +1,136 @@
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Physics2D.Abstract;
+using Syntriax.Engine.Physics2D.Primitives;
+
+namespace Syntriax.Engine.Physics2D;
+
+public class CollisionDetector2D : ICollisionDetector2D
+{
+ public bool TryDetect(T1 left, T2 right, out CollisionDetectionInformation collisionInformation)
+ where T1 : ICollider2D
+ where T2 : ICollider2D
+ {
+ collisionInformation = default;
+ if (left is IShapeCollider2D shapeColliderLeft)
+ {
+ if (right is IShapeCollider2D shapeColliderRight)
+ return DetectShapeShape(shapeColliderLeft, shapeColliderRight, out collisionInformation);
+ else if (right is ICircleCollider2D circleColliderRight)
+ return DetectShapeCircle(shapeColliderLeft, circleColliderRight, out collisionInformation);
+ }
+ else if (left is ICircleCollider2D circleColliderLeft)
+ {
+ if (right is IShapeCollider2D shapeColliderRight)
+ return DetectCircleShape(circleColliderLeft, shapeColliderRight, out collisionInformation);
+ else if (right is ICircleCollider2D circleColliderRight)
+ return DetectCircleCircle(circleColliderLeft, circleColliderRight, out collisionInformation);
+ }
+
+ return false;
+ }
+
+ private static bool DetectCircleShape(ICircleCollider2D circleCollider, IShapeCollider2D shapeCollider, out CollisionDetectionInformation collisionInformation)
+ {
+ return DetectShapeCircle(shapeCollider, circleCollider, out collisionInformation);
+ }
+
+ private static bool DetectShapeShape(IShapeCollider2D left, IShapeCollider2D right, out CollisionDetectionInformation collisionInformation)
+ {
+ collisionInformation = default;
+ return DetectShapeShapeOneWay(left, right, ref collisionInformation) && DetectShapeShapeOneWay(right, left, ref collisionInformation);
+ }
+
+ private static bool DetectShapeShapeOneWay(IShapeCollider2D left, IShapeCollider2D right, ref CollisionDetectionInformation collisionInformation)
+ {
+ 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.Left is null || 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)
+ {
+ collisionInformation = default;
+
+ 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.Left is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
+ collisionInformation = new(shapeCollider, circleCollider, projectionVector, depth);
+ }
+
+ {
+ 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.Left is null || Math.Abs(collisionInformation.Penetration) > Math.Abs(depth))
+ collisionInformation = new(shapeCollider, circleCollider, shapeToCircleProjectionVector, depth);
+ }
+
+ return true;
+ }
+
+ private static bool DetectCircleCircle(ICircleCollider2D left, ICircleCollider2D right, out CollisionDetectionInformation collisionInformation)
+ {
+ collisionInformation = default;
+
+ Vector2D leftToRightCenterProjectionVector = left.CircleWorld.Center.FromTo(right.CircleWorld.Center).Normalized;
+
+ Projection leftProjection = left.CircleWorld.ToProjection(leftToRightCenterProjectionVector);
+ Projection rightProjection = right.CircleWorld.ToProjection(leftToRightCenterProjectionVector);
+
+ bool collision = leftProjection.Overlaps(rightProjection, out float depth);
+
+ if (collision)
+ 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;
+ // }
+}
diff --git a/Engine.Physics2D/CollisionResolver2D.cs b/Engine.Physics2D/CollisionResolver2D.cs
new file mode 100644
index 0000000..4cb6c04
--- /dev/null
+++ b/Engine.Physics2D/CollisionResolver2D.cs
@@ -0,0 +1,44 @@
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+public class CollisionResolver2D : ICollisionResolver2D
+{
+ public void Resolve(CollisionDetectionInformation collisionInformation)
+ {
+ Vector2D displacementVector = collisionInformation.Normal * collisionInformation.Penetration;
+
+ ICollider2D left = collisionInformation.Left;
+ ICollider2D right = collisionInformation.Right;
+
+ bool isLeftStatic = left.RigidBody2D?.IsStatic ?? true;
+ bool isRightStatic = right.RigidBody2D?.IsStatic ?? true;
+
+ if (isLeftStatic && isRightStatic)
+ return;
+
+ if (isLeftStatic)
+ right.Transform.Position += displacementVector;
+ else if (isRightStatic)
+ left.Transform.Position -= displacementVector;
+ else
+ {
+ float leftMass = left.RigidBody2D?.Mass ?? float.Epsilon;
+ float rightMass = right.RigidBody2D?.Mass ?? float.Epsilon;
+ float sumMass = leftMass + rightMass;
+
+ float leftMomentumPercentage = leftMass / sumMass;
+ float rightMomentumPercentage = rightMass / sumMass;
+
+ right.Transform.Position += leftMomentumPercentage * displacementVector;
+ left.Transform.Position -= rightMomentumPercentage * displacementVector;
+ }
+
+ left.Recalculate();
+ right.Recalculate();
+
+ left.OnCollisionResolved?.Invoke(collisionInformation.Left, collisionInformation);
+ right.OnCollisionResolved?.Invoke(right, collisionInformation);
+ }
+}
diff --git a/Engine.Physics2D/Engine.Physics2D.csproj b/Engine.Physics2D/Engine.Physics2D.csproj
new file mode 100644
index 0000000..adb4974
--- /dev/null
+++ b/Engine.Physics2D/Engine.Physics2D.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+ net8.0
+ disable
+ enable
+
+
+
diff --git a/Engine.Physics2D/Physics2D.cs b/Engine.Physics2D/Physics2D.cs
new file mode 100644
index 0000000..a9f722b
--- /dev/null
+++ b/Engine.Physics2D/Physics2D.cs
@@ -0,0 +1,74 @@
+using System;
+
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Physics2D;
+using Syntriax.Engine.Physics2D.Primitives;
+
+namespace Engine.Physics2D;
+
+public static partial class Physics2D
+{
+ public static bool Overlaps(this Circle left, Circle right)
+ {
+ float distanceSquared = left.Center.FromTo(right.Center).LengthSquared();
+ float radiusSumSquared = left.RadiusSquared + right.RadiusSquared;
+
+ return distanceSquared < radiusSumSquared;
+ }
+
+ public static bool Overlaps(this Circle left, Circle right, out Vector2D normal, out float depth)
+ {
+ Vector2D distanceVector = left.Center.FromTo(right.Center);
+ float distanceSquared = distanceVector.LengthSquared();
+ float radiusSumSquared = left.RadiusSquared + right.RadiusSquared;
+ bool isOverlapping = distanceSquared < radiusSumSquared;
+
+ depth = 0f;
+ normal = distanceVector.Normalized;
+
+ if (isOverlapping)
+ depth = MathF.Sqrt(radiusSumSquared - distanceSquared);
+
+ return isOverlapping;
+ }
+
+ public static bool Overlaps(this Circle circle, Vector2D point) => circle.Center.FromTo(point).LengthSquared() <= circle.RadiusSquared;
+ public static bool Overlaps(this Circle circle, Vector2D point, out Vector2D normal, out float depth)
+ {
+ Vector2D distanceVector = circle.Center.FromTo(point);
+ float distanceSquared = distanceVector.LengthSquared();
+ float radiusSquared = circle.RadiusSquared;
+ bool isOverlapping = distanceSquared < radiusSquared;
+
+ depth = 0f;
+ normal = distanceVector.Normalized;
+
+ if (isOverlapping)
+ depth = MathF.Sqrt(radiusSquared - distanceSquared);
+
+ return isOverlapping;
+ }
+
+ public static bool Overlaps(this AABB aabb, Vector2D point)
+ => point.X >= aabb.LowerBoundary.X && point.X <= aabb.UpperBoundary.X &&
+ point.Y >= aabb.LowerBoundary.Y && point.Y <= aabb.UpperBoundary.Y;
+
+ public static bool Overlaps(this AABB left, AABB right)
+ => left.LowerBoundary.X <= right.UpperBoundary.X && left.UpperBoundary.X >= right.LowerBoundary.X &&
+ left.LowerBoundary.Y <= right.UpperBoundary.Y && left.UpperBoundary.Y >= right.LowerBoundary.Y;
+
+ public static bool Overlaps(Triangle triangle, Vector2D point)
+ {
+ float originalTriangleArea = triangle.Area;
+
+ float pointTriangleArea1 = new Triangle(point, triangle.B, triangle.C).Area;
+ float pointTriangleArea2 = new Triangle(triangle.A, point, triangle.C).Area;
+ float pointTriangleArea3 = new Triangle(triangle.A, triangle.B, point).Area;
+
+ float pointTriangleAreasSum = pointTriangleArea1 + pointTriangleArea2 + pointTriangleArea3;
+
+ return originalTriangleArea.ApproximatelyEquals(pointTriangleAreasSum, float.Epsilon * 3f);
+ }
+
+ public static bool LaysOn(this Vector2D point, Line line) => Line.Intersects(line, point);
+}
diff --git a/Engine.Physics2D/PhysicsEngine2D.cs b/Engine.Physics2D/PhysicsEngine2D.cs
new file mode 100644
index 0000000..c61af05
--- /dev/null
+++ b/Engine.Physics2D/PhysicsEngine2D.cs
@@ -0,0 +1,125 @@
+using System.Collections.Generic;
+
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Core.Abstract;
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+public class PhysicsEngine2D : IPhysicsEngine2D
+{
+ private readonly List rigidBodies = new(32);
+ private readonly List colliders = new(64);
+
+ private int _iterationCount = 1;
+ private ICollisionDetector2D collisionDetector = new CollisionDetector2D();
+ private ICollisionResolver2D collisionResolver = new CollisionResolver2D();
+
+ public int IterationCount { get => _iterationCount; set => _iterationCount = value < 1 ? 1 : value; }
+
+ public void AddRigidBody(IRigidBody2D rigidBody)
+ {
+ if (rigidBodies.Contains(rigidBody))
+ return;
+
+ rigidBodies.Add(rigidBody);
+
+ foreach (var collider2D in rigidBody.BehaviourController.GetBehaviours())
+ colliders.Add(collider2D);
+
+ rigidBody.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
+ rigidBody.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
+ }
+
+ public void RemoveRigidBody(IRigidBody2D rigidBody)
+ {
+ rigidBodies.Remove(rigidBody);
+ }
+
+ public void Step(float deltaTime)
+ {
+ float intervalDeltaTime = deltaTime / IterationCount;
+
+ for (int iterationIndex = 0; iterationIndex < IterationCount; iterationIndex++)
+ {
+ // Can Parallel
+ for (int i = 0; i < rigidBodies.Count; i++)
+ StepRigidBody(rigidBodies[i], intervalDeltaTime);
+
+ // Can Parallel
+ foreach (var collider in colliders)
+ collider.Recalculate();
+
+ // Can Parallel
+ for (int x = 0; x < colliders.Count; x++)
+ {
+ ICollider2D? colliderX = colliders[x];
+ if (!colliderX.IsActive)
+ return;
+
+ for (int y = x + 1; y < colliders.Count; y++)
+ {
+ ICollider2D? colliderY = colliders[y];
+
+ if (!colliderY.IsActive)
+ return;
+
+ if (colliderX.RigidBody2D == colliderY.RigidBody2D)
+ continue;
+
+ bool bothCollidersAreTriggers = colliderX.IsTrigger && colliderX.IsTrigger == colliderY.IsTrigger;
+ if (bothCollidersAreTriggers)
+ continue;
+
+ bool bothCollidersAreStatic = colliderX.RigidBody2D?.IsStatic ?? true && colliderX.RigidBody2D?.IsStatic == colliderY.RigidBody2D?.IsStatic;
+ if (bothCollidersAreStatic)
+ continue;
+
+ if (collisionDetector.TryDetect(colliderX, colliderY, out CollisionDetectionInformation information))
+ {
+ if (colliderX.IsTrigger)
+ {
+ colliderX.OnTriggered?.Invoke(colliderX, colliderY);
+ continue;
+ }
+ else if (colliderY.IsTrigger)
+ {
+ colliderY.OnTriggered?.Invoke(colliderY, colliderY);
+ continue;
+ }
+
+ colliderX.OnCollisionDetected?.Invoke(colliderX, information);
+ colliderY.OnCollisionDetected?.Invoke(colliderY, information);
+
+ collisionResolver?.Resolve(information);
+ }
+ }
+ }
+ }
+ }
+
+ private static void StepRigidBody(IRigidBody2D rigidBody, float intervalDeltaTime)
+ {
+ if (rigidBody.IsStatic)
+ return;
+
+ rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
+ rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
+ }
+
+ private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
+ {
+ if (behaviour is not ICollider2D collider2D)
+ return;
+
+ colliders.Add(collider2D);
+ }
+
+ private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
+ {
+ if (behaviour is not ICollider2D collider2D)
+ return;
+
+ colliders.Remove(collider2D);
+ }
+}
diff --git a/Engine.Physics2D/PhysicsMaterial2D.cs b/Engine.Physics2D/PhysicsMaterial2D.cs
new file mode 100644
index 0000000..23e462e
--- /dev/null
+++ b/Engine.Physics2D/PhysicsMaterial2D.cs
@@ -0,0 +1,9 @@
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+public readonly struct PhysicsMaterial2D(float Friction, float Restitution) : IPhysicsMaterial2D
+{
+ public readonly float Friction { get; init; } = Friction;
+ public readonly float Restitution { get; init; } = Restitution;
+}
diff --git a/Engine.Physics2D/PhysicsMaterial2DDefault.cs b/Engine.Physics2D/PhysicsMaterial2DDefault.cs
new file mode 100644
index 0000000..41d1709
--- /dev/null
+++ b/Engine.Physics2D/PhysicsMaterial2DDefault.cs
@@ -0,0 +1,10 @@
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+public readonly struct PhysicsMaterial2DDefault : IPhysicsMaterial2D
+{
+ public readonly float Friction => .1f;
+
+ public readonly float Restitution => .1f;
+}
diff --git a/Engine.Physics2D/Primitives/AABB.cs b/Engine.Physics2D/Primitives/AABB.cs
new file mode 100644
index 0000000..e165fd8
--- /dev/null
+++ b/Engine.Physics2D/Primitives/AABB.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+
+using Syntriax.Engine.Core;
+
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
+public readonly struct AABB(Vector2D LowerBoundary, Vector2D UpperBoundary)
+{
+ public readonly Vector2D LowerBoundary { get; init; } = LowerBoundary;
+ public readonly Vector2D UpperBoundary { get; init; } = UpperBoundary;
+
+ public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
+ public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
+ public readonly Vector2D SizeHalf => Size * .5f;
+
+ public static AABB FromVectors(IEnumerable vectors)
+ {
+ int counter = 0;
+
+ Vector2D lowerBoundary = new(float.MaxValue, float.MaxValue);
+ Vector2D upperBoundary = new(float.MinValue, float.MinValue);
+
+ foreach (Vector2D vector in vectors)
+ {
+ lowerBoundary = Vector2D.Min(lowerBoundary, vector);
+ upperBoundary = Vector2D.Max(upperBoundary, vector);
+ counter++;
+ }
+
+ if (counter < 2)
+ throw new System.ArgumentException($"Parameter {nameof(vectors)} must have at least 2 items.");
+
+ return new(lowerBoundary, upperBoundary);
+ }
+
+ public static bool ApproximatelyEquals(AABB left, AABB right)
+ => left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary);
+}
+
+public static class AABBExtensions
+{
+ public static AABB ToAABB(this IEnumerable vectors) => AABB.FromVectors(vectors);
+
+ public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right);
+}
diff --git a/Engine.Physics2D/Primitives/Circle.cs b/Engine.Physics2D/Primitives/Circle.cs
new file mode 100644
index 0000000..2be2b56
--- /dev/null
+++ b/Engine.Physics2D/Primitives/Circle.cs
@@ -0,0 +1,47 @@
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Core.Abstract;
+
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("Center: {Center.ToString(), nq}, Radius: {Radius}")]
+public readonly struct Circle(Vector2D Center, float Radius)
+{
+ public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
+
+ public readonly Vector2D Center { get; init; } = Center;
+ public readonly float Radius { get; init; } = Radius;
+
+ public readonly float RadiusSquared => Radius * Radius;
+ public readonly float Diameter => 2f * Radius;
+
+ public static Circle SetCenter(Circle circle, Vector2D center) => new(center, circle.Radius);
+ public static Circle SetRadius(Circle circle, float radius) => new(circle.Center, 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);
+
+ public static bool ApproximatelyEquals(Circle left, Circle right)
+ => left.Center.ApproximatelyEquals(right.Center) && left.Radius.ApproximatelyEquals(right.Radius);
+}
+
+public static class CircleExtensions
+{
+ public static Circle SetCenter(this Circle circle, Vector2D center) => Circle.SetCenter(circle, center);
+ public static Circle SetRadius(this Circle circle, float radius) => Circle.SetRadius(circle, radius);
+
+ 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);
+}
diff --git a/Engine.Physics2D/Primitives/Line.cs b/Engine.Physics2D/Primitives/Line.cs
new file mode 100644
index 0000000..7d0f1ec
--- /dev/null
+++ b/Engine.Physics2D/Primitives/Line.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+using Syntriax.Engine.Core;
+
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("From: {From.ToString(), nq}, To: {To.ToString(), nq}, Direction: {Direction.ToString(), nq}, Length: {Length}")]
+public readonly struct Line(Vector2D From, Vector2D To)
+{
+ public readonly Vector2D From { get; init; } = From;
+ public readonly Vector2D To { get; init; } = To;
+
+ public readonly Line Reversed => new(To, From);
+ public readonly Vector2D Direction => From.FromTo(To).Normalize();
+ public readonly float Length => From.FromTo(To).Length();
+ public readonly float LengthSquared => From.FromTo(To).LengthSquared();
+
+ public static LineEquation GetLineEquation(Line line)
+ {
+ Vector2D slopeVector = line.From.FromTo(line.To);
+ float slope = slopeVector.Y / slopeVector.X;
+
+ float yOffset = line.From.Y - (slope * line.From.X);
+
+ return new LineEquation(slope, yOffset);
+ }
+
+ public static bool Intersects(Line line, Vector2D point)
+ => LineEquation.Resolve(GetLineEquation(line), point.X).ApproximatelyEquals(point.Y);
+
+ public static float GetT(Line line, Vector2D point)
+ {
+ float fromX = MathF.Abs(line.From.X);
+ float toX = MathF.Abs(line.To.X);
+ float pointX = MathF.Abs(point.X);
+
+ float min = MathF.Min(fromX, toX);
+ float max = MathF.Max(fromX, toX) - min;
+
+ pointX -= min;
+
+ float t = pointX / max;
+
+ // FIXME
+ // I don't even know, apparently whatever I wrote up there doesn't take into account of the direction of the line
+ // Which... I can see how, but I am also not sure how I can make it take into account. Or actually I'm for some reason
+ // too unmotivated to find a solution. Future me, find a better way if possible, please.
+ if (!Lerp(line, t).ApproximatelyEquals(point))
+ return 1f - t;
+ return t;
+ }
+
+ public static bool Exist(Line line, List vertices)
+ {
+ for (int i = 0; i < vertices.Count - 1; i++)
+ {
+ Vector2D vertexCurrent = vertices[i];
+ Vector2D vertexNext = vertices[i];
+ if (line.From == vertexCurrent && line.To == vertexNext) return true;
+ if (line.From == vertexNext && line.To == vertexCurrent) return true;
+ }
+
+ Vector2D vertexFirst = vertices[0];
+ Vector2D vertexLast = vertices[^1];
+ if (line.From == vertexFirst && line.To == vertexLast) return true;
+ if (line.From == vertexLast && line.To == vertexFirst) return true;
+ return false;
+ }
+
+ public static float IntersectionParameterT(Line left, Line right)
+ {
+ float numerator = (left.From.X - right.From.X) * (right.From.Y - right.To.Y) - (left.From.Y - right.From.Y) * (right.From.X - right.To.X);
+ float denominator = (left.From.X - left.To.X) * (right.From.Y - right.To.Y) - (left.From.Y - left.To.Y) * (right.From.X - right.To.X);
+
+ // Lines are parallel
+ if (denominator == 0)
+ return float.NaN;
+
+ return numerator / denominator;
+ }
+
+ public static Vector2D Lerp(Line line, float t)
+ => new Vector2D(
+ line.From.X + (line.To.X - line.From.X) * t,
+ line.From.Y + (line.To.Y - line.From.Y) * t
+ );
+
+ public static Vector2D ClosestPointTo(Line line, Vector2D point)
+ {
+ // Convert edge points to vectors
+ var edgeVector = new Vector2D(line.To.X - line.From.X, line.To.Y - line.From.Y);
+ var pointVector = new Vector2D(point.X - line.From.X, point.Y - line.From.Y);
+
+ // Calculate the projection of pointVector onto edgeVector
+ float t = (pointVector.X * edgeVector.X + pointVector.Y * edgeVector.Y) / (edgeVector.X * edgeVector.X + edgeVector.Y * edgeVector.Y);
+
+ // Clamp t to the range [0, 1] to ensure the closest point is on the edge
+ t = MathF.Max(0, MathF.Min(1, t));
+
+ // Calculate the closest point on the edge
+ float closestX = line.From.X + t * edgeVector.X;
+ float closestY = line.From.Y + t * edgeVector.Y;
+
+ return new Vector2D((float)closestX, (float)closestY);
+ }
+
+ public static Vector2D IntersectionPoint(Line left, Line right)
+ => Vector2D.Lerp(left.From, left.To, IntersectionParameterT(left, right));
+
+ public static bool Intersects(Line left, Line right)
+ {
+ int o1 = Vector2D.Orientation(left.From, left.To, right.From);
+ int o2 = Vector2D.Orientation(left.From, left.To, right.To);
+ int o3 = Vector2D.Orientation(right.From, right.To, left.From);
+ int o4 = Vector2D.Orientation(right.From, right.To, left.To);
+
+ if (o1 != o2 && o3 != o4)
+ return true;
+
+ if (o1 == 0 && OnSegment(left, right.From)) return true;
+ if (o2 == 0 && OnSegment(left, right.To)) return true;
+ if (o3 == 0 && OnSegment(right, left.From)) return true;
+ if (o4 == 0 && OnSegment(right, left.To)) return true;
+
+ return false;
+ }
+
+ public static bool OnSegment(Line line, Vector2D point)
+ {
+ if (point.X <= MathF.Max(line.From.X, line.To.X) && point.X >= MathF.Min(line.From.X, line.To.X) &&
+ point.Y <= MathF.Max(line.From.Y, line.To.Y) && point.Y >= MathF.Min(line.From.Y, line.To.Y))
+ return true;
+
+ return false;
+ }
+
+ public static bool Intersects(Line left, Line right, [NotNullWhen(returnValue: true)] out Vector2D? point)
+ {
+ point = null;
+
+ bool result = Intersects(left, right);
+
+ if (result)
+ point = IntersectionPoint(left, right);
+
+ return result;
+ }
+
+ public static bool ApproximatelyEquals(Line left, Line right)
+ => left.From.ApproximatelyEquals(right.From) && left.To.ApproximatelyEquals(right.To);
+}
+
+public static class LineExtensions
+{
+ public static bool ApproximatelyEquals(this Line left, Line right) => Line.ApproximatelyEquals(left, right);
+}
diff --git a/Engine.Physics2D/Primitives/LineEquation.cs b/Engine.Physics2D/Primitives/LineEquation.cs
new file mode 100644
index 0000000..3a3e417
--- /dev/null
+++ b/Engine.Physics2D/Primitives/LineEquation.cs
@@ -0,0 +1,21 @@
+using Syntriax.Engine.Core;
+
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
+public readonly struct LineEquation(float Slope, float OffsetY)
+{
+ public readonly float Slope { get; init; } = Slope;
+ public readonly float OffsetY { get; init; } = OffsetY;
+
+ public static float Resolve(LineEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
+
+ public static bool ApproximatelyEquals(LineEquation left, LineEquation right)
+ => left.Slope.ApproximatelyEquals(right.Slope) && left.OffsetY.ApproximatelyEquals(right.OffsetY);
+}
+
+public static class LineEquationExtensions
+{
+ public static float Resolve(this LineEquation lineEquation, float x) => LineEquation.Resolve(lineEquation, x);
+ public static bool ApproximatelyEquals(this LineEquation left, LineEquation right) => LineEquation.ApproximatelyEquals(left, right);
+}
diff --git a/Engine.Physics2D/Primitives/Projection.cs b/Engine.Physics2D/Primitives/Projection.cs
new file mode 100644
index 0000000..2f0af58
--- /dev/null
+++ b/Engine.Physics2D/Primitives/Projection.cs
@@ -0,0 +1,49 @@
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
+public readonly struct Projection(float Min, float Max)
+{
+ public readonly float Min { get; init; } = Min;
+ public readonly float Max { get; init; } = 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);
+}
diff --git a/Engine.Physics2D/Primitives/Shape.cs b/Engine.Physics2D/Primitives/Shape.cs
new file mode 100644
index 0000000..d332ab0
--- /dev/null
+++ b/Engine.Physics2D/Primitives/Shape.cs
@@ -0,0 +1,155 @@
+using System.Collections;
+using System.Collections.Generic;
+
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Core.Abstract;
+
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")]
+public readonly struct Shape(IList Vertices) : IEnumerable
+{
+ 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 readonly IList Vertices { get; init; } = Vertices;
+
+
+ public Vector2D this[System.Index index] => Vertices[index];
+
+ public static Shape CreateCopy(Shape shape) => new(new List(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 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)
+ {
+ float minX = float.MaxValue, minY = float.MaxValue;
+ float maxX = float.MinValue, maxY = float.MinValue;
+
+ foreach (Vector2D point in shape.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;
+
+ Vector2D p1 = new((float)midX - 20f * (float)deltaMax, (float)midY - (float)deltaMax);
+ Vector2D p2 = new((float)midX, (float)midY + 20 * (float)deltaMax);
+ Vector2D p3 = new((float)midX + 20 * (float)deltaMax, (float)midY - (float)deltaMax);
+
+ return new Triangle(p1, p2, p3);
+ }
+
+ public static void GetLines(Shape shape, IList lines)
+ {
+ lines.Clear();
+ for (int i = 0; i < shape.Vertices.Count - 1; i++)
+ lines.Add(new(shape.Vertices[i], shape.Vertices[i + 1]));
+ lines.Add(new(shape.Vertices[^1], shape.Vertices[0]));
+ }
+
+ public static List GetLines(Shape shape)
+ {
+ List lines = new(shape.Vertices.Count - 1);
+ GetLines(shape, lines);
+ return lines;
+ }
+
+ public static void Project(Shape shape, Vector2D projectionVector, IList 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;
+
+ for (int i = 0; i < shape.Vertices.Count; i++)
+ {
+ float projectedLength = projectionVector.Dot(shape.Vertices[i]);
+ min = Math.Min(projectedLength, min);
+ max = Math.Max(projectedLength, max);
+ }
+
+ return new(min, max);
+ }
+
+ public static Shape TransformShape(Shape shape, ITransform transform)
+ {
+ List vertices = new(shape.Vertices.Count);
+
+ int count = shape.Vertices.Count;
+ for (int i = 0; i < count; i++)
+ vertices.Add(transform.TransformVector2D(shape[i]));
+
+ return new Shape(vertices);
+ }
+
+ public static void TransformShape(Shape from, ITransform transform, ref Shape to)
+ {
+ to.Vertices.Clear();
+
+ int count = from.Vertices.Count;
+ for (int i = 0; i < count; i++)
+ to.Vertices.Add(transform.TransformVector2D(from[i]));
+ }
+
+ public static bool ApproximatelyEquals(Shape left, Shape right)
+ {
+ if (left.Vertices.Count != right.Vertices.Count)
+ return false;
+
+ for (int i = 0; i < left.Vertices.Count; i++)
+ if (!left.Vertices[i].ApproximatelyEquals(right.Vertices[i]))
+ return false;
+
+ return true;
+ }
+
+ public IEnumerator GetEnumerator() => Vertices.GetEnumerator();
+ IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
+}
+
+public static class ShapeExtensions
+{
+ public static Shape CreateCopy(this Shape shape) => Shape.CreateCopy(shape);
+ public static Triangle ToSuperTriangle(this Shape shape) => Shape.GetSuperTriangle(shape);
+ public static void ToLines(this Shape shape, IList lines) => Shape.GetLines(shape, lines);
+ public static List ToLines(this Shape shape) => Shape.GetLines(shape);
+
+ public static void ToProjection(this Shape shape, Vector2D projectionVector, IList 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);
+
+ public static bool ApproximatelyEquals(this Shape left, Shape right) => Shape.ApproximatelyEquals(left, right);
+}
diff --git a/Engine.Physics2D/Primitives/Triangle.cs b/Engine.Physics2D/Primitives/Triangle.cs
new file mode 100644
index 0000000..9695460
--- /dev/null
+++ b/Engine.Physics2D/Primitives/Triangle.cs
@@ -0,0 +1,49 @@
+using System;
+
+using Syntriax.Engine.Core;
+
+namespace Syntriax.Engine.Physics2D.Primitives;
+
+[System.Diagnostics.DebuggerDisplay("A: {A.ToString(), nq}, B: {B.ToString(), nq}, B: {C.ToString(), nq}")]
+public readonly struct Triangle(Vector2D A, Vector2D B, Vector2D C)
+{
+ public readonly Vector2D A { get; init; } = A;
+ public readonly Vector2D B { get; init; } = B;
+ public readonly Vector2D C { get; init; } = C;
+
+ public readonly float Area
+ => .5f * MathF.Abs(
+ A.X * (B.Y - C.Y) +
+ B.X * (C.Y - A.Y) +
+ C.X * (A.Y - B.Y)
+ );
+
+ public static Circle GetCircumCircle(Triangle triangle)
+ {
+ Vector2D midAB = (triangle.A + triangle.B) / 2f;
+ Vector2D midBC = (triangle.B + triangle.C) / 2f;
+
+ 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);
+
+ Vector2D center;
+ if (MathF.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)) / (2f * (slopeBC - slopeAB));
+ float y = -(x - (triangle.A.X + triangle.B.X) / 2f) / slopeAB + (triangle.A.Y + triangle.B.Y) / 2f;
+ center = new Vector2D(x, y);
+ }
+ else
+ center = (midAB + midBC) * .5f;
+
+ return new(center, Vector2D.Distance(center, triangle.A));
+ }
+
+ public static bool ApproximatelyEquals(Triangle left, Triangle right)
+ => left.A.ApproximatelyEquals(right.A) && left.B.ApproximatelyEquals(right.B) && left.C.ApproximatelyEquals(right.C);
+}
+
+public static class TriangleExtensions
+{
+ public static bool ApproximatelyEquals(this Triangle left, Triangle right) => Triangle.ApproximatelyEquals(left, right);
+}
diff --git a/Engine.Physics2D/RigidBody2D.cs b/Engine.Physics2D/RigidBody2D.cs
new file mode 100644
index 0000000..3820170
--- /dev/null
+++ b/Engine.Physics2D/RigidBody2D.cs
@@ -0,0 +1,29 @@
+using System;
+
+using Syntriax.Engine.Core;
+using Syntriax.Engine.Core.Abstract;
+using Syntriax.Engine.Physics2D.Abstract;
+
+namespace Syntriax.Engine.Physics2D;
+
+public class RigidBody2D : BehaviourOverride, IRigidBody2D
+{
+ public Action? OnTransformAssigned { get => GameObject.OnTransformAssigned; set => GameObject.OnTransformAssigned = value; }
+
+ private const float LOWEST_ALLOWED_MASS = 0.00001f;
+ private float _mass = 1f;
+
+
+ public IPhysicsMaterial2D Material { get; set; } = new PhysicsMaterial2DDefault();
+
+ public Vector2D Velocity { get; set; } = Vector2D.Zero;
+ public float AngularVelocity { get; set; } = 0f;
+ public bool IsStatic { get; set; } = false;
+
+ public float Mass { get => _mass; set => _mass = Core.Math.Max(value, LOWEST_ALLOWED_MASS); }
+
+ ITransform IAssignableTransform.Transform => Transform;
+
+
+ public bool Assign(ITransform transform) => GameObject.Assign(transform);
+}
diff --git a/Engine.sln b/Engine.sln
index 35f3e6c..e76e3fd 100644
--- a/Engine.sln
+++ b/Engine.sln
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Core", "Engine.Core\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Input", "Engine.Input\Engine.Input.csproj", "{12149E55-1EE8-45B4-A82E-15BA981B0C6A}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Physics2D", "Engine.Physics2D\Engine.Physics2D.csproj", "{3B3C3332-07E3-4A00-9898-0A5410BCB08C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,5 +26,9 @@ Global
{12149E55-1EE8-45B4-A82E-15BA981B0C6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12149E55-1EE8-45B4-A82E-15BA981B0C6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12149E55-1EE8-45B4-A82E-15BA981B0C6A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B3C3332-07E3-4A00-9898-0A5410BCB08C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B3C3332-07E3-4A00-9898-0A5410BCB08C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B3C3332-07E3-4A00-9898-0A5410BCB08C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B3C3332-07E3-4A00-9898-0A5410BCB08C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal