bevy_xpbd icon indicating copy to clipboard operation
bevy_xpbd copied to clipboard

joint strain limits, after which they break

Open mattdm opened this issue 2 years ago • 9 comments

I know I can make a system to do this, but it seems like it'd be nice for bevy_xpbd to take care of it. I'd like to configure the maximum strain a joint can take when pushed past its limits — either those restricted by the joint type, or by the configured limits of the joint within its allowed free rotations or translations.

I am imaging that this could do one of three things (possibly configurable per joint):

  1. If the joint exceeds any limit, destroy it.
  2. If the joint exceeds a restriction of the joint type, destroy it. If it exceeds the configured aligned or free limit, remove that limit (so now e.g. a turnstyle can spin freely).
  3. Do nothing, but send an event. This event should include what constraint is overpowered, so a system can respond appropriately.

3 is obviously the most powerful, but I can definitely think of cases where simply doing 1 or 2 would be handy.

mattdm avatar Oct 25 '23 13:10 mattdm

One option would be to do it like PhysX. A joint will break when the force or torque exceeds some user-defined threshold. Broken joints aren't simulated, but they're not despawned automatically, so you could re-enable them if you wanted to.

In our case, I think a flexible and ergonomic way to handle breakage would be to add a component like JointBroken when the joint is broken. This way, our constraint systems can just skip them using Without<JointBroken>, users can detect breakage with Added<JointBroken>, and people can "unbreak" joints by simply removing the component. Events could be used as well, but I think you can achieve the same result in a more flexible and efficient way with a component.

Jondolf avatar Oct 25 '23 16:10 Jondolf

Yeah, that makes sense.

I admit I'm thinking further than anything I have a current practical use for, but I like the idea of having different break conditions — forcing a crank or lever past its limit as something different than yanking it from the wall. Maybe this could be something like LimitBroken?

Am I over-thinking?

mattdm avatar Oct 25 '23 16:10 mattdm

I think it's better to have one simple component responsible for joint breaking behavior rather than having several different components with subtle differences that may be confusing for users. The component should have logical default behavior, like breaking when a force threshold is reached, since I feel like that's the most common use-case (like tearing a rope or cloth, or pulling the cork on a bottle).

The nice thing about having it as a component is that you could just override the default behavior and do whatever you want, like the "break on limit exceeded" case you're describing. You could just create a system that adds the JointBroken component when the desired criteria is met, like pulling a lever too far. I feel like these are niche enough that it doesn't make much sense to support built in as long as it's reasonably easy to do manually.

Jondolf avatar Oct 25 '23 17:10 Jondolf

In keeping with the joint constraint compliance thoughts, even without having different events or components for different types of breakage, it would be nice to have different limits in every axis and rotation. Or, at least, distinguish between the constrained/free (or limited) movements. (Except for distance joint!)

mattdm avatar Nov 07 '23 20:11 mattdm

We can have separate compliances and breaking thresholds for "locked" axes and the limits, but not for each individual translational/rotational axis, because it would probably be a lot more expensive. Currently, e.g. the prismatic joint can combine the corrections along both locked translational axes into one operation, but if they had different compliances, the axes would have to be handled separately.

Jondolf avatar Nov 07 '23 21:11 Jondolf

Or it's technically not that expensive actually; it would have to do just the computation below two or three times instead of once, and it's relatively cheap

let tilde_compliance = compliance / dt.powi(2);
(-c - tilde_compliance * lagrange) / (w_sum + tilde_compliance)

Jondolf avatar Nov 07 '23 21:11 Jondolf

Another issue is that the joint structs already have a lot of properties (look at e.g. SphericalJoint), and per-axis compliance and breaking thresholds would add even more. Each joint type also has different limits and DOFs, so the code isn't currently shared much. We also can't have a nice trait method for things like the breaking thresholds of different axis limits, so the breaking system would have to be implemented separately for each joint instead of using generics.

What we could do is have something like Rapier's GenericJoint that can be used for pretty much all joint types with configuration for each translational and rotational axis. Something like this very rough outline:

struct GenericJoint {
    translational_axes: [JointAxis; 3],
    rotational_axes: [JointAxis; 3],
    motors: [Option<JointMotor>; 3],
}

enum JointAxis {
    Free,
    Limited(JointLimit, ConstraintStrength),
    Locked(ConstraintStrength),
}

struct JointLimit {
    min: Scalar,
    max: Scalar,
}

struct ConstraintStrength {
    compliance: Scalar,
    lagrange: Scalar,
    force: Scalar, // or torque for rotational axes
    break_threshold: Scalar,
}

Then, e.g. PrismaticJoint would just contain GenericJoint with two locked translational axes and three locked rotational axes, and they could all be configured individually (same for motors, one per axis). If this was possible to do efficiently with XPBD, it would reduce code duplication significantly while also making it much easier to make new joint types since all of the DOFs would be handled by the single joint type. Not sure how viable it is, but maybe worth experimenting with.

Edit: The D6 joint in PhysX is similar to this generic joint too https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/docs/Joints.html#d6-joint

Jondolf avatar Nov 07 '23 21:11 Jondolf

What we could do is have something like Rapier's GenericJoint that can be used for pretty much all joint types with configuration for each translational and rotational axis. Something like this very rough outline:

Yeah, when I started actually looking at the implementation yesterday, that's what I expected. I can't think of anything that would make it impossible for xpbd, but it's a pretty big refactor.

mattdm avatar Nov 08 '23 20:11 mattdm

Yep, it's a big refactor, but it would be a pretty big improvement in terms of customizability, usability and maintainability, and it's better to do sooner rather than later (assuming it is possible in XPBD; I don't see why it wouldn't be).

I'll see if I can get an MVP working over the weekend. With a generic joint like this, implementing the limits, breaking thresholds and joint motors would be significantly easier as we don't need to implement them separately for each joint type. I'd like joint improvements to be a major focus for 0.4, and I think this could be the first step.

Jondolf avatar Nov 08 '23 20:11 Jondolf