Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
ObiColliders and Procedural Load
#1
Hello,

Like a lot of people who need an asset as sophisticated as Obi, my world loads procedurally and every instance of every object has to be created by code not exist in a pre-fabricated scene as a prop.

I've run into kind of a major hangup with getting Obi Rope to work in that environment. From the introduction(?) on the tutorials page: "Add a ObiCollider component to any collider in your scene to make it work with Obi."

I have no idea what colliders in my scene need to interact with the rope, how would I? Once loaded my scene has around 3,000 active colliders at a time (out of millions total). Adding another component to all of them during runtime especially when it searches the hierarchy for a rigidbody that will never exist seems like an impossible amount of overhead. A rope will only be active in my scene about 0.1% of the time.

So the only solution I can think of is to basically spherecast from the position of every particle, every frame, whenever a rope is active, and for each hit test if it already has the component, then find the rigidbody and assign the ObiRigidbody manually because I need a reference, then assign the ObiCollider because I need a reference to that as well, then test each reference each frame and remove it if it is out of range of the rope, and remove all those components if the rope becomes inactive. That would be, again, probably more overhead than the rope itself creates - and really I think an unfair amount of code that I would have to write to get this rope to be a rope.

So my question is what is the correct solution to this problem?

What I'm trying to do is really pretty straightforward; just have a rope that behaves like you'd expect a rope to behave, not passing through other physical objects.

Side note, creating a rope by code is something that seems to come up fairly often in this fourm and looking at the commonly referenced ObiRopeHelper.cs is still requiring me to make some fairly large logical leaps to get anything but runtime errors that leave me wondering if I'm using the product wrong, creating wasteful code or at least wasting a lot of time. An example that actually shows how to create an example rope by code would be extremely helpful. You can't just do new ObiRopeHelper() because that script assumes it's already set up in the hierarchy with references to unspecified objects.

Thanks,

Jon
Reply
#2
(18-01-2018, 07:03 AM)jonworks Wrote: Hello,

Like a lot of people who need an asset as sophisticated as Obi, my world loads procedurally and every instance of every object has to be created by code not exist in a pre-fabricated scene as a prop.

I've run into kind of a major hangup with getting Obi Rope to work in that environment. From the introduction(?) on the tutorials page: "Add a ObiCollider component to any collider in your scene to make it work with Obi."

I have no idea what colliders in my scene need to interact with the rope, how would I? Once loaded my scene has around 3,000 active colliders at a time (out of millions total). Adding another component to all of them during runtime especially when it searches the hierarchy for a rigidbody that will never exist seems like an impossible amount of overhead. A rope will only be active in my scene about 0.1% of the time.

So the only solution I can think of is to basically spherecast from the position of every particle, every frame, whenever a rope is active, and for each hit test if it already has the component, then find the rigidbody and assign the ObiRigidbody manually because I need a reference, then assign the ObiCollider because I need a reference to that as well, then test each reference each frame and remove it if it is out of range of the rope, and remove all those components if the rope becomes inactive. That would be, again, probably more overhead than the rope itself creates - and really I think an unfair amount of code that I would have to write to get this rope to be a rope.

So my question is what is the correct solution to this problem?

What I'm trying to do is really pretty straightforward; just have a rope that behaves like you'd expect a rope to behave, not passing through other physical objects.

Side note, creating a rope by code is something that seems to come up fairly often in this fourm and looking at the commonly referenced ObiRopeHelper.cs is still requiring me to make some fairly large logical leaps to get anything but runtime errors that leave me wondering if I'm using the product wrong, creating wasteful code or at least wasting a lot of time. An example that actually shows how to create an example rope by code would be extremely helpful. You can't just do new ObiRopeHelper() because that script assumes it's already set up in the hierarchy with references to unspecified objects.

Thanks,

Jon

Hi Jon,

Obi does not use Unity's built-in physics engine (as it is way too unstable to create any useful rope, as you might have discovered). Instead we use our own position-based physics engine, and interface with Unity's colliders and rigidbodies via impulses. Since Unity does not give access at runtime to their collider broad phase and their raw data, we must use a wrapper over the regular "Collider" components that convert the Unity definition of a collider to our own engine's representation, and pass that information to our C++ engine for simulation.

Note that once the ObiCollider component is added, you no longer need to take care of it. It will make Obi aware of that collider automatically, and will behave just like a regular Unity collider. You don't need to remove the ObiCollider components once you've added them either. If a collider is not moving or rotating, the ObiCollider component does absolutely nothing. Only when any property of the transform or the collider changes, it updates Obi's representation of that collider. This update is also very quick, since it updates transform and collider properties separately.

This is pretty much what every other custom physics engine in the store does (AgXDynamics and CaronteFX come to mind), and it's the best way to do it.

Usually this is not a problem, since most games use prefabs for their objects (ObiColliders and ObiRigidbodies are created in the editor and saved with the prefab, then just instantiated at runtime: so zero overhead).

Even if all your colliders are generated at runtime, adding an additional ObiCollider component is not a big deal. It's just one more component per object, and it only looks for a rigidbody component in the same object or up its hierarchy upon re-parenting (not every frame). Unless your objects are extremely deep compound collider hierarchies (with a rigidbody component way up the hierarchy for every leaf object) and you're instantiating millions of them per frame, you will notice no impact at all. This is a small constant-time operation per collider, done only once when you create it. Doing a sphere cast every frame for every particle will be much, much worse performance wise.

We've done tests with hundreds of thousands of procedurally generated colliders in the past and this was no problem at all. See:
http://blog.virtualmethodstudio.com/2017...whats-new/
Reply
#3
In response, the suggestion to add another component to every collider that ever exists in my scene won't work for me and I'll have to think of something else. I can accept this and keep a favorable impression of this asset because mine is not a typical use case.

But to expand on my side note, I'm finding it extremely challenging to get Obi Rope to work at all. It would be easy to dismiss my troubles by saying that Obi exposes a lot of options making it more flexible but harder to configure. However, the users who require a robust product also need to implement it in a "real" way - IE doing it procedurally instead of using the amateur approach of setting up a prefabricated scene for every "level" and making players wait for loading time every time they walk through a door. Unless you're making the eighty billionth Mario clone, that just doesn't work for most people anymore. 

Now far be it from me to ask you to write my code for me, but I've read through every forum post and the entire Tutorials section and it's left me more confused than when I started. Everything is organized requiring the user to understand the entire Obi system instead of just what they're using, and there are constant references to "solver's ObiColliderGroup" which I don't even think applies anymore, and the Creating a pendulum video doesn't even mention ObiColliders. Everything in those resources assumes a static game world that is not loaded in real time. I've tried copying how ObiRopeHelper.cs works three times from scratch. Everything I do trying to figure it out results in some bizarre and unpredictable behavior making me think my entire methodology is wrong, which wouldn't surprise me at all since I've been completely guessing my way through it.

There are only a handful of functions needed to make rope work in a procedural environment, and I know a huge number of users would appreciate guidance in this area:

public class MyRope { //NOT a monobehavior
        private ObiRope rope;

       
        /// <summary>
        /// Creates a straight rope anchored to a transform at the top.
        /// Transform may or may not move around and may or may not have a rigidbody.
        /// When you call this the rope will appear in the scene and immediately interact with gravity and objects with ObiColliders.
        /// Called from anywhere (main thread only)
        /// Called when there is an empty scene (aside from passed transform) with nothing loaded and no prefabs available.
        /// </summary>
        public void make_rope(Transform anchored_to, Vector3 attachment_point_offset, float rope_length) { 
            //WHAT GOES HERE???

            [i]//
rope = ????
[/i]

        }

        /// <summary>
        /// make_rope and add_pendulum may be called on the same frame.
        /// Just adds a pendulum to the rope on the un-anchored end.
        /// </summary>
        public void add_pendulum(ObiRigidbody pendulum, Vector3 attachment_point_offset) {
            //WHAT GOES HERE???
        }

        /// <summary>
        /// remove_pendulum and add_pendulum may be called on the same frame.
        /// </summary>
        public void remove_pendulum() {
            //WHAT GOES HERE???
        }

        /// <summary>
        /// Like extending or retracting a winch.
        /// May need to include changes to the iteration count, 
        /// tether setup, or rope weight to prevent rope stretching depending on length and attached mass.
        /// </summary>
        public void change_rope_length(float change_amount) {
            //WHAT GOES HERE???
        }
    }


I'm getting so frustrated trying to make this work that I've considered leaving a negative review - something I've never done after downloading over two dozen assets - and I probably would have already done so if not for the reasonable support response time with intelligent answers. I'm sure you can fill in the blanks and test within a couple of hours since you understand the product so well, and if you can't then it should only underscore the importance of this code sample. I made it extremely generic on purpose so it would benefit the largest amount of users; it doesn't suit my case perfectly but would tell me what I need to know to figure it out.

Thanks,

Jon
Reply
#4
(01-02-2018, 08:23 PM)jonworks Wrote: In response, the suggestion to add another component to every collider that ever exists in my scene won't work for me and I'll have to think of something else. I can accept this and keep a favorable impression of this asset because mine is not a typical use case.

But to expand on my side note, I'm finding it extremely challenging to get Obi Rope to work at all. It would be easy to dismiss my troubles by saying that Obi exposes a lot of options making it more flexible but harder to configure. However, the users who require a robust product also need to implement it in a "real" way - IE doing it procedurally instead of using the amateur approach of setting up a prefabricated scene for every "level" and making players wait for loading time every time they walk through a door. Unless you're making the eighty billionth Mario clone, that just doesn't work for most people anymore. 

Now far be it from me to ask you to write my code for me, but I've read through every forum post and the entire Tutorials section and it's left me more confused than when I started. Everything is organized requiring the user to understand the entire Obi system instead of just what they're using, and there are constant references to "solver's ObiColliderGroup" which I don't even think applies anymore, and the Creating a pendulum video doesn't even mention ObiColliders. Everything in those resources assumes a static game world that is not loaded in real time. I've tried copying how ObiRopeHelper.cs works three times from scratch. Everything I do trying to figure it out results in some bizarre and unpredictable behavior making me think my entire methodology is wrong, which wouldn't surprise me at all since I've been completely guessing my way through it.

There are only a handful of functions needed to make rope work in a procedural environment, and I know a huge number of users would appreciate guidance in this area:

public class MyRope { //NOT a monobehavior
        private ObiRope rope;

       
        /// <summary>
        /// Creates a straight rope anchored to a transform at the top.
        /// Transform may or may not move around and may or may not have a rigidbody.
        /// When you call this the rope will appear in the scene and immediately interact with gravity and objects with ObiColliders.
        /// Called from anywhere (main thread only)
        /// Called when there is an empty scene (aside from passed transform) with nothing loaded and no prefabs available.
        /// </summary>
        public void make_rope(Transform anchored_to, Vector3 attachment_point_offset, float rope_length) { 
            //WHAT GOES HERE???

            [i]//
rope = ????
[/i]

        }

        /// <summary>
        /// make_rope and add_pendulum may be called on the same frame.
        /// Just adds a pendulum to the rope on the un-anchored end.
        /// </summary>
        public void add_pendulum(ObiRigidbody pendulum, Vector3 attachment_point_offset) {
            //WHAT GOES HERE???
        }

        /// <summary>
        /// remove_pendulum and add_pendulum may be called on the same frame.
        /// </summary>
        public void remove_pendulum() {
            //WHAT GOES HERE???
        }

        /// <summary>
        /// Like extending or retracting a winch.
        /// May need to include changes to the iteration count, 
        /// tether setup, or rope weight to prevent rope stretching depending on length and attached mass.
        /// </summary>
        public void change_rope_length(float change_amount) {
            //WHAT GOES HERE???
        }
    }


I'm getting so frustrated trying to make this work that I've considered leaving a negative review - something I've never done after downloading over two dozen assets - and I probably would have already done so if not for the reasonable support response time with intelligent answers. I'm sure you can fill in the blanks and test within a couple of hours since you understand the product so well, and if you can't then it should only underscore the importance of this code sample. I made it extremely generic on purpose so it would benefit the largest amount of users; it doesn't suit my case perfectly but would tell me what I need to know to figure it out.

Thanks,

Jon

Hi Jon,

There's a sample script included in the Obi/Scripts/Utils/ folder called "ObiRopeHelper.cs" that creates a rope at runtime from scratch. I know you mention you've been trying to follow it, but that's all there is to it really. Fixing the rope at one of its ends is just a matter of setting the first particle's inverse mass to zero using the Get/Set methods for particle properties (see http://obi.virtualmethodstudio.com/tutor...icles.html). ObiRopeHelper fixes both ends instead of just one, though.

The steps to follow when doing this programmatically are pretty much the same ones you do in the editor manually (Obi has been designed primarily with the goal to be able to create everything at runtime. Then, editor classes wrap this functionality and expose it trough the editor's UI):

- Create a ObiSolver, a ObiRope, a rope section and a one of the curve path components (we use ObiCatmullRomCurve in the sample script).
- Set the rope up: assign the solver, the path, and the section references.
- Add control points to the curve, to create any initial shape you want for the rope.
- Call GeneratePhysicRepresentationForMesh() (keep in mind that it is a coroutine).
- Call AddToSolver() on the rope.

Changing rope length can be accomplished trough the use of the ObiRopeCursor component. Just add this component to your rope and call ChangeLength(newLength). There's an example of this in Obi/SampleScenes/Crane. Should be pretty straightforward to use.

I'll whip a complete example script for you tomorrow.

cheers,
Reply
#5
Here's the sample script:

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

public class RuntimeRopeGenerator
{
    private ObiRope rope;
    private ObiRopeCursor cursor;
    private ObiSolver solver;

    /// <summary>
    /// Creates a straight rope anchored to a transform at the top.
    /// Transform may or may not move around and may or may not have a rigidbody.
    /// When you call this the rope will appear in the scene and immediately interact with gravity and objects with ObiColliders.
    /// Called from anywhere (main thread only)
    /// Called when there is an empty scene (aside from passed transform) with nothing loaded and no prefabs available.
    /// </summary>
    public IEnumerator MakeRope(Transform anchoredTo, Vector3 attachmentOffset, float ropeLength)
    {
        // create a new GameObject with the required components: a solver, a rope, and a curve.
        // we also throw a cursor in to be able to change its length.
        GameObject ropeObject = new GameObject("rope",typeof(ObiSolver),
                                                      typeof(ObiRope),
                                                      typeof(ObiCatmullRomCurve),
                                                      typeof (ObiRopeCursor));

        // get references to all components:
        rope                     = ropeObject.GetComponent<ObiRope>();
        cursor                     = ropeObject.GetComponent<ObiRopeCursor>();
        solver                     = ropeObject.GetComponent<ObiSolver>();
        ObiCatmullRomCurve path = ropeObject.GetComponent<ObiCatmullRomCurve>();

        // set up component references (see ObiRopeHelper.cs)
        rope.Solver = solver;
        rope.ropePath = path;    
        rope.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");
        cursor.rope = rope;

        // set path control points (duplicate end points, to set curvature as required by CatmullRom splines):
        path.controlPoints.Clear();
        path.controlPoints.Add(Vector3.zero);
        path.controlPoints.Add(Vector3.zero);
        path.controlPoints.Add(Vector3.down*ropeLength);
        path.controlPoints.Add(Vector3.down*ropeLength);

        // parent the rope to the anchor transform:
        rope.transform.SetParent(anchoredTo,false);
        rope.transform.localPosition = attachmentOffset;

        // generate particles/constraints and add them to the solver (see ObiRopeHelper.cs)
        yield return rope.StartCoroutine(rope.GeneratePhysicRepresentationForMesh());
        rope.AddToSolver(null);

        // fix first particle in place (see http://obi.virtualmethodstudio.com/tutorials/scriptingparticles.html)
        rope.invMasses[0] = 0;
        Oni.SetParticleInverseMasses(solver.OniSolver,new float[]{0},1,rope.particleIndices[0]);
    }

    /// <summary>
    /// make_rope and add_pendulum may NOT be called on the same frame. You must wait for the make_rope coroutine to finish first, as creating a rope is an asynchronous operation.
    /// Just adds a pendulum to the rope on the un-anchored end.
    /// </summary>
    public void AddPendulum(ObiCollider pendulum, Vector3 attachmentOffset)
    {
        // simply add a new pin constraint (see http://obi.virtualmethodstudio.com/tutorials/scriptingconstraints.html)
        rope.PinConstraints.RemoveFromSolver(null);
        ObiPinConstraintBatch batch = (ObiPinConstraintBatch)rope.PinConstraints.GetBatches()[0];
        batch.AddConstraint(rope.UsedParticles-1, pendulum, attachmentOffset, 1);
        rope.PinConstraints.AddToSolver(null);
    }

    /// <summary>
    /// remove_pendulum and add_pendulum may be called on the same frame.
    /// </summary>
    public void RemovePendulum()
    {
        // simply remove all pin constraints (see http://obi.virtualmethodstudio.com/tutorials/scriptingconstraints.html)
        rope.PinConstraints.RemoveFromSolver(null);
        rope.PinConstraints.GetBatches()[0].Clear();
        rope.PinConstraints.AddToSolver(null);
    }

    /// <summary>
    /// Like extending or retracting a winch.
    /// May need to include changes to the iteration count,
    /// tether setup, or rope weight to prevent rope stretching depending on length and attached mass.
    /// Further explanation: there's no way to correlate iteration count or weight to length in order to maintain constant stiffness (not in Obi, nor any existing engine). You could create a single
    /// tether constraint joining the first and last particle in the rope, and set its length to be rope.RestLenght. This is a common workaround in many physics engines.
    /// </summary>
    public void ChangeRopeLength(float changeAmount)
    {
        // the cursor will automatically add/remove/modify constraints and particles as needed to obtain the new length.
        cursor.ChangeLength(rope.RestLength + changeAmount);
    }
}

And a minimal example on how to use it:

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

public class RuntimeRopeGeneratorUse : MonoBehaviour
{
    public ObiCollider pendulum;
    RuntimeRopeGenerator rg;

    public IEnumerator Start()
    {
        rg = new RuntimeRopeGenerator();

        // Create a rope:
        yield return rg.MakeRope(transform,Vector3.zero,1);

        // Add a pendulum (you should adjust the attachment point depending on your particular pendulum object)
        rg.AddPendulum(pendulum,Vector3.up*0.5f);
    }

    public void Update(){
    
        if (Input.GetKey(KeyCode.W)){
            rg.ChangeRopeLength(- Time.deltaTime);
        }

        if (Input.GetKey(KeyCode.S)){
            rg.ChangeRopeLength(  Time.deltaTime);
        }
        
    }
}

Everything is created from scratch. No pre-existing rope, solver, or anything is needed, an empty scene will do fine. Most of the code is copy-pasted from the scripting section of the manual or the ObiRopeHelper class, the only real difference is that the rope and its components are created using new GameObject() instead of being public variables. Also I´ve made a few assumptions: the rope is completely straight, looks down the Y axis, and has standard resolution and thickness. Also I've added no material to its renderer, so it will be pink.

To test it just add an empty GameObject to your scene, add the RuntimeRopeGeneratorUse component to it and press Play. Optionally, provide a ObiCollider object to act as the pendulum. Note that this collider can also be created at runtime, by simply calling new GameObject() with the appropriate parameters.

I've commented both scripts to make things simpler to understand. Note that creating a rope is a potentially heavy operation (it all depends on your rope length and resolution!) so rope.GeneratePhysicRepresentationForMesh() is a coroutine to avoid stalling your game. This means that you cannot make a rope and begin using it in the same frame, you must use the yield instruction to wait until it has been created. Please note that if calling MakeRope() and other methods in the same frame is a requirement, you can just advance the coroutine manually until it is done by calling MoveNext.

You can call AddPendulum(), RemovePendulum() and ChangeLength() multiple times per frame, as often as you wish and in any order. By default the rope will collide with all ObiColliders present in the scene, as long as they are in the "Default" layer.

cheers,

Edit: quick and dirty video showing the result (apologies for the quality). I use the W and S keys to lengthen/shorten the rope. Keep in mind that the maximum length of the rope is determined by the amount of particles in the particle pool (see http://obi.virtualmethodstudio.com/tutor...eters.html). Once no more particles are available in the pool, the rope will stop increasing its length.
Reply
#6
I've added a new page to the scripting section of the manual to cover this:

http://obi.virtualmethodstudio.com/tutor...ctors.html

I've used your code template as a final example so that it is available to everyone, as it seems runtime creation of cloth/rope/fluid is a common source of frustration for many users.

cheers,
Reply
#7
Wow! This is really great. Thank you for doing this. It has transformed the difficulty of using this asset for me.

Have you thought about putting a simple rope generator in a static function? 

So I've got the rope generating and something hanging from it, but I'm having the same problem as a lot of other people again - the rope is unusably elastic like a rubber band. In your comments you wrote:

/// Further explanation: there's no way to correlate iteration count or weight to length in order to maintain constant stiffness (not in Obi, nor any existing engine). You could create a single
/// tether constraint joining the first and last particle in the rope, and set its length to be rope.RestLenght. This is a common workaround in many physics engines.

I realize it's not possible to have perfectly uniform and predictable elasticity of the rope with this asset, however it has to be possible and it's absolutely critical to get it somewhere in the ballpark of not behaving like a rubber band. Will the settings for making this happen always be the same (and if so why isn't the rope configured like that by default) or will they vary depending on the length and attached mass of the rope? If they will vary depending on the length and attached mass then there must be some algorithm that can evaluate those variables and output at least approximate settings.

It might be necessary to derive that algorithm through trial and error, which is what I've been trying to do. But this is what I've run into.

The three factors I've found that play into this are rope mass (INV_MASSES), distance constraint iterations, and tethers.

By adjusting the distance constraint iterations while the application is running in editor, I have not been able to observe any difference in rope behavior. I ran into the same situation when I tried adding a pin constraint while the application is running. Are these things that can only be done when the application is stopped?

I tried adding tethers but I can't seem to make anything happen. Does nothing:
        tether_constraints = rope.GetComponent<ObiTetherConstraints>();
        tether_constraints.stiffness = .99f;
        tether_constraints.tetherScale = .99f;
        tether_constraints.PushDataToSolver();
This changes the values displayed in inspector but does not change the behavior of the rope.

Also does nothing:
rope
.GenerateTethers(ObiActor.TetherType.Hierarchical);


I can click generate tethers in inspector while the application is running, and the behavior of the rope changes (although it looks like it won't be a complete solution) - and also whenever I change the length of the rope with cursor.ChangeLength the tethers get completely messed up.

Thanks,

Jon
Reply
#8
(03-02-2018, 04:13 AM)jonworks Wrote: Wow! This is really great. Thank you for doing this. It has transformed the difficulty of using this asset for me.

Have you thought about putting a simple rope generator in a static function? 

So I've got the rope generating and something hanging from it, but I'm having the same problem as a lot of other people again - the rope is unusably elastic like a rubber band. In your comments you wrote:

/// Further explanation: there's no way to correlate iteration count or weight to length in order to maintain constant stiffness (not in Obi, nor any existing engine). You could create a single
/// tether constraint joining the first and last particle in the rope, and set its length to be rope.RestLenght. This is a common workaround in many physics engines.

I realize it's not possible to have perfectly uniform and predictable elasticity of the rope with this asset, however it has to be possible and it's absolutely critical to get it somewhere in the ballpark of not behaving like a rubber band. Will the settings for making this happen always be the same (and if so why isn't the rope configured like that by default) or will they vary depending on the length and attached mass of the rope? If they will vary depending on the length and attached mass then there must be some algorithm that can evaluate those variables and output at least approximate settings.

It might be necessary to derive that algorithm through trial and error, which is what I've been trying to do. But this is what I've run into.

The three factors I've found that play into this are rope mass (INV_MASSES), distance constraint iterations, and tethers.

By adjusting the distance constraint iterations while the application is running in editor, I have not been able to observe any difference in rope behavior. I ran into the same situation when I tried adding a pin constraint while the application is running. Are these things that can only be done when the application is stopped?

I tried adding tethers but I can't seem to make anything happen. Does nothing:
        tether_constraints = rope.GetComponent<ObiTetherConstraints>();
        tether_constraints.stiffness = .99f;
        tether_constraints.tetherScale = .99f;
        tether_constraints.PushDataToSolver();
This changes the values displayed in inspector but does not change the behavior of the rope.

Also does nothing:
rope
.GenerateTethers(ObiActor.TetherType.Hierarchical);


I can click generate tethers in inspector while the application is running, and the behavior of the rope changes (although it looks like it won't be a complete solution) - and also whenever I change the length of the rope with cursor.ChangeLength the tethers get completely messed up.

Thanks,

Jon

Hi Jon,

There is no general way to get 100% non-stretchy chain of constraints in Obi, or in any existing engine, unless you're willing to accept some limitations. It's just the way physics engines (specially iterative ones) work. If you need inextensibility in the general case, your only options are:

- Reduce the physics timestep (Edit->Project settings->Time->Fixed timestep)
- Increase the amount of solver iterations.

Both the issue and the solutions are common to all engines, not just Obi: PhysX, Box2D, Bullet, Newton, etc. Try setting Unity's fixed timestep to 0.01 and the amount of distance constraint iterations to 15, for relatively short ropes this will work ok, unless your mass ratios are extremely large (very heavy object hanging from a very light rope) or your rope has lots of constraints. Both settings can be applied at any time both in editor and play mode, and will have immediate effect on the rope behavior, making it less elastic.

The right values depend a lot on the particular situation you're trying to simulate. Longer ropes (ropes that have many constraints), ropes dealing with large mass ratios, and ropes dealing with high speeds will all need more iterations and/or shorter timesteps to look inextensible. The stiffness of the rope does not increase linearly with the amount of iterations, also depends on the timestep and the maximum speed, the mass ratios, etc. In fact, only very recently a way to get stiffness working in a semi-physical way with position-based engines was discovered (XPBD, which is implemented in Obi since 3.0): http://mmacklin.com/xpbd.pdf. Still, the minimum amount of iterations needed to achieve a certain stiffness given the timestep, the constraints graph and the maximum mass ratio is just not possible to calculate. So your best bet is to crank up the amount of iterations and/or reduce the timestep until the worst possible case in your game works acceptably well.

The default parameters provided by Obi are meant for acceptable quality in short ropes, with a minimum performance hit. Most people want things to run as fast as possible out of the box, then tweak them if they don't fit their particular use case. Obi is meant for advanced users, so knowledge about basic concepts like timestep and convergence is assumed (since they are common to all engines and absolutely needed to be able to use any of them effectively for any non-trivial purpose), as it is that most users will immediately resort to tweak Unity's timestep and the solver iteration count if the simulation is too stretchy.

Regarding your tethers code:

- Setting tether parameters and pushing them to the solver will have no effect at all if no tether constraints have been generated (for obvious reasons). Also setting the length/stiffness to be 99% of their default values will have very little, if any, noticeable effect.
- Generating tethers will only have any effect after calling GeneratePhysicalRepresentationForMesh has finished. There's no particles to add tethers to before that.

A good solution for the particular case of a pendulum (or any object hanging from a rope) is outlined in the same comment you quoted:

Quote:You could create a single tether constraint joining the first and last particle in the rope, and set its length to be rope.RestLenght. This is a common workaround in many physics engines.

This approach is extremely simple to implement, routinely used in a lot of games and works well for most situations. Here's a basic implementation using the same sample code:

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

public class RuntimeRopeGenerator
{
    private ObiRope rope;
    private ObiRopeCursor cursor;
    private ObiSolver solver;
    private int pinnedParticle = -1;

    /// <summary>
   /// Creates a straight rope anchored to a transform at the top.
   /// Transform may or may not move around and may or may not have a rigidbody.
   /// When you call this the rope will appear in the scene and immediately interact with gravity and objects with ObiColliders.
   /// Called from anywhere (main thread only)
   /// </summary>
    public IEnumerator MakeRope(Transform anchoredTo, Vector3 attachmentOffset, float ropeLength)
    {
        // create a new GameObject with the required components: a solver, a rope, and a curve.
        // we also throw a cursor in to be able to change its length.
        GameObject ropeObject = new GameObject("rope",typeof(ObiSolver),
                                                      typeof(ObiRope),
                                                      typeof(ObiCatmullRomCurve),
                                                      typeof (ObiRopeCursor));

        // get references to all components:
        rope                     = ropeObject.GetComponent<ObiRope>();
        cursor                     = ropeObject.GetComponent<ObiRopeCursor>();
        solver                     = ropeObject.GetComponent<ObiSolver>();
        ObiCatmullRomCurve path = ropeObject.GetComponent<ObiCatmullRomCurve>();

        // set up component references (see ObiRopeHelper.cs)
        rope.Solver = solver;
        rope.ropePath = path;    
        rope.section = Resources.Load<ObiRopeSection>("DefaultRopeSection");
        cursor.rope = rope;

        // set path control points (duplicate end points, to set curvature as required by CatmullRom splines):
        path.controlPoints.Clear();
        path.controlPoints.Add(Vector3.zero);
        path.controlPoints.Add(Vector3.zero);
        path.controlPoints.Add(Vector3.down*ropeLength);
        path.controlPoints.Add(Vector3.down*ropeLength);

        // parent the rope to the anchor transform:
        rope.transform.SetParent(anchoredTo,false);
        rope.transform.localPosition = attachmentOffset;

        // generate particles/constraints and add them to the solver (see ObiRopeHelper.cs)
        yield return rope.StartCoroutine(rope.GeneratePhysicRepresentationForMesh());
        rope.AddToSolver(null);

        // get the last particle in the rope at its rest state.
        pinnedParticle = rope.UsedParticles-1;

        // add a tethers batch:
        ObiTetherConstraintBatch tetherBatch = new ObiTetherConstraintBatch(true,false,0,1);
        rope.TetherConstraints.AddBatch(tetherBatch);
        UpdateTethers();

        // fix first particle in place (see http://obi.virtualmethodstudio.com/tutorials/scriptingparticles.html)
        rope.invMasses[0] = 0;
        Oni.SetParticleInverseMasses(solver.OniSolver,new float[]{0},1,rope.particleIndices[0]);
    }

    /// <summary>
   /// MakeRope and AddPendulum may NOT be called on the same frame. You must wait for the MakeRope coroutine to finish first, as creating a rope is an asynchronous operation.
   /// Just adds a pendulum to the rope on the un-anchored end.
   /// </summary>
    public void AddPendulum(ObiCollider pendulum, Vector3 attachmentOffset)
    {
        // simply add a new pin constraint (see http://obi.virtualmethodstudio.com/tutorials/scriptingconstraints.html)
        rope.PinConstraints.RemoveFromSolver(null);
        ObiPinConstraintBatch batch = (ObiPinConstraintBatch)rope.PinConstraints.GetBatches()[0];
        batch.AddConstraint(pinnedParticle, pendulum, attachmentOffset, 1);
        rope.PinConstraints.AddToSolver(null);
    }

    /// <summary>
   /// RemovePendulum and AddPendulum may be called on the same frame.
   /// </summary>
    public void RemovePendulum()
    {
        // simply remove all pin constraints (see http://obi.virtualmethodstudio.com/tutorials/scriptingconstraints.html)
        rope.PinConstraints.RemoveFromSolver(null);
        rope.PinConstraints.GetBatches()[0].Clear();
        rope.PinConstraints.AddToSolver(null);
    }

    /// <summary>
    /// Like extending or retracting a winch.
    /// </summary>
    public void ChangeRopeLength(float changeAmount)
    {
        // the cursor will automatically add/remove/modify constraints and particles as needed to obtain the new length.
        cursor.ChangeLength(rope.RestLength + changeAmount);
        UpdateTethers();
    }

    private void UpdateTethers()
    {
        rope.TetherConstraints.RemoveFromSolver(null);
        ObiTetherConstraintBatch batch = (ObiTetherConstraintBatch)rope.TetherConstraints.GetBatches()[0];
        batch.Clear();
        batch.AddConstraint(0,pinnedParticle, rope.RestLength, 1, 1);
        batch.Cook();
        rope.TetherConstraints.AddToSolver(null);
    }
}

As you can see the only additions are the creation of a new tethers batch, and the UpdateTethers() method that re-generates a single tether of the adequate length every time the rope length changes. Note that if your rope is long enough it might still "droop" at low iteration counts even though the pendulum object maintains the proper distance from the attachment point.
Reply
#9
Hello,

Thanks for replying on a Saturday.

Can you tell me what the practical limit is for rope length in this asset? I've taken every suggestion and I'm still not able to get anything that even vaguely resembles how a rope would behave.

About the tether solution you mentioned, "Note that if your rope is long enough it might still "droop"". In this attachment my rope is only 6m long and this is a screen shot of its steady state (after the rope is settled). You can see that the iterations are set to 50. Currently the mass of the attached Rigidbody is 20 and the mass of each rope particle is 20. I was not able to achieve any significant change to this unreasonable amount of droop by changing either the rope mass or Rigidbody mass. I've set iterations all the way up at 500 and it reduces the droop, but still not enough to look even adequate (plus that causes the rope to bounce back and forth forever and never settle).

Rope solutions using the regular physics engine can simulate rope more than 10x this long (60m) and work OK the 90% of the time they aren't crashing the game. What am I doing wrong?

Thanks,

Jon
Reply
#10
(03-02-2018, 08:13 PM)jonworks Wrote: Hello,

Thanks for replying on a Saturday.

Can you tell me what the practical limit is for rope length in this asset? I've taken every suggestion and I'm still not able to get anything that even vaguely resembles how a rope would behave.

About the tether solution you mentioned, "Note that if your rope is long enough it might still "droop"". In this attachment my rope is only 6m long and this is a screen shot of its steady state (after the rope is settled). You can see that the iterations are set to 50. Currently the mass of the attached Rigidbody is 20 and the mass of each rope particle is 20. I was not able to achieve any significant change to this unreasonable amount of droop by changing either the rope mass or Rigidbody mass. I've set iterations all the way up at 500 and it reduces the droop, but still not enough to look even adequate (plus that causes the rope to bounce back and forth forever and never settle).

Rope solutions using the regular physics engine can simulate rope more than 10x this long (60m) and work OK the 90% of the time they aren't crashing the game. What am I doing wrong?

Thanks,

Jon

Hi Jon,

Is it absolutely necessary for your purpose to use such high rope resolution? I think you'd achieve better results by using lower resolution and cranking the rope smoothness up a bit. This would result in less constraints, which would require fewer iterations to appear stiff. See: http://obi.virtualmethodstudio.com/tutor...eters.html

The regular physics engine allows to use capsules as the building blocks for rope. This means they usually use less rigidbodies to create the rope (equivalent to a lower resolution rope in Obi) so they need very few iterations. The downside is that as you might have discovered by now, traditional velocity-based constraint solvers are exceptionally unstable for many applications.

The practical length limit in ObiRope is very much determined by the resolution, your simulation budget and the accuracy you need. Long ropes made of 10 particles and short ropes made of 10 particles are basically the same, as far as the physics solver is concerned. If lowering the resolution is not an option for you, instead of creating just 1 tether you can create one tether per particle, and attach them all to the one that's fixed at the top.
Reply