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 } }