Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Help  Fixing particle causes slight "jump"?
#1
Hi!

Still working on a proper spooling-simulation - adding rope onto a spool.
In order to lock a particle i set its invMass to zero, store the particles offset from the spool and rotate the offset each update. The locked particle moves around the spool as intended.

However, each time a particle is locked, the "next" particle in line to be locked will jump slightly as if the previously locked particle straightens out the "bone" (see pictures below).
I am unable to determine if it is due to me setting the position slightly off, or if there is something happening behind the scenes between the locked particle and the next one.
When a particle is locked, the particle in question does not seem to move at all, which makes me believe that this is physics-related.
Is there any way to get around this? Might this be caused by the orientation of the particle? I am not the greatest at physics, but i might get it to work properly with some additional pointers Sonrisa

Also, is there any way to prevent the rope from making a twitch/skip when adding a new particle? Or at least to smooth it out a bit? Im having to start the simulation quite far from the actual spool to not get twitches from particles spawning.



Image 1: https://imgur.com/a/Y5kVhfJ
Image 2: https://imgur.com/a/YGdOb22
A red line indicate that the particle has been locked. The angle of the cable around the center of the spool increases with each locked particle due to the cable being straightened by the tiny "jump".


Best regards, Christoffer
Reply
#2
(18-12-2023, 10:32 AM)Christoffer Wrote: Hi!

Still working on a proper spooling-simulation - adding rope onto a spool.
In order to lock a particle i set its invMass to zero, store the particles offset from the spool and rotate the offset each update. The locked particle moves around the spool as intended.

However, each time a particle is locked, the "next" particle in line to be locked will jump slightly as if the previously locked particle straightens out the "bone" (see pictures below).
I am unable to determine if it is due to me setting the position slightly off, or if there is something happening behind the scenes between the locked particle and the next one.
When a particle is locked, the particle in question does not seem to move at all, which makes me believe that this is physics-related.
Is there any way to get around this?

Locking a particle in place will just prevent the simulation from updating its position. Bend constraints work by straightening out groups of 3 particles - projecting them onto a line, so if one of the particles in the group is impossible to move due to being locked then the other 2 will move more to compensate and get all 3 to sit straight.

In a real-world situation this would also happen: imagine pinching a flexible tube in place between two fingers, as you slide the fingers along the cable the section that's free would straighten out and bend less, up to the point where the cable is completely straight. In a discretized rope this is more noticeable as you can't smoothly "slide" the locked position along the rope, you lock/unlock specific points instead.

(18-12-2023, 10:32 AM)Christoffer Wrote: Might this be caused by the orientation of the particle? I am not the greatest at physics, but i might get it to work properly with some additional pointers Sonrisa

Ropes do not use particle orientation at all, only rods do. See:
http://obi.virtualmethodstudio.com/manua...setup.html


(18-12-2023, 10:32 AM)Christoffer Wrote: Also, is there any way to prevent the rope from making a twitch/skip when adding a new particle? Or at least to smooth it out a bit? Im having to start the simulation quite far from the actual spool to not get twitches from particles spawning.

This happens because adding a new particle increases the mass of the entire rope suddenly and changes its discretization, this leads to a sudden "jump" in dynamics as the rope adjusts to its new mass. This is similar to the locking issue. Ideally ropes should be continuous, but in a computer everything needs to be discretized so unfortunately there's no way around this.

kind regards,
Reply
#3
Hi,

Thanks for the swift reply!

If i understand your answers correctly, my current approach will not work for what i am trying to achieve?
If i have to "lock" particles for performance reasons, and i cant "lock" them without the bending constraint straightening the cable - this will not work.

Do you have any suggestions to another approach to the problem using ObiRope, or do you see this as a problem that needs a different/custom solution?
Or rather: would you be able to create something similar with ObiRope? (if not, then i definitely wont be able to!)

Best, Christoffer
Reply
#4
(18-12-2023, 12:18 PM)Christoffer Wrote: Hi,

Thanks for the swift reply!

If i understand your answers correctly, my current approach will not work for what i am trying to achieve?
If i have to "lock" particles for performance reasons, and i cant "lock" them without the bending constraint straightening the cable - this will not work.

Yes, it should work fine as long as particles are locked in the same position they're at prior to locking them, and resolution is high enough that it approximates a continuous cable well enough.

Now, if you need the cable bending to be kept to a minimum (very stiff cable) and at the same time reduce discretization issues to a minimum, these are at odds with each other since more particles means more work to keep the cable stiff.

Could you share the code you're currently using to lock particles in place, so that I may take a look to spot any potential problems?

kind regards,
Reply
#5
(18-12-2023, 01:04 PM)josemendez Wrote: Yes, it should work fine as long as particles are locked in the same position they're at prior to locking them, and resolution is high enough that it approximates a continuous cable well enough.

Now, if you need the cable bending to be kept to a minimum (very stiff cable) and at the same time reduce discretization issues to a minimum, these are at odds with each other since more particles means more work to keep the cable stiff.

Could you share the code you're currently using to lock particles in place, so that I may take a look to spot any potential problems?

kind regards,

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

public class SC_CableLengthController : MonoBehaviour
{
    public float speed = 0.5f;
    public SC_Spool spool;

    private ObiRopeCursor cursor;
    private ObiRope rope;

    private List<Vector3> particlePosOffsets;
    private List<bool> particleLockedStates;
    private List<float> particleUnlockedTime;
    private List<int> solverIndices;

    private float unlockedTimeMin = 6.0f;
    private int currentParticleIndex = 0;

    void Start()
    {
        cursor = GetComponent<ObiRopeCursor>();
        rope = GetComponent<ObiRope>();
        
        particlePosOffsets = new List<Vector3>();
        particleLockedStates = new List<bool>();
        particleUnlockedTime = new List<float>();
        solverIndices = new List<int>();

        // Offset to skip start/end of rope
        currentParticleIndex = 1;
        for (int i = 4; i < rope.elements.Count-1; i++)
        {
            particlePosOffsets.Add(Vector3.zero);
            particleLockedStates.Add(false);
            particleUnlockedTime.Add((i * - 1.0f) - unlockedTimeMin);
            solverIndices.Add(rope.solverIndices[i]);
        }

        solverIndices.Reverse();
    }

    void FixedUpdate()
    {
        // Increase rope length
        cursor.ChangeLength(rope.restLength + (Time.fixedDeltaTime * speed));

        Vector3 newPosition = new Vector3();
        for (int i = 0; i < solverIndices.Count; i++)
        {
            particleUnlockedTime[i] += Time.fixedDeltaTime;

            // Update locked particle positions
            if (particleLockedStates[i] == true)
            {
                // World position -> Rotate around spool pivot (0,0,0) for simplicity
                newPosition = spool.transform.rotation * particlePosOffsets[i];
                Debug.DrawLine(newPosition, newPosition + Vector3.up, Color.red);

                // World -> local
                newPosition = newPosition - rope.solver.transform.position;
                rope.solver.positions[solverIndices[i]] = new Vector4(newPosition.x, newPosition.y, newPosition.z, 1.0f);
            }
        }

        // Lock vertices if they have been alive for X time. More heuristics added when locking works as intended.
        if (particleUnlockedTime[currentParticleIndex] > unlockedTimeMin && currentParticleIndex < solverIndices.Count-1)
        {
            LockParticleAtIndex(currentParticleIndex);
            currentParticleIndex++;
        }
    }

    void LockParticleAtIndex(int particleIndex)
    {
        // Store particle position in world space, accounting for current spool rotation
        particlePosOffsets[particleIndex] = Quaternion.Inverse(spool.transform.rotation) * ((Vector3)rope.solver.positions[solverIndices[particleIndex]] + rope.solver.transform.position);

        particleLockedStates[particleIndex] = true;
        rope.solver.invMasses[solverIndices[particleIndex]] = 0;
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.KeypadPlus))
        {
            spool.Speed += 1;
        }
        if (Input.GetKeyDown(KeyCode.KeypadMinus))
        {
            spool.Speed = Mathf.Max(0, spool.Speed - 1);
        }
    }
}

Particles are locked in LockParticleAtIndex() and their position is updated in FixedUpdate()
Vertices are currently locked based on time, but that is fine until (if) i get the "locking" to work properly.

I stumbled upon an old thread with a similar problem and i think this is closer to what i need. However, i would need the cable to loop around 1-2 laps before "converting" into a static mesh, as the speed of the spool might affect more of the cable than in the example.
I already experimented with having two cursors, on at the start extending rope, and one at the end retracting. This might work, but it is very senstive and easily ends up with the rope going though the center walls if the rope length is just a tiny bit too short.

Do you think this is feasible with either the "locking"- or static mesh-replacement method?
Reply
#6
(18-12-2023, 04:15 PM)Christoffer Wrote:
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Obi;

public class SC_CableLengthController : MonoBehaviour
{
public float speed = 0.5f;
public SC_Spool spool;

private ObiRopeCursor cursor;
private ObiRope rope;

private List<Vector3> particlePosOffsets;
private List<bool> particleLockedStates;
private List<float> particleUnlockedTime;
private List<int> solverIndices;

private float unlockedTimeMin = 6.0f;
private int currentParticleIndex = 0;

void Start()
{
cursor = GetComponent<ObiRopeCursor>();
rope = GetComponent<ObiRope>();

particlePosOffsets = new List<Vector3>();
particleLockedStates = new List<bool>();
particleUnlockedTime = new List<float>();
solverIndices = new List<int>();

// Offset to skip start/end of rope
currentParticleIndex = 1;
for (int i = 4; i < rope.elements.Count-1; i++)
{
particlePosOffsets.Add(Vector3.zero);
particleLockedStates.Add(false);
particleUnlockedTime.Add((i * - 1.0f) - unlockedTimeMin);
solverIndices.Add(rope.solverIndices[i]);
}

solverIndices.Reverse();
}

void FixedUpdate()
{
// Increase rope length
cursor.ChangeLength(rope.restLength + (Time.fixedDeltaTime * speed));

Vector3 newPosition = new Vector3();
for (int i = 0; i < solverIndices.Count; i++)
{
particleUnlockedTime[i] += Time.fixedDeltaTime;

// Update locked particle positions
if (particleLockedStates[i] == true)
{
// World position -> Rotate around spool pivot (0,0,0) for simplicity
newPosition = spool.transform.rotation * particlePosOffsets[i];
Debug.DrawLine(newPosition, newPosition + Vector3.up, Color.red);

// World -> local
newPosition = newPosition - rope.solver.transform.position;
rope.solver.positions[solverIndices[i]] = new Vector4(newPosition.x, newPosition.y, newPosition.z, 1.0f);
}
}

// Lock vertices if they have been alive for X time. More heuristics added when locking works as intended.
if (particleUnlockedTime[currentParticleIndex] > unlockedTimeMin && currentParticleIndex < solverIndices.Count-1)
{
LockParticleAtIndex(currentParticleIndex);
currentParticleIndex++;
}
}

void LockParticleAtIndex(int particleIndex)
{
// Store particle position in world space, accounting for current spool rotation
particlePosOffsets[particleIndex] = Quaternion.Inverse(spool.transform.rotation) * ((Vector3)rope.solver.positions[solverIndices[particleIndex]] + rope.solver.transform.position);

particleLockedStates[particleIndex] = true;
rope.solver.invMasses[solverIndices[particleIndex]] = 0;
}

void Update()
{
if (Input.GetKeyDown(KeyCode.KeypadPlus))
{
spool.Speed += 1;
}
if (Input.GetKeyDown(KeyCode.KeypadMinus))
{
spool.Speed = Mathf.Max(0, spool.Speed - 1);
}
}
}

Particles are locked in LockParticleAtIndex() and their position is updated in FixedUpdate()
Vertices are currently locked based on time, but that is fine until (if) i get the "locking" to work properly.

I stumbled upon an old thread with a similar problem and i think this is closer to what i need. However, i would need the cable to loop around 1-2 laps before "converting" into a static mesh, as the speed of the spool might affect more of the cable than in the example.
I already experimented with having two cursors, on at the start extending rope, and one at the end retracting. This might work, but it is very senstive and easily ends up with the rope going though the center walls if the rope length is just a tiny bit too short.

Do you think this is feasible with either the "locking"- or static mesh-replacement method?

Hi,

There's two subtle bugs in your code:

- The 4th component in all dimensional data in Obi is expected to be zero, as it's only there for memory alignment reasons. You're passing 1.0 as the fourth component of positions instead, which will lead to incorrect constraint solves (as they all expect it to be zero).

- You're using FixedUpdate(), and Obi also uses FixedUpdate() by default in order to run before rigidbody physics. Since Unity's event call order is undefined for different components, your script might randomly run before or after particle simulation. You should use ObiSolver.OnEndStep instead, to ensure your code is called consistently after the simulation has run each frame.

Making these modifications to your code couldn't perceive any "jumps" in particle movement.

kind regards,
Reply