Obi Official Forum

Full Version: Creating rope between two points at Runtime
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Hello, I've been working on a bridge-building project similar to Polybridge. I stumbled upon your asset when I wanted to figure out how to implement cables for the bridges. This means they need to be created in runtime with a custom length and span between two given points.

I have been able to create particle attachments, I've made sure the connected objects have obiColliders and rigidBodies on them but my main problem is getting the ropes to visually span the distance between the points. I don't know if it's a obiRopeCursor problem where I just need to adjust the length, or if I need to insert control points into the rope's path. 

Here's my code for instantiating the rope and creating the attachment points. GameManager.AllConnections is a dictionary that takes a Vector3 as a key with an object as the value.

Code:
ObiRope newRope = Instantiate(lightCablePrefab, (StartPosition + EndPosition) / 2, Quaternion.FromToRotation(new Vector3(1, 0, 0), EndPosition - StartPosition)).GetComponent<ObiRope>();

        ObiParticleAttachment attachment1 = newRope.gameObject.AddComponent(typeof(ObiParticleAttachment)) as ObiParticleAttachment;
        ObiParticleAttachment attachment2 = newRope.gameObject.AddComponent(typeof(Obi.ObiParticleAttachment)) as ObiParticleAttachment;
       
        newRope.path.InsertControlPoint(newRope.path.ControlPointCount - 1, StartPosition, (StartPosition - EndPosition).normalized, (EndPosition - StartPosition).normalized, Vector3.up, 0.1f, 0.1f, 0.2f, 0, Color.red, "New Start");
        newRope.path.InsertControlPoint(newRope.path.ControlPointCount - 1, EndPosition, (EndPosition - StartPosition).normalized, (StartPosition - EndPosition).normalized, Vector3.up, 0.1f, 0.1f, 0.2f, 0, Color.red, "New End");

        attachment1.target = GameManager.AllConnections[StartPosition].transform;
        attachment1.attachmentType = Obi.ObiParticleAttachment.AttachmentType.Dynamic;
        attachment1.particleGroup = newRope.blueprint.groups[1];
        attachment2.target = GameManager.AllConnections[EndPosition].transform;
        attachment2.attachmentType = Obi.ObiParticleAttachment.AttachmentType.Dynamic;
        attachment2.particleGroup = newRope.blueprint.groups[2];

        ObiRopeCursor cursor = newRope.GetComponent<ObiRopeCursor>();

        cursor.ChangeLength(Vector3.Magnitude(StartPosition - EndPosition));

        newRope.transform.parent = StaticVars.vars.ObiSolver;


And here is a video of what it looks like in-game: Video
Hi there!

There's many things off in your code:

1.- You're rotating the rope's transform so that it aligns to the vector from EndPosition to StartPosition. However, you're inserting control points at EndPosition and StartPosition too. Rope control points are expressed in the rope's local space, so you should either leave the rope's transform intact and place the control points at start/end positions (assuming your start/end data is expressed in world space) or convert them to local space if you want to position/rotate the transform.

2.- Minor detail: you're using InsertControlPoint and passing the last index of the rope. Would be easier to use AddControlPoint(), as it automatically appends a new control point at the end.

3.- You're accessing newRope.blueprint.groups[1] and newRope.blueprint.groups[2] fro the attachments. Array indices in C# are zero-based, so if your rope has 2 control points, there will only be 2 groups: newRope.blueprint.groups[0] and newRope.blueprint.groups[1]. Accessing newRope.blueprint.groups[2] should result in an out of bounds exception.

3.- You're using a cursor to set the rope length to the exact same length it has at rest. This doesn't make much sense, will have no effect on the rope but will slightly affect performance. You can get rid of [code]cursor.ChangeLength(Vector3.Magnitude(StartPosition - EndPosition));
Here's a sample script that you can use as reference. I've opted to place the transform in between both points (as you do), then convert the control point positions to local space:

Code:
using UnityEngine;
using Obi;

public class RopeBetweenTwoPoints : MonoBehaviour
{
    public Transform start;
    public Transform end;
    public ObiSolver solver;

    void Start()
    {
        // Generate rope synchronously:
        Generate();
    }

    void Generate()
    {
        if (start != null && end != null)
        {
            // Adjust our transform:
            transform.position = (start.position + end.position) / 2;
            transform.rotation = Quaternion.FromToRotation(Vector3.right, end.position - start.position);

            // Calculate control point positions and tangent vector:
            Vector3 startPositionLS = transform.InverseTransformPoint(start.position);
            Vector3 endPositionLS = transform.InverseTransformPoint(end.position);
            Vector3 tangentLS = (endPositionLS - startPositionLS).normalized;

            // Create the blueprint:
            var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();

            // Build the rope path:
            blueprint.path.AddControlPoint(startPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "start");
            blueprint.path.AddControlPoint(endPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "end");
            blueprint.path.FlushEvents();

            // Generate particles/constraints:
            blueprint.GenerateImmediate();

            // Add rope actor / renderer / attachment components:
            var rope = gameObject.AddComponent<ObiRope>();
            var ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
            var attachment1 = gameObject.AddComponent<ObiParticleAttachment>();
            var attachment2 = gameObject.AddComponent<ObiParticleAttachment>();

            // Load the default rope section for rendering:
            ropeRenderer.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");

            // Set the blueprint:
            rope.ropeBlueprint = blueprint;

            // Attach both ends:
            attachment1.target = start;
            attachment2.target = end;
            attachment1.particleGroup = blueprint.groups[0];
            attachment2.particleGroup = blueprint.groups[1];

            // Parent the actor under a solver to start the simulation:
            transform.SetParent(solver.transform);
        }
    }
}
(18-06-2021, 08:46 AM)josemendez Wrote: [ -> ]Here's a sample script that you can use as reference. I've opted to place the transform in between both points (as you do), then convert the control point positions to local space:

Code:
using UnityEngine;
using Obi;

public class RopeBetweenTwoPoints : MonoBehaviour
{
    public Transform start;
    public Transform end;
    public ObiSolver solver;

    void Start()
    {
        // Generate rope synchronously:
        Generate();
    }

    void Generate()
    {
        if (start != null && end != null)
        {
            // Adjust our transform:
            transform.position = (start.position + end.position) / 2;
            transform.rotation = Quaternion.FromToRotation(Vector3.right, end.position - start.position);

            // Calculate control point positions and tangent vector:
            Vector3 startPositionLS = transform.InverseTransformPoint(start.position);
            Vector3 endPositionLS = transform.InverseTransformPoint(end.position);
            Vector3 tangentLS = (endPositionLS - startPositionLS).normalized;

            // Create the blueprint:
            var blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();

            // Build the rope path:
            blueprint.path.AddControlPoint(startPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "start");
            blueprint.path.AddControlPoint(endPositionLS, -tangentLS, tangentLS, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "end");
            blueprint.path.FlushEvents();

            // Generate particles/constraints:
            blueprint.GenerateImmediate();

            // Add rope actor / renderer / attachment components:
            var rope = gameObject.AddComponent<ObiRope>();
            var ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
            var attachment1 = gameObject.AddComponent<ObiParticleAttachment>();
            var attachment2 = gameObject.AddComponent<ObiParticleAttachment>();

            // Load the default rope section for rendering:
            ropeRenderer.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");

            // Set the blueprint:
            rope.ropeBlueprint = blueprint;

            // Attach both ends:
            attachment1.target = start;
            attachment2.target = end;
            attachment1.particleGroup = blueprint.groups[0];
            attachment2.particleGroup = blueprint.groups[1];

            // Parent the actor under a solver to start the simulation:
            transform.SetParent(solver.transform);
        }
    }
}

Thank you so much! This helped a lot. I didn't realize I could create and edit a blueprint at runtime. That is super helpful. Thank you again.