using static Sandbox.Component; namespace Sandbox; /// /// Used to abstract the listening of collision events /// class CollisionEventSystem : IDisposable { private readonly PhysicsBody _body; private readonly GameObject _gameObject; public Action OnCollisionStart; public Action OnCollisionStop; public Action OnCollisionUpdate; public IReadOnlyCollection Touching => _touchingColliders is null ? Array.Empty() : _touchingColliders; private record struct TouchingPair( Collider Self, Collider Other, GameObject GameObject ); private HashSet _touchingPairs; private HashSet _touchingColliders; private HashSet _touchingObjects; private HashSet TouchingPairs => _touchingPairs ??= new(); private HashSet TouchingColliders => _touchingColliders ??= new(); private HashSet TouchingObjects => _touchingObjects ??= new(); public CollisionEventSystem( PhysicsBody body, GameObject go = null ) { _body = body; _gameObject = go ?? body.GameObject; _body.OnIntersectionStart += OnIntersectionStart; _body.OnIntersectionEnd += OnIntersectionEnd; _body.OnIntersectionUpdate += OnIntersectionUpdate; _body.OnTriggerBegin += OnTriggerBegin; _body.OnTriggerEnd += OnTriggerEnd; } private void OnTriggerBegin( PhysicsIntersection c ) { var self = new CollisionSource( c.Self ); var other = new CollisionSource( c.Other ); var gameObject = other.Body.GameObject; OnObjectTriggerStart( self.Collider, gameObject ); OnColliderTriggerStart( self.Collider, other.Collider, gameObject ); } private void OnTriggerEnd( PhysicsIntersectionEnd c ) { var self = new CollisionSource( c.Self ); var other = new CollisionSource( c.Other ); var gameObject = other.Body.GameObject; OnColliderTriggerStop( self.Collider, other.Collider, gameObject ); if ( IsTouching( other.Component ) ) return; OnObjectTriggerStop( self.Collider, gameObject ); } private void OnIntersectionStart( PhysicsIntersection c ) { var self = new CollisionSource( c.Self ); var other = new CollisionSource( c.Other ); var o = new Collision( self, other, c.Contact ); OnCollisionStart?.Invoke( o ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnCollisionStart( o ) ); } private bool IsTouching( Component other ) { return false; } private void OnIntersectionEnd( PhysicsIntersectionEnd c ) { var self = new CollisionSource( c.Self ); var other = new CollisionSource( c.Other ); var o = new CollisionStop( self, other ); OnCollisionStop?.Invoke( o ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnCollisionStop( o ) ); } private void OnIntersectionUpdate( PhysicsIntersection c ) { var self = new CollisionSource( c.Self ); var other = new CollisionSource( c.Other ); var o = new Collision( self, other, c.Contact ); OnCollisionUpdate?.Invoke( o ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnCollisionUpdate( o ) ); } private void OnColliderTriggerStart( Collider self, Collider other, GameObject go ) { if ( self is null ) return; if ( other is null ) return; if ( !TouchingPairs.Any( x => x.Other == other ) ) { TouchingColliders.Add( other ); other.OnComponentDisabled += RemoveDeactivated; } if ( !TouchingPairs.Add( new TouchingPair( self, other, go ) ) ) return; if ( !self.IsValid() ) return; if ( !other.IsValid() ) return; if ( !self.IsTrigger ) return; self.OnTriggerEnter?.InvokeWithWarning( other ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnTriggerEnter( self, other ) ); } private void OnColliderTriggerStop( Collider self, Collider other, GameObject go ) { if ( self is null ) return; if ( other is null ) return; foreach ( var shape in self.Shapes ) { if ( other.Shapes.Any( x => shape.IsTouching( x, true ) ) ) return; } if ( !TouchingPairs.Remove( new TouchingPair( self, other, go ) ) ) return; if ( !TouchingPairs.Any( x => x.Other == other ) ) { TouchingColliders.Remove( other ); other.OnComponentDisabled -= RemoveDeactivated; } if ( !self.IsValid() ) return; if ( !other.IsValid() ) return; if ( !self.IsTrigger ) return; self.OnTriggerExit?.InvokeWithWarning( other ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnTriggerExit( self, other ) ); } private void OnObjectTriggerStart( Collider self, GameObject other ) { if ( self is null ) return; if ( other is null ) return; if ( !TouchingObjects.Add( other ) ) return; if ( !self.IsValid() ) return; if ( !other.IsValid() ) return; if ( !self.IsTrigger ) return; self.OnObjectTriggerEnter?.InvokeWithWarning( other ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnTriggerEnter( self, other ) ); } private void OnObjectTriggerStop( Collider self, GameObject other ) { if ( self is null ) return; if ( other is null ) return; if ( TouchingPairs.Any( x => x.GameObject == other ) ) return; if ( !TouchingObjects.Remove( other ) ) return; if ( !self.IsValid() ) return; if ( !other.IsValid() ) return; if ( !self.IsTrigger ) return; self.OnObjectTriggerExit?.InvokeWithWarning( other ); _gameObject.Components.ExecuteEnabledInSelfAndDescendants( x => x.OnTriggerExit( self, other ) ); } private void RemoveDeactivated() { Action actions = default; foreach ( var pair in TouchingPairs ) { if ( pair.Other.Active ) continue; actions += () => OnColliderTriggerStop( pair.Self, pair.Other, pair.GameObject ); actions += () => OnObjectTriggerStop( pair.Self, pair.GameObject ); } actions?.InvokeWithWarning(); } public void Dispose() { _touchingPairs?.Clear(); _touchingColliders?.Clear(); _touchingObjects?.Clear(); if ( !_body.IsValid() ) return; _body.OnIntersectionStart -= OnIntersectionStart; _body.OnIntersectionEnd -= OnIntersectionEnd; _body.OnIntersectionUpdate -= OnIntersectionUpdate; _body.OnTriggerBegin -= OnTriggerBegin; _body.OnTriggerEnd -= OnTriggerEnd; } }