Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Grab Rope - VR
#21
(04-02-2021, 01:52 PM)josemendez Wrote: 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.

That's a very good point and I made that change, though I had to move the code that finds the solver back to Awake() as the script stopped displaying any debug messages at all, suggesting solver variable was returning null. Unfortunately, it didn't seem to resolve the problem and with an extra debug I can confirm you're correct that the pinConstraints is null, though it remains so even when I look for it in Start(). Also it seems important that the script is only registering collisions and getting far enough to throw the error when colliding with the rope and hook simultaneously, as though it's the collision between the hand and grapple that somehow satisfies the collisionEvent. I really appreciate your input on this and I hope I'm not being a bother with this issue, it's a stubborn one! If there's anything more specific like video of what I'm attempting or even the project file itself that might help here, I'd be happy to share. Here's the more revised script anyway:

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>();
    }
    void Start()
    {
        // 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();
        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.other].owner;
                    ObiSolver.ParticleInActor pa = solver.particleToActor[contact.particle];

                    Debug.Log(pa +" hit " + contactCollider);
                    if (canGrab)
                    {
                        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);

                        }
                    }
                }
            }
        }
    }

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

}
Reply
#22
Hi there,

You're casting the constraints to the wrong type, using the "as" operator. This will return null if the casting fails: https://docs.microsoft.com/en-us/dotnet/...g-and-cast

This line:

Code:
pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<IObiConstraintsBatch>;

should be:

Quote:pinConstraints = rope.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;

Don't forget to change the declaration of pinConstraints to the same type. See: http://obi.virtualmethodstudio.com/tutor...aints.html

The reason for this is that generics covariance doesn't work as you'd expect in C# < 4.0, and even in later versions you need to be more explicit about it in the generic definition. You can't just cast a generic<TDerived> to a generic<TBase>.
Reply
#23
Tested your script, and there's one more thing missing that I didn't pick up earlier: you're not actually activating the constraints you add to the batch. After the batch.AddConstraint line, add:

Code:
batch.activeConstraintCount = 1;

Otherwise the batch will have zero active constraints and the pin constraint will be ignored. You can just copy-paste this from the sample in the manual, or the GrapplingHook.cs sample script.

Here's the fixed & tested script, should work correctly. let me know otherwise Sonrisa:


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<ObiPinConstraintsBatch> pinConstraints;

    void Awake()
    {
        solver = FindObjectOfType<ObiSolver>();
        obiCollider = GetComponent<ObiCollider>();
    }
    void Start()
    {
        // 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 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.other].owner;
                    ObiSolver.ParticleInActor pa = solver.particleToActor[contact.particle];

                    Debug.Log(pa + " hit " + contactCollider);
                    if (canGrab)
                    {
                        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);
                            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;
        }
    }

}
Reply
#24
Thank you for catching those mistakes! The script is no longer throwing null references but still the collisions only register towards one end of the rope, and if the pin constraints are being generated even then, they're not having any effect on the rope itself. It's quite strange that despite visibly pushing the rope around with my hand colliders, the "Collision" debug message that simply requires a solver and any collision event will not show unless the box collider I currently have on the hand touches the grapple's mesh collider as well as the rope. Are there any factors other than phase that can influence whether a collision is registered? I've set the phase to 1 on both control points at the beginning and end within the rope path, while the hands and the grapple are set to phase 0 on the inspector. In fact, the initial "Collision" message shows if I press the grab button while touching the hook and not the rope, should this be happening then they are both set to the same phase, or do the different phases only affect particle collisions? 

To clarify, when you tested the script were you able to achieve the grabbing effect we're aiming for through whatever input system you were using?

Thanks again, it's been said before but your customer support is top notch!
Reply
#25
Hi!

Yes, i was able to get the grabbing effect when trying the script out.

Phases affect both particle-particle contacts and particle-collider contacts. You say your colliders are in phase 0 and the rope in phase 1, so they should collide and report contacts.

Note however that a solver will only report contacts for ropes it’s in charge of simulating (kinda obvious but still worth noting imho). I mention this because you’re getting the solver using FindObjectOfType(), which will return the first object of that type it finds in the scene. If there’s more than one solver in the scene this might or might not be the rope’s solver, so you can end up looking trough the contacts of a completely different solver. To avoid confusion, in this case it’s more robust to get the solver from the rope itself: rope.solver. That way you know which solver you’re getting.

If this issue persists, I think the easiest route to resolution would be taking a look at your actual scene/project. Would it be possible for you to send it to support(at)virtualmethodstudio.com?

Thanks! Sonrisa
Reply
#26
Another astute suggestion, thanks. However I made that change, then moved the line to Start() to avoid the issue of the rope not being initialised yet, and now there's no collisions registering at all, which has frankly confused me even more than it would have if there was no effect.  Huh 

I'm going to send you over the zipped project folder, it's quite small as the project is still in very early days. Thanks so much!
Reply
#27
(05-02-2021, 10:10 PM)Xanduffy Wrote: Another astute suggestion, thanks. However I made that change, then moved the line to Start() to avoid the issue of the rope not being initialised yet, and now there's no collisions registering at all, which has frankly confused me even more than it would have if there was no effect.  Huh 

You're getting a reference to the solver in Start(), which is called after OnEnable() (see: https://docs.unity3d.com/Manual/ExecutionOrder.html). However you're subscribing to the solver collisions in OnEnable(), so solver == null at that point.

You should get the solver in OnEnable(), then subscribe to its collision event:
Code:
private void OnEnable()
    {
        solver = rope.solver;
        if (solver != null)
            solver.OnCollision += Solver_OnCollision;
    }

Another issue is that you're converting the contact particle index (which is already a solver index, since contacts are reported per-solver, not per-actor) to a solver index again. This line:

Code:
int solverIndex = rope.solverIndices[contact.particle];

should just be

Code:
int solverIndex = contact.particle;

The problem with indexing the actor's solverIndices array this way is that you risk out of bounds accesses (runtime error) at worst or non-functional attachments at best. The same issue exists when passing the particle index to the pin constraint, so replace the AddConstraint line with this:

Code:
batch.AddConstraint(solverIndex, obiCollider, positionCS, Quaternion.identity, 0, 0, float.PositiveInfinity);

That gets everything working properly for me. cheers!
Reply
#28
Hey sorry for the delayed response, I had some unrelated technical difficulties that stopped me trying out your suggestion yesterday. I've made those exact changes and frustratingly it is still not working, with little change if any. No collisions are being registered between my hands and the rope, meanwhile if there's a collision between my hands and the grappling hook while I press the grab button the Debug line shows the following result.

Code:
var contactCollider = world.colliderHandles[contact.other].owner;
ObiSolver.ParticleInActor pa = solver.particleToActor[contact.particle];
Debug.Log(pa + " hit " + contactCollider);

   

I'm at a loss, particularly as to why the hands are not triggering the initial debug message despite visibly pushing the rope around while pressing grab. I have no idea why this is working for you but not even registering those baseline collisions for me. If you'd like I can record video of the issue? 

I considered attempting to instantiate new gameobjects when the player grabs the rope and pin the rope to those rather than the hands directly, in case there was an issue with the hand tracking script overriding the pin constraint(I may be off base with that), but this isn't possible either while no collisions are being registered with the rope. 

I appreciate all this help.
Reply
#29
It gets worse, I've somehow lost the 'edit path' functionality, clicking that button now does nothing but disable the transform gizmo, I can no longer see the control points of the rope(or any new ropes) nor the small UI box that allows adding, removing and editing them. I'm not sure how long it's been like this, other than that the copy of the project I sent to you seems to have the same problem. I've tried deleting obi rope from my assets and reinstalling it through the package manager, as well as importing it into an entirely new project and the problem persists regardless. Once again I'm at a loss, unless I have somehow downloaded a corrupted version of obi rope? Beginning to lose hope in obi working out for me here...
Reply
#30
(09-02-2021, 09:04 PM)Xanduffy Wrote: It gets worse, I've somehow lost the 'edit path' functionality, clicking that button now does nothing but disable the transform gizmo, I can no longer see the control points of the rope(or any new ropes) nor the small UI box that allows adding, removing and editing them. I'm not sure how long it's been like this, other than that the copy of the project I sent to you seems to have the same problem. I've tried deleting obi rope from my assets and reinstalling it through the package manager, as well as importing it into an entirely new project and the problem persists regardless. Once again I'm at a loss, unless I have somehow downloaded a corrupted version of obi rope? Beginning to lose hope in obi working out for me here...

You have an inspector open in debug mode. Unity won't draw any scene view windows (such as the path editor's small UI box) then. Either hunt for the inspector in your tabs and take it out of debug mode, or reset the window layout to the default (the latter tends to be quicker). From the FAQ:
http://obi.virtualmethodstudio.com/faq.html

Quote:I can't see the path editor when clicking the Edit Path button in the rope inspector. Is this a bug?

The rope inspector is a sceneview window, and Unity won't draw sceneview windows if any inspector is in debug mode. Close all inspectors in debug mode (easiest, quickest way is to revert the window layout to the default) and it will become visible again.
Reply