|
|
- //======= Copyright (c) Valve Corporation, All rights reserved. ===============
- //
- // Purpose: Simple two bone ik solver.
- //
- //=============================================================================
-
- using UnityEngine;
-
- 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
- }
- }
-
|