172 lines
6.1 KiB
C#
172 lines
6.1 KiB
C#
|
//======= 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
|
|||
|
}
|
|||
|
}
|
|||
|
}
|