Obi Official Forum

Full Version: Grab Rope - VR
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2 3 4 5 6
Hey everyone,

Just came to this forum for a completely unrelated reason and came across this thread. Coincidentally just solved this problem this week to build a grappling hook in the vanilla SteamVR 2.0 unity plugin. Proof:



The trick was getting steamVR to interact with the obi particles. Here's my ROUGH, unpolished code:



using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using Obi;
using Valve.VR;

[RequireComponent(typeof(ObiSolver))]
public class RopeClimb : MonoBehaviour
{
    public ObiSolver solver;
    protected Valve.VR.InteractionSystem.VelocityEstimator velocityEstimator;

    public GameObject Player;

    public float releaseVelocityTimeOffset = -0.011f;

    private CharacterController m_CharacterController;
    private bool detectingCollisions = true;

    private Valve.VR.InteractionSystem.Hand draggingHand = null;
    void Start()
    {
        m_CharacterController = Player.GetComponent<CharacterController>();
    }

    void Update()
    {
        // subscribe to collision
        if (detectingCollisions == true) {
            solver.OnCollision += Solver_OnCollision;
            detectingCollisions = false;
        }

        // only set upon successful collision + grip
        if (draggingHand != null) {
            DragMove(draggingHand);
        }
    }

    void Solver_OnCollision(object senderObi.ObiSolver.ObiCollisionEventArgs e)
    {
        var world = ObiColliderWorld.GetInstance();
        foreach (Oni.Contact contact in e.contacts)
        {
            // this one is an actual collision:
            if (contact.distance < 0.01)
            {
                ObiColliderBase collider = world.colliderHandles[contact.other].owner;
         
                if (collider != null && collider.gameObject.tag == "VRHand")
                {
                    Valve.VR.InteractionSystem.Hand hand = collider.gameObject.GetComponent<Valve.VR.InteractionSystem.Hand>();
                    // set that we are currently dragging
                    draggingHand = hand;
                }
            }
        }
    }

    void DragMove(Valve.VR.InteractionSystem.Hand hand) {
        Vector3 velocity;
        Vector3 massagedVelocity;
        Vector3 angularVelocity;
        Vector3 currentPositon = hand.transform.position;
        Valve.VR.InteractionSystem.GrabTypes startingGrabType = hand.GetGrabStarting();
        if (velocityEstimator != null) {
            velocityEstimator.BeginEstimatingVelocity();
        }
        velocity = hand.GetTrackedObjectVelocity(releaseVelocityTimeOffset);
        angularVelocity = hand.GetTrackedObjectAngularVelocity(releaseVelocityTimeOffset);
        massagedVelocity = -velocity / 25//arbitrary
        Valve.VR.InteractionSystem.GrabTypes bestGrabType = hand.GetBestGrabbingType();

        if ( bestGrabType != Valve.VR.InteractionSystem.GrabTypes.None && hand.currentAttachedObject == null) {
            hand.Hide();
            m_CharacterController.Move(massagedVelocity);

        }

        // upon release grip, stop dragmove state
        // TODO - unset when out of range (can't drag forever)
        if ( bestGrabType == Valve.VR.InteractionSystem.GrabTypes.None ) {
            hand.Show();
            draggingHand = null;
        };
    }
}





A couple of notes:

-I tagged the VR hands with the tag "VRHand".

-I added a simple box collider with "trigger" set to true and Obi Collider to Valve's VR hands on the Player object.

-You have to pass in the player object and Obi Solver to the script. There's probably a better way of doing this. Like I said, this is an unpolished script!

-If you want to have this "grab" the rope particle instead of climbing it in a dragmove style, you could apply the velocity from the hands ("velocity" in the script) to the velocity of the currently grabbed rope particle (could be derived from "collider" in the script here, could create new top-level private variable "activeParticle" and set it when grabbed).
(18-02-2021, 04:16 AM)mo1ok Wrote: [ -> ]-If you want to have this "grab" the rope particle instead of climbing it in a dragmove style, you could apply the velocity from the hands ("velocity" in the script) to the velocity of the currently grabbed rope particle (could be derived from "collider" in the script here, could create new top-level private variable "activeParticle" and set it when grabbed).

As Jose pointed out to me early in this thread, I think it's better create a new pin constraint between the grabbed particle and the hand if you want the other rope constraints to factor in rather than allowing the rope to stretch infinitely by overriding the velocity directly. This is my understanding at least! Nice to see someone else working on a similar project!
I have tried to add many Obi Particle Attachments so I could grab each one of them instead of particles but there are some problems with this approach. There is a digression from the actual rope and when you grab one Particle and the velocity does not apply to the rest of them.

Maybe I could make all other Rigid Bodies Kinematic apart from the one being grabbed.

But probably working with particles directly instead of Particle Attachments is the right way. Although I still cant find a solution for the weird jitter and rotations when I attach my hand to particles.
(22-02-2021, 12:11 PM)tpaslou Wrote: [ -> ]I have tried to add many Obi Particle Attachments so I could grab each one of them instead of particles but there are some problems with this approach.

Dynamic particle attachments create pin constraints for individual particles under the hood, so it's exactly the same as you manually creating pin constraints, only more convoluted.

Quote:There is a digression from the actual rope and when you grab one Particle the velocity does not apply to the rest of them.

There should not be any digression if the attachment (or pin constraints) are properly set up: no pin constraints inside colliders, appropriate offset passed to the pin constraint, etc.

Grabbing one particle will only set the velocity for that one particle, it can't possible affect other particles directly. The rest of particles will move as a result of distance/bend constraints in the rope.
(19-02-2021, 09:35 PM)Xanduffy Wrote: [ -> ]As Jose pointed out to me early in this thread, I think it's better create a new pin constraint between the grabbed particle and the hand if you want the other rope constraints to factor in rather than allowing the rope to stretch infinitely by overriding the velocity directly. This is my understanding at least!

100% correct Guiño. Pin constraints enable what's typically known in physics as two-way coupling: the rope will exert forces on the object it is pinned to, and the object will also exert forces on the rope. Both become "aware" of each other.

Directly setting the velocity or position of a particle only affects the particle, which means the object will act as if no rope was present at all. This is what static attachments do.

When grabbing objects in VR, you want your hands to behave within the physical constraints of the VR environment, which means you don't want to be able to stretch a rope indefinitely (as you wouldn't be able to lift a very heavy object, put your hands inside a wall, etc). The rope should resist stretching once it's completely tense, by applying a force to your hand that opposes the pulling force. So you need dynamic attachments (or pin constraints, which is what dynamic attachments use internally) for this.
(22-02-2021, 12:21 PM)josemendez Wrote: [ -> ]Dynamic particle attachments create pin constraints for individual particles under the hood, so it's exactly the same as you manually creating pin constraints, only more convoluted.


There should not be any digression if the attachment (or pin constraints) are properly set up: no pin constraints inside colliders, appropriate offset passed to the pin constraint, etc.

Grabbing one particle will only set the velocity for that one particle, it can't possible affect other particles directly. The rest of particles will move as a result of distance/bend constraints in the rope.

So I created a script which enables and disables Kinematic and Particle attachments based on the "node" grabbed. I want the kinematic nodes to follow the transform of the closest Particle. How can I achieve that via script ?
(23-02-2021, 12:42 PM)tpaslou Wrote: [ -> ]So I created a script which enables and disables Kinematic and Particle attachments based on the "node" grabbed. I want the kinematic nodes to follow the transform of the closest Particle. How can I achieve that via script ?

Should be pretty straightforward: iterate over all particle positions, find the closest one, and then set the kinematic object's position to that. See:
http://obi.virtualmethodstudio.com/tutor...icles.html

Obi exposes particle positions that you can iterate over, the rest of it is run of the mill code.
Let me know if you have trouble calculating distances, finding the closest, etc.


Edit: if the idea is to have one kinematic rigidbody per particle, and then use these for collision detection and grabbing, that's not a good one imho. Particles already perform collision detection and can be pinned to any position of a body, so this is just reinventing the wheel. Having a rigidbody per particle won't be very performant, either. A good solution is to iterate over contacts, find the one closest to the hand, and pin it (as Xanduffy's code does).
(23-02-2021, 12:45 PM)josemendez Wrote: [ -> ]Edit: if the idea is to have one kinematic rigidbody per particle, and then use these for collision detection and grabbing, that's not a good one imho. Particles already perform collision detection and can be pinned to any position of a body, so this is just reinventing the wheel. Having a rigidbody per particle won't be very performant, either. A good solution is to iterate over contacts, find the one closest to the hand, and pin it (as Xanduffy's code does).

I have used that code but the rope behaves unexpectedly when grabbing. It rotates at the point of grab and this affects the whole rope
(24-02-2021, 10:35 AM)tpaslou Wrote: [ -> ]I have used that code but the rope behaves unexpectedly when grabbing. It rotates at the point of grab and this affects the whole rope

It shouldn't do that, it's just constraining the position of a particle. Can you share a video of this?
(24-02-2021, 10:39 AM)josemendez Wrote: [ -> ]It shouldn't do that, it's just constraining the position of a particle. Can you share a video of this?

So this is the video :  https://vimeo.com/516147132

The code that I used is :

Code:
using UnityEngine;
using System.Collections.Generic;
using Obi;
using HurricaneVR.Framework.Shared;
using HurricaneVR.Framework.ControllerInput;

/**
* Sample component that makes a collider "grab" any particle it touches (regardless of which Actor it belongs to).
*/
[RequireComponent(typeof(ObiCollider))]
public class ObiGrabber : MonoBehaviour
{
    /*public HVRController RightController => HVRInputManager.Instance.RightController;
    public HVRController LeftController => HVRInputManager.Instance.LeftController;*/


    public HVRController handController;
    public bool canGrab = true;
    ObiSolver solver;
    ObiCollider obiCollider;
    public ObiRope rope;
    ObiSolver.ObiCollisionEventArgs collisionEvent;
    ObiPinConstraintsBatch newBatch;
    ObiConstraints<ObiPinConstraintsBatch> pinConstraints;

    void Awake()
    {
        solver = FindObjectOfType<ObiSolver>();
        obiCollider = GetComponent<ObiCollider>();
    }
    void Start()
    {
        InitializeController();


        // get a hold of the constraint type we want, in this case, pin constraints:
        pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
    }

    private void OnEnable()
    {

        if (solver != null)
            solver.OnCollision += Solver_OnCollision;
    }

    private void OnDisable()
    {
        if (solver != null)
            solver.OnCollision -= Solver_OnCollision;
    }

    private void InitializeController()
    {
        if (tag == "LeftHandGrabber")
        {
            handController = HVRInputManager.Instance.LeftController;
        }
        else if (tag == "RightHandGrabber")
        {
            handController = HVRInputManager.Instance.RightController;
        }
        else
        {
            Debug.LogError("Failed Initializing Controller on Rope Grabber");
        }
    }


    private void Solver_OnCollision(object sender, Obi.ObiSolver.ObiCollisionEventArgs e)
    {
        collisionEvent = e;
    }

    public void Grab()
    {
        var world = ObiColliderWorld.GetInstance();
        Debug.Log(pinConstraints);

        if (solver != null && collisionEvent != null)
        {
            Debug.Log("Collision");
            foreach (Oni.Contact contact in collisionEvent.contacts)
            {
                if (contact.distance < 0.01f)
                {
                    var contactCollider = world.colliderHandles[contact.bodyB].owner;
                    ObiSolver.ParticleInActor pa = solver.particleToActor[contact.bodyA];

                    Debug.Log(pa + " hit " + contactCollider);
                    if (canGrab)
                    {
                        if (contactCollider == obiCollider)
                        {
                            Debug.Log("Hand Collision");
                            var batch = new ObiPinConstraintsBatch();
                            int solverIndex = rope.solverIndices[contact.bodyA];
                            Vector3 positionWS = solver.transform.TransformPoint(solver.positions[solverIndex]); // particle position from solver to world space
                            Vector3 positionCS = obiCollider.transform.InverseTransformPoint(positionWS); // particle position from world to collider space
                            batch.AddConstraint(rope.solverIndices[contact.bodyA], obiCollider, positionCS, Quaternion.identity, 0, 0, float.PositiveInfinity);
                            batch.activeConstraintCount = 1;
                            newBatch = batch;
                            pinConstraints.AddBatch(newBatch);

                            canGrab = false;

                            // this will cause the solver to rebuild pin constraints at the beginning of the next frame:
                            rope.SetConstraintsDirty(Oni.ConstraintType.Pin);

                        }
                    }
                }
            }
        }
    }

    public void Release()
    {
        if (!canGrab)
        {
            Debug.Log("Release");
            pinConstraints.RemoveBatch(newBatch);
            rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
            canGrab = true;
        }
    }

    private void Update()
    {

        if (handController.TriggerButtonState.Active || handController.GripButtonState.Active)
        {
            Debug.Log("PRESSED");
            Grab();
        }

        else
        {
            Release();
        }

    }

}
Pages: 1 2 3 4 5 6