Merge branch 'feat/physics2d' into development

This commit is contained in:
Syntriax 2024-01-30 11:58:00 +03:00
commit 4000e761a7
46 changed files with 1842 additions and 101 deletions

484
.gitignore vendored Normal file
View File

@ -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

View File

@ -16,4 +16,9 @@ public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignab
/// Call priority of the <see cref="IBehaviour"/>.
/// </summary>
int Priority { get; set; }
/// <summary>
/// If the <see cref="IBehaviour"/> is active.
/// </summary>
bool IsActive { get; }
}

View File

@ -1,12 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
public interface ICamera : IAssignableTransform
{
Action<ICamera>? OnZoomChanged { get; set; }
float Zoom { get; set; }
void Update();
}

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
namespace Syntriax.Engine.Core.Abstract;
public interface IGameManager : IEntity, IEnumerable<IGameObject>
{
Action<GameManager, IGameObject>? OnGameObjectRegistered { get; set; }
Action<GameManager, IGameObject>? OnGameObjectUnRegistered { get; set; }
IReadOnlyList<IGameObject> GameObjects { get; }
void RegisterGameObject(IGameObject gameObject);
T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject;
IGameObject RemoveGameObject(IGameObject gameObject);
void Update(EngineTime time);
void PreDraw();
}

View File

@ -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<IAssignable>? 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;

View File

@ -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<IBehaviourController>? OnPreUpdate { get; set; }
@ -55,17 +57,17 @@ public class BehaviourController : IBehaviourController
public IList<T> GetBehaviours<T>()
{
IList<T> behaviours = new List<T>();
List<T>? behaviours = null;
foreach (var behaviourItem in this.behaviours)
{
if (behaviourItem is not T behaviour)
continue;
behaviours ??= new List<T>();
behaviours ??= [];
behaviours.Add(behaviour);
}
return behaviours;
return behaviours ?? Enumerable.Empty<T>().ToList();
}
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -8,9 +8,9 @@ using Syntriax.Engine.Core.Factory;
namespace Syntriax.Engine.Core;
public class GameManager : IEntity, IEnumerable<IGameObject>
[System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")]
public class GameManager : IGameManager
{
public Action<GameManager>? OnCameraChanged { get; set; } = null;
public Action<GameManager, IGameObject>? OnGameObjectRegistered { get; set; } = null;
public Action<GameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } = null;
@ -20,12 +20,11 @@ public class GameManager : IEntity, IEnumerable<IGameObject>
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
private IList<IGameObject> _gameObjects = new List<IGameObject>(Constants.GAME_OBJECTS_SIZE_INITIAL);
private readonly List<IGameObject> _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<IGameObject>
}
public bool Initialized => _initialized;
public IList<IGameObject> GameObjects => _gameObjects;
public IReadOnlyList<IGameObject> GameObjects => _gameObjects;
public IStateEnable StateEnable
{
get
@ -54,19 +54,6 @@ public class GameManager : IEntity, IEnumerable<IGameObject>
}
}
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<IGameObject>
foreach (var gameObject in GameObjects)
gameObject.Initialize();
_initialized = true;
OnInitialized?.Invoke(this);
return true;
}
@ -114,6 +102,7 @@ public class GameManager : IEntity, IEnumerable<IGameObject>
GameObjects[i].Finalize();
OnFinalized?.Invoke(this);
_initialized = false;
return true;
}

View File

@ -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<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;

35
Engine.Core/Math.cs Normal file
View File

@ -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>(T x) where T : INumber<T> => 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<T>(this T x, T min, T max) where T : INumber<T> => (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>(T x, T y) where T : INumber<T> => (x > y) ? x : y;
public static float MaxMagnitude(float x, float y) => MathF.MaxMagnitude(x, y);
public static T Min<T>(T x, T y) where T : INumber<T> => (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>(T x) where T : INumber<T> => x * x;
public static float Sqrt(float x) => MathF.Sqrt(x);
public static float Truncate(float x) => MathF.Truncate(x);
}

View File

@ -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<ITransform>? OnPositionChanged { get; set; } = null;

View File

@ -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;
/// <summary>
/// Finds the Orientation of 3 <see cref="Vector2D"/>s
/// </summary>
/// <returns>0 -> Collinear, 1 -> Clockwise, 2 -> Counterclockwise</returns>
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);
}

View File

@ -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

View File

@ -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; }
}

View File

@ -0,0 +1,18 @@
using System;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract;
public interface ICollider2D : IBehaviour, IAssignableTransform
{
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; }
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; }
Action<ICollider2D, ICollider2D>? OnTriggered { get; set; }
IRigidBody2D? RigidBody2D { get; }
bool IsTrigger { get; set; }
void Recalculate();
}

View File

@ -0,0 +1,8 @@
using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D;
public interface ICollisionDetector2D
{
bool TryDetect<T1, T2>(T1 left, T2 right, out CollisionDetectionInformation collisionInformation) where T1 : ICollider2D where T2 : ICollider2D;
}

View File

@ -0,0 +1,6 @@
namespace Syntriax.Engine.Physics2D.Abstract;
public interface ICollisionResolver2D
{
void Resolve(CollisionDetectionInformation collisionInformation);
}

View File

@ -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);
}

View File

@ -0,0 +1,7 @@
namespace Syntriax.Engine.Physics2D.Abstract;
public interface IPhysicsMaterial2D
{
float Friction { get; }
float Restitution { get; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; } = null;
public Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; } = null;
public Action<ICollider2D, ICollider2D>? 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>? 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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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, T2>(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;
// }
}

View File

@ -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);
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Engine.Core\Engine.Core.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@ -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);
}

View File

@ -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<IRigidBody2D> rigidBodies = new(32);
private readonly List<ICollider2D> 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<ICollider2D>())
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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<Vector2D> 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<Vector2D> vectors) => AABB.FromVectors(vectors);
public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right);
}

View File

@ -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);
}

View File

@ -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<Vector2D> 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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<Vector2D> Vertices) : IEnumerable<Vector2D>
{
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<Vector2D> Vertices { get; init; } = Vertices;
public Vector2D this[System.Index index] => Vertices[index];
public static Shape CreateCopy(Shape shape) => new(new List<Vector2D>(shape.Vertices));
public static Shape CreateNgon(int vertexCount) => CreateNgon(vertexCount, Vector2D.Up);
public static Shape CreateNgon(int vertexCount, Vector2D positionToRotate)
{
if (vertexCount < 3)
throw new System.ArgumentException($"{nameof(vertexCount)} must have a value of more than 2.");
List<Vector2D> vertices = new(vertexCount);
float radiansPerVertex = 360f / vertexCount * Math.DegreeToRadian;
for (int i = 0; i < vertexCount; i++)
vertices.Add(positionToRotate.Rotate(i * radiansPerVertex));
return new(vertices);
}
public static Triangle GetSuperTriangle(Shape shape)
{
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<Line> 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<Line> GetLines(Shape shape)
{
List<Line> lines = new(shape.Vertices.Count - 1);
GetLines(shape, lines);
return lines;
}
public static void Project(Shape shape, Vector2D projectionVector, IList<float> list)
{
list.Clear();
int count = shape.Vertices.Count;
for (int i = 0; i < count; i++)
list.Add(projectionVector.Dot(shape[i]));
}
public static Projection Project(Shape shape, Vector2D projectionVector)
{
float min = float.MaxValue;
float max = float.MinValue;
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<Vector2D> 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<Vector2D> 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<Line> lines) => Shape.GetLines(shape, lines);
public static List<Line> ToLines(this Shape shape) => Shape.GetLines(shape);
public static void ToProjection(this Shape shape, Vector2D projectionVector, IList<float> list) => Shape.Project(shape, projectionVector, list);
public static Projection ToProjection(this Shape shape, Vector2D projectionVector) => Shape.Project(shape, projectionVector);
public static Shape TransformShape(this ITransform transform, Shape shape) => Shape.TransformShape(shape, transform);
public static void TransformShape(this ITransform transform, Shape from, ref Shape to) => Shape.TransformShape(from, transform, ref to);
public static bool ApproximatelyEquals(this Shape left, Shape right) => Shape.ApproximatelyEquals(left, right);
}

View File

@ -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);
}

View File

@ -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<IAssignableTransform>? 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);
}

View File

@ -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