google-deepmind / mujoco

Multi-Joint dynamics with Contact. A general purpose physics simulator.
https://mujoco.org
Apache License 2.0
7.73k stars 765 forks source link

Uni-directional Contact with MPL Hand in Unity #1781

Open michaeljrose opened 1 month ago

michaeljrose commented 1 month ago

Hi,

I'm a student and I'm trying to use the MuJoCo Unity plug-in and HAPTIX to study human performance of referred robotic arm control. I am using the MPL hand and only have control of the wrist flexion DoF and a coupled grasp DoF (by study design). I am developing an interactive task where the user controlling the hand will have to interact with blocks (mj bodies) in the environment. I was wondering if there is a way to enable only one-directional contact between the hand and the blocks (i.e. the hand could pass through the blocks in one direction but contact in the other?)

I've attached a couple images below, one with a possible starting position of the hand and block and the second with a position I want the hand to achieve by passing through the block. However, at the position in the second block, if the user flexes the fingers to grasp the block, I want the block to now be contacted. Basically if the back of the hand is the first thing to contact the block I wand the hand to pass through, but if the inside of the hand/fingers contact the block first I want the contact to occur.

I know in Unity the Platform Effector 2D component would do this in 2D and I believe there are ways to do this in 3D -- I was wondering if there was a way to do this with mujoco objects? The way I imagine this would work the best is doing something like the Platform Effector where only the inside of the hand is able to make contact with the object.

Thanks, Michael

image image

Balint-H commented 1 month ago

Hello!

Do you just want the palm surface only to make contact, or not even that, provided the direction of movement is the correct one?

If you want the palm surface to always make contact, then you can add dedicated collision geometry only to the surface, then disable collisions of the prosthesis hand (with contype/conaffinity filtering).

If instead you really want the discontinuous behaviour to work, where even the palm surface collides conditionally, you'll have to do some logic in a MonoBehaviour.

My approach would be to have a script that subscribes a method to MjScene.Instance.ctrlCallback. That method should check the contact array of mjData, and if the object touches the back of the hand (add a geom with a large margin so it doesn't produce force just detects collisions).

Quick proposed un-tested code:


public class OneWayPassThroughManager : MonoBehaviour
{
    [SerializeField]
    List<MjGeom> managedGeoms;

    [SerializeField]
    MjGeom backHandCollider;

    public unsafe void CheckPassthrough(object sender, MjStepArgs e)
    {
        if (IsContacting(backHandCollider.MujocoId, e.data) && !managedGeoms.Any(g => IsContacting(g.MujocoId, e.data)))
        {
            foreach (var g in managedGeoms)
            {
                // Could maybe do it with contype and conaffinity instead, as I think this margin approach might still produce some low force? 
                // But then you need another way to check if you would collide with the hand or not (e.g. Unity colliders?)
                g.Settings.Solver.Margin = 1000; 
            }
            MjScene.Instance.RecreateScene();  //Or maybe set request scene recreation at LateUpdate flag;
        }
        else if (!IsContacting(backHandCollider.MujocoId, e.data) && !managedGeoms.Any(g => IsContacting(g.MujocoId, e.data)))
        {
            foreach (var g in managedGeoms)
            {
                g.Settings.Solver.Margin = 0;
            }
            MjScene.Instance.RecreateScene();  //Or maybe set request scene recreation at LateUpdate flag;
        }
    }

    private static unsafe IEnumerable<MujocoLib.mjContact_> GetContacts(MujocoLib.mjData_* data)
    {
        int ncon = data->ncon;
        return Enumerable.Range(0, ncon).Select(i => data->contact[i]);
    }

    private static unsafe bool IsContacting(int mjId, MujocoLib.mjData_* data)
    {
        return GetContacts(data).Any(c => c.geom1 == mjId || c.geom2 == mjId);
    }

    public void Awake()
    {
        MjScene.Instance.ctrlCallback += CheckPassThrough;
    }
}

You can refactor this to be a bit more efficient in terms of contact filtering.

Balint-H commented 1 month ago

However you can also consider alternatives, such as teleporting the hand and setting its pose so it is preshaped for grasping. (Or using a mocap solution so the user can move and align the hand themselves)