using System;
using UnityEngine;
using Debug = System.Diagnostics.Debug;
namespace Framework.Support {
/// Contains helper methods for dealing with IK problems
public static class InverseKinematicsHelper {
///
/// Calculates the angle of two bones so the second bone ends at a desired location
///
/// Near bone in the the IK chain
/// Far bone in the IK chain
/// Bone to which the far bone is connected
///
/// Position at the should be placed at
///
/// Normal of the 2D plane on which the bending takes place
/// Factor by how much the IK should be applied (1.0 = full)
/// >
/// Code by anonymous pastebin contributor.
///
public static void SolveTwoBoneIK(
Transform nearBone, Transform farBone, Transform endBone,
Vector3 targetPosition, Vector3 bendNormal, float weight
) {
if(weight <= 0.0f) {
return;
}
// Direction of the limb in solver
targetPosition = Vector3.Lerp(endBone.position, targetPosition, weight);
Vector3 dir = targetPosition - nearBone.position;
// Distance between the first and the last node solver positions
float length = dir.magnitude;
if(length == 0.0f) {
return;
}
float sqrMag1 = (farBone.position - nearBone.position).sqrMagnitude;
float sqrMag2 = (endBone.position - farBone.position).sqrMagnitude;
// Get the general world space bending direction
Vector3 bendDir = Vector3.Cross(dir, bendNormal);
// Get the direction to the trigonometrically solved position of the second node
Vector3 toBendPoint = getDirectionToBendPoint(dir, length, bendDir, sqrMag1, sqrMag2);
// Position the second node
Quaternion q1 = Quaternion.FromToRotation(
farBone.position - nearBone.position, toBendPoint
);
if(weight < 1.0f) {
q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);
}
nearBone.rotation = q1 * nearBone.rotation;
Quaternion q2 = Quaternion.FromToRotation(
endBone.position - farBone.position, targetPosition - farBone.position
);
if (weight < 1.0f) q2 = Quaternion.Lerp(Quaternion.identity, q2, weight);
farBone.rotation = q2 * farBone.rotation;
}
///
/// Calculates the angle of two bones so the second bone ends at a desired location
///
///
/// Solve for positive angle 2 instead of negative angle 2?
///
/// Length of bone 1. Assume to be >= zero
/// Length of bone 2. Assume to be >= zero
/// Target position for the bones to reach
/// Receives the target angle of bone 1
/// Receives the target angle of bone 2
/// True when a valid solution was found
///
///
/// Given a two bone chain located at the origin (bone1 is the parent of bone2), this
/// function will compute the bone angles needed for the end of the chain to line up
/// with a target position. If there is no valid solution, the angles will be set to
/// get as close to the target as possible.
///
///
/// Algorithm by Ryan Juckett
///
///
/// Copyright (c) 2008-2009 Ryan Juckett
/// http://www.ryanjuckett.com/
///
///
/// This software is provided 'as-is', without any express or implied
/// warranty. In no event will the authors be held liable for any damages
/// arising from the use of this software.
///
///
/// Permission is granted to anyone to use this software for any purpose,
/// including commercial applications, and to alter it and redistribute it
/// freely, subject to the following restrictions:
///
///
/// 1. The origin of this software must not be misrepresented; you must not
/// claim that you wrote the original software. If you use this software
/// in a product, an acknowledgment in the product documentation would be
/// appreciated but is not required.
///
///
/// 2. Altered source versions must be plainly marked as such, and must not be
/// misrepresented as being the original software.
///
///
/// 3. This notice may not be removed or altered from any source
/// distribution.
///
///
public static bool SolveTwoBoneIK(
bool solvePosAngle2,
double length1, double length2,
Vector2 target,
out double angle1, out double angle2
) {
Debug.Assert(length1 >= 0);
Debug.Assert(length2 >= 0);
const double epsilon = 0.0001; // used to prevent division by small numbers
bool foundValidSolution = true;
double targetDistSqr = (target.x * target.x + target.y * target.y);
// Compute a new value for angle2 along with its cosine
double sinAngle2, cosAngle2;
double cosAngle2_denom = 2.0 * length1 * length2;
if(cosAngle2_denom > epsilon) {
cosAngle2 = (targetDistSqr - length1 * length1 - length2 * length2) / (cosAngle2_denom);
// If our result is not in the legal cosine range, we can not find a
// legal solution for the target
if((cosAngle2 < -1.0) || (cosAngle2 > 1.0)) {
// Check: Could this be solved by stretching the bones?
foundValidSolution = false;
}
// Clamp our value into range so we can calculate the best
// solution when there are no valid ones
cosAngle2 = Math.Max(-1, Math.Min( 1, cosAngle2));
// Second bone angle
angle2 = Math.Acos( cosAngle2 );
if(!solvePosAngle2) {
angle2 = -angle2;
}
sinAngle2 = Math.Sin( angle2 );
} else {
// At least one of the bones had a zero length. This means our
// solvable domain is a circle around the origin with a radius
// equal to the sum of our bone lengths.
double totalLenSqr = (length1 + length2) * (length1 + length2);
if(
targetDistSqr < (totalLenSqr-epsilon) ||
targetDistSqr > (totalLenSqr+epsilon)
) {
foundValidSolution = false;
}
// Only the value of angle1 matters at this point. We can just
// set angle2 to zero.
angle2 = 0.0;
cosAngle2 = 1.0;
sinAngle2 = 0.0;
}
//===
// Compute the value of angle1 based on the sine and cosine of angle2
double triAdjacent = length1 + length2 * cosAngle2;
double triOpposite = length2 * sinAngle2;
double tanY = target.y * triAdjacent - target.x * triOpposite;
double tanX = target.x * triAdjacent + target.y * triOpposite;
// Note that it is safe to call Atan2(0,0) which will happen if targetX and
// targetY are zero
angle1 = Math.Atan2(tanY, tanX);
return foundValidSolution;
}
/// Calculates the bend direction based on the law of cosines
/// Direction from base of joint to end of second limb
/// Distance from joint base to limb end
/// Normal vector of the plane on which the joint rotates
/// Length of segment before joint
/// Length of segment after joint
/// The bend direction for the joint
///
/// Magnitude of the returned vector does not equal to the length of the first bone!
///
private static Vector3 getDirectionToBendPoint(
Vector3 direction, float directionMag, Vector3 bendDirection, float sqrMag1, float sqrMag2
) {
float x = ((directionMag * directionMag) + (sqrMag1 - sqrMag2)) / 2.0f / directionMag;
float y = (float)Math.Sqrt(Mathf.Clamp(sqrMag1 - x * x, 0.0f, Mathf.Infinity));
if(direction == Vector3.zero) {
return Vector3.zero;
}
return Quaternion.LookRotation(direction, bendDirection) * new Vector3(0.0f, y, x);
}
}
} // namespace Framework.Support