Julio Jerez wrote:Using cone and twist, this is a problem, for wants, if the joint passes the 180, it is possible that the meaning of the twist change sign, so it may snap. This is inevitable, even when using quat, and and step is too large.
It is possible to make handle 180 if the joint moves along the quaternion between the two frames.
That will be a short angle.
But the body of the joint would have to be different.
If I remember, in 4.00 it use the quaternion, not a cone and a twist angle.
This sounds like you're saying limit cone > 180 degrees is not possible, or if so a decomposition into cone / twist is no longer possible due to using quaternions.
But for me this works well. Posting code for reference, maybe it helps somebody.
It's confusing because i still mix math libraries and my cone limit is more complex. My cone is like a flat piece of pizza, but the pin is allowed to have some distance (angle) to that pizza, so i get kind of pizza extruded by cone. I do this for more natural human limits.
But the interesting part is the twist anyway.
EDIT: I remembered there is a problem with my quaternion joint, but i don't understand it fully yet.
The angle between min and max twist limits varies depending on the cone angle, and the effect is mostly noticeable when the cone is 90 degrees from its neutral position. So there is again a problem if the current cone forms 180 degrees.
Can't remember if the twist limit gets smaller or larger, but i think this creates a local minima / maxima, so likely it can cause discontinuities with simulation.
I did not notice this when testing the joint with a cone limit of 300 degrees, but i have never used such large limits beyond initial tests.
I guess it can be a practical solution where large limits are needed, but the method isn't perfect either.
- Code: Select all
void SubmitTwistSwingConstraints (ndConstraintDescritor& desc)
{
const ndFloat32 ×tep = desc.m_timestep;
const ndFloat32 &invTimestep = desc.m_invTimestep;
assert (timestep > 0.0f);
ndMatrix matrix0 = m_body0->GetMatrix();
ndMatrix matrix1 = m_body1->GetMatrix();
Mat4 matrix0_, matrix1_;
matrix0_ = (Mat4&)matrix0; matrix0_ *= (Mat4&)m_localMatrix0;
matrix1_ = (Mat4&)matrix1; matrix1_ *= (Mat4&)m_localMatrix1;
ndQuaternion q0 = m_body0->GetRotation();
ndQuaternion q1 = m_body1->GetRotation();
Quat q0_, q1_;
q0_ = (Quat&)q0; q0_ *= localRot0;
q1_ = (Quat&)q1; q1_ *= localRot1;
matrix0 = ToNdMatrix(matrix0_);
matrix1 = ToNdMatrix(matrix1_);
AddPointToPointConstraint (desc, matrix0, matrix1);
const Vec3& dir0 = matrix0_[0];
const Vec3& dir1 = matrix1_[0];
float dot = dir0.Dot (dir1);
if (dot < -0.999f) return;
if (twistLimitAngleP >= twistLimitAngleN) // do the twist
{
// factor rotation about x axis: Quat qt = q0.Inversed() * q1; float halfTwistAngle = atan (qt[0] / qt[3]);
float twistAngle = 2.0 * atan (
( ( ( (q0_[3] * q1_[0]) + (-q0_[0] * q1_[3]) ) + (-q0_[1] * q1_[2]) ) - (-q0_[2] * q1_[1]) ) /
( ( ( (q0_[3] * q1_[3]) - (-q0_[0] * q1_[0]) ) - (-q0_[1] * q1_[1]) ) - (-q0_[2] * q1_[2]) ) );
//Vec3 twistAxis = dir1;
Vec3 twistAxis_ = Vec3(dir0+dir1).Unit();
ndVector twistAxis (twistAxis_[0], twistAxis_[1], twistAxis_[2], 0.f);
if (twistLimitAngleP == twistLimitAngleN)
{
AddAngularRowJacobian(desc, twistAxis, ndFloat32(0.0f));
//NewtonUserJointAddAngularRow (joint, twistAngle - twistLimitAngleP, (float*)&twistAxis);
}
else if (twistAngle > twistLimitAngleP)
{
AddAngularRowJacobian (desc, twistAxis, twistAngle - twistLimitAngleP);
SetLowerFriction (desc, 0.f);
//NewtonUserJointAddAngularRow (joint, twistAngle - twistLimitAngleP, (float*)&twistAxis);
//NewtonUserJointSetRowMinimumFriction (joint, 0.0f);
}
else if (twistAngle < twistLimitAngleN)
{
AddAngularRowJacobian (desc, twistAxis, twistAngle - twistLimitAngleN);
SetHighFriction (desc, 0.f);
}
}
if (swingLimitAngle > 0 && dot < 0.999f) // do the swing (pizza cone limit)
{
Vec3 d = matrix1_.Unrotate (dir0);
Vec3 cone = d; cone[1] = 0; cone.Normalize();
if (cone[0] < swingSideAngleCos)
cone = Vec3 (swingSideAngleCos, 0, ((cone[2]<0) ? -swingSideAngleSin : swingSideAngleSin));
float angle = acos (max(-1.f, min(1.f, d.Dot (cone)))) - swingLimitAngle;
if (angle > 0)
{
Vec3 swingAxis_ = (matrix1_.Rotate(d.Cross(cone))).Unit();
ndVector swingAxis (swingAxis_[0], swingAxis_[1], swingAxis_[2], 0.f);
AddAngularRowJacobian (desc, swingAxis, angle);
SetLowerFriction (desc, 0.f);
}
}
}
Btw, i had very good progress on the ragdoll by replacing exact math with approximation to calculate target joint angular velocity from the pose my IK solver generates.
Here's a link:
https://theorangeduck.com/page/exponential-map-angle-axis-angular-velocityI knew about this before and wanted to try it for years, but i would not have thought what a difference it makes.
Using exact math there are problems with small changes because we need to normalize to get the rotation axis.
Now using the approximation instead those problems are gone, and accuracy and smoothness is much, much better. Actually my ragdoll animation looks so smooth it's no longer visible there is physics simulation involved. It looks like animation, and even too smooth to look realistic.
Really a game changer. Anyone trying to control stuff with motors wants to take a look at this, i propose.