Code/HumanoidRetargeter/Core/Formats/Mmd/Bullet/BulletCollision/HashedOverlappingPairCache.cs

HashedOverlappingPairCache, an internal C# port of Bullet's overlapping-pair hash table used by the broadphase collision system. It stores and manages BroadphasePair entries, adds/removes pairs, handles hashing/growing the hash table, iterates and processes pairs, and interacts with overlap filter and ghost callbacks.

Native Interop
/*
 * C# / XNA  port of Bullet (c) 2011 Mark Neale <[email protected]>
 *
 * Bullet Continuous Collision Detection and Physics Library
 * Copyright (c) 2003-2008 Erwin Coumans  http://www.bulletphysics.com/
 *
 * This software is provided 'as-is', without any express or implied warranty.
 * In no event will the authors be held liable for any damages arising from
 * the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 */
// Adapted from BulletXNA 73b2e1b9: namespace isolation, no XNA rendering, one type per file.
#nullable disable
#pragma warning disable CS1591, CS0168, CS0219, CS0414, CS0660, CS0661, CS0659, CS0693, CS8981, CS0108, CS0114
using System;
using System.Diagnostics;
using HumanoidRetargeter.Core.Formats.Mmd.Bullet.LinearMath;

namespace HumanoidRetargeter.Core.Formats.Mmd.Bullet.BulletCollision;

internal class HashedOverlappingPairCache : IOverlappingPairCache
{
	public HashedOverlappingPairCache()
	{
		m_overlapFilterCallback = null;
		m_blockedForChanges = false;
		m_ghostPairCallback = null;
		int initialAllocatedSize = 2;
		m_overlappingPairArray = new ObjectArray<BroadphasePair>(initialAllocatedSize);
		GrowTables();
	}

	public virtual void Cleanup()
	{
	}

	public virtual void RemoveOverlappingPairsContainingProxy(BroadphaseProxy proxy, IDispatcher dispatcher)
	{
		RemovePairCallback removeCallback = new RemovePairCallback(proxy);
		ProcessAllOverlappingPairs(removeCallback, dispatcher);
	}

	public virtual Object RemoveOverlappingPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1, IDispatcher dispatcher)
	{
		OverlappingPairCacheGlobals.gRemovePairs++;
		if (proxy0.m_uniqueId > proxy1.m_uniqueId)
		{
			BroadphaseProxy temp = proxy0;
			proxy0 = proxy1;
			proxy1 = temp;
		}

		int proxyId1 = proxy0.GetUid();
		int proxyId2 = proxy1.GetUid();
		int hash = (int)(GetHash((uint)(proxyId1), (uint)(proxyId2)) & (m_overlappingPairArray.Capacity - 1));
		BroadphasePair pair = InternalFindPair(proxy0, proxy1, hash);
		if (pair == null)
		{
			return null;
		}

		CleanOverlappingPair(pair, dispatcher);
		Object userData = pair.m_internalInfo1;
		{
		}

		{
		}

		//int pairIndex = m_overlappingPairArray.IndexOf(pair);
		// we've already found this.
		int pairIndex = pair.m_index;
		{
		}

		// Remove the pair from the hash table.
		int index = m_hashTable[hash];
		{
		}

		int previous = BT_NULL_PAIR;
		while (index != pairIndex)
		{
			previous = index;
			index = m_next[index];
		}

		if (previous != BT_NULL_PAIR)
		{
			{
			}

			m_next[previous] = m_next[pairIndex];
		}
		else
		{
			m_hashTable[hash] = m_next[pairIndex];
		}

		// We now move the last pair into spot of the
		// pair being removed. We need to fix the hash
		// table indices to support the move.
		int lastPairIndex = m_overlappingPairArray.Count - 1;
		if (m_ghostPairCallback != null)
		{
			m_ghostPairCallback.RemoveOverlappingPair(proxy0, proxy1, dispatcher);
		}

		// If the removed pair is the last pair, we are done.
		if (lastPairIndex == pairIndex)
		{
			m_overlappingPairArray.RemoveAt(lastPairIndex);
			return userData;
		}

		// Remove the last pair from the hash table.
		BroadphasePair last = m_overlappingPairArray[lastPairIndex];
		/* missing swap here too, Nat. */
		int lastHash = (int)(GetHash((uint)(last.m_pProxy0.GetUid()), (uint)(last.m_pProxy1.GetUid())) & (m_overlappingPairArray.Capacity - 1));
		index = m_hashTable[lastHash];
		{
		}

		previous = BT_NULL_PAIR;
		while (index != lastPairIndex)
		{
			previous = index;
			index = m_next[index];
		}

		if (previous != BT_NULL_PAIR)
		{
			{
			}

			m_next[previous] = m_next[lastPairIndex];
		}
		else
		{
			m_hashTable[lastHash] = m_next[lastPairIndex];
		}

		// Copy the last pair into the remove pair's spot.
		m_overlappingPairArray[pairIndex] = m_overlappingPairArray[lastPairIndex];
		// Insert the last pair into the hash table
		m_next[pairIndex] = m_hashTable[lastHash];
		m_hashTable[lastHash] = pairIndex;
		m_overlappingPairArray.RemoveAt(lastPairIndex);
		return userData;
	}

	public bool NeedsBroadphaseCollision(BroadphaseProxy proxy0, BroadphaseProxy proxy1)
	{
		if (m_overlapFilterCallback != null)
		{
			return m_overlapFilterCallback.NeedBroadphaseCollision(proxy0, proxy1);
		}

		bool collides = (proxy0.m_collisionFilterGroup & proxy1.m_collisionFilterMask) != 0;
		collides = collides && ((proxy1.m_collisionFilterGroup & proxy0.m_collisionFilterMask) != 0);
		return collides;
	}

	// Add a pair and return the new pair. If the pair already exists,
	// no new pair is created and the old one is returned.
	public virtual BroadphasePair AddOverlappingPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1)
	{
		OverlappingPairCacheGlobals.gAddedPairs++;
		if (!NeedsBroadphaseCollision(proxy0, proxy1))
		{
			return null;
		}

		return InternalAddPair(proxy0, proxy1);
	}

	public void CleanProxyFromPairs(BroadphaseProxy proxy, IDispatcher dispatcher)
	{
		CleanPairCallback cleanPairs = new CleanPairCallback(proxy, this, dispatcher);
		ProcessAllOverlappingPairs(cleanPairs, dispatcher);
	}

	public virtual void ProcessAllOverlappingPairs(IOverlapCallback callback, IDispatcher dispatcher)
	{
		for (int i = 0; i < m_overlappingPairArray.Count;)
		{
			BroadphasePair pair = m_overlappingPairArray[i];
			if (callback.ProcessOverlap(pair))
			{
				RemoveOverlappingPair(pair.m_pProxy0, pair.m_pProxy1, dispatcher);
				OverlappingPairCacheGlobals.gOverlappingPairs--;
			}
			else
			{
				i++;
			}
		}
	}

	public ObjectArray<BroadphasePair> GetOverlappingPairArray()
	{
		return m_overlappingPairArray;
	}

	public void CleanOverlappingPair(BroadphasePair pair, IDispatcher dispatcher)
	{
		if (pair.m_algorithm != null)
		{
			dispatcher.FreeCollisionAlgorithm(pair.m_algorithm);
			pair.m_algorithm = null;
		}
	}

	public BroadphasePair FindPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1)
	{
		OverlappingPairCacheGlobals.gFindPairs++;
		if (proxy0.m_uniqueId > proxy1.m_uniqueId)
		{
			BroadphaseProxy temp;
			temp = proxy0;
			proxy0 = proxy1;
			proxy1 = temp;
		}

		int proxyId1 = proxy0.GetUid();
		int proxyId2 = proxy1.GetUid();
		/*if (proxyId1 > proxyId2)
                btSwap(proxyId1, proxyId2);*/
		int hash = (int)(GetHash((uint)(proxyId1), (uint)(proxyId2)) & (m_overlappingPairArray.Capacity - 1));
		if (hash >= m_hashTable.Count)
		{
			return null;
		}

		int index = m_hashTable[hash];
		while (index != BT_NULL_PAIR && EqualsPair(m_overlappingPairArray[index], proxyId1, proxyId2) == false)
		{
			index = m_next[index];
		}

		if (index == BT_NULL_PAIR)
		{
			return null;
		}

		{
		}

		return m_overlappingPairArray[index];
	}

	public int GetCount()
	{
		return m_overlappingPairArray.Count;
	}

	//	btBroadphasePair* GetPairs() { return m_pairs; }
	public IOverlapFilterCallback GetOverlapFilterCallback()
	{
		return m_overlapFilterCallback;
	}

	public void SetOverlapFilterCallback(IOverlapFilterCallback callback)
	{
		m_overlapFilterCallback = callback;
	}

	public int GetNumOverlappingPairs()
	{
		return m_overlappingPairArray.Count;
	}

	private BroadphasePair InternalAddPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1)
	{
		if (proxy0.m_uniqueId > proxy1.m_uniqueId)
		{
			BroadphaseProxy temp = proxy0;
			proxy0 = proxy1;
			proxy1 = temp;
		}

		int proxyId1 = proxy0.GetUid();
		int proxyId2 = proxy1.GetUid();
		int hash = (int)(GetHash((uint)proxyId1, (uint)proxyId2) & (m_overlappingPairArray.Capacity - 1)); // New hash value with new mask
		BroadphasePair pair = InternalFindPair(proxy0, proxy1, hash);
		if (pair != null)
		{
			return pair;
		}
		else
		{
			/*for(int i=0;i<m_overlappingPairArray.size();++i)
                    {
                    if(	(m_overlappingPairArray[i].m_pProxy0==proxy0)&&
                        (m_overlappingPairArray[i].m_pProxy1==proxy1))
                        {
                        printf("Adding duplicated %u<>%u\r\n",proxyId1,proxyId2);
                        internalFindPair(proxy0, proxy1, hash);
                        }
                    }*/
			int count = m_overlappingPairArray.Count;
			int oldCapacity = m_overlappingPairArray.Capacity;
			// MAN - 2.76 - uses expand noninitializing....??
			//void* mem = &m_overlappingPairArray.expand();
			//this is where we add an actual pair, so also call the 'ghost'
			if (m_ghostPairCallback != null)
			{
				m_ghostPairCallback.AddOverlappingPair(proxy0, proxy1);
			}

			pair = new BroadphasePair(proxy0, proxy1);
			m_overlappingPairArray.Add(pair);
			int newCapacity = m_overlappingPairArray.Capacity;
			if (oldCapacity < newCapacity)
			{
				GrowTables();
				//hash with new capacity
				hash = (int)(GetHash((uint)(proxyId1), (uint)(proxyId2)) & (m_overlappingPairArray.Capacity - 1));
			}

			m_next[count] = m_hashTable[hash];
			m_hashTable[hash] = count;
			return pair;
		}
	}

	private void GrowTables()
	{
		int newCapacity = m_overlappingPairArray.Capacity;
		if (m_hashTable.Capacity < newCapacity)
		{
			int curHashTableSize = m_hashTable.Count;
			m_hashTable.Capacity = newCapacity;
			m_next.Capacity = newCapacity;
			for (int i = 0; i < newCapacity; ++i)
			{
				m_hashTable[i] = BT_NULL_PAIR;
			}

			for (int i = 0; i < newCapacity; ++i)
			{
				m_next[i] = BT_NULL_PAIR;
			}

			for (int i = 0; i < curHashTableSize; i++)
			{
				BroadphasePair pair = m_overlappingPairArray[i];
				int proxyId1 = pair.m_pProxy0.GetUid();
				int proxyId2 = pair.m_pProxy1.GetUid();
				/*if (proxyId1 > proxyId2)
                        btSwap(proxyId1, proxyId2);*/
				int hashValue = (int)(GetHash((uint)(proxyId1), (uint)(proxyId2)) & (m_overlappingPairArray.Capacity - 1)); // New hash value with new mask
				m_next[i] = m_hashTable[hashValue];
				m_hashTable[hashValue] = i;
			}

			int ibreak = 0;
		}
	}

	private bool EqualsPair(BroadphasePair pair, int proxyId1, int proxyId2)
	{
		return pair.m_pProxy0.m_uniqueId == proxyId1 && pair.m_pProxy1.m_uniqueId == proxyId2;
	}

	/*
        // Thomas Wang's hash, see: http://www.concentric.net/~Ttwang/tech/inthash.htm
        // This assumes proxyId1 and proxyId2 are 16-bit.
        SIMD_FORCE_INLINE int getHash(int proxyId1, int proxyId2)
        {
            int key = (proxyId2 << 16) | proxyId1;
            key = ~key + (key << 15);
            key = key ^ (key >> 12);
            key = key + (key << 2);
            key = key ^ (key >> 4);
            key = key * 2057;
            key = key ^ (key >> 16);
            return key;
        }
        */
	private uint GetHash(uint proxyId1, uint proxyId2)
	{
		int key = (int)(((uint)proxyId1) | ((uint)proxyId2) << 16);
		// Thomas Wang's hash
		key += ~(key << 15);
		key ^= (key >> 10);
		key += (key << 3);
		key ^= (key >> 6);
		key += ~(key << 11);
		key ^= (key >> 16);
		return (uint)(key);
	}

	public BroadphasePair InternalFindPair(BroadphaseProxy proxy0, BroadphaseProxy proxy1, int hash)
	{
		BroadphasePair[] rawPairArray = m_overlappingPairArray.GetRawArray();
		int proxyId1 = proxy0.GetUid();
		int proxyId2 = proxy1.GetUid();
		//#if 0 // wrong, 'equalsPair' use unsorted uids, copy-past devil striked again. Nat.
		//if (proxyId1 > proxyId2)
		//    btSwap(proxyId1, proxyId2);
		//#endif
		int index = m_hashTable[hash];
		while (index != BT_NULL_PAIR && EqualsPair(rawPairArray[index], proxyId1, proxyId2) == false)
		{
			index = m_next[index];
		}

		if (index == BT_NULL_PAIR)
		{
			return null;
		}

		//btAssert(index < m_overlappingPairArray.size());
		// if we know this then don't we don't need to look it up again..
		rawPairArray[index].m_index = index;
		return rawPairArray[index];
	}

	public virtual bool HasDeferredRemoval()
	{
		return false;
	}

	public virtual void SetInternalGhostPairCallback(IOverlappingPairCallback ghostPairCallback)
	{
		m_ghostPairCallback = ghostPairCallback;
	}

	public virtual void SortOverlappingPairs(IDispatcher dispatcher)
	{
		ObjectArray<BroadphasePair> tmpPairs = new ObjectArray<BroadphasePair>();
		tmpPairs.AddRange(m_overlappingPairArray);
		for (int i = 0; i < tmpPairs.Count; i++)
		{
			RemoveOverlappingPair(tmpPairs[i].m_pProxy0, tmpPairs[i].m_pProxy1, dispatcher);
		}

		for (int i = 0; i < m_next.Count; i++)
		{
			m_next[i] = BT_NULL_PAIR;
		}

		tmpPairs.Sort();
		//tmpPairs.quickSort(btBroadphasePairSortPredicate());
		for (int i = 0; i < tmpPairs.Count; i++)
		{
			AddOverlappingPair(tmpPairs[i].m_pProxy0, tmpPairs[i].m_pProxy1);
		}
	}

	//protected int[]	m_hashTable;
	//protected int[] m_next;
	protected ObjectArray<int> m_hashTable = new ObjectArray<int>();
	protected ObjectArray<int> m_next = new ObjectArray<int>();
	protected IOverlappingPairCallback m_ghostPairCallback;
	private ObjectArray<BroadphasePair> m_overlappingPairArray;
	private IOverlapFilterCallback m_overlapFilterCallback;
	private bool m_blockedForChanges;
	const int BT_NULL_PAIR = -1;
	private static Object NULL_PAIR = new Object();
}