using UnityEngine; using UnityEngine.Events; using System; using System.Collections.Generic; namespace Obi { [RequireComponent(typeof(ObiSolver))] public class ObiContactEventDispatcher : MonoBehaviour { private ObiSolver solver; private Oni.Contact[] prevData; private int prevCount; private ContactComparer comparer; private class ContactComparer : IComparer { ObiSolver solver; public ContactComparer(ObiSolver solver) { this.solver = solver; } public int Compare(Oni.Contact x, Oni.Contact y) { return CompareByRef(ref x, ref y, solver); } } private static int CompareByRef(ref Oni.Contact a, ref Oni.Contact b, ObiSolver solver) { if (a.bodyB == b.bodyB) { int hashA = solver.particleToActor[a.bodyA].actor.GetInstanceID(); int hashB = solver.particleToActor[b.bodyA].actor.GetInstanceID(); return hashA.CompareTo(hashB); } return a.bodyB.CompareTo(b.bodyB); } [System.Serializable] public class ContactCallback : UnityEvent { } public float distanceThreshold = 0.01f; public ContactCallback onContactEnter = new ContactCallback(); public ContactCallback onContactStay = new ContactCallback(); public ContactCallback onContactExit = new ContactCallback(); void Awake() { solver = GetComponent(); comparer = new ContactComparer(solver); prevData = new Oni.Contact[0]; } void OnEnable() { solver.OnCollision += Solver_OnCollision; } void OnDisable() { solver.OnCollision -= Solver_OnCollision; } private int FilterOutDistantContacts(Oni.Contact[] data, int count) { int filteredCount = count; // simply iterate trough all contacts, // moving the ones above the threshold to the end of the array: for (int i = count - 1; i >= 0; --i) if (data[i].distance > distanceThreshold) ObiUtils.Swap(ref data[i], ref data[--filteredCount]); return filteredCount; } private int RemoveDuplicates(Oni.Contact[] data, int count) { if (count == 0) return 0; // assuming the array is sorted, iterate trough the array // replacing duplicates by the first contact that's different: int i = 0, r = 0; while (++i != count) if (CompareByRef(ref data[i], ref data[r], solver) != 0 && ++r != i) data[r] = data[i]; return ++r; } private void InvokeCallbacks(Oni.Contact[] data, int count) { int a = 0, b = 0; int lengthA = count, lengthB = prevCount; // while we haven't reached the end of either array: while (a < lengthA && b < lengthB) { // compare both contacts: int compare = CompareByRef(ref data[a], ref prevData[b], solver); // call the appropiate event depending on the comparison result: if (compare < 0) onContactEnter.Invoke(solver, data[a++]); else if (compare > 0) onContactExit.Invoke(solver, prevData[b++]); else { onContactStay.Invoke(solver, data[a++]); b++; } } // finish iterating trough both lists: while (a < lengthA) onContactEnter.Invoke(solver, data[a++]); while (b < lengthB) onContactExit.Invoke(solver, prevData[b++]); } void Solver_OnCollision(object sender, ObiNativeContactList contacts) { // here we access the internal backing array (Data) directly, // instead of using the accessor property. This slightly improves performance. // note: the backing array length is the lists' capacity, so we // need to use args.contacts.Count to get the actual number of contacts. // skip all contacts above the distance threshold by moving them to the end of the array: /*int filteredCount = FilterOutDistantContacts(contacts, args.contacts.Count); // sort the remaining contacts by collider, then by actor: Array.Sort(args.contacts.Data, 0, filteredCount, comparer); // remove duplicates: filteredCount = RemoveDuplicates(args.contacts.Data, filteredCount); // zip trough the current and previous contact lists once, invoking events when appropiate. InvokeCallbacks(args.contacts.Data, filteredCount); // store current contact list/count for next frame. // could get better performance by double buffering instead of copying: if (filteredCount > prevData.Length) Array.Resize(ref prevData, filteredCount); Array.Copy(args.contacts.Data, prevData, filteredCount); prevCount = filteredCount;*/ } } }