File size: 6,884 Bytes
7b853a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/*
 * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "InverseKinematics.h"
#include "Math/Scalar.h"
#include <iostream>


using namespace IK;

namespace
{

float getAngleWithTwoSideVectors(const Math::Vector& vecLeft, const Math::Vector& vecRight)
{
    auto lNorm = vecLeft.GetNormalized3();
    auto rNorm = vecRight.GetNormalized3();

    float cosine = lNorm.GetDot3(rNorm);
    float sine = lNorm.Cross3(rNorm).GetLength3();

    return atan2f(sine, cosine);  // in radian
}

float getAngleWithCosineRule (const float lSideLeft, const float lSideRight, const float lSideAcross)
{
    float val =
        (lSideRight * lSideRight + lSideLeft * lSideLeft - lSideAcross * lSideAcross) /
            (2.0f * lSideLeft * lSideRight);
    val = Math::Clamp(val, -1.0f, 1.0f);  // numerical stability. also avoid impossible trangulars
    return acosf(val);  // in radian
}

}


void IK::TwoBoneIk(
    Pose& pose,
    const Math::Transform& rootTransform,
    uint32_t cIdx,
    float weight,
    const Math::Vector& target,
    const std::vector<int>& joint_parents_vec,
    const Math::Vector& hintOffset
)
{
    weight = Math::Clamp(weight, 0.0f, 1.0f);
    if (!(weight > 0.0f))
        return;

    // Two bone IK: joints are represented as "a", "b", "c" in the below comments:
    //  1. stage 1, bend joint a and joint b, so that |ac| = |at|, while vec_ac maintain the same direction
    //  2. stage 2, rotate start joint a so that c and t are in the same place

    //  a                   a                   a             |
    //  |\                  |\                  |\            |
    //  | \                 |  \                | \           |
    //  |  \  (stage 1 ->)  |   \  (stage 2 ->) |  \          |
    //  |   b               |    b              |   b         |
    //  |    \              |    |              |  /          |
    //  |     \             |     |             | /           |
    //  t      c            t      c            t(c)          |
    //  (a is the root joint, b is the middle joint and c is the end joint)
    //

    int32_t bIdx = joint_parents_vec[cIdx];
    if (bIdx < 0)
    {
        return;
    }
    int32_t aIdx = joint_parents_vec[bIdx];
    if (aIdx < 0)
    {
        return;
    }

    // Find the parent world transform of joint a:
    Math::Transform aParentWorldTransform = Math::Transform::Identity;
    int32_t idx = joint_parents_vec[aIdx];
    while (idx >= 0)
    {
        aParentWorldTransform = aParentWorldTransform * pose[idx];
        idx = joint_parents_vec[idx];
    }
    aParentWorldTransform = aParentWorldTransform * rootTransform;

    // Compute world space transforms of a, b and c:
    Math::Transform aWorld = pose[aIdx] * aParentWorldTransform;
    Math::Transform bWorld = pose[bIdx] * aWorld;
    Math::Transform cWorld = pose[cIdx] * bWorld;

    auto a = aWorld.GetTranslation();
    auto b = bWorld.GetTranslation();
    auto c = cWorld.GetTranslation();
    auto t = Math::Vector::Lerp(c, target, weight);

    // step 1 (stage 1): extend / contract the joint chain to match the distance
    float eps = 0.0001f;  // numerical stability
    float l_ab = (b - a).Length3().GetX();
    float l_bc = (c - b).Length3().GetX();
    float l_at = (a - t).Length3().GetX();
    l_at = Math::Clamp(l_at, eps, (l_ab + l_bc) * 0.999f); // when not reachable, replace with maximum reachable length

    // get the current angles
    float theta_bac_current = getAngleWithTwoSideVectors(a - b, a - c);
    float theta_abc_current = getAngleWithTwoSideVectors(b - a, b - c);
    // get the desired angles
    if (l_ab < eps || l_bc < eps || l_at < eps)
    {
        return;  // the length is too small. rejecting potentially numerically unstable requests.
    }
    float theta_bac_desired = getAngleWithCosineRule(l_ab, l_at, l_bc);
    float theta_abc_desired = getAngleWithCosineRule(l_ab, l_bc, l_at);

    // in joint[0]'s parent's space
    Math::Vector rotationAxis = Math::Vector::Cross3(c - a, bWorld.TransformPoint(hintOffset) - a);
    float l = rotationAxis.GetLength3();
    if (l == 0)
    {
        rotationAxis = Math::Vector(0,0,1);
    }
    else
    {
        rotationAxis /= l;
    }

    // get the rotation with axis in the local space of joint a and joint b
    Math::Vector rotationAxisLocalInBSpace = bWorld.GetRotation().RotateVectorInverse(rotationAxis);
    Math::Transform rotateInB(
        Math::Quaternion(rotationAxisLocalInBSpace,
            (theta_abc_desired - theta_abc_current)), Math::Vector::Zero);

    pose[bIdx] = rotateInB * pose[bIdx];

    Math::Vector rotationAxisLocalInASpace = aWorld.GetRotation().RotateVectorInverse(rotationAxis);
    Math::Transform rotateInA(
        Math::Quaternion(rotationAxisLocalInASpace,
            (theta_bac_desired - theta_bac_current)), Math::Vector::Zero);

    pose[aIdx] = rotateInA * pose[aIdx];

    // recompute a's world space transform as we're going to need it:
    aWorld = pose[aIdx] * aParentWorldTransform;

    // step 2 (stage 2): rotate joint a so that the target and the end joint c matches
    auto acLocal = aWorld.GetRotation().RotateVectorInverse(
        c - a);
    auto atLocal = aWorld.GetRotation().RotateVectorInverse(
        target - a);
    Math::Transform rotateStageTwo(
        Math::Quaternion::FromRotationBetweenVectors(acLocal, atLocal), Math::Vector::Zero
    );

    pose[aIdx] = rotateStageTwo * pose[aIdx];

}

void IK::OneBoneIk(
    Pose& pose,
    const Math::Transform& rootTransform,
    uint32_t bIdx,
    float weight,
    const Math::Vector& target,
    const std::vector<int>& joint_parents_vec
)
{
    weight = Math::Clamp(weight, 0.0f, 1.0f);
    if (!(weight > 0.0f))
        return;

    int32_t aIdx = joint_parents_vec[bIdx];
    if (aIdx < 0)
    {
        return;
    }

    // Find the parent world transform of joint a:
    Math::Transform aParentWorldTransform = Math::Transform::Identity;
    int32_t idx = joint_parents_vec[aIdx];
    while (idx >= 0)
    {
        aParentWorldTransform = aParentWorldTransform * pose[idx];
        idx = joint_parents_vec[idx];
    }
    aParentWorldTransform = aParentWorldTransform * rootTransform;

    // Compute world space transforms of a, b and c:
    Math::Transform aWorld = pose[aIdx] * aParentWorldTransform;
    Math::Transform bWorld = pose[bIdx] * aWorld;

    auto abLocal = aWorld.GetRotation().RotateVectorInverse(
        bWorld.GetTranslation() - aWorld.GetTranslation());
    auto atLocal = aWorld.GetRotation().RotateVectorInverse(
        target - aWorld.GetTranslation());

    auto deltaRLocal = Math::Quaternion::NLerp(Math::Quaternion::Identity, Math::Quaternion::FromRotationBetweenVectors(abLocal, atLocal), weight);
    pose[aIdx].SetRotation(deltaRLocal * pose[aIdx].GetRotation());
}