NVIDIA-Omniverse / PhysX

NVIDIA PhysX SDK
BSD 3-Clause "New" or "Revised" License
2.62k stars 375 forks source link

D6 Gimbal issues caused by rotational limits #323

Open FlimFlamm opened 3 weeks ago

FlimFlamm commented 3 weeks ago

Steps to Trigger Behavior

  1. Make D6 joint with fully constrained linear axes (so it cant be displaced)
  2. Fully constrain the rotational X axis
  3. Partially constrain the rotational Y axis (-45 and +45 will be sufficient)
  4. Leave the rotational Z axis unconstrained
  5. Set angular drives with stiffness for Y and Z rotational axes
  6. Set target rotation of 179 in the Z drive (or (pi - 0.01) in radian space)
  7. Set target rotation of 45 in the Y drive (or (pi/4) in radian space)
  8. Set target rotation of 0 in the Z drive
  9. Set target rotation of 0 in the Y drive
  10. See the issue

Expected Behavior

The joint be back at it's starting position

Actual Behavior

The joint has accumulated rotation in the X axis (in which it should be constrained). It has accumulated a sort of gimbal error.

More information:

If the X axis is unconstrained and an angular drive with stiffness is used to keep it steady (as opposed to the hard and soft joint limits that are used when that axis is constrained) then the issue will not manifest. I believe that the joint limits suffer from some sort of gimbal-lock type of issue. This is definitely a problem because 179 (pi - 0.01) should be a perfectly valid target rotation, but in this configuration it causes the accumulation of error that essentially breaks the joint. This might also be related to the behavioural issues mentioned in the "PhysX limitations" document from IsaacSim, and explain why unexpected forces are or are not applied. (if you create this sort of configuration and simply try to have the Z axis smoothly swivel around like a turret would do, then error is accumulated in the X axis, and after a full rotation, anomalies start to emerge).

I discovered the issue when trying to use the D6 joint to make a freely rotating Azimuth+Elevation style targeting system (think telescope or gun turret). Having to remove the limit on the X axis and set some high stiffness is not ideal, and having to use two different revolute joints (an extra rigidbody) to achieve the same effect is also not ideal.

It's my hope that this set of reproduction steps is enough to track down the mathematical issue at play, so that i can be fixed.

EDIT: Adding a video just for clarity: Screencast from 2024-10-31 04-22-12.webm

vreutskyy commented 3 weeks ago

Hi @FlimFlamm, thanks for the report. It's said in PhysX's docs that D6 Joint has gimbal issues. To avoid them, try not to set limits higher than (or close to) 90° to the swing axes (Y and Z). In your setup, you could try setting -179/+179 limit to the twist axis - X, -45/+45 limit to one of the swing axis, say Z, and constrain the other one - Y. image

FlimFlamm commented 3 weeks ago

Hi @FlimFlamm, thanks for the report. It's said in PhysX's docs that D6 Joint has gimbal issues. To avoid them, try not to set limits higher than (or close to) 90° to the swing axes (Y and Z). In your setup, you could try setting -179/+179 limit to the twist axis - X, -45/+45 limit to one of the swing axis, say Z, and constrain the other one - Y. image

Just to make sure we're on the same page, in my setup, I fully constrained the twist axis (X), and fully unconstrained the Z swing axis (side to side swinging, so that it can swivel freely around 360 degrees). The up/down Y axis was set to -45 and +45.

The problem is that twist was accumulating when it should have been fully constrained in that axis. Setting it up as you describe leads to a joint that can swing around the Z, and twist along the X, but it doesn't achieve the azimuth+elevation style setup I as going for. The gimbal issues still emerge when trying this also (you set a high value, even less than half pi, in one of the axes, then set a high value in the other axis, then set them both back to zero in the same order, and gimbal problems will have occurred). AFAIK there should not be this severe of a gimbal problem with ranges under half-pi (it also goes away if the third axis is not constrained, but held in place with stiffness).

Possibly I can configure the joint so that the X twist axis becomes a swing axis, but it might take me a bit to figure out the right setup (although I suspect the gimbal issues will still remain).

vreutskyy commented 3 weeks ago

aa, sorry, I misread you setup. it would be easier if you could provide a usd file with the problematic setup along with the steps to reproduce the issue.

FlimFlamm commented 3 weeks ago

problematic_D6_setup.zip

Steps:

0) Press Play
1) Set Z angular drive to any value (45 will work)
2) Set Y angular drive to any value (20 will work)
3) Set Z angular drive back to 0 (the start position)
4) Set Y angular drive back to 0 (the start position
5) See that twist along the X axis has been accumulated

The issue would not occur if the X axis was not constrained (if stiffness in the X angular drive was being used instead of the soft/hard limits). To me this point to a gimbal issue specifically inside the hard or soft joint limits, but it's hard to say...

FlimFlamm commented 3 weeks ago

aa, sorry, I misread you setup. it would be easier if you could provide a usd file with the problematic setup along with the steps to reproduce the issue.

Video of reproduction for clarity:

Screencast from 2024-10-31 20-41-33.webm

That this occurs with angles under half-pi is pretty surprising. This should essentially act like a spherical joint.

aa, sorry, I misread you setup. it would be easier if you could provide a usd file with the problematic setup along with the steps to reproduce the issue.

vreutskyy commented 3 weeks ago

hm. in this file, twist axis (X) isn't really fully constrained. it has -1/+1 limits. and this residual rotation you see is actually 1° rotation allowed by this limits. if I set limits to 0/0 the residual rotation disappears.

vreutskyy commented 3 weeks ago

btw, the same goes for all translational limits. they are also -1/+1

vreutskyy commented 3 weeks ago
image
FlimFlamm commented 3 weeks ago

btw, the same goes for all translational limits. they are also -1/+1

Thanks so much!

I was following a practice that I had read from documentation somewhere, advising to set the low limit to 1, and the high limit to -1. Clearly that was wrong :D. Just tried your alteration and it seems to have solved the problem.

:pray: Thank you so much!

FlimFlamm commented 3 weeks ago

Problem solved: some poor documentation somewhere told me to set +1, -1 limits for low and high constraints respectively (to create an inaccessible buffer or something....) Clearly that as wrong! Setting constraints to 0/0 is the proper thing to do

vreutskyy commented 3 weeks ago

I just checked in the code to make sure, and it's actually not wrong. Setting the lower limit higher than the upper limit does mark the axis as locked. So after all it's a bug. Thanks for reporting.

FlimFlamm commented 3 weeks ago

The plot thickens!

Reopening to give a bit more info that might be helpful:

For whatever reason, setting the limits to 0 and 0 does fix the issue I was observing in the reproduction steps I gave, but it does not fully address the anomalies that happen when i try to make it do a steady swivel by altering rotational position (though I had chalked it up to normal gimbal/singularity problems). EG: with the limits at 0 and 0 (technically not "locked", but still fully constrained), we get this sort of behavior past half PI:

Note: the positions i am setting are always between -PI and +PI Screencast from 2024-10-31 23-58-29.webm

With the constraints fully locked though (1 and -1 low and high limit respectively), the behaviour gets much weirder:

Screencast from 2024-11-01 00-02-54.webm

If I alter the amount of constraint available in the Y axis (the up/down swing in this setup) from -45 +45 to -3 +3, then the results get even more odd: Screencast from 2024-11-01 00-07-16.webm

vreutskyy commented 3 weeks ago

hm. I can't reproduce it. I'm changing the position by hands up to 179, then jump to -179, and then move it up again. it moves smoothly.

also, can you try to remove the limits on X completely? you still have the drive forcing it to 0 position.

FlimFlamm commented 3 weeks ago

hm. I can't reproduce it. I'm changing the position by hands up to 179, then jump to -179, and then move it up again. it moves smoothly.

In the editor this seems to work smoothly. The above videos are with a playtime script that is incrementing the target position every physics step, so I think that's where/why it's accumulating the error.

I am working out of the IsaacLab repo so I don't have it set up to play with joint targets directly in IsaacSim native API (give me a bit and I'll figure that out though). In the meantime, here's the gist of what I'm doing:

# within a loop of some kind
step_count += 1
pi = math.pi
sim_delta_time = 0.0166666
z_target_position =  0.1 * step_count * sim_delta_time

# "wrap" the target to force it to be between -pi and pi
z_target_position = ((z_target_position + pi) % (2 * pi)) - pi

set_z_drive_target_position(z_target_position) # <--- pseudo code

If you increment or set the target position from a script at play-time, I'm guessing a similar error will manifest.

also, can you try to remove the limits on X completely? you still have the drive forcing it to 0 position.

I can't be super sure if I've not misconfigured this (apologies if so; the IsaacLab API has some abstractions that are still slightly opaque and easy to fudge for me), but these are the results I get for that case: Screencast from 2024-11-01 01-42-26.webm

Interestingly it smooths out once it has made a few rotations.

If setting the position every step from a play-loop doesn't cause the issues to manifest for you, we might be able to blame IsaacLab specifically. I'll try to perform the same tests in IsaacSim ASAP (today or tomorrow hopefully).

Let me know if there's anything more I can do to assist with the bug-find!