jrouwe / JoltPhysics

A multi core friendly rigid body physics and collision detection library. Written in C++. Suitable for games and VR applications. Used by Horizon Forbidden West.
MIT License
6k stars 374 forks source link

HingeConstraint does not behave as expected #1131

Closed fhoenig closed 1 month ago

fhoenig commented 1 month ago

The hinge constraint seems to work well, but only when mPoint1=mPoint2 and mAxis1=mAxis2, etc... Possibly this is a confusion on my end, but I would think the behavior with regards to these settings should be equivalent to the DistanceConstraint.

Here is one way to reproduce the (for me) odd behavior:

From the sample app:

{
    // Two bodies connected with a hard hinge
    Body *body1 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(1.0f)), RVec3(4, 5, 0), Quat::sIdentity(), EMotionType::Static, Layers::NON_MOVING));
    body1->SetCollisionGroup(CollisionGroup(group_filter, 0, 0));
    mBodyInterface->AddBody(body1->GetID(), EActivation::DontActivate);
    Body *body2 = mBodyInterface->CreateBody(BodyCreationSettings(new BoxShape(Vec3::sReplicate(1.0f)), RVec3(6, 5, 0), Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING));
    body2->SetCollisionGroup(CollisionGroup(group_filter, 0, 1));
    mBodyInterface->AddBody(body2->GetID(), EActivation::Activate);

    HingeConstraintSettings hinge;
    hinge.mSpace = JPH::EConstraintSpace::LocalToBodyCOM;
    //hinge.mPoint1 = hinge.mPoint2 = RVec3(5, 4, 0);
    //hinge.mHingeAxis1 = hinge.mHingeAxis2 = Vec3::sAxisZ();
    //hinge.mNormalAxis1 = hinge.mNormalAxis2 = Vec3::sAxisY();
    //hinge.mLimitsMin = DegreesToRadians(-10.0f);
    //hinge.mLimitsMax = DegreesToRadians(110.0f);
    mPhysicsSystem->AddConstraint(hinge.Create(*body1, *body2));
}

To keep it simple and remove the world to COM space transformations, the idea is that we leave everything to default, except setting the space. Now the two cubes will have their COM as the hinge frame origin, one cube dim from each other.

When the simulation starts the dynamic cube assumes the position of the other cube as if this was a distance constraint with min and max distance 0.0. One would expect the initial distance between mPoint1 and mPoint2 being retained.

Similar issues (but more confusing) seem to happen with multiple axes.

jrouwe commented 1 month ago

What you've created with the example code is this:

image

The constraint enforces the local space mPoint1 and mPoint2 to coincide in world space. It also enforces that the green axis align in world space. If the red axis align in world space we have HingeConstraint::GetCurrentAngle() == 0.

In your example, as soon as the simulation starts, the bodies move together so that their COM's coincide (because both points were set to 0):

image

and then they can rotate like this (the shared green axis):

image

fhoenig commented 1 month ago

Thanks for clarifying. This is indeed the behavior I have observed.

I guess the confusion came from HingeConstraintSettings when defined in world space. In that case the really only meaningful way to "fill out" that struct is to set point and axis to the same values, like in all examples. I thought you could define two relative rotational degrees of freedom with this single constraint. Like a wheel that rotates but also can "squat" sideways, like some cars when accelerating.