Jondolf / avian

ECS-driven 2D and 3D physics engine for the Bevy game engine.
https://crates.io/crates/avian3d
Apache License 2.0
1.5k stars 119 forks source link

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

Open floppyhammer opened 1 year ago

floppyhammer commented 1 year ago

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 commented 1 year ago

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).

Jondolf commented 1 year ago

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.

floppyhammer commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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);
    }
}
Jondolf commented 1 year ago

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.

floppyhammer commented 1 year ago

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