Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Adding pin constraints to a rope at runtime
#1
Hello,

I am working on a game in Unity where I want to add pin constraints all the way down the Obi Rope at runtime. Every unit of the rope should be covered with a constraint. The idea is that I'm attaching selectable colliders that can be "grabbed" by the player and then swung on.

I copied almost exactly the code from the "adding/removing constraints" page on the website here: http://obi.virtualmethodstudio.com/tutor...aints.html
Unity didn't like a few of the casts so I modified it slightly, but it's more or less the same.

Here's my current script:


Code:
    [SerializeField]
    private GameObject _networkedGrabPoint;

    private ObiRope _obiRope;

    [SerializeField]
    private List<GameObject> _chainLinks = new List<GameObject>();

    [SerializeField]
    private List<GameObject> _ropeLinks = new List<GameObject>();

    void Start()
    {
        _obiRope = GetComponentInChildren<ObiRope>();
        _chainLinks = _obiRope.chainLinks;

        for (int i = 0; i < _obiRope.positions.Length; i++)
        {
            GameObject _ropeLink = GameObject.Instantiate(_networkedGrabPoint, transform);
            _ropeLink.transform.localPosition = _obiRope.positions[i];
            _ropeLinks.Add(_ropeLink);
        }
    }
    
    void Update () {
        if (Input.GetKeyDown(KeyCode.R))
        {
            ObiPinConstraints constraints = _obiRope.GetComponent<ObiPinConstraints>();
            ObiPinConstraintBatch batch = constraints.GetBatches() as ObiPinConstraintBatch;
            
            // remove the constraints from the solver, because we cannot modify the constraints list while the solver is using it.
            constraints.RemoveFromSolver(null);

            ObiCollider ropeLink = _ropeLinks[0].GetComponent<ObiCollider>();
            batch.AddConstraint(0, ropeLink as ObiColliderBase, Vector3.zero, 1.0f);

            // add all constraints back:
            constraints.AddToSolver(null);
            
        }
    }

Almost word for word copied from what was given on the website. For some reason it didn't like the line "(ObiPinConstraintBatch)constraints.Getbatches()[0]" so I modified it to "constraints.GetBatches() as ObiPinConstraintBatch".

Right now, it's giving me a "null reference exception: object reference not set to an instance of an object" error on this line: batch.AddConstraint(0, ropeLink as ObiColliderBase, Vector3.zero, 1.0f);

I don't understand why. I checked the ObiColliderBase and it seems to definitely exist so I don't know why it would give me a null reference error there.

Any help would be very appreciated!

Thank you.
Reply
#2
(17-03-2018, 01:12 PM)Chilly5 Wrote: Hello,

I am working on a game in Unity where I want to add pin constraints all the way down the Obi Rope at runtime. Every unit of the rope should be covered with a constraint. The idea is that I'm attaching selectable colliders that can be "grabbed" by the player and then swung on.

I copied almost exactly the code from the "adding/removing constraints" page on the website here: http://obi.virtualmethodstudio.com/tutor...aints.html
Unity didn't like a few of the casts so I modified it slightly, but it's more or less the same.

Here's my current script:


Code:
[SerializeField]
private GameObject _networkedGrabPoint;

private ObiRope _obiRope;

[SerializeField]
private List<GameObject> _chainLinks = new List<GameObject>();

[SerializeField]
private List<GameObject> _ropeLinks = new List<GameObject>();

void Start()
{
_obiRope = GetComponentInChildren<ObiRope>();
_chainLinks = _obiRope.chainLinks;

for (int i = 0; i < _obiRope.positions.Length; i++)
{
GameObject _ropeLink = GameObject.Instantiate(_networkedGrabPoint, transform);
_ropeLink.transform.localPosition = _obiRope.positions[i];
_ropeLinks.Add(_ropeLink);
}
}

void Update () {
if (Input.GetKeyDown(KeyCode.R))
{
ObiPinConstraints constraints = _obiRope.GetComponent<ObiPinConstraints>();
ObiPinConstraintBatch batch = constraints.GetBatches() as ObiPinConstraintBatch;

// remove the constraints from the solver, because we cannot modify the constraints list while the solver is using it.
constraints.RemoveFromSolver(null);

ObiCollider ropeLink = _ropeLinks[0].GetComponent<ObiCollider>();
batch.AddConstraint(0, ropeLink as ObiColliderBase, Vector3.zero, 1.0f);

// add all constraints back:
constraints.AddToSolver(null);

}
}

Almost word for word copied from what was given on the website. For some reason it didn't like the line "(ObiPinConstraintBatch)constraints.Getbatches()[0]" so I modified it to "constraints.GetBatches() as ObiPinConstraintBatch".

Right now, it's giving me a "null reference exception: object reference not set to an instance of an object" error on this line: batch.AddConstraint(0, ropeLink as ObiColliderBase, Vector3.zero, 1.0f);

I don't understand why. I checked the ObiColliderBase and it seems to definitely exist so I don't know why it would give me a null reference error there.

Any help would be very appreciated!

Thank you.

Hi,

This line will always cause "batch" to be null:
Code:
ObiPinConstraintBatch batch = constraints.GetBatches() as ObiPinConstraintBatch;

This is because you're trying to cast a IEnumerable (sort of a fancy array, which is what GetBatches() returns) to a completely unrelated type. The "as" operator in C# tries to perform a cast and returns null if the types are not convertible instead of raising an exception immediately, which is what classic casts do. See:

https://docs.microsoft.com/en-us/dotnet/...eywords/as

Afterwards you call batch.AddConstraint which of course raises a NullReferenceException because batch is null.

Solution:
use this instead:
Code:
ObiPinConstraintBatch batch = constraints.GetFirstBatch() as ObiPinConstraintBatch;

This will grab the first constraint batch (which is an object of type ObiConstraintBatch) and cast it to ObiPinConstraintBatch successfully.
Reply
#3
Thanks for the quick answer! Works like a charm. Thank you so much.
Reply
#4
So I came across the same issue regarding my code where I need to generate ropes with balls on the end of them from a height and length set up before rendering the ropes. the ropes generate correctly but I have been unable to pin the prefab ball to the ropes as they generate previously I had the issue before with IEnumerators and [] indexing, but your tip on using GetFirstBatch helped solve that, yet now it is giving me a NullReferenceException. 

I know the reference to the instantiated object is correct because I used it to set the material of the ball. 

Here I believe the offending code is.
Code:
using UnityEngine;
using System.Collections;
using Obi;

namespace Obi
{
    [RequireComponent(typeof(ObiRope))]
    [RequireComponent(typeof(ObiCatmullRomCurve))]
    [RequireComponent(typeof(ObiPinConstraints))]
    public class RopeMaker : MonoBehaviour {

        public ObiSolver solver;
        public ObiRopeSection section;
        public Material material;
        public Vector3 start;
        public Vector3 end;
        public int ropeNum;
        private ObiRope rope;
        private ObiCatmullRomCurve path;
    
        public IEnumerator MakeRope () {
    
            // Get all needed components and interconnect them:
            rope = GetComponent<ObiRope>();
            path = GetComponent<ObiCatmullRomCurve>();
            rope.Solver = solver;
            rope.ropePath = path;    
            rope.section = section;
            GetComponent<MeshRenderer>().material = material;
            
            // Calculate rope start/end and direction in local space:
            Vector3 localStart = transform.InverseTransformPoint(start);
            Vector3 localEnd = transform.InverseTransformPoint(end);
            Vector3 direction = (localEnd-localStart).normalized;

            // Generate rope path:
            path.controlPoints.Clear();
            path.controlPoints.Add(localStart-direction);
            path.controlPoints.Add(localStart);
            path.controlPoints.Add(localEnd);
            path.controlPoints.Add(localEnd+direction);

            // Setup the simulation:
            yield return StartCoroutine(rope.GeneratePhysicRepresentationForMesh());
            rope.AddToSolver(null);
            // Fix first and last particle in place:
            rope.invMasses[0] = 0;
            Oni.SetParticleInverseMasses(solver.OniSolver,new float[]{0},1,rope.particleIndices[0]);
            //Set Phase:
            for (int i = 0; i < rope.TotalParticles; ++i) {
                rope.phases [i] = Oni.MakePhase (ropeNum, 0);
                rope.PushDataToSolver (ParticleData.PHASES);
                yield return new WaitForFixedUpdate ();
            }
        }



        public void AddPendulum(ObiCollider pendulum, Vector3 offSet){
            

            ObiPinConstraints pins = rope.GetComponent<ObiPinConstraints>();
            ObiPinConstraintBatch batch = pins.GetFirstBatch()[0] as ObiPinConstraintBatch;
                pins.RemoveFromSolver (null);
                batch.AddConstraint (rope.UsedParticles - 1, pendulum, offSet, 0);
                pins.AddToSolver (null);
                pins.PushDataToSolver ();
            
        }

    }
}
And the other code that feeds into the generation code.
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;


public class RopeSetup : MonoBehaviour {

    public Material[] materials = new Material[8];

    [Range(1,8)]
    public int numBalls = 1;
    [Range(0.5f,2.5f)]
    public float height = 1f;
    [Range(2f,10f)]
    public float length = 3f;
    public ObiSolver obiSolver;
    public ObiCollider ball;
    public GameObject rope;
    [Range(0.01f,0.05f)]
    public float width =0.03f;
    [Range(0.001f,1f)]
    public float resolution =0.5f;
    public bool collision = true;
    private Vector3 start;
    private Vector3 end;
    private int ropeNum =1;
    private GameObject ballmesh;
    private GameObject newRope;

    // Use this for initialization
    void update () {
        
    }

    public void MakeRopes(){
        
        if (ropeNum <= materials.Length){
        start = new Vector3 (0,length+height,0);
        end = new Vector3 (0,height,0);
        newRope = Instantiate (rope, start, transform.rotation);
        RopeMaker setup = (RopeMaker) newRope.GetComponent("RopeMaker");
        setup.start = start;
        setup.end = end;
        setup.material = materials[ropeNum-1];
        setup.solver= obiSolver;
        setup.ropeNum = ropeNum;



            StartCoroutine (GenRopes ());


            ObiCollider ballcopy = Instantiate (ball, end, transform.rotation);
            MeshRenderer mesh = ballcopy.GetComponentInChildren<MeshRenderer> ();
            mesh.material = materials[ropeNum - 1];
            ropeNum++;
            setup.AddPendulum (ballcopy, new Vector3(0,0,0));
        
        }
    }
    public IEnumerator GenRopes (){
        
        RopeMaker setup = (RopeMaker)newRope.GetComponent ("RopeMaker");
        yield return setup.MakeRope();
    }
        
}
Reply
#5
(18-03-2018, 05:46 PM)Gieve Wrote: So I came across the same issue regarding my code where I need to generate ropes with balls on the end of them from a height and length set up before rendering the ropes. the ropes generate correctly but I have been unable to pin the prefab ball to the ropes as they generate previously I had the issue before with IEnumerators and [] indexing, but your tip on using GetFirstBatch helped solve that, yet now it is giving me a NullReferenceException. 

I know the reference to the instantiated object is correct because I used it to set the material of the ball. 

Here I believe the offending code is.
Code:
using UnityEngine;
using System.Collections;
using Obi;

namespace Obi
{
[RequireComponent(typeof(ObiRope))]
[RequireComponent(typeof(ObiCatmullRomCurve))]
[RequireComponent(typeof(ObiPinConstraints))]
public class RopeMaker : MonoBehaviour {

public ObiSolver solver;
public ObiRopeSection section;
public Material material;
public Vector3 start;
public Vector3 end;
public int ropeNum;
private ObiRope rope;
private ObiCatmullRomCurve path;

public IEnumerator MakeRope () {

// Get all needed components and interconnect them:
rope = GetComponent<ObiRope>();
path = GetComponent<ObiCatmullRomCurve>();
rope.Solver = solver;
rope.ropePath = path;
rope.section = section;
GetComponent<MeshRenderer>().material = material;

// Calculate rope start/end and direction in local space:
Vector3 localStart = transform.InverseTransformPoint(start);
Vector3 localEnd = transform.InverseTransformPoint(end);
Vector3 direction = (localEnd-localStart).normalized;

// Generate rope path:
path.controlPoints.Clear();
path.controlPoints.Add(localStart-direction);
path.controlPoints.Add(localStart);
path.controlPoints.Add(localEnd);
path.controlPoints.Add(localEnd+direction);

// Setup the simulation:
yield return StartCoroutine(rope.GeneratePhysicRepresentationForMesh());
rope.AddToSolver(null);
// Fix first and last particle in place:
rope.invMasses[0] = 0;
Oni.SetParticleInverseMasses(solver.OniSolver,new float[]{0},1,rope.particleIndices[0]);
//Set Phase:
for (int i = 0; i < rope.TotalParticles; ++i) {
rope.phases [i] = Oni.MakePhase (ropeNum, 0);
rope.PushDataToSolver (ParticleData.PHASES);
yield return new WaitForFixedUpdate ();
}
}



public void AddPendulum(ObiCollider pendulum, Vector3 offSet){


ObiPinConstraints pins = rope.GetComponent<ObiPinConstraints>();
ObiPinConstraintBatch batch = pins.GetFirstBatch()[0] as ObiPinConstraintBatch;
pins.RemoveFromSolver (null);
batch.AddConstraint (rope.UsedParticles - 1, pendulum, offSet, 0);
pins.AddToSolver (null);
pins.PushDataToSolver ();

}

}
}

Change this:
Code:
ObiPinConstraintBatch batch = pins.GetFirstBatch()[0] as ObiPinConstraintBatch;

to this:
Code:
ObiPinConstraintBatch batch = pins.GetFirstBatch() as ObiPinConstraintBatch;

pins.GetFirstBatch() returns an object, not an array.
Reply
#6
(18-03-2018, 05:51 PM)josemendez Wrote: Exact same problem as OP.

Change this:
Code:
ObiPinConstraintBatch batch = pins.GetFirstBatch()[0] as ObiPinConstraintBatch;

to this:
Code:
ObiPinConstraintBatch batch = pins.GetFirstBatch() as ObiPinConstraintBatch;
Sorry I had already done that, just reverted it to remind myself of the error forgot to re-set it before copying the code.

Sorry for the confusion, the error "NullReferenceException: Object reference not set to an instance of an object" occurs with the code set as in your suggestion.
Reply
#7
(18-03-2018, 05:56 PM)Gieve Wrote: Sorry I had already done that, just reverted it to remind myself of the error forgot to re-set it before copying the code.

Sorry for the confusion, the error "NullReferenceException: Object reference not set to an instance of an object" occurs with the code set as in your suggestion.

What line is the exception pointing to in your code?
Reply
#8
(18-03-2018, 05:58 PM)josemendez Wrote: What line is the exception pointing to in your code?

Code:
NullReferenceException: Object reference not set to an instance of an object
Obi.RopeMaker.AddPendulum (Obi.ObiCollider pendulum, Vector3 offSet)
(at Assets/Scripts/RopeMaker.cs:65)
RopeSetup.MakeRopes () (at Assets/Scripts/RopeSetup.cs:58)
RopeMakerEditor.OnInspectorGUI () (at Assets/Editor/RopeMakerEditor.cs:14)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex,
Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext,
UnityEngine.Rect& importedObjectBarRect)
(at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1253)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
The Full Error read out, 

points at line 65 the AddConstraint function call, and then refers back to all referencing code.
Reply
#9
(18-03-2018, 06:04 PM)Gieve Wrote:
Code:
NullReferenceException: Object reference not set to an instance of an object
Obi.RopeMaker.AddPendulum (Obi.ObiCollider pendulum, Vector3 offSet)
(at Assets/Scripts/RopeMaker.cs:65)
RopeSetup.MakeRopes () (at Assets/Scripts/RopeSetup.cs:58)
RopeMakerEditor.OnInspectorGUI () (at Assets/Editor/RopeMakerEditor.cs:14)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex,
Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext,
UnityEngine.Rect& importedObjectBarRect)
(at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1253)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
The Full Error read out, 

points at line 65 the AddConstraint function call, and then refers back to all referencing code.

Are you waiting for the rope to be fully initialized before calling AddPendulum? There will be no pin constraint batches (or constraints of any kind, for that matter) before the initialization coroutine  (MakeRope()) has finished... this is explained in the comments to that function:

Quote:/// MakeRope and AddPendulum may NOT be called on the same frame.
/// You must wait for the MakeRope coroutine to finish first.
/// Just adds a pendulum to the rope on the un-anchored end.
Reply
#10
(18-03-2018, 06:12 PM)josemendez Wrote: Are you waiting for the rope to be fully initialized before calling AddPendulum? There will be no pin constraint batches (or constraints of any kind, for that matter) before the initialization coroutine  (MakeRope()) has finished... this is explained in the comments to that function:

fantastic!!! for those interested I added another call to a coroutine call DoLast that had a while(!generated) loop with a waitforseconds call of 0.1 second, when the rope set up coroutine finishes it sets the flag to true, then it set up the pin.

If there is a more eloquent way, i'd like to know. 

Thank you so so much for your help Jose.

sorry Chilly5 for hijacking your thread, was too similar to my issue to start my own.


Oh, I am unclear as to which variable controls the particle's mass, would you mind if I ask, so I can set those at generation as well.
Reply