Editor/Services/CalibrationRebaser.cs

Editor utility for rebasing and clearing weapon animation data. RebaseAnimationData transforms clip keys, binding targets and workspace overrides from a previous calibration to the document's current calibration; DiscardAnimationData clears animation tracks and resets bindings. Contains a small helper to resolve named rig controls.

Reflection
#nullable enable annotations

using System;
using System.Linq;
using Sandbox;

namespace SboxWeaponAnimator.Editor;

public static class CalibrationRebaser
{
	public static void RebaseAnimationData(
		WeaponAnimationDocument document,
		CalibrationSnapshot previous )
	{
		var oldPlacement = WeaponAnimationMath.Compose(
			previous.PhysicalTransform,
			previous.FramingTransform );
		var newPlacement = WeaponAnimationMath.Compose(
			document.Calibration.PhysicalTransform,
			document.Calibration.FramingTransform );

		foreach ( var clip in document.Clips )
		{
			foreach ( var track in clip.Tracks )
			{
				var isRoot = track.Target.Equals( document.Rig.RootBone, StringComparison.OrdinalIgnoreCase )
					|| track.Target.Equals( "weapon_root", StringComparison.OrdinalIgnoreCase );
				var isWorldControl = track.Target.StartsWith( "@" )
					&& ResolveControl( document, track.Target ) is { AttachedBone.Length: 0 };
				var isCamera = track.Kind == RigControlKind.Camera;
				if ( !isRoot && !isWorldControl && !isCamera )
					continue;

				foreach ( var key in track.Keys )
				{
					var original = new Transform( key.Position, key.Rotation, key.Scale );
					var relative = oldPlacement.ToLocal( original );
					var rebased = new Transform(
						newPlacement.PointToWorld( relative.Position ),
						newPlacement.Rotation * relative.Rotation,
						newPlacement.Scale * relative.Scale );
					key.Position = rebased.Position;
					key.Rotation = rebased.Rotation;
					key.Scale = rebased.Scale;
				}
			}
		}

		foreach ( var target in new[]
		{
			document.Binding.PrimaryHand,
			document.Binding.SupportHand,
			document.Binding.PrimaryElbowPole,
			document.Binding.SupportElbowPole
		}.Where( x => string.IsNullOrWhiteSpace( x.AttachedBone ) ) )
		{
			var relative = oldPlacement.ToLocal( target.Transform );
			target.Transform = new Transform(
				newPlacement.PointToWorld( relative.Position ),
				newPlacement.Rotation * relative.Rotation,
				newPlacement.Scale * relative.Scale );
		}

		foreach ( var working in document.Workspace.WorkingPoseOverrides )
		{
			var isRoot = working.Target.Equals( document.Rig.RootBone, StringComparison.OrdinalIgnoreCase )
				|| working.Target.Equals( "weapon_root", StringComparison.OrdinalIgnoreCase );
			var isWorldControl = working.Target.StartsWith( "@", StringComparison.Ordinal )
				&& ResolveControl( document, working.Target ) is { AttachedBone.Length: 0 };
			if ( !isRoot && !isWorldControl && working.Kind != RigControlKind.Camera )
				continue;

			var relative = oldPlacement.ToLocal( working.Transform );
			working.Transform = new Transform(
				newPlacement.PointToWorld( relative.Position ),
				newPlacement.Rotation * relative.Rotation,
				newPlacement.Scale * relative.Scale );
		}
	}

	public static void DiscardAnimationData( WeaponAnimationDocument document )
	{
		foreach ( var clip in document.Clips )
		{
			clip.Tracks.Clear();
			clip.VisibilityTracks.Clear();
			clip.Constraints.Clear();
			clip.Tags.Clear();
			clip.ParameterEvents.Clear();
			clip.ImportedSequence = "";
			clip.Readiness = ClipReadiness.NotStarted;
		}

		document.Binding = new ArmBindingDefinition();
		document.Workspace.WorkingPoseOverrides.Clear();
	}

	private static RigTarget? ResolveControl( WeaponAnimationDocument document, string name ) => name switch
	{
		"@primary_hand" => document.Binding.PrimaryHand,
		"@support_hand" => document.Binding.SupportHand,
		"@primary_elbow" => document.Binding.PrimaryElbowPole,
		"@support_elbow" => document.Binding.SupportElbowPole,
		_ => null
	};
}