Performance is about the same or slightly better for typical IK chains. In extreme cases with many bones and multiple targets, of which some are unreachable, I've seen 2x speedups.
436 lines
11 KiB
C++
436 lines
11 KiB
C++
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
|
|
* All rights reserved.
|
|
*
|
|
* The Original Code is: all of this file.
|
|
*
|
|
* Original Author: Laurence
|
|
* Contributor(s): Brecht
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file iksolver/intern/IK_QJacobian.cpp
|
|
* \ingroup iksolver
|
|
*/
|
|
|
|
|
|
#include "IK_QJacobian.h"
|
|
|
|
IK_QJacobian::IK_QJacobian()
|
|
: m_sdls(true), m_min_damp(1.0)
|
|
{
|
|
}
|
|
|
|
IK_QJacobian::~IK_QJacobian()
|
|
{
|
|
}
|
|
|
|
void IK_QJacobian::ArmMatrices(int dof, int task_size)
|
|
{
|
|
m_dof = dof;
|
|
m_task_size = task_size;
|
|
|
|
m_jacobian.resize(task_size, dof);
|
|
m_jacobian.setZero();
|
|
|
|
m_alpha.resize(dof);
|
|
m_alpha.setZero();
|
|
|
|
m_nullspace.resize(dof, dof);
|
|
|
|
m_d_theta.resize(dof);
|
|
m_d_theta_tmp.resize(dof);
|
|
m_d_norm_weight.resize(dof);
|
|
|
|
m_norm.resize(dof);
|
|
m_norm.setZero();
|
|
|
|
m_beta.resize(task_size);
|
|
|
|
m_weight.resize(dof);
|
|
m_weight_sqrt.resize(dof);
|
|
m_weight.setOnes();
|
|
m_weight_sqrt.setOnes();
|
|
|
|
if (task_size >= dof) {
|
|
m_transpose = false;
|
|
|
|
m_jacobian_tmp.resize(task_size, dof);
|
|
|
|
m_svd_u.resize(task_size, dof);
|
|
m_svd_v.resize(dof, dof);
|
|
m_svd_w.resize(dof);
|
|
|
|
m_svd_u_beta.resize(dof);
|
|
}
|
|
else {
|
|
// use the SVD of the transpose jacobian, it works just as well
|
|
// as the original, and often allows using smaller matrices.
|
|
m_transpose = true;
|
|
|
|
m_jacobian_tmp.resize(dof, task_size);
|
|
|
|
m_svd_u.resize(task_size, task_size);
|
|
m_svd_v.resize(dof, task_size);
|
|
m_svd_w.resize(task_size);
|
|
|
|
m_svd_u_beta.resize(task_size);
|
|
}
|
|
}
|
|
|
|
void IK_QJacobian::SetBetas(int id, int, const Vector3d& v)
|
|
{
|
|
m_beta[id + 0] = v.x();
|
|
m_beta[id + 1] = v.y();
|
|
m_beta[id + 2] = v.z();
|
|
}
|
|
|
|
void IK_QJacobian::SetDerivatives(int id, int dof_id, const Vector3d& v, double norm_weight)
|
|
{
|
|
m_jacobian(id + 0, dof_id) = v.x() * m_weight_sqrt[dof_id];
|
|
m_jacobian(id + 1, dof_id) = v.y() * m_weight_sqrt[dof_id];
|
|
m_jacobian(id + 2, dof_id) = v.z() * m_weight_sqrt[dof_id];
|
|
|
|
m_d_norm_weight[dof_id] = norm_weight;
|
|
}
|
|
|
|
void IK_QJacobian::Invert()
|
|
{
|
|
if (m_transpose) {
|
|
// SVD will decompose Jt into V*W*Ut with U,V orthogonal and W diagonal,
|
|
// so J = U*W*Vt and Jinv = V*Winv*Ut
|
|
Eigen::JacobiSVD<MatrixXd> svd(m_jacobian.transpose(), Eigen::ComputeThinU | Eigen::ComputeThinV);
|
|
m_svd_u = svd.matrixV();
|
|
m_svd_w = svd.singularValues();
|
|
m_svd_v = svd.matrixU();
|
|
}
|
|
else {
|
|
// SVD will decompose J into U*W*Vt with U,V orthogonal and W diagonal,
|
|
// so Jinv = V*Winv*Ut
|
|
Eigen::JacobiSVD<MatrixXd> svd(m_jacobian, Eigen::ComputeThinU | Eigen::ComputeThinV);
|
|
m_svd_u = svd.matrixU();
|
|
m_svd_w = svd.singularValues();
|
|
m_svd_v = svd.matrixV();
|
|
}
|
|
|
|
if (m_sdls)
|
|
InvertSDLS();
|
|
else
|
|
InvertDLS();
|
|
}
|
|
|
|
bool IK_QJacobian::ComputeNullProjection()
|
|
{
|
|
double epsilon = 1e-10;
|
|
|
|
// compute null space projection based on V
|
|
int i, j, rank = 0;
|
|
for (i = 0; i < m_svd_w.size(); i++)
|
|
if (m_svd_w[i] > epsilon)
|
|
rank++;
|
|
|
|
if (rank < m_task_size)
|
|
return false;
|
|
|
|
MatrixXd basis(m_svd_v.rows(), rank);
|
|
int b = 0;
|
|
|
|
for (i = 0; i < m_svd_w.size(); i++)
|
|
if (m_svd_w[i] > epsilon) {
|
|
for (j = 0; j < m_svd_v.rows(); j++)
|
|
basis(j, b) = m_svd_v(j, i);
|
|
b++;
|
|
}
|
|
|
|
m_nullspace = basis * basis.transpose();
|
|
|
|
for (i = 0; i < m_nullspace.rows(); i++)
|
|
for (j = 0; j < m_nullspace.cols(); j++)
|
|
if (i == j)
|
|
m_nullspace(i, j) = 1.0 - m_nullspace(i, j);
|
|
else
|
|
m_nullspace(i, j) = -m_nullspace(i, j);
|
|
|
|
return true;
|
|
}
|
|
|
|
void IK_QJacobian::SubTask(IK_QJacobian& jacobian)
|
|
{
|
|
if (!ComputeNullProjection())
|
|
return;
|
|
|
|
// restrict lower priority jacobian
|
|
jacobian.Restrict(m_d_theta, m_nullspace);
|
|
|
|
// add angle update from lower priority
|
|
jacobian.Invert();
|
|
|
|
// note: now damps secondary angles with minimum damping value from
|
|
// SDLS, to avoid shaking when the primary task is near singularities,
|
|
// doesn't work well at all
|
|
int i;
|
|
for (i = 0; i < m_d_theta.size(); i++)
|
|
m_d_theta[i] = m_d_theta[i] + /*m_min_damp * */ jacobian.AngleUpdate(i);
|
|
}
|
|
|
|
void IK_QJacobian::Restrict(VectorXd& d_theta, MatrixXd& nullspace)
|
|
{
|
|
// subtract part already moved by higher task from beta
|
|
m_beta = m_beta - m_jacobian * d_theta;
|
|
|
|
// note: should we be using the norm of the unrestricted jacobian for SDLS?
|
|
|
|
// project jacobian on to null space of higher priority task
|
|
m_jacobian = m_jacobian * nullspace;
|
|
}
|
|
|
|
void IK_QJacobian::InvertSDLS()
|
|
{
|
|
// Compute the dampeds least squeares pseudo inverse of J.
|
|
//
|
|
// Since J is usually not invertible (most of the times it's not even
|
|
// square), the psuedo inverse is used. This gives us a least squares
|
|
// solution.
|
|
//
|
|
// This is fine when the J*Jt is of full rank. When J*Jt is near to
|
|
// singular the least squares inverse tries to minimize |J(dtheta) - dX)|
|
|
// and doesn't try to minimize dTheta. This results in eratic changes in
|
|
// angle. The damped least squares minimizes |dtheta| to try and reduce this
|
|
// erratic behaviour.
|
|
//
|
|
// The selectively damped least squares (SDLS) is used here instead of the
|
|
// DLS. The SDLS damps individual singular values, instead of using a single
|
|
// damping term.
|
|
|
|
double max_angle_change = M_PI / 4.0;
|
|
double epsilon = 1e-10;
|
|
int i, j;
|
|
|
|
m_d_theta.setZero();
|
|
m_min_damp = 1.0;
|
|
|
|
for (i = 0; i < m_dof; i++) {
|
|
m_norm[i] = 0.0;
|
|
for (j = 0; j < m_task_size; j += 3) {
|
|
double n = 0.0;
|
|
n += m_jacobian(j, i) * m_jacobian(j, i);
|
|
n += m_jacobian(j + 1, i) * m_jacobian(j + 1, i);
|
|
n += m_jacobian(j + 2, i) * m_jacobian(j + 2, i);
|
|
m_norm[i] += sqrt(n);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < m_svd_w.size(); i++) {
|
|
if (m_svd_w[i] <= epsilon)
|
|
continue;
|
|
|
|
double wInv = 1.0 / m_svd_w[i];
|
|
double alpha = 0.0;
|
|
double N = 0.0;
|
|
|
|
// compute alpha and N
|
|
for (j = 0; j < m_svd_u.rows(); j += 3) {
|
|
alpha += m_svd_u(j, i) * m_beta[j];
|
|
alpha += m_svd_u(j + 1, i) * m_beta[j + 1];
|
|
alpha += m_svd_u(j + 2, i) * m_beta[j + 2];
|
|
|
|
// note: for 1 end effector, N will always be 1, since U is
|
|
// orthogonal, .. so could be optimized
|
|
double tmp;
|
|
tmp = m_svd_u(j, i) * m_svd_u(j, i);
|
|
tmp += m_svd_u(j + 1, i) * m_svd_u(j + 1, i);
|
|
tmp += m_svd_u(j + 2, i) * m_svd_u(j + 2, i);
|
|
N += sqrt(tmp);
|
|
}
|
|
alpha *= wInv;
|
|
|
|
// compute M, dTheta and max_dtheta
|
|
double M = 0.0;
|
|
double max_dtheta = 0.0, abs_dtheta;
|
|
|
|
for (j = 0; j < m_d_theta.size(); j++) {
|
|
double v = m_svd_v(j, i);
|
|
M += fabs(v) * m_norm[j];
|
|
|
|
// compute tmporary dTheta's
|
|
m_d_theta_tmp[j] = v * alpha;
|
|
|
|
// find largest absolute dTheta
|
|
// multiply with weight to prevent unnecessary damping
|
|
abs_dtheta = fabs(m_d_theta_tmp[j]) * m_weight_sqrt[j];
|
|
if (abs_dtheta > max_dtheta)
|
|
max_dtheta = abs_dtheta;
|
|
}
|
|
|
|
M *= wInv;
|
|
|
|
// compute damping term and damp the dTheta's
|
|
double gamma = max_angle_change;
|
|
if (N < M)
|
|
gamma *= N / M;
|
|
|
|
double damp = (gamma < max_dtheta) ? gamma / max_dtheta : 1.0;
|
|
|
|
for (j = 0; j < m_d_theta.size(); j++) {
|
|
// slight hack: we do 0.80*, so that if there is some oscillation,
|
|
// the system can still converge (for joint limits). also, it's
|
|
// better to go a little to slow than to far
|
|
|
|
double dofdamp = damp / m_weight[j];
|
|
if (dofdamp > 1.0) dofdamp = 1.0;
|
|
|
|
m_d_theta[j] += 0.80 * dofdamp * m_d_theta_tmp[j];
|
|
}
|
|
|
|
if (damp < m_min_damp)
|
|
m_min_damp = damp;
|
|
}
|
|
|
|
// weight + prevent from doing angle updates with angles > max_angle_change
|
|
double max_angle = 0.0, abs_angle;
|
|
|
|
for (j = 0; j < m_dof; j++) {
|
|
m_d_theta[j] *= m_weight[j];
|
|
|
|
abs_angle = fabs(m_d_theta[j]);
|
|
|
|
if (abs_angle > max_angle)
|
|
max_angle = abs_angle;
|
|
}
|
|
|
|
if (max_angle > max_angle_change) {
|
|
double damp = (max_angle_change) / (max_angle_change + max_angle);
|
|
|
|
for (j = 0; j < m_dof; j++)
|
|
m_d_theta[j] *= damp;
|
|
}
|
|
}
|
|
|
|
void IK_QJacobian::InvertDLS()
|
|
{
|
|
// Compute damped least squares inverse of pseudo inverse
|
|
// Compute damping term lambda
|
|
|
|
// Note when lambda is zero this is equivalent to the
|
|
// least squares solution. This is fine when the m_jjt is
|
|
// of full rank. When m_jjt is near to singular the least squares
|
|
// inverse tries to minimize |J(dtheta) - dX)| and doesn't
|
|
// try to minimize dTheta. This results in eratic changes in angle.
|
|
// Damped least squares minimizes |dtheta| to try and reduce this
|
|
// erratic behaviour.
|
|
|
|
// We don't want to use the damped solution everywhere so we
|
|
// only increase lamda from zero as we approach a singularity.
|
|
|
|
// find the smallest non-zero W value, anything below epsilon is
|
|
// treated as zero
|
|
|
|
double epsilon = 1e-10;
|
|
double max_angle_change = 0.1;
|
|
double x_length = sqrt(m_beta.dot(m_beta));
|
|
|
|
int i, j;
|
|
double w_min = std::numeric_limits<double>::max();
|
|
|
|
for (i = 0; i < m_svd_w.size(); i++) {
|
|
if (m_svd_w[i] > epsilon && m_svd_w[i] < w_min)
|
|
w_min = m_svd_w[i];
|
|
}
|
|
|
|
// compute lambda damping term
|
|
|
|
double d = x_length / max_angle_change;
|
|
double lambda;
|
|
|
|
if (w_min <= d / 2)
|
|
lambda = d / 2;
|
|
else if (w_min < d)
|
|
lambda = sqrt(w_min * (d - w_min));
|
|
else
|
|
lambda = 0.0;
|
|
|
|
lambda *= lambda;
|
|
|
|
if (lambda > 10)
|
|
lambda = 10;
|
|
|
|
// immediately multiply with Beta, so we can do matrix*vector products
|
|
// rather than matrix*matrix products
|
|
|
|
// compute Ut*Beta
|
|
m_svd_u_beta = m_svd_u.transpose() * m_beta;
|
|
|
|
m_d_theta.setZero();
|
|
|
|
for (i = 0; i < m_svd_w.size(); i++) {
|
|
if (m_svd_w[i] > epsilon) {
|
|
double wInv = m_svd_w[i] / (m_svd_w[i] * m_svd_w[i] + lambda);
|
|
|
|
// compute V*Winv*Ut*Beta
|
|
m_svd_u_beta[i] *= wInv;
|
|
|
|
for (j = 0; j < m_d_theta.size(); j++)
|
|
m_d_theta[j] += m_svd_v(j, i) * m_svd_u_beta[i];
|
|
}
|
|
}
|
|
|
|
for (j = 0; j < m_d_theta.size(); j++)
|
|
m_d_theta[j] *= m_weight[j];
|
|
}
|
|
|
|
void IK_QJacobian::Lock(int dof_id, double delta)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < m_task_size; i++) {
|
|
m_beta[i] -= m_jacobian(i, dof_id) * delta;
|
|
m_jacobian(i, dof_id) = 0.0;
|
|
}
|
|
|
|
m_norm[dof_id] = 0.0; // unneeded
|
|
m_d_theta[dof_id] = 0.0;
|
|
}
|
|
|
|
double IK_QJacobian::AngleUpdate(int dof_id) const
|
|
{
|
|
return m_d_theta[dof_id];
|
|
}
|
|
|
|
double IK_QJacobian::AngleUpdateNorm() const
|
|
{
|
|
int i;
|
|
double mx = 0.0, dtheta_abs;
|
|
|
|
for (i = 0; i < m_d_theta.size(); i++) {
|
|
dtheta_abs = fabs(m_d_theta[i] * m_d_norm_weight[i]);
|
|
if (dtheta_abs > mx)
|
|
mx = dtheta_abs;
|
|
}
|
|
|
|
return mx;
|
|
}
|
|
|
|
void IK_QJacobian::SetDoFWeight(int dof, double weight)
|
|
{
|
|
m_weight[dof] = weight;
|
|
m_weight_sqrt[dof] = sqrt(weight);
|
|
}
|
|
|