bevy_xpbd icon indicating copy to clipboard operation
bevy_xpbd copied to clipboard

Different joint behaviour when changing the translation and rotation of a rigidbody's parent

Open floppyhammer opened this issue 2 years ago • 7 comments
trafficstars

image

I use DistanceJoint to connect hair joints. When I move the model's root entity, the hair will not be affected. But when I change the rotation, it does have effect on the hair.

7

In addition, if I change the rotation too quickly, the game becomes super laggy.

floppyhammer avatar Sep 04 '23 07:09 floppyhammer

By the way, do we have SpringJoint (or something similar) in bevy_xpbd?

image

In the case of a DistanceJoint (the green line), the blue ball will move towards the red one. A so-called SpringJoint will try to keep the blue ball in its rest position (but will deform like a spring depending on the exerted forces).

floppyhammer avatar Sep 04 '23 08:09 floppyhammer

I use DistanceJoint to connect hair joints. When I move the model's root entity, the hair will not be affected. But when I change the rotation, it does have effect on the hair.

I'm not 100% sure, but if the strands of hair are children of the body you're moving, I think this is somewhat expected. Changes in the parent's Transform will propagate to the children, so the positions of the hair strands will change instantly as well. The relative position of the hair and the hair's "roots" (the joints) won't change, so you don't see any effect on the hair.

However, the rotation does have an effect, because the hair will snap to the parent's rotation just like the position. The hair won't be pointing directly down anymore, so it will move due to gravity.

If you instead change the parent's Position component, does that affect the hair? There might be an inconsistency in how hierarchies are handled differently when changing Transform vs. Position. Changing the Position should currently leave the children unaffected if that's what you want.

In the future I'll try to make this consistent and maybe have some component for controlling how transform changes affect children.

By the way, do we have SpringJoint (or something similar) in bevy_xpbd?

So you want to not allow the blue ball to "swing" around the red ball when the red ball is stationary, and to be kept at some rest distance along one axis (the green line) with a given stiffness?

The PrismaticJoint is very close to this. It only allows translation along one axis like you described, and you can use joint limits to determine the minimum and maximum rest distance. You can control the compliance (inverse of stiffness), but it currently affects the strength of the joint along all axes, not just the specified axis.

If you want a custom spring joint, it should be pretty easy to add by just copying PrismaticJoint and changing a few lines. Let me know if this is something you want and I can give more help as needed.

Jondolf avatar Sep 04 '23 15:09 Jondolf

If you instead change the parent's Position component, does that affect the hair?

The parent doesn't have Position component. Only hair joints were added a rigidbody, so I can't really answer this question.

The translation and rotation effect I expected is that if I move the parent, it will affect the child like below (maybe through the component PreviousPosition and PreviousRotation):

8

However, this might be something beyond what a standard physics engine provides. I may need to implement such behaviour with xpbd myself.

floppyhammer avatar Sep 05 '23 02:09 floppyhammer

If you want a custom spring joint, it should be pretty easy to add by just copying PrismaticJoint and changing a few lines. Let me know if this is something you want and I can give more help as needed.

PrismaticJoint is not really what I wanted. I realize my expression isn't accurate. I wanted something like this

7

Such a joint won't allow movement in the axis direction but allow rotation along all axes (like hair or clothes).

image

floppyhammer avatar Sep 05 '23 02:09 floppyhammer

I managed to get a close effect using custom constraint:

9

I'll just leave the code here in case someone is interested.

#[derive(Component)]
struct SpringConstraint {
    entity1: Entity,
    entity2: Entity,
    // Relative position from entity2 to entity1.
    relative_rest_position: Vector,
    lagrange: Scalar,
    compliance: Scalar,
}

impl PositionConstraint for SpringConstraint {}

impl XpbdConstraint<2> for SpringConstraint {
    fn entities(&self) -> [Entity; 2] {
        [self.entity1, self.entity2]
    }

    fn clear_lagrange_multipliers(&mut self) {
        self.lagrange = 0.0;
    }

    fn solve(&mut self, bodies: [&mut RigidBodyQueryItem; 2], dt: Scalar) {
        let [body1, body2] = bodies;

        // Local attachment points at the centers of the bodies for simplicity.
        let [r1, r2] = [Vector::ZERO, Vector::ZERO];

        // Compute the positional difference.
        let delta_pos = body1.current_position() - body2.current_position();

        // The current separation distance.
        let length = delta_pos.length();

        // The value of the constraint function. When this is zero, the constraint is satisfied.
        let c = delta_pos - self.relative_rest_position;

        // Avoid division by zero and unnecessary computation.
        if length <= 0.0 || c.length() == 0.0 {
            return;
        }

        let n = c.normalize();

        // Compute generalized inverse masses (method from PositionConstraint).
        let w1 = self.compute_generalized_inverse_mass(body1, r1, n);
        let w2 = self.compute_generalized_inverse_mass(body2, r2, n);
        let w = [w1, w2];

        // Constraint gradients, i.e. how the bodies should be moved
        // relative to each other in order to satisfy the constraint.
        let gradients = [n, -n];

        // Compute Lagrange multiplier update, essentially the signed magnitude of the correction.
        let delta_lagrange =
            self.compute_lagrange_update(self.lagrange, c.length(), &gradients, &w, self.compliance, dt);
        self.lagrange += delta_lagrange;

        // Apply positional correction (method from PositionConstraint).
        self.apply_positional_correction(body1, body2, delta_lagrange, n, r1, r2);
    }
}

floppyhammer avatar Sep 07 '23 08:09 floppyhammer

Isn't that just the DistanceJoint? It allows setting a rest distance (and optional min/max distance limits), which is what your custom constraint does.

Jondolf avatar Sep 07 '23 12:09 Jondolf

From my limited experience of using DistanceJoint, not exactly. This custom constraint keeps the body at its rest position (relative), while a DistanceJoint only limits movement along the joint direction.

Initial setup

image

How a DistanceJoint behaves (with gravity)

image

How this custom joint behaves (with gravity)

image

floppyhammer avatar Sep 08 '23 02:09 floppyhammer