BoBiCo Soft-Physics/SoftBoneCollisionRegistry.cs

Registry for soft-bone collision shapes. It stores published per-chain chain collider snapshots and collects shapes each physics step, filters out culled renderers, and provides APIs to publish, remove and gather colliders for chain-vs-chain and body-vs-body collision queries.

File Access
using System.Collections.Generic;
using Sandbox;
using System.Linq;


public readonly struct SoftBoneChainColliderSnapshot
{
	public readonly SkinnedModelRenderer Renderer;
	public readonly SoftBoneColliderShape Shape;

	public SoftBoneChainColliderSnapshot( SkinnedModelRenderer renderer, SoftBoneColliderShape shape )
	{
		Renderer = renderer;
		Shape = shape;
	}
}

public readonly struct SoftBoneBodyColliderSnapshot
{
	public readonly SkinnedModelRenderer Renderer;
	public readonly SoftBoneColliderShape Shape;

	public SoftBoneBodyColliderSnapshot( SkinnedModelRenderer renderer, SoftBoneColliderShape shape )
	{
		Renderer = renderer;
		Shape = shape;
	}
}

/// <summary>Publishes the previous solved link shapes used by cross-chain contacts.</summary>
public static class SoftBoneCollisionRegistry
{
	private static readonly Dictionary<BoBiCoSoftBoneChain, List<SoftBoneChainColliderSnapshot>> PublishedShapes = new();
	private static readonly Dictionary<BoBiCoSoftBoneChain, List<SoftBoneChainColliderSnapshot>> QueryShapes = new();
	private static readonly List<SoftBoneBodyColliderSnapshot> QueryBodyShapes = new();
	private static readonly HashSet<SkinnedModelRenderer> CulledRenderers = new();

	public static void SetRendererCulled( SkinnedModelRenderer renderer, bool culled )
	{
		if ( renderer is null || !renderer.IsValid() ) return;
		if ( culled ) CulledRenderers.Add( renderer );
		else CulledRenderers.Remove( renderer );
	}

	public static void Publish( BoBiCoSoftBoneChain chain, SkinnedModelRenderer renderer, List<SoftBoneChainColliderSnapshot> shapes )
	{
		PublishedShapes[chain] = new List<SoftBoneChainColliderSnapshot>( shapes );
	}

	public static void BeginStep( Scene scene )
	{
		QueryShapes.Clear();
		foreach ( var entry in PublishedShapes )
			QueryShapes.Add( entry.Key, new List<SoftBoneChainColliderSnapshot>( entry.Value ) );

		QueryBodyShapes.Clear();
		if ( scene is null )
			return;

		foreach ( var collider in scene.GetAllComponents<BoBiCoSoftBoneCollider>() )
		{
			if ( collider is null || !collider.Enabled )
				continue;

			var renderer = collider.GetOwningRenderer();
			if ( renderer is not null && CulledRenderers.Contains( renderer ) )
				continue;
			if ( collider.TryGetWorldShape( renderer, out var shape ) )
				QueryBodyShapes.Add( new SoftBoneBodyColliderSnapshot( renderer, shape ) );
		}
	}

	public static void Remove( BoBiCoSoftBoneChain chain )
	{
		PublishedShapes.Remove( chain );
		QueryShapes.Remove( chain );
	}

	public static void CollectChainShapes( BoBiCoSoftBoneChain owner, SkinnedModelRenderer renderer, SoftBoneCollisionMode mode, List<SoftBoneColliderShape> output )
	{
		if ( mode == SoftBoneCollisionMode.BaseCollision )
			return;

		foreach ( var entry in QueryShapes )
		{
			if ( entry.Key == owner || !entry.Key.Enabled )
				continue;

			var sameRenderer = entry.Value.Count > 0 && entry.Value[0].Renderer == renderer;
			if ( !sameRenderer && mode != SoftBoneCollisionMode.FullCollision )
				continue;

			foreach ( var snapshot in entry.Value )
				output.Add( snapshot.Shape );
		}
	}

	public static void CollectExternalBodyShapes( SkinnedModelRenderer ownerRenderer, List<SoftBoneColliderShape> output )
	{
		foreach ( var snapshot in QueryBodyShapes )
			if ( snapshot.Renderer != ownerRenderer )
				output.Add( snapshot.Shape );
	}
}