using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK
{
///
/// The base abstract class for all Rotation limits. Contains common functionality and static helper methods
///
public abstract class RotationLimit : MonoBehaviour
{
#region Main Interface
///
/// The main axis of the rotation limit.
///
public Vector3 axis = Vector3.forward;
///
/// Map the zero rotation point to the current local rotation of this gameobject.
///
public void SetDefaultLocalRotation()
{
defaultLocalRotation = transform.localRotation;
defaultLocalRotationSet = true;
defaultLocalRotationOverride = false;
}
///
/// Map the zero rotation point to the specified rotation.
///
public void SetDefaultLocalRotation(Quaternion localRotation)
{
defaultLocalRotation = localRotation;
defaultLocalRotationSet = true;
defaultLocalRotationOverride = true;
}
///
/// Returns the limited local rotation.
///
public Quaternion GetLimitedLocalRotation(Quaternion localRotation, out bool changed)
{
// Making sure the Rotation Limit is initiated
if (!initiated) Awake();
// Subtracting defaultLocalRotation
Quaternion rotation = Quaternion.Inverse(defaultLocalRotation) * localRotation;
Quaternion limitedRotation = LimitRotation(rotation);
#if UNITY_2018_3_OR_NEWER
limitedRotation = Quaternion.Normalize(limitedRotation);
#endif
changed = limitedRotation != rotation;
if (!changed) return localRotation;
// Apply defaultLocalRotation back on
return defaultLocalRotation * limitedRotation;
}
///
/// Apply the rotation limit to transform.localRotation. Returns true if the limit has changed the rotation.
///
public bool Apply()
{
bool changed = false;
transform.localRotation = GetLimitedLocalRotation(transform.localRotation, out changed);
return changed;
}
///
/// Disable this instance making sure it is initiated. Use this if you intend to manually control the updating of this Rotation Limit.
///
public void Disable()
{
if (initiated)
{
enabled = false;
return;
}
Awake();
enabled = false;
}
#endregion Main Interface
/*
* An arbitrary secondary axis that we get by simply switching the axes
* */
public Vector3 secondaryAxis { get { return new Vector3(axis.y, axis.z, axis.x); } }
/*
* Cross product of axis and secondaryAxis
* */
public Vector3 crossAxis { get { return Vector3.Cross(axis, secondaryAxis); } }
/*
* The default local rotation of the gameobject. By default stored in Awake.
* */
[HideInInspector] public Quaternion defaultLocalRotation;
public bool defaultLocalRotationOverride { get; private set; }
protected abstract Quaternion LimitRotation(Quaternion rotation);
private bool initiated;
private bool applicationQuit;
private bool defaultLocalRotationSet;
/*
* Initiating the Rotation Limit
* */
void Awake()
{
// Store the local rotation to map the zero rotation point to the current rotation
if (!defaultLocalRotationSet) SetDefaultLocalRotation();
if (axis == Vector3.zero) Debug.LogError("Axis is Vector3.zero.");
initiated = true;
}
/*
* Using LateUpdate here because you most probably want to apply the limits after animation.
* If you need precise control over the execution order, disable this script and call Apply() whenever you need
* */
void LateUpdate()
{
Apply();
}
/*
* Logs the warning if no other warning has beed logged in this session.
* */
public void LogWarning(string message)
{
Warning.Log(message, transform);
}
#region Static helper methods for all Rotation Limits
/*
* Limits rotation to a single degree of freedom (along axis)
* */
protected static Quaternion Limit1DOF(Quaternion rotation, Vector3 axis)
{
return Quaternion.FromToRotation(rotation * axis, axis) * rotation;
}
/*
* Applies uniform twist limit to the rotation
* */
protected static Quaternion LimitTwist(Quaternion rotation, Vector3 axis, Vector3 orthoAxis, float twistLimit)
{
twistLimit = Mathf.Clamp(twistLimit, 0, 180);
if (twistLimit >= 180) return rotation;
Vector3 normal = rotation * axis;
Vector3 orthoTangent = orthoAxis;
Vector3.OrthoNormalize(ref normal, ref orthoTangent);
Vector3 rotatedOrthoTangent = rotation * orthoAxis;
Vector3.OrthoNormalize(ref normal, ref rotatedOrthoTangent);
Quaternion fixedRotation = Quaternion.FromToRotation(rotatedOrthoTangent, orthoTangent) * rotation;
if (twistLimit <= 0) return fixedRotation;
// Rotate from zero twist to free twist by the limited angle
return Quaternion.RotateTowards(fixedRotation, rotation, twistLimit);
}
/*
* Returns the angle between two vectors on a plane with the specified normal
* */
protected static float GetOrthogonalAngle(Vector3 v1, Vector3 v2, Vector3 normal)
{
Vector3.OrthoNormalize(ref normal, ref v1);
Vector3.OrthoNormalize(ref normal, ref v2);
return Vector3.Angle(v1, v2);
}
#endregion
}
}