Straight Joints With Particles

Sage
Posts: 1,066
Joined: 2004.07
Post: #1
I've begun working out how to attach two particles together by a set distance, but I'm having trouble. I worked out this method by hand and it works in theory, but in practice the results aren't the best. One of my particles is zero mass so it's stationary. The other should just swing around the bottom of it. Instead, it swings to the bottom and stops. It's odd. I'm thinking I need to be applying forces to keep them together instead of setting the position, but I'm not sure how to calculate that. Here's the code I have right now. Any suggestions?

Code:
updateParticles( time );
    
    //calculate distance and move particles to proper distance apart
    float actualDistance = Vector2D::DistanceBetweenVectors( start->getPosition(), end->getPosition() );
    float change = actualDistance - length;
    
    if( change != 0.0f ) {
        float startMass = start->getMass();
        float endMass = end->getMass();
        float totalMass = startMass + endMass;
        
        float startChange = startMass / totalMass * change;
        float endChange = endMass / totalMass * change;
        
        Vector2D changeVector = ( start->getPosition() - end->getPosition() ).normalize();
        
        Vector2D newStartPosition = start->getPosition();
        newStartPosition -= ( changeVector * startChange );
        start->setPosition( newStartPosition );
        
        Vector2D newEndPosition = end->getPosition();
        newEndPosition -= ( changeVector * endChange );
        end->setPosition( newEndPosition );
    }
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #2
Yes, your problem is that you need to change the velocity, not the position. You can do that by applying forces or impulses. Impulses are simpler because they are instantaneous and don't rely on the time step. Unless you are going to do something really fancy, I'd go the impulse route instead of penalty forces.

To find the correct impulse, j, you would need to use this equation:
j = -(v2 - v1)/(1/m1 + 1/m2)
and apply this to your particles:
v1 += j•n/m1
v2 += j•n/m2
where • is the dot product, and n is the normalized direction from obj2 to obj1.
(NOTE: I probably have some signs flipped, proceed with caution.)

EDIT: Oh, forgot to mention, this will only solve the velocity constraint. You will still need to satisfy the position constraint like you are already doing or the objects will slowly drift apart due to the discrete time steps.

For constrained particle physics, Jakobsen physics is king. It's super simple, fast, and needs no explicit velocity representation. If you aren't interested in doing rigid body simulation, I'd go this route. (You can use this method for rigid body physics as well, but it doesn't work particularly well.)

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #3
I'm having a problem with this part:

Skorche Wrote:v1 += j•n/m1
v2 += j•n/m2

Wouldn't j•n/m1 give me a single float value and not a vector? How then do I simply add that to my velocity?
Quote this message in a reply
DoG
Moderator
Posts: 869
Joined: 2003.01
Post: #4
What you really want is to change the position, not the velocity. Changing the velocity is only viable for collisions, not constraints. Really, read that Jakobsen article, it's good. And when you're done with that, look at "Constrained Rigid Body Dynamics" by Chris Hecker.
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #5
DoG, I'm pretty sure that that's not true. You need to do both. If you don't change the velocity, the object they will get pushed back together, but their velocities con continue to be divergent. This was the problem Nick was having, despite being attached to a solid particle, the second one was continuing to accelerate downward. You need to make their relative velocity in the direction of the constraint zero.

Nick, sorry about that. Those equations should become:
j = (v2 - v1)/(1/m1 + 1/m2) #drop the negation, that was for collisions
v1 += (j•n/m1)*n #vector projection of j/m1 onto n (not the scalar projection, sorry)
v2 -= (j•n/m2)*n #same as v1, but should have been negated.

Sorry about that. I was trying giving a quick reply on my way out.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #6
Thanks for the response. I have the velocity change implemented, but it still is just falling and sitting at the bottom instead of doing a pendulum effect. Here's the code I'm using so far:

Creating Particles and Joint:
Code:
    p1 = new Particle();
    p1->p = Vector2D( 375, 500 );
    p1->m = 15.0f;
    
    p2 = new Particle();
    p2->p = Vector2D( 425, 500 );
    p2->m = 0.0f;
    
    j = new CharacterBone( p1, p2 );
    j->length = 50;

Adding gravity to both:
Code:
p1->f += Vector2D( 0, -98 );
    p2->f += Vector2D( 0, -98 );

Updating Particles:
Code:
updateParticles( time );
    
    //calculate distance and move particles to proper distance apart
    float actualDistance = Vector2D::DistanceBetweenVectors( p2->p, p1->p );
    float change = actualDistance - length;
    
    if( change != 0.0f ) {
        float p1Change = p1->m / ( p1->m + p2->m ) * change;
        float p2Change = p2->m / ( p1->m + p2->m ) * change;
        
        Vector2D n = ( p2->p - p1->p ).normalize();
        Vector2D j = ( p2->v - p1->v ) / ( 1 / p1->m + 1 / p2->m );
        
        if( p1->m > 0 )
            p1->v += n * ( Vector2D::DotProduct( j, n ) / p1->m );
        
        if( p2->m > 0 )
            p2->v -= n * ( Vector2D::DotProduct( j, n ) / p2->m );
        
        p1->p += n * p1Change;
        p2->p += n * p2Change;
    }

The updateParticles() function just calls the following for both particles:
Code:
void Particle::integrateForceAndTorque( float timeStep ) {
    if( m > 0.0f ) {
        Vector2D linearAcceleration = f / m;
        p += v * timeStep + linearAcceleration * 0.5f * timeStep * timeStep;
        v += linearAcceleration * timeStep;
        
        float angularAcceleration = t / m;
        a += rv * timeStep + angularAcceleration * 0.5f * timeStep * timeStep;
        rv += angularAcceleration * timeStep;
        
        if( a < 0 )
            a += 2 * M_PI;
        else if( a > 2 * M_PI )
            a -= 2 * M_PI;
    }
    
    f.set( 0.0f, 0.0f );
    t = 0.0f;
}
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #7
I figured out my problem. I need to conditionally created j in case of one particle having zero mass. Here's how I'm doing it now (and it works great). Thanks Skorche.

Code:
        Vector2D j;
        if( p1->m > 0 && p2->m > 0 )
            j = ( p2->v - p1->v ) / ( 1 / p1->m + 1 / p2->m );
        else if( p1->m > 0 && p2->m <= 0 )
            j = ( p2->v - p1->v ) / ( 1 / p1->m );
        else if( p1->m <= 0 && p2->m > 0 )
            j = ( p2->v - p1->v ) / ( 1 / p2->m );
        else
            return;
        
        if( p1->m > 0 )
            p1->v += n * ( Vector2D::DotProduct( j, n ) / p1->m );
        if( p2->m > 0 )
            p2->v -= n * ( Vector2D::DotProduct( j, n ) / p2->m );
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #8
I'm back with another (hopefully easier) question. I'm trying to solve for my constraint using two different sized masses. I want it to behave realistically so that larger masses pull smaller masses in rather than the other way around. This (I assumed) was more related to the positioning of the masses rather than adjusting the velocities. Here is what I have right now:
Code:
    Vector2D n = p2->p - p1->p;
    float change = n.getMagnitude() - length;
    
    if( change != 0.0f ) {
        float p1Change = 0.0f, p2Change = 0.0f;
        
        if( p1->m > 0 && p2->m > 0 ) {
            p1Change = p2->m / ( p1->m + p2->m ) * change;
            p2Change = p1->m / ( p1->m + p2->m ) * change;
        }
        else if( p1->m <= 0 && p2->m > 0 )
            p2Change = change;
        else if( p1->m > 0 && p2->m <= 0 )
            p1Change = change;
        
        n = n.normalize();
        
        p1->p += n * p1Change;
        p2->p -= n * p2Change;

The important part is in the first if statement where I use the opposite particle's mass to determine the percentage of the difference that particle has to move. This made logical sense, but I'm not sure if it's correct. As of now, my simulation doesn't seem to take mass into account very well (even very large masses get swung around like they were nothing and if I set all particles to a smaller mass on my chain, they move faster).

Any ideas? Besides this change and my last post, all the code is the same as my post two posts ago.

EDIT: To make things easier for people to help, I've uploaded the whole project for download. Xcode 2 required. I've included the two SDL frameworks I'm using as well. Download Here (2.4 MB ). The files of interest are easily identifiable, with Chain being the top level class. Chain is comprised of StickConstraint which is a subclass of Joint. Joint contains two Particles. You can look at main.cpp to see how I'm using it if it helps.

EDIT 2: Almost forgot, push P to begin the simulation.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #9
So my problem turned out to be that I was using a constant gravity force rather than constant gravity acceleration. I fixed that and now have yet another question. I've created a way that I can stick the static end of my rope to my mouse and drag it around, but it doesn't let me throw the rope around it like it should. I've tried setting the velocity manually, but I'm not sure I'm doing it right. Here's the code I'm using:

Code:
for(int i = 0; i < 180; i++ ) {
        if( holding ) {
            int x, y;
            SDL_GetMouseState( &x, &y );

                    //SDL puts (0,0) in top left, I want (0, 0) in bottom left
            y = app->getScreenHeight() - y;

            ball1->v.set( ( ( float )x - ball1->p.x ) * step, ( ( float )y - ball1->p.y ) * step);
            ball1->p.set( x, y );
        }
        
        for( itr = particles.begin(); itr != particles.end(); ++itr ) {
            if( ( *itr ) ) {
                ( *itr )->f += Vector2D( 0, -9.8 * ( *itr )->m );
                ( *itr )->integrateForceAndTorque( step );
            }
        }
        
        chain->update();
    }
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #10
I think I added the changes correctly.

My first guess is that the problem stems from the fact that the mouse input is really coarse. You only receive a mouse event every few frames, so your end particle is moving really fast for one frame, and then comes to a dead halt for the next few frames.

I'll try and take a closer look later, but try that first.

EDIT:
Yeah, I think that pretty much hit it on the nose. Comment out the line where you set the particle velocity. It will have almost no effect on the simulation. Because the mouse velocity is so sporadic, it will have almost zero effect on the velocities of the particles in the string. The mouse position on the other hand will still have a lot of effect on the positions of the string particles. What you are seeing is the velocity getting out of sync with the positions. That's why if you whip the string around like crazy it will soon settle back to the same swinging motion it had before.

This is one of the reasons why Jakobsen style physics rocks for these kinds of simulations. There is no explicit velocity state, so it can't get out of sync with the positions. Smoothing the mouse velocities will help (probably alot), but you really want to consider implementing Jakobsen physics to do this sort of thing. The downsides of Jakobsen physics are that because there is no velocity state, it makes it difficult to implement friction, restitution, or interactions with objects that do use a velocity state.

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Sage
Posts: 1,066
Joined: 2004.07
Post: #11
It seems to work, but it seems like it should swing around faster. Even if I stick a particle with a mass of 20 on the end of the rope, it doesn't swing around when I move the mouse around.
Quote this message in a reply
Sage
Posts: 1,482
Joined: 2002.09
Post: #12
Well, I had a nice long answer that I was tying up, but it seems that I accidentally quit the browser.

Anyway, the problem is with how you are applying the velocity to the attached particle. You only get a mouse motion event every few frames. This means that you are changing the velocity from 0 to a large value and then back to 0 for a few frames. The velocities of the other particles in the rope never have a chance to converge. Instead they will converge around to 0.

The position on the other hand isn't so jumpy. It moves somewhere and stays there for a while. This allows the positions of the attached particles to converge on their new intended positions over several frames.

The effect of this then is the same problem that you were having before when you were modifying the positions, but not the velocities. The swinging motion of the rope is mostly residual velocity from however the rope was swinging before you moved the mouse. If you whip the string around, it will very quickly settle into the same motion it had before. Although it's following the position of the mouse, it isn't picking up the mouse's velocity.

I've never implemented anything quite in the way you are, but I can think of a few suggestions to try out anyway:
1) Smooth out the mouse deltas over several frames. This will allow the velocities of the particles in the rope a chance to converge. (easy)
2) Use Jakobsen style physics. This would be very easy, fast, and would completely remove the position/velocity synchronization problem, but can make other advanced features more difficult.
3) When constraining the positions, use the the correction for the positions and also apply that to the velocities (scaled by the time step of course). You might even be able to remove the explicit velocity constraint this way. This seems like it would be similar to how the vertlet integration used in Jakobsen physics works, but I'm not really sure. (Maybe that's what DoG meant perhaps?)
4) Use a different time stepping algorithm. (update velocities, constrain velocities, update positions, constrain positions)

Scott Lembcke - Howling Moon Software
Author of Chipmunk Physics - A fast and simple rigid body physics library in C.
Quote this message in a reply
Post Reply