How to use RevoluteConstraint?

Hey, I’m trying to make a double pendulum simulation with kiss3d and nphysics2. Do I need to use a RevoluteConstraint as the “strings”?

If so, I don’t quite understand how to apply this example to my code, in particular what the 2 Point arguments represent exactly https://github.com/rustsim/nphysics/blob/95500a81229e9380778138bab817b07c7119dd17/examples2d/constraints2.rs#L59

For example let’s say my pendulums start from positions (x1, y1) and (x2, y2), what Points would I need to provide?

Hi!

First you need to decide whether you want to model your double pendulum with constraints or with reduced-cordinates joints. In the example you mentioned, constraints are used, and the right constraint for your use-case is indeed RevoluteConstraint as the “strings”.

In RevoluteConstraint, the anchor positions are where you want to attach the string on each bodies it is attached to. The coordinates must be given in the local-space of the bodies they are attached to.

So, assuming they are RigidBody’s do I just use Point::origin() in both? When I do that, the two objects don’t behave like I expect (they just fall through each other and then rest at the “string”'s max length overlapping each other)

Ah, sorry, my advice was misleading (and kind of wrong actually). So, each point is a point expressed in the local-space of each RigidBody. What the constraints tells is that, at all times, these two points must coincide in world-space. So assuming you have one rigid body at (0, 0) and another one at (0, 10) you need to set the first anchor as (0, 0) and the second anchor is (0, -10). This will result in the second body rotating wrt the first body with its center of rotation at the center of the first body.

I tried this and while it’s better (the pendulums end up with the correct distances between each other) they still pass through each other and it looks like gravity doesn’t affect them and they are instead pushed in the direction of the strings… here’s a link to my code that does it, you should be able to just cargo run the repo to see the problem. If you comment out the constraints’ code they fall down and obey gravity so the issue is definitely in the constraints. Also try changing the thetas in line 124 to 2.0 or something to see completely unexplainable behavior

Thank you for sharing the code. There are a few problems here, but let me give you the correct code first:

    use ncollide2d::shape::Ball;
    use nphysics2d::volumetric::Volumetric; // for .unit_angular_inertia()


    let position1 = UnitComplex::new(theta1) * Translation2::new(0.0, 0.0 - FIRST_RADIUS);
    let position2 =
        position1 * UnitComplex::new(theta2) * Translation2::new(0.0, 0.0 - SECOND_RADIUS);

    // Build the first pendulum
    let ball = Ball::new(1.0);
    let i = ball.unit_angular_inertia();
    let pendulum1 = RigidBodyDesc::new()
        .position(position1)
        .mass(FIRST_MASS)
        .angular_inertia(i[(0, 0)] * FIRST_MASS)
        .build();
    let pendulum1 = bodies.insert(pendulum1);

    // Build the second pendulum
    let pendulum2 = RigidBodyDesc::new()
        .position(position2)
        .mass(SECOND_MASS)
        .angular_inertia(i[(0, 0)] * SECOND_MASS)
        .build();
    let pendulum2 = bodies.insert(pendulum2);

    let rope1 = RevoluteConstraint::new(
        BodyPartHandle(origin, 0),
        BodyPartHandle(pendulum1, 0),
        Point2::origin(),
        Point2::new(0.0, FIRST_RADIUS),
    );
    joint_constraints.insert(rope1);
    let rope2 = RevoluteConstraint::new(
        BodyPartHandle(pendulum1, 0),
        BodyPartHandle(pendulum2, 0),
        Point2::origin(),
        Point2::new(0.0, SECOND_RADIUS),
    );
    joint_constraints.insert(rope2);

You can see I changed a few things:

  • The way the initial positions are computed.
  • The initialization of the angular inertia of the two pendulums.
  • The initialization of the second anchor of each pendulum.

Why did you see objects passing through each other

The bodies should be initialized in a way that it satisfies the RevoluteConstraints. If they don’t satisfy these constraints at initialization-time, the physics engine will automatically move the rigid bodies in order to correct the constraint violation. In your case, the violation was due to the fact that, while the position was correctly set (well not completely correct because for non-zero theta angles they were wrong, hence my modifications), the anchor were incorrect. The anchor must be expressed in the local-space of each rigid bodies:

  • If you consider your first pendulum, its center of rotation relative to the ground is located FIRST_RADIUS on top of the first pendulum. So this is what it should be initialized to.
  • If you consider your second pendulum, its center of rotation relative the first pendulum is SECOND_RADIUS on top of the second pendulum.

Because these are in local-space, they are independent from the rotation of the objects (and thus for theta1 and theta2).

Why the objects did not seem affected by gravity

You did set the mass of the pendulum, but you did not set their angular inertia. Because they did not have any angular inertia, they were forbidden to rotate. And because they cant rotate, they can’t move at all because of the RevoluteConstraint (no rotation + revolute constraint = all degrees of freedom are locked). You may be interested by that question which is similar to this problem.

In the code above, I computed the angular inertia of a ball using the .unit_angular_inertia method multiplied by the ball’s mass.

Why the erratic behavior with theta = 0.2

This is because the bodies were placed in a way that violate the constraints, causing the engine to fix them by applying some artificial movements. You will see the correct position in the code above.

I see, so RevoluteConstraint wasn’t quite what I imagined - I guess what I imagined is closer to RevoluteJoint, where it keeps them a certain distance apart while allowing them both to rotate about the “connection” to the joint, while looks like RevoluteConstraint the second object’s center constant in the first object’s local space while allowing the second object to rotate?

I tested your code and it works, thanks for the detailed help!

Additionally, looks like in the simulation the "string"s are very jiggly as opposed to for example this simulation where their length is absolutely constant. Is there a way to fix that? This is my current code.

I see, so RevoluteConstraint wasn’t quite what I imagined - I guess what I imagined is closer to RevoluteJoint, where it keeps them a certain distance apart while allowing them both to rotate about the “connection” to the joint, while looks like RevoluteConstraint the second object’s center constant in the first object’s local space while allowing the second object to rotate?

Actually RevoluteConstraint and RevoluteJoint just implement two different mathematical model to simulate the same thing commonly used in robotics: revolute joints. They look like this (taken randomly from google image):

image

They are not, strictly speaking, constraints on the distance between the two bodies. It’s better seen as a joint that only allow one relative rotation between two bodies. The classical physics double pendulum is often modeled conceptually with revolute joints. And there are two ways to model a revolute joint mathematically:

  • Using reduced coordinates, i.e., the position of both bodies are analytically determined by two angles (theta1 and theta2) and the position of the ground. It is called “reduced coordinates” because you solve a system of differential equations for only two variables (the two thetas). This is the model implemented by RevoluteJoint in nphysics. Is supsect that the simulation from myphysicslab uses this approach too.
  • Using full coordinates, i.e. the position of both bodies are determined by a system of equations that constraint the degrees of freedoms of the two bodies. It is called “full coordinates” because you solve a system of differential equation with 6 variables in 2D (translation1, rotation1, translation2, rotation2) and 5 constraints that ensure the bodies don’t do any forbidden relative motion. This is the model implemented by RevoluteConstraint in nphysics.

Both models have their advantages and inconvenient. The reduced-coordinates approach is much more accurate because there is no way for the two bodies to achieve invalid position wrt. the joint. However, it can be much more computationally expensive for large joint system. The full-coordinates approach can result in constraints violation if the constraint solver does not converge (and this is where your “jiggly” problem comes from). However when there are lots of joints, the full-coordinates approach can be much more computationally efficient.

Additionally, looks like in the simulation the "string"s are very jiggly as opposed to for example this simulation where their length is absolutely constant. Is there a way to fix that? This is my current code.

There are a few issues here. First you are comparing two simulations with completely different inputs, so you can’t expect the same behavior numerically speaking! In your code, you are using huge objects. A good way to visualize things is to think that 1 unit in the physics world is 1 meter. In the simulation from myphysicslab, the balls are about 20cm wide. In your simulation, your are using balls of radius 20metters and your strings are 100m long. Because these values are so big, the constraints solver in nphysics will have a harder time to converge, hence the “jiggly” problem. You should use the same initial condition as in myphysicslab if you want to compare things.

Though maybe I understand why you did this: if you use smaller values, the 2D visualization becomes too small to understand. Here is some code that sets up a 2D camera in kiss3d (Sidescroll) with a proper zoom to visualize a smaller scene. I also modified the FIRST_RADIUS and SECOND_RADIUS values to 1.0:

const FIRST_RADIUS: f32 = 1.0;
const SECOND_RADIUS: f32 = 1.0;


fn main() {
    let mut window = Window::new("Double Pendulum");
    window.set_light(Light::StickToCamera);
    let mut camera = FirstPerson::new(Point3::new(0.0, -5.0, -0.0), Point3::new(0.0, -5.0, 0.0));
    let mut planar_camera = Sidescroll::new();
    planar_camera.set_zoom(100.0);
    camera.set_up_axis(Vector3::new(0.0, 1.0, 0.0));

    let pendulum1 = window.add_circle(0.1);
    let pendulum2 = window.add_circle(0.1);


    let mut model = Model {
        world: create_world(2.0, 3.0),
        pendulum1,
        pendulum2,
    };

    while window.render_with_cameras(&mut camera, &mut planar_camera) {
        for _ in 0..SIMULATION_SPEED_MULTIPLIER {
            model.world.step();
        }
        model.draw(&mut window);
    }
}

If you still think this is not accurate enough for your uses then you have two solutions:

  • You can either increase the number of constraint resolution iteration: mechanical_world.integration_parameters.max_velocity_iterations = 50.
  • Or you can use a multibody with RevoluteJoint (based on reduced-coordinates) instead of of using rigid bodies with RevoluteConstraint (based on full-coordinates). The RevoluteJoint approach will be much more accurate for the reasons mentioned earlier.

The jittering is gone with the smaller objects but I noticed that the system is losing energy over time. I intend this to act as a screensaver so if it loses energy it will become very boring quickly… Would using RevoluteJoint (and therefore the more accurate calculations) fix that? I’m trying to play with the example at nphysics.org but it looks like the chain in the Multibody example loses energy too… Damping is set to 0 so that’s not it either.

The damping is not set to 0 in the example of nphysics.org (except for the 3D universal joint which is indeed in perpetual motion). All joints are automatically initialized with some small non-zero damping. If you need a zero damping, you need to explicitly set it to zero with multibody.damping_mut().fill(0.0);.