Code/ShrimpleRagdoll.Move.cs
namespace ShrimpleRagdolls;

public partial class ShrimpleRagdoll
{
	/// <summary>
	/// Teleport all body GameObjects to their animation bone transforms
	/// </summary>
	public void MoveObjectsFromAnimations()
	{
		if ( !Renderer.IsValid() || !Renderer.SceneModel.IsValid() )
			return;

		foreach ( var body in Bodies )
		{
			if ( !body.Component.IsValid() )
				continue;

			var bone = Renderer.Model.Bones.AllBones[body.Bone];
			if ( !Renderer.TryGetBoneTransformAnimation( bone, out var targetTransform ) )
				continue;

			body.Component.WorldTransform = targetTransform;
		}
	}

	/// <summary>
	/// Kinematically move all rigidbodies toward their animation bone transforms
	/// </summary>
	public void MoveBodiesFromAnimations()
	{
		if ( !Renderer.IsValid() || !Renderer.SceneModel.IsValid() )
			return;

		foreach ( var body in Bodies )
			MoveBodyFromAnimation( body );
	}

	private void MoveBodyFromAnimation( ModelPhysics.Body body )
	{
		if ( !Renderer.IsValid() || !Renderer.SceneModel.IsValid() )
			return;

		if ( !body.Component.IsValid() || !body.Component.PhysicsBody.IsValid() )
			return;

		var bone = Renderer.Model.Bones.AllBones[body.Bone];
		if ( !Renderer.TryGetBoneTransformAnimation( bone, out var targetTransform ) )
			return;

		body.Component.PhysicsBody.SmoothMove( in targetTransform, MathF.Max( ActiveLerpTime, Time.Delta ), Time.Delta );
	}

	/// <summary>
	/// Enable motors on all joints with the given frequency and damping
	/// </summary>
	public void EnableJointMotors( float frequency = 30f, float dampingRatio = 1f )
	{
		foreach ( var joint in Joints )
		{
			if ( !joint.Component.IsValid() )
				continue;

			if ( joint.Component is BallJoint ballJoint )
			{
				ballJoint.Motor = BallJoint.MotorMode.TargetRotation;
				ballJoint.Frequency = frequency;
				ballJoint.DampingRatio = dampingRatio;
			}
			else if ( joint.Component is HingeJoint hingeJoint )
			{
				hingeJoint.Motor = HingeJoint.MotorMode.TargetAngle;
				hingeJoint.Frequency = frequency;
				hingeJoint.DampingRatio = dampingRatio;
			}
		}
	}

	/// <summary>
	/// Disable and reset motors on all joints
	/// </summary>
	public void DisableJointMotors()
	{
		foreach ( var joint in Joints )
		{
			if ( !joint.Component.IsValid() )
				continue;

			if ( joint.Component is BallJoint ballJoint )
			{
				ballJoint.Motor = BallJoint.MotorMode.Disabled;
				ballJoint.Frequency = 0f;
			}
			else if ( joint.Component is HingeJoint hingeJoint )
			{
				hingeJoint.Motor = HingeJoint.MotorMode.Disabled;
				hingeJoint.Frequency = 0f;
			}
		}
	}

	/// <summary>
	/// Drive every joint toward its animation pose using joint motors
	/// </summary>
	public void MoveJointsFromAnimations( float frequency = 30f, float dampingRatio = 1f )
	{
		if ( !Renderer.IsValid() || !Renderer.SceneModel.IsValid() )
			return;

		foreach ( var joint in Joints )
			MoveJointFromAnimation( joint, frequency, dampingRatio );
	}

	private void MoveJointFromAnimation( ModelPhysics.Joint joint, float frequency = 30f, float dampingRatio = 1f )
	{
		if ( !Renderer.IsValid() || !Renderer.SceneModel.IsValid() )
			return;

		if ( !joint.Component.IsValid() )
			return;

		var childBone = Renderer.Model.Bones.AllBones[joint.Body2.Bone];
		var parentBone = Renderer.Model.Bones.AllBones[joint.Body1.Bone];
		if ( childBone == null || parentBone == null )
			return;

		if ( !Renderer.TryGetBoneTransformAnimation( childBone, out var animChildTransform ) ||
			!Renderer.TryGetBoneTransformAnimation( parentBone, out var animParentTransform ) )
			return;

		var animRotation = animParentTransform.ToLocal( animChildTransform ).Rotation;

		if ( joint.Component is BallJoint ballJoint )
		{
			ballJoint.Motor = BallJoint.MotorMode.TargetRotation;
			ballJoint.Frequency = frequency;
			ballJoint.DampingRatio = dampingRatio;
			ballJoint.TargetRotation = joint.Component.Point1.LocalRotation.Inverse * animRotation * joint.Component.Point2.LocalRotation;
		}
		else if ( joint.Component is HingeJoint hingeJoint )
		{
			hingeJoint.Motor = HingeJoint.MotorMode.TargetAngle;
			hingeJoint.Frequency = frequency;
			hingeJoint.DampingRatio = dampingRatio;
			var targetJointRot = joint.Component.Point1.LocalRotation.Inverse * animRotation * joint.Component.Point2.LocalRotation;
			hingeJoint.TargetAngle = GetSignedAngleAroundAxis( targetJointRot, hingeJoint.Axis );
		}
	}
}