Constraint limit... In world space?

Hi,

Since 2.0 does not have (yet) ContraintComponents, I did set myself the task to create my own all-do constraint component using the Generic6DoFConstraint.

I managed to make it a good component that I can use to bind rigid bodies together and even offset the constraint position relatively to the child (in its local space, to make things easier to work with in Prefabs).

However, I’m kinda hitting a wall there in the fact that it looks like all the limits of the constraint are set in world space?

Or… If they are set in Local Space, do I need to rotate the joint itself? (Then how? Haha)

I’m trying to dig into bullet a little bit but I am fairly disappointed at the lack of documentation/complex examples I can find around those. :confused:

If you’ve got any idea on how to make those limits local spaced, please let me know (doing a Vector3.Transform will not work since it will basically still try to use world axis and just end up allowing more axises to rotate than I want to…)

Thanks!

Hi!
Yes sadly there is still no easy way to set constraints and probably there are a few bugs laying around too!
Anyway limits should be in local space.
Are you setting the frame matrices properly? Using constructor static method and or setframes?

Hi sinkingsugar!

As far as I know, yes I am setting my frames properly since my constraint’s center gets set exactly where I do intend it to be, relatively to the child.

This is my setup:


EDIT: it says -2.5 for the RelativeContraintLocation. But ignore it, the proper value is 2.5 and it works fine to have the constraint place where the cube is. :slight_smile:

The cube at the bottom, 2.5 units away from the selected cube, is where my constraint is meant to be (relative to the cube). The cube on the right of the screenshot is the parent, which is a kinematic rigid body (0 mass).

You can also see the current parameters of my component on the far right.

Here is the code that’s meant to do the thing, As you will see, I’m pretty sure that my parent frame is correct. I’m very confused about the child’s frame though…

        RigidbodyComponent parentBody = null; //The Rigid body this entity will attach to.
        RigidbodyComponent childBody = Entity.Get<RigidbodyComponent>(); //This entity's rigid body.
        Generic6DoFConstraint newConstraint = null;

        if (childBody.ColliderShapes.Count <= 0)
        {
            Console.WriteLine("Entity's rigid body has no collider shape. No constraint will be created.");
            return;
        }

        Matrix childFrame = Matrix.Translation(RelativeConstraintLocation);
        Quaternion childWorldRotation = Quaternion.RotationMatrix(Entity.Transform.WorldMatrix);

        if (ParentEntity?.GetAll<RigidbodyComponent>().Count(component => true) == 1)
        {
            parentBody = ParentEntity.Get<RigidbodyComponent>(); //The Rigid body this entity will attach to.

            if (parentBody.ColliderShapes.Count <= 0)
            {
                Console.WriteLine("Parent entity's rigid body has no collider shape. No constraint will be created.");
                return;
            }

            // Copy of the RelativeConstraintLocation vector, just to make sure and manipulate it.
            Vector3 parentOffset = RelativeConstraintLocation;

            // Rotates the offset using world rotation so that it lines up with the local space to compute the parent's frame constraint point.
            parentOffset = Vector3.Transform(parentOffset, childWorldRotation);

            // Calculates the position of the constraint center taking into account the rotation of the child (since the center is in its relative space)
            Vector3 parentFrameVector = (Entity.Transform.WorldMatrix.TranslationVector - ParentEntity.Transform.WorldMatrix.TranslationVector) + parentOffset;
            // Definition of the parent frame translation, basically the local offset to the constraint center put in a matrix.
            Matrix parentFrame = Matrix.Identity * Matrix.Translation(parentFrameVector);
            // Multiplying the matrix to counter-rotate the constraint so that it does not line up with the parent, but keeps world space regardless of the parent's rotation (otherwise it will just re-align with it and the whole system will rotate)
            parentFrame *= Matrix.RotationQuaternion(QuaternionInverse(Quaternion.RotationMatrix(ParentEntity.Transform.WorldMatrix)));
            // Defining the child's frame which is basically the relative offset for the translation. I'm not sure I need to counter-rotate it in this case of rotate it.. those frames usage is a bit opaque and hard to grasp...
            childFrame = Matrix.Identity * Matrix.Translation(RelativeConstraintLocation);

            Console.WriteLine("Parent anchor point = {0} \nChild anchor point = {1}", parentFrame.TranslationVector, childFrame.TranslationVector);

            // Make sure that those bodies cannot sleep.
            parentBody.CanSleep = childBody.CanSleep = false;

            // Create the constraint and returns it as a Generic6DoFConstraint
            newConstraint = Simulation.CreateConstraint(ConstraintTypes.Generic6DoF, parentBody, childBody, parentFrame, childFrame, true) as Generic6DoFConstraint;
        }
        else if (ParentEntity == null || ParentEntity?.GetAll<RigidbodyComponent>().Count(component => true) != 1)
        {
            newConstraint = Simulation.CreateConstraint(ConstraintTypes.Generic6DoF, childBody, childFrame, true) as Generic6DoFConstraint;
        }

        if (newConstraint != null)
        {
            // Sets limits... In world space? :'( 
            newConstraint.AngularLowerLimit = Vector3DegreesToRadians(AngularLowerLimit);
            newConstraint.AngularUpperLimit = Vector3DegreesToRadians(AngularUpperLimit);
            newConstraint.LinearLowerLimit = LinearLowerLimit;
            newConstraint.LinearUpperLimit = LinearUpperLimit;

            // Adds constraint to simulation.
            this.GetSimulation().AddConstraint(newConstraint, DisableCollisionBetweenBodies);

            if (parentBody != null)
            {
                parentBody.ClearForces();
                parentBody.AngularVelocity = parentBody.LinearVelocity = Vector3.Zero;
            }

            childBody.ClearForces();
            childBody.AngularVelocity = childBody.LinearVelocity = Vector3.Zero;
        }

I did put some comments to help read and understand what I’m doing that’s important in there. (also, yes I’m pre-setting the childFrame, but I did re-set it later, it’s not optimal yet, things that will get cleaned up afterward :slight_smile: )

There is also the function “QuaternionInverse” which is defined in the following way since matrix.invert does not return a new matrix but only modified the one existing. :slight_smile:

    private Quaternion QuaternionInverse(Quaternion quat)
    {
        quat.Invert();

        return quat;
    }

To be honest, I hope that I just messed up my frames. But if that’s not the case… uhhh. That will be a huge problem.

There seems to be a couple of bugs with the limits as well yes, since setting up the Y axis as free axis and having my DynamicCube revolve around it resulted in 180 rotations only… When it reached either side… It got brutally sent back the other way.
EDIT 2: This issue only happens on Y axis, X and Z rotate just fine. :o

Thanks for taking the time to help me with this. :slight_smile:

I see that you are setting angular limits correctly as radians.
I will look into it.
It would make it much easier and faster if you could send me a sample project. :slight_smile:

Also I noticed that you are using the constructor with just one Rigidbody, this will indeed create a constraint between the Rigidbody and the world. So maybe that is the issue.

Try use Constraint CreateConstraint(ConstraintTypes type, RigidbodyComponent rigidBodyA, RigidbodyComponent rigidBodyB, Matrix frameA, Matrix frameB, bool useReferenceFrameA = false)

I will take a look at packaging you a sample project. (maybe even the current one since it’s just about testings anyway).

Also, there is the CreateConstraint with a single rigidbody, but this is a fallback for the case where there is no ParentEntity specified. I am not going through it currently :slight_smile: (I checked in debugging with breakpoints and step by step)

Alright, after some struggle and starting my frame computations from scratch, first setting up the rotations and then the translation, I managed to have it working!!! :smiley:
Thanks for your time and help :slight_smile:

In the end, the limits are not in world space, they are in frame-space. So you need to rotate the frames properly to orient the whole constraint. In this case, I was especially missing the application of the child’s world rotation on the parent frame before canceling the rotation of the parent.

Here find the current code for the script, it’s still only a baby part of it, but it allows to setup a 6DOF joint following Bullet’s standard.

public class SuspensionConstraint : AsyncScript
{
    // Declared public member fields and properties will show in the game studio
    public bool Enabled = false;

    public Entity ParentEntity = null;

    public bool DisableCollisionBetweenBodies = false;

    public Vector3 RelativeConstraintLocation = Vector3.Zero;
    public Vector3 AngularLowerLimit = Vector3.One;
    public Vector3 AngularUpperLimit = -Vector3.One;
    public Vector3 LinearLowerLimit = Vector3.Zero;
    public Vector3 LinearUpperLimit = Vector3.Zero;
    //public RotationalLimitMotor RotationalLimitMotors;
    //public TranslationalLimitMotor TranslationalLimitMotors;

    public override async Task Execute()
    {
        // Initialization of the script.

        if (!Enabled)
        {
            return;
        }

        if (Entity.GetAll<RigidbodyComponent>().Count(component => true) != 1)
        {
            Console.WriteLine("Entity {0} has {1} RigidBodyComponent where it should only have 1. No Constraint will be created", Entity.Name, Entity.GetAll<RigidbodyComponent>().Count(component => true));
            return;
        }

        RigidbodyComponent parentBody = null; //The Rigid body this entity will attach to.
        RigidbodyComponent childBody = Entity.Get<RigidbodyComponent>(); //This entity's rigid body.
        Generic6DoFConstraint newConstraint = null;

        if (childBody.ColliderShapes.Count <= 0)
        {
            Console.WriteLine("Entity's rigid body has no collider shape. No constraint will be created.");
            return;
        }

        Matrix childFrame = Matrix.Translation(RelativeConstraintLocation);
        Quaternion childWorldRotation = Quaternion.RotationMatrix(Entity.Transform.WorldMatrix);

        if (ParentEntity?.GetAll<RigidbodyComponent>().Count(component => true) == 1)
        {
            parentBody = ParentEntity.Get<RigidbodyComponent>(); //The Rigid body this entity will attach to.

            if (parentBody.ColliderShapes.Count <= 0)
            {
                Console.WriteLine("Parent entity's rigid body has no collider shape. No constraint will be created.");
                return;
            }

            Quaternion parentWorldRotation = Quaternion.RotationMatrix(ParentEntity.Transform.WorldMatrix);

            // Resets Matrices to Identity (to start from scratch)
            Matrix parentFrame = Matrix.Identity;
            childFrame = Matrix.Identity;

            // Child frame only requires the local offset since the rotation is the one of the
            // object, so in local space no rotation
            childFrame *= Matrix.Translation(RelativeConstraintLocation);
            // sets the parent frame rotation first, applying the child rotation and then the
            // invert rotation of the parent (to ignore it and have the constraint in local space
            // of the child)
            parentFrame *= Matrix.RotationQuaternion(childWorldRotation);
            parentFrame *= Matrix.RotationQuaternion(QuaternionInverse(parentWorldRotation));

            // Computes the direction vector between the child and and parent (parent to child)
            // and adds the relative offset rotated by the child world rotation.
            Vector3 childParentDistance = Entity.Transform.WorldMatrix.TranslationVector - ParentEntity.Transform.WorldMatrix.TranslationVector + Vector3.Transform(RelativeConstraintLocation, childWorldRotation);
            // Rotates the direction vector between child/parent by the inverse of the parent
            // world rotation to align it with the original child position
            childParentDistance = Vector3.Transform(childParentDistance, QuaternionInverse(parentWorldRotation));
            // Translate the
            parentFrame *= Matrix.Translation(childParentDistance);

            // Make sure that those bodies cannot sleep.
            parentBody.CanSleep = childBody.CanSleep = false;

            // Create the constraint and returns it as a Generic6DoFConstraint
            newConstraint = Simulation.CreateConstraint(ConstraintTypes.Generic6DoF, parentBody, childBody, parentFrame, childFrame, true) as Generic6DoFConstraint;
        }
        else if (ParentEntity == null || ParentEntity?.GetAll<RigidbodyComponent>().Count(component => true) != 1)
        {
            newConstraint = Simulation.CreateConstraint(ConstraintTypes.Generic6DoF, childBody, childFrame, true) as Generic6DoFConstraint;
        }

        if (newConstraint != null)
        {
            newConstraint.BreakingImpulseThreshold = float.MaxValue;
            newConstraint.AngularLowerLimit = Vector3DegreesToRadians(AngularLowerLimit);
            newConstraint.AngularUpperLimit = Vector3DegreesToRadians(AngularUpperLimit);
            newConstraint.LinearLowerLimit = LinearLowerLimit;
            newConstraint.LinearUpperLimit = LinearUpperLimit;

            // Adds constraint to simulation.
            this.GetSimulation().AddConstraint(newConstraint, DisableCollisionBetweenBodies);

            if (parentBody != null)
            {
                parentBody.ClearForces();
                parentBody.AngularVelocity = parentBody.LinearVelocity = Vector3.Zero;
            }

            childBody.ClearForces();
            childBody.AngularVelocity = childBody.LinearVelocity = Vector3.Zero;
        }
    }

    private Vector3 Vector3DegreesToRadians(Vector3 degreeVector3)
    {
        return new Vector3(MathUtil.DegreesToRadians(degreeVector3.X), MathUtil.DegreesToRadians(degreeVector3.Y), MathUtil.DegreesToRadians(degreeVector3.Z));
    }

    private Quaternion QuaternionInverse(Quaternion quat)
    {
        quat.Invert();

        return quat;
    }
}

I’ll keep improving it for myself, so only use it if it can help you achieve your goals with something similar for now.

I mainly commented the hard maths about frames computation, because this was the hard part. The rest of it is just checks to ensure that the parent and child have rigid bodies etc etc. And a fallback in case there is no parent (so you can still constraint an entity to the world).

Cheers, I’ll keep you posted. :slight_smile:

1 Like