Compare commits

...

125 Commits

Author SHA1 Message Date
Syntriax 72492a9f5a docs: Basic README.md 2024-02-05 12:20:12 +03:00
Syntriax 2f043c19a6 feat: GameManagerExtensions.InstantiateGameObject 2024-02-05 11:20:15 +03:00
Syntriax dbb263ebed feat: IEntity.Id & BaseEntity 2024-02-02 17:40:46 +03:00
Syntriax 5d897f2f56 feat: EngineTime.DeltaTimeFrame 2024-02-01 18:43:33 +03:00
Syntriax 1dc8f3d272 refactor: Math.AbsMax & AbsMin to Single Liners 2024-02-01 15:36:52 +03:00
Syntriax 0725468f2c feat: Math.AbsMax & AbsMin 2024-02-01 15:35:16 +03:00
Syntriax 5826230e7a docs(core): Math 2024-02-01 15:31:47 +03:00
Syntriax 636331e18f style: Forgotten Line Between Namespace and Class Declaration 2024-02-01 14:51:49 +03:00
Syntriax 09a8e71fe3 fix: Renamed Vector2D.Subdivide to Divide 2024-02-01 14:42:45 +03:00
Syntriax 81a0cf645a docs(core): Vector2D 2024-02-01 14:41:56 +03:00
Syntriax ab0e868d52 style: IterationCount to IterationPerStep 2024-02-01 11:33:23 +03:00
Syntriax 0257911018 docs(physics2d): Primitives 2024-02-01 11:25:30 +03:00
Syntriax 2b19b24a26 docs(physics2d): Abstract 2024-02-01 11:20:40 +03:00
Syntriax 2f4137dae2 docs(core): Abstract 2024-02-01 11:15:49 +03:00
Syntriax 4ce9c8e0d9 docs: Core IAssignable 2024-02-01 11:02:54 +03:00
Syntriax 3e9c393817 style: ICamera2D.WorldToScreenPosition Parameter Name Fixed 2024-02-01 09:06:02 +03:00
Syntriax a1f63d2728 feat: ICamera2D Interface 2024-01-31 17:30:20 +03:00
Syntriax 6c36d4d21d feat: IBehaviourController.GetBehaviour 2024-01-31 17:03:40 +03:00
Syntriax de336d0ee5 refactor: Dependency Injection to PhysicsEngine2Ds 2024-01-31 10:08:13 +03:00
Syntriax 8619778d52 feat: PhysicsEngine2DCacher
This class uses BehaviourCacher to track IGameManager's Rigidbody2D & Collider2Ds
2024-01-31 10:01:50 +03:00
Syntriax 4facfdb6cf fix: Physics Engine Stepping Inactive Rigid Bodies Fixed 2024-01-31 09:59:42 +03:00
Syntriax f61f71dfe0 BREAKING CHANGE: Removed Add & Remove Rigidbody Methods from IPhysicsEngine2D 2024-01-31 09:59:11 +03:00
Syntriax 005c78a26e feat: BehaviourCacher<T> 2024-01-31 09:57:54 +03:00
Syntriax 01a99daf8a feat: IBehaviourController Enumarable<IGameObject> 2024-01-31 09:29:39 +03:00
Syntriax 8269c789a6 fix: IGameManager Action Types 2024-01-31 09:27:24 +03:00
Syntriax 3817ebebfe feat: Transform Extensions 2024-01-30 16:59:43 +03:00
Syntriax fa7eeed267 feat: GameObject Extensions 2024-01-30 16:59:03 +03:00
Syntriax 07666359f2 feat: FindBehaviour/s 2024-01-30 14:11:20 +03:00
Syntriax 514e5b5762 feat: IBehaviourController.GetBehaviours(List) 2024-01-30 13:49:54 +03:00
Syntriax 1438b19e35 feat: GameObjects are now Connected to a Single IGameManager 2024-01-30 13:17:02 +03:00
Syntriax 4000e761a7 Merge branch 'feat/physics2d' into development 2024-01-30 11:58:00 +03:00
Syntriax 9768dbdded refactor(core): Removed ICamera 2024-01-30 11:57:43 +03:00
Syntriax 9853e0af36 feat: IGameManager 2024-01-30 11:51:43 +03:00
Syntriax 0461454793 feat: Math's Abs, Clamp, Max, Min & Sqr Methods Converted to Generics 2024-01-30 10:38:36 +03:00
Syntriax d7d53e467a fix: Vector2D Normal Debugger Display 2024-01-29 17:41:53 +03:00
Syntriax 1c7d941bc1 feat(core): IBehavior.IsActive 2024-01-28 14:56:50 +03:00
Syntriax dc96b93024 feat(physics): Engine Rigidbody2D Static Check 2024-01-28 14:56:13 +03:00
Syntriax 1ffddab2c1 fix: Collider2DShapeBehaviour.ShapeWorld Create Copy of Shape.Box 2024-01-27 21:50:55 +03:00
Syntriax c1c1676b9a feat: Behaviour DebuggerDisplay Displays Behaviour Type Name Now 2024-01-27 21:21:58 +03:00
Syntriax 11483231a5 fix: ShapeExtensions.CreateCopy not having this Keyword 2024-01-27 21:21:35 +03:00
Syntriax 2ca243d79c fix: Shape & Circle Colliders Parametered Constructs Null Transform Error 2024-01-27 21:15:17 +03:00
Syntriax 69eca44dd8 fix: DebuggerDisplay Wrongly Typed Parameters 2024-01-27 21:05:56 +03:00
Syntriax 6a104d8abd fix: Collider2DShapeBehaviour TransformShape ref Requirement 2024-01-27 20:34:58 +03:00
Syntriax affd2bb8c4 feat: ICollider.OnTriggered 2024-01-27 20:31:51 +03:00
Syntriax 15788f2eca refactor: Improved Shape & Circle Code Readability 2024-01-27 20:20:28 +03:00
Syntriax ab2489f6cf feat: Shape & Circle Collider Constructors 2024-01-27 20:19:55 +03:00
Syntriax 574104c224 feat: ICollider IsTrigger 2024-01-27 20:19:00 +03:00
Syntriax 0d4c96a2fc fix: Forgotten Build Errors on Collider2DBehaviourBase Actions 2024-01-27 20:17:46 +03:00
Syntriax 5620f2f1eb feat: Circle.UnitCircle 2024-01-27 20:14:55 +03:00
Syntriax a3c4afb223 refactor: Basic Collision Resolver 2024-01-27 20:08:16 +03:00
Syntriax 4d9121118d refactor: Renamed OnCollisionPreResolve to OnCollisionDetected 2024-01-27 19:59:27 +03:00
Syntriax 05d88f7ca2 refactor: Renamed ICollisionDetector to ICollisionDetector2D 2024-01-27 19:58:55 +03:00
Syntriax 309c8db6e1 feat: ICollider2D Action Calls 2024-01-27 19:22:59 +03:00
Syntriax 0ba6913a61 fix: Static Rigidbodies Moving When Velocity or AngularVelocity Assigned 2024-01-27 19:21:18 +03:00
Syntriax 9556be6f17 feat: Static & Mass Consideration For Rigidbodies 2024-01-27 15:07:40 +03:00
Syntriax bd43d39367 fix: Circles Not Colliding Accurately Fixed 2024-01-27 15:07:07 +03:00
Syntriax 32e2a6e7d3 feat: Rigidbody Mass Restriction 2024-01-27 14:58:50 +03:00
Syntriax 7b47703ba0 refactor: DebuggerDisplays For Basic Types 2024-01-27 00:51:34 +03:00
Syntriax b14d10db0c perf: Drastically Improved Memory Usage
TIL, records are not value types and are actually just reference types. So I was pretty much allocating from heap every time I used any of my data types (Like Vector2D). Needless to say, they are all now readonly structs as I originally intended them to be.
2024-01-27 00:35:12 +03:00
Syntriax c32add40ff fix: Shape to Shape Detection 2024-01-26 20:35:05 +03:00
Syntriax 058c6dafe3 refactor: Removed Unused Methods 2024-01-26 17:09:21 +03:00
Syntriax 85bad951ff fix: Shape Collision on Larger Shapes 2024-01-26 17:06:42 +03:00
Syntriax 6a84c3ec1a feat: Useful Readonly Shapes 2024-01-26 16:38:38 +03:00
Syntriax ceb29cc42f feat: Shape.CreateNgon 2024-01-26 16:38:08 +03:00
Syntriax c6d2bad23e feat: Basic Shape to Shape Collision Detection 2024-01-26 16:16:27 +03:00
Syntriax 2bfd391286 feat: Basic Shape to Circle Collision Detection 2024-01-26 16:05:11 +03:00
Syntriax ac09b78edd feat: Transform Recalculation Conditions Updated 2024-01-26 15:52:59 +03:00
Syntriax 4607955d55 feat: Vector2D.Perpendicular 2024-01-26 14:37:48 +03:00
Syntriax 271a9a244b feat: CollisionDetector CircleCircle Projection 2024-01-26 13:34:43 +03:00
Syntriax f980546d37 feat: Circle & Shape Projections 2024-01-26 13:33:01 +03:00
Syntriax dfcc877e58 chore: Removed System Using 2024-01-26 13:32:33 +03:00
Syntriax 8ebde9dedf feat: Projection Data Record 2024-01-26 13:30:26 +03:00
Syntriax 238bf2d574 feat: Shape.CreateCopy 2024-01-26 10:06:22 +03:00
Syntriax ceebe21041 chore: Removed Unnecessary .sln Files 2024-01-26 09:58:25 +03:00
Syntriax 0ba8927858 perf: Collider2DBase NeedsRecalculation Field 2024-01-25 22:00:49 +03:00
Syntriax ab9181fe3f refactor: Removed Unused Using 2024-01-25 21:54:39 +03:00
Syntriax 266443504f feat: Improved Test Collision Resolving 2024-01-25 21:54:23 +03:00
Syntriax 3c39e6709d fix: Collision Detector CircleCircle Depth Calculation Fixed 2024-01-25 21:53:28 +03:00
Syntriax e7ca96e2e2 fix: Collider2D Not Registering Rigidbody2D Attached 2024-01-25 21:52:56 +03:00
Syntriax 3b83be695c fix: Circle Collider2D Recalculate Wrong Calculation 2024-01-25 21:52:40 +03:00
Syntriax 601d15fa45 feat: Math.Sqr 2024-01-25 21:52:03 +03:00
Syntriax 00b80f1a01 feat: Collider2DBehaviourBase 2024-01-25 20:42:49 +03:00
Syntriax 9e1f38897f fix: Self Referencing Call 2024-01-25 17:49:22 +03:00
Syntriax 0af1b11396 feat: Test Collision Detection & Move 2024-01-25 17:45:22 +03:00
Syntriax f5be49609b feat: NotNullWhen to TryDetect 2024-01-25 17:43:40 +03:00
Syntriax f7467a62ee feat: Collision Detector Circles 2024-01-25 17:30:57 +03:00
Syntriax 816f09fffe feat: Math.Sqrt 2024-01-25 17:07:37 +03:00
Syntriax 24cbc8a267 feat: New CollisionDetectionInformation 2024-01-25 16:44:01 +03:00
Syntriax 385defd8e6 refactor: Possible Bugs 2024-01-25 16:08:50 +03:00
Syntriax ed15238dcd BREAKING CHANGE: New ICollider 2024-01-24 17:35:14 +03:00
Syntriax 350ef030ac refactor: Circle.Position to Circle.Center 2024-01-24 16:31:00 +03:00
Syntriax 3428fcc6ca perf: BehaviourController.GetBehaviours now uses Linq.Enumerable.Empty if None Found 2024-01-24 14:41:10 +03:00
Syntriax 528649659d feat: AABB Center, Size & HalfSize 2024-01-24 14:09:41 +03:00
Syntriax da67f4559b fix: Forgotten this keyword on Vector2DExtensions.Abs 2024-01-24 14:07:36 +03:00
Syntriax 08b31d9db1 feat: Vector2D.Abs 2024-01-24 14:06:35 +03:00
Syntriax 326bcfca61 fix: AABB.FromVectors 2024-01-24 13:59:15 +03:00
Syntriax bfab35c27e feat: Shape IEnumerable 2024-01-24 13:57:52 +03:00
Syntriax 468615e4cb feat: AABB.FromVectors 2024-01-24 12:16:42 +03:00
Syntriax 56b46b93eb fix: Build Errors 2024-01-24 12:04:34 +03:00
Syntriax 0c3bf48d2c feat: Vector2D.Orientation 2024-01-24 11:53:37 +03:00
Syntriax 77e1949f59 refactor: Math Constants Now Use Each Other as References 2024-01-24 11:33:21 +03:00
Syntriax fbf9bd5832 refactor: Removed Old PhysicsMath Class 2024-01-24 11:28:11 +03:00
Syntriax d40183db65 feat: float & Vector2D.ApproximatelyEquals 2024-01-24 11:26:54 +03:00
Syntriax a60f79f12b chore: Removed Physics2D Degree Constants 2024-01-24 11:21:50 +03:00
Syntriax 4bf618251f feat: Math.Clamp 2024-01-24 11:21:25 +03:00
Syntriax b3d404bb6b feat: TransformExtensions.TransformVector2D 2024-01-24 11:17:12 +03:00
Syntriax 87bf47eefd feat: Math 2024-01-24 11:15:44 +03:00
Syntriax e5732f0ac5 fix: Build Error Caused by Parameter Name 2024-01-24 10:45:06 +03:00
Syntriax c3bcaaee06 refactor: Vector2D.Invert to Operator 2024-01-24 10:42:01 +03:00
Syntriax 83d8a03be3 feat: Basic Operation Methods
Vector2D.Invert
Vector2D.Add
Vector2D.Subtract
Vector2D.Multiply
Vector2D.Subdivide
2024-01-24 10:40:24 +03:00
Syntriax 1acecdf3ce feat: Vector2.Rotate 2024-01-24 10:40:23 +03:00
Syntriax 51b1f79a5d refactor: int to Index for Shape Accessor 2024-01-24 10:40:10 +03:00
Syntriax 09c63b65df fix: Scale Calling FromTo Instead of Scale 2024-01-24 10:40:02 +03:00
Syntriax 909b93088c feat: Shape Index Accessor 2024-01-24 10:39:24 +03:00
Syntriax e7587a0827 fix: Wrong Method Call 2024-01-23 17:32:08 +03:00
Syntriax 5ed7ccdded fix: Build Errors 2024-01-23 17:31:32 +03:00
Syntriax 0d29ab066f refactor: Added Static Methods Back 2024-01-23 17:30:46 +03:00
Syntriax bd03d036aa refactor: Shape 2024-01-23 17:29:55 +03:00
Syntriax 3b299c947c refactor: LineEquation 2024-01-23 17:29:21 +03:00
Syntriax 6a5d10980a refactor: Triangle 2024-01-23 17:28:59 +03:00
Syntriax dcda78b010 refactor: Line 2024-01-23 17:27:57 +03:00
Syntriax 5170dd0aea refactor: Circle 2024-01-23 17:27:38 +03:00
Syntriax 5c185a664c refactor: AABB 2024-01-23 17:26:45 +03:00
Syntriax 8ccebaa8fb chore: Solution File for Physics2D 2024-01-23 17:24:37 +03:00
Syntriax a9485475c7 feat: Initial Physics Code From Previous Repo 2024-01-23 15:40:04 +03:00
70 changed files with 3628 additions and 324 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

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignable public interface IAssignable
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle. /// Event triggered when the <see cref="IAssignable"/>'s fields are unassigned and completely ready to recycle.
/// </summary> /// </summary>
Action<IAssignable>? OnUnassigned { get; set; } Action<IAssignable>? OnUnassigned { get; set; }

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableBehaviourController : IAssignable public interface IAssignableBehaviourController : IAssignable
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value. /// Event triggered when the <see cref="IBehaviourController"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableBehaviourController : IAssignable
IBehaviourController BehaviourController { get; } IBehaviourController BehaviourController { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IBehaviourController"/> field of this object /// Assign a value to the <see cref="IBehaviourController"/> field of this object.
/// </summary> /// </summary>
/// <param name="behaviourController">New <see cref="IBehaviourController"/> to assign.</param> /// <param name="behaviourController">New <see cref="IBehaviourController"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableEntity : IAssignable public interface IAssignableEntity : IAssignable
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IEntity"/> value has has been assigned a new value. /// Event triggered when the <see cref="IEntity"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableEntity>? OnEntityAssigned { get; set; } Action<IAssignableEntity>? OnEntityAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableEntity : IAssignable
IEntity Entity { get; } IEntity Entity { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IEntity"/> field of this object /// Assign a value to the <see cref="IEntity"/> field of this object.
/// </summary> /// </summary>
/// <param name="entity">New <see cref="IEntity"/> to assign.</param> /// <param name="entity">New <see cref="IEntity"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -0,0 +1,26 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="ITransform"/> field.
/// </summary>
public interface IAssignableGameManager : IAssignable
{
/// <summary>
/// Event triggered when the <see cref="IGameManager"/> value has has been assigned a new value.
/// </summary>
Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; }
/// <inheritdoc cref="IGameManager" />
IGameManager GameManager { get; }
/// <summary>
/// Assign a value to the <see cref="IGameManager"/> field of this object.
/// </summary>
/// <param name="gameManager">New <see cref="IGameManager"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IGameManager gameManager);
}

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableGameObject : IAssignable public interface IAssignableGameObject : IAssignable
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IGameObject"/> value has has been assigned a new value. /// Event triggered when the <see cref="IGameObject"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; } Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableGameObject : IAssignable
IGameObject GameObject { get; } IGameObject GameObject { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IGameObject"/> field of this object /// Assign a value to the <see cref="IGameObject"/> field of this object.
/// </summary> /// </summary>
/// <param name="gameObject">New <see cref="IGameObject"/> to assign.</param> /// <param name="gameObject">New <see cref="IGameObject"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableStateEnable : IAssignable public interface IAssignableStateEnable : IAssignable
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IStateEnable"/> value has has been assigned a new value. /// Event triggered when the <see cref="IStateEnable"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableStateEnable : IAssignable
IStateEnable StateEnable { get; } IStateEnable StateEnable { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="IStateEnable"/> field of this object /// Assign a value to the <see cref="IStateEnable"/> field of this object.
/// </summary> /// </summary>
/// <param name="stateEnable">New <see cref="IStateEnable"/> to assign.</param> /// <param name="stateEnable">New <see cref="IStateEnable"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -8,7 +8,7 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignableTransform : IAssignable public interface IAssignableTransform : IAssignable
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="ITransform"/> value has has been assigned a new value. /// Event triggered when the <see cref="ITransform"/> value has has been assigned a new value.
/// </summary> /// </summary>
Action<IAssignableTransform>? OnTransformAssigned { get; set; } Action<IAssignableTransform>? OnTransformAssigned { get; set; }
@ -16,7 +16,7 @@ public interface IAssignableTransform : IAssignable
ITransform Transform { get; } ITransform Transform { get; }
/// <summary> /// <summary>
/// Assign a value to the <see cref="ITransform"/> field of this object /// Assign a value to the <see cref="ITransform"/> field of this object.
/// </summary> /// </summary>
/// <param name="transform">New <see cref="ITransform"/> to assign.</param> /// <param name="transform">New <see cref="ITransform"/> to assign.</param>
/// <returns> /// <returns>

View File

@ -0,0 +1,105 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
public abstract class BaseEntity : IEntity
{
public Action<IEntity, string>? OnIdChanged { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private string _id = string.Empty;
public virtual IStateEnable StateEnable => _stateEnable;
public virtual bool IsActive => StateEnable.Enabled;
public string Id
{
get => _id;
set
{
if (value == _id)
return;
string previousId = _id;
_id = value;
OnIdChanged?.Invoke(this, previousId);
}
}
public bool Initialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnStateEnableAssigned?.Invoke(this);
return true;
}
protected virtual void UnassignInternal() { }
public bool Unassign()
{
if (Initialized)
return false;
UnassignInternal();
OnUnassigned?.Invoke(this);
return true;
}
protected virtual void InitializeInternal() { }
public bool Initialize()
{
if (Initialized)
return false;
InitializeInternal();
Initialized = true;
return true;
}
protected virtual void FinalizeInternal() { }
public bool Finalize()
{
if (!Initialized)
return false;
FinalizeInternal();
Initialized = false;
return true;
}
protected BaseEntity() => _id = Guid.NewGuid().ToString("D");
protected BaseEntity(string id) => _id = id;
}

View File

@ -3,17 +3,22 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Responsible for every behaviour an object in the game might have, controlled by <see cref="IBehaviourController"/>. /// Represents a behaviour that any object in the game might use to interact with itself or other objects.
/// </summary> /// </summary>
public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignableStateEnable, IInitialize public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignableStateEnable, IInitialize
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="Priority"/> has changed. /// Event triggered when the priority of the <see cref="IBehaviour"/> changes.
/// </summary> /// </summary>
Action<IBehaviour>? OnPriorityChanged { get; set; } Action<IBehaviour>? OnPriorityChanged { get; set; }
/// <summary> /// <summary>
/// Call priority of the <see cref="IBehaviour"/>. /// The priority of the <see cref="IBehaviour"/>.
/// </summary> /// </summary>
int Priority { get; set; } int Priority { get; set; }
/// <summary>
/// The value indicating whether the <see cref="IBehaviour"/> is active.
/// </summary>
bool IsActive { get; }
} }

View File

@ -5,89 +5,101 @@ using System.Diagnostics.CodeAnalysis;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary> /// <summary>
/// Responsible for controlling <see cref="IBehaviour"/>s and notify them accordingly about the engine's updates. Connected to an <see cref="IGameObject"/>. /// Represents a controller for managing <see cref="IBehaviour"/>s and notify them accordingly about the engine's updates. Connected to an <see cref="IGameObject"/>.
/// </summary> /// </summary>
public interface IBehaviourController : IAssignableGameObject public interface IBehaviourController : IAssignableGameObject, IEnumerable<IBehaviour>
{ {
/// <summary> /// <summary>
/// Callback triggered when the <see cref="Update()"/> is called but right before the <see cref="OnUpdate"/> action is triggered. /// Event triggered before the update of <see cref="IBehaviour"/>s.
/// </summary> /// </summary>
Action<IBehaviourController>? OnPreUpdate { get; set; } Action<IBehaviourController>? OnPreUpdate { get; set; }
/// <summary> /// <summary>
/// Callback triggered when the <see cref="Update()"/> is called. /// Event triggered during the update of <see cref="IBehaviour"/>s.
/// </summary> /// </summary>
Action<IBehaviourController>? OnUpdate { get; set; } Action<IBehaviourController>? OnUpdate { get; set; }
/// <summary> /// <summary>
/// Callback triggered when the <see cref="OnPreDraw()"/> is called. /// Event triggered before the drawing phase.
/// </summary> /// </summary>
Action<IBehaviourController>? OnPreDraw { get; set; } Action<IBehaviourController>? OnPreDraw { get; set; }
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IBehaviourController"/> has been registered a new <see cref="IBehaviour"/>. /// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; } Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; }
/// <summary> /// <summary>
/// Callback triggered when the <see cref="IBehaviourController"/> has been removed an existing <see cref="IBehaviour"/>. /// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; } Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; }
/// <summary> /// <summary>
/// Registers the provided <see cref="IBehaviour"/> to be controlled by the <see cref="IBehaviourController"/>. /// Adds a <see cref="IBehaviour"/> to the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <param name="behaviour">Uninitialized <see cref="IBehaviour"/> to be registered.</param> /// <typeparam name="T">The type of <see cref="IBehaviour"/> to add.</typeparam>
/// <typeparam name="T">An implemented class of <see cref="IBehaviour"/></typeparam> /// <param name="behaviour">The <see cref="IBehaviour"/> to add.</param>
/// <returns>The provided <see cref="IBehaviour"/> class after initialization.</returns> /// <returns>The added <see cref="IBehaviour"/>.</returns>
T AddBehaviour<T>(T behaviour) where T : class, IBehaviour; T AddBehaviour<T>(T behaviour) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Instantiates the provided <see cref="IBehaviour"/> type and registers it to the <see cref="IBehaviourController"/>. /// Adds a <see cref="IBehaviour"/> of the specified type to the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <param name="args">Constructor parameters for the given <see cref="IBehaviour"/> class.</param> /// <typeparam name="T">The type of <see cref="IBehaviour"/> to add.</typeparam>
/// <typeparam name="T">An implemented class of <see cref="IBehaviour"/></typeparam> /// <param name="args">Construction parameters for the <see cref="IBehaviour"/>.</param>
/// <returns>The instantiated <see cref="IBehaviour"/> class after initialization.</returns> /// <returns>The added <see cref="IBehaviour"/>.</returns>
T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour; T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Looks up and tries to get the <see cref="IBehaviour"/> that is controlled by the <see cref="IBehaviourController"/>. /// Gets a <see cref="IBehaviour"/> of the specified type.
/// </summary> /// </summary>
/// <param name="behaviour">If return value is <see cref="true"/> outputs the class found in the <see cref="IBehaviourController"/>. If the return value is falls, this parameter is <see cref="null"/></param> /// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <typeparam name="T">An implemented class or <see cref="interface"/></typeparam> /// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, <see cref="null"/>.</returns>
/// <returns> T? GetBehaviour<T>();
/// <see cref="true"/>, if the type of <see cref="IBehaviour"/> is present in the <see cref="IBehaviourController"/>, <see cref="false"/> if not.
/// </returns> /// <summary>
/// Tries to get a <see cref="IBehaviour"/> of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, see.</param>
/// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found; otherwise, <see cref="false"/>.</returns>
bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour); bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour);
/// <typeparam name="T">An implemented class or <see cref="interface"/>.</typeparam> /// <summary>
/// <returns>Returns a list of all the matching <see cref="IBehaviour"/>s found in the <see cref="IBehaviourController"/>.</returns> /// Gets all <see cref="IBehaviour"/>s of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <returns>A list of <see cref="IBehaviour"/>s of the specified type.</returns>
IList<T> GetBehaviours<T>(); IList<T> GetBehaviours<T>();
/// <summary> /// <summary>
/// Removes the <see cref="IBehaviour"/> found in the <see cref="IBehaviourController"/>. /// Gets all <see cref="IBehaviour"/>s of the specified type and stores them in the provided list.
/// </summary> /// </summary>
/// <param name="removeAll">If all of the instances of the given Type is to be removed or not.</param> /// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <typeparam name="T">An implemented class or <see cref="interface"/> of <see cref="IBehaviour"/></typeparam> /// <param name="behaviours">The list to store the <see cref="IBehaviour"/>s.</param>
void GetBehaviours<T>(List<T> behaviours);
/// <summary>
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to remove.</typeparam>
/// <param name="removeAll">A flag indicating whether to remove all <see cref="IBehaviour"/>s of the specified type.</param>
void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour; void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour;
/// <summary> /// <summary>
/// Removes the <see cref="IBehaviour"/> found in the <see cref="IBehaviourController"/>. /// Removes the specified <see cref="IBehaviour"/> from the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <param name="removeAll">If all of the instances of the given Type is to be removed or not.</param> /// <typeparam name="T">The type of <see cref="IBehaviour"/> to remove.</typeparam>
/// <typeparam name="T">An implemented class or <see cref="interface"/> of <see cref="IBehaviour"/></typeparam> /// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour; void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
/// <summary> /// <summary>
/// To be called in every frame of the engine. Responsible for notifying <see cref="IBehaviour"/>'s under the <see cref="IBehaviourController"/>'s control that a new frame is happening. /// Updates all <see cref="IBehaviour"/>s in the <see cref="IBehaviourController"/>.
/// </summary> /// </summary>
/// <param name=""><see cref=""/> information from the game.</param>
void Update(); void Update();
/// <summary> /// <summary>
/// To be called before every draw call from the engine. Responsible for notifying <see cref="IBehaviour"/>'s under the <see cref="IBehaviourController"/>'s control that the engine is about to start drawing into the screen. /// Performs pre-draw operations.
/// </summary> /// </summary>
/// <param name=""><see cref=""/> information from the game.</param>
void UpdatePreDraw(); void UpdatePreDraw();
} }

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,26 @@
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents a 2D camera in the engine.
/// </summary>
public interface ICamera2D : IBehaviour, IAssignableTransform
{
/// <summary>
/// The zoom level of the camera.
/// </summary>
float Zoom { get; set; }
/// <summary>
/// Converts a position from screen coordinates to world coordinates.
/// </summary>
/// <param name="screenPosition">The position in screen coordinates.</param>
/// <returns>The position in world coordinates.</returns>
Vector2D ScreenToWorldPosition(Vector2D screenPosition);
/// <summary>
/// Converts a position from world coordinates to screen coordinates.
/// </summary>
/// <param name="worldPosition">The position in world coordinates.</param>
/// <returns>The position in screen coordinates.</returns>
Vector2D WorldToScreenPosition(Vector2D worldPosition);
}

View File

@ -1,5 +1,20 @@
using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents a basic entity in the engine.
/// </summary>
public interface IEntity : IInitialize, IAssignableStateEnable public interface IEntity : IInitialize, IAssignableStateEnable
{ {
/// <summary>
/// Event triggered when the <see cref="Id"/> of the <see cref="IEntity"/> changes.
/// The string action parameter is the previous <see cref="Id"/> of the <see cref="IEntity"/>.
/// </summary>
Action<IEntity, string>? OnIdChanged { get; set; }
/// <summary>
/// The ID of the <see cref="IEntity"/>.
/// </summary>
string Id { get; set; }
} }

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents a game world responsible for managing <see cref="IGameObject"/>s.
/// </summary>
public interface IGameManager : IEntity, IEnumerable<IGameObject>
{
/// <summary>
/// Event triggered when a <see cref="IGameObject"/> is registered to the <see cref="IGameManager"/>.
/// </summary>
Action<IGameManager, IGameObject>? OnGameObjectRegistered { get; set; }
/// <summary>
/// Event triggered when a <see cref="IGameObject"/> is unregistered from the <see cref="IGameManager"/>.
/// </summary>
Action<IGameManager, IGameObject>? OnGameObjectUnRegistered { get; set; }
/// <summary>
/// Gets a read-only list of <see cref="IGameObject"/>s managed by the <see cref="IGameManager"/>.
/// </summary>
IReadOnlyList<IGameObject> GameObjects { get; }
/// <summary>
/// Registers a <see cref="IGameObject"/> to the <see cref="IGameManager"/>.
/// </summary>
/// <param name="gameObject">The <see cref="IGameObject"/> to register.</param>
void RegisterGameObject(IGameObject gameObject);
/// <summary>
/// Instantiates a <see cref="IGameObject"/> of type T with the given arguments and registers it to the <see cref="IGameManager"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="IGameObject"/> to instantiate.</typeparam>
/// <param name="args">Constructor parameters for the given type of <see cref="IGameObject"/>.</param>
/// <returns>The instantiated <see cref="IGameObject"/>.</returns>
T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject;
/// <summary>
/// Removes a <see cref="IGameObject"/> from the <see cref="IGameManager"/>.
/// </summary>
/// <param name="gameObject">The <see cref="IGameObject"/> to remove.</param>
/// <returns>The removed <see cref="IGameObject"/>.</returns>
IGameObject RemoveGameObject(IGameObject gameObject);
/// <summary>
/// Updates the <see cref="IGameManager"/> with the given engine time data.
/// </summary>
/// <param name="time">The engine time.</param>
void Update(EngineTime time);
/// <summary>
/// Performs operations that should be done before the draw calls.
/// </summary>
void PreDraw();
}

View File

@ -2,9 +2,18 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
public interface IGameObject : IEntity, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize /// <summary>
/// Represents a game object with various properties and functionalities.
/// </summary>
public interface IGameObject : IEntity, IAssignableGameManager, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize
{ {
/// <summary>
/// Event triggered when the <see cref="Update"/> method is called.
/// </summary>
Action<IGameObject>? OnUpdated { get; set; } Action<IGameObject>? OnUpdated { get; set; }
/// <summary>
/// Updates the game object.
/// </summary>
void Update(); void Update();
} }

View File

@ -2,12 +2,35 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an entity that can be initialized and finalized. This information is useful for objects we know that are not in use and can be either recycled or dropped for garbage collection.
/// </summary>
public interface IInitialize public interface IInitialize
{ {
/// <summary>
/// Event triggered when the <see cref="Initialize"/> method is called successfully.
/// </summary>
Action<IInitialize>? OnInitialized { get; set; } Action<IInitialize>? OnInitialized { get; set; }
/// <summary>
/// Event triggered when the <see cref="Finalize"/> method is called successfully.
/// </summary>
Action<IInitialize>? OnFinalized { get; set; } Action<IInitialize>? OnFinalized { get; set; }
/// <summary>
/// The value indicating whether the entity has been initialized.
/// </summary>
bool Initialized { get; } bool Initialized { get; }
/// <summary>
/// Initializes the entity.
/// </summary>
/// <returns><see cref="true"/> if initialization is successful, otherwise <see cref="false"/>.</returns>
bool Initialize(); bool Initialize();
/// <summary>
/// Finalizes the entity so it can either be recycled or garbage collected.
/// </summary>
/// <returns><see cref="true"/> if finalization is successful, otherwise <see cref="false"/>.</returns>
bool Finalize(); bool Finalize();
} }

View File

@ -2,8 +2,18 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an entity with a name.
/// </summary>
public interface INameable public interface INameable
{ {
/// <summary>
/// Event triggered when the name of the entity changes.
/// </summary>
Action<IEntity>? OnNameChanged { get; set; } Action<IEntity>? OnNameChanged { get; set; }
/// <summary>
/// The name of the entity.
/// </summary>
string Name { get; set; } string Name { get; set; }
} }

View File

@ -2,8 +2,18 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents an entity with an enable state that can be toggled.
/// </summary>
public interface IStateEnable : IAssignableEntity public interface IStateEnable : IAssignableEntity
{ {
/// <summary>
/// Event triggered when the <see cref="Enabled"/> state of the <see cref="IStateEnable"/> changes.
/// </summary>
Action<IStateEnable>? OnEnabledChanged { get; set; } Action<IStateEnable>? OnEnabledChanged { get; set; }
/// <summary>
/// The value indicating whether the <see cref="IStateEnable"/> is enabled.
/// </summary>
bool Enabled { get; set; } bool Enabled { get; set; }
} }

View File

@ -2,14 +2,38 @@ using System;
namespace Syntriax.Engine.Core.Abstract; namespace Syntriax.Engine.Core.Abstract;
/// <summary>
/// Represents the transformation properties of an object such as position, scale, and rotation.
/// </summary>
public interface ITransform public interface ITransform
{ {
/// <summary>
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform"/> changes.
/// </summary>
Action<ITransform>? OnPositionChanged { get; set; } Action<ITransform>? OnPositionChanged { get; set; }
/// <summary>
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform"/> changes.
/// </summary>
Action<ITransform>? OnScaleChanged { get; set; } Action<ITransform>? OnScaleChanged { get; set; }
/// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes.
/// </summary>
Action<ITransform>? OnRotationChanged { get; set; } Action<ITransform>? OnRotationChanged { get; set; }
/// <summary>
/// The position of the <see cref="ITransform"/> in 2D space.
/// </summary>
Vector2D Position { get; set; } Vector2D Position { get; set; }
/// <summary>
/// The scale of the <see cref="ITransform"/>.
/// </summary>
Vector2D Scale { get; set; } Vector2D Scale { get; set; }
/// <summary>
/// The rotation of the <see cref="ITransform"/> in degrees.
/// </summary>
float Rotation { get; set; } float Rotation { get; set; }
} }

View File

@ -5,41 +5,21 @@ using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public abstract class Behaviour : IBehaviour [System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class Behaviour : BaseEntity, IBehaviour
{ {
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null; public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IBehaviour>? OnPriorityChanged { get; set; } = null; public Action<IBehaviour>? OnPriorityChanged { get; set; } = null;
private IBehaviourController _behaviourController = null!; private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private int _priority = 0; private int _priority = 0;
public IStateEnable StateEnable => _stateEnable;
public IBehaviourController BehaviourController => _behaviourController; public IBehaviourController BehaviourController => _behaviourController;
public bool Initialized public override bool IsActive => base.IsActive && BehaviourController.GameObject.StateEnable.Enabled;
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public int Priority public int Priority
{ {
@ -54,17 +34,6 @@ public abstract class Behaviour : IBehaviour
} }
} }
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnStateEnableAssigned?.Invoke(this);
return true;
}
public bool Assign(IBehaviourController behaviourController) public bool Assign(IBehaviourController behaviourController)
{ {
if (Initialized) if (Initialized)
@ -75,36 +44,16 @@ public abstract class Behaviour : IBehaviour
return true; return true;
} }
public bool Unassign() protected override void UnassignInternal()
{ {
if (Initialized) base.UnassignInternal();
return false;
_stateEnable = null!;
_behaviourController = null!; _behaviourController = null!;
OnUnassigned?.Invoke(this);
return true;
} }
public bool Initialize() protected override void InitializeInternal()
{ {
if (Initialized) base.InitializeInternal();
return false;
NotAssignedException.Check(this, _behaviourController); NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, _stateEnable); NotAssignedException.Check(this, StateEnable);
Initialized = true;
return true;
}
public bool Finalize()
{
if (!Initialized)
return false;
Initialized = false;
return true;
} }
} }

View File

@ -0,0 +1,102 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class BehaviourCacher<T> : IAssignableGameManager, IEnumerable<T>
{
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
public Action<BehaviourCacher<T>, T>? OnCached { get; set; } = null;
public Action<BehaviourCacher<T>, T>? OnUncached { get; set; } = null;
private readonly List<T> _behaviours = new(32);
public IReadOnlyList<T> Behaviours => _behaviours;
public IGameManager GameManager { get; private set; } = null!;
public T this[Index index] => _behaviours[index];
public BehaviourCacher() { }
public BehaviourCacher(IGameManager gameManager) => Assign(gameManager);
private void OnGameObjectRegistered(IGameManager manager, IGameObject gameObject)
{
gameObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
gameObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
}
private void OnGameObjectUnregistered(IGameManager manager, IGameObject gameObject)
{
gameObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdded;
gameObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemoved;
}
private void OnBehaviourAdded(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
_behaviours.Add(tBehaviour);
OnCached?.Invoke(this, tBehaviour);
}
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is not T tBehaviour)
return;
if (!_behaviours.Remove(tBehaviour))
return;
OnUncached?.Invoke(this, tBehaviour);
}
public bool Assign(IGameManager gameManager)
{
if (GameManager is not null)
return false;
foreach (IGameObject gameObject in gameManager)
{
OnGameObjectRegistered(gameManager, gameObject);
foreach (IBehaviour behaviour in gameObject.BehaviourController)
OnBehaviourAdded(gameObject.BehaviourController, behaviour);
}
gameManager.OnGameObjectRegistered += OnGameObjectRegistered;
gameManager.OnGameObjectUnRegistered += OnGameObjectUnregistered;
GameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (GameManager is null)
return false;
foreach (IGameObject gameObject in GameManager)
{
OnGameObjectUnregistered(GameManager, gameObject);
foreach (IBehaviour behaviour in gameObject.BehaviourController)
OnBehaviourRemoved(gameObject.BehaviourController, behaviour);
}
GameManager.OnGameObjectRegistered -= OnGameObjectRegistered;
GameManager.OnGameObjectUnRegistered -= OnGameObjectUnregistered;
GameManager = null!;
OnUnassigned?.Invoke(this);
return true;
}
public IEnumerator<T> GetEnumerator() => _behaviours.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _behaviours.GetEnumerator();
}

View File

@ -1,11 +1,14 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Syntriax.Engine.Core.Abstract; using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
public class BehaviourController : IBehaviourController public class BehaviourController : IBehaviourController
{ {
public Action<IBehaviourController>? OnPreUpdate { get; set; } public Action<IBehaviourController>? OnPreUpdate { get; set; }
@ -38,34 +41,46 @@ public class BehaviourController : IBehaviourController
public T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour public T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour
=> AddBehaviour(new Factory.BehaviourFactory().Instantiate<T>(_gameObject, args)); => AddBehaviour(new Factory.BehaviourFactory().Instantiate<T>(_gameObject, args));
public bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour) public T? GetBehaviour<T>()
{ {
foreach (var behaviourItem in behaviours) foreach (var behaviourItem in behaviours)
{ if (behaviourItem is T result)
if (behaviourItem is not T result) return result;
continue;
behaviour = result; return default;
return true; }
}
behaviour = default; public bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour)
return false; {
behaviour = GetBehaviour<T>();
return behaviour is not null;
} }
public IList<T> GetBehaviours<T>() public IList<T> GetBehaviours<T>()
{ {
IList<T> behaviours = new List<T>(); List<T>? behaviours = null;
foreach (var behaviourItem in this.behaviours) foreach (var behaviourItem in this.behaviours)
{ {
if (behaviourItem is not T behaviour) if (behaviourItem is not T behaviour)
continue; continue;
behaviours ??= new List<T>(); behaviours ??= [];
behaviours.Add(behaviour); behaviours.Add(behaviour);
} }
return behaviours; return behaviours ?? Enumerable.Empty<T>().ToList();
}
public void GetBehaviours<T>(List<T> behaviors)
{
behaviors.Clear();
foreach (var behaviourItem in behaviours)
{
if (behaviourItem is not T _)
continue;
behaviours.Add(behaviourItem);
}
} }
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
@ -155,4 +170,7 @@ public class BehaviourController : IBehaviourController
behaviours.Remove(behaviour); behaviours.Remove(behaviour);
InsertBehaviourByPriority(behaviour); InsertBehaviourByPriority(behaviour);
} }
public IEnumerator<IBehaviour> GetEnumerator() => behaviours.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => behaviours.GetEnumerator();
} }

View File

@ -39,13 +39,13 @@ public abstract class BehaviourOverride : Behaviour
OnFinalize(); OnFinalize();
} }
protected virtual void OnPreUpdatePreEnabledCheck() { } protected virtual void OnPreUpdatePreActiveCheck() { }
protected virtual void OnPreUpdate() { } protected virtual void OnPreUpdate() { }
private void PreUpdate(IBehaviourController _) private void PreUpdate(IBehaviourController _)
{ {
OnPreUpdatePreEnabledCheck(); OnPreUpdatePreActiveCheck();
if (!StateEnable.Enabled) if (!IsActive)
return; return;
if (isInitializedThisFrame) if (isInitializedThisFrame)
@ -61,23 +61,23 @@ public abstract class BehaviourOverride : Behaviour
isInitializedThisFrame = false; isInitializedThisFrame = false;
} }
protected virtual void OnUpdatePreEnabledCheck() { } protected virtual void OnUpdatePreActiveCheck() { }
protected virtual void OnUpdate() { } protected virtual void OnUpdate() { }
private void Update(IBehaviourController _) private void Update(IBehaviourController _)
{ {
OnUpdatePreEnabledCheck(); OnUpdatePreActiveCheck();
if (!StateEnable.Enabled) if (!IsActive)
return; return;
OnUpdate(); OnUpdate();
} }
protected virtual void OnPreDrawPreEnabledCheck() { } protected virtual void OnPreDrawPreActiveCheck() { }
protected virtual void OnPreDraw() { } protected virtual void OnPreDraw() { }
private void PreDraw(IBehaviourController _) private void PreDraw(IBehaviourController _)
{ {
OnPreDrawPreEnabledCheck(); OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled) if (!StateEnable.Enabled)
return; 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,10 @@ using System;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public record EngineTime public readonly struct EngineTime(TimeSpan Total, TimeSpan Elapsed)
( {
TimeSpan Total, public readonly TimeSpan Total = Total;
TimeSpan Elapsed public readonly TimeSpan Elapsed = Elapsed;
);
public readonly float DeltaTimeFrame = (float)Elapsed.TotalMilliseconds;
}

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,32 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class BehaviourExtensions
{
public static bool TryFindBehaviour<T>(this IEnumerable<IGameObject> gameObjects, [NotNullWhen(returnValue: true)] out T? behaviour)
{
behaviour = default;
foreach (IGameObject gameObject in gameObjects)
if (gameObject.BehaviourController.TryGetBehaviour(out behaviour))
return true;
return false;
}
public static void FindBehaviours<T>(this IEnumerable<IGameObject> gameObjects, List<T> behaviours)
{
behaviours.Clear();
List<T> cache = [];
foreach (IGameObject gameObject in gameObjects)
{
gameObject.BehaviourController.GetBehaviours(cache);
behaviours.AddRange(cache);
}
}
}

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

@ -0,0 +1,9 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class GameManagerExtensions
{
public static IGameObject InstantiateGameObject(this IGameManager gameManager, params object?[]? args)
=> gameManager.InstantiateGameObject<GameObject>(args);
}

View File

@ -0,0 +1,12 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class GameObjectExtensions
{
public static IGameObject SetGameObject(this IGameObject gameObject, string name)
{
gameObject.Name = name;
return gameObject;
}
}

View File

@ -0,0 +1,14 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public static class TransformExtensions
{
public static ITransform SetTransform(this ITransform transform, Vector2D? position = null, float? rotation = null, Vector2D? scale = null)
{
if (position.HasValue) transform.Position = position.Value;
if (rotation.HasValue) transform.Rotation = rotation.Value;
if (scale.HasValue) transform.Scale = scale.Value;
return transform;
}
}

View File

@ -1,22 +1,188 @@
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
/// <summary>
/// Provides extension methods for <see cref="Vector2D"/> type.
/// </summary>
public static class Vector2DExtensions public static class Vector2DExtensions
{ {
/// <summary>
/// Calculates the length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The length of the <see cref="Vector2D"/>.</returns>
public static float Length(this Vector2D vector) => Vector2D.Length(vector); public static float Length(this Vector2D vector) => Vector2D.Length(vector);
/// <summary>
/// Calculates the squared length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The squared length of the <see cref="Vector2D"/>.</returns>
public static float LengthSquared(this Vector2D vector) => Vector2D.LengthSquared(vector); public static float LengthSquared(this Vector2D vector) => Vector2D.LengthSquared(vector);
/// <summary>
/// Calculates the distance between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector2D"/>.</param>
/// <param name="to">The ending <see cref="Vector2D"/>.</param>
/// <returns>The distance between the two <see cref="Vector2D"/>s.</returns>
public static float Distance(this Vector2D from, Vector2D to) => Vector2D.Distance(from, to); public static float Distance(this Vector2D from, Vector2D to) => Vector2D.Distance(from, to);
public static Vector2D Reflect(this Vector2D vector, Vector2D normal) => Vector2D.Reflect(vector, normal); /// <summary>
public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector); /// Returns the <see cref="Vector2D"/> with its components inverted.
public static Vector2D FromTo(this Vector2D from, Vector2D to) => Vector2D.FromTo(from, to); /// </summary>
public static Vector2D Scale(this Vector2D vector, Vector2D scale) => Vector2D.FromTo(vector, scale); /// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The inverted <see cref="Vector2D"/>.</returns>
public static Vector2D Invert(this Vector2D vector) => Vector2D.Invert(vector);
/// <summary>
/// Adds two <see cref="Vector2D"/>s component-wise.
/// </summary>
/// <param name="vector">The first <see cref="Vector2D"/>.</param>
/// <param name="vectorToAdd">The vector <see cref="Vector2D"/> to be added.</param>
/// <returns>The result of the addition.</returns>
public static Vector2D Add(this Vector2D vector, Vector2D vectorToAdd) => Vector2D.Add(vector, vectorToAdd);
/// <summary>
/// Subtracts one <see cref="Vector2D"/> from another component-wise.
/// </summary>
/// <param name="vector">The first <see cref="Vector2D"/>.</param>
/// <param name="vectorToSubtract">The <see cref="Vector2D"/> to be subtracted.</param>
/// <returns>The result of the subtraction.</returns>
public static Vector2D Subtract(this Vector2D vector, Vector2D vectorToSubtract) => Vector2D.Subtract(vector, vectorToSubtract);
/// <summary>
/// Multiplies a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to multiply.</param>
/// <param name="value">The scalar value to multiply with.</param>
/// <returns>The result of the multiplication.</returns>
public static Vector2D Multiply(this Vector2D vector, float value) => Vector2D.Multiply(vector, value);
/// <summary>
/// Divides a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to divide.</param>
/// <param name="value">The scalar value to divide with.</param>
/// <returns>The result of the division.</returns>
public static Vector2D Divide(this Vector2D vector, float value) => Vector2D.Divide(vector, value);
/// <summary>
/// Returns a <see cref="Vector2D"/> with the absolute values of each component.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> with absolute values.</returns>
public static Vector2D Abs(this Vector2D vector) => Vector2D.Abs(vector);
/// <summary>
/// Reflects a <see cref="Vector2D"/> off a surface with the specified normal.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to reflect.</param>
/// <param name="normal">The normal <see cref="Vector2D"/> of the reflecting surface.</param>
/// <returns>The reflected <see cref="Vector2D"/>.</returns>
public static Vector2D Reflect(this Vector2D vector, Vector2D normal) => Vector2D.Reflect(vector, normal);
/// <summary>
/// Normalizes the <see cref="Vector2D"/> (creates a <see cref="Vector2D"/> with the same direction but with a length of 1).
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>The normalized <see cref="Vector2D"/>.</returns>
public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector);
/// <summary>
/// Creates a <see cref="Vector2D"/> pointing from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector2D"/> pointing from <paramref name="from"/> to <paramref name="to"/>.</returns>
public static Vector2D FromTo(this Vector2D from, Vector2D to) => Vector2D.FromTo(from, to);
/// <summary>
/// Scales a <see cref="Vector2D"/> by another <see cref="Vector2D"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to scale.</param>
/// <param name="scale">The <see cref="Vector2D"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector2D"/>.</returns>
public static Vector2D Scale(this Vector2D vector, Vector2D scale) => Vector2D.Scale(vector, scale);
/// <summary>
/// Calculates the perpendicular <see cref="Vector2D"/> to the given <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>A <see cref="Vector2D"/> perpendicular to the input <see cref="Vector2D"/>.</returns>
public static Vector2D Perpendicular(this Vector2D vector) => Vector2D.Perpendicular(vector);
/// <summary>
/// Rotates a <see cref="Vector2D"/> by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to rotate.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector2D"/>.</returns>
public static Vector2D Rotate(this Vector2D vector, float angleInRadian) => Vector2D.Rotate(vector, angleInRadian);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the minimum components from both input <see cref="Vector2D"/>s.</returns>
public static Vector2D Min(this Vector2D left, Vector2D right) => Vector2D.Min(left, right); public static Vector2D Min(this Vector2D left, Vector2D right) => Vector2D.Min(left, right);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the maximum components from both input <see cref="Vector2D"/>s.</returns>
public static Vector2D Max(this Vector2D left, Vector2D right) => Vector2D.Max(left, right); public static Vector2D Max(this Vector2D left, Vector2D right) => Vector2D.Max(left, right);
/// <summary>
/// Clamps each component of a <see cref="Vector2D"/> between the corresponding component of two other <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to clamp.</param>
/// <param name="min">The <see cref="Vector2D"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector2D"/> representing the maximum values for each component.</param>
/// <returns>The clamped <see cref="Vector2D"/>.</returns>
public static Vector2D Clamp(this Vector2D vector, Vector2D min, Vector2D max) => Vector2D.Clamp(vector, min, max); public static Vector2D Clamp(this Vector2D vector, Vector2D min, Vector2D max) => Vector2D.Clamp(vector, min, max);
/// <summary>
/// Linearly interpolates between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector2D"/>.</param>
/// <param name="to">The end <see cref="Vector2D"/>.</param>
/// <param name="t">The interpolation parameter (between 0 and 1).</param>
/// <returns>The interpolated <see cref="Vector2D"/>.</returns>
public static Vector2D Lerp(this Vector2D from, Vector2D to, float t) => Vector2D.Lerp(from, to, t); public static Vector2D Lerp(this Vector2D from, Vector2D to, float t) => Vector2D.Lerp(from, to, t);
/// <summary>
/// Calculates the cross product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The cross product of the two <see cref="Vector2D"/>s.</returns>
public static float Cross(this Vector2D left, Vector2D right) => Vector2D.Cross(left, right); public static float Cross(this Vector2D left, Vector2D right) => Vector2D.Cross(left, right);
/// <summary>
/// Calculates the angle in radians between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The angle between the two <see cref="Vector2D"/>s in radians.</returns>
public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right); public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right);
/// <summary>
/// Calculates the dot product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The dot product of the two <see cref="Vector2D"/>s.</returns>
public static float Dot(this Vector2D left, Vector2D right) => Vector2D.Dot(left, right); public static float Dot(this Vector2D left, Vector2D right) => Vector2D.Dot(left, right);
/// <summary>
/// Checks whether two <see cref="Vector2D"/>s are approximately equal within a certain epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <param name="epsilon">The maximum difference allowed between components.</param>
/// <returns>True if the <see cref="Vector2D"/>s are approximately equal, false otherwise.</returns>
public static bool ApproximatelyEquals(this Vector2D left, Vector2D right, float epsilon = float.Epsilon) => Vector2D.ApproximatelyEquals(left, right, epsilon);
} }

View File

@ -8,24 +8,16 @@ using Syntriax.Engine.Core.Factory;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public class GameManager : IEntity, IEnumerable<IGameObject> [System.Diagnostics.DebuggerDisplay("GameObject Count: {_gameObjects.Count}")]
public class GameManager : BaseEntity, IGameManager
{ {
public Action<GameManager>? OnCameraChanged { get; set; } = null; public Action<IGameManager, IGameObject>? OnGameObjectRegistered { get; set; } = null;
public Action<GameManager, IGameObject>? OnGameObjectRegistered { get; set; } = null; public Action<IGameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } = null;
public Action<GameManager, IGameObject>? OnGameObjectUnRegistered { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
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 GameObjectFactory _gameObjectFactory = null!;
private bool _initialized = false;
private ICamera _camera = null!;
private GameObjectFactory GameObjectFactory private GameObjectFactory GameObjectFactory
{ {
@ -37,33 +29,20 @@ public class GameManager : IEntity, IEnumerable<IGameObject>
} }
} }
public bool Initialized => _initialized; public IReadOnlyList<IGameObject> GameObjects => _gameObjects;
public IList<IGameObject> GameObjects => _gameObjects;
public IStateEnable StateEnable public override IStateEnable StateEnable
{ {
get get
{ {
if (_stateEnable is null) if (base.StateEnable is null)
{ {
Assign(new StateEnableFactory().Instantiate(this)); Assign(new StateEnableFactory().Instantiate(this));
if (_stateEnable is null) if (base.StateEnable is null)
throw NotAssignedException.From(this, _stateEnable); throw NotAssignedException.From(this, base.StateEnable);
} }
return _stateEnable; return base.StateEnable;
}
}
public ICamera Camera
{
get => _camera;
set
{
if (_camera == value)
return;
_camera = value;
OnCameraChanged?.Invoke(this);
} }
} }
@ -91,50 +70,20 @@ public class GameManager : IEntity, IEnumerable<IGameObject>
return gameObject; return gameObject;
} }
public bool Initialize() protected override void InitializeInternal()
{ {
if (Initialized) base.InitializeInternal();
return false;
NotAssignedException.Check(this, StateEnable); NotAssignedException.Check(this, StateEnable);
foreach (var gameObject in GameObjects) foreach (var gameObject in GameObjects)
gameObject.Initialize(); gameObject.Initialize();
OnInitialized?.Invoke(this);
return true;
} }
public bool Finalize() protected override void FinalizeInternal()
{ {
if (!Initialized) base.FinalizeInternal();
return false;
for (int i = GameObjects.Count; i >= 0; i--) for (int i = GameObjects.Count; i >= 0; i--)
GameObjects[i].Finalize(); GameObjects[i].Finalize();
OnFinalized?.Invoke(this);
return true;
}
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
OnStateEnableAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Initialized)
return false;
_stateEnable = null!;
OnUnassigned?.Invoke(this);
return true;
} }
public void Update(EngineTime time) public void Update(EngineTime time)
@ -154,6 +103,8 @@ public class GameManager : IEntity, IEnumerable<IGameObject>
private void Register(IGameObject gameObject) private void Register(IGameObject gameObject)
{ {
gameObject.Assign(this);
gameObject.OnFinalized += OnGameObjectFinalize; gameObject.OnFinalized += OnGameObjectFinalize;
_gameObjects.Add(gameObject); _gameObjects.Add(gameObject);

View File

@ -5,47 +5,28 @@ using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
public class GameObject : IGameObject [System.Diagnostics.DebuggerDisplay("Name: {Name}, Initialized: {Initialized}")]
public class GameObject : BaseEntity, IGameObject
{ {
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IAssignableTransform>? OnTransformAssigned { get; set; } = null; public Action<IAssignableTransform>? OnTransformAssigned { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null; public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
public Action<IEntity>? OnNameChanged { get; set; } = null; public Action<IEntity>? OnNameChanged { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IGameObject>? OnUpdated { get; set; } = null; public Action<IGameObject>? OnUpdated { get; set; } = null;
private ITransform _transform = null!; private ITransform _transform = null!;
private IBehaviourController _behaviourController = null!; private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!; private IStateEnable _stateEnable = null!;
private IGameManager _gameManager = null!;
private string _name = nameof(GameObject); private string _name = nameof(GameObject);
private bool _initialized = false;
public ITransform Transform => _transform; public ITransform Transform => _transform;
public IBehaviourController BehaviourController => _behaviourController; public IBehaviourController BehaviourController => _behaviourController;
public IStateEnable StateEnable => _stateEnable; public IGameManager GameManager => _gameManager;
public bool Initialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
public string Name public string Name
{ {
@ -59,17 +40,14 @@ public class GameObject : IGameObject
} }
} }
public bool Initialize() protected override void InitializeInternal()
{ {
if (Initialized) base.InitializeInternal();
return false;
NotAssignedException.Check(this, _transform); NotAssignedException.Check(this, _transform);
NotAssignedException.Check(this, _behaviourController); NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, _stateEnable); NotAssignedException.Check(this, _stateEnable);
NotAssignedException.Check(this, _gameManager);
Initialized = true;
return true;
} }
public void Update() public void Update()
@ -80,29 +58,12 @@ public class GameObject : IGameObject
OnUpdated?.Invoke(this); OnUpdated?.Invoke(this);
} }
public bool Finalize() protected override void FinalizeInternal()
{ {
if (!Initialized) base.FinalizeInternal();
return false;
foreach (IBehaviour behaviour in _behaviourController.GetBehaviours<IBehaviour>())
System.Threading.Tasks.Parallel.ForEach( behaviour.Finalize();
_behaviourController.GetBehaviours<IBehaviour>(),
behaviour => behaviour.Finalize()
);
Initialized = false;
return true;
}
public bool Assign(IStateEnable stateEnable)
{
if (Initialized)
return false;
_stateEnable = stateEnable;
OnStateEnableAssigned?.Invoke(this);
return true;
} }
public bool Assign(ITransform transform) public bool Assign(ITransform transform)
@ -125,17 +86,24 @@ public class GameObject : IGameObject
return true; return true;
} }
public bool Unassign() public bool Assign(IGameManager gameManager)
{ {
if (Initialized) if (Initialized)
return false; return false;
_gameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
protected override void UnassignInternal()
{
base.UnassignInternal();
_stateEnable = null!; _stateEnable = null!;
_transform = null!; _transform = null!;
_behaviourController = null!; _behaviourController = null!;
_gameManager = null!;
OnUnassigned?.Invoke(this);
return true;
} }
public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; } public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; }

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

@ -0,0 +1,190 @@
using System;
using System.Numerics;
namespace Syntriax.Engine.Core;
public static class Math
{/// <summary>
/// The value of Pi (π), a mathematical constant approximately equal to 3.14159.
/// </summary>
public const float PI = 3.1415926535897932f;
/// <summary>
/// The value of Tau (τ), a mathematical constant equal to 2π, approximately equal to 6.28319.
/// </summary>
public const float Tau = 2f * PI;
/// <summary>
/// The base of the natural logarithm, approximately equal to 2.71828.
/// </summary>
public const float E = 2.718281828459045f;
/// <summary>
/// The conversion factor from radians to degrees.
/// </summary>
public const float RadianToDegree = 180f / PI;
/// <summary>
/// The conversion factor from degrees to radians.
/// </summary>
public const float DegreeToRadian = PI / 180f;
/// <summary>
/// Returns the absolute value of a number.
/// </summary>
/// <typeparam name="T">The type of the number.</typeparam>
/// <param name="x">The number.</param>
/// <returns>The absolute value of <paramref name="x"/>.</returns>
public static T Abs<T>(T x) where T : INumber<T> => x > T.Zero ? x : -x;
/// <summary>
/// Returns the arccosine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The arccosine of <paramref name="x"/>.</returns>
public static float Acos(float x) => MathF.Acos(x);
/// <summary>
/// Returns the arcsine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The arcsine of <paramref name="x"/>.</returns>
public static float Asin(float x) => MathF.Asin(x);
/// <summary>
/// Returns the angle whose tangent is the quotient of two specified numbers.
/// </summary>
/// <param name="y">The y-coordinate of a point.</param>
/// <param name="x">The x-coordinate of a point.</param>
/// <returns>The angle, measured in radians.</returns>
public static float Atan2(float y, float x) => MathF.Atan2(y, x);
/// <summary>
/// Returns the hyperbolic arctangent of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The hyperbolic arctangent of <paramref name="x"/>.</returns>
public static float Atanh(float x) => MathF.Atanh(x);
/// <summary>
/// Clamps a number between a minimum and maximum value.
/// </summary>
/// <typeparam name="T">The type of the number.</typeparam>
/// <param name="x">The number to clamp.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <returns>The clamped value.</returns>
public static T Clamp<T>(this T x, T min, T max) where T : INumber<T> => (x < min) ? min : (x > max) ? max : x;
/// <summary>
/// Returns the smallest integral value that is greater than or equal to the specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The smallest integral value that is greater than or equal to <paramref name="x"/>.</returns>
public static float Ceiling(float x) => MathF.Ceiling(x);
/// <summary>
/// Returns a value with the magnitude of <paramref name="x"/> and the sign of <paramref name="y"/>.
/// </summary>
/// <param name="x">The magnitude value.</param>
/// <param name="y">The sign value.</param>
/// <returns>A value with the magnitude of <paramref name="x"/> and the sign of <paramref name="y"/>.</returns>
public static float CopySign(float x, float y) => MathF.CopySign(x, y);
/// <summary>
/// Returns the largest integral value that is less than or equal to the specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The largest integral value that is less than or equal to <paramref name="x"/>.</returns>
public static float Floor(float x) => MathF.Floor(x);
/// <summary>
/// Returns the remainder of the division of two specified numbers.
/// </summary>
/// <param name="x">The dividend.</param>
/// <param name="y">The divisor.</param>
/// <returns>The remainder of the division of <paramref name="x"/> by <paramref name="y"/>.</returns>
public static float IEEERemainder(float x, float y) => MathF.IEEERemainder(x, y);
/// <summary>
/// Returns the natural (base e) logarithm of a specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <param name="y">The base.</param>
/// <returns>The natural logarithm of <paramref name="x"/> with base <paramref name="y"/>.</returns>
public static float Log(float x, float y) => MathF.Log(x, y);
/// <summary>
/// Returns the larger of two numbers.
/// </summary>
/// <typeparam name="T">The type of the numbers.</typeparam>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The larger of <paramref name="x"/> and <paramref name="y"/>.</returns>
public static T Max<T>(T x, T y) where T : INumber<T> => (x > y) ? x : y;
/// <summary>
/// Returns the number whose absolute value is larger.
/// </summary>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The number whose absolute value is larger.</returns>
public static T AbsMax<T>(T x, T y) where T : INumber<T> => (Abs(x) > Abs(y)) ? x : y;
/// <summary>
/// Returns the smaller of two numbers.
/// </summary>
/// <typeparam name="T">The type of the numbers.</typeparam>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The smaller of <paramref name="x"/> and <paramref name="y"/>.</returns>
public static T Min<T>(T x, T y) where T : INumber<T> => (x < y) ? x : y;
/// <summary>
/// Returns the number whose absolute value is smaller.
/// </summary>
/// <param name="x">The first number.</param>
/// <param name="y">The second number.</param>
/// <returns>The number whose absolute value is smaller.</returns>
public static T AbsMin<T>(T x, T y) where T : INumber<T> => (Abs(x) < Abs(y)) ? x : y;
/// <summary>
/// Returns a specified number raised to the specified power.
/// </summary>
/// <param name="x">The number to raise to a power.</param>
/// <param name="y">The power to raise <paramref name="x"/> to.</param>
/// <returns>The number <paramref name="x"/> raised to the power <paramref name="y"/>.</returns>
public static float Pow(float x, float y) => MathF.Pow(x, y);
/// <summary>
/// Rounds a number to a specified number of fractional digits.
/// </summary>
/// <param name="x">The number to round.</param>
/// <param name="digits">The number of fractional digits in the return value.</param>
/// <param name="mode">Specification for how to round <paramref name="x"/> if it is midway between two other numbers.</param>
/// <returns>The number <paramref name="x"/> rounded to <paramref name="digits"/> fractional digits.</returns>
public static float Round(float x, int digits, MidpointRounding mode) => MathF.Round(x, digits, mode);
/// <summary>
/// Returns the square of a number.
/// </summary>
/// <typeparam name="T">The type of the number.</typeparam>
/// <param name="x">The number to square.</param>
/// <returns>The square of <paramref name="x"/>.</returns>
public static T Sqr<T>(T x) where T : INumber<T> => x * x;
/// <summary>
/// Returns the square root of a specified number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The square root of <paramref name="x"/>.</returns>
public static float Sqrt(float x) => MathF.Sqrt(x);
/// <summary>
/// Calculates the integral part of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The integral part of <paramref name="x"/>.</returns>
public static float Truncate(float x) => MathF.Truncate(x);
}

View File

@ -9,5 +9,7 @@ public static class Time
public static TimeSpan Total => _engineTime.Total; public static TimeSpan Total => _engineTime.Total;
public static TimeSpan Elapsed => _engineTime.Elapsed; public static TimeSpan Elapsed => _engineTime.Elapsed;
public static float DeltaTimeFrame => _engineTime.DeltaTimeFrame;
public static void SetTime(EngineTime engineTime) => _engineTime = engineTime; public static void SetTime(EngineTime engineTime) => _engineTime = engineTime;
} }

View File

@ -4,6 +4,7 @@ using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("Position: {Position.ToString(), nq}, Scale: {Scale.ToString(), nq}, Rotation: {Rotation}")]
public class Transform : ITransform public class Transform : ITransform
{ {
public Action<ITransform>? OnPositionChanged { get; set; } = null; public Action<ITransform>? OnPositionChanged { get; set; } = null;

View File

@ -2,18 +2,65 @@ using System;
namespace Syntriax.Engine.Core; namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized}")] /// <summary>
public record Vector2D(float X, float Y) /// Represents a two-dimensional vector.
/// </summary>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
public readonly struct Vector2D(float x, float y)
{ {
/// <summary>
/// The X coordinate of the <see cref="Vector2D"/>.
/// </summary>
public readonly float X = x;
/// <summary>
/// The Y coordinate of the <see cref="Vector2D"/>.
/// </summary>
public readonly float Y = y;
/// <summary>
/// The magnitude (length) of the <see cref="Vector2D"/>.
/// </summary>
public float Magnitude => Length(this); public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector2D"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this); public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// The normalized form of the <see cref="Vector2D"/> (a <see cref="Vector2D"/> with the same direction and a magnitude of 1).
/// </summary>
public Vector2D Normalized => Normalize(this); public Vector2D Normalized => Normalize(this);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing upwards.
/// </summary>
public readonly static Vector2D Up = new(0f, 1f); public readonly static Vector2D Up = new(0f, 1f);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing downwards.
/// </summary>
public readonly static Vector2D Down = new(0f, -1f); public readonly static Vector2D Down = new(0f, -1f);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing leftwards.
/// </summary>
public readonly static Vector2D Left = new(-1f, 0f); public readonly static Vector2D Left = new(-1f, 0f);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing rightwards.
/// </summary>
public readonly static Vector2D Right = new(1f, 0f); public readonly static Vector2D Right = new(1f, 0f);
/// <summary>
/// Represents the zero <see cref="Vector2D"/>.
/// </summary>
public readonly static Vector2D Zero = new(0f, 0f); public readonly static Vector2D Zero = new(0f, 0f);
/// <summary>
/// Represents the <see cref="Vector2D"/> with both components equal to 1.
/// </summary>
public readonly static Vector2D One = new(1f, 1f); public readonly static Vector2D One = new(1f, 1f);
public static Vector2D operator -(Vector2D vector) => new(0f - vector.X, 0f - vector.Y); public static Vector2D operator -(Vector2D vector) => new(0f - vector.X, 0f - vector.Y);
@ -22,25 +69,231 @@ 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 *(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 *(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 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;
/// <summary>
/// Calculates the length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The length of the <see cref="Vector2D"/>.</returns>
public static float Length(Vector2D vector) => MathF.Sqrt(LengthSquared(vector)); public static float Length(Vector2D vector) => MathF.Sqrt(LengthSquared(vector));
/// <summary>
/// Calculates the squared length of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The squared length of the <see cref="Vector2D"/>.</returns>
public static float LengthSquared(Vector2D vector) => vector.X * vector.X + vector.Y * vector.Y; public static float LengthSquared(Vector2D vector) => vector.X * vector.X + vector.Y * vector.Y;
/// <summary>
/// Calculates the distance between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector2D"/>.</param>
/// <param name="to">The end <see cref="Vector2D"/>.</param>
/// <returns>The distance between the two <see cref="Vector2D"/>s.</returns>
public static float Distance(Vector2D from, Vector2D to) => Length(FromTo(from, to)); public static float Distance(Vector2D from, Vector2D to) => Length(FromTo(from, to));
/// <summary>
/// Inverts the direction of the <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The inverted <see cref="Vector2D"/>.</returns>
public static Vector2D Invert(Vector2D vector) => -vector;
/// <summary>
/// Adds two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The sum of the two <see cref="Vector2D"/>s.</returns>
public static Vector2D Add(Vector2D left, Vector2D right) => left + right;
/// <summary>
/// Subtracts one <see cref="Vector2D"/> from another.
/// </summary>
/// <param name="left">The <see cref="Vector2D"/> to subtract from.</param>
/// <param name="right">The <see cref="Vector2D"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Vector2D"/> from the first.</returns>
public static Vector2D Subtract(Vector2D left, Vector2D right) => left - right;
/// <summary>
/// Multiplies a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Vector2D"/> by the scalar value.</returns>
public static Vector2D Multiply(Vector2D vector, float value) => vector * value;
/// <summary>
/// Divides a <see cref="Vector2D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Vector2D"/> by the scalar value.</returns>
public static Vector2D Divide(Vector2D vector, float value) => vector / value;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> with each component's absolute value.</returns>
public static Vector2D Abs(Vector2D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y));
/// <summary>
/// Normalizes the <see cref="Vector2D"/> (creates a unit <see cref="Vector2D"/> with the same direction).
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to normalize.</param>
/// <returns>The normalized <see cref="Vector2D"/>.</returns>
public static Vector2D Normalize(Vector2D vector) => vector / Length(vector); public static Vector2D Normalize(Vector2D vector) => vector / Length(vector);
/// <summary>
/// Reflects a <see cref="Vector2D"/> off a surface with the specified normal.
/// </summary>
/// <param name="vector">The incident <see cref="Vector2D"/>.</param>
/// <param name="normal">The normal <see cref="Vector2D"/> of the surface.</param>
/// <returns>The reflected <see cref="Vector2D"/>.</returns>
public static Vector2D Reflect(Vector2D vector, Vector2D normal) => vector - 2f * Dot(vector, normal) * normal; public static Vector2D Reflect(Vector2D vector, Vector2D normal) => vector - 2f * Dot(vector, normal) * normal;
/// <summary>
/// Calculates the <see cref="Vector2D"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector2D"/> from the starting point to the ending point.</returns>
public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from; public static Vector2D FromTo(Vector2D from, Vector2D to) => to - from;
/// <summary>
/// Scales a <see cref="Vector2D"/> by another <see cref="Vector2D"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to scale.</param>
/// <param name="scale">The <see cref="Vector2D"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector2D"/>.</returns>
public static Vector2D Scale(Vector2D vector, Vector2D scale) => new(vector.X * scale.X, vector.Y * scale.Y); public static Vector2D Scale(Vector2D vector, Vector2D scale) => new(vector.X * scale.X, vector.Y * scale.Y);
/// <summary>
/// Calculates a perpendicular <see cref="Vector2D"/> to the given <see cref="Vector2D"/>.
/// </summary>
/// <param name="vector">The input <see cref="Vector2D"/>.</param>
/// <returns>A <see cref="Vector2D"/> perpendicular to the input <see cref="Vector2D"/>.</returns>
public static Vector2D Perpendicular(Vector2D vector) => new(-vector.Y, vector.X);
/// <summary>
/// Rotates a <see cref="Vector2D"/> by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to rotate.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector2D"/>.</returns>
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);
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the minimum components from both input <see cref="Vector2D"/>s.</returns>
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 Min(Vector2D left, Vector2D right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The <see cref="Vector2D"/> containing the maximum components from both input <see cref="Vector2D"/>s.</returns>
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 Max(Vector2D left, Vector2D right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y);
/// <summary>
/// Clamps each component of a <see cref="Vector2D"/> between the corresponding component of two other <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to clamp.</param>
/// <param name="min">The <see cref="Vector2D"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector2D"/> representing the maximum values for each component.</param>
/// <returns>A <see cref="Vector2D"/> with each component clamped between the corresponding components of the min and max <see cref="Vector2D"/>s.</returns>
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)); 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));
/// <summary>
/// Performs linear interpolation between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector2D"/> (t = 0).</param>
/// <param name="to">The ending <see cref="Vector2D"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Vector2D"/>.</returns>
public static Vector2D Lerp(Vector2D from, Vector2D to, float t) => from + FromTo(from, to) * t; public static Vector2D Lerp(Vector2D from, Vector2D to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Calculates the cross product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The cross product of the two <see cref="Vector2D"/>s.</returns>
public static float Cross(Vector2D left, Vector2D right) => left.X * right.Y - left.Y * right.X; public static float Cross(Vector2D left, Vector2D right) => left.X * right.Y - left.Y * right.X;
/// <summary>
/// Calculates the angle between two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The angle between the two <see cref="Vector2D"/>s in radians.</returns>
public static float Angle(Vector2D left, Vector2D right) => MathF.Acos(Dot(left, right) / (Length(left) * Length(right))); public static float Angle(Vector2D left, Vector2D right) => MathF.Acos(Dot(left, right) / (Length(left) * Length(right)));
/// <summary>
/// Calculates the dot product of two <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <returns>The dot product of the two <see cref="Vector2D"/>s.</returns>
public static float Dot(Vector2D left, Vector2D right) => left.X * right.X + left.Y * right.Y; public static float Dot(Vector2D left, Vector2D right) => left.X * right.X + left.Y * right.Y;
/// <summary>
/// Determines the orientation of three points represented by <see cref="Vector2D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="middle">The second <see cref="Vector2D"/>.</param>
/// <param name="right">The third <see cref="Vector2D"/>.</param>
/// <returns>
/// <para>0 - Collinear.</para>
/// <para>1 - Clockwise.</para>
/// <para>2 - Counterclockwise.</para>
/// </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;
}
/// <summary>
/// Checks if two <see cref="Vector2D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Vector2D"/>.</param>
/// <param name="right">The second <see cref="Vector2D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Vector2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Vector2D left, Vector2D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon);
/// <summary>
/// Converts the <see cref="Vector2D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector2D"/>.</returns>
public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})"; public override string ToString() => $"{nameof(Vector2D)}({X}, {Y})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector2D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector2D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector2D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector2D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y);
/// <summary>
/// Generates a hash code for the <see cref="Vector2D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector2D"/>.</returns>
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,19 @@
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a <see cref="ICollider2D"/> with the shape of a <see cref="Circle"/>.
/// </summary>
public interface ICircleCollider2D : ICollider2D
{
/// <summary>
/// The local <see cref="Circle"/> shape of the <see cref="ICollider2D"/>.
/// </summary>
Circle CircleLocal { get; set; }
/// <summary>
/// The world space representation of the <see cref="Circle"/> shape.
/// </summary>
Circle CircleWorld { get; }
}

View File

@ -0,0 +1,41 @@
using System;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D collider.
/// </summary>
public interface ICollider2D : IBehaviour, IAssignableTransform
{
/// <summary>
/// Event triggered when a collision is detected.
/// </summary>
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionDetected { get; set; }
/// <summary>
/// Event triggered when a collision is resolved.
/// </summary>
Action<ICollider2D, CollisionDetectionInformation>? OnCollisionResolved { get; set; }
/// <summary>
/// Event triggered when another <see cref="ICollider2D"/> triggers this <see cref="ICollider2D"/>.
/// </summary>
Action<ICollider2D, ICollider2D>? OnTriggered { get; set; }
/// <summary>
/// The <see cref="IRigidBody2D"/> associated with the <see cref="ICollider2D"/>.
/// </summary>
IRigidBody2D? RigidBody2D { get; }
/// <summary>
/// The value indicating whether the <see cref="ICollider2D"/> is a trigger.
/// </summary>
bool IsTrigger { get; set; }
/// <summary>
/// Recalculates <see cref="ICollider2D"/> properties.
/// </summary>
void Recalculate();
}

View File

@ -0,0 +1,20 @@
using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D;
/// <summary>
/// Represents a 2D collision detector.
/// </summary>
public interface ICollisionDetector2D
{
/// <summary>
/// Attempts to detect a collision between two <see cref="ICollider2D"/>s.
/// </summary>
/// <typeparam name="T1">Type of the first <see cref="ICollider2D"/>.</typeparam>
/// <typeparam name="T2">Type of the second <see cref="ICollider2D"/>.</typeparam>
/// <param name="left">The first <see cref="ICollider2D"/>.</param>
/// <param name="right">The second <see cref="ICollider2D"/>.</param>
/// <param name="collisionInformation">Information about the collision.</param>
/// <returns><see cref="true"/> if a collision is detected, otherwise <see cref="false"/>.</returns>
bool TryDetect<T1, T2>(T1 left, T2 right, out CollisionDetectionInformation collisionInformation) where T1 : ICollider2D where T2 : ICollider2D;
}

View File

@ -0,0 +1,13 @@
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D collision resolver.
/// </summary>
public interface ICollisionResolver2D
{
/// <summary>
/// Resolves collisions based on collision detection information provided.
/// </summary>
/// <param name="collisionInformation">Information about the collision.</param>
void Resolve(CollisionDetectionInformation collisionInformation);
}

View File

@ -0,0 +1,18 @@
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D physics engine.
/// </summary>
public interface IPhysicsEngine2D
{
/// <summary>
/// The number of iterations the <see cref="IPhysicsEngine2D"/> performs per step.
/// </summary>
int IterationPerStep { get; set; }
/// <summary>
/// Advances the physics simulation by the specified time.
/// </summary>
/// <param name="deltaTime">The time step.</param>
void Step(float deltaTime);
}

View File

@ -0,0 +1,17 @@
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D physics object's responsive attributes.
/// </summary>
public interface IPhysicsMaterial2D
{
/// <summary>
/// The friction coefficient of the physics object.
/// </summary>
float Friction { get; }
/// <summary>
/// The restitution (bounciness) coefficient of the physics object.
/// </summary>
float Restitution { get; }
}

View File

@ -0,0 +1,35 @@
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a 2D rigid body in the engine.
/// </summary>
public interface IRigidBody2D : IBehaviour, IAssignableTransform
{
/// <summary>
/// The physics material of the <see cref="IRigidBody2D"/>.
/// </summary>
IPhysicsMaterial2D Material { get; set; }
/// <summary>
/// The velocity of the <see cref="IRigidBody2D"/>.
/// </summary>
Vector2D Velocity { get; set; }
/// <summary>
/// The angular velocity (rotation rate) of the <see cref="IRigidBody2D"/>.
/// </summary>
float AngularVelocity { get; set; }
/// <summary>
/// The mass of the <see cref="IRigidBody2D"/>.
/// </summary>
float Mass { get; set; }
/// <summary>
/// The value indicating whether the <see cref="IRigidBody2D"/> is static/immovable.
/// </summary>
bool IsStatic { get; set; }
}

View File

@ -0,0 +1,20 @@
using Syntriax.Engine.Physics2D.Primitives;
namespace Syntriax.Engine.Physics2D.Abstract;
/// <summary>
/// Represents a <see cref="ICollider2D"/> with a custom <see cref="Shape"/>.
/// </summary>
public interface IShapeCollider2D : ICollider2D
{
/// <summary>
/// Gets or sets the local <see cref="Shape"/> of the <see cref="ICollider2D"/>.
/// </summary>
Shape ShapeLocal { get; set; }
/// <summary>
/// Gets the world space representation of the <see cref="Shape"/>.
/// </summary>
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,138 @@
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 readonly ICollisionDetector2D collisionDetector = null!;
private readonly ICollisionResolver2D collisionResolver = null!;
public int IterationPerStep { 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 / IterationPerStep;
for (int iterationIndex = 0; iterationIndex < IterationPerStep; 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 || !rigidBody.IsActive)
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);
}
public PhysicsEngine2D()
{
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2D(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
}

View File

@ -0,0 +1,150 @@
using System;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Physics2D.Abstract;
namespace Syntriax.Engine.Physics2D;
public class PhysicsEngine2DCacher : IPhysicsEngine2D, IAssignableGameManager
{
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableGameManager>? OnGameManagerAssigned { get; set; } = null;
private int _iterationPerStep = 1;
protected readonly ICollisionDetector2D collisionDetector = null!;
protected readonly ICollisionResolver2D collisionResolver = null!;
protected BehaviourCacher<IRigidBody2D> rigidBodyCacher = new();
protected BehaviourCacher<ICollider2D> colliderCacher = new();
public int IterationPerStep { get => _iterationPerStep; set => _iterationPerStep = value < 1 ? 1 : value; }
public IGameManager GameManager { get; private set; } = null!;
public void Step(float deltaTime)
{
float intervalDeltaTime = deltaTime / IterationPerStep;
for (int iterationIndex = 0; iterationIndex < IterationPerStep; iterationIndex++)
{
// Can Parallel
foreach (var rigidBody in rigidBodyCacher)
StepRigidBody(rigidBody, intervalDeltaTime);
// Can Parallel
foreach (var collider in colliderCacher)
collider.Recalculate();
// Can Parallel
for (int x = 0; x < colliderCacher.Behaviours.Count; x++)
{
ICollider2D? colliderX = colliderCacher.Behaviours[x];
if (!colliderX.IsActive)
return;
for (int y = x + 1; y < colliderCacher.Behaviours.Count; y++)
{
ICollider2D? colliderY = colliderCacher.Behaviours[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 || !rigidBody.IsActive)
return;
rigidBody.Transform.Position += rigidBody.Velocity * intervalDeltaTime;
rigidBody.Transform.Rotation += rigidBody.AngularVelocity * intervalDeltaTime;
}
public bool Assign(IGameManager gameManager)
{
if (GameManager is not null)
return false;
colliderCacher.Assign(gameManager);
rigidBodyCacher.Assign(gameManager);
GameManager = gameManager;
OnGameManagerAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (GameManager is null)
return false;
colliderCacher.Unassign();
rigidBodyCacher.Unassign();
GameManager = null!;
OnUnassigned?.Invoke(this);
return true;
}
public PhysicsEngine2DCacher()
{
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2DCacher(IGameManager gameManager)
{
Assign(gameManager);
collisionDetector = new CollisionDetector2D();
collisionResolver = new CollisionResolver2D();
}
public PhysicsEngine2DCacher(IGameManager gameManager, ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
Assign(gameManager);
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
public PhysicsEngine2DCacher(ICollisionDetector2D collisionDetector, ICollisionResolver2D collisionResolver)
{
this.collisionDetector = collisionDetector;
this.collisionResolver = collisionResolver;
}
}

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,97 @@
using System.Collections.Generic;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents an Axis-Aligned Bounding Box (AABB) in 2D space.
/// </summary>
/// <param name="lowerBoundary">The lower boundary of the <see cref="AABB"/>.</param>
/// <param name="upperBoundary">The upper boundary of the <see cref="AABB"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="AABB"/> struct with the specified lower and upper boundaries.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("LowerBoundary: {LowerBoundary.ToString(), nq}, UpperBoundary: {UpperBoundary.ToString(), nq}")]
public readonly struct AABB(Vector2D lowerBoundary, Vector2D upperBoundary)
{
/// <summary>
/// The lower boundary of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D LowerBoundary = lowerBoundary;
/// <summary>
/// The upper boundary of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D UpperBoundary = upperBoundary;
/// <summary>
/// Gets the center point of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D Center => (LowerBoundary + UpperBoundary) * .5f;
/// <summary>
/// Gets the size of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D Size => LowerBoundary.FromTo(UpperBoundary).Abs();
/// <summary>
/// Gets half the size of the <see cref="AABB"/>.
/// </summary>
public readonly Vector2D SizeHalf => Size * .5f;
/// <summary>
/// Creates an <see cref="AABB"/> from a collection of <see cref="Vector2D"/>s.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
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);
}
/// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB"/>.</param>
/// <param name="right">The second <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(AABB left, AABB right)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary);
}
/// <summary>
/// Provides extension methods for the <see cref="AABB"/> struct.
/// </summary>
public static class AABBExtensions
{
/// <summary>
/// Converts a collection of <see cref="Vector2D"/>s to an <see cref="AABB"/>.
/// </summary>
/// <param name="vectors">The collection of <see cref="Vector2D"/>s.</param>
/// <returns>An <see cref="AABB"/> that bounds all the <see cref="Vector2D"/>s.</returns>
public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);
/// <summary>
/// Checks if two <see cref="AABB"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="AABB"/>.</param>
/// <param name="right">The second <see cref="AABB"/>.</param>
/// <returns><see cref="true"/> if the <see cref="AABB"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(this AABB left, AABB right) => AABB.ApproximatelyEquals(left, right);
}

View File

@ -0,0 +1,115 @@
using System.Diagnostics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a 2D circle.
/// </summary>
/// <param name="center">The center of the circle.</param>
/// <param name="radius">The radius of the circle.</param>
/// <remarks>
/// Initializes a new instance of the Circle struct with the specified center and radius.
/// </remarks>
[DebuggerDisplay("Center: {Center.ToString(),nq}, Radius: {Radius}")]
public readonly struct Circle(Vector2D center, float radius)
{
/// <summary>
/// The center of the circle.
/// </summary>
public readonly Vector2D Center = center;
/// <summary>
/// The radius of the <see cref="Circle"/>.
/// </summary>
public readonly float Radius = radius;
/// <summary>
/// Gets the squared radius of the <see cref="Circle"/>.
/// </summary>
public readonly float RadiusSquared => Radius * Radius;
/// <summary>
/// Gets the diameter of the <see cref="Circle"/>.
/// </summary>
public readonly float Diameter => 2f * Radius;
/// <summary>
/// A predefined unit <see cref="Circle"/> with a center at the origin and a radius of 1.
/// </summary>
public static readonly Circle UnitCircle = new(Vector2D.Zero, 1f);
/// <summary>
/// Sets the center of the <see cref="Circle"/>.
/// </summary>
public static Circle SetCenter(Circle circle, Vector2D center) => new(center, circle.Radius);
/// <summary>
/// Sets the radius of the <see cref="Circle"/>.
/// </summary>
public static Circle SetRadius(Circle circle, float radius) => new(circle.Center, radius);
/// <summary>
/// Displaces the <see cref="Circle"/> by the specified <see cref="Vector2D"/>.
/// </summary>
public static Circle Displace(Circle circle, Vector2D displaceVector) => new(circle.Center + displaceVector, circle.Radius);
/// <summary>
/// Projects the <see cref="Circle"/> onto the specified <see cref="Vector2D"/>.
/// </summary>
public static Projection Project(Circle circle, Vector2D projectionVector)
{
float projectedCenter = circle.Center.Dot(projectionVector);
return new(projectedCenter - circle.Radius, projectedCenter + circle.Radius);
}
/// <summary>
/// Transforms the <see cref="Circle"/> by the specified <see cref="ITransform"/>.
/// </summary>
public static Circle TransformCircle(ITransform transform, Circle circle)
=> new(transform.TransformVector2D(circle.Center), circle.Radius * transform.Scale.Magnitude);
/// <summary>
/// Checks if two <see cref="Circle"/>s are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(Circle left, Circle right)
=> left.Center.ApproximatelyEquals(right.Center) && left.Radius.ApproximatelyEquals(right.Radius);
}
/// <summary>
/// Provides extension methods for the <see cref="Circle"/> struct.
/// </summary>
public static class CircleExtensions
{
/// <summary>
/// Sets the center of the <see cref="Circle"/>.
/// </summary>
public static Circle SetCenter(this Circle circle, Vector2D center) => Circle.SetCenter(circle, center);
/// <summary>
/// Sets the radius of the <see cref="Circle"/>.
/// </summary>
public static Circle SetRadius(this Circle circle, float radius) => Circle.SetRadius(circle, radius);
/// <summary>
/// Moves the <see cref="Circle"/> by the specified <see cref="Vector2D"/>.
/// </summary>
public static Circle Displace(this Circle circle, Vector2D displaceVector) => Circle.Displace(circle, displaceVector);
/// <summary>
/// Projects the <see cref="Circle"/> onto the specified <see cref="Vector2D"/>.
/// </summary>
public static Projection ToProjection(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector);
/// <summary>
/// Transforms the <see cref="Circle"/> by the specified <see cref="ITransform"/>.
/// </summary>
public static Circle TransformCircle(this ITransform transform, Circle circle) => Circle.TransformCircle(transform, circle);
/// <summary>
/// Checks if two <see cref="Circle"/>s are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(this Circle left, Circle right) => Circle.ApproximatelyEquals(left, right);
}

View File

@ -0,0 +1,209 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a 2D line segment defined by two endpoints.
/// </summary>
/// <remarks>
/// Initializes a new instance of the Line struct with the specified endpoints.
/// </remarks>
/// <param name="from">The starting point of the <see cref="Line"/> segment.</param>
/// <param name="to">The ending point of the <see cref="Line"/> segment.</param>
[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)
{
/// <summary>
/// The starting point of the <see cref="Line"/> segment.
/// </summary>
public readonly Vector2D From = from;
/// <summary>
/// The ending point of the <see cref="Line"/> segment.
/// </summary>
public readonly Vector2D To = to;
/// <summary>
/// The reversed <see cref="Line"/> segment.
/// </summary>
public readonly Line Reversed => new(To, From);
/// <summary>
/// The normalized direction <see cref="Vector2D"/> of the <see cref="Line"/> segment.
/// </summary>
public readonly Vector2D Direction => From.FromTo(To).Normalize();
/// <summary>
/// The length of the <see cref="Line"/> segment.
/// </summary>
public readonly float Length => From.FromTo(To).Length();
/// <summary>
/// The squared length of the <see cref="Line"/> segment.
/// </summary>
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
/// <summary>
/// The equation of the <see cref="Line"/> defined by this <see cref="Line"/> segment.
/// </summary>
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);
}
/// <summary>
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line"/>.
/// </summary>
public static bool Intersects(Line line, Vector2D point)
=> LineEquation.Resolve(GetLineEquation(line), point.X).ApproximatelyEquals(point.Y);
/// <summary>
/// Calculates the parameter 't' representing the point's position on the <see cref="Line"/> segment.
/// </summary>
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;
}
/// <summary>
/// Checks if the <see cref="Line"/> segment intersects with another <see cref="Line"/> segment.
/// </summary>
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;
}
/// <summary>
/// Checks if the point lies within the <see cref="Line"/> segment.
/// </summary>
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;
}
/// <summary>
/// Determines whether two <see cref="Line"/> segments intersect.
/// </summary>
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;
}
/// <summary>
/// Finds the point of intersection between two <see cref="Line"/> segments.
/// </summary>
public static Vector2D IntersectionPoint(Line left, Line right)
=> Vector2D.Lerp(left.From, left.To, IntersectionParameterT(left, right));
/// <summary>
/// Calculates the parameter 't' representing the intersection point's position on the <see cref="Line"/> segment.
/// </summary>
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;
}
/// <summary>
/// Linearly interpolates between the two endpoints of the <see cref="Line"/> segment using parameter 't'.
/// </summary>
public static Vector2D Lerp(Line line, float t)
=> new(
line.From.X + (line.To.X - line.From.X) * t,
line.From.Y + (line.To.Y - line.From.Y) * t
);
/// <summary>
/// Calculates the closest point on the <see cref="Line"/> segment to the specified point.
/// </summary>
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);
}
/// <summary>
/// Checks if two <see cref="Line"/> segments are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(Line left, Line right)
=> left.From.ApproximatelyEquals(right.From) && left.To.ApproximatelyEquals(right.To);
}
/// <summary>
/// Provides extension methods for the Line struct.
/// </summary>
public static class LineExtensions
{
/// <summary>
/// Checks if two <see cref="Line"/>s are approximately equal.
/// </summary>
public static bool ApproximatelyEquals(this Line left, Line right) => Line.ApproximatelyEquals(left, right);
}

View File

@ -0,0 +1,64 @@
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a line equation in the form y = mx + b.
/// </summary>
/// <param name="slope">The slope of the line.</param>
/// <param name="offsetY">The y-intercept of the line.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="LineEquation"/> struct with the specified slope and y-intercept.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
public readonly struct LineEquation(float slope, float offsetY)
{
/// <summary>
/// The slope of the line equation.
/// </summary>
public readonly float Slope = slope;
/// <summary>
/// The y-intercept of the line equation.
/// </summary>
public readonly float OffsetY = offsetY;
/// <summary>
/// Resolves the y-coordinate for a given x-coordinate using the line equation.
/// </summary>
/// <param name="lineEquation">The line equation to resolve.</param>
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</param>
/// <returns>The y-coordinate resolved using the line equation.</returns>
public static float Resolve(LineEquation lineEquation, float x) => lineEquation.Slope * x + lineEquation.OffsetY; // y = mx + b
/// <summary>
/// Checks if two line equations are approximately equal.
/// </summary>
/// <param name="left">The first line equation to compare.</param>
/// <param name="right">The second line equation to compare.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(LineEquation left, LineEquation right)
=> left.Slope.ApproximatelyEquals(right.Slope) && left.OffsetY.ApproximatelyEquals(right.OffsetY);
}
/// <summary>
/// Provides extension methods for the LineEquation struct.
/// </summary>
public static class LineEquationExtensions
{
/// <summary>
/// Resolves the y-coordinate for a given x-coordinate using the line equation.
/// </summary>
/// <param name="lineEquation">The line equation to resolve.</param>
/// <param name="x">The x-coordinate for which to resolve the y-coordinate.</param>
/// <returns>The y-coordinate resolved using the line equation.</returns>
public static float Resolve(this LineEquation lineEquation, float x) => LineEquation.Resolve(lineEquation, x);
/// <summary>
/// Checks if two line equations are approximately equal.
/// </summary>
/// <param name="left">The first line equation to compare.</param>
/// <param name="right">The second line equation to compare.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(this LineEquation left, LineEquation right) => LineEquation.ApproximatelyEquals(left, right);
}

View File

@ -0,0 +1,96 @@
namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a range of values along a single axis.
/// </summary>
/// <param name="min">The minimum value of the projection.</param>
/// <param name="max">The maximum value of the projection.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Projection"/> struct with the specified minimum and maximum values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
public readonly struct Projection(float min, float max)
{
/// <summary>
/// Gets the minimum value of the projection.
/// </summary>
public readonly float Min = min;
/// <summary>
/// Gets the maximum value of the projection.
/// </summary>
public readonly float Max = max;
/// <summary>
/// Checks if two projections overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(Projection left, Projection right) => Overlaps(left, right, out var _);
/// <summary>
/// Checks if two projections overlap and calculates the depth of the overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <param name="depth">The depth of the overlap, if any.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
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;
}
}
/// <summary>
/// Provides extension methods for the <see cref="Projection"/> struct.
/// </summary>
public static class ProjectionExtensions
{
/// <summary>
/// Checks if two projections overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(this Projection left, Projection right) => Projection.Overlaps(left, right);
/// <summary>
/// Checks if two projections overlap and calculates the depth of the overlap.
/// </summary>
/// <param name="left">The first projection to check.</param>
/// <param name="right">The second projection to check.</param>
/// <param name="depth">The depth of the overlap, if any.</param>
/// <returns><see cref="true"/> if the projections overlap; otherwise, <see cref="false"/>.</returns>
public static bool Overlaps(this Projection left, Projection right, out float depth) => Projection.Overlaps(left, right, out depth);
}

View File

@ -0,0 +1,294 @@
using System.Collections;
using System.Collections.Generic;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Physics2D.Primitives;
/// <summary>
/// Represents a shape defined by a collection of vertices.
/// </summary>
/// <param name="vertices">The vertices of the shape.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Shape"/> struct with the specified vertices.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")]
public readonly struct Shape(List<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);
private readonly List<Vector2D> _verticesList = vertices;
/// <summary>
/// Gets the vertices of the shape.
/// </summary>
public IReadOnlyList<Vector2D> Vertices => _verticesList;
/// <summary>
/// The vertex at the specified index.
/// </summary>
/// <param name="index">The zero-based index of the vertex to get or set.</param>
/// <returns>The vertex at the specified index.</returns>
public Vector2D this[System.Index index] => Vertices[index];
/// <summary>
/// Returns a copy of the current shape.
/// </summary>
/// <param name="shape">The shape to copy.</param>
/// <returns>A copy of the input shape.</returns>
public static Shape CreateCopy(Shape shape) => new(new List<Vector2D>(shape.Vertices));
/// <summary>
/// Creates a regular polygon (ngon) with the specified number of vertices.
/// </summary>
/// <param name="vertexCount">The number of vertices in the polygon.</param>
/// <returns>A regular polygon with the specified number of vertices.</returns>
public static Shape CreateNgon(int vertexCount) => CreateNgon(vertexCount, Vector2D.Up);
/// <summary>
/// Creates a regular polygon (ngon) with the specified number of vertices and a rotation position.
/// </summary>
/// <param name="vertexCount">The number of vertices in the polygon.</param>
/// <param name="positionToRotate">The position to use for rotation.</param>
/// <returns>A regular polygon with the specified number of vertices and rotation position.</returns>
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);
}
/// <summary>
/// Gets the super triangle that encloses the given shape.
/// </summary>
/// <param name="shape">The shape to enclose.</param>
/// <returns>The super triangle that encloses the given shape.</returns>
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);
}
/// <summary>
/// Gets the lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <param name="lines">The list to populate with lines.</param>
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]));
}
/// <summary>
/// Gets a list of lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <returns>A list of lines that form the edges of the shape.</returns>
public static List<Line> GetLines(Shape shape)
{
List<Line> lines = new(shape.Vertices.Count - 1);
GetLines(shape, lines);
return lines;
}
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <param name="list">The list to populate with projected values.</param>
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]));
}
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <returns>The projection of the shape onto the vector.</returns>
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);
}
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="shape">The shape to transform.</param>
/// <param name="transform">The transform to apply.</param>
/// <returns>The transformed shape.</returns>
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);
}
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="from">The shape to transform.</param>
/// <param name="transform">The transform to apply.</param>
/// <param name="to">The transformed shape.</param>
public static void TransformShape(Shape from, ITransform transform, ref Shape to)
{
to._verticesList.Clear();
int count = from._verticesList.Count;
for (int i = 0; i < count; i++)
to._verticesList.Add(transform.TransformVector2D(from[i]));
}
/// <summary>
/// Determines whether two shapes are approximately equal.
/// </summary>
/// <param name="left">The first shape to compare.</param>
/// <param name="right">The second shape to compare.</param>
/// <returns><c>true</c> if the shapes are approximately equal; otherwise, <c>false</c>.</returns>
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;
}
/// <inheritdoc/>
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
}
/// <summary>
/// Provides extension methods for the <see cref="Shape"/> struct.
/// </summary>
public static class ShapeExtensions
{
/// <summary>
/// Creates a copy of the shape.
/// </summary>
/// <param name="shape">The shape to copy.</param>
/// <returns>A copy of the input shape.</returns>
public static Shape CreateCopy(this Shape shape) => Shape.CreateCopy(shape);
/// <summary>
/// Gets the super triangle that encloses the shape.
/// </summary>
/// <param name="shape">The shape to enclose.</param>
/// <returns>The super triangle that encloses the shape.</returns>
public static Triangle ToSuperTriangle(this Shape shape) => Shape.GetSuperTriangle(shape);
/// <summary>
/// Gets the lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <param name="lines">The list to populate with lines.</param>
public static void ToLines(this Shape shape, IList<Line> lines) => Shape.GetLines(shape, lines);
/// <summary>
/// Gets a list of lines that form the edges of the shape.
/// </summary>
/// <param name="shape">The shape to get lines from.</param>
/// <returns>A list of lines that form the edges of the shape.</returns>
public static List<Line> ToLines(this Shape shape) => Shape.GetLines(shape);
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <param name="list">The list to populate with projected values.</param>
public static void ToProjection(this Shape shape, Vector2D projectionVector, IList<float> list) => Shape.Project(shape, projectionVector, list);
/// <summary>
/// Projects the shape onto a vector.
/// </summary>
/// <param name="shape">The shape to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <returns>The projection of the shape onto the vector.</returns>
public static Projection ToProjection(this Shape shape, Vector2D projectionVector) => Shape.Project(shape, projectionVector);
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="transform">The transform to apply.</param>
/// <param name="shape">The shape to transform.</param>
/// <returns>The transformed shape.</returns>
public static Shape TransformShape(this ITransform transform, Shape shape) => Shape.TransformShape(shape, transform);
/// <summary>
/// Transforms the shape using the specified transform.
/// </summary>
/// <param name="transform">The transform to apply.</param>
/// <param name="from">The shape to transform.</param>
/// <param name="to">The transformed shape.</param>
public static void TransformShape(this ITransform transform, Shape from, ref Shape to) => Shape.TransformShape(from, transform, ref to);
/// <summary>
/// Determines whether two shapes are approximately equal.
/// </summary>
/// <param name="left">The first shape to compare.</param>
/// <param name="right">The second shape to compare.</param>
/// <returns><c>true</c> if the shapes are approximately equal; otherwise, <c>false</c>.</returns>
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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Input", "Engine.Input\Engine.Input.csproj", "{12149E55-1EE8-45B4-A82E-15BA981B0C6A}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Input", "Engine.Input\Engine.Input.csproj", "{12149E55-1EE8-45B4-A82E-15BA981B0C6A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Physics2D", "Engine.Physics2D\Engine.Physics2D.csproj", "{3B3C3332-07E3-4A00-9898-0A5410BCB08C}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{12149E55-1EE8-45B4-A82E-15BA981B0C6A}.Release|Any CPU.Build.0 = 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 EndGlobalSection
EndGlobal EndGlobal

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# Work In Progress
This engine is still in development but the implemented features include:
- Modular Systems
- Behaviour System
- 2D Physics Engine(**Not Fully Completed, but usable**)
- Rigid Body Simulations
- Collision Detection (Convex Shape & Circle)
- Collision Resolution (**Not Fully Completed**)
- Vector2D, AABB, Circle, Line, LineEquation, Projection & Shape Data Types
- General Math
---
**A detailed README file will be written in the future. If you want to check out how to use this, please checkout this example Pong game made using this engine on top of [MonoGame](https://monogame.net/) from the link bellow.**
[Pong Source Code](https://git.syntriax.com/Syntriax/Engine-Pong)