178 lines
8.1 KiB
C++
178 lines
8.1 KiB
C++
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
|
|
|
#include "InverseKinematics.h"
|
|
|
|
void InverseKinematics::SolveAimIK(const Transform& node, const Vector3& target, Quaternion& outNodeCorrection)
|
|
{
|
|
Vector3 toTarget = target - node.Translation;
|
|
toTarget.Normalize();
|
|
const Vector3 fromNode = Vector3::Forward;
|
|
Quaternion::FindBetween(fromNode, toTarget, outNodeCorrection);
|
|
}
|
|
|
|
void InverseKinematics::SolveTwoBoneIK(Transform& rootTransform, Transform& midJointTransform, Transform& endEffectorTransform, const Vector3& targetPosition, const Vector3& poleVector, bool allowStretching, float maxStretchScale)
|
|
{
|
|
// Calculate limb segment lengths
|
|
Real lowerLimbLength = (endEffectorTransform.Translation - midJointTransform.Translation).Length();
|
|
Real upperLimbLength = (midJointTransform.Translation - rootTransform.Translation).Length();
|
|
Vector3 midJointPos = midJointTransform.Translation;
|
|
|
|
// Calculate the direction and length towards the target
|
|
Vector3 toTargetVector = targetPosition - rootTransform.Translation;
|
|
Real toTargetLength = toTargetVector.Length();
|
|
Real totalLimbLength = lowerLimbLength + upperLimbLength;
|
|
|
|
// Normalize the direction vector or set a default direction if too small
|
|
Vector3 toTargetDir;
|
|
if (toTargetLength < ZeroTolerance)
|
|
{
|
|
toTargetLength = ZeroTolerance;
|
|
toTargetDir = Vector3(1, 0, 0);
|
|
}
|
|
else
|
|
{
|
|
toTargetDir = toTargetVector.GetNormalized();
|
|
}
|
|
|
|
// Calculate the pole vector direction
|
|
Vector3 poleVectorDelta = poleVector - rootTransform.Translation;
|
|
const Real poleVectorLengthSqr = poleVectorDelta.LengthSquared();
|
|
|
|
Vector3 jointPlaneNormal, bendDirection;
|
|
if (poleVectorLengthSqr < ZeroTolerance * ZeroTolerance)
|
|
{
|
|
bendDirection = Vector3::Forward;
|
|
jointPlaneNormal = Vector3::Up;
|
|
}
|
|
else
|
|
{
|
|
jointPlaneNormal = toTargetDir ^ poleVectorDelta;
|
|
if (jointPlaneNormal.LengthSquared() < ZeroTolerance * ZeroTolerance)
|
|
{
|
|
toTargetDir.FindBestAxisVectors(jointPlaneNormal, bendDirection);
|
|
}
|
|
else
|
|
{
|
|
jointPlaneNormal.Normalize();
|
|
bendDirection = poleVectorDelta - (poleVectorDelta | toTargetDir) * toTargetDir;
|
|
bendDirection.Normalize();
|
|
}
|
|
}
|
|
|
|
// Handle limb stretching if allowed
|
|
if (allowStretching)
|
|
{
|
|
const Real initialStretchRatio = 1.0f;
|
|
const Real stretchRange = maxStretchScale - initialStretchRatio;
|
|
if (stretchRange > ZeroTolerance && totalLimbLength > ZeroTolerance)
|
|
{
|
|
const Real reachRatio = toTargetLength / totalLimbLength;
|
|
const Real scalingFactor = (maxStretchScale - 1.0f) * Math::Saturate((reachRatio - initialStretchRatio) / stretchRange);
|
|
if (scalingFactor > ZeroTolerance)
|
|
{
|
|
lowerLimbLength *= 1.0f + scalingFactor;
|
|
upperLimbLength *= 1.0f + scalingFactor;
|
|
totalLimbLength *= 1.0f + scalingFactor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate new positions for joint and end effector
|
|
Vector3 newEndEffectorPos = targetPosition;
|
|
Vector3 newMidJointPos = midJointPos;
|
|
|
|
if (toTargetLength >= totalLimbLength)
|
|
{
|
|
// Target is beyond the reach of the limb
|
|
Vector3 rootToEnd = (targetPosition - rootTransform.Translation).GetNormalized();
|
|
|
|
// Calculate the slight offset towards the pole vector
|
|
Vector3 rootToPole = (poleVector - rootTransform.Translation).GetNormalized();
|
|
Vector3 slightBendDirection = Vector3::Cross(rootToEnd, rootToPole);
|
|
if (slightBendDirection.LengthSquared() < ZeroTolerance * ZeroTolerance)
|
|
{
|
|
slightBendDirection = Vector3::Up;
|
|
}
|
|
else
|
|
{
|
|
slightBendDirection.Normalize();
|
|
}
|
|
|
|
// Calculate the direction from root to mid joint with a slight offset towards the pole vector
|
|
Vector3 midJointDirection = Vector3::Cross(slightBendDirection, rootToEnd).GetNormalized();
|
|
Real slightOffset = upperLimbLength * 0.01f; // Small percentage of the limb length for slight offset
|
|
newMidJointPos = rootTransform.Translation + rootToEnd * (upperLimbLength - slightOffset) + midJointDirection * slightOffset;
|
|
}
|
|
else
|
|
{
|
|
// Target is within reach, calculate joint position
|
|
const Real twoAb = 2.0f * upperLimbLength * toTargetLength;
|
|
const Real cosAngle = twoAb > ZeroTolerance ? (upperLimbLength * upperLimbLength + toTargetLength * toTargetLength - lowerLimbLength * lowerLimbLength) / twoAb : 0.0f;
|
|
const bool reverseUpperBone = cosAngle < 0.0f;
|
|
const Real angle = Math::Acos(cosAngle);
|
|
const Real jointLineDist = upperLimbLength * Math::Sin(angle);
|
|
const Real projJointDistSqr = upperLimbLength * upperLimbLength - jointLineDist * jointLineDist;
|
|
Real projJointDist = projJointDistSqr > 0.0f ? Math::Sqrt(projJointDistSqr) : 0.0f;
|
|
if (reverseUpperBone)
|
|
projJointDist *= -1.0f;
|
|
newMidJointPos = rootTransform.Translation + projJointDist * toTargetDir + jointLineDist * bendDirection;
|
|
}
|
|
|
|
// Update root joint orientation
|
|
{
|
|
// Vector from root joint to mid joint (local Y-axis direction)
|
|
Vector3 localY = (newMidJointPos - rootTransform.Translation).GetNormalized();
|
|
|
|
// Vector from mid joint to end effector (used to calculate plane normal)
|
|
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
|
|
|
// Calculate the plane normal (local Z-axis direction)
|
|
Vector3 localZ = Vector3::Cross(localY, midToEnd).GetNormalized();
|
|
|
|
// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
|
|
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
|
|
|
|
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
|
|
localZ = Vector3::Cross(localX, localY).GetNormalized();
|
|
|
|
// Construct a rotation from the orthogonal basis vectors
|
|
Quaternion newRootJointOrientation = Quaternion::LookRotation(localZ, localY);
|
|
|
|
// Apply the new rotation to the root joint
|
|
rootTransform.Orientation = newRootJointOrientation;
|
|
}
|
|
|
|
// Update mid joint orientation to point Y-axis towards the end effector and Z-axis perpendicular to the IK plane
|
|
{
|
|
// Vector from mid joint to end effector (local Y-axis direction after rotation)
|
|
Vector3 midToEnd = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
|
|
|
// Calculate the plane normal using the root, mid joint, and end effector positions (will be the local Z-axis direction)
|
|
Vector3 rootToMid = (newMidJointPos - rootTransform.Translation).GetNormalized();
|
|
Vector3 planeNormal = Vector3::Cross(rootToMid, midToEnd).GetNormalized();
|
|
|
|
// Vector from mid joint to end effector (local Y-axis direction)
|
|
Vector3 localY = (newEndEffectorPos - newMidJointPos).GetNormalized();
|
|
|
|
// Calculate the plane normal using the root, mid joint, and end effector positions (local Z-axis direction)
|
|
Vector3 localZ = Vector3::Cross(rootToMid, localY).GetNormalized();
|
|
|
|
//// Calculate the local X-axis direction, should be perpendicular to the Y and Z axes
|
|
Vector3 localX = Vector3::Cross(localY, localZ).GetNormalized();
|
|
|
|
// Correct the local Z-axis direction based on the cross product of X and Y to ensure orthogonality
|
|
localZ = Vector3::Cross(localX, localY).GetNormalized();
|
|
|
|
// Construct a rotation from the orthogonal basis vectors
|
|
// The axes are used differently here than a standard LookRotation to align Z towards the end and Y perpendicular
|
|
Quaternion newMidJointOrientation = Quaternion::LookRotation(localZ, localY); // Assuming FromLookRotation creates a rotation with the first vector as forward and the second as up
|
|
|
|
// Apply the new rotation to the mid joint
|
|
midJointTransform.Orientation = newMidJointOrientation;
|
|
midJointTransform.Translation = newMidJointPos;
|
|
}
|
|
|
|
// Update end effector transform
|
|
endEffectorTransform.Translation = newEndEffectorPos;
|
|
}
|