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

A UnionFind (disjoint set) data structure ported from Bullet physics. It stores elements with parent id and size, supports Allocate/Free/Reset, Find with path compression, Unite (union by size not strictly enforced), and sortIslands which tags each element with its root id then quicksorts elements by that id.

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 HumanoidRetargeter.Core.Formats.Mmd.Bullet.LinearMath;

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

internal class UnionFind
{
	public UnionFind()
	{
		m_elements = new ObjectArray<Element>();
	}

	public virtual void Cleanup()
	{
		Free();
	}

	//this is a special operation, destroying the content of btUnionFind.
	//it sorts the elements, based on island id, in order to make it easy to iterate over islands
	public void sortIslands()
	{
		//first store the original body index, and islandId
		int numElements = m_elements.Count;
		Element[] raw = m_elements.GetRawArray();
		for (int i = 0; i < numElements; i++)
		{
			raw[i].m_id = Find(i);
		}

		//m_elements.Sort();
		m_elements.QuickSort(m_sortPredicate);
	}

	public void Reset(int N)
	{
		Allocate(N);
		Element[] raw = m_elements.GetRawArray();
		for (int i = 0; i < N; i++)
		{
			raw[i].m_id = i;
			raw[i].m_sz = 1;
		}
	}

	public int GetNumElements()
	{
		return m_elements.Count;
	}

	public bool IsRoot(int x)
	{
		return (x == m_elements[x].m_id);
	}

	public Element GetElement(int index)
	{
		return m_elements[index];
	}

	public void SetElementSize(int index, int size)
	{
		m_elements.GetRawArray()[index].m_sz = size;
	}

	public void Allocate(int N)
	{
		m_elements.Resize(N, true);
	}

	public void Free()
	{
		m_elements.Clear();
	}

	public bool Find(int p, int q)
	{
		return (Find(p) == Find(q));
	}

	public void Unite(int p, int q)
	{
		int i = Find(p), j = Find(q);
		if (i == j)
		{
			return;
		}

		m_elements.GetRawArray()[i].m_id = j;
		m_elements.GetRawArray()[j].m_sz += m_elements[i].m_sz;
	}

	public int Find(int x)
	{
		//btAssert(x < m_N);
		//btAssert(x >= 0);
		Element[] rawElements = m_elements.GetRawArray();
		while (x != rawElements[x].m_id)
		{
			//not really a reason not to use path compression, and it flattens the trees/improves find performance dramatically
			//m_elements[x].m_id = m_elements[m_elements[x].m_id].m_id;
			Element elementPtr = rawElements[rawElements[x].m_id];
			rawElements[x].m_id = elementPtr.m_id;
			x = elementPtr.m_id;
		//btAssert(x < m_N);
		//btAssert(x >= 0);
		}

		return x;
	}

	private ObjectArray<Element> m_elements;
	private UnionFindElementSortPredicate m_sortPredicate = new UnionFindElementSortPredicate();
}