377 Commits

Author SHA1 Message Date
ce3cc895f4 fix: tween reset caused issues 2025-05-30 23:44:59 +03:00
76ad60fad3 perf: tween pooling 2025-05-30 20:55:09 +03:00
1b123a3cc0 chore: get init to only get for events 2025-05-30 20:13:32 +03:00
0d61735ae5 fix: button input methods having wrong signatures 2025-05-30 19:57:37 +03:00
b5140a94de Amend Me 2025-05-30 18:37:35 +03:00
846aa75dd5 perf: regular events to custom events 2025-05-30 13:06:11 +03:00
b1b5af94d3 perf!: behaviour controller memory allocation issues fixed by removing the enumerable interface 2025-05-30 13:04:09 +03:00
b0f8b0dad6 refactor: behaviour collector Count and indexer accessors added 2025-05-29 23:17:11 +03:00
67d7f401b8 refactor: memory leaks caused by behaviour collectors fixed 2025-05-29 22:34:01 +03:00
9bf17cc191 perf: physics engine memory leaks fixed 2025-05-29 22:33:47 +03:00
bf8fbebae3 perf: DelegateExtensions.InvokeSafe marked obsolete for memory allocation reasons, soon to be removed 2025-05-29 21:48:08 +03:00
1b0f25e854 perf: update manager list precache 2025-05-29 10:30:30 +03:00
61a7f685c1 perf: delegate InvokeSafe method allocations are lowered 2025-05-29 00:16:00 +03:00
feb2a05aa3 feat: additive transform tweens added 2025-05-28 16:55:48 +03:00
cd30047e4a feat: GetOrAddBehaviour with fallback type added 2025-05-28 16:55:38 +03:00
a3b03efd47 feat: IPhysicsEngine2D.StepIndividual method for individual object simulation 2025-05-27 15:54:07 +03:00
4213b3f8b5 fix: fixed an issue where when there is an inactive collider in the universe messing up the physics engine 2025-05-27 13:52:53 +03:00
d3fb612904 feat: extension methods for parent & children behaviour list search 2025-05-27 13:36:42 +03:00
8f8558a262 docs: added performance warnings to find methods 2025-05-25 13:56:59 +03:00
2df41e1881 docs: added universe and universe object extension documentation comments 2025-05-25 13:28:36 +03:00
114fa82b9d feat: Find & FindRequired for general type search 2025-05-25 12:59:37 +03:00
bcce427376 feat: added GetUniverseObject/InChildren/InParent to UniverseObjectExtensions 2025-05-25 12:20:37 +03:00
6a750f8ce0 refactor: organized extension methods 2025-05-25 12:05:02 +03:00
3e02ee7b6f refactor: changed concrete list arguments to interface list arguments 2025-05-25 11:43:05 +03:00
6b9020bd24 fix: update manager not calling first frame methods once 2025-05-24 19:56:22 +03:00
832514ba7d docs: added documentation to draw & update interfaces 2025-05-24 13:59:36 +03:00
877a004a13 refactor: added pre, regular & post physics update interfaces 2025-05-24 13:59:07 +03:00
b1970d93f9 refactor: draw & update managers to use active & sorted by priority collector 2025-05-23 22:39:32 +03:00
e7bd924494 refactor: update & draw calls have been refactored into systems 2025-05-22 23:51:08 +03:00
37b87f0f85 feat: added post, regular & post events for Update and Draw 2025-05-22 23:10:47 +03:00
3b6a93d37a refactor: behaviour factory universe object parameter removed 2025-05-18 00:38:49 +03:00
0bf38234c6 feat: async serializer methods 2025-05-04 19:00:54 +03:00
ed6969c16a feat: progression trackers added 2025-05-04 18:57:26 +03:00
b0b421151f refactor: TypeFactory ReloadTypes made multithread friendly 2025-05-04 18:57:01 +03:00
41c5def097 refactor: renamed DelegateHelpers to DelegateExtensions 2025-05-04 18:52:47 +03:00
fbbdfb07fa chore: bumped .netcore version to 9 2025-05-04 18:46:21 +03:00
bf283d804c chore: updated Shape2D tween to look more aesthetic by choosing more linearly distributed vertices instead of the last vertex 2025-05-03 23:31:06 +03:00
063ea08707 feat: added RoundToInt RoundMode for midway values 2025-05-03 23:30:02 +03:00
fd11a94ddf refactor: easings have a singleton base so we don't create an unnecessary instance or cache everytime 2025-05-03 22:38:40 +03:00
be2295b92d feat: added engine member tween extensions 2025-05-03 22:23:52 +03:00
a93e55619c refactor: extracted interface from TweenManager 2025-05-03 22:23:28 +03:00
48ae24af47 chore: added safeguard value clamps for color operations 2025-05-03 22:21:58 +03:00
1366a417f1 feat: added Math.OneMinus method 2025-05-03 22:16:14 +03:00
4bfe98852c refactor: tween extensions method spacings fixed 2025-05-03 20:46:20 +03:00
98edbe1af5 chore: disabled all ImplicitUsings 2025-05-03 20:41:26 +03:00
3725a3b0fd feat: added preserver class & method to preserve assembly loading 2025-05-03 20:22:35 +03:00
f43ab36742 feat: added loggers 2025-05-03 17:01:58 +03:00
c7aafd85bc refactor: renamed assert helper and moved to Debug subfolder 2025-05-03 15:37:52 +03:00
5de08b8fe4 refactor: primitives now use Core.Math for math 2025-05-02 18:57:42 +03:00
16e4077d40 chore: HSV hue is normalized between 0 and 1 2025-05-02 18:54:08 +03:00
fc3c1ed1f9 refactor: Shape2D converted into a class as it has a reference type 2025-05-02 12:46:23 +03:00
b100b5c2fe feat: added color primitives 2025-05-02 00:51:58 +03:00
5e28ba8814 chore: updated README.md 2025-05-02 00:14:58 +03:00
4c235e3230 feat: added basic math operations as Math methods 2025-05-02 00:14:41 +03:00
131203d578 refactor: Yaml serialization moved from Core to own project 2025-05-02 00:00:03 +03:00
bd5eb432b7 feat: serialized state machine & states 2025-05-02 00:00:03 +03:00
d2ca85568f feat: entity register for serialized entity references 2025-05-02 00:00:03 +03:00
4c41870732 perf: made SerializedClass private and public fields optional 2025-05-02 00:00:03 +03:00
f77afa3632 chore: removed forgotten removed project reference 2025-05-02 00:00:03 +03:00
eb61598489 chore: reordered UniverserObjectSerializer fields for better readable yaml output 2025-05-02 00:00:03 +03:00
efe51b491d chore: universe serializer filters in only the root universe objects 2025-05-02 00:00:03 +03:00
fa3a4d1e0d feat: added universe serializer 2025-05-02 00:00:03 +03:00
6e7a0993f5 refactor: renamed converters to serializers 2025-05-02 00:00:03 +03:00
d70bee2c6b feat: serializable Transform2D 2025-05-02 00:00:03 +03:00
5812f43117 refactor: moved type container one directory up 2025-05-02 00:00:03 +03:00
d102c5471d feat: type container added back for field/property serialization 2025-05-02 00:00:03 +03:00
fb363970fc refactor: moved serialization into core project 2025-05-02 00:00:03 +03:00
791349686b chore: removed unused classes 2025-05-02 00:00:03 +03:00
3a0942ff46 fix: ignore serialization objects being included in serialization fixed 2025-05-02 00:00:03 +03:00
b002dd469a feat: behaviour & behaviour controller converters added 2025-05-02 00:00:03 +03:00
f92f36442c feat: state enable converted added 2025-05-02 00:00:03 +03:00
bb934b59f3 feat: wip universe object converter added 2025-05-02 00:00:03 +03:00
c704173183 feat: serialize all attribute 2025-05-02 00:00:03 +03:00
c3876add1e chore: added serialized entity class 2025-05-02 00:00:03 +03:00
35a75d993b chore: experimentations 2025-05-02 00:00:03 +03:00
2637f99456 fix: fixed fields/properties like behaviour controllers not being explored by entity finder 2025-05-02 00:00:03 +03:00
9581f5aa54 refactor: removed unnecessary logs 2025-05-02 00:00:02 +03:00
82cc25a9ef feat: entity finder added 2025-05-02 00:00:02 +03:00
336e7e16e7 chore: memberInfo.HasAttribute method added 2025-05-02 00:00:02 +03:00
a3a8fb4e84 chore: depth limit for debugging 2025-05-01 23:59:43 +03:00
35f6c3850e fix: GetTypeData not including base class proprety & fields 2025-04-28 22:29:08 +03:00
f51d5f342e chore: added a generic converter 2025-04-28 22:29:08 +03:00
9c129cefe2 feat: added state enable serialization 2025-04-28 22:29:08 +03:00
a254bb721b chore: changed entity reference order 2025-04-28 22:29:08 +03:00
5fa7420c04 feat: added entity converter 2025-04-28 22:29:08 +03:00
5bcc256777 feat: added type container serialization 2025-04-28 22:29:08 +03:00
680d718957 chore: moved primitive converters under subfolder 2025-04-28 22:29:08 +03:00
20bc6a1adb chore: updated to forked version of YamlDotNet that fixes sequence indentations 2025-04-28 22:29:08 +03:00
eb454a471c feat: added primitive serialization 2025-04-28 22:29:08 +03:00
c205e710bc chore: some experimentations with DotNetYaml 2025-04-28 22:29:08 +03:00
cddb30c631 refactor: optimized & added reload method for type factory 2025-04-28 22:26:33 +03:00
29f6c83bf0 chore: removed unnecessary partial keyword 2025-04-27 22:28:35 +03:00
c20f210b29 refactor: rewritten GetType in a more readable way 2025-04-27 22:28:21 +03:00
1ea1844677 fix: Transform2D not raising OnPositionChanged event with correct parameters 2025-04-26 14:26:17 +03:00
5b2c13f8bf fix: BehaviourController assigning a new state enable to all newly added behaviours fixed 2025-04-26 14:10:40 +03:00
c39ee44442 fix: behaviour controller initializing added behaviours when it itself is not initialized 2025-04-25 21:54:05 +03:00
4623b4861a fix: behaviour controllers of universe objects not being initialized 2025-04-25 21:26:01 +03:00
0a868b82e5 fix: behaviour controller not respecting it's own state enable 2025-04-25 21:05:20 +03:00
d92d16cfad refactor: IBehaviourController is now an IEntity as well 2025-04-22 15:50:26 +03:00
0184d1758c feat: added more methods for TypeFactory 2025-04-20 00:06:48 +03:00
6e5b805803 chore: updated core diagram 2025-04-15 23:41:07 +03:00
8293c58f9f refactor: removed X.Abstract namespaces and moved StateMachine to under Systems namespace 2025-04-15 23:33:58 +03:00
94d01521d4 feat: IUniverse.OnTimeChanged event added 2025-04-14 12:19:23 +03:00
5c1c025fe3 chore: forgotten InvokeSafe usage in TweenExtensions 2025-04-13 22:23:57 +03:00
1d292a104e chore: removed unnecessary null check 2025-04-13 22:23:36 +03:00
70c884acfe refactor!: renamed GameManager to Universe and HierarchyObject to UniverseObject 2025-04-13 21:57:05 +03:00
a9f5974568 fix: InvokeSafe params causing warnings for possible null parameter calls 2025-04-13 21:41:53 +03:00
dae72b11c5 refactor: renamed AssertHelpers namespace to Core.Debug 2025-04-13 19:12:34 +03:00
58eb373c79 feat: safe delegate invocation helper added 2025-04-13 19:08:47 +03:00
00f7b1aaab chore: hierarchy objects now get their type name as their Name in constructor 2025-04-13 13:42:05 +03:00
9e4c74ed1d feat: IGameManager.FindRequiredBehaviour extension method added 2025-04-13 13:18:02 +03:00
2e2306c5bb refactor: IBehaviourController.GetBehaviours return value replaced with IReadOnlyList 2025-04-13 12:53:55 +03:00
86b8cd9b55 feat!: GetRequiredBehaviour/HierarchyObject methods added for cleaner null handling 2025-04-13 12:52:27 +03:00
bfbcfdce4f fix: ticker behaviour working unexpectedly on instances where time increment is bigger than the period 2025-04-12 17:49:30 +03:00
80202d4a07 feat: time scaling 2025-04-12 11:49:44 +03:00
2be99d2142 fix: hierarchy object remove child parameter name fixed 2025-04-11 23:29:04 +03:00
4081693d32 fix: removing an object from hierarchy setting all children's parents null 2025-04-11 23:28:46 +03:00
193d23c877 chore: updated README.md 2025-04-11 20:29:33 +03:00
c135035d5b chore: added default instantiate for hierarchy object factory 2025-04-11 20:02:02 +03:00
fabc485689 fix: switched to for in GameManager because of list modifications 2025-04-09 23:19:53 +03:00
48710b0a7f fix: physics engine not progressing properly due to wrong delta time parameter 2025-04-09 23:19:20 +03:00
bf34e52dc8 chore: added Circle.Transform extension method 2025-04-09 12:26:16 +03:00
e3845a2f5c fix: colliders broken after previous commit 2025-04-09 12:25:56 +03:00
26a80452bc fix: collider2Ds not updating collider after new assignment 2025-04-08 23:18:33 +03:00
2535a1d6ec refactor: TransformFoo like extension methods renamed to Transform 2025-04-08 23:15:19 +03:00
3a385900fb chore: SetHierarchyObject parent parameter added 2025-04-08 22:39:12 +03:00
b94bbc8ed7 chore: updated SetHierarchyObject to be generic so we don't lose original reference type 2025-04-08 20:35:42 +03:00
3f7a646bf0 refactor: updated PhysicsEngine2D to be plug & play 2025-04-08 20:30:32 +03:00
f119a23d2b feat: added IGameManager.OnPreUpdate event 2025-04-08 20:29:53 +03:00
61488aa0e5 fix: physics engines not triggering with correct parameters 2025-04-08 18:22:29 +03:00
66b46e3d36 fix: 2D shape collider parametered constructor not creating a copy of the given shape 2025-04-07 22:37:23 +03:00
1ee07b41f8 fix: IHierarchyObject.SetParent not entering the object into the IGameManager hierarchy fixed 2025-04-06 18:06:33 +03:00
6f425776cc refactor: timer methods names cleaned up 2025-04-06 17:26:57 +03:00
98c9dde98a fix: GetBehaviourInParent not working properly 2025-04-06 15:50:21 +03:00
04d325f38b fix: Transform2D fields & parental transform references not working properly fixed 2025-04-06 11:21:59 +03:00
901585d4bb fix: HierarchyObject not throwing any error when trying to set itself as parent 2025-04-06 10:57:22 +03:00
33a452a62e fix: sorted collector behaviours not working properly as the last object 2025-04-06 10:21:10 +03:00
906edf096e refactor: IEasing.Evaluate parameter renamed to x 2025-04-05 22:46:11 +03:00
d1129c95df chore!: renamed Shape2D.Box to Square for consistency 2025-04-05 17:25:50 +03:00
40f483974d chore: added shape.Transform extension methods 2025-04-05 15:59:26 +03:00
4b856420f9 docs: improved documentation no Shape2D 2025-04-05 15:06:40 +03:00
7f93d95f6b feat: Shape2D convex triangulation methods added 2025-04-05 14:59:29 +03:00
5756b96002 chore: removed unused code piece from HierarchyObject 2025-04-01 19:19:06 +03:00
c71bf71fb3 chore: cleanup 2025-04-01 13:29:56 +03:00
417ddca972 feat: IActive interface added for hierarchy active state 2025-04-01 13:22:14 +03:00
d4c6288b38 feat: added behaviour collector for active behaviours only 2025-04-01 12:19:58 +03:00
21600b856f refactor: removed unnecessary underscore on behaviour collector 2025-04-01 12:19:34 +03:00
803b670433 fix: IBehaviourCollector delegate parameter forgot on the concrete implementation 2025-04-01 12:19:12 +03:00
067bc51487 refactor: made factories static 2025-04-01 12:18:33 +03:00
9f3e39e337 feat: basic tween system added 2025-03-31 19:38:01 +03:00
cd2cd89eae fix: Engine referencing removed class library 2025-03-31 19:37:09 +03:00
90c1dd9348 chore: EnumExtensions added 2025-03-31 16:46:19 +03:00
e8ef41af41 chore: forgotten HierarchyObject extension method name 2025-03-31 16:45:58 +03:00
d1a289885b refactor: IGameManager.Enumerable<IHierarchyObject> 2025-03-31 14:25:00 +03:00
6170de4a73 refactor: moved Engine.Input into Engine.Systems 2025-03-31 13:00:30 +03:00
e2fdf1f538 chore: added root namespace definitions for projects 2025-03-31 12:48:05 +03:00
7a1dd7eb1a feat: time systems added 2025-03-30 20:34:42 +03:00
b73c9ed0ae refactor: got rid of the static Time class and implemented EngineTime on IGameManager 2025-03-30 20:27:22 +03:00
82705ba378 chore: added Engine class library to include all projects 2025-03-29 21:59:36 +03:00
f9785462b0 refactor: delegate names updated to have no "On" prefix 2025-03-29 21:51:51 +03:00
5c3e0f6581 feat: added basic state machine system & Engine.Systems class library 2025-03-29 21:40:30 +03:00
b9ee1ec232 fix: unassigned fields on factories 2025-03-29 10:30:31 +03:00
4ec1a32db2 refactor!: IGameObject removed 2025-03-28 17:48:31 +03:00
d825bb55d3 refactor!: Transform events now send previous values of their changed fields 2025-03-26 14:19:47 +03:00
5fc8c012b3 feat: quaternion to matrix4x4 methods 2025-03-26 13:57:46 +03:00
95ddba0230 docs: updated extension methods to inherit the original method's documentation 2025-03-21 23:01:47 +03:00
30caa202dc refactor: added forgotten epsilon for Shape2D.ApproximatelyEquals 2025-03-21 22:57:00 +03:00
b2a286b5e5 feat: added quaternion struct 2025-03-21 22:04:00 +03:00
5c542039ed chore: added more implicit conversions on Vector2D and Vector3D 2025-03-21 21:53:53 +03:00
25043bbcde docs: fixed typo on Vector3D 2025-03-18 22:10:59 +03:00
183966d239 refactor: moved 2D primitives from Physics2D to Core 2025-03-17 21:57:09 +03:00
9ecf0b900f refactor: renamed primitives 2025-03-17 21:54:43 +03:00
62b43025b9 chore: improved exception messages 2025-03-17 21:38:26 +03:00
9af44d48b3 refactor: code styles enforced with .editorconfig 2025-03-17 21:32:37 +03:00
d71c135491 feat: added 3D vectors 2025-03-17 21:15:31 +03:00
e73c076243 refactor: removed wrong library calls for math functions from Vector2D 2025-03-17 21:03:53 +03:00
7743ccadbf fix: Typo on Namespace 2025-01-22 22:06:00 +03:00
d0ab442f7f chore: AABB ToString() Method 2024-12-08 16:18:49 +03:00
cdfe655ac4 refactor: Added IButtonInput Events for Any Button 2024-11-29 21:41:43 +03:00
b4659def55 chore: Removed Unused Editor Config 2024-11-26 23:07:37 +03:00
58a9ada345 refactor: IBehaviourController & Sorted Collector 2024-11-24 22:20:11 +03:00
eb5345dc77 refactor: CreateCoroutine to StartCoroutine 2024-11-24 21:44:10 +03:00
4416f64287 refactor: IBehaviourController.TryGetBehaviour to Extension Method 2024-11-24 11:34:36 +03:00
1b3f40be5f refactor: Removed Duplicate Interface Declaration 2024-11-24 11:20:00 +03:00
e725a4e89c refactor: Renamed Delegate Declarations as EventHandler 2024-11-23 23:14:44 +03:00
981db0190f feat: GetOrAddBehaviour Extension for IBehaviourController 2024-11-23 22:25:45 +03:00
c0c48c7d51 refactor: IHierarchyObject Nullable IGameManager to Non-Nullable
Since in a typical use case of these classes they are already in the
hierarchy and it bloats the behaviour code with lots of null checks
2024-11-17 22:05:22 +03:00
cc4068fa2e refactor: Removed IEnumerable<GameObjects> from IGameManager 2024-11-16 22:44:45 +03:00
4d59dcb9ab fix: IBehaviour.GetBehaviours<T>(List) 2024-11-13 15:30:22 +03:00
1b3b999a95 chore: .editorconfig file for Code Style 2024-11-10 20:34:55 +03:00
1545291942 fix: Issues With Behaviours Entering / Leaving the Hierarchy Callbacks Not Firing 2024-11-10 20:21:53 +03:00
81625abd25 refactor: Improved & Fixed Issues with Transforms with Parents 2024-11-10 19:18:44 +03:00
ea94bed00d fix: Transform Rotation Miscalculation 2024-11-09 17:15:57 +03:00
12cb144688 feat: Math.Sin & Math.Cos 2024-11-06 23:00:10 +03:00
85f0555c59 fix: Duplicate Values on Behaviour Collector 2024-11-03 20:31:51 +03:00
55ed8b84f6 Refactor: CollisionDetectionInformation Names 2024-11-03 20:25:28 +03:00
4856800f5f feat: Improved Entering & Exiting of Hierarchy Objects 2024-11-03 20:13:25 +03:00
cc44e1ea69 fix: OnEnterHierarchy Not Being Called Correctly 2024-11-03 19:58:57 +03:00
cb60c71184 feat: ICoroutineYield for Delaying Coroutines 2024-11-02 22:27:04 +03:00
eb445604e8 fix: Math Method Mistakenly Declared as an Extension 2024-11-02 13:53:58 +03:00
8eb34a0a6d fix: Transform Local Fields Being Miscalculated 2024-11-02 13:47:08 +03:00
9f522bdb66 fix: Physics Engine Not Resolving Static Objects Correctly 2024-11-02 09:39:53 +03:00
d7f0b76485 fix: Circle Transformation Miscalculation 2024-11-02 09:38:01 +03:00
90370a2b43 feat: Physics Coroutine Manager 2024-10-29 23:14:08 +03:00
0f3f1594d0 refactor: Wrong Accessibility Level On Field 2024-10-29 22:45:08 +03:00
24b50eba12 fix: IGameObjects Not Being Initialized Properly 2024-10-26 22:45:24 +03:00
6bc9043a86 feat: Basic CoroutineManager 2024-10-26 13:47:39 +03:00
43f1749b04 refactor: IHierarchy Integration with Overall Code Base 2024-10-26 13:46:04 +03:00
62e50aefc1 feat: IPhysicsEngine2D Events 2024-10-25 23:10:32 +03:00
9c2b098821 feat: IGameManager Events 2024-10-25 23:10:20 +03:00
91aa26e15a BREAKING CHANGE: Added IHierarchObject with Hierarchy Enter & Exit 2024-10-22 22:05:22 +03:00
cf8a5de580 fix: Behaviour Collector Not Recognizing Registered GameObjects After Initial Setup 2024-10-22 21:46:26 +03:00
1f8fa78b76 BREAKING CHANGE: Renamed IInitialize.Initialized to IsInitialized 2024-10-22 20:57:12 +03:00
fdc38fc800 feat: Engine.Core.MathExtensions 2024-10-20 15:12:20 +03:00
fc34a60f30 chore: Another Code Style Mistake 2024-10-20 15:11:51 +03:00
eca23c5b89 feat: Math.Lerp 2024-10-20 14:47:42 +03:00
f08f721f52 chore: Fixed Indentation Mistake 2024-10-20 14:40:58 +03:00
fb402acc30 fix: BehaviourCollector Skipping Unregistered GameObjects 2024-10-05 23:08:44 +03:00
923b25e26e feat: Missing Line Extension Methods 2024-10-05 22:59:50 +03:00
2bcd1c5a89 fix: BehaviourController Manual AddBehaviour Assign Exceptions 2024-09-30 19:55:14 +03:00
40d1ce7c68 chore: Removed Unnecessary Method from IButtonInputs 2024-09-30 19:24:14 +03:00
e7c80871fe refactor: Renamed Behaviour to BehaviourBase & BehaviourOverride to Behaviour 2024-09-30 12:18:51 +03:00
c51eda49bf fix: GameManager Instantiation/Deletion during Update causing Array Changed Exception 2024-09-27 18:38:24 +03:00
0f8a7db567 fix: EngineTime DeltaTimeFrame 2024-09-26 22:59:25 +03:00
ffa0128813 feat: IPhysicsUpdate 2024-09-26 18:55:15 +03:00
15984bcc06 refactor: IButtonInputs from Actions to Delegate 2024-09-25 14:36:57 +03:00
ef21cdf213 refactor: Actions to Delegates 2024-07-15 01:13:39 +03:00
2cf6135063 refactor: Renamed BehaviourCacher to BehaviourCollector 2024-02-09 09:43:15 +03:00
be06575f91 feat: BehaviourExtensions.FindBehaviour 2024-02-08 17:58:15 +03:00
ed6975bf24 fix: Null Reference Error on ITransform.SetParent 2024-02-07 14:24:19 +03:00
d9660c08b1 feat: Collider RigidBody Reference Update on Parent Change 2024-02-07 12:33:07 +03:00
3902f1caca feat: Parent Change Propagation to Children 2024-02-07 12:32:55 +03:00
14e3337daa feat: BehaviourControllerExtensions
- TryGetBehaviourInParent
- GetBehaviourInParent
- TryGetBehaviourInChildren
- GetBehaviourInChildren
2024-02-07 11:53:57 +03:00
f729cdc0a8 revert: refactor: ITransformWithGameObject
This reverts commit f96c58cbd4.
2024-02-07 11:45:14 +03:00
c767e1e856 docs(core): Parent & Child Methods 2024-02-06 17:42:24 +03:00
f96c58cbd4 refactor: ITransformWithGameObject 2024-02-06 17:38:11 +03:00
fed288859f feat: IAssignableGameObject to ITransform
I originally didn't want ITransform to have a reference to any IGameObject, since it didn't seem necessary to couple these two, and to make ITransform more flexible and reusable but without it we can't get a reference to the IGameObject(s) that's using that ITransform without doing some very stupid workarounds. I'll try to find a better way for this.
2024-02-06 17:32:39 +03:00
6e4c9b0ef8 feat: Transform Hierarchy System 2024-02-06 15:55:07 +03:00
b931abb735 feat: Shape to Vector2D Overlap 2024-02-06 10:56:54 +03:00
72492a9f5a docs: Basic README.md 2024-02-05 12:20:12 +03:00
2f043c19a6 feat: GameManagerExtensions.InstantiateGameObject 2024-02-05 11:20:15 +03:00
dbb263ebed feat: IEntity.Id & BaseEntity 2024-02-02 17:40:46 +03:00
5d897f2f56 feat: EngineTime.DeltaTimeFrame 2024-02-01 18:43:33 +03:00
1dc8f3d272 refactor: Math.AbsMax & AbsMin to Single Liners 2024-02-01 15:36:52 +03:00
0725468f2c feat: Math.AbsMax & AbsMin 2024-02-01 15:35:16 +03:00
5826230e7a docs(core): Math 2024-02-01 15:31:47 +03:00
636331e18f style: Forgotten Line Between Namespace and Class Declaration 2024-02-01 14:51:49 +03:00
09a8e71fe3 fix: Renamed Vector2D.Subdivide to Divide 2024-02-01 14:42:45 +03:00
81a0cf645a docs(core): Vector2D 2024-02-01 14:41:56 +03:00
ab0e868d52 style: IterationCount to IterationPerStep 2024-02-01 11:33:23 +03:00
0257911018 docs(physics2d): Primitives 2024-02-01 11:25:30 +03:00
2b19b24a26 docs(physics2d): Abstract 2024-02-01 11:20:40 +03:00
2f4137dae2 docs(core): Abstract 2024-02-01 11:15:49 +03:00
4ce9c8e0d9 docs: Core IAssignable 2024-02-01 11:02:54 +03:00
3e9c393817 style: ICamera2D.WorldToScreenPosition Parameter Name Fixed 2024-02-01 09:06:02 +03:00
a1f63d2728 feat: ICamera2D Interface 2024-01-31 17:30:20 +03:00
6c36d4d21d feat: IBehaviourController.GetBehaviour 2024-01-31 17:03:40 +03:00
de336d0ee5 refactor: Dependency Injection to PhysicsEngine2Ds 2024-01-31 10:08:13 +03:00
8619778d52 feat: PhysicsEngine2DCacher
This class uses BehaviourCacher to track IGameManager's Rigidbody2D & Collider2Ds
2024-01-31 10:01:50 +03:00
4facfdb6cf fix: Physics Engine Stepping Inactive Rigid Bodies Fixed 2024-01-31 09:59:42 +03:00
f61f71dfe0 BREAKING CHANGE: Removed Add & Remove Rigidbody Methods from IPhysicsEngine2D 2024-01-31 09:59:11 +03:00
005c78a26e feat: BehaviourCacher<T> 2024-01-31 09:57:54 +03:00
01a99daf8a feat: IBehaviourController Enumarable<IGameObject> 2024-01-31 09:29:39 +03:00
8269c789a6 fix: IGameManager Action Types 2024-01-31 09:27:24 +03:00
3817ebebfe feat: Transform Extensions 2024-01-30 16:59:43 +03:00
fa7eeed267 feat: GameObject Extensions 2024-01-30 16:59:03 +03:00
07666359f2 feat: FindBehaviour/s 2024-01-30 14:11:20 +03:00
514e5b5762 feat: IBehaviourController.GetBehaviours(List) 2024-01-30 13:49:54 +03:00
1438b19e35 feat: GameObjects are now Connected to a Single IGameManager 2024-01-30 13:17:02 +03:00
4000e761a7 Merge branch 'feat/physics2d' into development 2024-01-30 11:58:00 +03:00
9768dbdded refactor(core): Removed ICamera 2024-01-30 11:57:43 +03:00
9853e0af36 feat: IGameManager 2024-01-30 11:51:43 +03:00
0461454793 feat: Math's Abs, Clamp, Max, Min & Sqr Methods Converted to Generics 2024-01-30 10:38:36 +03:00
d7d53e467a fix: Vector2D Normal Debugger Display 2024-01-29 17:41:53 +03:00
1c7d941bc1 feat(core): IBehavior.IsActive 2024-01-28 14:56:50 +03:00
dc96b93024 feat(physics): Engine Rigidbody2D Static Check 2024-01-28 14:56:13 +03:00
1ffddab2c1 fix: Collider2DShapeBehaviour.ShapeWorld Create Copy of Shape.Box 2024-01-27 21:50:55 +03:00
c1c1676b9a feat: Behaviour DebuggerDisplay Displays Behaviour Type Name Now 2024-01-27 21:21:58 +03:00
11483231a5 fix: ShapeExtensions.CreateCopy not having this Keyword 2024-01-27 21:21:35 +03:00
2ca243d79c fix: Shape & Circle Colliders Parametered Constructs Null Transform Error 2024-01-27 21:15:17 +03:00
69eca44dd8 fix: DebuggerDisplay Wrongly Typed Parameters 2024-01-27 21:05:56 +03:00
6a104d8abd fix: Collider2DShapeBehaviour TransformShape ref Requirement 2024-01-27 20:34:58 +03:00
affd2bb8c4 feat: ICollider.OnTriggered 2024-01-27 20:31:51 +03:00
15788f2eca refactor: Improved Shape & Circle Code Readability 2024-01-27 20:20:28 +03:00
ab2489f6cf feat: Shape & Circle Collider Constructors 2024-01-27 20:19:55 +03:00
574104c224 feat: ICollider IsTrigger 2024-01-27 20:19:00 +03:00
0d4c96a2fc fix: Forgotten Build Errors on Collider2DBehaviourBase Actions 2024-01-27 20:17:46 +03:00
5620f2f1eb feat: Circle.UnitCircle 2024-01-27 20:14:55 +03:00
a3c4afb223 refactor: Basic Collision Resolver 2024-01-27 20:08:16 +03:00
4d9121118d refactor: Renamed OnCollisionPreResolve to OnCollisionDetected 2024-01-27 19:59:27 +03:00
05d88f7ca2 refactor: Renamed ICollisionDetector to ICollisionDetector2D 2024-01-27 19:58:55 +03:00
309c8db6e1 feat: ICollider2D Action Calls 2024-01-27 19:22:59 +03:00
0ba6913a61 fix: Static Rigidbodies Moving When Velocity or AngularVelocity Assigned 2024-01-27 19:21:18 +03:00
9556be6f17 feat: Static & Mass Consideration For Rigidbodies 2024-01-27 15:07:40 +03:00
bd43d39367 fix: Circles Not Colliding Accurately Fixed 2024-01-27 15:07:07 +03:00
32e2a6e7d3 feat: Rigidbody Mass Restriction 2024-01-27 14:58:50 +03:00
7b47703ba0 refactor: DebuggerDisplays For Basic Types 2024-01-27 00:51:34 +03:00
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
c32add40ff fix: Shape to Shape Detection 2024-01-26 20:35:05 +03:00
058c6dafe3 refactor: Removed Unused Methods 2024-01-26 17:09:21 +03:00
85bad951ff fix: Shape Collision on Larger Shapes 2024-01-26 17:06:42 +03:00
6a84c3ec1a feat: Useful Readonly Shapes 2024-01-26 16:38:38 +03:00
ceb29cc42f feat: Shape.CreateNgon 2024-01-26 16:38:08 +03:00
c6d2bad23e feat: Basic Shape to Shape Collision Detection 2024-01-26 16:16:27 +03:00
2bfd391286 feat: Basic Shape to Circle Collision Detection 2024-01-26 16:05:11 +03:00
ac09b78edd feat: Transform Recalculation Conditions Updated 2024-01-26 15:52:59 +03:00
4607955d55 feat: Vector2D.Perpendicular 2024-01-26 14:37:48 +03:00
271a9a244b feat: CollisionDetector CircleCircle Projection 2024-01-26 13:34:43 +03:00
f980546d37 feat: Circle & Shape Projections 2024-01-26 13:33:01 +03:00
dfcc877e58 chore: Removed System Using 2024-01-26 13:32:33 +03:00
8ebde9dedf feat: Projection Data Record 2024-01-26 13:30:26 +03:00
238bf2d574 feat: Shape.CreateCopy 2024-01-26 10:06:22 +03:00
ceebe21041 chore: Removed Unnecessary .sln Files 2024-01-26 09:58:25 +03:00
0ba8927858 perf: Collider2DBase NeedsRecalculation Field 2024-01-25 22:00:49 +03:00
ab9181fe3f refactor: Removed Unused Using 2024-01-25 21:54:39 +03:00
266443504f feat: Improved Test Collision Resolving 2024-01-25 21:54:23 +03:00
3c39e6709d fix: Collision Detector CircleCircle Depth Calculation Fixed 2024-01-25 21:53:28 +03:00
e7ca96e2e2 fix: Collider2D Not Registering Rigidbody2D Attached 2024-01-25 21:52:56 +03:00
3b83be695c fix: Circle Collider2D Recalculate Wrong Calculation 2024-01-25 21:52:40 +03:00
601d15fa45 feat: Math.Sqr 2024-01-25 21:52:03 +03:00
00b80f1a01 feat: Collider2DBehaviourBase 2024-01-25 20:42:49 +03:00
9e1f38897f fix: Self Referencing Call 2024-01-25 17:49:22 +03:00
0af1b11396 feat: Test Collision Detection & Move 2024-01-25 17:45:22 +03:00
f5be49609b feat: NotNullWhen to TryDetect 2024-01-25 17:43:40 +03:00
f7467a62ee feat: Collision Detector Circles 2024-01-25 17:30:57 +03:00
816f09fffe feat: Math.Sqrt 2024-01-25 17:07:37 +03:00
24cbc8a267 feat: New CollisionDetectionInformation 2024-01-25 16:44:01 +03:00
385defd8e6 refactor: Possible Bugs 2024-01-25 16:08:50 +03:00
ed15238dcd BREAKING CHANGE: New ICollider 2024-01-24 17:35:14 +03:00
350ef030ac refactor: Circle.Position to Circle.Center 2024-01-24 16:31:00 +03:00
3428fcc6ca perf: BehaviourController.GetBehaviours now uses Linq.Enumerable.Empty if None Found 2024-01-24 14:41:10 +03:00
528649659d feat: AABB Center, Size & HalfSize 2024-01-24 14:09:41 +03:00
da67f4559b fix: Forgotten this keyword on Vector2DExtensions.Abs 2024-01-24 14:07:36 +03:00
08b31d9db1 feat: Vector2D.Abs 2024-01-24 14:06:35 +03:00
326bcfca61 fix: AABB.FromVectors 2024-01-24 13:59:15 +03:00
bfab35c27e feat: Shape IEnumerable 2024-01-24 13:57:52 +03:00
468615e4cb feat: AABB.FromVectors 2024-01-24 12:16:42 +03:00
56b46b93eb fix: Build Errors 2024-01-24 12:04:34 +03:00
0c3bf48d2c feat: Vector2D.Orientation 2024-01-24 11:53:37 +03:00
77e1949f59 refactor: Math Constants Now Use Each Other as References 2024-01-24 11:33:21 +03:00
fbf9bd5832 refactor: Removed Old PhysicsMath Class 2024-01-24 11:28:11 +03:00
d40183db65 feat: float & Vector2D.ApproximatelyEquals 2024-01-24 11:26:54 +03:00
a60f79f12b chore: Removed Physics2D Degree Constants 2024-01-24 11:21:50 +03:00
4bf618251f feat: Math.Clamp 2024-01-24 11:21:25 +03:00
b3d404bb6b feat: TransformExtensions.TransformVector2D 2024-01-24 11:17:12 +03:00
87bf47eefd feat: Math 2024-01-24 11:15:44 +03:00
e5732f0ac5 fix: Build Error Caused by Parameter Name 2024-01-24 10:45:06 +03:00
c3bcaaee06 refactor: Vector2D.Invert to Operator 2024-01-24 10:42:01 +03:00
83d8a03be3 feat: Basic Operation Methods
Vector2D.Invert
Vector2D.Add
Vector2D.Subtract
Vector2D.Multiply
Vector2D.Subdivide
2024-01-24 10:40:24 +03:00
1acecdf3ce feat: Vector2.Rotate 2024-01-24 10:40:23 +03:00
51b1f79a5d refactor: int to Index for Shape Accessor 2024-01-24 10:40:10 +03:00
09c63b65df fix: Scale Calling FromTo Instead of Scale 2024-01-24 10:40:02 +03:00
909b93088c feat: Shape Index Accessor 2024-01-24 10:39:24 +03:00
e7587a0827 fix: Wrong Method Call 2024-01-23 17:32:08 +03:00
5ed7ccdded fix: Build Errors 2024-01-23 17:31:32 +03:00
0d29ab066f refactor: Added Static Methods Back 2024-01-23 17:30:46 +03:00
bd03d036aa refactor: Shape 2024-01-23 17:29:55 +03:00
3b299c947c refactor: LineEquation 2024-01-23 17:29:21 +03:00
6a5d10980a refactor: Triangle 2024-01-23 17:28:59 +03:00
dcda78b010 refactor: Line 2024-01-23 17:27:57 +03:00
5170dd0aea refactor: Circle 2024-01-23 17:27:38 +03:00
5c185a664c refactor: AABB 2024-01-23 17:26:45 +03:00
8ccebaa8fb chore: Solution File for Physics2D 2024-01-23 17:24:37 +03:00
a9485475c7 feat: Initial Physics Code From Previous Repo 2024-01-23 15:40:04 +03:00
d08495afbb feat: GameObject Register Actions Added 2024-01-23 09:47:13 +03:00
fdb5936573 feat: IEnumerable<IGameObject> to GameManager 2024-01-23 09:43:49 +03:00
39e553ebbf feat: EngineTime Parameter for GameManager.Update 2024-01-23 09:21:38 +03:00
0ce6f8ab23 style: Renamed GameTime to EngineTime 2024-01-23 09:20:43 +03:00
485dfcc51e BREAKING CHANGE: Removed Engine.Graphics 2024-01-22 23:42:30 +03:00
8cffc43f23 BREAKING CHANGE: Removed MonoGame Package 2024-01-22 23:38:37 +03:00
de10a859ae perf: Array/Dictionary Improvement 2024-01-22 23:30:21 +03:00
388e7f4788 feat: Time 2024-01-22 23:23:32 +03:00
81f9ef10bf BREAKING CHANGE: Removed MonoGame Package 2024-01-22 22:45:40 +03:00
1c884d49bb feat: Added Vector2D 2024-01-22 22:03:56 +03:00
c03d74dbe0 feat: Added a RemoveBehaviour with Parameter 2023-11-30 17:54:32 +03:00
bb6990a80c feat: PreUpdate and FirstActiveFrame to Behaviour 2023-11-30 10:39:40 +03:00
d2881e94df fix: Fix Big Textures Not Positioned Correctly 2023-11-27 15:12:04 +03:00
229 changed files with 11004 additions and 2470 deletions

225
.editorconfig Normal file
View File

@@ -0,0 +1,225 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# (Please don't specify an indent_size here; that has too many unintended consequences.)
spelling_exclusion_path = SpellingExclusions.dic
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
# XML project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# XML config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
# Powershell files
[*.ps1]
indent_size = 2
# Shell script files
[*.sh]
end_of_line = lf
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:refactoring
dotnet_style_qualification_for_property = false:refactoring
dotnet_style_qualification_for_method = false:refactoring
dotnet_style_qualification_for_event = false:refactoring
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:none
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Whitespace options
dotnet_style_allow_multiple_blank_lines_experimental = false:error
# IDE0055: Fix formatting
# Workaround for https://github.com/dotnet/roslyn/issues/70570
dotnet_diagnostic.IDE0055.severity = suggestion
# https://github.com/dotnet/roslyn-analyzers/issues/7436 - False positives from valid GetDeclaredSymbol calls
dotnet_diagnostic.RS1039.severity = none
# CSharp code style settings:
# IDE0029: Use coalesce expression
dotnet_diagnostic.IDE0029.severity = none
# IDE0040: Add accessibility modifiers
dotnet_diagnostic.IDE0040.severity = warning
[*.cs]
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Whitespace options
csharp_style_allow_embedded_statements_on_same_line_experimental = true
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:error
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
# Prefer "var" everywhere
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = false:warning
csharp_style_var_elsewhere = false:warning
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Blocks are allowed
csharp_prefer_braces = true:silent
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
# IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0060.severity = warning
[src/{Compilers,ExpressionEvaluator,Scripting}/**Test**/*.{cs,vb}]
# IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0060.severity = none
[src/{Analyzers,CodeStyle,Features,Workspaces,EditorFeatures,VisualStudio}/**/*.{cs,vb}]
# IDE0011: Add braces
csharp_prefer_braces = false:warning
# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201
dotnet_diagnostic.IDE0011.severity = warning
# IDE0040: Add accessibility modifiers
dotnet_diagnostic.IDE0040.severity = warning
# IDE0052: Remove unread private member
dotnet_diagnostic.IDE0052.severity = warning
# IDE0059: Unnecessary assignment to a value
dotnet_diagnostic.IDE0059.severity = warning
# CA1012: Abstract types should not have public constructors
dotnet_diagnostic.CA1012.severity = warning
# CA1822: Make member static
dotnet_diagnostic.CA1822.severity = warning
# Prefer "var" everywhere
dotnet_diagnostic.IDE0007.severity = warning
csharp_style_var_for_built_in_types = false:warning
csharp_style_var_when_type_is_apparent = false:warning
csharp_style_var_elsewhere = false:warning
# csharp_style_allow_embedded_statements_on_same_line_experimental
dotnet_diagnostic.IDE2001.severity = warning
# csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental
dotnet_diagnostic.IDE2004.severity = warning
# csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental
dotnet_diagnostic.IDE2005.severity = warning
# csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental
dotnet_diagnostic.IDE2006.severity = warning
[src/{VisualStudio}/**/*.{cs,vb}]
# CA1822: Make member static
# There is a risk of accidentally breaking an internal API that partners rely on though IVT.
dotnet_code_quality.CA1822.api_surface = private
[**/{ExternalAccess}/**/*.{cs,vb}]
# RS0016: Only enable if API files are present
dotnet_public_api_analyzer.require_api_files = true
dotnet_diagnostic.RS0051.severity = error
dotnet_diagnostic.RS0052.severity = error
dotnet_diagnostic.RS0053.severity = error
dotnet_diagnostic.RS0054.severity = error
dotnet_diagnostic.RS0055.severity = error
dotnet_diagnostic.RS0056.severity = error
dotnet_diagnostic.RS0057.severity = error
dotnet_diagnostic.RS0058.severity = error
dotnet_diagnostic.RS0059.severity = error
dotnet_diagnostic.RS0060.severity = error
dotnet_diagnostic.RS0061.severity = error

View File

@@ -1,484 +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
## 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

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "Engine.Serializers/YamlDotNet"]
path = Engine.Serializers/YamlDotNet
url = git@github.com:Syntriax/YamlDotNet.git

View File

@@ -482,3 +482,5 @@ $RECYCLE.BIN/
# Vim temporary swap files
*.swp
!Debug

View File

@@ -1,6 +1,4 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <summary>
/// Indicates the class implementing it has Assignable fields that are necessary for the engine to work properly.
@@ -8,9 +6,9 @@ namespace Syntriax.Engine.Core.Abstract;
public interface IAssignable
{
/// <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>
Action<IAssignable>? OnUnassigned { get; set; }
Event<IAssignable>? OnUnassigned { get; }
/// <summary>
/// Unassign <see cref="IAssignable"/>'s all fields and make it ready to recycle.

View File

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

View File

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

View File

@@ -1,26 +0,0 @@
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 IAssignableTransform : IAssignable
{
/// <summary>
/// Callback triggered when the <see cref="ITransform"/> value has has been assigned a new value.
/// </summary>
Action<IAssignableTransform>? OnTransformAssigned { get; set; }
/// <inheritdoc cref="ITransform" />
ITransform Transform { get; }
/// <summary>
/// Assign a value to the <see cref="ITransform"/> field of this object
/// </summary>
/// <param name="transform">New <see cref="ITransform"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(ITransform transform);
}

View File

@@ -1,22 +1,20 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IBehaviourController"/> field.
/// </summary>
public interface IAssignableBehaviourController : IAssignable
public interface IHasBehaviourController : IAssignable
{
/// <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>
Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; }
Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; }
/// <inheritdoc cref="IBehaviourController" />
IBehaviourController BehaviourController { get; }
/// <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>
/// <param name="behaviourController">New <see cref="IBehaviourController"/> to assign.</param>
/// <returns>

View File

@@ -1,22 +1,20 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IEntity"/> field.
/// </summary>
public interface IAssignableEntity : IAssignable
public interface IHasEntity : IAssignable
{
/// <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>
Action<IAssignableEntity>? OnEntityAssigned { get; set; }
Event<IHasEntity> OnEntityAssigned { get; }
/// <inheritdoc cref="IEntity" />
IEntity Entity { get; }
/// <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>
/// <param name="entity">New <see cref="IEntity"/> to assign.</param>
/// <returns>

View File

@@ -1,22 +1,20 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IStateEnable"/> field.
/// </summary>
public interface IAssignableStateEnable : IAssignable
public interface IHasStateEnable : IAssignable
{
/// <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>
Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; }
Event<IHasStateEnable> OnStateEnableAssigned { get; }
/// <inheritdoc cref="IStateEnable" />
IStateEnable StateEnable { get; }
/// <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>
/// <param name="stateEnable">New <see cref="IStateEnable"/> to assign.</param>
/// <returns>

View File

@@ -0,0 +1,24 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverse"/> field.
/// </summary>
public interface IHasUniverse : IAssignable
{
/// <summary>
/// Event triggered when the <see cref="IUniverse"/> value has has been assigned a new value.
/// </summary>
Event<IHasUniverse> OnUniverseAssigned { get; }
/// <inheritdoc cref="IUniverse" />
IUniverse Universe { get; }
/// <summary>
/// Assign a value to the <see cref="IUniverse"/> field of this object.
/// </summary>
/// <param name="universe">New <see cref="IUniverse"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IUniverse universe);
}

View File

@@ -0,0 +1,24 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Indicates the object is an <see cref="IAssignable"/> with an assignable <see cref="IUniverseObject"/> field.
/// </summary>
public interface IHasUniverseObject : IAssignable
{
/// <summary>
/// Event triggered when the <see cref="IUniverseObject"/> value has has been assigned a new value.
/// </summary>
Event<IHasUniverseObject> OnUniverseObjectAssigned { get; }
/// <inheritdoc cref="IUniverseObject" />
IUniverseObject UniverseObject { get; }
/// <summary>
/// Assign a value to the <see cref="IUniverseObject"/> field of this object.
/// </summary>
/// <param name="universeObject">New <see cref="IUniverseObject"/> to assign.</param>
/// <returns>
/// <see cref="true"/>, if the value given assigned successfully assigned, <see cref="false"/> if not.
/// </returns>
bool Assign(IUniverseObject universeObject);
}

View File

@@ -0,0 +1,19 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an entity which can be active or not.
/// </summary>
public interface IActive
{
/// <summary>
/// Event triggered when the <see cref="IsActive"/> state of the <see cref="IActive"/> changes.
/// </summary>
Event<IActive, ActiveChangedArguments> OnActiveChanged { get; }
/// <summary>
/// The value indicating whether the <see cref="IActive"/> is enabled.
/// </summary>
bool IsActive { get; }
readonly record struct ActiveChangedArguments(bool PreviousState);
}

View File

@@ -1,19 +1,19 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <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 engine that might use to interact with itself or other objects.
/// </summary>
public interface IBehaviour : IEntity, IAssignableBehaviourController, IAssignableStateEnable, IInitialize
public interface IBehaviour : IEntity, IActive, IHasBehaviourController, IHasStateEnable
{
/// <summary>
/// Callback triggered when the <see cref="Priority"/> has changed.
/// Event triggered when the priority of the <see cref="IBehaviour"/> changes.
/// </summary>
Action<IBehaviour>? OnPriorityChanged { get; set; }
Event<IBehaviour, PriorityChangedArguments> OnPriorityChanged { get; }
/// <summary>
/// Call priority of the <see cref="IBehaviour"/>.
/// The priority of the <see cref="IBehaviour"/>.
/// </summary>
int Priority { get; set; }
readonly record struct PriorityChangedArguments(int PreviousPriority);
}

View File

@@ -0,0 +1,6 @@
namespace Syntriax.Engine.Core;
public interface IBehaviour2D : IBehaviour
{
ITransform2D Transform { get; }
}

View File

@@ -0,0 +1,42 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a collector for the class type of <typeparamref name="T"/>.
/// Provides mechanisms for tracking additions and removals, and notifies subscribers when such events occur on the assigned <see cref="IUniverse"/>.
/// </summary>
/// <typeparam name="T">The type of objects tracked by the collector.</typeparam>
public interface IBehaviourCollector<T> : IHasUniverse where T : class
{
/// <summary>
/// Event triggered when an object of type <typeparamref name="T"/> is added to the collector.
/// </summary>
Event<IBehaviourCollector<T>, BehaviourCollectedArguments> OnCollected { get; }
/// <summary>
/// Event triggered when an object of type <typeparamref name="T"/> is removed from the collector.
/// </summary>
Event<IBehaviourCollector<T>, BehaviourRemovedArguments> OnRemoved { get; }
/// <summary>
/// Amount of <typeparamref name="T"/> collected.
/// </summary>
int Count { get; }
/// <summary>
/// Get a <typeparamref name="T"/> collected by it's index.
/// </summary>
T this[System.Index index] { get; }
/// <summary>
/// Delegate for handling the <see cref="OnCollected"/> event.
/// </summary>
/// <param name="sender">The instance of the <see cref="IBehaviourCollector{T}"/> that triggered the event.</param>
/// <param name="behaviourCollected">The object of type <typeparamref name="T"/> that was added to the collector.</param>
readonly record struct BehaviourCollectedArguments(T BehaviourCollected);
/// <summary>
/// Delegate for handling the <see cref="OnRemoved"/> event.
/// </summary>
/// <param name="BehaviourRemoved">The object of type <typeparamref name="T"/> that was removed from the collector.</param>
readonly record struct BehaviourRemovedArguments(T BehaviourRemoved);
}

View File

@@ -1,85 +1,83 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <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. Connected to an <see cref="IUniverseObject"/>.
/// </summary>
public interface IBehaviourController : IAssignableGameObject
public interface IBehaviourController : IEntity, IHasUniverseObject
{
/// <summary>
/// Callback triggered when the <see cref="Update(GameTime)"/> is called.
/// Event triggered when a <see cref="IBehaviour"/> is added to the <see cref="IBehaviourController"/>.
/// </summary>
Action<IBehaviourController, GameTime>? OnUpdate { get; set; }
Event<IBehaviourController, BehaviourAddedArguments> OnBehaviourAdded { get; }
/// <summary>
/// Callback triggered when the <see cref="OnPreDraw(GameTime)"/> is called.
/// Event triggered when a <see cref="IBehaviour"/> is removed from the <see cref="IBehaviourController"/>.
/// </summary>
Action<IBehaviourController, GameTime>? OnPreDraw { get; set; }
Event<IBehaviourController, BehaviourRemovedArguments> OnBehaviourRemoved { get; }
/// <summary>
/// Callback triggered when the <see cref="IBehaviourController"/> has been registered a new <see cref="IBehaviour"/>.
/// Amount of <see cref="IBehaviour"/> collected.
/// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; }
int Count { get; }
/// <summary>
/// Callback triggered when the <see cref="IBehaviourController"/> has been removed an existing <see cref="IBehaviour"/>.
/// Get a <see cref="IBehaviour"/> collected by it's index.
/// </summary>
Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; }
IBehaviour this[System.Index index] { get; }
/// <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>
/// <param name="behaviour">Uninitialized <see cref="IBehaviour"/> to be registered.</param>
/// <typeparam name="T">An implemented class of <see cref="IBehaviour"/></typeparam>
/// <returns>The provided <see cref="IBehaviour"/> class after initialization.</returns>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to add.</typeparam>
/// <param name="behaviour">The <see cref="IBehaviour"/> to add.</param>
/// <returns>The added <see cref="IBehaviour"/>.</returns>
T AddBehaviour<T>(T behaviour) where T : class, IBehaviour;
/// <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>
/// <param name="args">Constructor parameters for the given <see cref="IBehaviour"/> class.</param>
/// <typeparam name="T">An implemented class of <see cref="IBehaviour"/></typeparam>
/// <returns>The instantiated <see cref="IBehaviour"/> class after initialization.</returns>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to add.</typeparam>
/// <param name="args">Construction parameters for the <see cref="IBehaviour"/>.</param>
/// <returns>The added <see cref="IBehaviour"/>.</returns>
T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour;
/// <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>
/// <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">An implemented class or <see cref="interface"/></typeparam>
/// <returns>
/// <see cref="true"/>, if the type of <see cref="IBehaviour"/> is present in the <see cref="IBehaviourController"/>, <see cref="false"/> if not.
/// </returns>
bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour);
/// <typeparam name="T">An implemented class or <see cref="interface"/> of <see cref="IBehaviour"/></typeparam>
/// <returns>Returns a list of all the matching <see cref="IBehaviour"/>s found in the <see cref="IBehaviourController"/>.</returns>
IList<T> GetBehaviours<T>() where T : IBehaviour;
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, <see cref="null"/>.</returns>
T? GetBehaviour<T>();
/// <summary>
/// Removes the <see cref="IBehaviour"/> found in the <see cref="IBehaviourController"/>.
/// Gets all <see cref="IBehaviour"/>s of the specified type.
/// </summary>
/// <param name="removeAll">If all of the instances of the given Type is to be removed or not.</param>
/// <typeparam name="T">An implemented class or <see cref="interface"/> of <see cref="IBehaviour"/></typeparam>
void RemoveBehaviour<T>(bool removeAll = false) where T : IBehaviour;
/// <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>
IReadOnlyList<T> GetBehaviours<T>();
/// <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.
/// Gets all <see cref="IBehaviour"/>s of the specified type and stores them in the provided list.
/// </summary>
/// <param name="gameTime"><see cref="GameTime"/> information from the game.</param>
void Update(GameTime gameTime);
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <param name="results">The list to store the <see cref="IBehaviour"/>s.</param>
void GetBehaviours<T>(IList<T> results);
/// <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.
/// Removes <see cref="IBehaviour"/>s of the specified type from the <see cref="IBehaviourController"/>.
/// </summary>
/// <param name="gameTime"><see cref="GameTime"/> information from the game.</param>
void UpdatePreDraw(GameTime gameTime);
/// <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;
/// <summary>
/// Removes the specified <see cref="IBehaviour"/> from the <see cref="IBehaviourController"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to remove.</typeparam>
/// <param name="behaviour">The <see cref="IBehaviour"/> to remove.</param>
void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour;
readonly record struct BehaviourAddedArguments(IBehaviour BehaviourAdded);
readonly record struct BehaviourRemovedArguments(IBehaviour BehaviourRemoved);
}

View File

@@ -1,24 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
public interface ICamera
{
Action<ICamera>? OnMatrixTransformChanged { get; set; }
Action<ICamera>? OnViewportChanged { get; set; }
Action<ICamera>? OnPositionChanged { get; set; }
Action<ICamera>? OnRotationChanged { get; set; }
Action<ICamera>? OnZoomChanged { get; set; }
Matrix MatrixTransform { get; }
Viewport Viewport { get; set; }
Vector2 Position { get; set; }
float Rotation { get; set; }
float Zoom { get; set; }
void Update();
}

View File

@@ -0,0 +1,26 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a 2D camera in the engine.
/// </summary>
public interface ICamera2D : IBehaviour2D
{
/// <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

@@ -0,0 +1,6 @@
namespace Syntriax.Engine.Core;
public interface ICoroutineYield
{
bool Yield();
}

View File

@@ -1,8 +0,0 @@
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
public interface IDisplayable
{
public void Draw(SpriteBatch spriteBatch);
}

View File

@@ -1,5 +1,20 @@
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public interface IEntity : IInitialize, IAssignableStateEnable
/// <summary>
/// Represents a basic entity in the engine.
/// </summary>
public interface IEntity : IInitializable, IHasStateEnable
{
/// <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>
Event<IEntity, IdChangedArguments> OnIdChanged { get; }
/// <summary>
/// The ID of the <see cref="IEntity"/>.
/// </summary>
string Id { get; set; }
readonly record struct IdChangedArguments(string PreviousId);
}

View File

@@ -1,12 +0,0 @@
using System;
using Microsoft.Xna.Framework;
namespace Syntriax.Engine.Core.Abstract;
public interface IGameObject : IEntity, IAssignableTransform, IAssignableBehaviourController, INameable, IInitialize
{
Action<IGameObject, GameTime>? OnUpdated { get; set; }
void Update(GameTime time);
}

View File

@@ -0,0 +1,34 @@
namespace Syntriax.Engine.Core;
/// <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 IInitializable
{
/// <summary>
/// Event triggered when the <see cref="Initialize"/> method is called successfully.
/// </summary>
Event<IInitializable> OnInitialized { get; }
/// <summary>
/// Event triggered when the <see cref="IInitializable"/> method is called successfully.
/// </summary>
Event<IInitializable> OnFinalized { get; }
/// <summary>
/// The value indicating whether the entity has been initialized.
/// </summary>
bool IsInitialized { get; }
/// <summary>
/// Initializes the entity.
/// </summary>
/// <returns><see cref="true"/> if initialization is successful, otherwise <see cref="false"/>.</returns>
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();
}

View File

@@ -1,13 +0,0 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
public interface IInitialize
{
Action<IInitialize>? OnInitialized { get; set; }
Action<IInitialize>? OnFinalized { get; set; }
bool Initialized { get; }
bool Initialize();
bool Finalize();
}

View File

@@ -1,9 +1,19 @@
using System;
namespace Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an entity with a name.
/// </summary>
public interface INameable
{
Action<IEntity>? OnNameChanged { get; set; }
/// <summary>
/// Event triggered when the name of the entity changes.
/// </summary>
Event<INameable, NameChangedArguments> OnNameChanged { get; }
/// <summary>
/// The name of the entity.
/// </summary>
string Name { get; set; }
readonly record struct NameChangedArguments(string PreviousName);
}

View File

@@ -1,13 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core.Abstract;
// TODO Probably gonna have to rethink this
public interface ISprite
{
Action<ISprite>? OnTextureChanged { get; set; }
Texture2D Texture2D { get; set; }
}

View File

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

View File

@@ -1,17 +0,0 @@
using System;
using Microsoft.Xna.Framework;
namespace Syntriax.Engine.Core.Abstract;
public interface ITransform
{
Action<ITransform>? OnPositionChanged { get; set; }
Action<ITransform>? OnScaleChanged { get; set; }
Action<ITransform>? OnRotationChanged { get; set; }
Vector2 Position { get; set; }
Vector2 Scale { get; set; }
float Rotation { get; set; }
}

View File

@@ -0,0 +1,70 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents the transformation properties of an object such as position, scale, and rotation in 2D space.
/// </summary>
public interface ITransform2D : IBehaviour
{
/// <summary>
/// Event triggered when the <see cref="Position"/> of the <see cref="ITransform2D"/> changes.
/// </summary>
Event<ITransform2D, PositionChangedArguments> OnPositionChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Scale"/> of the <see cref="ITransform2D"/> changes.
/// </summary>
Event<ITransform2D, ScaleChangedArguments> OnScaleChanged { get; }
/// <summary>
/// Event triggered when the <see cref="Rotation"/> of the <see cref="ITransform"/> changes.
/// </summary>
Event<ITransform2D, RotationChangedArguments> OnRotationChanged { get; }
/// <summary>
/// The world position of the <see cref="ITransform2D"/> in 2D space.
/// </summary>
Vector2D Position { get; set; }
/// <summary>
/// The world scale of the <see cref="ITransform2D"/>.
/// </summary>
Vector2D Scale { get; set; }
/// <summary>
/// The world rotation of the <see cref="ITransform2D"/> in degrees.
/// </summary>
float Rotation { get; set; }
/// <summary>
/// The local position of the <see cref="ITransform2D"/> in 2D space.
/// </summary>
Vector2D LocalPosition { get; set; }
/// <summary>
/// The local scale of the <see cref="ITransform2D"/>.
/// </summary>
Vector2D LocalScale { get; set; }
/// <summary>
/// The local rotation of the <see cref="ITransform2D"/> in degrees.
/// </summary>
float LocalRotation { get; set; }
/// <summary>
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// </summary>
/// <param name="PreviousPosition">The previous <see cref="Position"/> of the <see cref="ITransform2D"/>.</param>
readonly record struct PositionChangedArguments(Vector2D PreviousPosition);
/// <summary>
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// </summary>
/// <param name="PreviousScale">The previous <see cref="Scale"/> of the <see cref="ITransform2D"/>.</param>
readonly record struct ScaleChangedArguments(Vector2D PreviousScale);
/// <summary>
/// Arguments for the event triggered when the <see cref="ITransform2D"/>'s rotation changes.
/// </summary>
/// <param name="PreviousRotation">The previous <see cref="Rotation"/> of the <see cref="ITransform2D"/>.</param>
readonly record struct RotationChangedArguments(float PreviousRotation);
}

View File

@@ -0,0 +1,110 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a universe responsible for managing <see cref="IUniverseObject"/>s.
/// </summary>
public interface IUniverse : IEntity, IEnumerable<IUniverseObject>
{
/// <summary>
/// Event triggered when <see cref="Update(UniverseTime)"/> is about to be called called on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UpdateArguments> OnPreUpdate { get; }
/// <summary>
/// Event triggered when <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UpdateArguments> OnUpdate { get; }
/// <summary>
/// Event triggered after <see cref="Update(UniverseTime)"/> is called on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UpdateArguments> OnPostUpdate { get; }
/// <summary>
/// Event triggered when <see cref="Draw"/> is about to be called called on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse> OnPreDraw { get; }
/// <summary>
/// Event triggered when <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse> OnDraw { get; }
/// <summary>
/// Event triggered after <see cref="Draw"/> is called on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse> OnPostDraw { get; }
/// <summary>
/// Event triggered when a <see cref="IUniverseObject"/> is registered to the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UniverseObjectRegisteredArguments> OnUniverseObjectRegistered { get; }
/// <summary>
/// Event triggered when a <see cref="IUniverseObject"/> is unregistered from the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, UniverseObjectUnRegisteredArguments> OnUniverseObjectUnRegistered { get; }
/// <summary>
/// Event triggered when <see cref="TimeScale"/> is changed on the <see cref="IUniverse"/>.
/// </summary>
Event<IUniverse, TimeScaleChangedArguments> OnTimeScaleChanged { get; }
/// <summary>
/// Current time scale the <see cref="IUniverse"/> operates on.
/// </summary>
float TimeScale { get; set; }
/// <summary>
/// Contains time data related to this <see cref="IUniverse"/>.
/// </summary>
UniverseTime Time { get; }
/// <summary>
/// Contains unscaled time data related to this <see cref="IUniverse"/>.
/// </summary>
UniverseTime UnscaledTime { get; }
/// <summary>
/// Gets a read-only list of <see cref="IUniverseObject"/>s managed by the <see cref="IUniverse"/>.
/// </summary>
IReadOnlyList<IUniverseObject> UniverseObjects { get; }
/// <summary>
/// Registers an <see cref="IUniverseObject"/> to the <see cref="IUniverse"/>.
/// </summary>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to register.</param>
void Register(IUniverseObject universeObject);
/// <summary>
/// Instantiates a <see cref="IUniverseObject"/> of type T with the given arguments and registers it to the <see cref="IUniverse"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to instantiate.</typeparam>
/// <param name="args">Constructor parameters for the given type of <see cref="IUniverseObject"/>.</param>
/// <returns>The instantiated <see cref="IUniverseObject"/>.</returns>
T InstantiateUniverseObject<T>(params object?[]? args) where T : class, IUniverseObject;
/// <summary>
/// Removes an <see cref="IUniverseObject"/> from the <see cref="IUniverse"/>.
/// </summary>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to remove.</param>
void Remove(IUniverseObject universeObject);
/// <summary>
/// Updates the <see cref="IUniverse"/> with the given delta time.
/// </summary>
/// <param name="universeTime">Delta time.</param>
void Update(UniverseTime universeTime);
/// <summary>
/// Performs operations that should be done to the draw.
/// </summary>
void Draw();
readonly record struct TimeScaleChangedArguments(float PreviousTimeScale);
readonly record struct UpdateArguments(UniverseTime EngineTime);
readonly record struct UniverseObjectRegisteredArguments(IUniverseObject UniverseObjectRegistered);
readonly record struct UniverseObjectUnRegisteredArguments(IUniverseObject UniverseObjectUnregistered);
}

View File

@@ -0,0 +1,131 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an <see cref="IEntity"/> that can enter and exit a universe within the <see cref="IUniverse"/> system.
/// This interface allows for tracking the object's presence in the universe and provides events
/// for notifying when the see enters or exits the universe.
/// </summary>
public interface IUniverseObject : IEntity, IActive, INameable, IHasBehaviourController, IEnumerable<IUniverseObject>
{
/// <summary>
/// Event triggered when the <see cref="IUniverseObject"/> enters the universe.
/// </summary>
Event<IUniverseObject, EnteredUniverseArguments> OnEnteredUniverse { get; }
/// <summary>
/// Event triggered when the <see cref="IUniverseObject"/> exits the universe.
/// </summary>
Event<IUniverseObject, ExitedUniverseArguments> OnExitedUniverse { get; }
/// <summary>
/// Event triggered when the <see cref="Parent"/> of the <see cref="IUniverseObject"/> changes. The second parameter is the old <see cref="IUniverseObject"/>.
/// </summary>
Event<IUniverseObject, ParentChangedArguments> OnParentChanged { get; }
/// <summary>
/// Event triggered when a new <see cref="IUniverseObject"/> is added to the <see cref="Children"/>.
/// </summary>
Event<IUniverseObject, ChildrenAddedArguments> OnChildrenAdded { get; }
/// <summary>
/// Event triggered when an <see cref="IUniverseObject"/> is removed from the <see cref="Children"/>.
/// </summary>
Event<IUniverseObject, ChildrenRemovedArguments> OnChildrenRemoved { get; }
/// <summary>
/// Gets the <see cref="IUniverse"/> this <see cref="IUniverseObject"/> is connected to, if any.
/// </summary>
IUniverse Universe { get; }
/// <summary>
/// Indicates whether the <see cref="IUniverseObject"/> is currently in the universe.
/// </summary>
bool IsInUniverse { get; }
/// <summary>
/// The parent <see cref="IUniverseObject"/> of the <see cref="IUniverseObject"/>.
/// </summary>
IUniverseObject? Parent { get; }
/// <summary>
/// The <see cref="IUniverseObject"/>s that have this <see cref="IUniverseObject"/> as their <see cref="Parent"/>.
/// </summary>
IReadOnlyList<IUniverseObject> Children { get; }
/// <summary>
/// Internal method to handle entering the universe.
/// This should be called by the system to properly manage universe states.
/// </summary>
/// <param name="universe">The <see cref="IUniverse"/> that is managing this universe.</param>
/// <returns>
/// <see cref="true"/> if the <see cref="IUniverseObject"/> successfully entered the universe;
/// <see cref="false"/> if it failed to do so.
/// </returns>
internal bool EnterUniverse(IUniverse universe);
/// <summary>
/// Internal method to handle exiting the universe.
/// This should be called by the system to properly manage universe states.
/// </summary>
/// <returns>
/// <see cref="true"/> if the <see cref="IUniverseObject"/> successfully exited the universe;
/// <see cref="false"/> if it failed to do so.
/// </returns>
internal bool ExitUniverse();
/// <summary>
/// Sets the parent <see cref="IUniverseObject"/> of this <see cref="IUniverseObject"/>.
/// </summary>
/// <param name="universeObject">The parent <see cref="IUniverseObject"/> to set.</param>
void SetParent(IUniverseObject? universeObject);
/// <summary>
/// Adds a child <see cref="IUniverseObject"/> to this <see cref="IUniverseObject"/>.
/// </summary>
/// <param name="universeObject">The child <see cref="IUniverseObject"/> to add.</param>
void AddChild(IUniverseObject universeObject);
/// <summary>
/// Removes a child <see cref="IUniverseObject"/> from this <see cref="IUniverseObject"/>.
/// </summary>
/// <param name="universeObject">The child <see cref="IUniverseObject"/> to remove.</param>
void RemoveChild(IUniverseObject universeObject);
/// <summary>
/// Arguments for the event triggered when the <see cref="IUniverseObject"/> enters the universe of a <see cref="IUniverse">.
/// </summary>
/// <param name="sender">The <see cref="IUniverseObject"/> that entered the universe.</param>
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has entered it's universe.</param>
readonly record struct EnteredUniverseArguments(IUniverse Universe);
/// <summary>
/// Arguments for the event triggered when the <see cref="IUniverseObject"/> exits the universe of a <see cref="IUniverse">.
/// </summary>
/// <param name="sender">The <see cref="IUniverseObject"/> that exited the universe.</param>
/// <param name="universe">The <see cref="IUniverse"/> that the <see cref="IUniverseObject"/> has exited it's universe.</param>
readonly record struct ExitedUniverseArguments(IUniverse Universe);
/// <summary>
/// Arguments for the event triggered when the <see cref="IUniverseObject"/>'s parent changes.
/// </summary>
/// <param name="sender">The <see cref="IUniverseObject"/> that the parent has changed.</param>
/// <param name="previousParent">The previous <see cref="IUniverseObject"/> the sender was a child of.</param>
/// <param name="newParent">The new and current <see cref="IUniverseObject"/> the sender is a child of.</param>
readonly record struct ParentChangedArguments(IUniverseObject? PreviousParent, IUniverseObject? CurrentParent);
/// <summary>
/// Arguments for the event triggered when a new <see cref="IUniverseObject"/> added as a child.
/// </summary>
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
readonly record struct ChildrenAddedArguments(IUniverseObject ChildrenAdded);
/// <summary>
/// Delegate for the event triggered when a new <see cref="IUniverseObject"/> removed from being a child.
/// </summary>
/// <param name="sender">The parent <see cref="IUniverseObject"/> this event is being called from.</param>
/// <param name="childrenAdded">The <see cref="IUniverseObject"/> that got removed as a children of the sender <see cref="IUniverseObject"/>.</param>
readonly record struct ChildrenRemovedArguments(IUniverseObject ChildrenRemoved);
}

View File

@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
public class ActiveBehaviourCollector<T> : IBehaviourCollector<T> where T : class, IBehaviour
{
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
public Event<IAssignable>? OnUnassigned { get; } = new();
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler cachedOnBehaviourAdded = null!;
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler cachedOnBehaviourRemoved = null!;
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler cachedOnBehaviourStateChanged = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler cachedOnUniverseObjectRegistered = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler cachedOnUniverseObjectUnregistered = null!;
private readonly List<T> monitoringBehaviours = new(32);
protected readonly List<T> activeBehaviours = new(32);
protected readonly Dictionary<IActive, T> monitoringActiveToBehaviour = new(32);
public IUniverse Universe { get; private set; } = null!;
private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments arguments)
{
IUniverseObject universeObject = arguments.UniverseObjectRegistered;
universeObject.BehaviourController.OnBehaviourAdded.AddListener(cachedOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.AddListener(cachedOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourAdded(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverse.UniverseObjectUnRegisteredArguments arguments)
{
IUniverseObject universeObject = arguments.UniverseObjectUnregistered;
universeObject.BehaviourController.OnBehaviourAdded.RemoveListener(cachedOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.RemoveListener(cachedOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviourController.BehaviourAddedArguments arguments)
{
if (arguments.BehaviourAdded is not T tBehaviour)
return;
monitoringBehaviours.Add(tBehaviour);
monitoringActiveToBehaviour.Add(tBehaviour, tBehaviour);
tBehaviour.OnActiveChanged.AddListener(cachedOnBehaviourStateChanged);
OnBehaviourStateChanged(tBehaviour, new(!tBehaviour.IsActive));
}
private void OnBehaviourStateChanged(IActive sender, IActive.ActiveChangedArguments arguments)
{
T behaviour = monitoringActiveToBehaviour[sender];
if (sender.IsActive)
{
activeBehaviours.Add(behaviour);
OnBehaviourAdd(behaviour);
OnCollected?.Invoke(this, new(behaviour));
}
else if (activeBehaviours.Remove(behaviour))
{
OnBehaviourRemove(behaviour);
OnRemoved?.Invoke(this, new(behaviour));
}
}
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments arguments)
{
if (arguments.BehaviourRemoved is not T tBehaviour)
return;
if (!monitoringBehaviours.Remove(tBehaviour) || !monitoringActiveToBehaviour.Remove(tBehaviour))
return;
tBehaviour.OnActiveChanged.RemoveListener(cachedOnBehaviourStateChanged);
if (activeBehaviours.Remove(tBehaviour))
{
OnBehaviourRemove(tBehaviour);
OnRemoved?.Invoke(this, new(tBehaviour));
}
}
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(cachedOnUniverseObjectRegistered);
universe.OnUniverseObjectUnRegistered.AddListener(cachedOnUniverseObjectUnregistered);
Universe = universe;
OnUniverseAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(cachedOnUniverseObjectRegistered);
Universe.OnUniverseObjectUnRegistered.RemoveListener(cachedOnUniverseObjectUnregistered);
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
public int Count => activeBehaviours.Count;
public T this[Index index] => activeBehaviours[index];
public ActiveBehaviourCollector()
{
cachedOnBehaviourAdded = OnBehaviourAdded;
cachedOnBehaviourRemoved = OnBehaviourRemoved;
cachedOnBehaviourStateChanged = OnBehaviourStateChanged;
cachedOnUniverseObjectRegistered = OnUniverseObjectRegistered;
cachedOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Syntriax.Engine.Core;
public class ActiveBehaviourCollectorSorted<T> : ActiveBehaviourCollector<T> where T : class, IBehaviour
{
private Comparison<T>? _sortBy = null;
public Comparison<T>? SortBy
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null)
activeBehaviours.Sort(value);
}
}
protected override void OnBehaviourAdd(IBehaviour behaviour)
{
if (SortBy is not null)
activeBehaviours.Sort(SortBy);
}
}

109
Engine.Core/BaseEntity.cs Normal file
View File

@@ -0,0 +1,109 @@
using System;
namespace Syntriax.Engine.Core;
public abstract class BaseEntity : IEntity
{
public Event<IEntity, IEntity.IdChangedArguments> OnIdChanged { get; } = new();
public Event<IInitializable> OnInitialized { get; } = new();
public Event<IInitializable> OnFinalized { get; } = new();
public Event<IHasStateEnable> OnStateEnableAssigned { get; } = new();
public Event<IAssignable> OnUnassigned { get; } = new();
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private string _id = string.Empty;
public virtual IStateEnable StateEnable => _stateEnable;
public string Id
{
get => _id;
set
{
if (IsInitialized)
throw new($"Can't change {nameof(Id)} of {_id} because it's initialized");
if (value == _id)
return;
string previousId = _id;
_id = value;
OnIdChanged?.Invoke(this, new(previousId));
}
}
public bool IsInitialized
{
get => _initialized;
private set
{
if (value == _initialized)
return;
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
}
protected virtual void OnAssign(IStateEnable stateEnable) { }
public bool Assign(IStateEnable stateEnable)
{
if (IsInitialized)
return false;
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnAssign(stateEnable);
OnStateEnableAssigned?.Invoke(this);
return true;
}
protected virtual void UnassignInternal() { }
public bool Unassign()
{
if (IsInitialized)
return false;
UnassignInternal();
_stateEnable = null!;
_stateEnable.Unassign();
OnUnassigned?.Invoke(this);
return true;
}
protected virtual void InitializeInternal() { }
public bool Initialize()
{
if (IsInitialized)
return false;
_stateEnable ??= Factory.StateEnableFactory.Instantiate(this);
InitializeInternal();
IsInitialized = true;
return true;
}
protected virtual void FinalizeInternal() { }
public bool Finalize()
{
if (!IsInitialized)
return false;
FinalizeInternal();
IsInitialized = false;
return true;
}
protected BaseEntity() => _id = Guid.NewGuid().ToString("D");
protected BaseEntity(string id) => _id = id;
}

View File

@@ -1,110 +1,146 @@
using System;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
public abstract class Behaviour : IBehaviour
public abstract class Behaviour : BehaviourBase, IFirstFrameUpdate,
IPreUpdate, IUpdate, IPostUpdate,
IPreDraw, IDraw, IPostDraw
{
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableStateEnable>? OnStateEnableAssigned { get; set; } = null;
public Action<IAssignableBehaviourController>? OnBehaviourControllerAssigned { get; set; } = null;
protected IUniverse Universe => BehaviourController.UniverseObject.Universe;
protected IUniverseObject UniverseObject => BehaviourController.UniverseObject;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IBehaviour>? OnPriorityChanged { get; set; } = null;
private readonly Event<IUniverseObject, IUniverseObject.EnteredUniverseArguments>.EventHandler cachedEnteredUniverse = null!;
private readonly Event<IUniverseObject, IUniverseObject.ExitedUniverseArguments>.EventHandler cachedExitedUniverse = null!;
private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!;
private bool _initialized = false;
private int _priority = 0;
public IStateEnable StateEnable => _stateEnable;
public IBehaviourController BehaviourController => _behaviourController;
public bool Initialized
public Behaviour()
{
get => _initialized;
private set
{
if (value == _initialized)
return;
OnInitialized.AddListener(OnInitialize);
OnFinalized.AddListener(OnFinalize);
OnUnassigned.AddListener(OnUnassign);
_initialized = value;
if (value)
OnInitialized?.Invoke(this);
else
OnFinalized?.Invoke(this);
}
cachedEnteredUniverse = EnteredUniverse;
cachedExitedUniverse = ExitedUniverse;
}
public int Priority
{
get => _priority;
set
{
if (value == _priority)
return;
protected virtual void OnUnassign() { }
protected virtual void OnUnassign(IAssignable assignable) => OnUnassign();
_priority = value;
OnPriorityChanged?.Invoke(this);
}
protected virtual void OnInitialize() { }
protected virtual void OnInitialize(IInitializable _)
{
BehaviourController.UniverseObject.OnEnteredUniverse.AddListener(cachedEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.AddListener(cachedExitedUniverse);
OnInitialize();
if (UniverseObject.IsInUniverse)
EnteredUniverse(UniverseObject, new(Universe));
}
public bool Assign(IStateEnable stateEnable)
protected virtual void OnFinalize() { }
protected virtual void OnFinalize(IInitializable _)
{
if (Initialized)
return false;
BehaviourController.UniverseObject.OnEnteredUniverse.RemoveListener(cachedEnteredUniverse);
BehaviourController.UniverseObject.OnExitedUniverse.RemoveListener(cachedExitedUniverse);
_stateEnable = stateEnable;
_stateEnable.Assign(this);
OnStateEnableAssigned?.Invoke(this);
return true;
OnFinalize();
if (UniverseObject.IsInUniverse)
ExitedUniverse(UniverseObject, new(Universe));
}
public bool Assign(IBehaviourController behaviourController)
protected virtual void OnFirstActiveFrame() { }
void IFirstFrameUpdate.FirstActiveFrame()
{
if (Initialized)
return false;
_behaviourController = behaviourController;
OnBehaviourControllerAssigned?.Invoke(this);
return true;
Debug.Assert.AssertInitialized(this);
OnFirstActiveFrame();
}
public bool Unassign()
protected virtual void OnPreUpdatePreActiveCheck() { }
protected virtual void OnPreUpdate() { }
void IPreUpdate.PreUpdate()
{
if (Initialized)
return false;
Debug.Assert.AssertInitialized(this);
_stateEnable = null!;
_behaviourController = null!;
OnPreUpdatePreActiveCheck();
OnUnassigned?.Invoke(this);
return true;
if (!IsActive)
return;
OnPreUpdate();
}
public bool Initialize()
protected virtual void OnUpdatePreActiveCheck() { }
protected virtual void OnUpdate() { }
void IUpdate.Update()
{
if (Initialized)
return false;
Debug.Assert.AssertInitialized(this);
NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, _stateEnable);
OnUpdatePreActiveCheck();
Initialized = true;
return true;
if (!IsActive)
return;
OnUpdate();
}
public bool Finalize()
protected virtual void OnPostUpdatePreActiveCheck() { }
protected virtual void OnPostUpdate() { }
void IPostUpdate.PostUpdate()
{
if (!Initialized)
return false;
Debug.Assert.AssertInitialized(this);
Initialized = false;
return true;
OnPostUpdatePreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPostUpdate();
}
protected virtual void OnPreDrawPreActiveCheck() { }
protected virtual void OnPreDraw() { }
void IPreDraw.PreDraw()
{
Debug.Assert.AssertInitialized(this);
OnPreDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPreDraw();
}
protected virtual void OnDrawPreActiveCheck() { }
protected virtual void OnDraw() { }
void IDraw.Draw()
{
Debug.Assert.AssertInitialized(this);
OnDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnDraw();
}
protected virtual void OnPostDrawPreActiveCheck() { }
protected virtual void OnPostDraw() { }
void IPostDraw.PostDraw()
{
Debug.Assert.AssertInitialized(this);
OnPostDrawPreActiveCheck();
if (!StateEnable.Enabled)
return;
OnPostDraw();
}
protected virtual void OnEnteredUniverse(IUniverse universe) { }
protected virtual void EnteredUniverse(IUniverseObject sender, IUniverseObject.EnteredUniverseArguments arguments) => OnEnteredUniverse(arguments.Universe);
protected virtual void OnExitedUniverse(IUniverse universe) { }
protected virtual void ExitedUniverse(IUniverseObject sender, IUniverseObject.ExitedUniverseArguments arguments) => OnExitedUniverse(arguments.Universe);
}

View File

@@ -0,0 +1,22 @@
namespace Syntriax.Engine.Core;
public abstract class Behaviour2D : Behaviour, IBehaviour2D
{
public ITransform2D Transform { get; private set; } = null!;
protected sealed override void OnInitialize(IInitializable _)
{
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
base.OnInitialize(_);
}
protected sealed override void OnFinalize(IInitializable _)
{
Transform = null!;
base.OnFinalize(_);
}
protected sealed override void OnUnassign(IAssignable assignable) => base.OnUnassign(assignable);
protected sealed override void EnteredUniverse(IUniverseObject sender, IUniverseObject.EnteredUniverseArguments arguments) => base.EnteredUniverse(sender, arguments);
protected sealed override void ExitedUniverse(IUniverseObject sender, IUniverseObject.ExitedUniverseArguments arguments) => base.ExitedUniverse(sender, arguments);
}

View File

@@ -0,0 +1,96 @@
namespace Syntriax.Engine.Core;
[System.Diagnostics.DebuggerDisplay("{GetType().Name, nq}, Priority: {Priority}, Initialized: {Initialized}")]
public abstract class BehaviourBase : BaseEntity, IBehaviour
{
public Event<IBehaviour, IBehaviour.PriorityChangedArguments> OnPriorityChanged { get; } = new();
public Event<IActive, IActive.ActiveChangedArguments> OnActiveChanged { get; } = new();
public Event<IHasBehaviourController> OnBehaviourControllerAssigned { get; } = new();
private readonly Event<IHasUniverseObject>.EventHandler cachedOnUniverseObjectAssigned = null!;
private readonly Event<IActive, IActive.ActiveChangedArguments>.EventHandler cachedOnUniverseObjectActiveChanged = null!;
private readonly Event<IStateEnable, IStateEnable.EnabledChangedArguments>.EventHandler cachedOnStateEnabledChanged = null!;
private IBehaviourController _behaviourController = null!;
public IBehaviourController BehaviourController => _behaviourController;
private int _priority = 0;
public int Priority
{
get => _priority;
set
{
if (value == _priority)
return;
int previousPriority = _priority;
_priority = value;
OnPriorityChanged?.Invoke(this, new(previousPriority));
}
}
private bool _isActive = false;
public bool IsActive => _isActive;
protected virtual void OnAssign(IBehaviourController behaviourController) { }
public bool Assign(IBehaviourController behaviourController)
{
if (IsInitialized)
return false;
_behaviourController = behaviourController;
OnAssign(behaviourController);
behaviourController.OnUniverseObjectAssigned.AddListener(cachedOnUniverseObjectAssigned);
if (behaviourController.UniverseObject is not null)
OnUniverseObjectAssigned(behaviourController);
OnBehaviourControllerAssigned?.Invoke(this);
return true;
}
private void OnUniverseObjectAssigned(IHasUniverseObject sender)
{
sender.UniverseObject.OnActiveChanged.AddListener(cachedOnUniverseObjectActiveChanged);
UpdateActive();
}
protected override void OnAssign(IStateEnable stateEnable)
{
base.OnAssign(stateEnable);
stateEnable.OnEnabledChanged.AddListener(cachedOnStateEnabledChanged);
}
protected override void UnassignInternal()
{
BehaviourController.UniverseObject.OnActiveChanged.RemoveListener(cachedOnUniverseObjectActiveChanged);
StateEnable.OnEnabledChanged.RemoveListener(cachedOnStateEnabledChanged);
BehaviourController.OnUniverseObjectAssigned.RemoveListener(cachedOnUniverseObjectAssigned);
base.UnassignInternal();
_behaviourController = null!;
}
protected override void InitializeInternal()
{
Debug.Assert.AssertBehaviourControllerAssigned(this);
Debug.Assert.AssertStateEnableAssigned(this);
}
private void OnStateEnabledChanged(IStateEnable sender, IStateEnable.EnabledChangedArguments arguments) => UpdateActive();
private void OnUniverseObjectActiveChanged(IActive sender, IActive.ActiveChangedArguments arguments) => UpdateActive();
private void UpdateActive()
{
bool previousActive = IsActive;
_isActive = StateEnable.Enabled && _behaviourController.UniverseObject.IsActive;
if (previousActive != IsActive)
OnActiveChanged?.Invoke(this, new(previousActive));
}
protected BehaviourBase()
{
cachedOnUniverseObjectAssigned = OnUniverseObjectAssigned;
cachedOnUniverseObjectActiveChanged = OnUniverseObjectActiveChanged;
cachedOnStateEnabledChanged = OnStateEnabledChanged;
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
public class BehaviourCollector<T> : IBehaviourCollector<T> where T : class
{
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourCollectedArguments> OnCollected { get; } = new();
public Event<IBehaviourCollector<T>, IBehaviourCollector<T>.BehaviourRemovedArguments> OnRemoved { get; } = new();
public Event<IHasUniverse> OnUniverseAssigned { get; } = new();
public Event<IAssignable>? OnUnassigned { get; } = new();
private readonly Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments>.EventHandler cachedOnBehaviourAdded = null!;
private readonly Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments>.EventHandler cachedOnBehaviourRemoved = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectRegisteredArguments>.EventHandler cachedOnUniverseObjectRegistered = null!;
private readonly Event<IUniverse, IUniverse.UniverseObjectUnRegisteredArguments>.EventHandler cachedOnUniverseObjectUnregistered = null!;
protected readonly List<T> behaviours = new(32);
public IUniverse Universe { get; private set; } = null!;
private void OnUniverseObjectRegistered(IUniverse manager, IUniverse.UniverseObjectRegisteredArguments arguments)
{
IUniverseObject universeObject = arguments.UniverseObjectRegistered;
universeObject.BehaviourController.OnBehaviourAdded.AddListener(cachedOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.AddListener(cachedOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourAdded(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
private void OnUniverseObjectUnregistered(IUniverse manager, IUniverse.UniverseObjectUnRegisteredArguments arguments)
{
IUniverseObject universeObject = arguments.UniverseObjectUnregistered;
universeObject.BehaviourController.OnBehaviourAdded.RemoveListener(cachedOnBehaviourAdded);
universeObject.BehaviourController.OnBehaviourRemoved.RemoveListener(cachedOnBehaviourRemoved);
for (int i = 0; i < universeObject.BehaviourController.Count; i++)
OnBehaviourRemoved(universeObject.BehaviourController, new(universeObject.BehaviourController[i]));
}
protected virtual void OnBehaviourAdd(IBehaviour behaviour) { }
private void OnBehaviourAdded(IBehaviourController controller, IBehaviourController.BehaviourAddedArguments arguments)
{
if (arguments.BehaviourAdded is not T tBehaviour)
return;
behaviours.Add(tBehaviour);
OnBehaviourAdd(arguments.BehaviourAdded);
OnCollected?.Invoke(this, new(tBehaviour));
}
protected virtual void OnBehaviourRemove(IBehaviour behaviour) { }
private void OnBehaviourRemoved(IBehaviourController controller, IBehaviourController.BehaviourRemovedArguments arguments)
{
if (arguments.BehaviourRemoved is not T tBehaviour)
return;
if (!behaviours.Remove(tBehaviour))
return;
OnBehaviourRemove(arguments.BehaviourRemoved);
OnRemoved?.Invoke(this, new(tBehaviour));
}
protected virtual void OnAssign(IUniverse universe) { }
public bool Assign(IUniverse universe)
{
if (Universe is not null)
return false;
foreach (IUniverseObject universeObject in universe.UniverseObjects)
OnUniverseObjectRegistered(universe, new(universeObject));
universe.OnUniverseObjectRegistered.AddListener(cachedOnUniverseObjectRegistered);
universe.OnUniverseObjectUnRegistered.AddListener(cachedOnUniverseObjectUnregistered);
Universe = universe;
OnAssign(universe);
OnUniverseAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Universe is null)
return false;
foreach (IUniverseObject universeObject in Universe.UniverseObjects)
OnUniverseObjectUnregistered(Universe, new(universeObject));
Universe.OnUniverseObjectRegistered.RemoveListener(cachedOnUniverseObjectRegistered);
Universe.OnUniverseObjectUnRegistered.RemoveListener(cachedOnUniverseObjectUnregistered);
Universe = null!;
OnUnassigned?.Invoke(this);
return true;
}
public int Count => behaviours.Count;
public T this[Index index] => behaviours[index];
public BehaviourCollector()
{
cachedOnBehaviourAdded = OnBehaviourAdded;
cachedOnBehaviourRemoved = OnBehaviourRemoved;
cachedOnUniverseObjectRegistered = OnUniverseObjectRegistered;
cachedOnUniverseObjectUnregistered = OnUniverseObjectUnregistered;
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Syntriax.Engine.Core;
public class BehaviourCollectorSorted<T> : BehaviourCollector<T> where T : class
{
private Comparison<T>? _sortBy = null;
public Comparison<T>? SortBy
{
get => _sortBy;
set
{
_sortBy = value;
if (value is not null)
behaviours.Sort(value);
}
}
protected override void OnBehaviourAdd(IBehaviour behaviour)
{
if (SortBy is not null)
behaviours.Sort(SortBy);
}
}

View File

@@ -1,128 +1,132 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core.Abstract;
using System.Linq;
namespace Syntriax.Engine.Core;
public class BehaviourController : IBehaviourController
[System.Diagnostics.DebuggerDisplay("Behaviour Count: {behaviours.Count}")]
public class BehaviourController : BaseEntity, IBehaviourController
{
public Action<IBehaviourController, GameTime>? OnUpdate { get; set; } = null;
public Action<IBehaviourController, GameTime>? OnPreDraw { get; set; } = null;
public Event<IBehaviourController, IBehaviourController.BehaviourAddedArguments> OnBehaviourAdded { get; } = new();
public Event<IBehaviourController, IBehaviourController.BehaviourRemovedArguments> OnBehaviourRemoved { get; } = new();
public Event<IHasUniverseObject> OnUniverseObjectAssigned { get; } = new();
public Action<IBehaviourController, IBehaviour>? OnBehaviourAdded { get; set; } = null;
public Action<IBehaviourController, IBehaviour>? OnBehaviourRemoved { get; set; } = null;
public Action<IAssignable>? OnUnassigned { get; set; } = null;
public Action<IAssignableGameObject>? OnGameObjectAssigned { get; set; } = null;
private readonly List<IBehaviour> behaviours = new(Constants.BEHAVIOURS_SIZE_INITIAL);
private IUniverseObject _universeObject = null!;
private readonly IList<IBehaviour> behaviours = new List<IBehaviour>(Constants.BEHAVIOURS_SIZE_INITIAL);
private IGameObject _gameObject = null!;
public IGameObject GameObject => _gameObject;
public IUniverseObject UniverseObject => _universeObject;
public int Count => behaviours.Count;
public IBehaviour this[Index index] => behaviours[index];
public T AddBehaviour<T>(T behaviour) where T : class, IBehaviour
{
InsertBehaviourByPriority(behaviour);
behaviour.Initialize();
behaviour.OnPriorityChanged += OnPriorityChange;
OnBehaviourAdded?.Invoke(this, behaviour);
behaviour.Assign(this);
if (IsInitialized)
behaviour.Initialize();
behaviour.OnPriorityChanged.AddListener(OnPriorityChange);
OnBehaviourAdded?.Invoke(this, new(behaviour));
return behaviour;
}
public T AddBehaviour<T>(params object?[]? args) where T : class, IBehaviour
=> AddBehaviour(new Factory.BehaviourFactory().Instantiate<T>(_gameObject, args));
public bool TryGetBehaviour<T>([NotNullWhen(returnValue: true)] out T? behaviour)
{
foreach (var behaviourItem in behaviours)
{
if (behaviourItem is not T result)
continue;
behaviour = result;
return true;
}
behaviour = default;
return false;
T behaviour = Factory.BehaviourFactory.Instantiate<T>(args);
return AddBehaviour(behaviour);
}
public IList<T> GetBehaviours<T>() where T : IBehaviour
public T? GetBehaviour<T>()
{
IList<T> behaviours = new List<T>();
foreach (var behaviourItem in this.behaviours)
foreach (IBehaviour behaviourItem in behaviours)
if (behaviourItem is T result)
return result;
return default;
}
public IReadOnlyList<T> GetBehaviours<T>()
{
List<T>? behaviours = null;
foreach (IBehaviour behaviourItem in this.behaviours)
{
if (behaviourItem is not T behaviour)
continue;
behaviours ??= new List<T>();
behaviours ??= [];
behaviours.Add(behaviour);
}
return behaviours;
return behaviours ?? Enumerable.Empty<T>().ToList();
}
public void RemoveBehaviour<T>(bool removeAll = false) where T : IBehaviour
public void GetBehaviours<T>(IList<T> results)
{
results.Clear();
foreach (IBehaviour behaviourItem in behaviours)
{
if (behaviourItem is not T behaviour)
continue;
results.Add(behaviour);
}
}
public void RemoveBehaviour<T>(bool removeAll = false) where T : class, IBehaviour
{
for (int i = behaviours.Count; i >= 0; i--)
{
if (behaviours[i] is not T behaviour)
continue;
behaviour.OnPriorityChanged -= OnPriorityChange;
behaviour.Finalize();
behaviours.RemoveAt(i);
OnBehaviourRemoved?.Invoke(this, behaviour);
RemoveBehaviour(behaviour);
if (!removeAll)
return;
}
}
public bool Assign(IGameObject gameObject)
public void RemoveBehaviour<T>(T behaviour) where T : class, IBehaviour
{
if (GameObject is not null && GameObject.Initialized)
if (!behaviours.Contains(behaviour))
throw new Exception($"{behaviour.GetType().Name} does not exist in {UniverseObject.Name}'s {nameof(IBehaviourController)}.");
behaviour.OnPriorityChanged.RemoveListener(OnPriorityChange);
behaviour.Finalize();
behaviours.Remove(behaviour);
OnBehaviourRemoved?.Invoke(this, new(behaviour));
}
protected virtual void OnAssign(IUniverseObject universeObject) { }
public bool Assign(IUniverseObject universeObject)
{
if (UniverseObject is not null && UniverseObject.IsInitialized)
return false;
_gameObject = gameObject;
OnGameObjectAssigned?.Invoke(this);
_universeObject = universeObject;
OnAssign(universeObject);
OnUniverseObjectAssigned?.Invoke(this);
return true;
}
public bool Unassign()
protected override void InitializeInternal()
{
if (GameObject is not null && GameObject.Initialized)
return false;
Debug.Assert.AssertUniverseObjectAssigned(this);
_gameObject = null!;
OnUnassigned?.Invoke(this);
return true;
foreach (IBehaviour behaviour in behaviours)
behaviour.Initialize();
}
public void Update(GameTime gameTime)
protected override void FinalizeInternal()
{
if (!GameObject.StateEnable.Enabled)
return;
OnUpdate?.Invoke(this, gameTime);
}
public void UpdatePreDraw(GameTime gameTime)
{
if (!GameObject.StateEnable.Enabled)
return;
OnPreDraw?.Invoke(this, gameTime);
foreach (IBehaviour behaviour in behaviours)
behaviour.Finalize();
}
public BehaviourController() { }
public BehaviourController(IGameObject gameObject) => Assign(gameObject);
public BehaviourController(IUniverseObject universeObject) => Assign(universeObject);
private void InsertBehaviourByPriority<T>(T behaviour) where T : class, IBehaviour
{
@@ -141,9 +145,9 @@ public class BehaviourController : IBehaviourController
behaviours.Add(behaviour);
}
private void OnPriorityChange(IBehaviour behaviour)
private void OnPriorityChange(IBehaviour sender, IBehaviour.PriorityChangedArguments arguments)
{
behaviours.Remove(behaviour);
InsertBehaviourByPriority(behaviour);
behaviours.Remove(sender);
InsertBehaviourByPriority(sender);
}
}

View File

@@ -1,62 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public abstract class BehaviourOverride : Behaviour
{
protected IGameObject GameObject => BehaviourController.GameObject;
protected ITransform Transform => BehaviourController.GameObject.Transform;
public BehaviourOverride()
{
OnInitialized += OnInitialize;
OnFinalized += OnFinalize;
OnUnassigned += OnUnassign;
}
protected virtual void OnUnassign() { }
private void OnUnassign(IAssignable assignable) => OnUnassign();
protected virtual void OnInitialize() { }
private void OnInitialize(IInitialize _)
{
BehaviourController.OnPreDraw += PreDraw;
BehaviourController.OnUpdate += Update;
OnInitialize();
}
protected virtual void OnFinalize() { }
private void OnFinalize(IInitialize _)
{
BehaviourController.OnPreDraw -= PreDraw;
BehaviourController.OnUpdate -= Update;
OnFinalize();
}
protected virtual void OnUpdatePreEnabledCheck(GameTime time) { }
protected virtual void OnUpdate(GameTime time) { }
private void Update(IBehaviourController _, GameTime time)
{
OnUpdatePreEnabledCheck(time);
if (!StateEnable.Enabled)
return;
OnUpdate(time);
}
protected virtual void OnPreDrawPreEnabledCheck(GameTime time) { }
protected virtual void OnPreDraw(GameTime time) { }
private void PreDraw(IBehaviourController _, GameTime time)
{
OnPreDrawPreEnabledCheck(time);
if (!StateEnable.Enabled)
return;
OnPreDraw(time);
}
}

View File

@@ -1,98 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core;
public class CameraBehaviour : BehaviourOverride, ICamera
{
public Action<ICamera>? OnPositionChanged { get; set; } = null;
public Action<ICamera>? OnMatrixTransformChanged { get; set; } = null;
public Action<ICamera>? OnViewportChanged { get; set; } = null;
public Action<ICamera>? OnRotationChanged { get; set; } = null;
public Action<ICamera>? OnZoomChanged { get; set; } = null;
private Matrix _matrixTransform = Matrix.Identity;
private Viewport _viewport = default;
private float _zoom = 1f;
public Matrix MatrixTransform
{
get => _matrixTransform;
set
{
if (_matrixTransform == value)
return;
_matrixTransform = value;
OnMatrixTransformChanged?.Invoke(this);
}
}
public Vector2 Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport
{
get => _viewport;
set
{
if (_viewport.Equals(value))
return;
_viewport = value;
OnViewportChanged?.Invoke(this);
}
}
public float Zoom
{
get => _zoom;
set
{
float newValue = value >= .1f ? value : .1f;
if (_zoom == newValue)
return;
_zoom = newValue;
OnZoomChanged?.Invoke(this);
}
}
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
public void Update()
{
MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f));
}
protected override void OnInitialize()
{
Transform.OnRotationChanged += OnTransformRotationChanged;
Transform.OnPositionChanged += OnTransformPositionChanged;
}
protected override void OnFinalize()
{
Transform.OnRotationChanged -= OnTransformRotationChanged;
Transform.OnPositionChanged -= OnTransformPositionChanged;
}
private void OnTransformRotationChanged(ITransform _) => OnRotationChanged?.Invoke(this);
private void OnTransformPositionChanged(ITransform _) => OnPositionChanged?.Invoke(this);
}

View File

@@ -0,0 +1,42 @@
using System.Collections;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
public class CoroutineManager : UniverseObject
{
private readonly List<IEnumerator> enumerators = [];
public IEnumerator StartCoroutine(IEnumerator enumerator)
{
enumerators.Add(enumerator);
return enumerator;
}
public void StopCoroutine(IEnumerator enumerator)
{
enumerators.Remove(enumerator);
}
protected override void OnEnteringUniverse(IUniverse universe)
{
universe.OnUpdate.AddListener(OnUpdate);
}
protected override void OnExitingUniverse(IUniverse universe)
{
universe.OnUpdate.RemoveListener(OnUpdate);
}
private void OnUpdate(IUniverse sender, IUniverse.UpdateArguments arguments)
{
for (int i = enumerators.Count - 1; i >= 0; i--)
{
if (enumerators[i].Current is ICoroutineYield coroutineYield && coroutineYield.Yield())
continue;
if (!enumerators[i].MoveNext())
enumerators.RemoveAt(i);
}
}
}

View File

@@ -0,0 +1,10 @@
using System;
namespace Syntriax.Engine.Core;
public class CoroutineYield(Func<bool> condition) : ICoroutineYield
{
private readonly Func<bool> condition = condition;
public bool Yield() => condition.Invoke();
}

View File

@@ -0,0 +1,30 @@
using System.Runtime.CompilerServices;
namespace Syntriax.Engine.Core.Debug;
public static class Assert
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertInitialized(IInitializable initializable)
=> System.Diagnostics.Debug.Assert(initializable.IsInitialized, $"{initializable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertBehaviourControllerAssigned(IHasBehaviourController assignable)
=> System.Diagnostics.Debug.Assert(assignable.BehaviourController is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertEntityAssigned(IHasEntity assignable)
=> System.Diagnostics.Debug.Assert(assignable.Entity is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertUniverseAssigned(IHasUniverse assignable)
=> System.Diagnostics.Debug.Assert(assignable.Universe is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertUniverseObjectAssigned(IHasUniverseObject assignable)
=> System.Diagnostics.Debug.Assert(assignable.UniverseObject is not null, $"{assignable.GetType().Name} must be initialized");
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AssertStateEnableAssigned(IHasStateEnable assignable)
=> System.Diagnostics.Debug.Assert(assignable.StateEnable is not null, $"{assignable.GetType().Name} must be initialized");
}

View File

@@ -0,0 +1,8 @@
using System;
namespace Syntriax.Engine.Core.Debug;
public class ConsoleLogger : LoggerBase
{
protected override void Write(string message) => Console.WriteLine(message);
}

View File

@@ -0,0 +1,20 @@
using System;
using System.IO;
namespace Syntriax.Engine.Core.Debug;
public class FileLogger : LoggerBase
{
public readonly string FilePath;
public FileLogger(string filePath)
{
FilePath = filePath;
File.Open(filePath, FileMode.Create).Close();
}
protected override void Write(string message)
{
File.AppendAllTextAsync(FilePath, $"{message}{Environment.NewLine}");
}
}

View File

@@ -0,0 +1,15 @@
namespace Syntriax.Engine.Core.Debug;
public interface ILogger
{
Level FilterLevel { get; set; }
void Log(string message, Level level = Level.Info, bool force = false);
enum Level
{
Info,
Warning,
Error,
};
}

View File

@@ -0,0 +1,20 @@
using System;
namespace Syntriax.Engine.Core.Debug;
public abstract class LoggerBase : ILogger
{
public ILogger.Level FilterLevel { get; set; } = ILogger.Level.Info;
public void Log(string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
{
if (!force && level < FilterLevel)
return;
string timestamp = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss tt");
Write($"[{timestamp}] [{level}] \t{message}");
}
protected abstract void Write(string message);
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Diagnostics;
namespace Syntriax.Engine.Core.Debug;
public static class LoggerExtensions
{
public static void Log<T>(this ILogger logger, T caller, string message, ILogger.Level level = ILogger.Level.Info, bool force = false)
{
string body = $"{caller?.GetType().Name ?? typeof(T).Name}: {message}";
logger.Log(body, level, force);
}
public static void LogWarning<T>(this ILogger logger, T caller, string message, bool force = false) => Log(logger, caller, message, ILogger.Level.Info, force);
public static void LogError<T>(this ILogger logger, T caller, string message, bool force = false)
{
Log(logger, caller, message, ILogger.Level.Error, force);
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{new StackTrace()}");
}
public static void LogException<T>(this ILogger logger, T caller, Exception exception, bool force = false)
{
Log(logger, caller, $"Message: {exception.Message}", ILogger.Level.Error, force);
Log(logger, caller, $"InnerException: {exception.InnerException}", ILogger.Level.Error, force);
Log(logger, caller, $"{nameof(StackTrace)}:{Environment.NewLine}{exception.StackTrace}");
}
}

View File

@@ -1,12 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>false</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Syntriax.Engine.Core</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,46 @@
@startuml "Core Engine Relations"
top to bottom direction
skinparam linetype ortho
skinparam nodesep 100
title Core Engine Relations
interface Engine.Core.IEntity extends Engine.Core.IInitializable {}
interface Engine.Core.IUniverseObject extends Engine.Core.IEntity, Engine.Core.INameable {}
interface Engine.Core.INameable {}
Engine.Core.IUniverseObject --> Engine.Core.IBehaviourController: has
Engine.Core.IBehaviourController "1" --> "0..*" Engine.Core.IBehaviour: has
interface Engine.Core.IBehaviourController {}
interface Engine.Core.IBehaviour {}
interface Engine.Core.IBehaviour2D extends Engine.Core.IBehaviour {}
interface Engine.Core.IBehaviour3D extends Engine.Core.IBehaviour {}
interface Engine.Core.IUniverse {}
Engine.Core.IUniverse "1" -r-> "0..*" Engine.Core.IUniverseObject: has
' together {
' interface Engine.Core.IAssignable {}
' interface Engine.Core.IHasStateEnable extends Engine.Core.IAssignable {}
' interface Engine.Core.IHasUniverse extends Engine.Core.IAssignable {}
' interface Engine.Core.IHasUniverseObject extends Engine.Core.IAssignable {}
' interface Engine.Core.IHasBehaviourController extends Engine.Core.IAssignable {}
' ' Engine.Core.IHasStateEnable --> Engine.Core.IStateEnable: has
' ' Engine.Core.IHasUniverse --> Engine.Core.IUniverse: has
' ' Engine.Core.IHasUniverseObject --> Engine.Core.IUniverseObject: has
' ' Engine.Core.IHasBehaviourController --> Engine.Core.IBehaviourController: has
' }
together {
interface Engine.Core.ITransform2D {}
interface Engine.Core.ICamera2D {}
interface Engine.Core.ICoroutineYield {}
interface Engine.Core.IStateEnable {}
interface Engine.Core.IInitializable {}
interface Engine.Core.IBehaviourCollector {}
}
@enduml

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

@@ -1,20 +0,0 @@
using System;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core.Exceptions;
public class AssignException : Exception
{
public AssignException() : base("Assign operation has failed.") { }
public AssignException(string? message) : base(message) { }
// public static AssignException FromStateEnable(IStateEnable? stateEnable)
// => new AssignException($"{nameof(IGameObject.AssignStateEnable)} failed on type {stateEnable?.GetType().ToString() ?? "\"null\""}");
public static AssignException From<T, T2>(T to, T2? value)
=> new AssignException($"Assign operation has failed on T: {typeof(T).FullName}, value: {value?.GetType().ToString() ?? "\"null\""}");
// public static AssignException FromBehaviourController(IBehaviourController? behaviourController)
// => new AssignException($"{nameof(IGameObject.AssignBehaviourController)} failed on type {behaviourController?.GetType().ToString() ?? "\"null\""}");
}
// throw new Exception($"{nameof(IGameObject.AssignTransform)} failed on type {transform?.GetType().ToString() ?? "null"} for type {typeof(T).FullName}");
// throw new Exception($"{nameof(IGameObject.AssignBehaviourController)} failed on type {behaviourController?.GetType().ToString() ?? "null"} for type {typeof(T).FullName}");
// throw new Exception($"{nameof(IGameObject.AssignStateEnable)} failed on type {stateEnable?.GetType().ToString() ?? "null"} for type {typeof(T).FullName}");

View File

@@ -0,0 +1,9 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
public class AssignFailedException(string? message) : Exception(message)
{
public static AssignFailedException From<T, T2>(T to, T2? value)
=> new($"Assign operation has failed on T: {to?.GetType().FullName ?? "\"null\""}, value: {value?.GetType().ToString() ?? "\"null\""}");
}

View File

@@ -0,0 +1,3 @@
namespace Syntriax.Engine.Core.Exceptions;
public class BehaviourNotFoundException(string? message) : NotFoundException(message);

View File

@@ -1,21 +1,9 @@
using System;
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core.Exceptions;
public class NotAssignedException : Exception
public class NotAssignedException(string? message) : Exception(message)
{
public NotAssignedException() : base("The object has not been assigned.") { }
public NotAssignedException(string? message) : base(message) { }
public static NotAssignedException From<T1, T2>(T1 to, T2? value) where T1 : IAssignable
=> new NotAssignedException($"{typeof(T2).Name} has not been assigned to {typeof(T1).Name}");
public static void Check<T1, T2>(T1 to, T2? value) where T1 : IAssignable
{
if (value is not null)
return;
throw From(to, value);
}
public static NotAssignedException From<T1, T2>(T1 to, T2? value)
=> new($"{value?.GetType().FullName ?? "\"null\""} has not been assigned to {to?.GetType().FullName ?? "\"null\""}");
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Syntriax.Engine.Core.Exceptions;
public class NotFoundException(string? message) : Exception(message)
{
public static NotAssignedException FromType<T>()
=> new($"{typeof(T).FullName} was not found");
}

View File

@@ -0,0 +1,3 @@
namespace Syntriax.Engine.Core.Exceptions;
public class UniverseObjectNotFoundException(string? message) : NotFoundException(message);

View File

@@ -0,0 +1,182 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
public static class BehaviourControllerExtensions
{
/// <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="behaviourController">The <see cref="IBehaviourController"/> to search in.</param>
/// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found; otherwise, <see cref="false"/>.</returns>
public static bool TryGetBehaviour<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour)
{
behaviour = behaviourController.GetBehaviour<T>();
return behaviour is not null;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in the provided <see cref="IBehaviourController"/>. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T GetRequiredBehaviour<T>(this IBehaviourController behaviourController) where T : class
=> behaviourController.GetBehaviour<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName}");
/// <summary>
/// Gets an existing <see cref="IBehaviour"/> of the specified type, or adds and returns a new one if it doesn't exist.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get or add.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to search in.</param>
/// <param name="args">Optional arguments to pass to the constructor of the <see cref="IBehaviour"/> if a new one is added.</param>
/// <returns>The existing or newly added <see cref="IBehaviour"/> of the specified type.</returns>
public static T GetOrAddBehaviour<T>(this IBehaviourController behaviourController, params object?[]? args) where T : class, IBehaviour
=> behaviourController.GetBehaviour<T>() ?? behaviourController.AddBehaviour<T>(args);
/// <summary>
/// Gets an existing <see cref="IBehaviour"/> of the specified type, or adds and returns the fallback type if it doesn't exist.
/// </summary>
/// <typeparam name="TOriginal">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <typeparam name="TFallback">The type of <see cref="IBehaviour"/> to add. It must be assignable from <typeparamref name="TOriginal"/></typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to search in.</param>
/// <param name="args">Optional arguments to pass to the constructor of the <see cref="IBehaviour"/> if a new one is added.</param>
/// <returns>The existing or newly added <see cref="IBehaviour"/> of the specified type.</returns>
public static TOriginal GetOrAddBehaviour<TOriginal, TFallback>(this IBehaviourController behaviourController, params object?[]? args)
where TOriginal : class
where TFallback : class, IBehaviour, TOriginal
=> behaviourController.GetBehaviour<TOriginal>() ?? behaviourController.AddBehaviour<TFallback>(args);
/// <summary>
/// Tries to get a <see cref="IBehaviour"/> of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found in the parent universe; otherwise, <see cref="false"/>.</returns>
public static bool TryGetBehaviourInParent<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = GetBehaviourInParent<T>(behaviourController);
return behaviour is not null;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, null.</returns>
public static T? GetBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
{
IBehaviourController? controller = behaviourController;
while (controller is not null)
{
if (controller.GetBehaviour<T>() is T behaviour)
return behaviour;
controller = controller.UniverseObject.Parent?.BehaviourController;
}
return default;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T GetRequiredBehaviourInParent<T>(this IBehaviourController behaviourController) where T : class
=> behaviourController.GetBehaviourInParent<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any parent");
/// <summary>
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s parents recursively and stores them in the provided list.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <param name="behavioursInParent">The list to store the <see cref="IBehaviour"/>s.</param>
public static void GetBehavioursInParent<T>(this IBehaviourController behaviourController, IList<T> behavioursInParent) where T : class
{
IBehaviourController? controller = behaviourController;
List<T> cache = [];
behavioursInParent.Clear();
while (controller is not null)
{
controller.GetBehaviours(cache);
foreach (T behaviour in cache)
behavioursInParent.Add(behaviour);
controller = controller.UniverseObject.Parent?.BehaviourController;
}
}
/// <summary>
/// Tries to get a <see cref="IBehaviour"/> of the specified type in it's <see cref="IUniverseObject"/>'s children recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <param name="behaviour">When this method returns, contains the <see cref="IBehaviour"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found in the child universe; otherwise, <see cref="false"/>.</returns>
public static bool TryGetBehaviourInChildren<T>(this IBehaviourController behaviourController, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = GetBehaviourInChildren<T>(behaviourController);
return behaviour is not null;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in it's <see cref="IUniverseObject"/>'s children recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, null.</returns>
public static T? GetBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
{
if (behaviourController.GetBehaviour<T>() is T localBehaviour)
return localBehaviour;
foreach (IUniverseObject child in behaviourController.UniverseObject)
if (GetBehaviourInChildren<T>(child.BehaviourController) is T behaviour)
return behaviour;
return default;
}
/// <summary>
/// Gets a <see cref="IBehaviour"/> of the specified type in the children recursively. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="behaviourController">The <see cref="IBehaviourController"/> to start searching from.</param>
/// <returns>The <see cref="IBehaviour"/> of the specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T GetRequiredBehaviourInChildren<T>(this IBehaviourController behaviourController) where T : class
=> behaviourController.GetBehaviourInChildren<T>() ?? throw new BehaviourNotFoundException($"{behaviourController.UniverseObject.Name}'s {nameof(IBehaviourController)} does not contain any {typeof(T).FullName} on any children ");
/// <summary>
/// Gets all <see cref="IBehaviour"/>s of the specified type in it's <see cref="IUniverseObject"/>'s children recursively and stores them in the provided list.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/>s to get.</typeparam>
/// <param name="behavioursInChildren">The list to store the <see cref="IBehaviour"/>s.</param>
public static void GetBehavioursInChildren<T>(this IBehaviourController behaviourController, IList<T> behavioursInChildren) where T : class
{
List<T> cache = [];
behavioursInChildren.Clear();
TraverseChildrenForBehaviour(behaviourController.UniverseObject, behavioursInChildren, cache);
}
private static void TraverseChildrenForBehaviour<T>(IUniverseObject universeObject, IList<T> behaviours, IList<T> cache) where T : class
{
universeObject.BehaviourController.GetBehaviours(cache);
foreach (T behaviour in cache)
behaviours.Add(behaviour);
foreach (IUniverseObject child in universeObject)
TraverseChildrenForBehaviour(child, behaviours, cache);
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Syntriax.Engine.Core;
public static class EnumExtensions
{
public static bool CheckFlag(this Enum left, Enum right)
=> ((int)(object)left & (int)(object)right) != 0;
}

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,17 @@
namespace Syntriax.Engine.Core;
public static class TransformExtensions
{
public static ITransform2D SetTransform(this ITransform2D transform,
Vector2D? position = null, float? rotation = null, Vector2D? scale = null,
Vector2D? localPosition = null, float? localRotation = null, Vector2D? localScale = null)
{
if (position.HasValue) transform.Position = position.Value;
if (rotation.HasValue) transform.Rotation = rotation.Value;
if (scale.HasValue) transform.Scale = scale.Value;
if (localPosition.HasValue) transform.LocalPosition = localPosition.Value;
if (localRotation.HasValue) transform.LocalRotation = localRotation.Value;
if (localScale.HasValue) transform.LocalScale = localScale.Value;
return transform;
}
}

View File

@@ -0,0 +1,36 @@
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
public static class UniverseExtensions
{
public static IUniverseObject InstantiateUniverseObject(this IUniverse universe, params object?[]? args)
=> universe.InstantiateUniverseObject<UniverseObject>(args);
/// <summary>
/// Searches through all <see cref="IUniverseObject"/>s to find the specified instance of the type.
/// </summary>
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam>
/// <returns>The specified type if found; otherwise, throws <see cref="UniverseObjectNotFoundException"/>.</returns>
public static T GetRequiredUniverseObject<T>(this IUniverse universe) where T : class
=> universe.GetUniverseObject<T>() ?? throw new UniverseObjectNotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} object of type {typeof(T).FullName}");
/// <summary>
/// Searches through all <see cref="IBehaviours"/>s to find the specified instance of the type.
/// </summary>
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam>
/// <returns>The specified type if found; otherwise, throws <see cref="BehaviourNotFoundException"/>.</returns>
public static T FindRequiredBehaviour<T>(this IUniverse universe) where T : class
=> universe.FindBehaviour<T>() ?? throw new BehaviourNotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} with {nameof(IBehaviour)} of type {typeof(T).FullName}");
/// <summary>
/// Searches through all <see cref="IUniverseObject"/>s and <see cref="IBehaviours"/>s to find the specified instance of the type.
/// </summary>
/// <remarks>
/// WARNING: This is more expensive compared to <see cref="GetRequiredUniverseObject{T}(IUniverse)"/> or <see cref="FindRequiredBehaviour{T}(IUniverse)"/> as it combines the two. If you know whether the type is either a type that gets implemented on an <see cref="IBehaviour"/> or <see cref="IUniverseObject"/> use the method appropriate for it for performance.
/// </remarks>
/// <typeparam name="T">Type to be searched through the <see cref="IUniverse"/>.</typeparam>
/// <returns>The specified type if found; otherwise, throws <see cref="NotFoundException"/>.</returns>
public static T FindRequired<T>(this IUniverse universe) where T : class
=> universe.Find<T>() ?? throw new NotFoundException($"{universe.GetType().FullName}({universe.Id}) does not contain any {nameof(IUniverseObject)} or {nameof(IBehaviour)} of type {typeof(T).FullName}");
}

View File

@@ -0,0 +1,255 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
public static class UniverseObjectExtensions
{
public static T SetUniverseObject<T>(this T universeObject, string? name = "", IUniverseObject? parent = null) where T : IUniverseObject
{
if (!string.IsNullOrWhiteSpace(name))
universeObject.Name = name;
if (parent is not null)
universeObject.SetParent(parent);
return universeObject;
}
#region Universe Object Search
/// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObjects">The <see cref="IUniverseObject"/>s to search.</param>
/// <returns>The first found <see cref="IUniverseObject"/> of the specified type; otherwise, null.</returns>
public static T? GetUniverseObject<T>(this IEnumerable<IUniverseObject> universeObjects) where T : class
{
foreach (IUniverseObject universeObject in universeObjects)
if (universeObject is T @object)
return @object;
return default;
}
/// <summary>
/// Tries to get a <see cref="IUniverseObject"/> of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObjects">The <see cref="IUniverseObject"/>s to search.</param>
/// <returns><see cref="true"/> if a <see cref="IUniverseObject"/> of the specified type was found in the universe objects; otherwise, <see cref="false"/>.</returns>
public static bool TryGetUniverseObject<T>(this IEnumerable<IUniverseObject> universeObjects, [NotNullWhen(returnValue: true)] out T? universeObject) where T : class
{
universeObject = GetUniverseObject<T>(universeObjects);
return universeObject is not null;
}
/// <summary>
/// Searches through the provided <see cref="IUniverseObject"/>s to collect a list of <see cref="IUniverseObject"/>s of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to search.</param>
/// <returns>The found <see cref="IUniverseObject"/>s of the specified types</returns>
public static void GetUniverseObjects<T>(this IEnumerable<IUniverseObject> universeObjects, IList<T> foundUniverseObjects) where T : class
{
foundUniverseObjects.Clear();
foreach (IUniverseObject universeObject in universeObjects)
if (universeObject is T @object)
foundUniverseObjects.Add(@object);
}
#endregion
#region Universe Object Search In Parent
/// <summary>
/// Tries to get a <see cref="IUniverseObject"/> of the specified type in it's parents recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="behaviour">When this method returns, contains the <see cref="IUniverseObject"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if a <see cref="IUniverseObject"/> of the specified type was found in the parent universe objects; otherwise, <see cref="false"/>.</returns>
public static bool TryGetUniverseObjectInParent<T>(this IUniverseObject universeObject, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = GetUniverseObjectInParent<T>(universeObject);
return behaviour is not null;
}
/// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type in it's parents recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to start searching from.</param>
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
public static T? GetUniverseObjectInParent<T>(this IUniverseObject universeObject) where T : class
{
if (universeObject.GetUniverseObject<T>() is T localUniverseObject)
return localUniverseObject;
IUniverseObject? parent = universeObject;
while (parent is not null)
{
if (parent is T behaviour)
return behaviour;
parent = universeObject.Parent;
}
return default;
}
/// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type in the parents recursively. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to start searching from.</param>
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, throws <see cref="UniverseObjectNotFoundException"/>.</returns>
public static T GetRequiredUniverseObjectInParent<T>(this IUniverseObject universeObject) where T : class
=> universeObject.GetUniverseObjectInParent<T>() ?? throw new UniverseObjectNotFoundException($"{universeObject.Name}'s {nameof(IUniverseObject)} does not contain any {typeof(T).FullName} on any parent ");
#endregion
#region Universe Object Search In Children
/// <summary>
/// Tries to get a <see cref="IUniverseObject"/> of the specified type in it's children recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="behaviour">When this method returns, contains the <see cref="IUniverseObject"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if a <see cref="IUniverseObject"/> of the specified type was found in the child universe objects; otherwise, <see cref="false"/>.</returns>
public static bool TryGetUniverseObjectInChildren<T>(this IUniverseObject universeObject, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = GetUniverseObjectInChildren<T>(universeObject);
return behaviour is not null;
}
/// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type in it's children recursively.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to start searching from.</param>
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, null.</returns>
public static T? GetUniverseObjectInChildren<T>(this IUniverseObject universeObject) where T : class
{
if (universeObject.GetUniverseObject<T>() is T localUniverseObject)
return localUniverseObject;
foreach (IUniverseObject child in universeObject)
if (GetUniverseObjectInChildren<T>(child) is T behaviour)
return behaviour;
return default;
}
/// <summary>
/// Gets a <see cref="IUniverseObject"/> of the specified type in the children recursively. Throws an error if not found.
/// </summary>
/// <typeparam name="T">The type of <see cref="IUniverseObject"/> to get.</typeparam>
/// <param name="universeObject">The <see cref="IUniverseObject"/> to start searching from.</param>
/// <returns>The <see cref="IUniverseObject"/> of the specified type if found; otherwise, throws <see cref="UniverseObjectNotFoundException"/>.</returns>
public static T GetRequiredUniverseObjectInChildren<T>(this IUniverseObject universeObject) where T : class
=> universeObject.GetUniverseObjectInChildren<T>() ?? throw new UniverseObjectNotFoundException($"{universeObject.Name}'s {nameof(IUniverseObject)} does not contain any {typeof(T).FullName} on any children ");
#endregion
#region Behaviour Search
/// <summary>
/// Finds a <see cref="IBehaviour"/> of the specified type in the provided <see cref="IUniverseObject"/>s.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to find.</typeparam>
/// <returns>The first found <see cref="IBehaviour"/> of the specified type; otherwise, null.</returns>
public static T? FindBehaviour<T>(this IEnumerable<IUniverseObject> universeObjects) where T : class
{
foreach (IUniverseObject universeObject in universeObjects)
if (universeObject.BehaviourController.GetBehaviour<T>() is T behaviour)
return behaviour;
return default;
}
/// <summary>
/// Tries to find a <see cref="IBehaviour"/> of the specified type in the provided <see cref="IUniverseObject"/>s.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to find.</typeparam>
/// <param name="behaviour">When this method returns, contains the <see cref="IUniverseObject"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if a <see cref="IBehaviour"/> of the specified type was found in the provided <see cref="IUniverseObject"/>s; otherwise, <see cref="false"/>.</returns>
public static bool TryFindBehaviour<T>(this IEnumerable<IUniverseObject> universeObjects, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = FindBehaviour<T>(universeObjects);
return behaviour is not null;
}
/// <summary>
/// Searches through the provided <see cref="IUniverseObject"/>s to collect a list of <see cref="IBehaviour"/>s of the specified type.
/// </summary>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="universeObjects">The <see cref="IUniverseObject"/>s to search.</param>
public static void FindBehaviours<T>(this IEnumerable<IUniverseObject> universeObjects, IList<T> behaviours) where T : class
{
behaviours.Clear();
List<T> cache = [];
foreach (IUniverseObject universeObject in universeObjects)
{
universeObject.BehaviourController.GetBehaviours(cache);
foreach (T behaviour in cache)
behaviours.Add(behaviour);
}
}
#endregion
#region General Search
/// <summary>
/// Finds an object of the specified type in the provided <see cref="IUniverseObject"/>s and their <see cref="IBehaviour"/>s.
/// </summary>
/// <remarks>
/// WARNING: This is more expensive compared to <see cref="GetUniverseObject{T}(IEnumerable{IUniverseObject})"/> or <see cref="FindBehaviour{T}(IEnumerable{IUniverseObject})"/> as it combines the two. If you know whether the type is either a type that gets implemented on an <see cref="IBehaviour"/> or <see cref="IUniverseObject"/> use the method appropriate for it for performance.
/// </remarks>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to find.</typeparam>
/// <returns>The first found instance of the specified type; otherwise, null.</returns>
public static T? Find<T>(this IEnumerable<IUniverseObject> universeObjects) where T : class
{
if (universeObjects.GetUniverseObject<T>() is T foundUniverseObject)
return foundUniverseObject;
if (universeObjects.FindBehaviour<T>() is T foundBehaviour)
return foundBehaviour;
return null;
}
/// <summary>
/// Tries to find an object of the specified type in the provided <see cref="IUniverseObject"/>s and their <see cref="IBehaviour"/>s.
/// </summary>
/// <remarks>
/// WARNING: This is more expensive compared to <see cref="TryGetUniverseObject{T}(IEnumerable{IUniverseObject}, out T?)"/> or <see cref="TryFindBehaviour{T}(IEnumerable{IUniverseObject}, out T?)"/> as it combines the two. If you know whether the type is either a type that gets implemented on an <see cref="IBehaviour"/> or <see cref="IUniverseObject"/> use the method appropriate for it for performance.
/// </remarks>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to find.</typeparam>
/// <param name="behaviour">When this method returns, contains the <see cref="IUniverseObject"/> of the specified type, if found; otherwise, null.</param>
/// <returns><see cref="true"/> if an object of the specified type was found in the provided <see cref="IUniverseObject"/>s; otherwise, <see cref="false"/>.</returns>
public static bool TryFind<T>(this IEnumerable<IUniverseObject> universeObjects, [NotNullWhen(returnValue: true)] out T? behaviour) where T : class
{
behaviour = Find<T>(universeObjects);
return behaviour is not null;
}
/// <summary>
/// Searches through the provided <see cref="IUniverseObject"/>s and their <see cref="IBehaviour"/>s to collect a list of the specified type.
/// </summary>
/// <remarks>
/// WARNING: This is more expensive compared to <see cref="GetUniverseObjects{T}(IEnumerable{IUniverseObject}, IList{T})"/> or <see cref="FindBehaviours{T}(IEnumerable{IUniverseObject}, IList{T})"/> as it combines the two. If you know whether the type is either a type that gets implemented on an <see cref="IBehaviour"/> or <see cref="IUniverseObject"/> use the method appropriate for it for performance.
/// </remarks>
/// <typeparam name="T">The type of <see cref="IBehaviour"/> to get.</typeparam>
/// <param name="instances">List of objects found wit the specified type.</param>
/// <param name="universeObjects">The <see cref="IUniverseObject"/>s to search.</param>
public static void Find<T>(this IEnumerable<IUniverseObject> universeObjects, IList<T> instances) where T : class
{
instances.Clear();
List<T> cache = [];
foreach (IUniverseObject universeObject in universeObjects)
{
universeObject.Find(cache);
foreach (T behaviour in cache)
instances.Add(behaviour);
}
}
#endregion
}

View File

@@ -1,20 +1,22 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
public class BehaviourControllerFactory
{
public IBehaviourController Instantiate(IGameObject gameObject)
=> Instantiate<BehaviourController>(gameObject);
public static IBehaviourController Instantiate(IUniverseObject universeObject)
=> Instantiate<BehaviourController>(universeObject);
public T Instantiate<T>(IGameObject gameObject, params object?[]? args)
public static T Instantiate<T>(IUniverseObject universeObject, params object?[]? args)
where T : class, IBehaviourController
{
T behaviourController = TypeFactory.Get<T>(args);
if (!behaviourController.Assign(gameObject))
throw AssignException.From(behaviourController, gameObject);
if (!universeObject.Assign(behaviourController))
throw AssignFailedException.From(universeObject, behaviourController);
if (!behaviourController.Assign(universeObject))
throw AssignFailedException.From(behaviourController, universeObject);
return behaviourController;
}

View File

@@ -1,26 +1,23 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
public class BehaviourFactory
{
public T Instantiate<T>(IGameObject gameObject, params object?[]? args) where T : class, IBehaviour
=> Instantiate<T>(gameObject, stateEnable: null, args);
public static T Instantiate<T>(params object?[]? args) where T : class, IBehaviour
=> Instantiate<T>(stateEnable: null, args);
public T Instantiate<T>(IGameObject gameObject, IStateEnable? stateEnable, params object?[]? args)
public static T Instantiate<T>(IStateEnable? stateEnable, params object?[]? args)
where T : class, IBehaviour
{
T behaviour = TypeFactory.Get<T>(args);
stateEnable ??= TypeFactory.Get<StateEnable>();
if (!stateEnable.Assign(behaviour))
throw AssignException.From(stateEnable, behaviour);
throw AssignFailedException.From(stateEnable, behaviour);
if (!behaviour.Assign(gameObject.BehaviourController))
throw AssignException.From(behaviour, gameObject.BehaviourController);
if (!behaviour.Assign(stateEnable))
throw AssignException.From(behaviour, stateEnable);
throw AssignFailedException.From(behaviour, stateEnable);
return behaviour;
}

View File

@@ -1,39 +0,0 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
public class GameObjectFactory
{
public T Instantiate<T>(params object?[]? args) where T : class, IGameObject
=> Instantiate<T>(transform: null, behaviourController: null, stateEnable: null, args);
public T Instantiate<T>(
ITransform? transform = null,
IBehaviourController? behaviourController = null,
IStateEnable? stateEnable = null,
params object?[]? args
)
where T : class, IGameObject
{
T gameObject = TypeFactory.Get<T>(args);
transform ??= TypeFactory.Get<Transform>();
behaviourController ??= TypeFactory.Get<BehaviourController>();
stateEnable ??= TypeFactory.Get<StateEnable>();
if (!behaviourController.Assign(gameObject))
throw AssignException.From(behaviourController, gameObject);
if (!stateEnable.Assign(gameObject))
throw AssignException.From(stateEnable, gameObject);
if (!gameObject.Assign(transform))
throw AssignException.From(gameObject, transform);
if (!gameObject.Assign(behaviourController))
throw AssignException.From(gameObject, behaviourController);
if (!gameObject.Assign(stateEnable))
throw AssignException.From(gameObject, stateEnable);
return gameObject;
}
}

View File

@@ -1,18 +1,20 @@
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
public class StateEnableFactory
{
public IStateEnable Instantiate(IEntity entity) => Instantiate<StateEnable>(entity);
public static IStateEnable Instantiate(IEntity entity) => Instantiate<StateEnable>(entity);
public T Instantiate<T>(IEntity entity, params object?[]? args) where T : class, IStateEnable
public static T Instantiate<T>(IEntity entity, params object?[]? args) where T : class, IStateEnable
{
T stateEnable = TypeFactory.Get<T>(args);
if (!entity.Assign(stateEnable))
throw AssignFailedException.From(entity, stateEnable);
if (!stateEnable.Assign(entity))
throw AssignException.From(stateEnable, entity);
throw AssignFailedException.From(stateEnable, entity);
return stateEnable;
}

View File

@@ -1,10 +1,8 @@
using Syntriax.Engine.Core.Abstract;
namespace Syntriax.Engine.Core.Factory;
public class TransformFactory
{
public ITransform Instantiate() => TypeFactory.Get<Transform>();
public T Instantiate<T>(params object?[]? args) where T : class, ITransform
public static ITransform2D Instantiate() => TypeFactory.Get<Transform2D>();
public static T Instantiate<T>(params object?[]? args) where T : class, ITransform2D
=> TypeFactory.Get<T>(args);
}

View File

@@ -1,21 +1,59 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Syntriax.Engine.Core.Factory;
public static class TypeFactory
{
public static T Get<T>(params object?[]? args) where T : class
private static readonly ConcurrentDictionary<string, Type> registeredTypes = [];
public static string GetTypeName(Type type) => type.FullName ?? throw new ArgumentException($"{type.Name} must be a resolvable type");
public static T Get<T>(params object?[]? args) where T : class => (T)Get(typeof(T), args);
public static object Get(string fullName, params object?[]? args) => Get(GetType(fullName), args);
public static object Get(Type type, params object?[]? args)
{
T? result;
object? result;
if (args is not null && args.Length != 0)
result = Activator.CreateInstance(typeof(T), args) as T;
result = Activator.CreateInstance(type, args);
else
result = Activator.CreateInstance(typeof(T)) as T;
result = Activator.CreateInstance(type);
if (result is null)
throw new Exception($"{typeof(T).Name} of type {typeof(T).Name} could not be created.");
throw new Exception($"Type {type.Name} could not be created.");
return result;
}
public static Type GetType(string fullName)
{
if (registeredTypes.TryGetValue(fullName, out Type? result))
return result;
ReloadTypes();
if (registeredTypes.TryGetValue(fullName, out Type? reloadedType))
return reloadedType;
throw new Exception($"Type {fullName} could not be found in the current domain.");
}
public static void ReloadTypes()
{
registeredTypes.Clear();
IEnumerable<Type> domainTypes = AppDomain.CurrentDomain
.GetAssemblies()
.SelectMany(a => a.GetTypes());
// TODO: Replace this
// There are some system & compiler generated types with duplicated names,
// it is ugly it will cause headaches in the future because it will not
// throw an error if there's a type with an unintended duplicate name
foreach (Type type in domainTypes)
registeredTypes.TryAdd(GetTypeName(type), type);
}
}

View File

@@ -0,0 +1,36 @@
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core.Factory;
public class UniverseObjectFactory
{
public static IUniverseObject Instantiate() => Instantiate<UniverseObject>();
public static T Instantiate<T>(params object?[]? args) where T : class, IUniverseObject
=> Instantiate<T>(behaviourController: null, stateEnable: null, args);
public static IUniverseObject Instantiate(IBehaviourController? behaviourController = null, IStateEnable? stateEnable = null) => Instantiate<UniverseObject>(behaviourController, stateEnable);
public static T Instantiate<T>(
IBehaviourController? behaviourController = null,
IStateEnable? stateEnable = null,
params object?[]? args
)
where T : class, IUniverseObject
{
T universeObject = TypeFactory.Get<T>(args);
behaviourController ??= TypeFactory.Get<BehaviourController>();
stateEnable ??= TypeFactory.Get<StateEnable>();
if (!behaviourController.Assign(universeObject))
throw AssignFailedException.From(behaviourController, universeObject);
if (!stateEnable.Assign(universeObject))
throw AssignFailedException.From(stateEnable, universeObject);
if (!universeObject.Assign(behaviourController))
throw AssignFailedException.From(universeObject, behaviourController);
if (!universeObject.Assign(stateEnable))
throw AssignFailedException.From(universeObject, stateEnable);
return universeObject;
}
}

View File

@@ -1,205 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
using Syntriax.Engine.Core.Factory;
namespace Syntriax.Engine.Core;
public class GameManager : IEntity
{
public Action<GameManager>? OnCameraChanged { 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 IList<IDisplayable> _drawables = new List<IDisplayable>(Constants.DRAWABLE_OBJECTS_SIZE_INITIAL);
private IStateEnable _stateEnable = null!;
private GameObjectFactory _gameObjectFactory = null!;
private bool _initialized = false;
private ICamera _camera = null!;
private GameObjectFactory GameObjectFactory
{
get
{
if (_gameObjectFactory is null)
_gameObjectFactory = new GameObjectFactory();
return _gameObjectFactory;
}
}
public bool Initialized => _initialized;
public IList<IGameObject> GameObjects => _gameObjects;
public IStateEnable StateEnable
{
get
{
if (_stateEnable is null)
{
Assign(new StateEnableFactory().Instantiate(this));
if (_stateEnable is null)
throw NotAssignedException.From(this, _stateEnable);
}
return _stateEnable;
}
}
public ICamera Camera
{
get => _camera;
set
{
if (_camera == value)
return;
_camera = value;
OnCameraChanged?.Invoke(this);
}
}
public void RegisterGameObject(IGameObject gameObject)
{
if (_gameObjects.Contains(gameObject))
throw new Exception($"{nameof(IGameObject)} named {gameObject.Name} is already registered to the {nameof(GameManager)}.");
Register(gameObject);
}
public T InstantiateGameObject<T>(params object?[]? args) where T : class, IGameObject
{
T gameObject = GameObjectFactory.Instantiate<T>(args);
Register(gameObject);
return gameObject;
}
public IGameObject RemoveGameObject(IGameObject gameObject)
{
if (!_gameObjects.Contains(gameObject))
throw new Exception($"{nameof(IGameObject)} named {gameObject.Name} is not registered to the {nameof(GameManager)}.");
Unregister(gameObject);
return gameObject;
}
public bool Initialize()
{
if (Initialized)
return false;
NotAssignedException.Check(this, StateEnable);
foreach (var gameObject in GameObjects)
gameObject.Initialize();
OnInitialized?.Invoke(this);
return true;
}
public bool Finalize()
{
if (!Initialized)
return false;
for (int i = GameObjects.Count; i >= 0; i--)
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(GameTime time)
{
foreach (var gameObject in GameObjects)
gameObject.BehaviourController.Update(time);
}
public void PreDraw(GameTime time)
{
foreach (var gameObject in GameObjects)
gameObject.BehaviourController.UpdatePreDraw(time);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: Camera.MatrixTransform);
foreach (var drawable in _drawables)
drawable.Draw(spriteBatch);
spriteBatch.End();
}
/////////////////////////////////////////////////////////////////
private void Unregister(IGameObject gameObject)
{
gameObject.BehaviourController.OnBehaviourAdded -= OnBehaviourAdd;
gameObject.BehaviourController.OnBehaviourRemoved -= OnBehaviourRemove;
gameObject.OnFinalized -= OnGameObjectFinalize;
if (gameObject.BehaviourController.TryGetBehaviour<IDisplayable>(out var drawable))
_drawables.Remove(drawable);
_gameObjects.Remove(gameObject);
}
private void Register(IGameObject gameObject)
{
gameObject.BehaviourController.OnBehaviourAdded += OnBehaviourAdd;
gameObject.BehaviourController.OnBehaviourRemoved += OnBehaviourRemove;
gameObject.OnFinalized += OnGameObjectFinalize;
if (gameObject.BehaviourController.TryGetBehaviour<IDisplayable>(out var drawable))
_drawables.Add(drawable);
_gameObjects.Add(gameObject);
}
private void OnGameObjectFinalize(IInitialize initialize)
{
if (initialize is IGameObject gameObject)
Unregister(gameObject);
}
private void OnBehaviourAdd(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is IDisplayable drawable)
_drawables.Add(drawable);
}
private void OnBehaviourRemove(IBehaviourController controller, IBehaviour behaviour)
{
if (behaviour is IDisplayable drawable)
_drawables.Remove(drawable);
}
}

View File

@@ -1,151 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Syntriax.Engine.Core.Abstract;
using Syntriax.Engine.Core.Exceptions;
namespace Syntriax.Engine.Core;
public class GameObject : IGameObject
{
public Action<IAssignableStateEnable>? OnStateEnableAssigned { 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<IEntity>? OnNameChanged { get; set; } = null;
public Action<IInitialize>? OnInitialized { get; set; } = null;
public Action<IInitialize>? OnFinalized { get; set; } = null;
public Action<IGameObject, GameTime>? OnUpdated { get; set; } = null;
private ITransform _transform = null!;
private IBehaviourController _behaviourController = null!;
private IStateEnable _stateEnable = null!;
private string _name = nameof(GameObject);
private bool _initialized = false;
public ITransform Transform => _transform;
public IBehaviourController BehaviourController => _behaviourController;
public IStateEnable StateEnable => _stateEnable;
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
{
get => _name;
set
{
if (value == _name) return;
_name = value;
OnNameChanged?.Invoke(this);
}
}
public bool Initialize()
{
if (Initialized)
return false;
NotAssignedException.Check(this, _transform);
NotAssignedException.Check(this, _behaviourController);
NotAssignedException.Check(this, _stateEnable);
Initialized = true;
return true;
}
public void Update(GameTime time)
{
if (!_stateEnable.Enabled)
return;
OnUpdated?.Invoke(this, time);
}
public bool Finalize()
{
if (!Initialized)
return false;
System.Threading.Tasks.Parallel.ForEach(
_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)
{
if (Initialized)
return false;
_transform = transform;
OnTransformAssigned?.Invoke(this);
return true;
}
public bool Assign(IBehaviourController behaviourController)
{
if (Initialized)
return false;
_behaviourController = behaviourController;
OnBehaviourControllerAssigned?.Invoke(this);
return true;
}
public bool Unassign()
{
if (Initialized)
return false;
_stateEnable = null!;
_transform = null!;
_behaviourController = null!;
OnUnassigned?.Invoke(this);
return true;
}
public GameObject() { OnBehaviourControllerAssigned += ConnectBehaviourController; }
private void ConnectBehaviourController(IAssignableBehaviourController controller)
{
controller.BehaviourController.OnBehaviourAdded += OnBehaviourAdded;
controller.BehaviourController.OnBehaviourRemoved += OnBehaviourRemoved;
}
private void OnBehaviourRemoved(IBehaviourController _, IBehaviour behaviour) { if (Initialized) behaviour.Initialize(); }
private void OnBehaviourAdded(IBehaviourController _, IBehaviour behaviour) { if (Initialized) behaviour.Finalize(); }
}

View File

@@ -0,0 +1,18 @@
using System;
namespace Syntriax.Engine.Core;
public static class DelegateExtensions
{
[Obsolete($"{nameof(InvokeSafe)} causes memory allocation, please use Invoke() instead.")]
public static void InvokeSafe(this Delegate @delegate, params object?[] args)
{
foreach (Delegate invocation in Delegate.EnumerateInvocationList(@delegate))
try { invocation.DynamicInvoke(args); }
catch (Exception exception)
{
string methodCallRepresentation = $"{invocation.Method.DeclaringType?.FullName}.{invocation.Method.Name}({string.Join(", ", args)})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
public class Event<TSender>
{
private readonly List<EventHandler> listeners = new(8);
public void AddListener(EventHandler listener) => listeners.Add(listener);
public void RemoveListener(EventHandler listener) => listeners.Remove(listener);
public void Clear() => listeners.Clear();
public void Invoke(TSender argument)
{
for (int i = 0; i < listeners.Count; i++)
try { listeners[i].Invoke(argument); }
catch (Exception exception)
{
string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}({argument})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
}
}
public delegate void EventHandler(TSender sender);
}
public class Event<TSender, TArguments>
{
private readonly List<EventHandler> listeners = new(8);
public void AddListener(EventHandler listener) => listeners.Add(listener);
public void RemoveListener(EventHandler listener) => listeners.Remove(listener);
public void Clear() => listeners.Clear();
public void Invoke(TSender sender, TArguments arguments)
{
for (int i = 0; i < listeners.Count; i++)
try { listeners[i].Invoke(sender, arguments); }
catch (Exception exception)
{
string methodCallRepresentation = $"{listeners[i].Method.DeclaringType?.FullName}.{listeners[i].Method.Name}({string.Join(", ", sender, arguments)})";
Console.WriteLine($"Unexpected exception on invocation of method {methodCallRepresentation}:{Environment.NewLine}{exception.InnerException}");
}
}
public delegate void EventHandler(TSender sender, TArguments arguments);
}

View File

@@ -0,0 +1,7 @@
namespace Syntriax.Engine.Core;
public interface IProgressionTracker : IReadOnlyProgressionTracker
{
void Set(float progression, string status);
void Reset();
}

View File

@@ -0,0 +1,12 @@
namespace Syntriax.Engine.Core;
public interface IReadOnlyProgressionTracker
{
Event<IReadOnlyProgressionTracker, ProgressionUpdatedArguments> OnUpdated { get; }
Event<IReadOnlyProgressionTracker> OnEnded { get; }
float Progression { get; }
string Status { get; }
readonly record struct ProgressionUpdatedArguments(float PreviousProgression, string PreviousStatus);
}

View File

@@ -0,0 +1,36 @@
namespace Syntriax.Engine.Core;
public class ProgressionTracker : IProgressionTracker
{
public Event<IReadOnlyProgressionTracker, IReadOnlyProgressionTracker.ProgressionUpdatedArguments> OnUpdated { get; } = new();
public Event<IReadOnlyProgressionTracker> OnEnded { get; } = new();
public float Progression { get; private set; } = 0f;
public string Status { get; private set; } = "Default";
void IProgressionTracker.Set(float progression, string status)
{
if (Progression >= 1f)
return;
float previousProgression = Progression;
string previousStatus = Status;
Progression = progression.Clamp(Progression, 1f);
Status = status;
OnUpdated?.Invoke(this, new(previousProgression, previousStatus));
if (progression >= 1f)
OnEnded?.Invoke(this);
}
void IProgressionTracker.Reset()
{
Progression = 0f;
Status = "Default";
OnUpdated.Clear();
OnEnded.Clear();
}
}

View File

@@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Syntriax.Engine.Core;
public record struct ProgressiveTask<T>(IReadOnlyProgressionTracker ProgressionTracker, Task<T> Task)
{
public static implicit operator (IReadOnlyProgressionTracker progressionTracker, Task<T> task)(ProgressiveTask<T> value) => (value.ProgressionTracker, value.Task);
public static implicit operator ProgressiveTask<T>((IReadOnlyProgressionTracker progressionTracker, Task<T> task) value) => new(value.progressionTracker, value.task);
}

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

@@ -0,0 +1,281 @@
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>
/// Gets one minus of given <see cref="T"/>.
/// </summary>
/// <param name="value">The value <see cref="T"/>.</param>
/// <returns>One minus of given <see cref="T"/>.</returns>
public static T OneMinus<T>(T value) where T : INumber<T> => T.One - value;
/// <summary>
/// Adds two <see cref="T"/>s.
/// </summary>
/// <param name="left">The first <see cref="T"/>.</param>
/// <param name="value">The second <see cref="T"/>.</param>
/// <returns>The sum of the two <see cref="T"/>s.</returns>
public static T Add<T>(T left, T value) where T : INumber<T> => left + value;
/// <summary>
/// Subtracts one <see cref="T"/> from another.
/// </summary>
/// <param name="left">The <see cref="T"/> to subtract from.</param>
/// <param name="value">The <see cref="T"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="T"/> from the first.</returns>
public static T Subtract<T>(T left, T value) where T : INumber<T> => left - value;
/// <summary>
/// Multiplies a <see cref="T"/> by a scalar value.
/// </summary>
/// <param name="left">The <see cref="T"/>.</param>
/// <param name="multiplier">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="T"/> by the scalar value.</returns>
public static T Multiply<T>(T left, T multiplier) where T : INumber<T> => left * multiplier;
/// <summary>
/// Divides a <see cref="T"/> by a scalar value.
/// </summary>
/// <param name="left">The <see cref="T"/>.</param>
/// <param name="divider">The scalar value.</param>
/// <returns>The result of dividing the <see cref="T"/> by the scalar value.</returns>
public static T Divide<T>(T left, T divider) where T : INumber<T> => left / divider;
/// <summary>
/// Returns the true mathematical modulus of a <see cref="T"/> value.
/// Unlike the remainder operator (%), this result is always non-negative,
/// even when the <paramref name="value"/> operand is negative.
/// </summary>
/// <typeparam name="T">A numeric type that implements <see cref="INumber{T}"/>.</typeparam>
/// <param name="value">The dividend <see cref="T"/> value.</param>
/// <param name="modulus">The modulus <see cref="T"/> value (must be non-zero).</param>
/// <returns>
/// The non-negative remainder of <paramref name="value"/> divided by <paramref name="modulus"/>.
/// </returns>
public static T Mod<T>(T value, T modulus) where T : INumber<T>
{
T result = value % modulus;
if (result < T.Zero)
result += modulus;
return result;
}
/// <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 cosine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The cosine of <paramref name="x"/>.</returns>
public static float Cos(float x) => MathF.Cos(x);
/// <summary>
/// Returns the sine of a number.
/// </summary>
/// <param name="x">The number.</param>
/// <returns>The sine of <paramref name="x"/>.</returns>
public static float Sin(float x) => MathF.Sin(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>(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>
/// Performs linear interpolation between two specified values.
/// </summary>
/// <typeparam name="T">The type of the values, which must implement <see cref="IFloatingPoint{T}"/>.</typeparam>
/// <param name="x">The starting value of the interpolation.</param>
/// <param name="y">The ending value of the interpolation.</param>
/// <param name="t">The interpolation factor, typically in the range [0, 1].</param>
/// <returns>A value that represents the linear interpolation between <paramref name="x"/> and <paramref name="y"/>.</returns>
public static T Lerp<T>(T x, T y, T t) where T : IFloatingPoint<T> => x + (y - x) * t;
/// <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>
/// Rounds a number to an integer.
/// </summary>
/// <param name="x">The number to round.</param>
/// <param name="roundMode">Specification for how to round <paramref name="x"/> if it's midway between two numbers</param>
/// <returns></returns>
public static int RoundToInt(float x, RoundMode roundMode = RoundMode.Ceil) => (int)MathF.Round(x, 0, roundMode == RoundMode.Ceil ? MidpointRounding.ToPositiveInfinity : MidpointRounding.ToNegativeInfinity);
public enum RoundMode { Ceil, Floor };
/// <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

@@ -0,0 +1,97 @@
using System;
using System.Numerics;
namespace Syntriax.Engine.Core;
public static class MathExtensions
{
/// <inheritdoc cref="Math.OneMinus{T}(T)" />
public static T OneMinus<T>(this T value) where T : INumber<T> => Math.OneMinus(value);
/// <inheritdoc cref="Math.Add{T}(T, T)" />
public static T Add<T>(this T left, T value) where T : INumber<T> => Math.Add(left, value);
/// <inheritdoc cref="Math.Subtract{T}(T, T)" />
public static T Subtract<T>(this T left, T value) where T : INumber<T> => Math.Subtract(left, value);
/// <inheritdoc cref="Math.Multiply{T}(T, T)" />
public static T Multiply<T>(this T left, T multiplier) where T : INumber<T> => Math.Multiply(left, multiplier);
/// <inheritdoc cref="Math.Divide{T}(T, T)" />
public static T Divide<T>(this T left, T divider) where T : INumber<T> => Math.Divide(left, divider);
/// <inheritdoc cref="Math.Mod{T}(T, T)" />
public static T Mod<T>(this T value, T modulus) where T : INumber<T> => Math.Mod(value, modulus);
/// <inheritdoc cref="Math.Abs{T}(T)" />
public static T Abs<T>(this T x) where T : INumber<T> => Math.Abs(x);
/// <inheritdoc cref="Math.Cos(float)" />
public static float Cos(this float x) => Math.Cos(x);
/// <inheritdoc cref="Math.Sin(float)" />
public static float Sin(this float x) => Math.Sin(x);
/// <inheritdoc cref="Math.Acos(float)" />
public static float Acos(this float x) => Math.Acos(x);
/// <inheritdoc cref="Math.Asin(float)" />
public static float Asin(this float x) => Math.Asin(x);
/// <inheritdoc cref="Math.Atan2(float, float)" />
public static float Atan2(this float y, float x) => Math.Atan2(y, x);
/// <inheritdoc cref="Math.Atanh(float)" />
public static float Atanh(this float x) => Math.Atanh(x);
/// <inheritdoc cref="Math.Clamp{T}(T, T, T)" />
public static T Clamp<T>(this T x, T min, T max) where T : INumber<T> => Math.Clamp(x, min, max);
/// <inheritdoc cref="Math.Ceiling(float)" />
public static float Ceiling(this float x) => Math.Ceiling(x);
/// <inheritdoc cref="Math.CopySign(float, float)" />
public static float CopySign(this float x, float y) => Math.CopySign(x, y);
/// <inheritdoc cref="Math.Floor(float)" />
public static float Floor(this float x) => Math.Floor(x);
/// <inheritdoc cref="Math.IEEERemainder(float, float)" />
public static float IEEERemainder(this float x, float y) => Math.IEEERemainder(x, y);
/// <inheritdoc cref="Math.Log(float, float)" />
public static float Log(this float x, float y) => Math.Log(x, y);
/// <inheritdoc cref="Math.Max{T}(T, T)" />
public static T Max<T>(this T x, T y) where T : INumber<T> => Math.Max(x, y);
/// <inheritdoc cref="Math.AbsMax{T}(T, T)" />
public static T AbsMax<T>(this T x, T y) where T : INumber<T> => Math.AbsMax(x, y);
/// <inheritdoc cref="Math.Min{T}(T, T)" />
public static T Min<T>(this T x, T y) where T : INumber<T> => Math.Min(x, y);
/// <inheritdoc cref="Math.AbsMin{T}(T, T)" />
public static T AbsMin<T>(this T x, T y) where T : INumber<T> => Math.AbsMin(x, y);
/// <inheritdoc cref="Math.Pow(float, float)" />
public static float Pow(this float x, float y) => Math.Pow(x, y);
/// <inheritdoc cref="Math.Lerp{T}(T, T, T)" />
public static T Lerp<T>(this T x, T y, T t) where T : IFloatingPoint<T> => Math.Lerp(x, y, t);
/// <inheritdoc cref="Math.Round(float, int, MidpointRounding)" />
public static float Round(this float x, int digits, MidpointRounding mode) => Math.Round(x, digits, mode);
/// <inheritdoc cref="Math.RoundToInt(float, Math.RoundMode)" />
public static int RoundToInt(this float x, Math.RoundMode roundMode = Math.RoundMode.Ceil) => Math.RoundToInt(x, roundMode);
/// <inheritdoc cref="Math.Sqr{T}(T)" />
public static T Sqr<T>(this T x) where T : INumber<T> => Math.Sqr(x);
/// <inheritdoc cref="Math.Sqrt(float)" />
public static float Sqrt(this float x) => Math.Sqrt(x);
/// <inheritdoc cref="Math.Truncate(float)" />
public static float Truncate(this float x) => Math.Truncate(x);
}

10
Engine.Core/Preserver.cs Normal file
View File

@@ -0,0 +1,10 @@
namespace Syntriax.Engine.Core
{
// This is pretty much so the assembly gets loaded automatically because
// the builds include the assembly but sometimes doesn't link load it at startup.
// I will hopefully one day fix it and remove this.
public static class Preserver
{
public static void Preserve() { }
}
}

View File

@@ -0,0 +1,93 @@
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
/// <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>
/// Converts the <see cref="AABB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="AABB"/>.</returns>
public override string ToString() => $"{nameof(AABB)}({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>
/// <param name="epsilon">The epsilon range.</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, float epsilon = float.Epsilon)
=> left.LowerBoundary.ApproximatelyEquals(right.LowerBoundary, epsilon) && left.UpperBoundary.ApproximatelyEquals(right.UpperBoundary, epsilon);
}
/// <summary>
/// Provides extension methods for the <see cref="AABB"/> struct.
/// </summary>
public static class AABBExtensions
{
/// <inheritdoc cref="AABB.ToAABB" />
public static AABB ToAABB(this IEnumerable<Vector2D> vectors) => AABB.FromVectors(vectors);
/// <inheritdoc cref="AABB.ApproximatelyEquals" />
public static bool ApproximatelyEquals(this AABB left, AABB right, float epsilon = float.Epsilon) => AABB.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,107 @@
using System.Diagnostics;
namespace Syntriax.Engine.Core;
/// <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 <see cref="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 Projection1D 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="ITransform2D"/>.
/// </summary>
public static Circle Transform(ITransform2D transform, Circle circle)
=> new(transform.Transform(circle.Center), circle.Radius * (transform.Scale.Magnitude / Vector2D.One.Magnitude));
/// <summary>
/// Checks if two <see cref="Circle"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="Circle"/>.</param>
/// <param name="right">The second <see cref="Circle"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Circle"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Circle left, Circle right, float epsilon = float.Epsilon)
=> left.Center.ApproximatelyEquals(right.Center, epsilon) && left.Radius.ApproximatelyEquals(right.Radius, epsilon);
}
/// <summary>
/// Provides extension methods for the <see cref="Circle"/> struct.
/// </summary>
public static class CircleExtensions
{
/// <inheritdoc cref="Circle.SetCenter(Circle, Vector2D)" />
public static Circle SetCenter(this Circle circle, Vector2D center) => Circle.SetCenter(circle, center);
/// <inheritdoc cref="Circle.SetRadius(Circle, float)" />
public static Circle SetRadius(this Circle circle, float radius) => Circle.SetRadius(circle, radius);
/// <inheritdoc cref="Circle.Displace(Circle, Vector2D)" />
public static Circle Displace(this Circle circle, Vector2D displaceVector) => Circle.Displace(circle, displaceVector);
/// <inheritdoc cref="Circle.Project(Circle, Vector2D)" />
public static Projection1D ToProjection(this Circle circle, Vector2D projectionVector) => Circle.Project(circle, projectionVector);
/// <inheritdoc cref="Circle.Transform(ITransform2D, Circle)" />
public static Circle Transform(this ITransform2D transform, Circle circle) => Circle.Transform(transform, circle);
/// <inheritdoc cref="Circle.Transform(ITransform2D, Circle)" />
public static Circle Transform(this Circle circle, ITransform2D transform) => Circle.Transform(transform, circle);
/// <inheritdoc cref="Circle.ApproximatelyEquals(Circle, Circle, float)" />
public static bool ApproximatelyEquals(this Circle left, Circle right, float epsilon = float.Epsilon) => Circle.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,185 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an HSV color.
/// </summary>
/// <param name="hue">Hue of the <see cref="ColorHSV"/>.</param>
/// <param name="saturation">Saturation of the <see cref="ColorHSV"/>.</param>
/// <param name="value">Value of the <see cref="ColorHSV"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="ColorHSV"/> struct with the specified values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct ColorHSV(float hue, float saturation, float value)
{
/// <summary>
/// The Hue value of the <see cref="ColorHSV"/>.
/// </summary>
public readonly float Hue = hue.Clamp(0f, 1f);
/// <summary>
/// The Saturation value of the <see cref="ColorHSV"/>.
/// </summary>
public readonly float Saturation = saturation.Clamp(0f, 1f);
/// <summary>
/// The Value value of the <see cref="ColorHSV"/>.
/// </summary>
public readonly float Value = value.Clamp(0f, 1f);
public static ColorHSV operator -(ColorHSV color) => new(color.Hue.OneMinus().Clamp(0f, 1f), color.Saturation.OneMinus().Clamp(0f, 1f), color.Value.OneMinus().Clamp(0f, 1f));
public static ColorHSV operator +(ColorHSV left, ColorHSV right) => new((left.Hue + right.Hue).Clamp(0f, 1f), (left.Saturation + right.Saturation).Clamp(0f, 1f), (left.Value + right.Value).Clamp(0f, 1f));
public static ColorHSV operator -(ColorHSV left, ColorHSV right) => new((left.Hue - right.Hue).Clamp(0f, 1f), (left.Saturation - right.Saturation).Clamp(0f, 1f), (left.Value - right.Value).Clamp(0f, 1f));
public static ColorHSV operator *(ColorHSV left, ColorHSV right) => new((left.Hue * right.Hue).Clamp(0f, 1f), (left.Saturation * right.Saturation).Clamp(0f, 1f), (left.Value * right.Value).Clamp(0f, 1f));
public static ColorHSV operator *(ColorHSV color, float value) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
public static ColorHSV operator *(float value, ColorHSV color) => new((color.Hue * value).Clamp(0f, 1f), (color.Saturation * value).Clamp(0f, 1f), (color.Value * value).Clamp(0f, 1f));
public static ColorHSV operator /(ColorHSV color, float value) => new((color.Hue / value).Clamp(0f, 1f), (color.Saturation / value).Clamp(0f, 1f), (color.Value / value).Clamp(0f, 1f));
public static bool operator ==(ColorHSV left, ColorHSV right) => left.Hue.ApproximatelyEquals(right.Hue) && left.Saturation.ApproximatelyEquals(right.Saturation) && left.Value.ApproximatelyEquals(right.Value);
public static bool operator !=(ColorHSV left, ColorHSV right) => !left.Hue.ApproximatelyEquals(right.Hue) || !left.Saturation.ApproximatelyEquals(right.Saturation) || !left.Value.ApproximatelyEquals(right.Value);
public static implicit operator ColorHSV(ColorRGBA rgba) => (ColorRGB)rgba;
public static implicit operator ColorHSV(ColorRGB rgb)
{
float hue;
float saturation;
float value;
float rd = rgb.R / 255f;
float gd = rgb.G / 255f;
float bd = rgb.B / 255f;
float max = Math.Max(rd, Math.Max(gd, bd));
float min = Math.Min(rd, Math.Min(gd, bd));
float delta = max - min;
if (delta.ApproximatelyEquals(0))
hue = 0f;
else if (max.ApproximatelyEquals(rd))
hue = 60f * ((gd - bd) / delta % 6f);
else if (max.ApproximatelyEquals(gd))
hue = 60f * (((bd - rd) / delta) + 2f);
else
hue = 60f * (((rd - gd) / delta) + 4f);
if (hue < 0f)
hue += 360f;
hue /= 360f;
saturation = max.ApproximatelyEquals(0f) ? 0f : delta / max;
value = max;
return new(hue, saturation, value);
}
/// <summary>
/// Inverts the given <see cref="ColorHSV"/>.
/// </summary>
/// <param name="color">The <see cref="ColorHSV"/>.</param>
/// <returns>The inverted <see cref="ColorHSV"/>.</returns>
public static ColorHSV Invert(ColorHSV color) => -color;
/// <summary>
/// Adds two <see cref="ColorHSV"/>s.
/// </summary>
/// <param name="left">The first <see cref="ColorHSV"/>.</param>
/// <param name="right">The second <see cref="ColorHSV"/>.</param>
/// <returns>The sum of the two <see cref="ColorHSV"/>s.</returns>
public static ColorHSV Add(ColorHSV left, ColorHSV right) => left + right;
/// <summary>
/// Subtracts one <see cref="ColorHSV"/> from another.
/// </summary>
/// <param name="left">The <see cref="ColorHSV"/> to subtract from.</param>
/// <param name="right">The <see cref="ColorHSV"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="ColorHSV"/> from the first.</returns>
public static ColorHSV Subtract(ColorHSV left, ColorHSV right) => left - right;
/// <summary>
/// Multiplies a <see cref="ColorHSV"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorHSV"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="ColorHSV"/> by the scalar value.</returns>
public static ColorHSV Multiply(ColorHSV color, float value) => color * value;
/// <summary>
/// Divides a <see cref="ColorHSV"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorHSV"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="ColorHSV"/> by the scalar value.</returns>
public static ColorHSV Divide(ColorHSV color, float value) => color / value;
/// <summary>
/// Calculates the <see cref="ColorHSV"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="ColorHSV"/> from the starting point to the ending point.</returns>
public static ColorHSV FromTo(ColorHSV from, ColorHSV to) => to - from;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorHSV"/>s.
/// </summary>
/// <param name="from">The starting <see cref="ColorHSV"/> (t = 0).</param>
/// <param name="to">The ending <see cref="ColorHSV"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorHSV"/>.</returns>
public static ColorHSV Lerp(ColorHSV from, ColorHSV to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Converts the <see cref="ColorHSV"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorHSV"/>.</returns>
public override string ToString() => $"{nameof(ColorHSV)}({Hue}, {Saturation}, {Value})";
/// <summary>
/// Checks if two <see cref="ColorHSV"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="ColorHSV"/>.</param>
/// <param name="right">The second <see cref="ColorHSV"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="ColorHSV"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(ColorHSV left, ColorHSV right, float epsilon = float.Epsilon)
=> left.Hue.ApproximatelyEquals(right.Hue, epsilon) && left.Saturation.ApproximatelyEquals(right.Saturation, epsilon) && left.Value.ApproximatelyEquals(right.Value, epsilon);
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorHSV"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorHSV"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorHSV"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorHSV objVec && Hue.Equals(objVec.Hue) && Saturation.Equals(objVec.Saturation) && Value.Equals(objVec.Value);
/// <summary>
/// Generates a hash code for the <see cref="ColorHSV"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorHSV"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(Hue, Saturation, Value);
}
/// <summary>
/// Provides extension methods for <see cref="ColorHSV"/> type.
/// </summary>
public static class ColorHSVExtensions
{
/// <inheritdoc cref="ColorHSV.Add(ColorHSV, ColorHSV)" />
public static ColorHSV Add(this ColorHSV color, ColorHSV value) => ColorHSV.Add(color, value);
/// <inheritdoc cref="ColorHSV.Subtract(ColorHSV, ColorHSV)" />
public static ColorHSV Subtract(this ColorHSV color, ColorHSV value) => ColorHSV.Subtract(color, value);
/// <inheritdoc cref="ColorHSV.Multiply(ColorHSV, ColorHSV)" />
public static ColorHSV Multiply(this ColorHSV color, float value) => ColorHSV.Multiply(color, value);
/// <inheritdoc cref="ColorHSV.Divide(ColorHSV, ColorHSV)" />
public static ColorHSV Divide(this ColorHSV color, float value) => ColorHSV.Divide(color, value);
/// <inheritdoc cref="ColorHSV.FromTo(ColorHSV, ColorHSV)" />
public static ColorHSV FromTo(this ColorHSV from, ColorHSV to) => ColorHSV.FromTo(from, to);
/// <inheritdoc cref="ColorHSV.Lerp(ColorHSV, ColorHSV, float)" />
public static ColorHSV Lerp(this ColorHSV from, ColorHSV to, float t) => ColorHSV.Lerp(from, to, t);
/// <inheritdoc cref="ColorHSV.ApproximatelyEquals(ColorHSV, ColorHSV, float) " />
public static bool ApproximatelyEquals(this ColorHSV left, ColorHSV right, float epsilon = float.Epsilon) => ColorHSV.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,164 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an RGB color.
/// </summary>
/// <param name="r">Red value of the <see cref="ColorRGB"/>.</param>
/// <param name="g">Green value of the <see cref="ColorRGB"/>.</param>
/// <param name="b">Blue value of the <see cref="ColorRGB"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="ColorRGB"/> struct with the specified values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct ColorRGB(byte r, byte g, byte b)
{
/// <summary>
/// The Red value of the <see cref="ColorRGB"/>.
/// </summary>
public readonly byte R = r;
/// <summary>
/// The Green value of the <see cref="ColorRGB"/>.
/// </summary>
public readonly byte G = g;
/// <summary>
/// The Blue value of the <see cref="ColorRGB"/>.
/// </summary>
public readonly byte B = b;
public static ColorRGB operator -(ColorRGB color) => new((byte)(255 - color.R), (byte)(255 - color.G), (byte)(255 - color.B));
public static ColorRGB operator +(ColorRGB left, ColorRGB right) => new((byte)(left.R + right.R).Clamp(0, 255), (byte)(left.G + right.G).Clamp(0, 255), (byte)(left.B + right.B).Clamp(0, 255));
public static ColorRGB operator -(ColorRGB left, ColorRGB right) => new((byte)(left.R - right.R).Clamp(0, 255), (byte)(left.G - right.G).Clamp(0, 255), (byte)(left.B - right.B).Clamp(0, 255));
public static ColorRGB operator *(ColorRGB left, ColorRGB right) => new((byte)(left.R * right.R).Clamp(0, 255), (byte)(left.G * right.G).Clamp(0, 255), (byte)(left.B * right.B).Clamp(0, 255));
public static ColorRGB operator *(ColorRGB color, float value) => new((byte)(color.R * value).Clamp(0, 255), (byte)(color.G * value).Clamp(0, 255), (byte)(color.B * value).Clamp(0, 255));
public static ColorRGB operator *(float value, ColorRGB color) => new((byte)(color.R * value).Clamp(0, 255), (byte)(color.G * value).Clamp(0, 255), (byte)(color.B * value).Clamp(0, 255));
public static ColorRGB operator /(ColorRGB color, float value) => new((byte)(color.R / value).Clamp(0, 255), (byte)(color.G / value).Clamp(0, 255), (byte)(color.B / value).Clamp(0, 255));
public static bool operator ==(ColorRGB left, ColorRGB right) => left.R == right.R && left.G == right.G && left.B == right.B;
public static bool operator !=(ColorRGB left, ColorRGB right) => left.R != right.R || left.G != right.G || left.B != right.B;
public static implicit operator ColorRGB(ColorRGBA rgba) => new(rgba.R, rgba.G, rgba.B);
public static implicit operator ColorRGB(ColorHSV hsv)
{
float hue = hsv.Hue * 360f;
float chroma = hsv.Value * hsv.Saturation;
float x = chroma * (1f - Math.Abs(hue / 60f % 2f - 1f));
float m = hsv.Value - chroma;
float r1 = 0f;
float g1 = 0f;
float b1 = 0f;
if (hue < 60) { r1 = chroma; g1 = x; b1 = 0; }
else if (hue < 120) { r1 = x; g1 = chroma; b1 = 0; }
else if (hue < 180) { r1 = 0; g1 = chroma; b1 = x; }
else if (hue < 240) { r1 = 0; g1 = x; b1 = chroma; }
else if (hue < 300) { r1 = x; g1 = 0; b1 = chroma; }
else if (hue <= 360) { r1 = chroma; g1 = 0; b1 = x; }
byte r = (byte)Math.RoundToInt((r1 + m) * 255);
byte g = (byte)Math.RoundToInt((g1 + m) * 255);
byte b = (byte)Math.RoundToInt((b1 + m) * 255);
return new(r, g, b);
}
/// <summary>
/// Inverts the given <see cref="ColorRGB"/>.
/// </summary>
/// <param name="color">The <see cref="ColorRGB"/>.</param>
/// <returns>The inverted <see cref="ColorRGB"/>.</returns>
public static ColorRGB Invert(ColorRGB color) => -color;
/// <summary>
/// Adds two <see cref="ColorRGB"/>s.
/// </summary>
/// <param name="left">The first <see cref="ColorRGB"/>.</param>
/// <param name="right">The second <see cref="ColorRGB"/>.</param>
/// <returns>The sum of the two <see cref="ColorRGB"/>s.</returns>
public static ColorRGB Add(ColorRGB left, ColorRGB right) => left + right;
/// <summary>
/// Subtracts one <see cref="ColorRGB"/> from another.
/// </summary>
/// <param name="left">The <see cref="ColorRGB"/> to subtract from.</param>
/// <param name="right">The <see cref="ColorRGB"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="ColorRGB"/> from the first.</returns>
public static ColorRGB Subtract(ColorRGB left, ColorRGB right) => left - right;
/// <summary>
/// Multiplies a <see cref="ColorRGB"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorRGB"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="ColorRGB"/> by the scalar value.</returns>
public static ColorRGB Multiply(ColorRGB color, float value) => color * value;
/// <summary>
/// Divides a <see cref="ColorRGB"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorRGB"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="ColorRGB"/> by the scalar value.</returns>
public static ColorRGB Divide(ColorRGB color, float value) => color / value;
/// <summary>
/// Calculates the <see cref="ColorRGB"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="ColorRGB"/> from the starting point to the ending point.</returns>
public static ColorRGB FromTo(ColorRGB from, ColorRGB to) => to - from;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorRGB"/>s.
/// </summary>
/// <param name="from">The starting <see cref="ColorRGB"/> (t = 0).</param>
/// <param name="to">The ending <see cref="ColorRGB"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorRGB"/>.</returns>
public static ColorRGB Lerp(ColorRGB from, ColorRGB to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Converts the <see cref="ColorRGB"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGB"/>.</returns>
public override string ToString() => $"{nameof(ColorRGB)}({R}, {G}, {B})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorRGB"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorRGB"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGB"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorRGB objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B);
/// <summary>
/// Generates a hash code for the <see cref="ColorRGB"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorRGB"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(R, G, B);
}
/// <summary>
/// Provides extension methods for <see cref="ColorRGB"/> type.
/// </summary>
public static class ColorRGBExtensions
{
/// <inheritdoc cref="ColorRGB.Add(ColorRGB, ColorRGB)" />
public static ColorRGB Add(this ColorRGB color, ColorRGB value) => ColorRGB.Add(color, value);
/// <inheritdoc cref="ColorRGB.Subtract(ColorRGB, ColorRGB)" />
public static ColorRGB Subtract(this ColorRGB color, ColorRGB value) => ColorRGB.Subtract(color, value);
/// <inheritdoc cref="ColorRGB.Multiply(ColorRGB, ColorRGB)" />
public static ColorRGB Multiply(this ColorRGB color, float value) => ColorRGB.Multiply(color, value);
/// <inheritdoc cref="ColorRGB.Divide(ColorRGB, ColorRGB)" />
public static ColorRGB Divide(this ColorRGB color, float value) => ColorRGB.Divide(color, value);
/// <inheritdoc cref="ColorRGB.FromTo(ColorRGB, ColorRGB)" />
public static ColorRGB FromTo(this ColorRGB from, ColorRGB to) => ColorRGB.FromTo(from, to);
/// <inheritdoc cref="ColorRGB.Lerp(ColorRGB, ColorRGB, float)" />
public static ColorRGB Lerp(this ColorRGB from, ColorRGB to, float t) => ColorRGB.Lerp(from, to, t);
}

View File

@@ -0,0 +1,147 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents an RGBA color.
/// </summary>
/// <param name="r">Red value of the <see cref="ColorRGBA"/>.</param>
/// <param name="g">Green value of the <see cref="ColorRGBA"/>.</param>
/// <param name="b">Blue value of the <see cref="ColorRGBA"/>.</param>
/// <param name="a">Alpha value of the <see cref="ColorRGBA"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="ColorRGBA"/> struct with the specified values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}")]
public readonly struct ColorRGBA(byte r, byte g, byte b, byte a = 255)
{
/// <summary>
/// The Red value of the <see cref="ColorRGBA"/>.
/// </summary>
public readonly byte R = r;
/// <summary>
/// The Green value of the <see cref="ColorRGBA"/>.
/// </summary>
public readonly byte G = g;
/// <summary>
/// The Blue value of the <see cref="ColorRGBA"/>.
/// </summary>
public readonly byte B = b;
/// <summary>
/// The Alpha value of the <see cref="ColorRGBA"/>.
/// </summary>
public readonly byte A = a;
public static ColorRGBA operator -(ColorRGBA color) => new((byte)(255 - color.R), (byte)(255 - color.G), (byte)(255 - color.B), color.A);
public static ColorRGBA operator +(ColorRGBA left, ColorRGBA right) => new((byte)(left.R + right.R).Clamp(0, 255), (byte)(left.G + right.G).Clamp(0, 255), (byte)(left.B + right.B).Clamp(0, 255), (byte)(left.A + right.A).Clamp(0, 255));
public static ColorRGBA operator -(ColorRGBA left, ColorRGBA right) => new((byte)(left.R - right.R).Clamp(0, 255), (byte)(left.G - right.G).Clamp(0, 255), (byte)(left.B - right.B).Clamp(0, 255), (byte)(left.A - right.A).Clamp(0, 255));
public static ColorRGBA operator *(ColorRGBA left, ColorRGBA right) => new((byte)(left.R * right.R).Clamp(0, 255), (byte)(left.G * right.G).Clamp(0, 255), (byte)(left.B * right.B).Clamp(0, 255), (byte)(left.A * right.A).Clamp(0, 255));
public static ColorRGBA operator *(ColorRGBA color, float value) => new((byte)(color.R * value).Clamp(0, 255), (byte)(color.G * value).Clamp(0, 255), (byte)(color.B * value).Clamp(0, 255), (byte)(color.A * value).Clamp(0, 255));
public static ColorRGBA operator *(float value, ColorRGBA color) => new((byte)(color.R * value).Clamp(0, 255), (byte)(color.G * value).Clamp(0, 255), (byte)(color.B * value).Clamp(0, 255), (byte)(color.A * value).Clamp(0, 255));
public static ColorRGBA operator /(ColorRGBA color, float value) => new((byte)(color.R / value).Clamp(0, 255), (byte)(color.G / value).Clamp(0, 255), (byte)(color.B / value).Clamp(0, 255), (byte)(color.A / value).Clamp(0, 255));
public static bool operator ==(ColorRGBA left, ColorRGBA right) => left.R == right.R && left.G == right.G && left.B == right.B && left.A == right.A;
public static bool operator !=(ColorRGBA left, ColorRGBA right) => left.R != right.R || left.G != right.G || left.B != right.B || left.A != right.A;
public static implicit operator ColorRGBA(ColorRGB rgb) => new(rgb.R, rgb.G, rgb.B, 255);
public static implicit operator ColorRGBA(ColorHSV hsv) => (ColorRGB)hsv;
/// <summary>
/// Inverts the given <see cref="ColorRGBA"/>.
/// </summary>
/// <param name="color">The <see cref="ColorRGBA"/>.</param>
/// <returns>The inverted <see cref="ColorRGBA"/>.</returns>
public static ColorRGBA Invert(ColorRGBA color) => -color;
/// <summary>
/// Adds two <see cref="ColorRGBA"/>s.
/// </summary>
/// <param name="left">The first <see cref="ColorRGBA"/>.</param>
/// <param name="right">The second <see cref="ColorRGBA"/>.</param>
/// <returns>The sum of the two <see cref="ColorRGBA"/>s.</returns>
public static ColorRGBA Add(ColorRGBA left, ColorRGBA right) => left + right;
/// <summary>
/// Subtracts one <see cref="ColorRGBA"/> from another.
/// </summary>
/// <param name="left">The <see cref="ColorRGBA"/> to subtract from.</param>
/// <param name="right">The <see cref="ColorRGBA"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="ColorRGBA"/> from the first.</returns>
public static ColorRGBA Subtract(ColorRGBA left, ColorRGBA right) => left - right;
/// <summary>
/// Multiplies a <see cref="ColorRGBA"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorRGBA"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="ColorRGBA"/> by the scalar value.</returns>
public static ColorRGBA Multiply(ColorRGBA color, float value) => color * value;
/// <summary>
/// Divides a <see cref="ColorRGBA"/> by a scalar value.
/// </summary>
/// <param name="color">The <see cref="ColorRGBA"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="ColorRGBA"/> by the scalar value.</returns>
public static ColorRGBA Divide(ColorRGBA color, float value) => color / value;
/// <summary>
/// Calculates the <see cref="ColorRGBA"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="ColorRGBA"/> from the starting point to the ending point.</returns>
public static ColorRGBA FromTo(ColorRGBA from, ColorRGBA to) => to - from;
/// <summary>
/// Performs linear interpolation between two <see cref="ColorRGBA"/>s.
/// </summary>
/// <param name="from">The starting <see cref="ColorRGBA"/> (t = 0).</param>
/// <param name="to">The ending <see cref="ColorRGBA"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="ColorRGBA"/>.</returns>
public static ColorRGBA Lerp(ColorRGBA from, ColorRGBA to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Converts the <see cref="ColorRGBA"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="ColorRGBA"/>.</returns>
public override string ToString() => $"{nameof(ColorRGBA)}({R}, {G}, {B}, {A})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="ColorRGBA"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="ColorRGBA"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="ColorRGBA"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is ColorRGBA objVec && R.Equals(objVec.R) && G.Equals(objVec.G) && B.Equals(objVec.B) && A.Equals(objVec.A);
/// <summary>
/// Generates a hash code for the <see cref="ColorRGBA"/>.
/// </summary>
/// <returns>A hash code for the <see cref="ColorRGBA"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(R, G, B, A);
}
/// <summary>
/// Provides extension methods for <see cref="ColorRGBA"/> type.
/// </summary>
public static class ColorRGBAExtensions
{
/// <inheritdoc cref="ColorRGBA.Add(ColorRGBA, ColorRGBA)" />
public static ColorRGBA Add(this ColorRGBA color, ColorRGBA value) => ColorRGBA.Add(color, value);
/// <inheritdoc cref="ColorRGBA.Subtract(ColorRGBA, ColorRGBA)" />
public static ColorRGBA Subtract(this ColorRGBA color, ColorRGBA value) => ColorRGBA.Subtract(color, value);
/// <inheritdoc cref="ColorRGBA.Multiply(ColorRGBA, ColorRGBA)" />
public static ColorRGBA Multiply(this ColorRGBA color, float value) => ColorRGBA.Multiply(color, value);
/// <inheritdoc cref="ColorRGBA.Divide(ColorRGBA, ColorRGBA)" />
public static ColorRGBA Divide(this ColorRGBA color, float value) => ColorRGBA.Divide(color, value);
/// <inheritdoc cref="ColorRGBA.FromTo(ColorRGBA, ColorRGBA)" />
public static ColorRGBA FromTo(this ColorRGBA from, ColorRGBA to) => ColorRGBA.FromTo(from, to);
/// <inheritdoc cref="ColorRGBA.Lerp(ColorRGBA, ColorRGBA, float)" />
public static ColorRGBA Lerp(this ColorRGBA from, ColorRGBA to, float t) => ColorRGBA.Lerp(from, to, t);
}

View File

@@ -0,0 +1,219 @@
using System.Diagnostics.CodeAnalysis;
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a 2D line segment defined by two endpoints.
/// </summary>
/// <param name="from">The starting point of the <see cref="Line2D"/> segment.</param>
/// <param name="to">The ending point of the <see cref="Line2D"/> segment.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Line2D"/> struct with the specified endpoints.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("From: {From.ToString(),nq}, To: {To.ToString(),nq}, Direction: {Direction.ToString(),nq}, Length: {Length}")]
public readonly struct Line2D(Vector2D from, Vector2D to)
{
/// <summary>
/// The starting point of the <see cref="Line2D"/> segment.
/// </summary>
public readonly Vector2D From = from;
/// <summary>
/// The ending point of the <see cref="Line2D"/> segment.
/// </summary>
public readonly Vector2D To = to;
/// <summary>
/// The reversed <see cref="Line2D"/> segment.
/// </summary>
public readonly Line2D Reversed => new(To, From);
/// <summary>
/// The normalized direction <see cref="Vector2D"/> of the <see cref="Line2D"/> segment.
/// </summary>
public readonly Vector2D Direction => From.FromTo(To).Normalize();
/// <summary>
/// The length of the <see cref="Line2D"/> segment.
/// </summary>
public readonly float Length => From.FromTo(To).Length();
/// <summary>
/// The squared length of the <see cref="Line2D"/> segment.
/// </summary>
public readonly float LengthSquared => From.FromTo(To).LengthSquared();
/// <summary>
/// The equation of the <see cref="Line2D"/> defined by this <see cref="Line2D"/> segment.
/// </summary>
public static Line2DEquation GetLineEquation(Line2D 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 Line2DEquation(slope, yOffset);
}
/// <summary>
/// Determines whether the specified <see cref="Vector2D"/> lies on the <see cref="Line2D"/>.
/// </summary>
public static bool Intersects(Line2D line, Vector2D point)
=> Line2DEquation.Resolve(GetLineEquation(line), point.X).ApproximatelyEquals(point.Y);
/// <summary>
/// Calculates the parameter 't' representing the point's position on the <see cref="Line2D"/> segment.
/// </summary>
public static float GetT(Line2D line, Vector2D point)
{
float fromX = Math.Abs(line.From.X);
float toX = Math.Abs(line.To.X);
float pointX = Math.Abs(point.X);
float min = Math.Min(fromX, toX);
float max = Math.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="Line2D"/> segment intersects with another <see cref="Line2D"/> segment.
/// </summary>
public static bool Intersects(Line2D left, Line2D 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="Line2D"/> segment.
/// </summary>
public static bool OnSegment(Line2D line, Vector2D point)
{
if (point.X <= Math.Max(line.From.X, line.To.X) && point.X >= Math.Min(line.From.X, line.To.X) &&
point.Y <= Math.Max(line.From.Y, line.To.Y) && point.Y >= Math.Min(line.From.Y, line.To.Y))
return true;
return false;
}
/// <summary>
/// Determines whether two <see cref="Line2D"/> segments intersect.
/// </summary>
public static bool Intersects(Line2D left, Line2D 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="Line2D"/> segments.
/// </summary>
public static Vector2D IntersectionPoint(Line2D left, Line2D 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="Line2D"/> segment.
/// </summary>
public static float IntersectionParameterT(Line2D left, Line2D 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="Line2D"/> segment using parameter 't'.
/// </summary>
public static Vector2D Lerp(Line2D line, float t)
=> Vector2D.Lerp(line.From, line.To, t);
/// <summary>
/// Calculates the closest point on the <see cref="Line2D"/> segment to the specified point.
/// </summary>
public static Vector2D ClosestPointTo(Line2D line, Vector2D point)
{
Vector2D edgeVector = line.From.FromTo(line.To);
Vector2D pointVector = point - line.From;
float t = (pointVector.X * edgeVector.X + pointVector.Y * edgeVector.Y) / (edgeVector.X * edgeVector.X + edgeVector.Y * edgeVector.Y);
t = Math.Max(0, Math.Min(1, t));
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="Line2D"/> segments are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="Line2D"/>.</param>
/// <param name="right">The second <see cref="Line2D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Line2D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Line2D left, Line2D right, float epsilon = float.Epsilon)
=> left.From.ApproximatelyEquals(right.From, epsilon) && left.To.ApproximatelyEquals(right.To, epsilon);
}
/// <summary>
/// Provides extension methods for the <see cref="Line2D"/> struct.
/// </summary>
public static class Line2DExtensions
{
/// <inheritdoc cref="Line2D.Lerp(Line2D, float)" />
public static Vector2D Lerp(this Line2D line, float t) => Line2D.Lerp(line, t);
/// <inheritdoc cref="Line2D.GetLineEquation(Line2D)" />
public static Line2DEquation ToLineEquation(this Line2D line) => Line2D.GetLineEquation(line);
/// <inheritdoc cref="Line2D.Intersects(Line2D, Vector2D)" />
public static bool Intersects(this Line2D line, Vector2D point) => Line2D.Intersects(line, point);
/// <inheritdoc cref="Line2D.GetT(Line2D, Vector2D)" />
public static float GetT(this Line2D line, Vector2D point) => Line2D.GetT(line, point);
/// <inheritdoc cref="Line2D.Intersects(Line2D, Line2D)" />
public static bool Intersects(this Line2D left, Line2D right) => Line2D.Intersects(left, right);
/// <inheritdoc cref="Line2D.Intersects(Line2D, Line2D, out Vector2D?)" />
public static bool Intersects(this Line2D left, Line2D right, [NotNullWhen(returnValue: true)] out Vector2D? point) => Line2D.Intersects(left, right, out point);
/// <inheritdoc cref="Line2D.ApproximatelyEquals(Line2D, Line2D, float)" />
public static bool ApproximatelyEquals(this Line2D left, Line2D right, float epsilon = float.Epsilon) => Line2D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,53 @@
namespace Syntriax.Engine.Core;
/// <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="Line2DEquation"/> struct with the specified slope and y-intercept.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("y = {Slope}x + {OffsetY}")]
public readonly struct Line2DEquation(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(Line2DEquation 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>
/// <param name="epsilon">The epsilon range.</param>
/// <returns>True if the line equations are approximately equal; otherwise, false.</returns>
public static bool ApproximatelyEquals(Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon)
=> left.Slope.ApproximatelyEquals(right.Slope, epsilon) && left.OffsetY.ApproximatelyEquals(right.OffsetY, epsilon);
}
/// <summary>
/// Provides extension methods for the LineEquation struct.
/// </summary>
public static class Line2DEquationExtensions
{
/// <inheritdoc cref="Line2DEquation.Resolve(Line2DEquation, float)" />
public static float Resolve(this Line2DEquation lineEquation, float x) => Line2DEquation.Resolve(lineEquation, x);
/// <inheritdoc cref="Line2DEquation.ApproximatelyEquals(Line2DEquation, Line2DEquation, float)" />
public static bool ApproximatelyEquals(this Line2DEquation left, Line2DEquation right, float epsilon = float.Epsilon) => Line2DEquation.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,85 @@
namespace Syntriax.Engine.Core;
/// <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="Projection1D"/> struct with the specified minimum and maximum values.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("Min: {Min}, Max: {Max}")]
public readonly struct Projection1D(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(Projection1D left, Projection1D right) => Overlaps(left, right, out float _);
/// <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(Projection1D left, Projection1D 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="Projection1D"/> struct.
/// </summary>
public static class Projection1DExtensions
{
/// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D)" />
public static bool Overlaps(this Projection1D left, Projection1D right) => Projection1D.Overlaps(left, right);
/// <inheritdoc cref="Projection1D.Overlaps(Projection1D, Projection1D, out float)" />
public static bool Overlaps(this Projection1D left, Projection1D right, out float depth) => Projection1D.Overlaps(left, right, out depth);
}

View File

@@ -0,0 +1,357 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a 3D space rotation.
/// </summary>
/// <param name="x">X(i) position of the <see cref="Quaternion"/>.</param>
/// <param name="y">Y(j) position of the <see cref="Quaternion"/>.</param>
/// <param name="z">Z(k) position of the <see cref="Quaternion"/>.</param>
/// <param name="w">W(a) position of the <see cref="Quaternion"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Quaternion"/> struct with the specified positions.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
public readonly struct Quaternion(float x, float y, float z, float w)
{
/// <summary>
/// The X(i) imaginary of the <see cref="Quaternion"/>.
/// </summary>
public readonly float X = x;
/// <summary>
/// The Y(j) imaginary of the <see cref="Quaternion"/>.
/// </summary>
public readonly float Y = y;
/// <summary>
/// The Z(k) imaginary of the <see cref="Quaternion"/>.
/// </summary>
public readonly float Z = z;
/// <summary>
/// The W(a) scalar of the <see cref="Quaternion"/>.
/// </summary>
public readonly float W = w;
/// <summary>
/// The magnitude (length) of the <see cref="Quaternion"/>.
/// </summary>
public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Quaternion"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// The normalized form of the <see cref="Quaternion"/> (a <see cref="Quaternion"/> with the same direction and a magnitude of 1).
/// </summary>
public Quaternion Normalized => Normalize(this);
/// <summary>
/// Represents the <see cref="Quaternion"/> with no rotation.
/// </summary>
public readonly static Quaternion Zero = new(0f, 0f, 0f, 0f);
/// <summary>
/// Represents the identity <see cref="Quaternion"/>.
/// </summary>
public readonly static Quaternion Identity = new(0f, 0f, 0f, 1f);
public static Quaternion operator -(Quaternion quaternion) => new(-quaternion.X, -quaternion.Y, -quaternion.Z, quaternion.W);
public static Quaternion operator +(Quaternion left, Quaternion right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z, left.W + right.W);
public static Quaternion operator -(Quaternion left, Quaternion right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z, left.W - right.W);
public static Quaternion operator *(Quaternion quaternion, float value) => new(quaternion.X * value, quaternion.Y * value, quaternion.Z * value, quaternion.W * value);
public static Quaternion operator *(float value, Quaternion quaternion) => new(quaternion.X * value, quaternion.Y * value, quaternion.Z * value, quaternion.W * value);
public static Quaternion operator *(Quaternion left, Quaternion right)
=> new(
left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y,
left.W * right.Y + left.Y * right.W + left.Z * right.X - left.X * right.Z,
left.W * right.Z + left.Z * right.W + left.X * right.Y - left.Y * right.X,
left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z
);
public static Quaternion operator /(Quaternion quaternion, float value) => new(quaternion.X / value, quaternion.Y / value, quaternion.Z / value, quaternion.W / value);
public static bool operator ==(Quaternion left, Quaternion right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z && left.W == right.W;
public static bool operator !=(Quaternion left, Quaternion right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z || left.W != right.W;
public static implicit operator Quaternion(System.Numerics.Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
public static implicit operator System.Numerics.Quaternion(Quaternion quaternion) => new(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W);
/// <summary>
/// Calculates the length of the <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/>.</param>
/// <returns>The length of the <see cref="Quaternion"/>.</returns>
public static float Length(Quaternion quaternion) => Math.Sqrt(LengthSquared(quaternion));
/// <summary>
/// Calculates the squared length of the <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/>.</param>
/// <returns>The squared length of the <see cref="Quaternion"/>.</returns>
public static float LengthSquared(Quaternion quaternion) => quaternion.X * quaternion.X + quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z + quaternion.Z * quaternion.Z + quaternion.W * quaternion.W;
/// <summary>
/// Adds two <see cref="Quaternion"/>s.
/// </summary>
/// <param name="left">The first <see cref="Quaternion"/>.</param>
/// <param name="right">The second <see cref="Quaternion"/>.</param>
/// <returns>The sum of the two <see cref="Quaternion"/>s.</returns>
public static Quaternion Add(Quaternion left, Quaternion right) => left + right;
/// <summary>
/// Subtracts one <see cref="Quaternion"/> from another.
/// </summary>
/// <param name="left">The <see cref="Quaternion"/> to subtract from.</param>
/// <param name="right">The <see cref="Quaternion"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Quaternion"/> from the first.</returns>
public static Quaternion Subtract(Quaternion left, Quaternion right) => left - right;
/// <summary>
/// Multiplies a <see cref="Quaternion"/> by a scalar value.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Quaternion"/> by the scalar value.</returns>
public static Quaternion Multiply(Quaternion quaternion, float value) => quaternion * value;
/// <summary>
/// Divides a <see cref="Quaternion"/> by a scalar value.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Quaternion"/> by the scalar value.</returns>
public static Quaternion Divide(Quaternion quaternion, float value) => quaternion / value;
/// <summary>
/// Normalizes the <see cref="Quaternion"/> (creates a unit <see cref="Quaternion"/> with the same direction).
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/> to normalize.</param>
/// <returns>The normalized <see cref="Quaternion"/>.</returns>
public static Quaternion Normalize(Quaternion quaternion) => quaternion / Length(quaternion);
/// <summary>
/// Inverts the direction of the <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/>.</param>
/// <returns>The inverted <see cref="Quaternion"/>.</returns>
public static Quaternion Invert(Quaternion quaternion) => Conjugate(quaternion) / LengthSquared(quaternion);
/// <summary>
/// Conjugate of the <see cref="Quaternion"/>.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/>.</param>
/// <returns>The inverted <see cref="Quaternion"/>.</returns>
public static Quaternion Conjugate(Quaternion quaternion) => -quaternion;
/// <summary>
/// Rotates a <see cref="Vector3D"/> by applying the provided <see cref="Quaternion"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to be rotated.</param>
/// <param name="quaternion">The <see cref="Quaternion"/> to used for applying rotation.</param>
/// <returns>The rotated <see cref="Vector3D"/>.</returns>
public static Vector3D RotateVector(Vector3D vector, Quaternion quaternion)
{
Quaternion rotation = quaternion * new Quaternion(vector.X, vector.Y, vector.Z, 0) * Invert(quaternion);
return new(rotation.X, rotation.Y, rotation.Z);
}
/// <summary>
/// Performs spherical linear interpolation between two <see cref="Quaternion"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Quaternion"/> (t = 0).</param>
/// <param name="to">The target <see cref="Quaternion"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Quaternion"/>.</returns>
public static Quaternion SLerp(Quaternion from, Quaternion to, float t)
{
float dot = Dot(from, to);
if (dot < 0.0f)
{
from = new Quaternion(-from.X, -from.Y, -from.Z, -from.W);
dot = -dot;
}
if (dot > 0.9995f)
return Lerp(from, to, t);
float angle = Math.Acos(dot);
float sinAngle = Math.Sin(angle);
float fromWeight = Math.Sin((1f - t) * angle) / sinAngle;
float toWeight = Math.Sin(t * angle) / sinAngle;
return from * fromWeight + to * toWeight;
}
/// <summary>
/// Performs linear interpolation between two <see cref="Quaternion"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Quaternion"/> (t = 0).</param>
/// <param name="to">The target <see cref="Quaternion"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Quaternion"/>.</returns>
public static Quaternion Lerp(Quaternion from, Quaternion to, float t) => Normalize(new(from.X.Lerp(to.X, t), from.W.Lerp(to.W, t), from.Z.Lerp(to.Z, t), from.W.Lerp(to.W, t)));
/// <summary>
/// Calculates the dot product of two <see cref="Quaternion"/>s.
/// </summary>
/// <param name="left">The first <see cref="Quaternion"/>.</param>
/// <param name="right">The second <see cref="Quaternion"/>.</param>
/// <returns>The dot product of the two <see cref="Quaternion"/>s.</returns>
public static float Dot(Quaternion left, Quaternion right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z + left.W * right.W;
/// <summary>
/// Calculates the <see cref="Quaternion"/> from given axis and angle.
/// </summary>
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
/// <param name="angle">The angle in radians.</param>
/// <returns>The rotation <see cref="Quaternion"/> calculated by the given parameters.</returns>
public static Quaternion FromAxisAngle(Vector3D axis, float angle)
{
float halfAngle = angle * .5f;
float sinHalf = Math.Sin(halfAngle);
return new Quaternion(axis.X * sinHalf, axis.Y * sinHalf, axis.Z * sinHalf, Math.Cos(halfAngle));
}
/// <summary>
/// Calculates the <see cref="Quaternion"/> from given yaw, pitch and roll values.
/// </summary>
/// <returns>The rotation <see cref="Quaternion"/> calculated by the given parameters.</returns>
public static Quaternion FromAngles(float x, float y, float z)
{
float cosX = Math.Cos(x * .5f);
float sinX = Math.Sin(x * .5f);
float cosY = Math.Cos(y * .5f);
float sinY = Math.Sin(y * .5f);
float cozZ = Math.Cos(z * .5f);
float sinZ = Math.Sin(z * .5f);
return new Quaternion(
x: sinX * cosY * cozZ - cosX * sinY * sinZ,
y: cosX * sinY * cozZ + sinX * cosY * sinZ,
z: cosX * cosY * sinZ - sinX * sinY * cozZ,
w: cosX * cosY * cozZ + sinX * sinY * sinZ
);
}
/// <summary>
/// Calculates the <see cref="System.Numerics.Matrix4x4"/> from given <see cref="Quaternion"/>.
/// </summary>
/// <param name="axis">The axis of the rotation in <see cref="Vector3D"/>.</param>
/// <param name="angle">The angle in radians.</param>
/// <returns>The rotation <see cref="System.Numerics.Matrix4x4"/> calculated by the given <see cref="Quaternion"/>.</returns>
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(Quaternion quaternion)
{
float m00 = 1 - 2 * (quaternion.Y * quaternion.Y + quaternion.Z * quaternion.Z);
float m01 = 2 * (quaternion.X * quaternion.Y - quaternion.W * quaternion.Z);
float m02 = 2 * (quaternion.X * quaternion.Z + quaternion.W * quaternion.Y);
float m03 = 0;
float m10 = 2 * (quaternion.X * quaternion.Y + quaternion.W * quaternion.Z);
float m11 = 1 - 2 * (quaternion.X * quaternion.X + quaternion.Z * quaternion.Z);
float m12 = 2 * (quaternion.Y * quaternion.Z - quaternion.W * quaternion.X);
float m13 = 0;
float m20 = 2 * (quaternion.X * quaternion.Z - quaternion.W * quaternion.Y);
float m21 = 2 * (quaternion.Y * quaternion.Z + quaternion.W * quaternion.X);
float m22 = 1 - 2 * (quaternion.X * quaternion.X + quaternion.Y * quaternion.Y);
float m23 = 0;
float m30 = 0;
float m31 = 0;
float m32 = 0;
float m33 = 1;
return new(
m00, m01, m02, m03,
m10, m11, m12, m13,
m20, m21, m22, m23,
m30, m31, m32, m33
);
}
/// <summary>
/// Checks if two <see cref="Quaternion"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Quaternion"/>.</param>
/// <param name="right">The second <see cref="Quaternion"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Quaternion"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Quaternion left, Quaternion right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon) && left.W.ApproximatelyEquals(right.W, epsilon);
/// <summary>
/// Converts the <see cref="Quaternion"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Quaternion"/>.</returns>
public override string ToString() => $"{nameof(Quaternion)}({W}, {X}, {Y}, {Z})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Quaternion"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Quaternion"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Quaternion"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Quaternion objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z) && W.Equals(objVec.W);
/// <summary>
/// Generates a hash code for the <see cref="Quaternion"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Quaternion"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
}
/// <summary>
/// Provides extension methods for <see cref="Quaternion"/> type.
/// </summary>
public static class QuaternionExtensions
{
/// <inheritdoc cref="Quaternion.Length(Quaternion)" />
public static float Length(this Quaternion quaternion) => Quaternion.Length(quaternion);
/// <inheritdoc cref="Quaternion.LengthSquared(Quaternion)" />
public static float LengthSquared(this Quaternion quaternion) => Quaternion.LengthSquared(quaternion);
/// <inheritdoc cref="Quaternion.Add(Quaternion, Quaternion)" />
public static Quaternion Add(this Quaternion left, Quaternion right) => Quaternion.Add(left, right);
/// <inheritdoc cref="Quaternion.Subtract(Quaternion, Quaternion)" />
public static Quaternion Subtract(this Quaternion left, Quaternion right) => Quaternion.Subtract(left, right);
/// <inheritdoc cref="Quaternion.Multiply(Quaternion, float)" />
public static Quaternion Multiply(this Quaternion quaternion, float value) => Quaternion.Multiply(quaternion, value);
/// <inheritdoc cref="Quaternion.Divide(Quaternion, float)" />
public static Quaternion Divide(this Quaternion quaternion, float value) => Quaternion.Divide(quaternion, value);
/// <inheritdoc cref="Quaternion.Normalize(Quaternion)" />
public static Quaternion Normalize(this Quaternion quaternion) => Quaternion.Normalize(quaternion);
/// <inheritdoc cref="Quaternion.Invert(Quaternion)" />
public static Quaternion Invert(this Quaternion quaternion) => Quaternion.Invert(quaternion);
/// <inheritdoc cref="Quaternion.Conjugate(Quaternion)" />
public static Quaternion Conjugate(this Quaternion quaternion) => Quaternion.Conjugate(quaternion);
/// <inheritdoc cref="Quaternion.RotateVector(Vector3D, Quaternion)" />
public static Vector3D RotateVector(this Vector3D vector, Quaternion quaternion) => Quaternion.RotateVector(vector, quaternion);
/// <inheritdoc cref="Quaternion.SLerp(Quaternion, Quaternion, float)" />
public static Quaternion SLerp(this Quaternion from, Quaternion to, float t) => Quaternion.SLerp(from, to, t);
/// <inheritdoc cref="Quaternion.Lerp(Quaternion, Quaternion, float)" />
public static Quaternion Lerp(this Quaternion from, Quaternion to, float t) => Quaternion.Lerp(from, to, t);
/// <inheritdoc cref="Quaternion.Dot(Quaternion, Quaternion)" />
public static float Dot(this Quaternion left, Quaternion right) => Quaternion.Dot(left, right);
/// <inheritdoc cref="Quaternion.ToRotationMatrix4x4(Quaternion, Quaternion)" />
public static System.Numerics.Matrix4x4 ToRotationMatrix4x4(this Quaternion quaternion) => Quaternion.ToRotationMatrix4x4(quaternion);
/// <inheritdoc cref="Quaternion.FromAxisAngle(Vector3D, float)" />
public static Quaternion CreateRotation(this Vector3D axis, float angle) => Quaternion.FromAxisAngle(axis, angle);
/// <inheritdoc cref="Quaternion.ApproximatelyEquals(Quaternion, Quaternion, float)" />
public static bool ApproximatelyEquals(this Quaternion left, Quaternion right, float epsilon = float.Epsilon) => Quaternion.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,306 @@
using System.Collections;
using System.Collections.Generic;
namespace Syntriax.Engine.Core;
/// <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 a <see cref="Shape2D"/> struct with the specified vertices.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("Vertices Count: {Vertices.Count}")]
public class Shape2D(List<Vector2D> vertices) : IEnumerable<Vector2D>
{
public static Shape2D Triangle => CreateNgon(3, Vector2D.Up);
public static Shape2D Square => CreateNgon(4, Vector2D.One);
public static Shape2D Pentagon => CreateNgon(5, Vector2D.Up);
public static Shape2D Hexagon => CreateNgon(6, Vector2D.Right);
public Event<Shape2D> OnShapeUpdated { get; } = new();
private List<Vector2D> _vertices = vertices;
/// <summary>
/// Gets the vertices of the <see cref="Shape2D"/>.
/// </summary>
public IReadOnlyList<Vector2D> Vertices
{
get => _vertices;
set
{
_vertices.Clear();
foreach (Vector2D vertex in value)
_vertices.Add(vertex);
OnShapeUpdated?.Invoke(this);
}
}
/// <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 <see cref="Shape2D"/>.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to copy.</param>
/// <returns>A copy of the input <see cref="Shape2D"/>.</returns>
public static Shape2D CreateCopy(Shape2D 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 Shape2D 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 Shape2D 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 <see cref="Shape2D"/>.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to enclose.</param>
/// <returns>The super triangle that encloses the given <see cref="Shape2D"/>.</returns>
public static Triangle GetSuperTriangle(Shape2D 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>
/// Triangulates the given convex <see cref="Shape2D"/>.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to triangulate.</param>
/// <param name="triangles">The list to populate with triangles.</param>
public static void TriangulateConvex(Shape2D shape, IList<Triangle> triangles)
{
triangles.Clear();
for (int i = 2; i < shape.Vertices.Count; i++)
triangles.Add(new Triangle(shape[0], shape[i - 1], shape[i]));
}
/// <summary>
/// Triangulates the given convex <see cref="Shape2D"/>.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to triangulate.</param>
/// <returns>A list of <see cref="Triangle"/>s that makes up the given convex <see cref="Shape2D"/>.</returns>
public static List<Triangle> TriangulateConvex(Shape2D shape)
{
List<Triangle> triangles = new(shape.Vertices.Count - 2);
TriangulateConvex(shape, triangles);
return triangles;
}
/// <summary>
/// Gets the <see cref="Line2D"/>s that form the edges of the <see cref="Shape2D"/>.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to get <see cref="Line2D"/>s from.</param>
/// <param name="lines">The list to populate with <see cref="Line2D"/>.</sparam>
public static void GetLines(Shape2D shape, IList<Line2D> 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 <see cref="Line2D"/>s that form the edges of the <see cref="Shape2D"/>.
/// </summary>
/// <param name="shape">The shape to get <see cref="Line2D"/>s from.</param>
/// <returns>A list of <see cref="Line2D"/>s that form the edges of the <see cref="Shape2D"/>.</returns>
public static List<Line2D> GetLines(Shape2D shape)
{
List<Line2D> lines = new(shape.Vertices.Count - 1);
GetLines(shape, lines);
return lines;
}
/// <summary>
/// Projects the <see cref="Shape2D"/> onto a 1D plane.
/// </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(Shape2D 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 <see cref="Shape2D"/> onto a vector.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to project.</param>
/// <param name="projectionVector">The vector to project onto.</param>
/// <returns>The projection of the <see cref="Shape2D"/> onto the vector.</returns>
public static Projection1D Project(Shape2D 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 <see cref="Shape2D"/> using the specified <see cref="ITransform2D"/>.
/// </summary>
/// <param name="shape">The <see cref="Shape2D"/> to transform.</param>
/// <param name="transform">The <see cref="ITransform2D"/> to apply.</param>
/// <returns>The transformed <see cref="Shape2D"/>.</returns>
public static Shape2D Transform(Shape2D shape, ITransform2D transform)
{
List<Vector2D> vertices = new(shape.Vertices.Count);
int count = shape.Vertices.Count;
for (int i = 0; i < count; i++)
vertices.Add(transform.Transform(shape[i]));
return new Shape2D(vertices);
}
/// <summary>
/// Transforms the <see cref="Shape2D"/> using the specified <see cref="ITransform2D"/>.
/// </summary>
/// <param name="from">The <see cref="Shape2D"/> to transform.</param>
/// <param name="transform">The <see cref="ITransform2D"/> to apply.</param>
/// <param name="to">The transformed <see cref="Shape2D"/>.</param>
public static void Transform(Shape2D from, ITransform2D transform, Shape2D to)
{
to._vertices.Clear();
int count = from._vertices.Count;
for (int i = 0; i < count; i++)
to._vertices.Add(transform.Transform(from[i]));
to.OnShapeUpdated?.Invoke(to);
}
/// <summary>
/// Determines whether two <see cref="Shape2D"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="Shape2D"/> to compare.</param>
/// <param name="right">The second <see cref="Shape2D"/> to compare.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><c>true</c> if the <see cref="Shape2D"/>s are approximately equal; otherwise, <c>false</c>.</returns>
public static bool ApproximatelyEquals(Shape2D left, Shape2D right, float epsilon = float.Epsilon)
{
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], epsilon))
return false;
return true;
}
/// <inheritdoc/>
public IEnumerator<Vector2D> GetEnumerator() => Vertices.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator() => Vertices.GetEnumerator();
public readonly record struct ShapeUpdatedArguments(Shape2D shape2D);
}
/// <summary>
/// Provides extension methods for the <see cref="Shape2D"/> struct.
/// </summary>
public static class Shape2DExtensions
{
/// <inheritdoc cref="Shape2D.CreateCopy(Shape2D)" />
public static Shape2D CreateCopy(this Shape2D shape) => Shape2D.CreateCopy(shape);
/// <inheritdoc cref="Shape2D.GetSuperTriangle(Shape2D)" />
public static Triangle ToSuperTriangle(this Shape2D shape) => Shape2D.GetSuperTriangle(shape);
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D, IList{Triangle})" />
public static void ToTrianglesConvex(this Shape2D shape, IList<Triangle> lines) => Shape2D.TriangulateConvex(shape, lines);
/// <inheritdoc cref="Shape2D.TriangulateConvex(Shape2D)" />
public static List<Triangle> ToTrianglesConvex(this Shape2D shape) => Shape2D.TriangulateConvex(shape);
/// <inheritdoc cref="Shape2D.GetLines(Shape2D, IList{Line2D})" />
public static void ToLines(this Shape2D shape, IList<Line2D> lines) => Shape2D.GetLines(shape, lines);
/// <inheritdoc cref="Shape2D.GetLines(Shape2D)" />
public static List<Line2D> ToLines(this Shape2D shape) => Shape2D.GetLines(shape);
/// <inheritdoc cref="Shape2D.Project(Shape2D, Vector2D, IList{float})" />
public static void ToProjection(this Shape2D shape, Vector2D projectionVector, IList<float> list) => Shape2D.Project(shape, projectionVector, list);
/// <inheritdoc cref="Shape2D.Project(Shape2D, Vector2D)" />
public static Projection1D ToProjection(this Shape2D shape, Vector2D projectionVector) => Shape2D.Project(shape, projectionVector);
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D)" />
public static Shape2D Transform(this ITransform2D transform, Shape2D shape) => Shape2D.Transform(shape, transform);
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D, Shape2D)" />
public static void Transform(this ITransform2D transform, Shape2D from, Shape2D to) => Shape2D.Transform(from, transform, to);
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D)" />
public static Shape2D Transform(this Shape2D shape, ITransform2D transform) => Shape2D.Transform(shape, transform);
/// <inheritdoc cref="Shape2D.Transform(Shape2D, ITransform2D,Shape2D)" />
public static void Transform(this Shape2D from, ITransform2D transform, Shape2D to) => Shape2D.Transform(from, transform, to);
/// <inheritdoc cref="Shape2D.ApproximatelyEquals(Shape2D, Shape2D, float)" />
public static bool ApproximatelyEquals(this Shape2D left, Shape2D right, float epsilon = float.Epsilon) => Shape2D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,53 @@
namespace Syntriax.Engine.Core;
[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 * Math.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 (Math.Abs(slopeAB - slopeBC) > float.Epsilon)
{
float x = (slopeAB * slopeBC * (triangle.A.Y - triangle.C.Y) + slopeBC * (triangle.A.X + triangle.B.X) - slopeAB * (triangle.B.X + triangle.C.X)) / (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));
}
/// <summary>
/// Determines whether two <see cref="Triangle"/>s are approximately equal.
/// </summary>
/// <param name="left">The first <see cref="Triangle"/> to compare.</param>
/// <param name="right">The second <see cref="Triangle"/> to compare.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><c>true</c> if the <see cref="Triangle"/>s are approximately equal; otherwise, <c>false</c>.</returns>
public static bool ApproximatelyEquals(Triangle left, Triangle right, float epsilon = float.Epsilon)
=> left.A.ApproximatelyEquals(right.A, epsilon) && left.B.ApproximatelyEquals(right.B, epsilon) && left.C.ApproximatelyEquals(right.C, epsilon);
}
public static class TriangleExtensions
{
/// <inheritdoc cref="Triangle.ApproximatelyEquals(Triangle, Triangle, float)" />
public static bool ApproximatelyEquals(this Triangle left, Triangle right, float epsilon = float.Epsilon) => Triangle.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,399 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a two-dimensional vector.
/// </summary>
/// <param name="x">X position of the <see cref="Vector2D"/>.</param>
/// <param name="y">Y position of the <see cref="Vector2D"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Vector2D"/> struct with the specified positions.
/// </remarks>
[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);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector2D"/>.
/// </summary>
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);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing upwards.
/// </summary>
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);
/// <summary>
/// Represents the unit <see cref="Vector2D"/> pointing leftwards.
/// </summary>
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);
/// <summary>
/// Represents the zero <see cref="Vector2D"/>.
/// </summary>
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 static Vector2D operator -(Vector2D vector) => new(0f - vector.X, 0f - vector.Y);
public static Vector2D operator +(Vector2D left, Vector2D right) => new(left.X + right.X, left.Y + right.Y);
public static Vector2D operator -(Vector2D left, Vector2D right) => new(left.X - right.X, left.Y - right.Y);
public static Vector2D operator *(Vector2D vector, float value) => new(vector.X * value, vector.Y * value);
public static Vector2D operator *(float value, Vector2D vector) => new(vector.X * value, vector.Y * value);
public static Vector2D operator /(Vector2D vector, float value) => new(vector.X / value, vector.Y / value);
public static bool operator ==(Vector2D left, Vector2D right) => left.X == right.X && left.Y == right.Y;
public static bool operator !=(Vector2D left, Vector2D right) => left.X != right.X || left.Y != right.Y;
public static implicit operator System.Numerics.Vector2(Vector2D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(Vector3D vector) => new(vector.X, vector.Y);
public static implicit operator Vector2D(System.Numerics.Vector3 vector) => new(vector.X, vector.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) => Math.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;
/// <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));
/// <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);
/// <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;
/// <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;
/// <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);
/// <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(Math.Cos(angleInRadian) * vector.X - Math.Sin(angleInRadian) * vector.Y, Math.Sin(angleInRadian) * vector.X + Math.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);
/// <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);
/// <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));
/// <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;
/// <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;
/// <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) => Math.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;
/// <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>
/// Transforms the <see cref="Vector2D"/> using the specified <see cref="ITransform2D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector2D"/> to transform.</param>
/// <param name="transform">The <see cref="ITransform2D"/> to apply.</param>
/// <returns>The transformed <see cref="Vector2D"/>.</returns>
public static Vector2D Transform(Vector2D vector, ITransform2D transform)
=> vector.Scale(transform.Scale)
.Rotate(transform.Rotation * Math.DegreeToRadian)
.Add(transform.Position);
/// <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})";
/// <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() => System.HashCode.Combine(X, Y);
}
/// <summary>
/// Provides extension methods for <see cref="Vector2D"/> type.
/// </summary>
public static class Vector2DExtensions
{
/// <inheritdoc cref="Vector2D.Length(Vector2D)" />
public static float Length(this Vector2D vector) => Vector2D.Length(vector);
/// <inheritdoc cref="Vector2D.LengthSquared(this vector) => Vector2D/>
public static float LengthSquared(this Vector2D vector) => Vector2D.LengthSquared(vector);
/// <inheritdoc cref="Vector2D.Distance(Vector2D, Vector2D)" />
public static float Distance(this Vector2D from, Vector2D to) => Vector2D.Distance(from, to);
/// <inheritdoc cref="Vector2D.Invert(this vector) => Vector2D/>
public static Vector2D Invert(this Vector2D vector) => Vector2D.Invert(vector);
/// <inheritdoc cref="Vector2D.Add(Vector2D, Vector2D)" />
public static Vector2D Add(this Vector2D vector, Vector2D vectorToAdd) => Vector2D.Add(vector, vectorToAdd);
/// <inheritdoc cref="Vector2D.Subtract(Vector2D, Vector2D)" />
public static Vector2D Subtract(this Vector2D vector, Vector2D vectorToSubtract) => Vector2D.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector2D.Multiply(Vector2D, float)" />
public static Vector2D Multiply(this Vector2D vector, float value) => Vector2D.Multiply(vector, value);
/// <inheritdoc cref="Vector2D.Divide(Vector2D, float)" />
public static Vector2D Divide(this Vector2D vector, float value) => Vector2D.Divide(vector, value);
/// <inheritdoc cref="Vector2D.Abs(Vector2D)" />
public static Vector2D Abs(this Vector2D vector) => Vector2D.Abs(vector);
/// <inheritdoc cref="Vector2D.Reflect(Vector2D, Vector2D)" />
public static Vector2D Reflect(this Vector2D vector, Vector2D normal) => Vector2D.Reflect(vector, normal);
/// <inheritdoc cref="Vector2D.Normalize(Vector2D)" />
public static Vector2D Normalize(this Vector2D vector) => Vector2D.Normalize(vector);
/// <inheritdoc cref="Vector2D.FromTo(Vector2D, Vector2D)" />
public static Vector2D FromTo(this Vector2D from, Vector2D to) => Vector2D.FromTo(from, to);
/// <inheritdoc cref="Vector2D.Scale(Vector2D, Vector2D)" />
public static Vector2D Scale(this Vector2D vector, Vector2D scale) => Vector2D.Scale(vector, scale);
/// <inheritdoc cref="Vector2D.Perpendicular(Vector2D)" />
public static Vector2D Perpendicular(this Vector2D vector) => Vector2D.Perpendicular(vector);
/// <inheritdoc cref="Vector2D.Rotate(Vector2D, float)" />
public static Vector2D Rotate(this Vector2D vector, float angleInRadian) => Vector2D.Rotate(vector, angleInRadian);
/// <inheritdoc cref="Vector2D.Min(Vector2D, Vector2D)" />
public static Vector2D Min(this Vector2D left, Vector2D right) => Vector2D.Min(left, right);
/// <inheritdoc cref="Vector2D.Max(Vector2D, Vector2D)" />
public static Vector2D Max(this Vector2D left, Vector2D right) => Vector2D.Max(left, right);
/// <inheritdoc cref="Vector2D.Clamp(Vector2D, Vector2D,Vector2D)" />
public static Vector2D Clamp(this Vector2D vector, Vector2D min, Vector2D max) => Vector2D.Clamp(vector, min, max);
/// <inheritdoc cref="Vector2D.Lerp(Vector2D, Vector2D," />
public static Vector2D Lerp(this Vector2D from, Vector2D to, float t) => Vector2D.Lerp(from, to, t);
/// <inheritdoc cref="Vector2D.Cross(Vector2D, Vector2D)" />
public static float Cross(this Vector2D left, Vector2D right) => Vector2D.Cross(left, right);
/// <inheritdoc cref="Vector2D.Angle(Vector2D, Vector2D)" />
public static float AngleBetween(this Vector2D left, Vector2D right) => Vector2D.Angle(left, right);
/// <inheritdoc cref="Vector2D.Dot(Vector2D, Vector2D)" />
public static float Dot(this Vector2D left, Vector2D right) => Vector2D.Dot(left, right);
/// <inheritdoc cref="Vector2D.Transform(Vector2D, ITransform2D)" />
public static Vector2D Transform(this Vector2D vector, ITransform2D transform) => Vector2D.Transform(vector, transform);
/// <inheritdoc cref="Vector2D.Transform(Vector2D, ITransform2D)" />
public static Vector2D Transform(this ITransform2D transform, Vector2D vector) => Vector2D.Transform(vector, transform);
/// <inheritdoc cref="Vector2D.ApproximatelyEquals(Vector2D, Vector2D, float) " />
public static bool ApproximatelyEquals(this Vector2D left, Vector2D right, float epsilon = float.Epsilon) => Vector2D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,364 @@
namespace Syntriax.Engine.Core;
/// <summary>
/// Represents a three-dimensional vector.
/// </summary>
/// <param name="x">X position of the <see cref="Vector3D"/>.</param>
/// <param name="y">Y position of the <see cref="Vector3D"/>.</param>
/// <param name="z">Z position of the <see cref="Vector3D"/>.</param>
/// <remarks>
/// Initializes a new instance of the <see cref="Vector3D"/> struct with the specified positions.
/// </remarks>
[System.Diagnostics.DebuggerDisplay("{ToString(),nq}, Length: {Magnitude}, LengthSquared: {MagnitudeSquared}, Normalized: {Normalized.ToString(),nq}")]
public readonly struct Vector3D(float x, float y, float z)
{
/// <summary>
/// The X coordinate of the <see cref="Vector3D"/>.
/// </summary>
public readonly float X = x;
/// <summary>
/// The Y coordinate of the <see cref="Vector3D"/>.
/// </summary>
public readonly float Y = y;
/// <summary>
/// The Z coordinate of the <see cref="Vector3D"/>.
/// </summary>
public readonly float Z = z;
/// <summary>
/// The magnitude (length) of the <see cref="Vector3D"/>.
/// </summary>
public float Magnitude => Length(this);
/// <summary>
/// The squared magnitude (length) of the <see cref="Vector3D"/>.
/// </summary>
public float MagnitudeSquared => LengthSquared(this);
/// <summary>
/// The normalized form of the <see cref="Vector3D"/> (a <see cref="Vector3D"/> with the same direction and a magnitude of 1).
/// </summary>
public Vector3D Normalized => Normalize(this);
/// <summary>
/// Represents the unit <see cref="Vector3D"/> pointing upwards.
/// </summary>
public readonly static Vector3D Up = new(0f, 1f, 0f);
/// <summary>
/// Represents the unit <see cref="Vector3D"/> pointing downwards.
/// </summary>
public readonly static Vector3D Down = new(0f, -1f, 0f);
/// <summary>
/// Represents the unit <see cref="Vector3D"/> pointing leftwards.
/// </summary>
public readonly static Vector3D Left = new(-1f, 0f, 0f);
/// <summary>
/// Represents the unit <see cref="Vector3D"/> pointing rightwards.
/// </summary>
public readonly static Vector3D Right = new(1f, 0f, 0f);
/// <summary>
/// Represents the unit <see cref="Vector3D"/> pointing forwards.
/// </summary>
public readonly static Vector3D Forward = new(0f, 0f, 1f);
/// <summary>
/// Represents the unit <see cref="Vector3D"/> pointing backwards.
public readonly static Vector3D Backward = new(0f, 0f, -1f);
/// <summary>
/// Represents the zero <see cref="Vector3D"/>.
/// </summary>
public readonly static Vector3D Zero = new(0f, 0f, 0f);
/// <summary>
/// Represents the <see cref="Vector3D"/> with both components equal to 1.
/// </summary>
public readonly static Vector3D One = new(1f, 1f, 1f);
public static Vector3D operator -(Vector3D vector) => new(0f - vector.X, 0f - vector.Y, 0f - vector.Z);
public static Vector3D operator +(Vector3D left, Vector3D right) => new(left.X + right.X, left.Y + right.Y, left.Z + right.Z);
public static Vector3D operator -(Vector3D left, Vector3D right) => new(left.X - right.X, left.Y - right.Y, left.Z - right.Z);
public static Vector3D operator *(Vector3D vector, float value) => new(vector.X * value, vector.Y * value, vector.Z * value);
public static Vector3D operator *(float value, Vector3D vector) => new(vector.X * value, vector.Y * value, vector.Z * value);
public static Vector3D operator /(Vector3D vector, float value) => new(vector.X / value, vector.Y / value, vector.Z / value);
public static bool operator ==(Vector3D left, Vector3D right) => left.X == right.X && left.Y == right.Y && left.Z == right.Z;
public static bool operator !=(Vector3D left, Vector3D right) => left.X != right.X || left.Y != right.Y || left.Z != right.Z;
public static implicit operator System.Numerics.Vector3(Vector3D vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(System.Numerics.Vector3 vector) => new(vector.X, vector.Y, vector.Z);
public static implicit operator Vector3D(Vector2D vector) => new(vector.X, vector.Y, 0f);
public static implicit operator Vector3D(System.Numerics.Vector2 vector) => new(vector.X, vector.Y, 0f);
/// <summary>
/// Calculates the length of the <see cref="Vector3D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/>.</param>
/// <returns>The length of the <see cref="Vector3D"/>.</returns>
public static float Length(Vector3D vector) => Math.Sqrt(LengthSquared(vector));
/// <summary>
/// Calculates the squared length of the <see cref="Vector3D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/>.</param>
/// <returns>The squared length of the <see cref="Vector3D"/>.</returns>
public static float LengthSquared(Vector3D vector) => vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
/// <summary>
/// Calculates the distance between two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="from">The start <see cref="Vector3D"/>.</param>
/// <param name="to">The end <see cref="Vector3D"/>.</param>
/// <returns>The distance between the two <see cref="Vector3D"/>s.</returns>
public static float Distance(Vector3D from, Vector3D to) => Length(FromTo(from, to));
/// <summary>
/// Inverts the direction of the <see cref="Vector3D"/>.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/>.</param>
/// <returns>The inverted <see cref="Vector3D"/>.</returns>
public static Vector3D Invert(Vector3D vector) => -vector;
/// <summary>
/// Adds two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <returns>The sum of the two <see cref="Vector3D"/>s.</returns>
public static Vector3D Add(Vector3D left, Vector3D right) => left + right;
/// <summary>
/// Subtracts one <see cref="Vector3D"/> from another.
/// </summary>
/// <param name="left">The <see cref="Vector3D"/> to subtract from.</param>
/// <param name="right">The <see cref="Vector3D"/> to subtract.</param>
/// <returns>The result of subtracting the second <see cref="Vector3D"/> from the first.</returns>
public static Vector3D Subtract(Vector3D left, Vector3D right) => left - right;
/// <summary>
/// Multiplies a <see cref="Vector3D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of multiplying the <see cref="Vector3D"/> by the scalar value.</returns>
public static Vector3D Multiply(Vector3D vector, float value) => vector * value;
/// <summary>
/// Divides a <see cref="Vector3D"/> by a scalar value.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/>.</param>
/// <param name="value">The scalar value.</param>
/// <returns>The result of dividing the <see cref="Vector3D"/> by the scalar value.</returns>
public static Vector3D Divide(Vector3D vector, float value) => vector / value;
/// <summary>
/// Calculates the absolute value of each component of the vector.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/>.</param>
/// <returns>The <see cref="Vector3D"/> with each component's absolute value.</returns>
public static Vector3D Abs(Vector3D vector) => new(Math.Abs(vector.X), Math.Abs(vector.Y), Math.Abs(vector.Z));
/// <summary>
/// Normalizes the <see cref="Vector3D"/> (creates a unit <see cref="Vector3D"/> with the same direction).
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to normalize.</param>
/// <returns>The normalized <see cref="Vector3D"/>.</returns>
public static Vector3D Normalize(Vector3D vector) => vector / Length(vector);
/// <summary>
/// Reflects a <see cref="Vector3D"/> off a surface with the specified normal.
/// </summary>
/// <param name="vector">The incident <see cref="Vector3D"/>.</param>
/// <param name="normal">The normal <see cref="Vector3D"/> of the surface.</param>
/// <returns>The reflected <see cref="Vector3D"/>.</returns>
public static Vector3D Reflect(Vector3D vector, Vector3D normal) => vector - 2f * Dot(vector, normal) * normal;
/// <summary>
/// Calculates the <see cref="Vector3D"/> from one point to another.
/// </summary>
/// <param name="from">The starting point.</param>
/// <param name="to">The ending point.</param>
/// <returns>The <see cref="Vector3D"/> from the starting point to the ending point.</returns>
public static Vector3D FromTo(Vector3D from, Vector3D to) => to - from;
/// <summary>
/// Scales a <see cref="Vector3D"/> by another <see cref="Vector3D"/> component-wise.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to scale.</param>
/// <param name="scale">The <see cref="Vector3D"/> containing the scaling factors for each component.</param>
/// <returns>The scaled <see cref="Vector3D"/>.</returns>
public static Vector3D Scale(Vector3D vector, Vector3D scale) => new(vector.X * scale.X, vector.Y * scale.Y, vector.Z * scale.Z);
/// <summary>
/// Rotates a <see cref="Vector3D"/> around a normal by the specified angle (in radians).
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to rotate.</param>
/// <param name="normal">The <see cref="Vector3D"/> to rotate around.</param>
/// <param name="angleInRadian">The angle to rotate by, in radians.</param>
/// <returns>The rotated <see cref="Vector3D"/>.</returns>
public static Vector3D Rotate(Vector3D vector, Vector3D normal, float angleInRadian) => vector * Math.Cos(angleInRadian) + Cross(normal, vector) * Math.Sin(angleInRadian) + normal * Dot(normal, vector) * (1f - Math.Cos(angleInRadian));
/// <summary>
/// Returns the component-wise minimum of two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <returns>The <see cref="Vector3D"/> containing the minimum components from both input <see cref="Vector3D"/>s.</returns>
public static Vector3D Min(Vector3D left, Vector3D right) => new((left.X < right.X) ? left.X : right.X, (left.Y < right.Y) ? left.Y : right.Y, (left.Z < right.Z) ? left.Z : right.Z);
/// <summary>
/// Returns the component-wise maximum of two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <returns>The <see cref="Vector3D"/> containing the maximum components from both input <see cref="Vector3D"/>s.</returns>
public static Vector3D Max(Vector3D left, Vector3D right) => new((left.X > right.X) ? left.X : right.X, (left.Y > right.Y) ? left.Y : right.Y, (left.Z > right.Z) ? left.Z : right.Z);
/// <summary>
/// Clamps each component of a <see cref="Vector3D"/> between the corresponding component of two other <see cref="Vector3D"/>s.
/// </summary>
/// <param name="vector">The <see cref="Vector3D"/> to clamp.</param>
/// <param name="min">The <see cref="Vector3D"/> representing the minimum values for each component.</param>
/// <param name="max">The <see cref="Vector3D"/> representing the maximum values for each component.</param>
/// <returns>A <see cref="Vector3D"/> with each component clamped between the corresponding components of the min and max <see cref="Vector3D"/>s.</returns>
public static Vector3D Clamp(Vector3D vector, Vector3D min, Vector3D max) => new(Math.Clamp(vector.X, min.X, max.X), Math.Clamp(vector.Y, min.Y, max.Y), Math.Clamp(vector.Z, min.Z, max.Z));
/// <summary>
/// Performs linear interpolation between two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="from">The starting <see cref="Vector3D"/> (t = 0).</param>
/// <param name="to">The ending <see cref="Vector3D"/> (t = 1).</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The interpolated <see cref="Vector3D"/>.</returns>
public static Vector3D Lerp(Vector3D from, Vector3D to, float t) => from + FromTo(from, to) * t;
/// <summary>
/// Calculates the cross product of two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <returns>The cross product of the two <see cref="Vector3D"/>s.</returns>
public static Vector3D Cross(Vector3D left, Vector3D right) => new(left.Y * right.Z - left.Z * right.Y, left.Z * right.X - left.X * right.Z, left.X * right.Y - left.Y * right.X);
/// <summary>
/// Calculates the angle between two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <returns>The angle between the two <see cref="Vector3D"/>s in radians.</returns>
public static float Angle(Vector3D left, Vector3D right) => Math.Acos(Dot(left, right) / (Length(left) * Length(right)));
/// <summary>
/// Calculates the dot product of two <see cref="Vector3D"/>s.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <returns>The dot product of the two <see cref="Vector3D"/>s.</returns>
public static float Dot(Vector3D left, Vector3D right) => left.X * right.X + left.Y * right.Y + left.Z * right.Z;
/// <summary>
/// Checks if two <see cref="Vector3D"/>s are approximately equal within a specified epsilon range.
/// </summary>
/// <param name="left">The first <see cref="Vector3D"/>.</param>
/// <param name="right">The second <see cref="Vector3D"/>.</param>
/// <param name="epsilon">The epsilon range.</param>
/// <returns><see cref="true"/> if the <see cref="Vector3D"/>s are approximately equal; otherwise, <see cref="false"/>.</returns>
public static bool ApproximatelyEquals(Vector3D left, Vector3D right, float epsilon = float.Epsilon)
=> left.X.ApproximatelyEquals(right.X, epsilon) && left.Y.ApproximatelyEquals(right.Y, epsilon) && left.Z.ApproximatelyEquals(right.Z, epsilon);
/// <summary>
/// Converts the <see cref="Vector3D"/> to its string representation.
/// </summary>
/// <returns>A string representation of the <see cref="Vector3D"/>.</returns>
public override string ToString() => $"{nameof(Vector3D)}({X}, {Y}, {Z})";
/// <summary>
/// Determines whether the specified object is equal to the current <see cref="Vector3D"/>.
/// </summary>
/// <param name="obj">The object to compare with the current <see cref="Vector3D"/>.</param>
/// <returns><see cref="true"/> if the specified object is equal to the current <see cref="Vector3D"/>; otherwise, <see cref="false"/>.</returns>
public override bool Equals(object? obj) => obj is Vector3D objVec && X.Equals(objVec.X) && Y.Equals(objVec.Y) && Z.Equals(objVec.Z);
/// <summary>
/// Generates a hash code for the <see cref="Vector3D"/>.
/// </summary>
/// <returns>A hash code for the <see cref="Vector3D"/>.</returns>
public override int GetHashCode() => System.HashCode.Combine(X, Y, Z);
}
/// <summary>
/// Provides extension methods for <see cref="Vector3D"/> type.
/// </summary>
public static class Vector3DExtensions
{
/// <inheritdoc cref="Vector3D.Length(Vector3D)" />
public static float Length(this Vector3D vector) => Vector3D.Length(vector);
/// <inheritdoc cref="Vector3D.LengthSquared(Vector3D)" />
public static float LengthSquared(this Vector3D vector) => Vector3D.LengthSquared(vector);
/// <inheritdoc cref="Vector3D.Distance(Vector3D, Vector3D)" />
public static float Distance(this Vector3D from, Vector3D to) => Vector3D.Distance(from, to);
/// <inheritdoc cref="Vector3D.Invert(Vector3D)" />
public static Vector3D Invert(this Vector3D vector) => Vector3D.Invert(vector);
/// <inheritdoc cref="Vector3D.Add(Vector3D, Vector3D)" />
public static Vector3D Add(this Vector3D vector, Vector3D vectorToAdd) => Vector3D.Add(vector, vectorToAdd);
/// <inheritdoc cref="Vector3D.Subtract(Vector3D, Vector3D)" />
public static Vector3D Subtract(this Vector3D vector, Vector3D vectorToSubtract) => Vector3D.Subtract(vector, vectorToSubtract);
/// <inheritdoc cref="Vector3D.Multiply(Vector3D, float)" />
public static Vector3D Multiply(this Vector3D vector, float value) => Vector3D.Multiply(vector, value);
/// <inheritdoc cref="Vector3D.Divide(Vector3D, float)" />
public static Vector3D Divide(this Vector3D vector, float value) => Vector3D.Divide(vector, value);
/// <inheritdoc cref="Vector3D.Abs(Vector3D)" />
public static Vector3D Abs(this Vector3D vector) => Vector3D.Abs(vector);
/// <inheritdoc cref="Vector3D.Reflect(Vector3D, Vector3D)" />
public static Vector3D Reflect(this Vector3D vector, Vector3D normal) => Vector3D.Reflect(vector, normal);
/// <inheritdoc cref="Vector3D.Normalize(Vector3D)" />
public static Vector3D Normalize(this Vector3D vector) => Vector3D.Normalize(vector);
/// <inheritdoc cref="Vector3D.FromTo(Vector3D, Vector3D)" />
public static Vector3D FromTo(this Vector3D from, Vector3D to) => Vector3D.FromTo(from, to);
/// <inheritdoc cref="Vector3D.Scale(Vector3D, Vector3D)" />
public static Vector3D Scale(this Vector3D vector, Vector3D scale) => Vector3D.Scale(vector, scale);
/// <inheritdoc cref="Vector3D.Rotate(Vector3D, Vector3D, float)" />
public static Vector3D Rotate(this Vector3D vector, Vector3D normal, float angleInRadian) => Vector3D.Rotate(vector, normal, angleInRadian);
/// <inheritdoc cref="Vector3D.Min(Vector3D, Vector3D)" />
public static Vector3D Min(this Vector3D left, Vector3D right) => Vector3D.Min(left, right);
/// <inheritdoc cref="Vector3D.Max(Vector3D, Vector3D)" />
public static Vector3D Max(this Vector3D left, Vector3D right) => Vector3D.Max(left, right);
/// <inheritdoc cref="Vector3D.Clamp(Vector3D, Vector3D, Vector3D)" />
public static Vector3D Clamp(this Vector3D vector, Vector3D min, Vector3D max) => Vector3D.Clamp(vector, min, max);
/// <inheritdoc cref="Vector3D.Lerp(Vector3D, Vector3D, float)" />
public static Vector3D Lerp(this Vector3D from, Vector3D to, float t) => Vector3D.Lerp(from, to, t);
/// <inheritdoc cref="Vector3D.Cross(Vector3D, Vector3D)" />
public static Vector3D Cross(this Vector3D left, Vector3D right) => Vector3D.Cross(left, right);
/// <inheritdoc cref="Vector3D.Angle(Vector3D, Vector3D)" />
public static float AngleBetween(this Vector3D left, Vector3D right) => Vector3D.Angle(left, right);
/// <inheritdoc cref="Vector3D.Dot(Vector3D, Vector3D)" />
public static float Dot(this Vector3D left, Vector3D right) => Vector3D.Dot(left, right);
/// <inheritdoc cref="Vector3D.ApproximatelyEquals(Vector3D, Vector3D, float)" />
public static bool ApproximatelyEquals(this Vector3D left, Vector3D right, float epsilon = float.Epsilon) => Vector3D.ApproximatelyEquals(left, right, epsilon);
}

View File

@@ -0,0 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Class)]
public class IgnoreSerializationAttribute : Attribute;

View File

@@ -0,0 +1,6 @@
using System;
namespace Syntriax.Engine.Core.Serialization;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class SerializeAllAttribute : Attribute;

Some files were not shown because too many files have changed in this diff Show More