#if COM_UNITY_MODULES_PHYSICS using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; using UnityEngine; namespace Unity.Netcode.Components { /// /// Information a returns to via
/// if the registers itself with as opposed to . ///
public struct ContactEventHandlerInfo { /// /// When set to true, the will include non-Rigidbody based contact events.
/// When the invokes the it will return null in place
/// of the collidingBody parameter if the contact event occurred with a collider that is not registered with the . ///
public bool ProvideNonRigidBodyContactEvents; /// /// When set to true, the will prioritize invoking

/// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a is owned by the local client
/// will assure is only invoked on the authoritative side. ///
public bool HasContactEventPriority; } /// /// Default implementation required to register a with a instance. /// /// /// Recommended to implement this method on a component /// public interface IContactEventHandler { /// /// Should return a . /// Rigidbody GetRigidbody(); /// /// Invoked by the instance. /// /// A unique contact event identifier. /// The average normal of the collision between two colliders. /// If not null, this will be a registered that was part of the collision contact event. /// The world space location of the contact event. /// Will be set if this is a collision stay contact event (i.e. it is not the first contact event and continually has contact) /// The average normal of the collision stay contact over time. void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default); } /// /// This is an extended version of and can be used to register a with a instance.
/// This provides additional information to the for each set of contact events it is processing. ///
public interface IContactEventHandlerWithInfo : IContactEventHandler { /// /// Invoked by for each set of contact events it is processing (prior to processing). /// /// ContactEventHandlerInfo GetContactEventHandlerInfo(); } /// /// Add this component to an in-scene placed GameObject to provide faster collision event processing between instances and optionally static colliders. ///
///
///
///
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")] public class RigidbodyContactEventManager : MonoBehaviour { public static RigidbodyContactEventManager Instance { get; private set; } private struct JobResultStruct { public bool HasCollisionStay; public int ThisInstanceID; public int OtherInstanceID; public Vector3 AverageNormal; public Vector3 AverageCollisionStayNormal; public Vector3 ContactPoint; } private NativeArray m_ResultsArray; private int m_Count = 0; private JobHandle m_JobHandle; private readonly Dictionary m_RigidbodyMapping = new Dictionary(); private readonly Dictionary m_HandlerMapping = new Dictionary(); private readonly Dictionary m_HandlerInfo = new Dictionary(); private void OnEnable() { m_ResultsArray = new NativeArray(16, Allocator.Persistent); Physics.ContactEvent += Physics_ContactEvent; if (Instance != null) { NetworkLog.LogError($"[Invalid][Multiple Instances] Found more than one instance of {nameof(RigidbodyContactEventManager)}: {name} and {Instance.name}"); NetworkLog.LogError($"[Disable][Additional Instance] Disabling {name} instance!"); gameObject.SetActive(false); return; } Instance = this; } /// /// Any implementation can register a to be handled by this instance. /// /// /// You should enable for each associated with the being registered.
/// You can enable this during run time or within the editor's inspector view. ///
/// or /// true to register and false to remove from being registered public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true) { var rigidbody = contactEventHandler.GetRigidbody(); var instanceId = rigidbody.GetInstanceID(); if (register) { if (!m_RigidbodyMapping.ContainsKey(instanceId)) { m_RigidbodyMapping.Add(instanceId, rigidbody); } if (!m_HandlerMapping.ContainsKey(instanceId)) { m_HandlerMapping.Add(instanceId, contactEventHandler); } if (!m_HandlerInfo.ContainsKey(instanceId)) { var handlerInfo = new ContactEventHandlerInfo() { HasContactEventPriority = true, ProvideNonRigidBodyContactEvents = false, }; var handlerWithInfo = contactEventHandler as IContactEventHandlerWithInfo; if (handlerWithInfo != null) { handlerInfo = handlerWithInfo.GetContactEventHandlerInfo(); } m_HandlerInfo.Add(instanceId, handlerInfo); } } else { m_RigidbodyMapping.Remove(instanceId); m_HandlerMapping.Remove(instanceId); } } private void OnDisable() { m_JobHandle.Complete(); m_ResultsArray.Dispose(); Physics.ContactEvent -= Physics_ContactEvent; m_RigidbodyMapping.Clear(); Instance = null; } private bool m_HasCollisions; private int m_CurrentCount = 0; private void ProcessCollisions() { foreach (var contactEventHandler in m_HandlerMapping) { var handlerWithInfo = contactEventHandler.Value as IContactEventHandlerWithInfo; if (handlerWithInfo != null) { m_HandlerInfo[contactEventHandler.Key] = handlerWithInfo.GetContactEventHandlerInfo(); } else { var info = m_HandlerInfo[contactEventHandler.Key]; info.HasContactEventPriority = !m_RigidbodyMapping[contactEventHandler.Key].isKinematic; m_HandlerInfo[contactEventHandler.Key] = info; } } ContactEventHandlerInfo contactEventHandlerInfo0; ContactEventHandlerInfo contactEventHandlerInfo1; // Process all collisions for (int i = 0; i < m_Count; i++) { var thisInstanceID = m_ResultsArray[i].ThisInstanceID; var otherInstanceID = m_ResultsArray[i].OtherInstanceID; var contactHandler0 = (IContactEventHandler)null; var contactHandler1 = (IContactEventHandler)null; var preferredContactHandler = (IContactEventHandler)null; var preferredContactHandlerNonRigidbody = false; var preferredRigidbody = (Rigidbody)null; var otherContactHandler = (IContactEventHandler)null; var otherRigidbody = (Rigidbody)null; var otherContactHandlerNonRigidbody = false; if (m_RigidbodyMapping.ContainsKey(thisInstanceID)) { contactHandler0 = m_HandlerMapping[thisInstanceID]; contactEventHandlerInfo0 = m_HandlerInfo[thisInstanceID]; if (contactEventHandlerInfo0.HasContactEventPriority) { preferredContactHandler = contactHandler0; preferredContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents; preferredRigidbody = m_RigidbodyMapping[thisInstanceID]; } else { otherContactHandler = contactHandler0; otherContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents; otherRigidbody = m_RigidbodyMapping[thisInstanceID]; } } if (m_RigidbodyMapping.ContainsKey(otherInstanceID)) { contactHandler1 = m_HandlerMapping[otherInstanceID]; contactEventHandlerInfo1 = m_HandlerInfo[otherInstanceID]; if (contactEventHandlerInfo1.HasContactEventPriority && preferredContactHandler == null) { preferredContactHandler = contactHandler1; preferredContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents; preferredRigidbody = m_RigidbodyMapping[otherInstanceID]; } else { otherContactHandler = contactHandler1; otherContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents; otherRigidbody = m_RigidbodyMapping[otherInstanceID]; } } if (preferredContactHandler == null && otherContactHandler != null) { preferredContactHandler = otherContactHandler; preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody; preferredRigidbody = otherRigidbody; otherContactHandler = null; otherContactHandlerNonRigidbody = false; otherRigidbody = null; } if (preferredContactHandler == null || (preferredContactHandler != null && otherContactHandler == null && !preferredContactHandlerNonRigidbody)) { continue; } if (m_ResultsArray[i].HasCollisionStay) { preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal); } else { preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint); } } } private void FixedUpdate() { // Only process new collisions if (!m_HasCollisions && m_CurrentCount == 0) { return; } // This assures we won't process the same collision // set after it has been processed. if (m_HasCollisions) { m_CurrentCount = m_Count; m_HasCollisions = false; m_JobHandle.Complete(); } ProcessCollisions(); } private void LateUpdate() { m_CurrentCount = 0; } private ulong m_EventId; private void Physics_ContactEvent(PhysicsScene scene, NativeArray.ReadOnly pairHeaders) { m_EventId++; m_HasCollisions = true; int n = pairHeaders.Length; if (m_ResultsArray.Length < n) { m_ResultsArray.Dispose(); m_ResultsArray = new NativeArray(Mathf.NextPowerOfTwo(n), Allocator.Persistent); } m_Count = n; var job = new GetCollisionsJob() { PairedHeaders = pairHeaders, ResultsArray = m_ResultsArray }; m_JobHandle = job.Schedule(n, 256); } private struct GetCollisionsJob : IJobParallelFor { [ReadOnly] public NativeArray.ReadOnly PairedHeaders; public NativeArray ResultsArray; public void Execute(int index) { Vector3 averageNormal = Vector3.zero; Vector3 averagePoint = Vector3.zero; Vector3 averageCollisionStay = Vector3.zero; int count = 0; int collisionStaycount = 0; int positionCount = 0; for (int j = 0; j < PairedHeaders[index].pairCount; j++) { ref readonly var pair = ref PairedHeaders[index].GetContactPair(j); if (pair.isCollisionExit) { continue; } for (int k = 0; k < pair.contactCount; k++) { ref readonly var contact = ref pair.GetContactPoint(k); averagePoint += contact.position; positionCount++; if (!pair.isCollisionStay) { averageNormal += contact.normal; count++; } else { averageCollisionStay += contact.normal; collisionStaycount++; } } } if (count != 0) { averageNormal /= count; } if (collisionStaycount != 0) { averageCollisionStay /= collisionStaycount; } if (positionCount != 0) { averagePoint /= positionCount; } var result = new JobResultStruct() { ThisInstanceID = PairedHeaders[index].bodyInstanceID, OtherInstanceID = PairedHeaders[index].otherBodyInstanceID, AverageNormal = averageNormal, HasCollisionStay = collisionStaycount != 0, AverageCollisionStayNormal = averageCollisionStay, ContactPoint = averagePoint }; ResultsArray[index] = result; } } } } #endif