kolenda
kolenda

Reputation: 2811

Clamp angle with continuous result

I'm writing my own Inverse Kinematics and working on joint constraints currently. It's mostly fine but I want to remove all discontinuities from the resulting animation, and constraints are the biggest problem.

For example let's say you're looking at a ball rotating around your head. It rotates right and you look right, up to your neck twisting limit. The ball still moves and when it passes your back then your head will suddenly be clipped against opposite limit, looking left. This is what we get with typical clamping and it gives me frame-to-frame discontinuity.

What I need is that your head will get from right to left during few frames. My first idea is to reflect the source direction with my X axis (between shoulders) and if the reflected direction meets constraint limits - return it. If it doesn't meet the limits - clip the reflected direction instead of the source one.

This should give me the nice and continuous movement and it's not a rocket science but there're a lot of details to take care of. My limits may be like min < max but also max < min, they may pass through switching angle value (like 360->0, or 180->-180) or not. Besides, when my allowed range is bigger than 180deg then the logic should be slightly different.

The amount of different cases build up very fast so I'm wondering if I could find it already done somewhere?

BTW: I'm using Unreal engine if it makes any diffference.

EDIT:

I'm sorry if I've misguided you with my head example - it was just an analogy to visualize my needs. I'm not going to use this code to orient a head bone (nor any other) in a final animation. I'm planning to use it as a very basic operation in solving IK. It's going to be used in every iteration in every IK chain, for every bone, many times. So it need to be as fast as possible. All the higher level concepts, like movement planning, dynamics, etc. will be added on another layer, if needed.

For now I'm just asking for a function like:

float clampAngle( float angle, float min, float max )

that would return a continuous value for changing inputs.

I'm working on different issues currently, but I'll post my code when I get back to this one.

Upvotes: 1

Views: 1693

Answers (2)

Julian Mann
Julian Mann

Reputation: 6476

If you think of this as a simulation it will be a lot clearer. You won't need any planning, as in your comment, because the next frame is calculated using only information available on the current frame.

Set up one joint for the neck as described in your first two paragraphs. It will flip from left to right in one frame as the ball comes around.

Then set up a second identical joint and write an angular spring that will rotate it towards the first joint over time. By adjusting spring coefficients - strength, damping etc - you will have control over the way the head rotates.

How to write an angular spring?

This may not the best way, but the code is pretty short and easy, so here goes...

Lets call the 2 joints master and a slave. You need to store rotation phi and angular velocity omega on the slave joint.

phi and omega are axis-angle vectors - a 3d vector whose magnitude is the number of radians to rotate around the axis defined by the vector. It makes simulating rotations pretty easy. Whether your joint rotations are stored as euler angles or matrices or quaternions, you'll probably have some classes un UE API to help extract axis/angle vectors.

When master joint changes, convert its rotation to axis-angle. Lets call this phi_m.

On the start frame, the Slave rotation phi should be set to the same value as master. phi = phi_m. It has no angular velocity, so omega is set to the zero vector (0,0,0).

Then you move forward one frame. A frame's length dt is maybe 1/60 sec or whatever.

While simulating you first work out a new value for phi_m. Then the difference between master and slave ( the vector phi_m - phi) represents the torque acting on the slave joint. The bigger the angle the stronger the torque. Assume mass is 1.0 then using F=ma, angular acceleration alpha is equal to the torque. That means the change in angular velocity for the slave joint over that frame is alpha*dt. Now we can work out new angular velocity: omega = omega + (alpha*dt). Similarly, the new rotation is the old rotation plus change in rotation over time (angular velocity). phi = phi + (omega * dt)

Putting it all together and adding in a coefficient for spring strength and damping, a simulation step can go something like this:

damping = 0.01 // between 0 and 1
strength = 0.2 // ..experiment
dt = currentTime-lastTimeEvaluated

phi_m = eulerToAxisAngle(master.rotate)
if (currentTime <= startTime || dt < 0) {
  slave.phi = phi_m
  slave.omega = (0,0,0)  
} else {
  alpha = (phi_m - slave.phi)*strength
  slave.omega = (slave.omega * (1.0 - damping)) + (alpha*dt)
  slave.phi = slave.phi + slave.omega*dt
}
slave.rotate = axisAngleToEuler(slave.phi)
lastTimeEvaluated = currentTime

Notice that damping just kills off a little of the stored velocity. If damping is very low, the joint will overshoot and oscillate into place - boing!!

Upvotes: 1

atb
atb

Reputation: 1462

one approach:

  • define the region where the constraint cannot be satisfied (cone behind the head)
  • measure of how far into this region your target is (angle between target and center of cone divided by half cone angle)
  • us this scalar value to smoothly mix your constrained object back to some pre-defined neutral position (ie turn head back to center/rest position smoothly as target moves behind the head)

Upvotes: 0

Related Questions