IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
SteamVR_IK.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: Simple two bone ik solver.
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 
9 public class SteamVR_IK : MonoBehaviour
10 {
11  public Transform target;
12  public Transform start, joint, end;
13  public Transform poleVector, upVector;
14 
15  public float blendPct = 1.0f;
16 
17  [HideInInspector]
18  public Transform startXform, jointXform, endXform;
19 
20  void LateUpdate()
21  {
22  const float epsilon = 0.001f;
23  if (blendPct < epsilon)
24  return;
25 
26  var preUp = upVector ? upVector.up : Vector3.Cross(end.position - start.position, joint.position - start.position).normalized;
27 
28  var targetPosition = target.position;
29  var targetRotation = target.rotation;
30 
31  Vector3 forward, up, result = joint.position;
32  Solve(start.position, targetPosition, poleVector.position,
33  (joint.position - start.position).magnitude,
34  (end.position - joint.position).magnitude,
35  ref result, out forward, out up);
36 
37  if (up == Vector3.zero)
38  return;
39 
40  var startPosition = start.position;
41  var jointPosition = joint.position;
42  var endPosition = end.position;
43 
44  var startRotationLocal = start.localRotation;
45  var jointRotationLocal = joint.localRotation;
46  var endRotationLocal = end.localRotation;
47 
48  var startParent = start.parent;
49  var jointParent = joint.parent;
50  var endParent = end.parent;
51 
52  var startScale = start.localScale;
53  var jointScale = joint.localScale;
54  var endScale = end.localScale;
55 
56  if (startXform == null)
57  {
58  startXform = new GameObject("startXform").transform;
59  startXform.parent = transform;
60  }
61 
62  startXform.position = startPosition;
63  startXform.LookAt(joint, preUp);
64  start.parent = startXform;
65 
66  if (jointXform == null)
67  {
68  jointXform = new GameObject("jointXform").transform;
69  jointXform.parent = startXform;
70  }
71 
72  jointXform.position = jointPosition;
73  jointXform.LookAt(end, preUp);
74  joint.parent = jointXform;
75 
76  if (endXform == null)
77  {
78  endXform = new GameObject("endXform").transform;
79  endXform.parent = jointXform;
80  }
81 
82  endXform.position = endPosition;
83  end.parent = endXform;
84 
85  startXform.LookAt(result, up);
86  jointXform.LookAt(targetPosition, up);
87  endXform.rotation = targetRotation;
88 
89  start.parent = startParent;
90  joint.parent = jointParent;
91  end.parent = endParent;
92 
93  end.rotation = targetRotation; // optionally blend?
94 
95  // handle blending in/out
96  if (blendPct < 1.0f)
97  {
98  start.localRotation = Quaternion.Slerp(startRotationLocal, start.localRotation, blendPct);
99  joint.localRotation = Quaternion.Slerp(jointRotationLocal, joint.localRotation, blendPct);
100  end.localRotation = Quaternion.Slerp(endRotationLocal, end.localRotation, blendPct);
101  }
102 
103  // restore scale so it doesn't blow out
104  start.localScale = startScale;
105  joint.localScale = jointScale;
106  end.localScale = endScale;
107  }
108 
109  public static bool Solve(
110  Vector3 start, // shoulder / hip
111  Vector3 end, // desired hand / foot position
112  Vector3 poleVector, // point to aim elbow / knee toward
113  float jointDist, // distance from start to elbow / knee
114  float targetDist, // distance from joint to hand / ankle
115  ref Vector3 result, // original and output elbow / knee position
116  out Vector3 forward, out Vector3 up) // plane formed by root, joint and target
117  {
118  var totalDist = jointDist + targetDist;
119  var start2end = end - start;
120  var poleVectorDir = (poleVector - start).normalized;
121  var baseDist = start2end.magnitude;
122 
123  result = start;
124 
125  const float epsilon = 0.001f;
126  if (baseDist < epsilon)
127  {
128  // move jointDist toward jointTarget
129  result += poleVectorDir * jointDist;
130 
131  forward = Vector3.Cross(poleVectorDir, Vector3.up);
132  up = Vector3.Cross(forward, poleVectorDir).normalized;
133  }
134  else
135  {
136  forward = start2end * (1.0f / baseDist);
137  up = Vector3.Cross(forward, poleVectorDir).normalized;
138 
139  if (baseDist + epsilon < totalDist)
140  {
141  // calculate the area of the triangle to determine its height
142  var p = (totalDist + baseDist) * 0.5f; // half perimeter
143  if (p > jointDist + epsilon && p > targetDist + epsilon)
144  {
145  var A = Mathf.Sqrt(p * (p - jointDist) * (p - targetDist) * (p - baseDist));
146  var height = 2.0f * A / baseDist; // distance of joint from line between root and target
147 
148  var dist = Mathf.Sqrt((jointDist * jointDist) - (height * height));
149  var right = Vector3.Cross(up, forward); // no need to normalized - already orthonormal
150 
151  result += (forward * dist) + (right * height);
152  return true; // in range
153  }
154  else
155  {
156  // move jointDist toward jointTarget
157  result += poleVectorDir * jointDist;
158  }
159  }
160  else
161  {
162  // move elboDist toward target
163  result += forward * jointDist;
164  }
165  }
166 
167  return false; // edge cases
168  }
169 }
170