Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  How to change the rest length of an existing rope at runtime?
#1
Pregunta 
I've encountered a situation where I need to change the rest length of a rope that is already present in the scene.

I think I've figured out the solution, and the code seems to work, but I wanted to have you look over it to make sure I'm doing everything correctly (or if there's a better way).

I've added the following method inside ObiRopeBlueprintBase:

Code:
public void SetRestLength(float newLength)
{
    if(path == null)
    {
        Debug.LogError("Cannot set length because the path is null.", this);
        return;
    }

    if(path.Length == 0)
    {
        Debug.LogError("Cannot set length because it is currently 0.", this);
        return;
    }

    float multiplier = newLength / path.Length;

    if(float.IsInfinity(multiplier) || float.IsNaN(multiplier))
    {
        Debug.LogError($"Cannot set length due to floating point error. Old length: {m_RestLength} New length: {newLength}", this);
        return;
    }

    m_InterParticleDistance *= multiplier;
    m_RestLength *= multiplier;

    if(restLengths != null)
    {
        int count = restLengths.Length;
        for(int i = 0; i < count; i++)
        {
            restLengths[i] *= multiplier;
        }
    }
    else
    {
        Debug.LogError($"{nameof(restLengths)} is null. Perhaps it wasn't initialized?", this);
    }

    var ssData = stretchShearConstraintsData;
    if(ssData != null)
    {
        var batches = ssData.batches;
        var batchesCount = batches.Count;

        for(int iBatch = 0; iBatch < batchesCount; iBatch++)
        {
            var batch = batches[iBatch];

            var rlenList = batch.restLengths;
            var rlenListCount = rlenList.count;

            for(int iRest = 0; iRest < rlenListCount; iRest++)
            {
                rlenList[iRest] *= multiplier;
            }
        }
    }

    var dData = distanceConstraintsData;
    if(dData != null)
    {
        var batches = dData.batches;
        var batchesCount = batches.Count;

        for(int iBatch = 0; iBatch < batchesCount; iBatch++)
        {
            var batch = batches[iBatch];

            var rlenList = batch.restLengths;
            var rlenListCount = rlenList.count;

            for(int iRest = 0; iRest < rlenListCount; iRest++)
            {
                rlenList[iRest] *= multiplier;
            }
        }
    }
}

Since I'm doing this after the rope has been made, I get the feeling I might not need to do this from the blueprint. But as far as I know this is where the "data" for the rope is stored, whereas the rope itself represents an instance. (RopeBlueprint is to Rope as Mesh is to MeshFilter/MeshRenderer)

Do I, for example, need to call ObiActorBlueprint.OnBlueprintGenerate after making this modification?

On a side note, if my analogy between blueprints and actors vs. meshes and mesh renderers is accurate, I have a suggestion: (Ignore this section if this comparison is way off.) I noticed that ObiRopeBase.cs contains a "restLength" property that is recalculated under some conditions. It seems to me that since all ropes that share the same blueprint would have the same rest length, this data should be stored and calculated inside the blueprint:
  • All ropes using the blueprint will have the correct restLength value automatically the next time they read the property.
  • No need to recalculate the same value for multiple ropes sharing the same blueprint.
  • No need to use a callback to update the rope instances using the blueprint when it changes. (I don't know to trigger these.)
  • Could be useful during blueprint generation (as I had encountered before in a previous thread).
  • All the data about the blueprint (and common to all ropes that share the blueprint) is organized in one location.
  • The existing property can remain as a shortcut to writing rope.blueprint.restLength.
Reply
#2
(26-05-2021, 09:27 PM)Hatchling Wrote: I've encountered a situation where I need to change the rest length of a rope that is already present in the scene.

I think I've figured out the solution, and the code seems to work, but I wanted to have you look over it to make sure I'm doing everything correctly (or if there's a better way).

I've added the following method inside ObiRopeBlueprintBase:

[code...]

Since I'm doing this after the rope has been made, I get the feeling I might not need to do this from the blueprint. But as far as I know this is where the "data" for the rope is stored, whereas the rope itself represents an instance. (RopeBlueprint is to Rope as Mesh is to MeshFilter/MeshRenderer)

As far as i can see, this code changes the rest length using a multiplier. Also, it changes the rest length of all constraints, so that they pull particles closer together or further apart. This doesn't really count as changing the rest length of the rope, since it changes both its length and its resolution (amount of particles per length unit). It's more like compressing/stretching the rope, similar to what the "stretching scale" parameter of the rope does.

As long as this is your intended result, the code is fine.


(26-05-2021, 09:27 PM)Hatchling Wrote: Do I, for example, need to call ObiActorBlueprint.OnBlueprintGenerate after making this modification?

OnBlueprintGenerate basically reloads the blueprint: removes the actor from the solver and immediately re-adds it, copying all particle and constraint data from the blueprint to the solver. If you're doing this at runtime, then it is a good idea to call OnBlueprintGenerate so that the changes to blueprint constraint rest lengths are applied.

A less taxing equivalent would be to modify the constraints loaded in the solver too, not just the ones in the blueprint. This would avoid having to reload particle data, since it does not change at all when you call your method.


(26-05-2021, 09:27 PM)Hatchling Wrote: It seems to me that since all ropes that share the same blueprint would have the same rest length.

Nope Sonrisa. The restLength value in the blueprint is used as the rope's initial rest length, but it can later change. Eg: you can change the rest length of a rope at runtime, using ObiRopeCursor (http://obi.virtualmethodstudio.com/tutor...ursor.html). So different instances of the same blueprint can end up having completely different rest lengths.

Same thing happens with tearable cloth: tearable cloth blueprints have a topology asset (a half-edge data structure), that is copied over to the instances. Then each individual instance can be independently torn in different ways. Tearing a cloth will only update its local copy of the topology.
Reply
#3
(27-05-2021, 09:05 AM)josemendez Wrote: A less taxing equivalent would be to modify the constraints loaded in the solver too, not just the ones in the blueprint. This would avoid having to reload particle data, since it does not change at all when you call your method.

I am not sure if I am modifying the constraints loaded in the solver through the code I posted. Your suggestion seems like it'd be the better way to do things, as I'd be directly modifying the data used to simulate the rope. (Note that I intend to apply this change AFTER the rope and its blueprint are fully generated and ready to simulate.)

I am modifying native data (the blueprint appears to create this data) but this code applies before the blueprint is assigned to the rope. I'm guessing then that modifying the blueprint afterwards will not change the behaviour of the rope.

I'll look into what this ObiRopeCursor class does. Also I'm guessing that "stretching scale" is what I'd need to use to modify the rope's length at runtime.
Reply
#4
(27-05-2021, 05:42 PM)Hatchling Wrote: I am not sure if I am modifying the constraints loaded in the solver through the code I posted. Your suggestion seems like it'd be the better way to do things, as I'd be directly modifying the data used to simulate the rope. (Note that I intend to apply this change AFTER the rope and its blueprint are fully generated and ready to simulate.)

Your code only modifies the constraint data in the blueprint. See: http://obi.virtualmethodstudio.com/tutor...aints.html for an example on how to modify constraints loaded in the solver at runtime. I believe this is a better approach in your case.


(27-05-2021, 05:42 PM)Hatchling Wrote: I am modifying native data (the blueprint appears to create this data) but this code applies before the blueprint is assigned to the rope. I'm guessing then that modifying the blueprint afterwards will not change the behaviour of the rope.

Exactly. What you're doing is the equivalent of modifying an .fbx file on disk, then loading it. However, modifying the blueprint afterwards will not have an impact on the data that's already loaded.

You'd want to modify the data loaded to memory from the .fbx instead. The end result is the same.

(27-05-2021, 05:42 PM)Hatchling Wrote: I'll look into what this ObiRopeCursor class does. Also I'm guessing that "stretching scale" is what I'd need to use to modify the rope's length at runtime.

ObiRopeCursor add/removes particles and constraints from a rope at a given point, while keeping the same resolution (amount of particles per unit length). Like so:

Before:
(1)-------(2)----/cursor>/---(3)-------(4)

After:
(1)-------(2)----/cursor>/---(5)-------(6)-------(3)-------(4)

Cursors are used in the Crane and FreightLift sample scenes to change the length of the rope.

Setting the rope's stretchingScale parameter will scale the rest length of each distance constraint (each "edge" in the rope, if you will). This is basically the same thing you're doing. This results in longer rope, but also reduces resolution (less particles per unit length, as length increases but the amount of particles does not). Think of it as scaling the rope along its medial axis:

Scale x1:
(1)-------(2)-------(3)-------(4)

Scale x1.5:
(1)------------(2)------------(3)------------(4)

Scale x2:
(1)-----------------(2)------------------(3)-----------------(4)

You can see what this looks like by tinkering with it in play mode, it's exposed in the rope's inspector.
Reply
#5
One thing I noticed in ObiRopeCursor is that when new particles are inserted, the new particle doesn't inherit the velocity of its neighbor(s). While it is easy enough to copy and paste the code that does this for positions, it is something I thought was worth pointing out. It'd be important in cases where the rope is moving quickly; a new particle with a velocity of 0 inserted could cause the rope to suddenly jerk.
Reply