Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Grab Rope - VR
#11
(14-12-2020, 04:24 PM)zig420 Wrote: I've been trying to get this working for weeks. It would be nice to have a clear tutorial or steps on how to get this working.

There really isn't much to it. Just call the Grab() method in response to user pressing a button and the Release() method in response to that same button being released. I don't think a tutorial would be required just for that? Could you describe the issues you're experiencing?
Reply
#12
(23-09-2020, 03:17 PM)josemendez Wrote: Here's a sample script (for Obi 5.6) using the particle API and contact callbacks to grab any particles in contact with a collider/trigger.

This script is fantastic and got me very close to what I'm currently aiming for, major thanks! I seem to be having two issues however. Firstly, it is quite difficult to successfully grab the particles as the obi collider attached to the player hand is constantly pushing the rope away and seemingly breaking the connection, and secondly, it appears the rope can only be grabbed with one hand at a time.

Am I correct in assuming that increasing the acceptable contact distance within the grab method should resolve that first problem?
Some guidance on how to allow two simultaneous 'grabbers' would be greatly appreciated.
Thanks again Jose!
Reply
#13
After some further testing it appears the problem was not that the hand collider was pushing the particles away, rather that it appears there is only a short section at the beginning of the rope which allows me to grab particles. I am changing the rope length at run time and the problem seems most prevalent when the rope is hanging vertically, while grabbing a horizontal rope at rest seems to work fine. I've gone through the script and I can't identify why this would be, as it seems the only requirement for particles to be grabbed is their distance to the hand. Is there anything such as other particle attachments that may be overriding this script?
Reply
#14
(27-01-2021, 05:05 PM)Xanduffy Wrote: After some further testing it appears the problem was not that the hand collider was pushing the particles away, rather that it appears there is only a short section at the beginning of the rope which allows me to grab particles. I am changing the rope length at run time and the problem seems most prevalent when the rope is hanging vertically, while grabbing a horizontal rope at rest seems to work fine. I've gone through the script and I can't identify why this would be, as it seems the only requirement for particles to be grabbed is their distance to the hand. Is there anything such as other particle attachments that may be overriding this script?

You should not use this script to grab the rope if yoy expect to be able to do it with both hands at once. The reason for this is that it basically parents particles to the colliders in touch, which will enable to to overstretch the rope.

Unless overstretching is fine for you, a better approach would be to use pin constraints for this and use two-way coupling with the hands (similar to what half-life alyx does). That way the VR hands would not be able to pull further once the rope is completely taut.

Regarding the issue with not being able to grab certain particles, there’s no reason for it that I can think of. Only contact distance between colliders is taken into account. This is a long shot but: maybe  there’s no contacts being generated for some particles, due to them having the same phase as the collider?
Reply
#15
(27-01-2021, 09:03 PM)josemendez Wrote: You should not use this script to grab the rope if yoy expect to be able to do it with both hands at once. The reason for this is that it basically parents particles to the colliders in touch, which will enable to to overstretch the rope.

Unless overstretching is fine for you, a better approach would be to use pin constraints for this and use two-way coupling with the hands (similar to what half-life alyx does). That way the VR hands would not be able to pull further once the rope is completely taut.

Regarding the issue with not being able to grab certain particles, there’s no reason for it that I can think of. Only contact distance between colliders is taken into account. This is a long shot but: maybe  there’s no contacts being generated for some particles, due to them having the same phase as the collider?

I took your advice and have spent the last few days attempting to implement a grab function that generates pin constraints at the location of the first particle in a collision, called by a controller input and I must admit I'm a little lost. I've been referencing this script, alongside the aforementioned 2017 script by nizmo and the sample pin constraints script located in the documentation, but I don't seem to be getting anywhere and would really appreciate some guidance. I'll provide my current script which is intended to be attached to the player's hands, though it does nothing currently. Attempting to grab the rope causes a small reaction in the rope mesh but there is no attachment or influence on the particles it seems. I hope you can help and tell me where I've gone wrong.

As for the phase question, the hand colliders are set to phase 6 to avoid that, so I'm not sure that was the cause.
Code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Obi;

[RequireComponent(typeof(ObiCollider))]
public class RopeGrabber : MonoBehaviour
{
    public bool canGrab = true;
    ObiSolver solver;
    ObiCollider obiCollider;
    public ObiRope rope;
    ObiSolver.ObiCollisionEventArgs collisionEvent;
    ObiPinConstraintsBatch newBatch;
    ObiConstraints<IObiConstraintsBatch> pinConstraints;

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

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

    private void OnEnable()
    {
        if (solver != null)
            solver.OnCollision += Solver_OnCollision;
    }

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

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

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

        if (solver != null && collisionEvent != null)
        {
            foreach (Oni.Contact contact in collisionEvent.contacts)
            {
                if (contact.distance < 0.01f && canGrab)
                {
                    var contactCollider = world.colliderHandles[contact.other].owner;

                    if (contactCollider == obiCollider)
                    {
                        var batch = new ObiPinConstraintsBatch();
                        batch.AddConstraint(rope.solverIndices[contact.particle], obiCollider, Vector3.zero, Quaternion.identity, 0, 0, float.PositiveInfinity);
                        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()
    {
        pinConstraints.RemoveBatch(newBatch);
        rope.SetConstraintsDirty(Oni.ConstraintType.Pin);
        canGrab = true;
    }

}
Reply
#16
Hi Xanduffy,

At first glance the script looks fine, but there's a few things to correct:

Code:
batch.AddConstraint(rope.solverIndices[contact.particle], obiCollider, Vector3.zero, Quaternion.identity, 0, 0, float.PositiveInfinity);

This line will pin the particle to the center of the collider, since you're passing Vector.zero as the pin offset (which is the pin position expressed in the object's local space). This is most likely not what you want in this situation. Instead, you want to pin the particle in its current position. You can do it by transforming the particle position from solver to collider space using Unity's transform methods like this:

Code:
int solverIndex = rope.solverIndices[contact.particle];
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.particle], obiCollider, positionCS, Quaternion.identity, 0, 0, float.PositiveInfinity);

Also, make sure pin constraints are globally enabled in the solver's constraints foldout. They won't have any effect otherwise.

Let me know how it goes,

cheers!
Reply
#17
Hey Jose, thanks for the help again. I'm at a bit of a loss now to be honest, I added the above code and it didn't seem to work, so I added some Debug messages into the grab function to show me where the script was having issues:

Code:
public void Grab()
    {
        var world = ObiColliderWorld.GetInstance();

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

                    Debug.Log(pa +" hit " + contactCollider);
                    if (contactCollider == obiCollider)
                    {
                        Debug.Log("Hand Collision");
                        var batch = new ObiPinConstraintsBatch();
                        int solverIndex = rope.solverIndices[contact.particle];
                        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.particle], obiCollider, positionCS, Quaternion.identity, 0, 0, float.PositiveInfinity);
                        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);

                    }

                }
            }
        }
    }
Based on that feedback, it seems like there's no collisions being registered between my hand colliders and the rope, despite being able to visibly push the rope around. The only collision feeding from the solver seems to be between the rope and the grappling hook which also has an obi rigidbody for obvious reasons. The rope is set to phase 1, and I reset the hands and the hook to phase 0z so it doesn't appear to be a straightforward issue. I'll add screenshots of the hands, rope and solver inspectors, I hope you can see what's wrong. 

   
   
   
Reply
#18
(04-02-2021, 12:03 PM)Xanduffy Wrote: Hey Jose, thanks for the help again. I'm at a bit of a loss now to be honest, I added the above code and it didn't seem to work, so I added some Debug messages into the grab function to show me where the script was having issues:

Code:
public void Grab()
    {
        var world = ObiColliderWorld.GetInstance();

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

                    Debug.Log(pa +" hit " + contactCollider);
                    if (contactCollider == obiCollider)
                    {
                        Debug.Log("Hand Collision");
                        var batch = new ObiPinConstraintsBatch();
                        int solverIndex = rope.solverIndices[contact.particle];
                        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.particle], obiCollider, positionCS, Quaternion.identity, 0, 0, float.PositiveInfinity);
                        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);

                    }

                }
            }
        }
    }
Based on that feedback, it seems like there's no collisions being registered between my hand colliders and the rope, despite being able to visibly push the rope around. The only collision feeding from the solver seems to be between the rope and the grappling hook which also has an obi rigidbody for obvious reasons. The rope is set to phase 1, and I reset the hands and the hook to phase 0z so it doesn't appear to be a straightforward issue. I'll add screenshots of the hands, rope and solver inspectors, I hope you can see what's wrong. 

I don't see anything wrong in your screenshots, and the code looks ok to me too.

However, if you're relying on the Debug.Log() in your code to see if there's contacts being reported between your hand and the rope, keep in mind that the Debug.Log() will only be called if canGrab is true. In case your contactCollider is any collider other than the hand collider, and it happens to appear in the contact list before contacts against the hand, canGrab will be set to false and the Debug.Log() for the hand will never be hit despite contacts being there.

This is the only thing that comes to mind that could cause this, since the contact list passed to the collision event is the exact same one used in the simulation. If the hand is able to push the rope around, contacts against it *must* be in the list.

Try testing for canGrab in a separate, nested if() and place the Debug.Log right before it. See if it reports contacts against the hand then.
Reply
#19
Update:

I managed to register some collisions, though most still don't. I made no changes since the last post so I'm unsure what the issue is or why it briefly stopped. However, even when the collisions were registered and I saw the "Hand Collision" message, newBatch returned a null value so something in the creation of the constraints batch seems not to be working. Any thoughts?

   

Line 67 is this:
pinConstraints.AddBatch(newBatch);

Edit: sorry I literally pasted the wrong line by accident!

Further update:
It gets stranger... It seems that the collisions only register when the player hand collides with both the hook and the rope simultaneously.

(04-02-2021, 12:09 PM)josemendez Wrote: I don't see anything wrong in your screenshots, and the code looks ok to me too.

However, if you're relying on the Debug.Log() in your code to see if there's contacts being reported between your hand and the rope, keep in mind that the Debug.Log() will only be called if canGrab is true. In case your contactCollider is any collider other than the hand collider, and it happens to appear in the contact list before contacts against the hand, canGrab will be set to false and the Debug.Log() for the hand will never be hit despite contacts being there.

This is the only thing that comes to mind that could cause this, since the contact list passed to the collision event is the exact same one used in the simulation. If the hand is able to push the rope around, contacts against it *must* be in the list.

Try testing for canGrab in a separate, nested if() and place the Debug.Log right before it. See if it reports contacts against the hand then.

I've implemented this change though it shouldn't set canGrab to false unless the contactCollider is the hand collider, I briefly added an update loop that debugged the value of canGrab and it never seems to change due to the error in adding the newBatch value to the solver if the grab even gets that far
Reply
#20
There's two things in line 67 that can be null: "pinConstraints" or "newBatch". Since you're just created newBatch and AddBatch() is a nop if you pass null to it, it's safe to say that it is pinConstraints that's null.

A quite probable cause for this is that you're getting it in Awake(), which Unity calls in no specific order for all objects in the scene. The rope's own Awake() might not have been called by the time you grab the reference to the pin constraints, so you get an uninitialized reference to the pin constraints (null). The usual Unity way to work around this is to use Start(), its intended use is to get references to other object's stuff once they've been all initialized in Awake(). Moving your Awake() code to Start() should solve this.
Reply