Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Generated rope with pin constraints
#1
Hello Jose and everybody in the community,

So far I'm using the Filo for hoist  simulations and am currently starting with Obi rope to simulate slings.
The features are truly impressive.
I am of course struggling with the mass ratio limitation of the iterative solver just as every other poster in this forum.

However, now I face a different problem.

I need to create slings at runtime to connect load to a hook.

I took the Grappling hook example and slightly modified it.
It casts a ray from the camera to the mouse pointer and attaches a rope between a hook (cube) and a load (cube) hit by the ray.

That part works fine. The generated rope is generated at correct position, the length is correct and it is attached to both game objects.

The problem is that the load cube must rotate around the point of attachment and it does not.
When the rope attaches to a side of the cube and gets retracted (which would normally roll the cube over)  the rope starts to move like a tentacle of an octopus suffering from epileptic seizure.

I use Unity 2019.3.1f1 and Obi Rope 5.3

A rope configured in the editor behaves correctly.



The rope is generated by this script:

Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;

/**
* Sample component that shows how to use Obi Rope to create a grappling hook for a 2.5D game.
* 95% of the code is the grappling hook logic (user input, scene raycasting, launching, attaching the hook, etc) and parameter setup,
* to show how to use Obi completely at runtime. This might not be practical for real-world scenarios,
* but illustrates how to do it.
*
* Note that the choice of using actual rope simulation for grapple dynamics is debatable. Usually
* a simple spring works better both in terms of performance and controllability.
*
* If complex interaction is required with the scene, a purely geometry-based approach (ala Worms ninja rope) can
* be the right choice under certain circumstances.
*/
public class Sling : MonoBehaviour
{

   public ObiSolver solver;
   public ObiCollider hook;
   public float hookExtendRetractSpeed = 2;
   public Material material;
   public ObiRopeSection section;

   private ObiRope rope;
   private ObiRopeBlueprint blueprint;
   private ObiRopeExtrudedRenderer ropeRenderer;

   private ObiRopeCursor cursor;

   private RaycastHit hookAttachment;
   private bool attached = false;

   void Awake()
   {

       // Create both the rope and the solver:    
       rope = gameObject.AddComponent<ObiRope>();
       ropeRenderer = gameObject.AddComponent<ObiRopeExtrudedRenderer>();
       ropeRenderer.section = section;
       ropeRenderer.uvScale = new Vector2(1, 5);
       ropeRenderer.normalizeV = false;
       ropeRenderer.uvAnchor = 0;
       rope.GetComponent<MeshRenderer>().material = material;

       // Setup a blueprint for the rope:
       blueprint = ScriptableObject.CreateInstance<ObiRopeBlueprint>();
       blueprint.resolution = 1.0f;
       blueprint.thickness = 0.1f;

       // Tweak rope parameters:
       rope.maxBending = 0.02f;

       // Add a cursor to be able to change rope length:
       cursor = rope.gameObject.AddComponent<ObiRopeCursor>();
       cursor.cursorMu = 0;
       cursor.direction = true;
   }

   private void OnDestroy()
   {
       DestroyImmediate(blueprint);
   }

   /**
     * Raycast against the scene to see if we can attach the hook to something.
     */
   private void LaunchHook()
   {

       // Get the mouse position in the scene, in the same XY plane as this object:
       Vector3 mouse = Input.mousePosition;
       mouse.z = 100000.0f;
       Vector3 mouseInScene = Camera.main.ScreenToWorldPoint(mouse);

       // Get a ray from the camera to the mouse:
       Ray ray = new Ray(Camera.main.transform.position, mouseInScene);


       // Raycast to see what we hit:
       if (Physics.Raycast(ray, out hookAttachment))
       {
           // We actually hit something, so attach the hook!
           StartCoroutine(AttachHook());
       }

   }

   private IEnumerator AttachHook()
   {
       yield return 0;
       Vector3 localHit = rope.transform.InverseTransformPoint(hookAttachment.point);

       // Procedurally generate the rope path (a simple straight line):
       blueprint.path.Clear();
       blueprint.path.AddControlPoint(hook.transform.localPosition + Vector3.down * 0.6f, Vector3.up, Vector3.down, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "Sling hook");
       blueprint.path.AddControlPoint(localHit, Vector3.up, Vector3.down, Vector3.up, 0.1f, 0.1f, 1, 1, Color.white, "Sling load");
       blueprint.path.FlushEvents();

       // Generate the particle representation of the rope (wait until it has finished):
       yield return blueprint.Generate();

       // Pin both ends of the rope (this enables two-way interaction between character and rope):
       var pinConstraints = blueprint.GetConstraintsByType(Oni.ConstraintType.Pin) as ObiConstraints<ObiPinConstraintsBatch>;
       var batch = pinConstraints.batches[0];
       batch.AddConstraint(0, hook, transform.localPosition + Vector3.down * 0.6f, Quaternion.identity);
       //batch.AddConstraint(blueprint.activeParticleCount - 1, hookAttachment.collider.GetComponent<ObiColliderBase>(), hookAttachment.collider.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity);
       batch.AddConstraint(blueprint.activeParticleCount - 1, hookAttachment.collider.GetComponent<ObiCollider>(), hookAttachment.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity);
       batch.activeConstraintCount = 2;

       // Set the blueprint (this adds particles/constraints to the solver and starts simulating them).
       rope.ropeBlueprint = blueprint;
       rope.GetComponent<MeshRenderer>().enabled = true;
   }

   private void DetachHook()
   {
       // Set the rope blueprint to null (automatically removes the previous blueprint from the solver, if any).
       rope.ropeBlueprint = null;
       rope.GetComponent<MeshRenderer>().enabled = false;
   }


   void Update()
   {

       if (Input.GetMouseButtonDown(0))
       {
           if (!rope.isLoaded)
               LaunchHook();
           else
               DetachHook();
       }

       if (rope.isLoaded)
       {
           if (Input.GetKey(KeyCode.W))
           {
               cursor.ChangeLength(rope.restLength - hookExtendRetractSpeed * Time.deltaTime);
           }
           if (Input.GetKey(KeyCode.S))
           {
               cursor.ChangeLength(rope.restLength + hookExtendRetractSpeed * Time.deltaTime);
           }
       }
   }
}


The hook cube has just a box collider and Obi collider, the load cube has also colliders and a Rigidbody component.
Rotation of the load cube is not frozen, its angular velocities are not zero, but are below 0.02 rad/s

Please let me know where I made a mistake.
Reply
#2
Hi,

You've probably pinned the rope inside the cube, very close to its center of mass. This will result in two issues:
- The cube won't rotate, as any force applied very close to the center of mass of a rigidbody does not cause a rotation (or a really small one).
- The rope and the cube will generate a force-feedback loop, as there's now two constraints that can't be met at the same time:
  • PinConstraint: the end of the rope must be inside the cube.
  • CollisionConstraint: the end of the rope must be outside of the cube.

Clearly these two are at odds with each other: the collision constraint pushes the rope away from the collider, but the pin constraint pushes it back in. This results in the seizure-like behavior in your video.

This situation, and why it is undesirable is explained in the last part of the manual page for pin constraints:
http://obi.virtualmethodstudio.com/tutor...aints.html

Solutions:
- Set the last few particles in the rope to the same phase as the cube collider.
- Make sure the rope is pinned outside of the cube (the offset position you pass to the AddConstraint method is the position the particle will be pinned to, expressed in the collider's local space).

Let me know how it goes!
Reply
#3
(07-05-2020, 02:11 PM)josemendez Wrote: Hi,

You've probably pinned the rope inside the cube, very close to its center of mass. This will result in two issues:
- The cube won't rotate, as any force applied very close to the center of mass of a rigidbody does not cause a rotation (or a really small one).
- The rope and the cube will generate a force-feedback loop, as there's now two constraints that can't be met at the same time:
  • PinConstraint: the end of the rope must be inside the cube.
  • CollisionConstraint: the end of the rope must be outside of the cube.

Clearly these two are at odds with each other: the collision constraint pushes the rope away from the collider, but the pin constraint pushes it back in. This results in the seizure-like behavior in your video.

This situation, and why it is undesirable is explained in the last part of the manual page for pin constraints:
http://obi.virtualmethodstudio.com/tutor...aints.html

Solutions:
- Set the last few particles in the rope to the same phase as the cube collider.
- Make sure the rope is pinned outside of the cube (the offset position you pass to the AddConstraint method is the position the particle will be pinned to, expressed in the collider's local space).

Let me know how it goes!

Hello Jose,
thank you for your answer.
The rope is pinned at the point where the ray hits the object:
Code:
batch.AddConstraint(blueprint.activeParticleCount - 1, hookAttachment.collider.GetComponent<ObiCollider>(),
     hookAttachment.transform.InverseTransformPoint(hookAttachment.point), Quaternion.identity);

The InTangentVector of the control point at that end of the rope points upwards.

I assume that this (and the feedback loop you mentioned) is the cause for the spazzing when I attach the rope to a vertical side of the cube/block and a stable behavior when the rope is attached on the top side of the block.

To quickly test it I moved the pin constraint -1m in the Z axis and it helped with the crazy movement.

Code:
batch.AddConstraint(blueprint.activeParticleCount - 1, hookAttachment.collider.GetComponent<ObiCollider>(),
     hookAttachment.transform.InverseTransformPoint(hookAttachment.point) + Vector3.back, Quaternion.identity);

Unfortunately something still keeps the block from rotating as long as the rope is attached.
The angular velocity is not just low, it quickly oscillates around zero and it does not change even if the small cube lands on the block.

I've uploaded the project to WeTransfer. You'll get the download link by email.
The size was too great for forum attachments (35MB zip including the Obi asset).
Could you please take a look at it?
Reply
#4
I found an interesting thing and it's exactly the opposite of what I would expect.

When I set the offset parameter of batch.AddConstraint() to vector3.zero, it pins the rope to the center of the collider and the attached object is free to rotate.
Of course the physics goes bonkers because of the conflicting constraints, but if I change the phase, it hangs there and rotates around the local center freely.
Any other value of the offset I tried, within or outside of the block, prevents the rotation.
Reply
#5
(07-05-2020, 09:24 PM)pQuex Wrote: I found an interesting thing and it's exactly the opposite of what I would expect.

When I set the offset parameter of batch.AddConstraint() to vector3.zero, it pins the rope to the center of the collider and the attached object is free to rotate.
Of course the physics goes bonkers because of the conflicting constraints, but if I change the phase, it hangs there and rotates around the local center freely.
Any other value of the offset I tried, within or outside of the block, prevents the rotation.

Hi,

Thing is you're passing Quaternion.identity as the target rotation of the pin constraint. By default, pins constraint both translation and orientation. So after instantiating the blueprint, you need to increase the rotation compliance. Paste this right after the "rope.GetComponent<MeshRenderer>().enabled = true;" line at the end of AttachHook() method.

Code:
var runtimePins = rope.GetConstraintsByType(Oni.ConstraintType.Pin);
        var runtimeBatch = runtimePins.GetBatchInterfaces()[0] as ObiPinConstraintsBatch;

        for (int i = 0; i < runtimeBatch.constraintCount; i++)
        {
            runtimeBatch.stiffnesses[i * 2] = 0; // 0 translation compliance.
            runtimeBatch.stiffnesses[i * 2 + 1] = 10000; // very large rotation compliance (allows rotations)
        }

This will grab the runtime pin constraints batch, and set the orientation compliance to a high value for all of them, allowing free rotation. This isn't yet documented anywhere (working on it), so my apologies: it wasn't exactly easy to figure out, and I would not expect anyone to be able to do so without a bit of help.
Reply
#6
Hello Jose,

thank you, it works perfectly.
With your dedication to customer support you can afford having some gaps in the documentation.  Guiño
Reply