5

So, I have a player body + fixture etc, it is essentially a ball that bounces around.

I want to detect when it is 'pretty much' finished moving.

At the moment I do this:

public Boolean isStopped() {
    return body.getLinearVelocity().x <= 0.3f && body.getLinearVelocity().y <= 0.3f;
}

This mostly works, the problem being when the player hits something, there's a split second where its velocity is 0, so this returns true. What I really wanted is to just return true when it is basically finished. Preferably within a range that I can set to whatever I like as I tweak the physics of my game world.

I can't use a check on whether it is sleeping or not as that comes too late, it doesn't sleep until after it has stopped having forces act upon it, I need just before.

I could just store how long it has been stopped/a count of stopped steps, but I was hoping there would be a nice pre existing method that I missed.

Any ideas?

Tom Manterfield
  • 6,515
  • 6
  • 36
  • 52

3 Answers3

4

You can keep track of recent movement and update it by mixing in a little of the current speed each time step:

float speedNow = body.getLinearVelocity().len();
recentSpeed = 0.1 * speedNow + 0.9 * recentSpeed;
if ( recentSpeed < someThreshold )
    ... do something ...

You would need to set recentSpeed to a suitably high value to begin with, otherwise it might be below the threshold in the first time step.

noone
  • 19,520
  • 5
  • 61
  • 76
iforce2d
  • 8,194
  • 3
  • 29
  • 40
  • Yeah, I figured something along these lines before. I was really looking for something baked in to the body class already though, that I had missed or misunderstood in the docs. If I were to go this kind of route I would (and currently have) just added the time delta as an argument to detect how long it has been stopped for. That works, but it just feels like there should be something better. – Tom Manterfield Feb 25 '14 at 15:37
  • Hey, whilst I used the delta approach in the end, I've accepted your answer as it at least showed me a different option. From looking at your code, am I right in thinking I could change my isMoving check to _Math.abs(body.getLinearVelocity().len()) >= 0.25f_ ? Is the Math.abs even necessary with .len()? Thanks very much for your answer by the way! – Tom Manterfield Feb 27 '14 at 12:58
  • OH, I just realised where I know iforce2d from! I use your RUBE editor for my level creation. That plus rubeloader has made my life much much easier. Well worth the money. – Tom Manterfield Feb 27 '14 at 13:00
  • Thanks Tom! The len() function just does sqrt(x*x+y*y) so it will always be positive. – iforce2d Feb 27 '14 at 15:15
1

Seeing how you've determined that your false positives are caused by the body making contact with another, why not add a couple of lines in your ContactListener's beginContact method, storing the body's current speed in its user data? Then you can check that speed in your isStopped method. If there is a stored speed and the current speed isn't greater, this means the body is in the process of bouncing off whatever it hit: ignore. If there is a stored speed and the current speed is greater, the ball has bounced and is proceeding in some new direction: clear the stored speed. If there is no stored speed and the current speed is below your threshold, you've detected the sought situation.

In your ContactListener:

public void beginContact(Contact contact) {
    Body a = contact.getFixtureA().getBody();
    Body b = contact.getFixtureB().getBody();

    if (a == mBall) {
        a.setUserData(a.getLinearVelocity().len());
    } else if (b == mBall) {
        b.setUserData(b.getLinearVelocity().len());
    }
}

And in your isStopped check:

public Boolean isStopped() {
    float storedSpd = (Float) body.getUserData();
    float currentSpd = body.getLinearVelocity().len();

    if ((storedSpd > Float.MIN_VALUE) && (currentSpd > storedSpd)) {
        body.setUserData(Float.MIN_VALUE);
        return false;
    } else {
        return (currentSpd < THRESHOLD);
    }
}

This is untested, but you get the idea. Also, remember to initially set the user data to Float.MIN_VALUE.

Emil Fors
  • 110
  • 6
0

In the end I have simply passed the delta from each render call to the isStopped() method.

public Boolean isStopped(float delta) {
    boolean isMoving = (
            Math.abs(body.getLinearVelocity().x) >= 0.25f || Math.abs(body.getLinearVelocity().y) >= 0.25f);
    if(isMoving) {
        timeStopped = 0f;
        return false;
    } else {
        timeStopped += delta;
        return timeStopped >= 0.3f;
    }
}

timeStopped is just a class property that starts off as zero. This does return true for the beginning of the game (before the user has made a move) but in my application that is absolutely fine. Besides which, it is true to say it has stopped in that circumstance.

I'd still love to see a way to do this without storing extra crap, since I'm guessing box2d must have this information somewhere in order to figure out if a body with zero velocity has no force acting upon or if it is just changing direction after an impact.

Tom Manterfield
  • 6,515
  • 6
  • 36
  • 52