Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Very simple PrismaticJoint numerically explodes for certain angles #746

Open
hpmv opened this issue Oct 3, 2024 · 1 comment
Open

Very simple PrismaticJoint numerically explodes for certain angles #746

hpmv opened this issue Oct 3, 2024 · 1 comment

Comments

@hpmv
Copy link

hpmv commented Oct 3, 2024

Setup: Two rigidbody cubes, one at (0, 0, 0) and one at (1, 0, 0), with a prismatic joint between them with local anchors (0, 0, 0) and the global common axis of (1, 0, 0). Both objects are exempt from gravity, so there's no force other than the joint itself, and with the way the positions are set up, the joint is already satisfied.

However, if the second rigidbody already has a rotation transform, even though the local axis for the second rigidbody is specified so that the global axis is still (1, 0, 0), for some rotations, the simulation explodes even in one step.

    #[test]
    fn test_prismatic_joint() {
        for i in 0..8 {
            let gravity = Vector3::new(0.0, -9.81, 0.0);
            let mut integration_parameters = IntegrationParameters::default();
            integration_parameters.dt = 0.016;
            let mut island_manager = IslandManager::new();
            let mut broad_phase = DefaultBroadPhase::new();
            let mut narrow_phase = NarrowPhase::new();
            let mut impulse_joint_set = ImpulseJointSet::new();
            let mut multibody_joint_set = MultibodyJointSet::new();
            let mut ccd_solver = CCDSolver::new();
            let mut query_pipeline = QueryPipeline::new();
            let mut physics_pipeline = PhysicsPipeline::new();
            let mut rigid_body_set = RigidBodySet::new();
            let mut collider_set = ColliderSet::new();

            let rotation = AngVector::new(0.0, std::f32::consts::PI / 2.0 * i as f32, 0.0);
            let local_axis_2 = Rotation::new(rotation).inverse() * Vector3::x_axis();
            assert!((Rotation::new(rotation) * local_axis_2).dot(&Vector3::x_axis()) > 0.999);

            let body1 = rigid_body_set.insert(
                RigidBodyBuilder::dynamic()
                    .translation(Vector3::new(0.0, 0.0, 0.0))
                    .gravity_scale(0.0)
                    .build(),
            );
            let body2 = rigid_body_set.insert(
                RigidBodyBuilder::dynamic()
                    .translation(Vector3::new(1.0, 0.0, 0.0))
                    .rotation(rotation)
                    .gravity_scale(0.0)
                    .build(),
            );

            let collider1 = ColliderBuilder::cuboid(1.0, 1.0, 1.0);
            let collider2 = ColliderBuilder::cuboid(1.0, 1.0, 1.0);

            collider_set.insert_with_parent(collider1, body1, &mut rigid_body_set);
            collider_set.insert_with_parent(collider2, body2, &mut rigid_body_set);

            let joint = PrismaticJointBuilder::new(Vector3::x_axis())
                .local_anchor1(Point3::new(0.0, 0.0, 0.0))
                .local_anchor2(Point3::new(0.0, 0.0, 0.0))
                .local_axis1(Vector3::x_axis())
                .local_axis2(local_axis_2)
                .contacts_enabled(false)
                .build();

            impulse_joint_set.insert(body1, body2, joint, true);

            physics_pipeline.step(
                &gravity,
                &integration_parameters,
                &mut island_manager,
                &mut broad_phase,
                &mut narrow_phase,
                &mut rigid_body_set,
                &mut collider_set,
                &mut impulse_joint_set,
                &mut multibody_joint_set,
                &mut ccd_solver,
                Some(&mut query_pipeline),
                &(),
                &(),
            );

            let translation1 = rigid_body_set.get(body1).unwrap().position().translation;
            let translation2 = rigid_body_set.get(body2).unwrap().position().translation;

            println!("Case {}:", i);

            println!(
                "  Body 1: {:.4} {:.4} {:.4}",
                translation1.x, translation1.y, translation1.z
            );
            println!(
                "  Body 2: {:.4} {:.4} {:.4}",
                translation2.x, translation2.y, translation2.z
            );
        }
    }

This selects 8 different angles to specify the same global axis, but some simulations are stable, but some are not:

Case 0:
  Body 1: 0.0000 0.0000 0.0000
  Body 2: 1.0000 0.0000 0.0000
Case 1:
  Body 1: 0.0000 0.0000 0.0000
  Body 2: 1.0000 -0.0000 -0.0000
Case 2:
  Body 1: 0.0000 -45756184.0000 0.0000
  Body 2: 0.0000 45756184.0000 0.0000
Case 3:
  Body 1: 0.2545 -1.0026 0.4699
  Body 2: 0.7455 1.0026 -0.4699
Case 4:
  Body 1: 0.0000 -0.0000 0.0000
  Body 2: 1.0000 0.0000 -0.0000
Case 5:
  Body 1: 0.0000 -0.0000 0.0000
  Body 2: 1.0000 0.0000 -0.0000
Case 6:
  Body 1: 0.7500 -23258734.0000 -22446404.0000
  Body 2: -0.5000 23258734.0000 22446404.0000
Case 7:
  Body 1: 0.0000 -7340032.0000 -75863659905024.0000
  Body 2: 0.0000 7340032.0000 75863659905024.0000
@hpmv
Copy link
Author

hpmv commented Oct 3, 2024

actually i luckily figured this out... prismatic joint's "local axis" approach is not sound, as it arbitrarily derives the other two axes based on that one axis vector. If the resulting frames end up being unaligned then the solver goes nuts. If I use a GenericJointBuilder and specify sane frames, then this is all fine.

The docs mention that there's the ability to specify a local tangent axis but that is nowhere to be found in the code. If that were implemented (or automatically calculated correctly like the document says), then we should be good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant