//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: Simple two bone ik solver. // //============================================================================= using UnityEngine; namespace Valve.VR { public class SteamVR_IK : MonoBehaviour { public Transform target; public Transform start, joint, end; public Transform poleVector, upVector; public float blendPct = 1.0f; [HideInInspector] public Transform startXform, jointXform, endXform; void LateUpdate() { const float epsilon = 0.001f; if (blendPct < epsilon) return; var preUp = upVector ? upVector.up : Vector3.Cross(end.position - start.position, joint.position - start.position).normalized; var targetPosition = target.position; var targetRotation = target.rotation; Vector3 forward, up, result = joint.position; Solve(start.position, targetPosition, poleVector.position, (joint.position - start.position).magnitude, (end.position - joint.position).magnitude, ref result, out forward, out up); if (up == Vector3.zero) return; var startPosition = start.position; var jointPosition = joint.position; var endPosition = end.position; var startRotationLocal = start.localRotation; var jointRotationLocal = joint.localRotation; var endRotationLocal = end.localRotation; var startParent = start.parent; var jointParent = joint.parent; var endParent = end.parent; var startScale = start.localScale; var jointScale = joint.localScale; var endScale = end.localScale; if (startXform == null) { startXform = new GameObject("startXform").transform; startXform.parent = transform; } startXform.position = startPosition; startXform.LookAt(joint, preUp); start.parent = startXform; if (jointXform == null) { jointXform = new GameObject("jointXform").transform; jointXform.parent = startXform; } jointXform.position = jointPosition; jointXform.LookAt(end, preUp); joint.parent = jointXform; if (endXform == null) { endXform = new GameObject("endXform").transform; endXform.parent = jointXform; } endXform.position = endPosition; end.parent = endXform; startXform.LookAt(result, up); jointXform.LookAt(targetPosition, up); endXform.rotation = targetRotation; start.parent = startParent; joint.parent = jointParent; end.parent = endParent; end.rotation = targetRotation; // optionally blend? // handle blending in/out if (blendPct < 1.0f) { start.localRotation = Quaternion.Slerp(startRotationLocal, start.localRotation, blendPct); joint.localRotation = Quaternion.Slerp(jointRotationLocal, joint.localRotation, blendPct); end.localRotation = Quaternion.Slerp(endRotationLocal, end.localRotation, blendPct); } // restore scale so it doesn't blow out start.localScale = startScale; joint.localScale = jointScale; end.localScale = endScale; } public static bool Solve( Vector3 start, // shoulder / hip Vector3 end, // desired hand / foot position Vector3 poleVector, // point to aim elbow / knee toward float jointDist, // distance from start to elbow / knee float targetDist, // distance from joint to hand / ankle ref Vector3 result, // original and output elbow / knee position out Vector3 forward, out Vector3 up) // plane formed by root, joint and target { var totalDist = jointDist + targetDist; var start2end = end - start; var poleVectorDir = (poleVector - start).normalized; var baseDist = start2end.magnitude; result = start; const float epsilon = 0.001f; if (baseDist < epsilon) { // move jointDist toward jointTarget result += poleVectorDir * jointDist; forward = Vector3.Cross(poleVectorDir, Vector3.up); up = Vector3.Cross(forward, poleVectorDir).normalized; } else { forward = start2end * (1.0f / baseDist); up = Vector3.Cross(forward, poleVectorDir).normalized; if (baseDist + epsilon < totalDist) { // calculate the area of the triangle to determine its height var p = (totalDist + baseDist) * 0.5f; // half perimeter if (p > jointDist + epsilon && p > targetDist + epsilon) { var A = Mathf.Sqrt(p * (p - jointDist) * (p - targetDist) * (p - baseDist)); var height = 2.0f * A / baseDist; // distance of joint from line between root and target var dist = Mathf.Sqrt((jointDist * jointDist) - (height * height)); var right = Vector3.Cross(up, forward); // no need to normalized - already orthonormal result += (forward * dist) + (right * height); return true; // in range } else { // move jointDist toward jointTarget result += poleVectorDir * jointDist; } } else { // move elboDist toward target result += forward * jointDist; } } return false; // edge cases } } }