From 83757bac0217c6ee1dd1aa2ad9f4128f05cc30a0 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Thu, 12 Dec 2024 07:50:36 +0100 Subject: [PATCH 01/18] contact ended system started. - CollisionEnded renamed to ContactEnded - New Contact class added - --- .../ExampleScenes/GameObjectHandlerExample.cs | 2 +- ShapeEngine/Core/CollisionObject.cs | 20 +-- ShapeEngine/Core/CollisionSystem/Collider.cs | 14 +- .../Core/CollisionSystem/CollisionHandler.cs | 155 +++++++++++++----- .../CollisionSystem/CollisionInformation.cs | 10 +- ShapeEngine/Core/CollisionSystem/Contact.cs | 33 ++++ ...ormation.cs => ContactEndedInformation.cs} | 35 ++-- ShapeEngine/Core/CollisionSystem/Overlap.cs | 6 +- 8 files changed, 187 insertions(+), 88 deletions(-) create mode 100644 ShapeEngine/Core/CollisionSystem/Contact.cs rename ShapeEngine/Core/CollisionSystem/{OverlapInformation.cs => ContactEndedInformation.cs} (64%) diff --git a/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs b/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs index 07faf443..ed10a6cf 100644 --- a/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs +++ b/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs @@ -154,7 +154,7 @@ protected override void Collision(List info) } } - protected override void CollisionEnded(List info) + protected override void ContactEnded(List info) { foreach (var overlapInfo in info) { diff --git a/ShapeEngine/Core/CollisionObject.cs b/ShapeEngine/Core/CollisionObject.cs index 7a95dabf..e96ef846 100644 --- a/ShapeEngine/Core/CollisionObject.cs +++ b/ShapeEngine/Core/CollisionObject.cs @@ -9,11 +9,11 @@ namespace ShapeEngine.Core; public abstract class CollisionObject : PhysicsObject { public event Action>? OnCollision; - public event Action>? OnCollisionEnded; + public event Action>? OnContactEnded; public event Action? OnColliderIntersected; public event Action? OnColliderOverlapped; - public event Action? OnColliderCollisionEnded; + public event Action? OnColliderContactEnded; public CollisionObject() @@ -85,19 +85,19 @@ internal void ResolveCollision(List informations) } - internal void ResolveCollisionEnded(List informations) + internal void ResolveContactEnded(List informations) { - CollisionEnded(informations); - OnCollisionEnded?.Invoke(informations); + ContactEnded(informations); + OnContactEnded?.Invoke(informations); if(AvancedCollisionNotification == false) return; foreach (var info in informations) { foreach (var overlap in info) { - overlap.Self.ResolveCollisionEnded(overlap.Other); - ColliderCollisionEnded(overlap.Other); - OnColliderCollisionEnded?.Invoke(overlap.Other); + overlap.Self.ResolveContactEnded(overlap.Other); + ColliderContactEnded(overlap.Other); + OnColliderContactEnded?.Invoke(overlap.Other); } } } @@ -113,7 +113,7 @@ protected virtual void Collision(List info) { } /// Called when 1 or more collider of this CollisionObject are no longer involved in a collision (intersection or overlap) /// /// - protected virtual void CollisionEnded(List info) { } + protected virtual void ContactEnded(List info) { } /// @@ -130,7 +130,7 @@ protected virtual void ColliderOverlapped(Overlap overlap) { } /// Only callded when AdvancedCollisionNotification is set to true. /// /// The other collider involved - protected virtual void ColliderCollisionEnded(Collider other) { } + protected virtual void ColliderContactEnded(Collider other) { } diff --git a/ShapeEngine/Core/CollisionSystem/Collider.cs b/ShapeEngine/Core/CollisionSystem/Collider.cs index af8807bd..633843f7 100644 --- a/ShapeEngine/Core/CollisionSystem/Collider.cs +++ b/ShapeEngine/Core/CollisionSystem/Collider.cs @@ -22,7 +22,7 @@ public abstract class Collider : Shape /// A collision (Intersection/Overlap) between this collider and another collider has ended. /// AvancedCollisionNotification has to be enabled on the parent for this event to be invoked. /// - public event Action? OnCollisionEnded; + public event Action? OnContactEnded; private CollisionObject? parent = null; @@ -111,10 +111,10 @@ internal void ResolveOverlapped(Overlap overlap) OnOverlapped?.Invoke(overlap); } - internal void ResolveCollisionEnded(Collider other) + internal void ResolveContactEnded(Collider other) { - CollisionEnded(other); - OnCollisionEnded?.Invoke(other); + ContactEnded(other); + OnContactEnded?.Invoke(other); } /// @@ -127,14 +127,14 @@ protected virtual void Intersected(Collision info) { } /// Will be called from the parent. Is only called when an overlap with this collider occurs where the intersection is not valid. /// AvancedCollisionNotification has to be enabled on the parent for this function to be called. /// - /// - protected virtual void Overlapped(Overlap overlap) { } + /// + protected virtual void Overlapped(Overlap contact) { } /// /// Will be called from the parent. Is only called when a collision (intersection / overlap) with this collider ends. /// AvancedCollisionNotification has to be enabled on the parent for this function to be called. /// /// - protected virtual void CollisionEnded(Collider other) { } + protected virtual void ContactEnded(Collider other) { } public override void InitializeShape(Transform2D parentTransform) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs index 247dc50a..be9836c3 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs @@ -10,15 +10,16 @@ namespace ShapeEngine.Core.CollisionSystem; public class CollisionHandler : IBounds { #region Support Classes + private class CollisionStack(int capacity) : Dictionary(capacity) { public bool AddCollisionRegister(CollisionObject owner, CollisionRegister register) { if (register.Count <= 0) return false; - + return TryAdd(owner, register); } - + public void ProcessCollisions() { foreach (var entry in this) @@ -30,19 +31,20 @@ public void ProcessCollisions() resolver.ResolveCollision(informations); } } - + } - + private class CollisionRegister : Dictionary { - public List? GetCollisionInformations() => Values.Count <= 0 ? null : Values.ToList(); + public List? GetCollisionInformations() => Values.Count <= 0 ? null : Values.ToList(); + public bool AddCollision(Collision collision) { var selfParent = collision.Self.Parent; var otherParent = collision.Other.Parent; - - if(selfParent == null || otherParent == null) return false; - + + if (selfParent == null || otherParent == null) return false; + if (TryGetValue(otherParent, out var cols)) { cols.Add(collision); @@ -51,7 +53,7 @@ public bool AddCollision(Collision collision) { var colInfo = new CollisionInformation(selfParent, otherParent); colInfo.Add(collision); - + Add(otherParent, colInfo); } @@ -59,26 +61,30 @@ public bool AddCollision(Collision collision) } } + //TODO: remove process overlap information: + // - this system is only needed for first contact determination between 2 colliders + // - OverlapEnded will be ContactEnded between 2 CollisionObjects + // - Rework to new generic stack/ register variant private class OverlapStack(int capacity) : Dictionary(capacity) { public OverlapRegister? GetRegister(CollisionObject owner) => !TryGetValue(owner, out var register) ? null : register; - public bool AddOverlap(Overlap overlap) + public bool AddOverlap(Contact contact) { - var selfParent = overlap.Self.Parent; - var otherParent = overlap.Other.Parent; - if(selfParent == null || otherParent == null) return false; + var selfParent = contact.Self.Parent; + var otherParent = contact.Other.Parent; + if (selfParent == null || otherParent == null) return false; if (!ContainsKey(selfParent)) { var newRegister = new OverlapRegister(2); - newRegister.AddOverlap(overlap); + newRegister.AddOverlap(contact); Add(selfParent, newRegister); } else { var register = this[selfParent]; - register.AddOverlap(overlap); + register.AddOverlap(contact); } return true; @@ -92,9 +98,9 @@ public void ProcessOverlaps() var resolver = entry.Key; var register = entry.Value; var informations = register.GetOverlapInformations(); - if(informations == null)continue; - resolver.ResolveCollisionEnded(informations); - + if (informations == null) continue; + resolver.ResolveContactEnded(informations); + // foreach (var entry in register) // { // resolver.ResolveCollisionEnded(entry.Value); @@ -103,45 +109,46 @@ public void ProcessOverlaps() } } - private class OverlapRegister(int capacity) : Dictionary(capacity) + private class OverlapRegister(int capacity) : Dictionary(capacity) { - public List? GetOverlapInformations() => Values.Count <= 0 ? null : Values.ToList(); - public bool AddOverlap(Overlap overlap) + public List? GetOverlapInformations() => Values.Count <= 0 ? null : Values.ToList(); + + public bool AddOverlap(Contact contact) { - var selfParent = overlap.Self.Parent; - var otherParent = overlap.Other.Parent; - - if(selfParent == null || otherParent == null) return false; - + var selfParent = contact.Self.Parent; + var otherParent = contact.Other.Parent; + + if (selfParent == null || otherParent == null) return false; + if (TryGetValue(otherParent, out var cols)) { - cols.Add(overlap); + cols.Add(contact); } else { - var overlapInfo = new OverlapInformation(selfParent, otherParent); - overlapInfo.Add(overlap); - + var overlapInfo = new ContactEndedInformation(selfParent, otherParent); + overlapInfo.Add(contact); + Add(otherParent, overlapInfo); } return true; } - public Overlap? PopOverlap(Collider self, Collider other) + public Contact? PopOverlap(Collider self, Collider other) { var otherParent = other.Parent; - if(otherParent == null) return null; + if (otherParent == null) return null; if (TryGetValue(otherParent, out var info)) { - return info.PopOverlap(self, other); + return info.PopContact(self, other); } return null; } } - - + + /*private class OverlapRegister { private HashSet entries; @@ -150,7 +157,7 @@ public OverlapRegister(int capacity) { entries = new(capacity); } - + public OverlapEntry? FindEntry(Collider self, Collider other) { foreach (var entry in entries) @@ -191,7 +198,7 @@ public OverlapEntry(Collider self, Collider other) } } */ - + private class ObjectRegister { public readonly HashSet AllObjects; @@ -204,7 +211,7 @@ public ObjectRegister(int capacity) tempHolding = new(capacity / 4); tempRemoving = new(capacity / 4); } - + public void Add(T obj) => tempHolding.Add(obj); public void AddRange(IEnumerable objs) => tempHolding.AddRange(objs); @@ -223,24 +230,34 @@ public void Process() { AllObjects.Remove(obj); } + tempRemoving.Clear(); - + foreach (var obj in tempHolding) { AllObjects.Add(obj); } + tempHolding.Clear(); - + } - protected virtual void ObjectAdded(T obj) { } - protected virtual void ObjectRemoved(T obj) { } + + protected virtual void ObjectAdded(T obj) + { + } + + protected virtual void ObjectRemoved(T obj) + { + } + public void Clear() { foreach (var obj in AllObjects) { ObjectRemoved(obj); } + AllObjects.Clear(); tempHolding.Clear(); tempRemoving.Clear(); @@ -250,6 +267,7 @@ public void Clear() private class CollisionObjectRegister : ObjectRegister { private readonly CollisionHandler handler; + public CollisionObjectRegister(int capacity, CollisionHandler handler) : base(capacity) { this.handler = handler; @@ -265,6 +283,52 @@ protected override void ObjectRemoved(CollisionObject obj) obj.OnCollisionSystemLeft(handler); } } + + + //TODO: Process function is called on the old stack and every entry that is remaining is to be considered contact ended! + // processing has to be handled outside now because of the generic nature of the stack + private class FirstContactStack(int capacity) : Dictionary>(capacity) + where T : class + where M : class + { + public bool RemoveEntry(T first, M second) + { + return TryGetValue(first, out var register) && register.Remove(second); + } + + public bool AddEntry(T first, M second) + { + if (TryGetValue(first, out var register)) + { + return register.Add(second); + } + + var newRegister = new HashSet(2); + Add(first, newRegister); + return true; + } + + //implement outside + // public abstract void ProcessEntries(); + } + + // private class FirstContactRegister(int capacity) : HashSet(capacity) where T : class + // { + // + // } + + // private class CollisionObjectFirstContactStack(int capacity) : FirstContactStack(capacity) + // { + // public override void ProcessEntries() + // {foreach (var kvp in this) + // { + // var resolver = kvp.Key; + // var register = kvp.Value; + // //!!! implement in collision object! + // //resolver.ContactEnded(register); + // } + // } + // } #endregion #region Members @@ -272,6 +336,9 @@ protected override void ObjectRemoved(CollisionObject obj) private readonly SpatialHash spatialHash; private readonly CollisionStack collisionStack; + + private FirstContactStack collisionObjectFirstContactRegisterActive = new(128); + private FirstContactStack collisionObjectFirstContactRegisterTemp = new(128); //use for detecting when overlap has ended private OverlapStack activeOverlapStack; @@ -398,7 +465,7 @@ private void ProcessCollisions(float dt) else { firstContact = true; - activeOverlapStack.AddOverlap(new Overlap(collider, candidate, true)); + activeOverlapStack.AddOverlap(new Contact(collider, candidate, true)); } if (computeIntersections) @@ -483,7 +550,7 @@ private void ProcessCollisions(float dt) else { firstContact = true; - activeOverlapStack.AddOverlap(new Overlap(collider, candidate, true)); + activeOverlapStack.AddOverlap(new Contact(collider, candidate, true)); } if (computeIntersections) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs b/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs index 61f84612..e67d1a76 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs @@ -15,21 +15,23 @@ public class CollisionInformation : List public readonly CollisionObject Self; public readonly CollisionObject Other; - + public readonly bool FirstContact; #endregion #region Constructors - public CollisionInformation(CollisionObject self, CollisionObject other) + public CollisionInformation(CollisionObject self, CollisionObject other, bool firstContact) { Self = self; Other = other; + FirstContact = firstContact; } - public CollisionInformation(CollisionObject self, CollisionObject other, List collisions) + public CollisionInformation(CollisionObject self, CollisionObject other, bool firstContact, List collisions) { Self = self; Other = other; + FirstContact = firstContact; AddRange(collisions); } @@ -194,7 +196,7 @@ public CollisionInformation Copy() { newCollisions.Add(collision.Copy()); } - return new CollisionInformation(Self, Other, newCollisions); + return new CollisionInformation(Self, Other, FirstContact, newCollisions); } public List? FilterCollisions(Predicate match) diff --git a/ShapeEngine/Core/CollisionSystem/Contact.cs b/ShapeEngine/Core/CollisionSystem/Contact.cs new file mode 100644 index 00000000..639f9ca2 --- /dev/null +++ b/ShapeEngine/Core/CollisionSystem/Contact.cs @@ -0,0 +1,33 @@ +using ShapeEngine.Core.CollisionSystem; + +namespace ShapeEngine.Core.CollisionSystem; + +/// +/// Contains the information of a contact between two colliders. +/// +public class Contact +{ + public readonly Collider Self; + public readonly Collider Other; + // public bool FirstContact { get; internal set; } + public Contact(Collider self, Collider other) + { + Self = self; + Other = other; + // FirstContact = false; + } + public Contact(Collider self, Collider other, bool firstContact) + { + Self = self; + Other = other; + // FirstContact = firstContact; + } + + private Contact(Contact contact) + { + Self = contact.Self; + Other = contact.Other; + // FirstContact = contact.FirstContact; + } + public Contact Copy() => new(this); +} \ No newline at end of file diff --git a/ShapeEngine/Core/CollisionSystem/OverlapInformation.cs b/ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs similarity index 64% rename from ShapeEngine/Core/CollisionSystem/OverlapInformation.cs rename to ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs index 62e1963a..fd926584 100644 --- a/ShapeEngine/Core/CollisionSystem/OverlapInformation.cs +++ b/ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs @@ -7,50 +7,50 @@ namespace ShapeEngine.Core.CollisionSystem; /// Contains the information of an overlap between two collision objects in form of a list of overlaps. /// An overlap contains the information of the two overlapping colliders. /// -public class OverlapInformation : List +public class ContactEndedInformation : List { public readonly CollisionObject Self; public readonly CollisionObject Other; - public OverlapInformation(CollisionObject self, CollisionObject other) + public ContactEndedInformation(CollisionObject self, CollisionObject other) { Self = self; Other = other; } - public OverlapInformation(CollisionObject self, CollisionObject other, List overlaps) + public ContactEndedInformation(CollisionObject self, CollisionObject other, List overlaps) { Self = self; Other = other; AddRange(overlaps); } - public OverlapInformation Copy() + public ContactEndedInformation Copy() { - var newOverlaps = new List(); - foreach (var overlap in this) + var contactsCopy = new List(); + foreach (var contact in this) { - newOverlaps.Add(overlap.Copy()); + contactsCopy.Add(contact.Copy()); } - return new (Self, Other, newOverlaps); + return new (Self, Other, contactsCopy); } - internal Overlap? PopOverlap(Collider self, Collider other) + internal Contact? PopContact(Collider self, Collider other) { - foreach (var overlap in this) + foreach (var contact in this) { - if (overlap.Self == self && overlap.Other == other) + if (contact.Self == self && contact.Other == other) { - Remove(overlap); - return overlap; + Remove(contact); + return contact; } } return null; } - public List? FilterOverlaps(Predicate match) + public List? FilterContacts(Predicate match) { if(Count <= 0) return null; - List? filtered = null; + List? filtered = null; foreach (var c in this) { if (match(c)) @@ -72,7 +72,8 @@ public OverlapInformation Copy() } return others; } - public List? GetAllFirstContactOverlaps() + + /*public List? GetAllFirstContactOverlaps() { return FilterOverlaps((c) => c.FirstContact); } @@ -86,6 +87,6 @@ public OverlapInformation Copy() others.Add(c.Other); } return others; - } + }*/ } \ No newline at end of file diff --git a/ShapeEngine/Core/CollisionSystem/Overlap.cs b/ShapeEngine/Core/CollisionSystem/Overlap.cs index 7a36c9c1..318cb5cc 100644 --- a/ShapeEngine/Core/CollisionSystem/Overlap.cs +++ b/ShapeEngine/Core/CollisionSystem/Overlap.cs @@ -1,10 +1,5 @@ -using ShapeEngine.Core.CollisionSystem; - namespace ShapeEngine.Core.CollisionSystem; -/// -/// Contains the information of an overlap between two colliders. -/// public class Overlap { public readonly Collider Self; @@ -27,6 +22,7 @@ private Overlap(Overlap overlap) { Self = overlap.Self; Other = overlap.Other; + FirstContact = overlap.FirstContact; } public Overlap Copy() => new(this); } \ No newline at end of file From bff3102eb54e73e2bff672bf10c5288db5386411 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:03:24 +0100 Subject: [PATCH 02/18] CollisionHandler first contact system overhaul finished. --- ShapeEngine/Core/CollisionObject.cs | 36 +- .../Core/CollisionSystem/CollisionHandler.cs | 427 +++++++++--------- ShapeEngine/Core/CollisionSystem/Contact.cs | 12 +- 3 files changed, 237 insertions(+), 238 deletions(-) diff --git a/ShapeEngine/Core/CollisionObject.cs b/ShapeEngine/Core/CollisionObject.cs index e96ef846..7c98c953 100644 --- a/ShapeEngine/Core/CollisionObject.cs +++ b/ShapeEngine/Core/CollisionObject.cs @@ -85,22 +85,32 @@ internal void ResolveCollision(List informations) } - internal void ResolveContactEnded(List informations) + + internal void ResolveContactEnded(HashSet endedContacts) + { + + } + + internal void ResolveContactEnded(HashSet endedContacts) { - ContactEnded(informations); - OnContactEnded?.Invoke(informations); - if(AvancedCollisionNotification == false) return; - foreach (var info in informations) - { - foreach (var overlap in info) - { - overlap.Self.ResolveContactEnded(overlap.Other); - ColliderContactEnded(overlap.Other); - OnColliderContactEnded?.Invoke(overlap.Other); - } - } } + // internal void ResolveContactEnded(List informations) + // { + // ContactEnded(informations); + // OnContactEnded?.Invoke(informations); + // + // if(AvancedCollisionNotification == false) return; + // foreach (var info in informations) + // { + // foreach (var overlap in info) + // { + // overlap.Self.ResolveContactEnded(overlap.Other); + // ColliderContactEnded(overlap.Other); + // OnColliderContactEnded?.Invoke(overlap.Other); + // } + // } + // } /// diff --git a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs index be9836c3..2f0a91e1 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs @@ -33,12 +33,11 @@ public void ProcessCollisions() } } - private class CollisionRegister : Dictionary { public List? GetCollisionInformations() => Values.Count <= 0 ? null : Values.ToList(); - public bool AddCollision(Collision collision) + public bool AddCollision(Collision collision, bool firstContact) { var selfParent = collision.Self.Parent; var otherParent = collision.Other.Parent; @@ -51,7 +50,7 @@ public bool AddCollision(Collision collision) } else { - var colInfo = new CollisionInformation(selfParent, otherParent); + var colInfo = new CollisionInformation(selfParent, otherParent, firstContact); colInfo.Add(collision); Add(otherParent, colInfo); @@ -61,144 +60,7 @@ public bool AddCollision(Collision collision) } } - //TODO: remove process overlap information: - // - this system is only needed for first contact determination between 2 colliders - // - OverlapEnded will be ContactEnded between 2 CollisionObjects - // - Rework to new generic stack/ register variant - private class OverlapStack(int capacity) : Dictionary(capacity) - { - public OverlapRegister? GetRegister(CollisionObject owner) => !TryGetValue(owner, out var register) ? null : register; - - public bool AddOverlap(Contact contact) - { - var selfParent = contact.Self.Parent; - var otherParent = contact.Other.Parent; - if (selfParent == null || otherParent == null) return false; - - if (!ContainsKey(selfParent)) - { - var newRegister = new OverlapRegister(2); - newRegister.AddOverlap(contact); - Add(selfParent, newRegister); - } - else - { - var register = this[selfParent]; - register.AddOverlap(contact); - } - - return true; - } - - public void ProcessOverlaps() - { - - foreach (var entry in this) - { - var resolver = entry.Key; - var register = entry.Value; - var informations = register.GetOverlapInformations(); - if (informations == null) continue; - resolver.ResolveContactEnded(informations); - - // foreach (var entry in register) - // { - // resolver.ResolveCollisionEnded(entry.Value); - // } - } - } - } - - private class OverlapRegister(int capacity) : Dictionary(capacity) - { - public List? GetOverlapInformations() => Values.Count <= 0 ? null : Values.ToList(); - - public bool AddOverlap(Contact contact) - { - var selfParent = contact.Self.Parent; - var otherParent = contact.Other.Parent; - - if (selfParent == null || otherParent == null) return false; - - if (TryGetValue(otherParent, out var cols)) - { - cols.Add(contact); - } - else - { - var overlapInfo = new ContactEndedInformation(selfParent, otherParent); - overlapInfo.Add(contact); - - Add(otherParent, overlapInfo); - } - - return true; - } - - public Contact? PopOverlap(Collider self, Collider other) - { - var otherParent = other.Parent; - if (otherParent == null) return null; - if (TryGetValue(otherParent, out var info)) - { - return info.PopContact(self, other); - } - - return null; - } - } - - - /*private class OverlapRegister - { - private HashSet entries; - - public OverlapRegister(int capacity) - { - entries = new(capacity); - } - - public OverlapEntry? FindEntry(Collider self, Collider other) - { - foreach (var entry in entries) - { - if (self == entry.Self && other == entry.Other) - { - return entry; - } - } - - return null; - } - - public bool AddEntry(OverlapEntry entry) => entries.Add(entry); - - public bool RemoveEntry(OverlapEntry entry) => entries.Remove(entry); - - public void ProcessEntries() - { - foreach (var entry in entries) - { - entry.Self.ResolveCollisionEnded(entry.Other); - } - } - - public void Clear() => entries.Clear(); - public void Swap(OverlapRegister other) => (entries, other.entries) = (other.entries, entries); - } - private class OverlapEntry - { - public readonly Collider Self; - public readonly Collider Other; - - public OverlapEntry(Collider self, Collider other) - { - this.Self = self; - this.Other = other; - } - } - */ - + private class ObjectRegister { public readonly HashSet AllObjects; @@ -283,17 +145,14 @@ protected override void ObjectRemoved(CollisionObject obj) obj.OnCollisionSystemLeft(handler); } } - - //TODO: Process function is called on the old stack and every entry that is remaining is to be considered contact ended! - // processing has to be handled outside now because of the generic nature of the stack private class FirstContactStack(int capacity) : Dictionary>(capacity) where T : class where M : class { public bool RemoveEntry(T first, M second) { - return TryGetValue(first, out var register) && register.Remove(second); + return TryGetValue(first, out var register) && register.Count > 0 && register.Remove(second); } public bool AddEntry(T first, M second) @@ -307,28 +166,7 @@ public bool AddEntry(T first, M second) Add(first, newRegister); return true; } - - //implement outside - // public abstract void ProcessEntries(); } - - // private class FirstContactRegister(int capacity) : HashSet(capacity) where T : class - // { - // - // } - - // private class CollisionObjectFirstContactStack(int capacity) : FirstContactStack(capacity) - // { - // public override void ProcessEntries() - // {foreach (var kvp in this) - // { - // var resolver = kvp.Key; - // var register = kvp.Value; - // //!!! implement in collision object! - // //resolver.ContactEnded(register); - // } - // } - // } #endregion #region Members @@ -337,13 +175,12 @@ public bool AddEntry(T first, M second) private readonly SpatialHash spatialHash; private readonly CollisionStack collisionStack; - private FirstContactStack collisionObjectFirstContactRegisterActive = new(128); - private FirstContactStack collisionObjectFirstContactRegisterTemp = new(128); + private FirstContactStack collisionObjectFirstContactRegisterActive; + private FirstContactStack collisionObjectFirstContactRegisterTemp; - //use for detecting when overlap has ended - private OverlapStack activeOverlapStack; - private OverlapStack oldOverlapStack; - + private FirstContactStack colliderFirstContactRegisterActive; + private FirstContactStack colliderFirstContactRegisterTemp; + private readonly HashSet collisionCandidateCheckRegister = new(); private List collisionCandidateBuckets = new(); @@ -361,8 +198,10 @@ public CollisionHandler(float x, float y, float w, float h, int rows, int cols, spatialHash = new(x, y, w, h, rows, cols); collisionBodyRegister = new(startCapacity, this); collisionStack = new(startCapacity / 4); - activeOverlapStack = new(startCapacity / 4); - oldOverlapStack = new(startCapacity / 4); + colliderFirstContactRegisterActive = new(startCapacity / 4); + colliderFirstContactRegisterTemp = new(startCapacity / 4); + collisionObjectFirstContactRegisterActive = new(startCapacity / 4); + collisionObjectFirstContactRegisterTemp = new(startCapacity / 4); } public CollisionHandler(Rect bounds, int rows, int cols, int startCapacity = 1024) @@ -371,8 +210,10 @@ public CollisionHandler(Rect bounds, int rows, int cols, int startCapacity = 102 collisionBodyRegister = new(startCapacity, this); collisionStack = new(startCapacity / 4); - activeOverlapStack = new(startCapacity / 4); - oldOverlapStack = new(startCapacity / 4); + colliderFirstContactRegisterActive = new(startCapacity / 4); + colliderFirstContactRegisterTemp = new(startCapacity / 4); + collisionObjectFirstContactRegisterActive = new(startCapacity / 4); + collisionObjectFirstContactRegisterTemp = new(startCapacity / 4); } #endregion @@ -420,7 +261,7 @@ private void ProcessCollisions(float dt) if (!collisionBody.Enabled || !collisionBody.HasColliders) continue; CollisionRegister? collisionRegister = null; - var oldOverlapRegister = oldOverlapStack.GetRegister(collisionBody); + // var oldOverlapRegister = oldOverlapStack.GetRegister(collisionBody); var passivChecking = collisionBody.Passive; if (collisionBody.ProjectShape) @@ -428,6 +269,7 @@ private void ProcessCollisions(float dt) foreach (var collider in collisionBody.Colliders) { if (!collider.Enabled) continue; + if (collider.Parent == null) continue; var projected = collider.Project(collisionBody.Velocity * dt); if(projected == null) continue; @@ -453,20 +295,11 @@ private void ProcessCollisions(float dt) bool overlap = projected.Overlap(candidate); if (overlap) { - var oldOverlap = oldOverlapRegister?.PopOverlap(collider, candidate); - bool firstContact; - if (oldOverlap != null) - { - firstContact = false; - oldOverlap.FirstContact = false; - activeOverlapStack.AddOverlap(oldOverlap); - - } - else - { - firstContact = true; - activeOverlapStack.AddOverlap(new Contact(collider, candidate, true)); - } + bool firstContactCollisionObject = !collisionObjectFirstContactRegisterActive.RemoveEntry(collider.Parent, candidate.Parent); + collisionObjectFirstContactRegisterTemp.AddEntry(collider.Parent, candidate.Parent); + + bool firstContactCollider = !colliderFirstContactRegisterActive.RemoveEntry(collider, candidate); + colliderFirstContactRegisterTemp.AddEntry(candidate, collider); if (computeIntersections) { @@ -493,15 +326,15 @@ private void ProcessCollisions(float dt) } } - Collision c = new(collider, candidate, firstContact, collisionPoints); + Collision c = new(collider, candidate, firstContactCollider, collisionPoints); collisionRegister??= new(); - collisionRegister.AddCollision(c); + collisionRegister.AddCollision(c, firstContactCollisionObject); } else { - Collision c = new(collider, candidate, firstContact); + Collision c = new(collider, candidate, firstContactCollider); collisionRegister??= new(); - collisionRegister.AddCollision(c); + collisionRegister.AddCollision(c, firstContactCollisionObject); } } } @@ -515,7 +348,7 @@ private void ProcessCollisions(float dt) foreach (var collider in collisionBody.Colliders) { if (!collider.Enabled) continue; - + if (collider.Parent == null) continue; collisionCandidateBuckets.Clear(); collisionCandidateCheckRegister.Clear(); spatialHash.GetRegisteredCollisionCandidateBuckets(collider, ref collisionCandidateBuckets); @@ -538,20 +371,12 @@ private void ProcessCollisions(float dt) bool overlap = collider.Overlap(candidate); // ShapeGeometry.Overlap(collider, candidate); if (overlap) { - var oldOverlap = oldOverlapRegister?.PopOverlap(collider, candidate); - bool firstContact; - if (oldOverlap != null) - { - firstContact = false; - oldOverlap.FirstContact = false; - activeOverlapStack.AddOverlap(oldOverlap); - - } - else - { - firstContact = true; - activeOverlapStack.AddOverlap(new Contact(collider, candidate, true)); - } + + bool firstContactCollisionObject = !collisionObjectFirstContactRegisterActive.RemoveEntry(collider.Parent, candidate.Parent); + collisionObjectFirstContactRegisterTemp.AddEntry(collider.Parent, candidate.Parent); + + bool firstContactCollider = !colliderFirstContactRegisterActive.RemoveEntry(collider, candidate); + colliderFirstContactRegisterTemp.AddEntry(candidate, collider); if (computeIntersections) { @@ -578,15 +403,15 @@ private void ProcessCollisions(float dt) } } - Collision c = new(collider, candidate, firstContact, collisionPoints); + Collision c = new(collider, candidate, firstContactCollider, collisionPoints); collisionRegister??= new(); - collisionRegister.AddCollision(c); + collisionRegister.AddCollision(c, firstContactCollisionObject); } else { - Collision c = new(collider, candidate, firstContact); + Collision c = new(collider, candidate, firstContactCollider); collisionRegister??= new(); - collisionRegister.AddCollision(c); + collisionRegister.AddCollision(c, firstContactCollisionObject); } } } @@ -609,10 +434,32 @@ private void Resolve() collisionStack.ProcessCollisions(); collisionStack.Clear(); - oldOverlapStack.ProcessOverlaps(); - oldOverlapStack.Clear(); - (oldOverlapStack, activeOverlapStack) = (activeOverlapStack, oldOverlapStack); - // activeRegister.Clear(); + foreach (var kvp in collisionObjectFirstContactRegisterActive) + { + var resolver = kvp.Key; + var others = kvp.Value; + if(others.Count <= 0) continue; + resolver.ResolveContactEnded(others); + } + collisionObjectFirstContactRegisterActive.Clear(); + (collisionObjectFirstContactRegisterActive, collisionObjectFirstContactRegisterTemp) = (collisionObjectFirstContactRegisterTemp, collisionObjectFirstContactRegisterActive); + + foreach (var kvp in colliderFirstContactRegisterActive) + { + var self = kvp.Key; + var resolver = self.Parent; + if(resolver == null) continue; + var others = kvp.Value; + if(others.Count <= 0) continue; + var contacts = new HashSet(); + foreach (var other in others) + { + contacts.Add(new Contact(self, other)); + } + resolver.ResolveContactEnded(contacts); + } + colliderFirstContactRegisterActive.Clear(); + (colliderFirstContactRegisterActive, colliderFirstContactRegisterTemp) = (colliderFirstContactRegisterTemp, colliderFirstContactRegisterActive); } #endregion @@ -1561,6 +1408,149 @@ public void DebugDraw(ColorRgba border, ColorRgba fill) } + + + +/* Version 2 +//TODO remove process overlap information: +// - this system is only needed for first contact determination between 2 colliders +// - OverlapEnded will be ContactEnded between 2 CollisionObjects +// - Rework to new generic stack/ register variant +private class OverlapStack(int capacity) : Dictionary(capacity) +{ + public OverlapRegister? GetRegister(CollisionObject owner) => !TryGetValue(owner, out var register) ? null : register; + + public bool AddOverlap(Contact contact) + { + var selfParent = contact.Self.Parent; + var otherParent = contact.Other.Parent; + if (selfParent == null || otherParent == null) return false; + + if (!ContainsKey(selfParent)) + { + var newRegister = new OverlapRegister(2); + newRegister.AddOverlap(contact); + Add(selfParent, newRegister); + } + else + { + var register = this[selfParent]; + register.AddOverlap(contact); + } + + return true; + } + + public void ProcessOverlaps() + { + + foreach (var entry in this) + { + var resolver = entry.Key; + var register = entry.Value; + var informations = register.GetOverlapInformations(); + if (informations == null) continue; + resolver.ResolveContactEnded(informations); + + // foreach (var entry in register) + // { + // resolver.ResolveCollisionEnded(entry.Value); + // } + } + } +} + +private class OverlapRegister(int capacity) : Dictionary(capacity) +{ + public List? GetOverlapInformations() => Values.Count <= 0 ? null : Values.ToList(); + + public bool AddOverlap(Contact contact) + { + var selfParent = contact.Self.Parent; + var otherParent = contact.Other.Parent; + + if (selfParent == null || otherParent == null) return false; + + if (TryGetValue(otherParent, out var cols)) + { + cols.Add(contact); + } + else + { + var overlapInfo = new ContactEndedInformation(selfParent, otherParent); + overlapInfo.Add(contact); + + Add(otherParent, overlapInfo); + } + + return true; + } + + public Contact? PopOverlap(Collider self, Collider other) + { + var otherParent = other.Parent; + if (otherParent == null) return null; + if (TryGetValue(otherParent, out var info)) + { + return info.PopContact(self, other); + } + + return null; + } +} +*/ + +/* Version 1 +private class OverlapRegister +{ + private HashSet entries; + + public OverlapRegister(int capacity) + { + entries = new(capacity); + } + + public OverlapEntry? FindEntry(Collider self, Collider other) + { + foreach (var entry in entries) + { + if (self == entry.Self && other == entry.Other) + { + return entry; + } + } + + return null; + } + + public bool AddEntry(OverlapEntry entry) => entries.Add(entry); + + public bool RemoveEntry(OverlapEntry entry) => entries.Remove(entry); + + public void ProcessEntries() + { + foreach (var entry in entries) + { + entry.Self.ResolveCollisionEnded(entry.Other); + } + } + + public void Clear() => entries.Clear(); + public void Swap(OverlapRegister other) => (entries, other.entries) = (other.entries, entries); +} +private class OverlapEntry +{ + public readonly Collider Self; + public readonly Collider Other; + + public OverlapEntry(Collider self, Collider other) + { + this.Self = self; + this.Other = other; + } +} +*/ + /*public Dictionary? QuerySpace(CollisionObject collisionObject, Vector2 origin, bool sorted = true) { if (!collisionObject.Enabled || !collisionObject.HasColliders) return null; @@ -1766,7 +1756,6 @@ public void DebugDraw(ColorRgba border, ColorRgba fill) } */ - /* private void ProcessCollisionsBucketFirst() { diff --git a/ShapeEngine/Core/CollisionSystem/Contact.cs b/ShapeEngine/Core/CollisionSystem/Contact.cs index 639f9ca2..79cb5586 100644 --- a/ShapeEngine/Core/CollisionSystem/Contact.cs +++ b/ShapeEngine/Core/CollisionSystem/Contact.cs @@ -16,12 +16,12 @@ public Contact(Collider self, Collider other) Other = other; // FirstContact = false; } - public Contact(Collider self, Collider other, bool firstContact) - { - Self = self; - Other = other; - // FirstContact = firstContact; - } + // public Contact(Collider self, Collider other, bool firstContact) + // { + // Self = self; + // Other = other; + // // FirstContact = firstContact; + // } private Contact(Contact contact) { From 82248d07e9d1aa34cad2c59b3b5fd7d1d63ac281 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:11:52 +0100 Subject: [PATCH 03/18] CollisionObject moved to CollisionSystem namespace. - OnContactEnded event fixed - OnColliderContact event fixed - ResolveContactEnded function implemented - ResolveColliderContactEnded function implemented --- .../{ => CollisionSystem}/CollisionObject.cs | 50 +++++++------------ 1 file changed, 19 insertions(+), 31 deletions(-) rename ShapeEngine/Core/{ => CollisionSystem}/CollisionObject.cs (90%) diff --git a/ShapeEngine/Core/CollisionObject.cs b/ShapeEngine/Core/CollisionSystem/CollisionObject.cs similarity index 90% rename from ShapeEngine/Core/CollisionObject.cs rename to ShapeEngine/Core/CollisionSystem/CollisionObject.cs index 7c98c953..3d1d3be5 100644 --- a/ShapeEngine/Core/CollisionObject.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionObject.cs @@ -4,16 +4,16 @@ using ShapeEngine.Core.Shapes; using ShapeEngine.Core.Structs; -namespace ShapeEngine.Core; +namespace ShapeEngine.Core.CollisionSystem; public abstract class CollisionObject : PhysicsObject { public event Action>? OnCollision; - public event Action>? OnContactEnded; + public event Action? OnContactEnded; public event Action? OnColliderIntersected; public event Action? OnColliderOverlapped; - public event Action? OnColliderContactEnded; + public event Action? OnColliderContactEnded; public CollisionObject() @@ -84,33 +84,20 @@ internal void ResolveCollision(List informations) } } - - internal void ResolveContactEnded(HashSet endedContacts) { - + foreach (var other in endedContacts) + { + ContactEnded(other); + OnContactEnded?.Invoke(this, other); + } } - - internal void ResolveContactEnded(HashSet endedContacts) + internal void ResolveColliderContactEnded(Collider self, Collider other) { - + if(AvancedCollisionNotification == false) return; + ColliderContactEnded(self, other); + OnColliderContactEnded?.Invoke(self, other); } - // internal void ResolveContactEnded(List informations) - // { - // ContactEnded(informations); - // OnContactEnded?.Invoke(informations); - // - // if(AvancedCollisionNotification == false) return; - // foreach (var info in informations) - // { - // foreach (var overlap in info) - // { - // overlap.Self.ResolveContactEnded(overlap.Other); - // ColliderContactEnded(overlap.Other); - // OnColliderContactEnded?.Invoke(overlap.Other); - // } - // } - // } /// @@ -120,10 +107,10 @@ internal void ResolveContactEnded(HashSet endedContacts) protected virtual void Collision(List info) { } /// - /// Called when 1 or more collider of this CollisionObject are no longer involved in a collision (intersection or overlap) + /// Called when all colliders between this CollisionObject and other have ended their contact. /// - /// - protected virtual void ContactEnded(List info) { } + /// The other collision object the contact has ended with. + protected virtual void ContactEnded(CollisionObject other) { } /// @@ -137,10 +124,11 @@ protected virtual void ColliderIntersected(Collision collision) { } /// The information about the overlap protected virtual void ColliderOverlapped(Overlap overlap) { } /// - /// Only callded when AdvancedCollisionNotification is set to true. + /// Only called when AdvancedCollisionNotification is set to true. Called when a collider of this CollisionObject and a collider of another CollisionObject have ended their contact. /// - /// The other collider involved - protected virtual void ColliderContactEnded(Collider other) { } + /// The collider of this CollisionObject. + /// The collider of the other CollisionObject. + protected virtual void ColliderContactEnded(Collider self, Collider other) { } From a2a7125938ed293227aa6128ba1137fcdf9d8be8 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:12:18 +0100 Subject: [PATCH 04/18] CollisionHandler resolve collider contact ended system improvements. --- ShapeEngine/Core/CollisionSystem/CollisionHandler.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs index 2f0a91e1..40ca5f07 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs @@ -451,12 +451,10 @@ private void Resolve() if(resolver == null) continue; var others = kvp.Value; if(others.Count <= 0) continue; - var contacts = new HashSet(); foreach (var other in others) { - contacts.Add(new Contact(self, other)); + resolver.ResolveColliderContactEnded(self, other); } - resolver.ResolveContactEnded(contacts); } colliderFirstContactRegisterActive.Clear(); (colliderFirstContactRegisterActive, colliderFirstContactRegisterTemp) = (colliderFirstContactRegisterTemp, colliderFirstContactRegisterActive); From a030dc419c504c130513eb8b806e42a99bac7823 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:12:40 +0100 Subject: [PATCH 05/18] Contact & ContactEndedInformation classes removed. --- ShapeEngine/Core/CollisionSystem/Contact.cs | 33 ------- .../ContactEndedInformation.cs | 92 ------------------- 2 files changed, 125 deletions(-) delete mode 100644 ShapeEngine/Core/CollisionSystem/Contact.cs delete mode 100644 ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs diff --git a/ShapeEngine/Core/CollisionSystem/Contact.cs b/ShapeEngine/Core/CollisionSystem/Contact.cs deleted file mode 100644 index 79cb5586..00000000 --- a/ShapeEngine/Core/CollisionSystem/Contact.cs +++ /dev/null @@ -1,33 +0,0 @@ -using ShapeEngine.Core.CollisionSystem; - -namespace ShapeEngine.Core.CollisionSystem; - -/// -/// Contains the information of a contact between two colliders. -/// -public class Contact -{ - public readonly Collider Self; - public readonly Collider Other; - // public bool FirstContact { get; internal set; } - public Contact(Collider self, Collider other) - { - Self = self; - Other = other; - // FirstContact = false; - } - // public Contact(Collider self, Collider other, bool firstContact) - // { - // Self = self; - // Other = other; - // // FirstContact = firstContact; - // } - - private Contact(Contact contact) - { - Self = contact.Self; - Other = contact.Other; - // FirstContact = contact.FirstContact; - } - public Contact Copy() => new(this); -} \ No newline at end of file diff --git a/ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs b/ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs deleted file mode 100644 index fd926584..00000000 --- a/ShapeEngine/Core/CollisionSystem/ContactEndedInformation.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Reflection.Emit; -using ShapeEngine.Core.CollisionSystem; - -namespace ShapeEngine.Core.CollisionSystem; - -/// -/// Contains the information of an overlap between two collision objects in form of a list of overlaps. -/// An overlap contains the information of the two overlapping colliders. -/// -public class ContactEndedInformation : List -{ - public readonly CollisionObject Self; - public readonly CollisionObject Other; - - public ContactEndedInformation(CollisionObject self, CollisionObject other) - { - Self = self; - Other = other; - } - - public ContactEndedInformation(CollisionObject self, CollisionObject other, List overlaps) - { - Self = self; - Other = other; - AddRange(overlaps); - } - - public ContactEndedInformation Copy() - { - var contactsCopy = new List(); - foreach (var contact in this) - { - contactsCopy.Add(contact.Copy()); - } - return new (Self, Other, contactsCopy); - } - internal Contact? PopContact(Collider self, Collider other) - { - foreach (var contact in this) - { - if (contact.Self == self && contact.Other == other) - { - Remove(contact); - return contact; - } - } - return null; - } - - public List? FilterContacts(Predicate match) - { - if(Count <= 0) return null; - List? filtered = null; - foreach (var c in this) - { - if (match(c)) - { - filtered??= new(); - filtered.Add(c); - } - } - return filtered; - } - - public HashSet? GetAllOtherColliders() - { - if(Count <= 0) return null; - HashSet others = new(); - foreach (var c in this) - { - others.Add(c.Other); - } - return others; - } - - /*public List? GetAllFirstContactOverlaps() - { - return FilterOverlaps((c) => c.FirstContact); - } - public HashSet? GetAllOtherFirstContactColliders() - { - var filtered = GetAllFirstContactOverlaps(); - if(filtered == null) return null; - HashSet others = new(); - foreach (var c in filtered) - { - others.Add(c.Other); - } - return others; - }*/ - -} \ No newline at end of file From 5f505bbe82e19a77c6c7226fb7b6751db5a42bec Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:27:06 +0100 Subject: [PATCH 06/18] First contact collision detection and contact ended detection fixes. --- .../Core/CollisionSystem/CollisionHandler.cs | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs index 40ca5f07..280ac32b 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs @@ -152,7 +152,15 @@ private class FirstContactStack(int capacity) : Dictionary>( { public bool RemoveEntry(T first, M second) { - return TryGetValue(first, out var register) && register.Count > 0 && register.Remove(second); + if (TryGetValue(first, out var register)) + { + bool removed = register.Remove(second); + if(register.Count <= 0) Remove(first); + return removed; + } + + return false; + // return TryGetValue(first, out var register) && register.Count > 0 && register.Remove(second); } public bool AddEntry(T first, M second) @@ -163,6 +171,7 @@ public bool AddEntry(T first, M second) } var newRegister = new HashSet(2); + newRegister.Add(second); Add(first, newRegister); return true; } @@ -295,8 +304,10 @@ private void ProcessCollisions(float dt) bool overlap = projected.Overlap(candidate); if (overlap) { - bool firstContactCollisionObject = !collisionObjectFirstContactRegisterActive.RemoveEntry(collider.Parent, candidate.Parent); - collisionObjectFirstContactRegisterTemp.AddEntry(collider.Parent, candidate.Parent); + //multiple colliders can be involved with the same pair of collision objects, therefore we also have to check if the collision object pair was already added to the temp register. + var removed = collisionObjectFirstContactRegisterActive.RemoveEntry(collider.Parent, candidate.Parent); + var added = collisionObjectFirstContactRegisterTemp.AddEntry(collider.Parent, candidate.Parent); + bool firstContactCollisionObject = !removed && added; bool firstContactCollider = !colliderFirstContactRegisterActive.RemoveEntry(collider, candidate); colliderFirstContactRegisterTemp.AddEntry(candidate, collider); @@ -371,9 +382,10 @@ private void ProcessCollisions(float dt) bool overlap = collider.Overlap(candidate); // ShapeGeometry.Overlap(collider, candidate); if (overlap) { - - bool firstContactCollisionObject = !collisionObjectFirstContactRegisterActive.RemoveEntry(collider.Parent, candidate.Parent); - collisionObjectFirstContactRegisterTemp.AddEntry(collider.Parent, candidate.Parent); + //multiple colliders can be involved with the same pair of collision objects, therefore we also have to check if the collision object pair was already added to the temp register. + var removed = collisionObjectFirstContactRegisterActive.RemoveEntry(collider.Parent, candidate.Parent); + var added = collisionObjectFirstContactRegisterTemp.AddEntry(collider.Parent, candidate.Parent); + bool firstContactCollisionObject = !removed && added; bool firstContactCollider = !colliderFirstContactRegisterActive.RemoveEntry(collider, candidate); colliderFirstContactRegisterTemp.AddEntry(candidate, collider); @@ -439,7 +451,10 @@ private void Resolve() var resolver = kvp.Key; var others = kvp.Value; if(others.Count <= 0) continue; - resolver.ResolveContactEnded(others); + foreach (var other in others) + { + resolver.ResolveContactEnded(other); + } } collisionObjectFirstContactRegisterActive.Clear(); (collisionObjectFirstContactRegisterActive, collisionObjectFirstContactRegisterTemp) = (collisionObjectFirstContactRegisterTemp, collisionObjectFirstContactRegisterActive); From 30291ca4bab4ccac8d74d7a10006b5a314398587 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:27:36 +0100 Subject: [PATCH 07/18] ResolveContactEnded function improved. --- ShapeEngine/Core/CollisionSystem/CollisionObject.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionObject.cs b/ShapeEngine/Core/CollisionSystem/CollisionObject.cs index 3d1d3be5..36fe63cc 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionObject.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionObject.cs @@ -84,13 +84,10 @@ internal void ResolveCollision(List informations) } } - internal void ResolveContactEnded(HashSet endedContacts) + internal void ResolveContactEnded(CollisionObject other) { - foreach (var other in endedContacts) - { - ContactEnded(other); - OnContactEnded?.Invoke(this, other); - } + ContactEnded(other); + OnContactEnded?.Invoke(this, other); } internal void ResolveColliderContactEnded(Collider self, Collider other) { From 5a1f21ab59a101238640b95617f2a5a0b3023375 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 09:27:56 +0100 Subject: [PATCH 08/18] GameObjectHandlerExample updated for new first contact / contact ended system. --- .../ExampleScenes/GameObjectHandlerExample.cs | 76 +++++++++++-------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs b/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs index ed10a6cf..3cac731b 100644 --- a/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs +++ b/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs @@ -102,7 +102,13 @@ internal class Overlapper : CollisionObject private CircleCollider circleCollider; private int overlapCount = 0; - private readonly ColorRgba[] overlapColors = [Colors.Light, Colors.Text, Colors.Cold, Colors.Warm, Colors.Highlight, Colors.Special, Colors.Special2]; + private readonly ColorRgba basicColor = Colors.Special; + private readonly ColorRgba overlapColor = Colors.Special2; + + private const float contactStartedDuration = 0.5f; + private const float contactEndedDuration = 0.5f; + private float contactStartedTimer = 0f; + private float contactEndedTimer = 0f; public Overlapper(Vector2 pos) : base(new Transform2D(pos, 0f, new Size(150, 0), 1f)) { @@ -131,50 +137,60 @@ internal class Overlapper : CollisionObject protected override void Collision(List info) { - foreach (var colInfo in info) { if (colInfo.Count > 0) { - foreach (var collision in colInfo) - { - if (!collision.FirstContact) continue; + if (!colInfo.FirstContact) continue; - if (colInfo.Other is BoundaryWall wall) - { - overlapCount++; - Velocity = -(Transform.Position).Normalize() * Velocity.Length(); - } - else - { - overlapCount++; - } + contactStartedTimer = contactStartedDuration; + overlapCount++; + if (colInfo.Other is BoundaryWall wall) + { + Velocity = -(Transform.Position).Normalize() * Velocity.Length(); } } } } - protected override void ContactEnded(List info) + protected override void ContactEnded(CollisionObject other) { - foreach (var overlapInfo in info) + contactEndedTimer = contactEndedDuration; + overlapCount--; + } + + public override void Update(GameTime time, ScreenInfo game, ScreenInfo gameUi, ScreenInfo ui) + { + base.Update(time, game, gameUi, ui); + if (contactStartedTimer > 0) { - foreach (var overlap in overlapInfo) - { - overlapCount--; - } + contactStartedTimer -= time.Delta; + if(contactStartedTimer <= 0) contactStartedTimer = 0; + } + if (contactEndedTimer > 0) + { + contactEndedTimer -= time.Delta; + if(contactEndedTimer <= 0) contactEndedTimer = 0; } } - + public override void DrawGame(ScreenInfo game) { var c = circleCollider.GetCircleShape(); - var colorIndex = 0; - if(overlapCount >= overlapColors.Length) colorIndex = overlapColors.Length - 1; - else if(overlapCount < 0) colorIndex = 0; - else colorIndex = overlapCount; - - var color = overlapColors[colorIndex]; - c.DrawLines(8f, color); + + //animated radius + var contactStartedF = contactStartedTimer / contactStartedDuration; + contactStartedF = ShapeTween.CircOut(contactStartedF); + var radius = ShapeMath.LerpFloat(c.Radius, c.Radius * 1.5f, contactStartedF); + c = c.SetRadius(radius); + + //animate thickness + var contactEndedF = contactEndedTimer / contactEndedDuration; + contactEndedF = ShapeTween.BounceIn(contactEndedF); + var thickness = ShapeMath.LerpFloat(8, 32, contactEndedF); + + var color = overlapCount > 0 ? overlapColor : basicColor; + c.DrawLines(thickness, color); } public override bool HasLeftBounds(Rect bounds) => !bounds.OverlapShape(circleCollider.GetCircleShape()); @@ -184,10 +200,6 @@ public override void DrawGame(ScreenInfo game) public override void DrawGameUI(ScreenInfo gameUi) { } - public override void FixedUpdate(GameTime fixedTime, ScreenInfo game, ScreenInfo gameUi, ScreenInfo ui) - { - - } } internal class Ball : CollisionObject From ddcba1a1bb6774f478674058cbb12a5978af43b8 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:26:56 +0100 Subject: [PATCH 09/18] CollisionHandler old stuff removed. --- .../Core/CollisionSystem/CollisionHandler.cs | 421 ------------------ 1 file changed, 421 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs index 280ac32b..561d115c 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs @@ -1419,424 +1419,3 @@ public void DebugDraw(ColorRgba border, ColorRgba fill) #endregion } - - - - - -/* Version 2 -//TODO remove process overlap information: -// - this system is only needed for first contact determination between 2 colliders -// - OverlapEnded will be ContactEnded between 2 CollisionObjects -// - Rework to new generic stack/ register variant -private class OverlapStack(int capacity) : Dictionary(capacity) -{ - public OverlapRegister? GetRegister(CollisionObject owner) => !TryGetValue(owner, out var register) ? null : register; - - public bool AddOverlap(Contact contact) - { - var selfParent = contact.Self.Parent; - var otherParent = contact.Other.Parent; - if (selfParent == null || otherParent == null) return false; - - if (!ContainsKey(selfParent)) - { - var newRegister = new OverlapRegister(2); - newRegister.AddOverlap(contact); - Add(selfParent, newRegister); - } - else - { - var register = this[selfParent]; - register.AddOverlap(contact); - } - - return true; - } - - public void ProcessOverlaps() - { - - foreach (var entry in this) - { - var resolver = entry.Key; - var register = entry.Value; - var informations = register.GetOverlapInformations(); - if (informations == null) continue; - resolver.ResolveContactEnded(informations); - - // foreach (var entry in register) - // { - // resolver.ResolveCollisionEnded(entry.Value); - // } - } - } -} - -private class OverlapRegister(int capacity) : Dictionary(capacity) -{ - public List? GetOverlapInformations() => Values.Count <= 0 ? null : Values.ToList(); - - public bool AddOverlap(Contact contact) - { - var selfParent = contact.Self.Parent; - var otherParent = contact.Other.Parent; - - if (selfParent == null || otherParent == null) return false; - - if (TryGetValue(otherParent, out var cols)) - { - cols.Add(contact); - } - else - { - var overlapInfo = new ContactEndedInformation(selfParent, otherParent); - overlapInfo.Add(contact); - - Add(otherParent, overlapInfo); - } - - return true; - } - - public Contact? PopOverlap(Collider self, Collider other) - { - var otherParent = other.Parent; - if (otherParent == null) return null; - if (TryGetValue(otherParent, out var info)) - { - return info.PopContact(self, other); - } - - return null; - } -} -*/ - -/* Version 1 -private class OverlapRegister -{ - private HashSet entries; - - public OverlapRegister(int capacity) - { - entries = new(capacity); - } - - public OverlapEntry? FindEntry(Collider self, Collider other) - { - foreach (var entry in entries) - { - if (self == entry.Self && other == entry.Other) - { - return entry; - } - } - - return null; - } - - public bool AddEntry(OverlapEntry entry) => entries.Add(entry); - - public bool RemoveEntry(OverlapEntry entry) => entries.Remove(entry); - - public void ProcessEntries() - { - foreach (var entry in entries) - { - entry.Self.ResolveCollisionEnded(entry.Other); - } - } - - public void Clear() => entries.Clear(); - public void Swap(OverlapRegister other) => (entries, other.entries) = (other.entries, entries); -} -private class OverlapEntry -{ - public readonly Collider Self; - public readonly Collider Other; - - public OverlapEntry(Collider self, Collider other) - { - this.Self = self; - this.Other = other; - } -} -*/ - -/*public Dictionary? QuerySpace(CollisionObject collisionObject, Vector2 origin, bool sorted = true) - { - if (!collisionObject.Enabled || !collisionObject.HasColliders) return null; - - Dictionary? result = null; - - foreach (var collider in collisionObject.Colliders) - { - var info = QuerySpace(collider, origin, sorted); - if (info != null && info.Count > 0) - { - result ??= new(); - result.Add(collider, info); - } - } - - return result; - } - public QueryInfos? QuerySpace(Collider collider, Vector2 origin, bool sorted = true) - { - QueryInfos? infos = null; - - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - spatialHash.GetCandidateBuckets(collider, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - var mask = collider.CollisionMask; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (candidate == collider) continue; - if (!mask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = collider.Intersect(candidate); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - public QueryInfos? QuerySpace(Segment shape, Vector2 origin, BitFlag collisionMask, bool sorted = true) - { - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - - spatialHash.GetCandidateBuckets(shape, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - QueryInfos? infos = null; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (!collisionMask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = shape.Intersect(candidate); // ShapeGeometry.Intersect(shape, candidate.GetCollider().GetShape()); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - public QueryInfos? QuerySpace(Triangle shape, Vector2 origin, BitFlag collisionMask, bool sorted = true) - { - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - - spatialHash.GetCandidateBuckets(shape, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - QueryInfos? infos = null; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (!collisionMask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = shape.Intersect(candidate); // ShapeGeometry.Intersect(shape, candidate.GetCollider().GetShape()); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - public QueryInfos? QuerySpace(Circle shape, Vector2 origin, BitFlag collisionMask, bool sorted = true) - { - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - - spatialHash.GetCandidateBuckets(shape, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - QueryInfos? infos = null; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (!collisionMask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = shape.Intersect(candidate); // ShapeGeometry.Intersect(shape, candidate.GetCollider().GetShape()); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - public QueryInfos? QuerySpace(Rect shape, Vector2 origin, BitFlag collisionMask, bool sorted = true) - { - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - - spatialHash.GetCandidateBuckets(shape, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - QueryInfos? infos = null; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (!collisionMask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = shape.Intersect(candidate); // ShapeGeometry.Intersect(shape, candidate.GetCollider().GetShape()); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - public QueryInfos? QuerySpace(Polygon shape, Vector2 origin, BitFlag collisionMask, bool sorted = true) - { - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - - spatialHash.GetCandidateBuckets(shape, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - QueryInfos? infos = null; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (!collisionMask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = shape.Intersect(candidate); // ShapeGeometry.Intersect(shape, candidate.GetCollider().GetShape()); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - public QueryInfos? QuerySpace(Polyline shape, Vector2 origin, BitFlag collisionMask, bool sorted = true) - { - collisionCandidateBuckets.Clear(); - collisionCandidateCheckRegister.Clear(); - - spatialHash.GetCandidateBuckets(shape, ref collisionCandidateBuckets); - if (collisionCandidateBuckets.Count <= 0) return null; - QueryInfos? infos = null; - foreach (var bucket in collisionCandidateBuckets) - { - foreach (var candidate in bucket) - { - if (!collisionMask.Has(candidate.CollisionLayer)) continue; - if (!collisionCandidateCheckRegister.Add(candidate)) continue; - - var collisionPoints = shape.Intersect(candidate); // ShapeGeometry.Intersect(shape, candidate.GetCollider().GetShape()); - - if (collisionPoints == null || !collisionPoints.Valid) continue; - - infos ??= new(); - infos.Add(new(candidate, origin, collisionPoints)); - } - } - if(sorted && infos is { Count: > 1 }) infos.SortClosest(origin); - return infos; - } - */ - -/* -private void ProcessCollisionsBucketFirst() -{ - int bucketCount = spatialHash.GetBucketCount(); - for (int i = 0; i < bucketCount; i++) - { - var bucketInfo = spatialHash.GetBucketInfo(i); - if (!bucketInfo.Valid) continue; - - foreach (var collidable in bucketInfo.Active) - { - var others = bucketInfo.GetOthers(collidable); - - bool computeIntersections = collidable.GetCollider().ComputeIntersections; - List> cols = new(); - foreach (var other in others) - { - //IterationsPerFrame++; - if (activeRegister.HasEntry(collidable, other)) continue; - - bool overlap = SGeometry.Overlap(collidable, other); - if (overlap) - { - bool firstContact = !oldRegister.RemoveEntry(collidable, other); - activeRegister.AddEntry(collidable, other); - if (computeIntersections) - { - //CollisionChecksPerFrame++; - var collisionPoints = SGeometry.Intersect(collidable, other); - - //shapes overlap but no collision points means collidable is completely inside other - //closest point on bounds of other are now used for collision point - if (collisionPoints.Count <= 0) - { - Vector2 refPoint = collidable.GetCollider().GetPreviousPosition(); - var shape = other.GetCollider().GetShape(); - if (!shape.IsPointInside(refPoint)) - { - CollisionPoint closest = shape.GetClosestPoint(refPoint); - collisionPoints.Add(closest); - //ClosestPointChecksPerFrame++; - } - - } - - Collision c = new(collidable, other, firstContact, collisionPoints); - cols.Add(c); - } - else - { - Collision c = new(collidable, other, firstContact); - cols.Add(c); - } - } - } - - if (cols.Count > 0) - { - if (collisionStack.ContainsKey(collidable)) - { - collisionStack[collidable].AddRange(cols); - } - else collisionStack.Add(collidable, cols); - } - } - //IterationsPerFrame++; - } - -} -*/ - From 7fd80b936575fe0b8f92eec0675adfc9b30f9d40 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:27:20 +0100 Subject: [PATCH 10/18] CollisionInformation cleanup and new functions added. --- .../CollisionSystem/CollisionInformation.cs | 96 +++++++++++-------- 1 file changed, 56 insertions(+), 40 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs b/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs index e67d1a76..591586ee 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs @@ -148,45 +148,6 @@ public bool Validate(out CollisionPointValidationResult result) #endregion - #region First Contact - - public bool IsFirstContact() - { - if(Count <= 0) return false; - foreach (var collision in this) - { - if(collision.FirstContact) return true; - } - - return false; - } - public int GetFirstContactCount() - { - if(Count <= 0) return 0; - var count = 0; - foreach (var collision in this) - { - if(collision.FirstContact)count++; - } - - return count; - } - public List? GetFirstContactCollisions() - { - List? result = null; - foreach (var collision in this) - { - if (collision.FirstContact) - { - result??= new(); - result.Add(collision); - } - } - return result; - } - - #endregion - #region Public Functions public CollisionInformation Copy() @@ -238,7 +199,62 @@ public CollisionInformation Copy() } return others; } - + + + //TODO: Implement all functions! + public CollisionPoint GetClosestCollisionPoint() + { + return new(); + } + + public CollisionPoint GetFurthestCollisionPoint() + { + return new(); + } + + public CollisionPoint GetCombinedCollisionPoint() + { + return new(); + } + + public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint) + { + return new(); + } + + public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint) + { + return new(); + } + + public CollisionPoint GetCombinedCollisionPoint(Vector2 referencePoint) + { + return new(); + } + + public CollisionPoint GetClosestCollisionPoint(out float minDistanceSquared) + { + minDistanceSquared = -1f; + return new(); + } + + public CollisionPoint GetFurthestCollisionPoint(out float maxDistanceSquared) + { + maxDistanceSquared = -1f; + return new(); + } + + public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint, out float minDistanceSquared) + { + minDistanceSquared = -1f; + return new(); + } + + public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint, out float maxDistanceSquared) + { + maxDistanceSquared = -1f; + return new(); + } #endregion } From 63fcce287fdb40cc69705f9b25d9209f839da1e9 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:37:21 +0100 Subject: [PATCH 11/18] Release preparations for the 3.1 release. --- ShapeEngine/ShapeEngine.csproj | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/ShapeEngine/ShapeEngine.csproj b/ShapeEngine/ShapeEngine.csproj index b3439998..0b16cfbd 100644 --- a/ShapeEngine/ShapeEngine.csproj +++ b/ShapeEngine/ShapeEngine.csproj @@ -10,7 +10,7 @@ Shape Engine Dave Green https://github.com/DaveGreen-Games/ShapeEngine - 3.0.0 + 3.1.0 My custom made 2d game engine based on the great Raylib Framework. Main focus is being performant and only using draw functions instead of textures. Custom input, audio, savegame, collision, shape, pathfinding, ui, camera, color palette, polygon fracturing, and text system are available with a few other nice things! https://davegreengames.itch.io/shape-engine shapeengine-nuget-icon-128px.png @@ -18,13 +18,41 @@ GameEngine Framework 2D raylib gamedev graphics shapes polygon rect line clipper2 polygonclipping Dave Green Games Shape Engine - 3.0.0 - 3.0.0 + 3.1.0 + 3.1.0 readme-nuget.md en-US Copyright (c) David Grueneis 2024 true + # 3.1 Release + This release addresses various fixes and improvements that were missed in the 3.0 release. + Despite introducing some breaking changes, I decided to classify this as a minor release rather than version 4.0, as all the breaking changes pertain to the same systems as the 3.0 release. + + ## FirstContact / ContactEnded System Overhaul: + - Previously, the FirstContact was only reported for Colliders and not for CollisionObjects. The system has now been split into two separate systems: one for Collider pairs and one for CollisionObject pairs. + - CollisionObjects: + + A FirstContact between two CollisionObjects is reported once any Collider of one contacts any Collider of the other CollisionObject. + + The ContactEnded() function for the CollisionObject is called once all contacts between all of the Colliders have ended. + - Colliders: + + A FirstContact between two Colliders is reported the first time they contact (no contact in the previous frame). + + The ColliderContactEnded() function is called once the contact ends (contact in the previous frame but no contact in the current frame). + + ## Other Changes: + - Namespace Changes: + + CollisionObject moved to the CollisionSystem namespace. + - Class Removals: + + ContactEndedInformation class removed. + + Contact class removed. + - Function Parameter Changes: + + ContactEnded(CollisionObject other) function parameters changed. + + ColliderContactEnded(Collider self, Collider other) function parameters changed. + - CollisionInformation Improvements: + + CollisionInformation now includes a FirstContact member, reported only for the two involved CollisionObjects, separate from any FirstContact reporting between Colliders. + + General improvements and cleanup of CollisionInformation, including new functions. + - ResolveCollision Update: + + CollisionObject.ResolveCollision() is now called for each generated CollisionInformation instead of receiving a list of CollisionInformation. + # 3.0 Release This release improves the way collision/overlap/query information is aggregated and handled. Before information was collected on a per collider basis and events/functions would be called on the active involved collider. This meant the actual collision object and parent of the colliders would not get notified. From 0c3386839771ad2c4929b6bebe268159ce9676c0 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 08:17:51 +0100 Subject: [PATCH 12/18] CollisionObject Collision() parameter changed to CollisionInformation info from List info. - CollisionHandler updated. - EndlessSpaceCollision scene updated. - GameObjectHandlerExample scene updated. --- .../ExampleScenes/EndlessSpaceCollision.cs | 91 ++++++++---------- .../ExampleScenes/GameObjectHandlerExample.cs | 96 ++++++++----------- .../Core/CollisionSystem/CollisionHandler.cs | 10 +- .../Core/CollisionSystem/CollisionObject.cs | 37 ++++--- 4 files changed, 100 insertions(+), 134 deletions(-) diff --git a/Examples/Scenes/ExampleScenes/EndlessSpaceCollision.cs b/Examples/Scenes/ExampleScenes/EndlessSpaceCollision.cs index e739f0a6..887b7ebe 100644 --- a/Examples/Scenes/ExampleScenes/EndlessSpaceCollision.cs +++ b/Examples/Scenes/ExampleScenes/EndlessSpaceCollision.cs @@ -483,23 +483,20 @@ public Bullet(Vector2 pos, Vector2 dir, BulletStats stats, ColorRgba color) } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { - foreach (var i in info) - { - if (i.Count <= 0) continue; + if (info.Count <= 0) return; - if (i.Other is AsteroidObstacle asteroid) + if (info.Other is AsteroidObstacle asteroid) + { + foreach (var collision in info) { - foreach (var collision in i) - { - if(!collision.FirstContact) continue; - asteroid.Damage(Transform.Position, stats.Damage, new Vector2(0f)); - effectTimer = effectDuration; - collider.Enabled = false; - Velocity = new(0f); - return; - } + if(!collision.FirstContact) continue; + asteroid.Damage(Transform.Position, stats.Damage, new Vector2(0f)); + effectTimer = effectDuration; + collider.Enabled = false; + Velocity = new(0f); + return; } } } @@ -617,32 +614,28 @@ public Ship(Vector2 pos, float shipSize) Health = MaxHp; } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { - foreach (var i in info) - { - if(i.Count <= 0 || i.Other is not AsteroidObstacle a) continue; - if(!i.Validate(out CollisionPoint combined)) continue; + if(info.Count <= 0 || info.Other is not AsteroidObstacle a) return; + if(!info.Validate(out CollisionPoint combined)) return; - a.Cut(GetCutShape()); + a.Cut(GetCutShape()); - if (collisionStunTimer <= 0f) - { - Health--; - if (Health <= 0) - { - collider.Enabled = false; - Kill(); - OnKilled?.Invoke(); - } - } - if (combined.Valid) + if (collisionStunTimer <= 0f) + { + Health--; + if (Health <= 0) { - Velocity = combined.Normal * 3500; - collisionStunTimer = CollisionStunTime; - collisionRotationDirection = Rng.Instance.RandDirF(); + collider.Enabled = false; + Kill(); + OnKilled?.Invoke(); } - + } + if (combined.Valid) + { + Velocity = combined.Normal * 3500; + collisionStunTimer = CollisionStunTime; + collisionRotationDirection = Rng.Instance.RandDirF(); } } @@ -926,19 +919,15 @@ public void Damage(Vector2 pos, float amount, Vector2 force) } } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { - foreach (var i in info) + if(info.Count <= 0) return; + if(!info.Validate(out CollisionPoint combined)) return; + foreach (var collision in info) { - if(i.Count <= 0) continue; - if(!i.Validate(out CollisionPoint combined)) continue; - foreach (var collision in i) - { - if(collision.Points == null || collision.Points.Count <= 0 || !collision.FirstContact)continue; + if(collision.Points == null || collision.Points.Count <= 0 || !collision.FirstContact)continue; - Velocity = Velocity.Reflect(combined.Normal); - } - + Velocity = Velocity.Reflect(combined.Normal); } } public void MoveTo(Vector2 newPosition) @@ -1864,18 +1853,14 @@ public Destructor(Vector2 position, Vector2 direction, ColorRgba color) Drag = 0f; } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { if (info.Count <= 0) return; - foreach (var collisionInfo in info) + if (info.Other is AsteroidObstacle asteroid) { - if(collisionInfo.Count <= 0) continue; - if (collisionInfo.Other is AsteroidObstacle asteroid) + if (info.FirstContact) { - if (collisionInfo[0].FirstContact) - { - asteroid.Damage(Transform.Position, 10000000, Vector2.Zero); - } + asteroid.Damage(Transform.Position, 10000000, Vector2.Zero); } } diff --git a/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs b/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs index 3cac731b..8fd9b23f 100644 --- a/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs +++ b/Examples/Scenes/ExampleScenes/GameObjectHandlerExample.cs @@ -135,20 +135,15 @@ internal class Overlapper : CollisionObject } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { - foreach (var colInfo in info) + if (info.Count > 0 && info.FirstContact) { - if (colInfo.Count > 0) + contactStartedTimer = contactStartedDuration; + overlapCount++; + if (info.Other is BoundaryWall wall) { - if (!colInfo.FirstContact) continue; - - contactStartedTimer = contactStartedDuration; - overlapCount++; - if (colInfo.Other is BoundaryWall wall) - { - Velocity = -(Transform.Position).Normalize() * Velocity.Length(); - } + Velocity = -(Transform.Position).Normalize() * Velocity.Length(); } } } @@ -224,22 +219,18 @@ internal class Ball : CollisionObject Layer = SpawnAreaLayers.ObjectFlag; } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { CollisionPoint p = new(); - foreach (var colInfo in info) + if (info.Count > 0) { - if (colInfo.Count > 0) + foreach (var collision in info) { - foreach (var collision in colInfo) + if(!collision.FirstContact) continue; + if(collision.Points == null) continue; + if (collision.Validate(out CollisionPoint combined)) { - if(!collision.FirstContact) continue; - if(collision.Points == null) continue; - if (collision.Validate(out CollisionPoint combined)) - { - // var cp = collision.Points.GetAverageCollisionPoint(); - if (combined.Valid) p = p.Average(combined); - } + if (combined.Valid) p = p.Average(combined); } } } @@ -331,24 +322,21 @@ public override void FixedUpdate(GameTime fixedTime, ScreenInfo game, ScreenInfo } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { CollisionPoint p = new(); - foreach (var colInfo in info) + if (info.Count > 0) { - if (colInfo.Count > 0) + foreach (var collision in info) { - foreach (var collision in colInfo) + if(!collision.FirstContact) continue; + if(collision.Points == null) continue; + if (collision.Validate(out var combined, out var closest)) { - if(!collision.FirstContact) continue; - if(collision.Points == null) continue; - if (collision.Validate(out var combined, out var closest)) - { - Transform = Transform.SetPosition(closest.Point); - Velocity = new(); - Enabled = false; - deadTimer = 2f; - } + Transform = Transform.SetPosition(closest.Point); + Velocity = new(); + Enabled = false; + deadTimer = 2f; } } } @@ -493,22 +481,19 @@ private void OnEnteredGameArea() } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { CollisionPoint p = new(); - foreach (var colInfo in info) + if (info.Count > 0) { - if (colInfo.Count > 0) + foreach (var collision in info) { - foreach (var collision in colInfo) + if(!collision.FirstContact) continue; + if(collision.Points == null) continue; + if (collision.Validate(out var combined, out var closest)) { - if(!collision.FirstContact) continue; - if(collision.Points == null) continue; - if (collision.Validate(out var combined, out var closest)) - { - // var cp = collision.Points.GetAverageCollisionPoint(); - if (combined.Valid) p = p.Average(combined); - } + // var cp = collision.Points.GetAverageCollisionPoint(); + if (combined.Valid) p = p.Average(combined); } } } @@ -585,22 +570,19 @@ internal class Bird : CollisionObject Layer = SpawnAreaLayers.ObjectFlag; } - protected override void Collision(List info) + protected override void Collision(CollisionInformation info) { CollisionPoint p = new(); - foreach (var colInfo in info) + if (info.Count > 0) { - if (colInfo.Count > 0) + foreach (var collision in info) { - foreach (var collision in colInfo) + if(!collision.FirstContact) continue; + if(collision.Points == null) continue; + if (collision.Validate(out CollisionPoint combined)) { - if(!collision.FirstContact) continue; - if(collision.Points == null) continue; - if (collision.Validate(out CollisionPoint combined)) - { - // var cp = collision.Points.GetAverageCollisionPoint(); - if (combined.Valid) p = p.Average(combined); - } + // var cp = collision.Points.GetAverageCollisionPoint(); + if (combined.Valid) p = p.Average(combined); } } } diff --git a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs index 561d115c..e84d8b4f 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionHandler.cs @@ -26,16 +26,18 @@ public void ProcessCollisions() { var resolver = entry.Key; var register = entry.Value; - var informations = register.GetCollisionInformations(); - if (informations == null) continue; - resolver.ResolveCollision(informations); + if(register.Count <= 0) continue; + foreach (var info in register.Values) + { + resolver.ResolveCollision(info); + } } } } private class CollisionRegister : Dictionary { - public List? GetCollisionInformations() => Values.Count <= 0 ? null : Values.ToList(); + // public List? GetCollisionInformations() => Values.Count <= 0 ? null : Values.ToList(); public bool AddCollision(Collision collision, bool firstContact) { diff --git a/ShapeEngine/Core/CollisionSystem/CollisionObject.cs b/ShapeEngine/Core/CollisionSystem/CollisionObject.cs index 36fe63cc..96dafc2c 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionObject.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionObject.cs @@ -8,7 +8,7 @@ namespace ShapeEngine.Core.CollisionSystem; public abstract class CollisionObject : PhysicsObject { - public event Action>? OnCollision; + public event Action? OnCollision; public event Action? OnContactEnded; public event Action? OnColliderIntersected; @@ -56,30 +56,27 @@ public bool Enabled public bool AvancedCollisionNotification = false; - internal void ResolveCollision(List informations) + internal void ResolveCollision(CollisionInformation information) { - Collision(informations); - OnCollision?.Invoke(informations); + Collision(information); + OnCollision?.Invoke(information); if(AvancedCollisionNotification == false) return; - foreach (var info in informations) + foreach (var collision in information) { - foreach (var collision in info) + if (collision.Points != null) { - if (collision.Points != null) - { - collision.Self.ResolveIntersected(collision); - ColliderIntersected(collision); - OnColliderIntersected?.Invoke(collision); - } - else - { - var overlap = collision.Overlap; - collision.Self.ResolveOverlapped(overlap); - ColliderOverlapped(overlap); - OnColliderOverlapped?.Invoke(overlap); - } + collision.Self.ResolveIntersected(collision); + ColliderIntersected(collision); + OnColliderIntersected?.Invoke(collision); + } + else + { + var overlap = collision.Overlap; + collision.Self.ResolveOverlapped(overlap); + ColliderOverlapped(overlap); + OnColliderOverlapped?.Invoke(overlap); } } @@ -101,7 +98,7 @@ internal void ResolveColliderContactEnded(Collider self, Collider other) /// Called when 1 or more collider of this CollisionObject is involved in a collision (intersection or overlap) /// /// - protected virtual void Collision(List info) { } + protected virtual void Collision(CollisionInformation info) { } /// /// Called when all colliders between this CollisionObject and other have ended their contact. From be2a90534fd07d123c9dfd2cc46995b1cc76ea13 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 08:34:45 +0100 Subject: [PATCH 13/18] CollisionPoints GetClosest/Furthest/FacingTowards functions now skip invalid collision points --- .../Core/CollisionSystem/CollisionPoints.cs | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs b/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs index 12b0b4df..00d0fec8 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs @@ -555,17 +555,18 @@ public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint) { if (!Valid) return new(); if(Count == 1) return this[0]; - - var closest = this[0]; - var closestDist = (closest.Point - referencePoint).LengthSquared(); - for (var i = 1; i < Count; i++) + + var closest = new CollisionPoint(); + var closestDistanceSquared = -1f; // (closest.Point - referencePoint).LengthSquared(); + for (var i = 0; i < Count; i++) { var p = this[i]; - var dis = (p.Point - referencePoint).LengthSquared(); - if (dis < closestDist) + if(!p.Valid) continue; + var distanceSquared = (p.Point - referencePoint).LengthSquared(); + if (distanceSquared < closestDistanceSquared || closestDistanceSquared <= 0f) { closest = p; - closestDist = dis; + closestDistanceSquared = distanceSquared; } } @@ -575,17 +576,18 @@ public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint) { if (!Valid) return new(); if(Count == 1) return this[0]; - - var furthest = this[0]; - var furthestDis = (furthest.Point - referencePoint).LengthSquared(); - for (var i = 1; i < Count; i++) + + var furthest = new CollisionPoint(); + var furthestDistanceSquared = -1f; + for (var i = 0; i < Count; i++) { var p = this[i]; - var dis = (p.Point - referencePoint).LengthSquared(); - if (dis > furthestDis) + if(!p.Valid) continue; + var distanceSquared = (p.Point - referencePoint).LengthSquared(); + if (distanceSquared > furthestDistanceSquared || furthestDistanceSquared <= 0f) { furthest = p; - furthestDis = dis; + furthestDistanceSquared = distanceSquared; } } @@ -596,14 +598,15 @@ public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint, out float closestDistanceSquared = -1; if (!Valid) return new(); if(Count == 1) return this[0]; - - var closest = this[0]; - closestDistanceSquared = (closest.Point - referencePoint).LengthSquared(); - for (var i = 1; i < Count; i++) + + var closest = new CollisionPoint(); + closestDistanceSquared = -1f; // (closest.Point - referencePoint).LengthSquared(); + for (var i = 0; i < Count; i++) { var p = this[i]; + if(!p.Valid) continue; var dis = (p.Point - referencePoint).LengthSquared(); - if (dis < closestDistanceSquared) + if (dis < closestDistanceSquared || closestDistanceSquared <= 0f) { closest = p; closestDistanceSquared = dis; @@ -617,14 +620,14 @@ public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint, out floa furthestDistanceSquared = -1; if (!Valid) return new(); if(Count == 1) return this[0]; - - var furthest = this[0]; + + var furthest = new CollisionPoint(); furthestDistanceSquared = (furthest.Point - referencePoint).LengthSquared(); - for (var i = 1; i < Count; i++) + for (var i = 0; i < Count; i++) { var p = this[i]; var dis = (p.Point - referencePoint).LengthSquared(); - if (dis > furthestDistanceSquared) + if (dis > furthestDistanceSquared || furthestDistanceSquared <= 0f) { furthest = p; furthestDistanceSquared = dis; @@ -643,17 +646,18 @@ public CollisionPoint GetCollisionPointFacingTowardsPoint(Vector2 referencePoint { if (!Valid) return new(); if(Count == 1) return this[0]; + + var best = new CollisionPoint(); + // var dir = (referencePoint - best.Point).Normalize(); + var maxDot = -10f; //dir.Dot(best.Normal); - var best = this[0]; - var dir = (referencePoint - best.Point).Normalize(); - var maxDot = dir.Dot(best.Normal); - - for (var i = 1; i < Count; i++) + for (var i = 0; i < Count; i++) { var p = this[i]; - dir = (referencePoint - p.Point).Normalize(); + if(!p.Valid) continue; + var dir = (referencePoint - p.Point).Normalize(); var dot = dir.Dot(p.Normal); - if (dot > maxDot) + if (dot > maxDot || maxDot < -5) { best = p; maxDot = dot; @@ -671,15 +675,16 @@ public CollisionPoint GetCollisionPointFacingTowardsDir(Vector2 referenceDir) { if (!Valid) return new(); if(Count == 1) return this[0]; + + var best = new CollisionPoint(); + var maxDot = -10f; // referenceDir.Dot(best.Normal); - var best = this[0]; - var maxDot = referenceDir.Dot(best.Normal); - - for (var i = 1; i < Count; i++) + for (var i = 0; i < Count; i++) { var p = this[i]; + if(!p.Valid) continue; var dot = referenceDir.Dot(p.Normal); - if (dot > maxDot) + if (dot > maxDot || maxDot < -5) { best = p; maxDot = dot; From 561f459d43260234cce70eded40c4a28d9a24a5d Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:10:45 +0100 Subject: [PATCH 14/18] Exists, Find, FindAll functions added to Collision. GetAverageCollisionPoint renamed to GetCombinedCollisionPoint in Collision. --- ShapeEngine/Core/CollisionSystem/Collision.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/Collision.cs b/ShapeEngine/Core/CollisionSystem/Collision.cs index 515356a8..efcd3f5e 100644 --- a/ShapeEngine/Core/CollisionSystem/Collision.cs +++ b/ShapeEngine/Core/CollisionSystem/Collision.cs @@ -1,5 +1,6 @@ using System.Numerics; using ShapeEngine.Core.Interfaces; +using ShapeEngine.Core.Shapes; using ShapeEngine.Core.Structs; using ShapeEngine.UI; @@ -117,10 +118,27 @@ public bool Validate(out CollisionPointValidationResult result) #region CollisionPoint - public CollisionPoint GetAverageCollisionPoint() + public bool Exists(Predicate match) + { + if(Points == null || Points.Count <= 0) return false; + return Points.Exists(match); + } + public CollisionPoint Find(Predicate match) + { + if(Points == null || Points.Count <= 0) return new(); + return Points.Find(match); + } + public CollisionPoints? FindAll(Predicate match) + { + if (Points == null || Points.Count <= 0) return null; + var result = Points.FindAll(match); + return new(result); + } + + public CollisionPoint GetCombinedCollisionPoint() { if(Points == null || Points.Count <= 0) return new CollisionPoint(); - return Points.GetAverageCollisionPoint(); + return Points.GetCombinedCollisionPoint(); } public CollisionPoint GetClosestCollisionPoint() { From 30be1f8596cdf33425b34285296a0f7d119a961e Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:11:59 +0100 Subject: [PATCH 15/18] GetClosest, Furthest, Combined - CollisionPoint functions (and overloads) added to CollisionInformation. Exists, Find, FindAll functions added to CollisionInformation. --- .../CollisionSystem/CollisionInformation.cs | 272 ++++++++++++++++-- 1 file changed, 247 insertions(+), 25 deletions(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs b/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs index 591586ee..50cc86ee 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionInformation.cs @@ -201,59 +201,281 @@ public CollisionInformation Copy() } - //TODO: Implement all functions! - public CollisionPoint GetClosestCollisionPoint() + /// + /// Return if a collision point exist in any valid collision matching the predicate. + /// + /// + /// + public bool ExistsCollisionPoint(Predicate match) + { + if(Count <= 0) return false; + foreach (var collision in this) + { + if (collision.Exists(match)) return true; + } + + return false; + } + /// + /// Returns the first collision point matching the predicate in any valid collision. If no collision point is found an empty CollisionPoint is returned. + /// + /// + /// + public CollisionPoint FindCollisionPoint(Predicate match) { + if(Count <= 0) return new(); + foreach (var collision in this) + { + var p = collision.Find(match); + if (p.Valid) return p; + } + return new(); } + /// + /// Finds all collision points matching the predicate in any valid collision. If no collision points are found null is returned. + /// + /// + /// + public CollisionPoints? FindAllCollisionPoints(Predicate match) + { + if (Count <= 0) return null; + CollisionPoints? result = null; + foreach (var collision in this) + { + var points = collision.FindAll(match); + if(points == null) continue; + + result??= new CollisionPoints(); + result.AddRange(points); + } + + return result; + } + + + /// + /// Get the closest collision point to Self.Transform.Position within all valid collisions. + /// + /// + public CollisionPoint GetClosestCollisionPoint() + { + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + var closestDistanceSquared = -1f; + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetClosestCollisionPoint(Self.Transform.Position, out float minDistanceSquared); + if (!cp.Valid) continue; + if (minDistanceSquared < closestDistanceSquared || closestDistanceSquared < 0f) + { + closestDistanceSquared = minDistanceSquared; + result = cp; + } + } + + return result; + } + /// + /// Get the furthest collision point to Self.Transform.Position within all valid collisions. + /// + /// public CollisionPoint GetFurthestCollisionPoint() { - return new(); + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + var furthestDistanceSquared = -1f; + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetFurthestCollisionPoint(Self.Transform.Position, out float maxDistanceSquared); + if (!cp.Valid) continue; + if (maxDistanceSquared < furthestDistanceSquared || furthestDistanceSquared < 0f) + { + furthestDistanceSquared = maxDistanceSquared; + result = cp; + } + } + + return result; } + /// + /// Get the combined collision point of all valid collision points in all valid collisions. + /// + /// public CollisionPoint GetCombinedCollisionPoint() { - return new(); + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetCombinedCollisionPoint(); + if (!cp.Valid) continue; + result = result.Average(cp); + } + + return result; } - + /// + /// Get the closest collision point to the reference point within all valid collisions. + /// + /// public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint) { - return new(); - } + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + var closestDistanceSquared = -1f; + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetClosestCollisionPoint(referencePoint, out float minDistanceSquared); + if (!cp.Valid) continue; + if (minDistanceSquared < closestDistanceSquared || closestDistanceSquared < 0f) + { + closestDistanceSquared = minDistanceSquared; + result = cp; + } + } - public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint) - { - return new(); + return result; } - - public CollisionPoint GetCombinedCollisionPoint(Vector2 referencePoint) + /// + /// Get the furthest collision point to the reference point within all valid collisions. + /// + /// + public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint) { - return new(); + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + var furthestDistanceSquared = -1f; + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetFurthestCollisionPoint(referencePoint, out float maxDistanceSquared); + if (!cp.Valid) continue; + if (maxDistanceSquared < furthestDistanceSquared || furthestDistanceSquared < 0f) + { + furthestDistanceSquared = maxDistanceSquared; + result = cp; + } + } + + return result; } - public CollisionPoint GetClosestCollisionPoint(out float minDistanceSquared) + /// + /// Get the closest collision point to Self.Transform.Position within all valid collisions. + /// + /// The closest distance squared between the closest collision point and Self.Transform.Position. If negative value is invalid. + /// + public CollisionPoint GetClosestCollisionPoint(out float closestDistanceSquared) { - minDistanceSquared = -1f; - return new(); + closestDistanceSquared = -1f; + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetClosestCollisionPoint(Self.Transform.Position, out float minDistanceSquared); + if (!cp.Valid) continue; + if (minDistanceSquared < closestDistanceSquared || closestDistanceSquared < 0f) + { + closestDistanceSquared = minDistanceSquared; + result = cp; + } + } + + return result; } - public CollisionPoint GetFurthestCollisionPoint(out float maxDistanceSquared) + /// + /// Get the furthest collision point to Self.Transform.Position within all valid collisions. + /// + /// The furthest distance squared between the furthest collision point and Self.Transform.Position. If negative value is invalid. + /// + public CollisionPoint GetFurthestCollisionPoint(out float furthestDistanceSquared) { - maxDistanceSquared = -1f; - return new(); + furthestDistanceSquared = -1f; + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetFurthestCollisionPoint(Self.Transform.Position, out float maxDistanceSquared); + if (!cp.Valid) continue; + if (maxDistanceSquared < furthestDistanceSquared || furthestDistanceSquared < 0f) + { + furthestDistanceSquared = maxDistanceSquared; + result = cp; + } + } + + return result; } - public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint, out float minDistanceSquared) + /// + /// Get the closest collision point to the reference point within all valid collisions. + /// + /// The reference point for finding the closest collision point. + /// The closest distance squared between the closest collision point and the reference point. If negative value is invalid. + /// + public CollisionPoint GetClosestCollisionPoint(Vector2 referencePoint, out float closestDistanceSquared) { - minDistanceSquared = -1f; - return new(); + closestDistanceSquared = -1f; + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetClosestCollisionPoint(referencePoint, out float minDistanceSquared); + if (!cp.Valid) continue; + if (minDistanceSquared < closestDistanceSquared || closestDistanceSquared < 0f) + { + closestDistanceSquared = minDistanceSquared; + result = cp; + } + } + + return result; } - public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint, out float maxDistanceSquared) + /// + /// Get the furthest collision point to the reference point within all valid collisions. + /// + /// The reference point for finding the furthest collision point. + /// The furthest distance squared between the furthest collision point and the reference point. If negative value is invalid. + /// + public CollisionPoint GetFurthestCollisionPoint(Vector2 referencePoint, out float furthestDistanceSquared) { - maxDistanceSquared = -1f; - return new(); + furthestDistanceSquared = -1f; + if(Count <= 0) return new CollisionPoint(); + + var result = new CollisionPoint(); + foreach (var collision in this) + { + if(collision.Points == null || collision.Points.Count <= 0) continue; + var cp = collision.Points.GetFurthestCollisionPoint(Self.Transform.Position, out float maxDistanceSquared); + if (!cp.Valid) continue; + if (maxDistanceSquared < furthestDistanceSquared || furthestDistanceSquared < 0f) + { + furthestDistanceSquared = maxDistanceSquared; + result = cp; + } + } + + return result; } #endregion } From 8fef3b5b76c25ddb76e94b7d67c05fa759609f1e Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:12:28 +0100 Subject: [PATCH 16/18] GetAverageCollisionPoint renamed to GetCombinedCollisionPoint in CollisionPoints. --- ShapeEngine/Core/CollisionSystem/CollisionPoints.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs b/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs index 00d0fec8..e6c80a35 100644 --- a/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs +++ b/ShapeEngine/Core/CollisionSystem/CollisionPoints.cs @@ -15,6 +15,7 @@ public CollisionPoints(int capacity = 0) : base(capacity) } public CollisionPoints(params CollisionPoint[] points) : base(points.Length) { AddRange(points); } public CollisionPoints(IEnumerable points, int count) : base(count) { AddRange(points); } + public CollisionPoints(List points) : base(points.Count) { AddRange(points); } public CollisionPoints(CollisionPoints other) : base(other.Count) { @@ -536,7 +537,7 @@ public bool Equals(CollisionPoints? other) #region CollisionPoint - public CollisionPoint GetAverageCollisionPoint() + public CollisionPoint GetCombinedCollisionPoint() { var avgPoint = new Vector2(); var avgNormal = new Vector2(); From 694c27b94762195bf5daa01b7ab66be41366e8a4 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:12:39 +0100 Subject: [PATCH 17/18] IntersectSpaceRegister updated. --- ShapeEngine/Core/CollisionSystem/IntersectSpaceRegister.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ShapeEngine/Core/CollisionSystem/IntersectSpaceRegister.cs b/ShapeEngine/Core/CollisionSystem/IntersectSpaceRegister.cs index 94623358..2f8915f6 100644 --- a/ShapeEngine/Core/CollisionSystem/IntersectSpaceRegister.cs +++ b/ShapeEngine/Core/CollisionSystem/IntersectSpaceRegister.cs @@ -37,7 +37,7 @@ public CollisionPoint GetAverageCollisionPoint() var avgNormal = new Vector2(); foreach (var entry in this) { - var sum = entry.GetAverageCollisionPoint(); + var sum = entry.GetCombinedCollisionPoint(); avgPoint += sum.Point; avgNormal += sum.Normal; } From 064ffbbcedb280090e95f10f32e63f043686137a Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:19:41 +0100 Subject: [PATCH 18/18] NugetPackage release notes updated. --- ShapeEngine/ShapeEngine.csproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ShapeEngine/ShapeEngine.csproj b/ShapeEngine/ShapeEngine.csproj index 0b16cfbd..88560fd9 100644 --- a/ShapeEngine/ShapeEngine.csproj +++ b/ShapeEngine/ShapeEngine.csproj @@ -50,6 +50,14 @@ - CollisionInformation Improvements: + CollisionInformation now includes a FirstContact member, reported only for the two involved CollisionObjects, separate from any FirstContact reporting between Colliders. + General improvements and cleanup of CollisionInformation, including new functions. + + New functions for Exists, Find, and FindAll CollisionPoints. + + New functions for finding Closest, Furthest, and Combined CollisionPoint. + - Collision Improvements: + + Functions for Exists, Find, and FindAll CollisionPoints added. + - CollisionPoints: + + GetClosest/Furthest/FacingTowards functions now skip invalid collision points + - Renaming: + + GetAverageCollisionPoint() renamed to GetCombinedCollisionPoint() in multiple classes. - ResolveCollision Update: + CollisionObject.ResolveCollision() is now called for each generated CollisionInformation instead of receiving a list of CollisionInformation.