using UnityEngine; using System.Collections; using System; namespace RootMotion.FinalIK { /// /// Rotates a hierarchy of bones to make a Transform aim at a target. /// If there are problems with continuity and the solver get's jumpy, make sure to keep IKPosition at a safe distance from the transform and try decreasing solver and bone weights. /// [System.Serializable] public class IKSolverAim : IKSolverHeuristic { #region Main Interface /// /// The transform that we want to aim at IKPosition. /// public Transform transform; /// /// The local axis of the Transform that you want to be aimed at IKPosition. /// public Vector3 axis = Vector3.forward; /// /// Keeps that axis of the Aim Transform directed at the polePosition. /// public Vector3 poleAxis = Vector3.up; /// /// The position in world space to keep the pole axis of the Aim Transform directed at. /// public Vector3 polePosition; /// /// The weight of the Pole. /// [Range(0f, 1f)] public float poleWeight; /// /// If assigned, will automatically set polePosition to the position of this Transform. /// public Transform poleTarget; /// /// Clamping rotation of the solver. 0 is free rotation, 1 is completely clamped to transform axis. /// [Range(0f, 1f)] public float clampWeight = 0.1f; /// /// Number of sine smoothing iterations applied to clamping to make it smoother. /// [Range(0, 2)] public int clampSmoothing = 2; /// /// Gets the angular offset. /// public float GetAngle() { return Vector3.Angle(transformAxis, IKPosition - transform.position); } /// /// Gets the Axis of the AimTransform is world space. /// public Vector3 transformAxis { get { return transform.rotation * axis; } } /// /// Gets the Pole Axis of the AimTransform is world space. /// public Vector3 transformPoleAxis { get { return transform.rotation * poleAxis; } } /// /// Called before each iteration of the solver. /// public IterationDelegate OnPreIteration; #endregion Main Interface protected override void OnInitiate() { if ((firstInitiation || !Application.isPlaying) && transform != null) { IKPosition = transform.position + transformAxis * 3f; polePosition = transform.position + transformPoleAxis * 3f; } // Disable Rotation Limits from updating to take control of their execution order for (int i = 0; i < bones.Length; i++) { if (bones[i].rotationLimit != null) bones[i].rotationLimit.Disable(); } step = 1f / (float)bones.Length; if (Application.isPlaying) axis = axis.normalized; } protected override void OnUpdate() { if (axis == Vector3.zero) { if (!Warning.logged) LogWarning("IKSolverAim axis is Vector3.zero."); return; } if (poleAxis == Vector3.zero && poleWeight > 0f) { if (!Warning.logged) LogWarning("IKSolverAim poleAxis is Vector3.zero."); return; } if (target != null) IKPosition = target.position; if (poleTarget != null) polePosition = poleTarget.position; if (XY) IKPosition.z = bones[0].transform.position.z; // Clamping weights if (IKPositionWeight <= 0) return; IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f); // Rotation Limit on the Aim Transform if (transform != lastTransform) { transformLimit = transform.GetComponent(); if (transformLimit != null) transformLimit.enabled = false; lastTransform = transform; } if (transformLimit != null) transformLimit.Apply(); // In case transform becomes unassigned in runtime if (transform == null) { if (!Warning.logged) LogWarning("Aim Transform unassigned in Aim IK solver. Please Assign a Transform (lineal descendant to the last bone in the spine) that you want to be aimed at IKPosition"); return; } clampWeight = Mathf.Clamp(clampWeight, 0f, 1f); clampedIKPosition = GetClampedIKPosition(); Vector3 dir = clampedIKPosition - transform.position; dir = Vector3.Slerp(transformAxis * dir.magnitude, dir, IKPositionWeight); clampedIKPosition = transform.position + dir; // Iterating the solver for (int i = 0; i < maxIterations; i++) { // Optimizations if (i >= 1 && tolerance > 0 && GetAngle() < tolerance) break; lastLocalDirection = localDirection; if (OnPreIteration != null) OnPreIteration(i); Solve(); } lastLocalDirection = localDirection; } protected override int minBones { get { return 1; }} private float step; private Vector3 clampedIKPosition; private RotationLimit transformLimit; private Transform lastTransform; /* * Solving the hierarchy * */ private void Solve() { // Rotating bones to get closer to target. for (int i = 0; i < bones.Length - 1; i++) RotateToTarget(clampedIKPosition, bones[i], step * (i + 1) * IKPositionWeight * bones[i].weight); RotateToTarget(clampedIKPosition, bones[bones.Length - 1], IKPositionWeight * bones[bones.Length - 1].weight); } /* * Clamping the IKPosition to legal range * */ private Vector3 GetClampedIKPosition() { if (clampWeight <= 0f) return IKPosition; if (clampWeight >= 1f) return transform.position + transformAxis * (IKPosition - transform.position).magnitude; // Getting the dot product of IK direction and transformAxis //float dot = (Vector3.Dot(transformAxis, (IKPosition - transform.position).normalized) + 1) * 0.5f; float angle = Vector3.Angle(transformAxis, (IKPosition - transform.position)); float dot = 1f - (angle / 180f); // Clamping the target float targetClampMlp = clampWeight > 0? Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f): 1f; // Calculating the clamp multiplier float clampMlp = clampWeight > 0? Mathf.Clamp(dot / clampWeight, 0f, 1f): 1f; for (int i = 0; i < clampSmoothing; i++) { float sinF = clampMlp * Mathf.PI * 0.5f; clampMlp = Mathf.Sin(sinF); } // Slerping the IK direction (don't use Lerp here, it breaks it) return transform.position + Vector3.Slerp(transformAxis * 10f, IKPosition - transform.position, clampMlp * targetClampMlp); } /* * Rotating bone to get transform aim closer to target * */ private void RotateToTarget(Vector3 targetPosition, IKSolver.Bone bone, float weight) { // Swing if (XY) { if (weight >= 0f) { Vector3 dir = transformAxis; Vector3 targetDir = targetPosition - transform.position; float angleDir = Mathf.Atan2(dir.x, dir.y) * Mathf.Rad2Deg; float angleTarget = Mathf.Atan2(targetDir.x, targetDir.y) * Mathf.Rad2Deg; bone.transform.rotation = Quaternion.AngleAxis(Mathf.DeltaAngle(angleDir, angleTarget), Vector3.back) * bone.transform.rotation; } } else { if (weight >= 0f) { Quaternion rotationOffset = Quaternion.FromToRotation(transformAxis, targetPosition - transform.position); if (weight >= 1f) { bone.transform.rotation = rotationOffset * bone.transform.rotation; } else { bone.transform.rotation = Quaternion.Lerp(Quaternion.identity, rotationOffset, weight) * bone.transform.rotation; } } // Pole if (poleWeight > 0f) { Vector3 poleDirection = polePosition - transform.position; // Ortho-normalize to transform axis to make this a twisting only operation Vector3 poleDirOrtho = poleDirection; Vector3 normal = transformAxis; Vector3.OrthoNormalize(ref normal, ref poleDirOrtho); Quaternion toPole = Quaternion.FromToRotation(transformPoleAxis, poleDirOrtho); bone.transform.rotation = Quaternion.Lerp(Quaternion.identity, toPole, weight * poleWeight) * bone.transform.rotation; } } if (useRotationLimits && bone.rotationLimit != null) bone.rotationLimit.Apply(); } /* * Gets the direction from last bone's forward in first bone's local space. * */ protected override Vector3 localDirection { get { return bones[0].transform.InverseTransformDirection(bones[bones.Length - 1].transform.forward); } } } }