SteamVR_IK.cs
6.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//======= 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
}
}
}