using System;
using System.Collections.Generic;
using UnityEngine.Scripting.APIUpdating;
using UnityEngine.Serialization;
using UnityEngine.U2D.Common;
namespace UnityEngine.U2D.IK
{
///
/// Abstract class for implementing a 2D IK Solver.
///
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
public abstract class Solver2D : MonoBehaviour, IPreviewable
{
[SerializeField]
bool m_ConstrainRotation = true;
[FormerlySerializedAs("m_RestoreDefaultPose")]
[SerializeField]
bool m_SolveFromDefaultPose = true;
[SerializeField]
[Range(0f, 1f)]
float m_Weight = 1f;
Plane m_Plane;
List m_TargetPositions = new List();
///
/// Used to evaluate if Solver2D needs to be updated.
///
float m_LastFinalWeight;
///
/// Returns the number of IKChain2D in the solver.
///
public int chainCount => GetChainCount();
///
/// Gets and sets the rotation constrain property.
///
public bool constrainRotation
{
get => m_ConstrainRotation;
set => m_ConstrainRotation = value;
}
///
/// Get and set restoring default pose before the update.
///
public bool solveFromDefaultPose
{
get => m_SolveFromDefaultPose;
set => m_SolveFromDefaultPose = value;
}
///
/// Returns true if the Solver2D is in a valid state.
///
public bool isValid => Validate();
///
/// Returns true if all chains in the Solver have a target.
///
public bool allChainsHaveTargets => HasTargets();
///
/// Get and Set Solver weights.
///
public float weight
{
get => m_Weight;
set => m_Weight = Mathf.Clamp01(value);
}
///
/// Validate and initialize the Solver.
///
protected virtual void OnValidate()
{
m_Weight = Mathf.Clamp01(m_Weight);
if (!isValid)
Initialize();
}
bool Validate()
{
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (!chain.isValid)
return false;
}
return DoValidate();
}
bool HasTargets()
{
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (chain.target == null)
return false;
}
return true;
}
///
/// Initializes the solver.
///
public void Initialize()
{
DoInitialize();
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
chain.Initialize();
}
}
void Prepare()
{
var rootTransform = GetPlaneRootTransform();
if (rootTransform != null)
{
m_Plane.normal = rootTransform.forward;
m_Plane.distance = -Vector3.Dot(m_Plane.normal, rootTransform.position);
}
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
var constrainTargetRotation = constrainRotation && chain.target != null;
if (m_SolveFromDefaultPose)
chain.RestoreDefaultPose(constrainTargetRotation);
}
DoPrepare();
}
void PrepareEffectorPositions()
{
m_TargetPositions.Clear();
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (chain.target)
m_TargetPositions.Add(chain.target.position);
}
}
///
/// Perform the Solver IK update.
///
/// Weight for position solving.
public void UpdateIK(float globalWeight)
{
if (allChainsHaveTargets)
{
PrepareEffectorPositions();
UpdateIK(m_TargetPositions, globalWeight);
}
}
///
/// Perform the Solver IK update with specified target positions.
///
/// Target positions.
/// Weight for position solving.
public void UpdateIK(List targetPositions, float globalWeight)
{
if (targetPositions.Count != chainCount)
return;
var finalWeight = globalWeight * weight;
var weightValueChanged = Math.Abs(finalWeight - m_LastFinalWeight) > 0.0001f;
m_LastFinalWeight = finalWeight;
if (finalWeight == 0f && !weightValueChanged)
return;
if (!isValid)
return;
if (finalWeight < 1f)
StoreLocalRotations();
Prepare();
DoUpdateIK(targetPositions);
if (constrainRotation)
{
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (chain.target)
chain.effector.rotation = chain.target.rotation;
}
}
if (finalWeight < 1f)
BlendFkToIk(finalWeight);
}
void StoreLocalRotations()
{
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
chain.StoreLocalRotations();
}
}
void BlendFkToIk(float finalWeight)
{
for (var i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
var constrainTargetRotation = constrainRotation && chain.target != null;
chain.BlendFkToIk(finalWeight, constrainTargetRotation);
}
}
///
/// Override to return the IKChain2D at the given index.
///
/// Index of the IKChain2D.
///
public abstract IKChain2D GetChain(int index);
///
/// Override to return the number of chains in the Solver.
///
/// Number of chains in the solver.
protected abstract int GetChainCount();
///
/// Override to perform Solver IK update.
///
/// Target position for the chain.
protected abstract void DoUpdateIK(List targetPositions);
///
/// Override to perform custom validation.
///
/// Returns true if the Solver is in a valid state. False otherwise.
protected virtual bool DoValidate() => true;
///
/// Override to initialize the solver.
///
protected virtual void DoInitialize() { }
///
/// Override to prepare the solver for update.
///
protected virtual void DoPrepare() { }
///
/// Override to return the root transform of the Solver. The default implementation returns the root transform of the first chain.
///
/// Transform representing the root.
protected virtual Transform GetPlaneRootTransform()
{
return chainCount > 0 ? GetChain(0).rootTransform : null;
}
///
/// Convert a world position coordinate to the solver's plane space.
///
/// Vector3 representing world position
/// Converted position in solver's plane
protected Vector3 GetPointOnSolverPlane(Vector3 worldPosition)
{
return GetPlaneRootTransform().InverseTransformPoint(m_Plane.ClosestPointOnPlane(worldPosition));
}
///
/// Convert a position from solver's plane to world coordinates.
///
/// Vector3 representing a position in the Solver's plane.
/// Converted position to world coordinates.
protected Vector3 GetWorldPositionFromSolverPlanePoint(Vector2 planePoint)
{
return GetPlaneRootTransform().TransformPoint(planePoint);
}
///
/// Empty method. Implemented for the IPreviewable interface.
///
public void OnPreviewUpdate() { }
}
}