using System; using System.Collections.Generic; using System.Linq; using Chernobyl.Collections.Generic; using Chernobyl.ComponentModel; using Chernobyl.Creation; using Chernobyl.Event; using Chernobyl.Readiness; using Chernobyl.Switch; using Chernobyl.Update; using Chernobyl.Values; namespace Chernobyl.Mathematics.Collision { /// /// A type that actively checks for collisions between the /// and the instances contained /// within . /// /// The type of the /// instances that are to be checked against for /// collision with . This ICollidable type must derive /// from . /// The type that will /// be checked for collisions against the /// instances contained within /// . public class CollisionChecker : Updateable where TContainedCollidable : IExtendedCollidable { /// /// Initializes a new instance of the /// class. /// /// The objects that are to be checked for /// collision against . /// The object that is to be checked for collision /// against the objects in . public CollisionChecker(ICollidableEnumerable collidables, TCollider collider) { _previousCollidables = new LinkedList(); _currentCollidables = new LinkedList(); _startedColliding = new LinkedList(); Collider = collider; Collidables = collidables; } /// /// An event that is raised when the has began /// colliding with one or more /// instances in . Any /// that is added to this event /// will get invoked immediately if a collision is already occurring. /// public event EventHandler CollisionStarted { add { _collisionStarted += value; if (_startedColliding.Any() == true) value(this, new CollisionEventArgs(_startedColliding, Collider)); } remove { _collisionStarted -= value; } } /// /// An event that is raised when the has stopped /// colliding with one or more /// instances in that were previously being /// collided with. /// public event EventHandler CollisionEnded; /// /// The objects that are to be checked for collision against /// . /// public ICollidableEnumerable Collidables { get { return _collidables; } private set { if(value == null) throw new ArgumentNullException("value", "You must provide " + "a non-null collection of collidables."); _collidables = value; } } /// /// The object that is to be checked for collision against the objects /// in . /// public TCollider Collider { get; private set; } /// /// Figures out which items are currently colliding and which are no /// longer colliding. The events and /// as needed from this method. /// /// The amount of time that has /// passed since this update was last called. public override void Update(TimeSpan deltaTime) { // get our collision point and update our collidable with this new // point IEnumerable collidables = Collidables.GetCollidables(Collider); // Inform both sides of the collision, collect the items that have // just started colliding with each other, and removed _startedColliding.Clear(); foreach (TContainedCollidable coll in collidables) { if (_previousCollidables.Remove(coll) == false) { // Note that we could perform the ICollidable.HandleCollisionStarted(ICollidable) // but that would create problems if a user removed an instance // from Collidables while we are iterating through it. So we // invoke those method later in this method. _startedColliding.AddLast(coll); } // Save this collidable for the next update. _currentCollidables.AddLast(coll); } // Let client code know of the newly hit items. if (_startedColliding.Any() == true) { foreach (TContainedCollidable collidable in _startedColliding) { // we have never hit this collidable; call the start of the // collision handling and add it to our previous collidables // list Collidables.HandleCollisionStarted(collidable); collidable.HandleCollisionStarted(Collidables); } if(_collisionStarted != null) _collisionStarted(this, new CollisionEventArgs(_startedColliding, Collider)); } // Any collidables left were not hit this time around so we handle // the collision ending and remove them from our previous collidables // list if (_previousCollidables.Any() == true) { if(CollisionEnded != null) CollisionEnded(this, new CollisionEventArgs(_previousCollidables, Collider)); foreach (ICollidable2D coll in _previousCollidables) { Collidables.HandleCollisionEnded(coll); coll.HandleCollisionEnded(Collidables); } } // now update our previous collidable list with our current collidables _previousCollidables.AddRange(_currentCollidables); _currentCollidables.Clear(); // The base class will update the children. base.Update(deltaTime); } /// /// An that contains data regarding collision. /// public class CollisionEventArgs : EventArgs { /// /// Initializes a new instance of the /// /// class. /// /// The instances that were involved in the /// collision. /// The instance that collided with the /// . public CollisionEventArgs(IEnumerable collidables, TCollider collider) { Collider = collider; Collidables = collidables; } /// /// The instances that were involved in the collision. /// public IEnumerable Collidables { get; private set; } /// /// The instance that collided with the . /// public TCollider Collider { get; private set; } } /// /// instances that were hit /// previously and may need to have their /// method /// invoked when they have stopped colliding. /// LinkedList _previousCollidables; /// /// The list that is used to hold the /// currently being collided with in the /// method. /// LinkedList _currentCollidables; /// /// The list of instances /// from that have just started colliding with /// . /// LinkedList _startedColliding; /// /// The backing field to . /// ICollidableEnumerable _collidables; /// /// The backing field to . /// EventHandler _collisionStarted; } }