How do I apply a force to a body in the direction it is traveling (Box2D)?

rphello101 picture rphello101 · Jun 29, 2012 · Viewed 9.4k times · Source

I'm using AndEngine/Box2d to develop a game. I have a ball that bounces around the screen. I've successfully made it ignore gravity by applying an opposite force, but it has a tenancy to slow down after the initial impulse, even with the elasticity set to 1. Essentially I want to:

if(speed < a number) apply force or impulse (which is better?) in direction of motion

How might I do this?

Answer

rphello101 picture rphello101 · Jun 29, 2012

Well unfortunately, the ball is interacting with other objects so setting velocity did not work, but I have found a solution!

Using forces and fairly extensive trig, I finally came up with:

private static class Ball extends Sprite  {
    Body body;

    public Ball(final float pX, final float pY, final ITextureRegion pTextureRegion, final VertexBufferObjectManager pVertexBufferObjectManager) {
        super(pX, pY, pTextureRegion, pVertexBufferObjectManager);
        body = PhysicsFactory.createCircleBody(mPhysicsWorld, this, BodyType.DynamicBody, PhysicsFactory.createFixtureDef(0, 1, 0));
        body.applyLinearImpulse(((float)5),(float) 5, body.getWorldCenter().x,  body.getWorldCenter().y);
        mPhysicsWorld.registerPhysicsConnector(new PhysicsConnector(this, body, true, true));
        scene.attachChild(this);
    }

    @Override
    protected void onManagedUpdate(final float pSecondsElapsed) {       
        body.applyForce(new Vector2(0,-SensorManager.GRAVITY_EARTH), new Vector2(body.getWorldCenter()));

        float vx = body.getLinearVelocity().x, vy = body.getLinearVelocity().y, vr=(float) Math.sqrt(vx*vx+vy*vy);
        float t= (float) Math.atan(Math.abs(vy/vx));
        if(vr<destroyerVelocity){
            if(vx>0&&vy<0)
                body.applyForce((float) ((destroyerVelocity-vr)*Math.cos(t)*body.getMass()), (float) (-(destroyerVelocity-vr)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
            else if(vx>0&&vy>0)
                body.applyForce((float) ((destroyerVelocity-vr)*Math.cos(t)*body.getMass()), (float) ((destroyerVelocity-vr)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
            else if(vx<0&&vy>0)
                body.applyForce((float) (-(destroyerVelocity-vr)*Math.cos(t)*body.getMass()), (float) ((destroyerVelocity-vr)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
            else if(vx<0&&vy<0)
                body.applyForce((float) (-(destroyerVelocity-vr)*Math.cos(t)*body.getMass()), (float) (-(destroyerVelocity-vr)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
        }
        if(vr>destroyerVelocity){
            if(vx>0&&vy<0)
                body.applyForce((float) (-(vr-destroyerVelocity)*Math.cos(t)*body.getMass()), (float) ((vr-destroyerVelocity)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
            else if(vx>0&&vy>0)
                body.applyForce((float) (-(vr-destroyerVelocity)*Math.cos(t)*body.getMass()), (float) (-(vr-destroyerVelocity)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
            else if(vx<0&&vy>0)
                body.applyForce((float) ((vr-destroyerVelocity)*Math.cos(t)*body.getMass()), (float) (-(vr-destroyerVelocity)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
            else if(vx<0&&vy<0)
                body.applyForce((float) ((vr-destroyerVelocity)*Math.cos(t)*body.getMass()), (float) ((vr-destroyerVelocity)*Math.sin(t)*body.getMass()), body.getWorldCenter().x,  body.getWorldCenter().y);
        }
        super.onManagedUpdate(pSecondsElapsed);
    }
}

Essentially what this does is create a body and apply an impulse to it to get it moving (worked a lot better than a force for some reason). On every update, a force is applied opposite that of gravity in order to keep the ball aloft (floating, essentially). The elasticity is set to 1, so collisions are almost perfectly elastic. And now the tricky part: I calculated the x and y velocities (vx and vy respectively) and used these to calculate the resultant velocity (vr) and the angle that they were traveling in (t).

If vr is less than the velocity I want (destroyerVelocity), then a force is applied to bump it back up to the destroyer velocity. Since F=mv/t and I just used t=1, the force applied in one direction is equal to the wanted velocity - the actual velocity * the x/y (that's the cos/sin) * the mass of the object. If the object is traveling in the positive x direction and negative y direction, then a force of (x,-y) is applied, and so on.

In order to keep the velocity as close to the destroyerVelocity as possible, I had to apply a force in the opposite direction if it happened to be greater than the destroyerVelocity. This was done in the same manner, just with an opposite force.

Run a log statement of the resultant velocity (with the destroyerVelocity being 7) and it reads something like: 6.900001, 6.9995001, 7.00028, 7.13005,...

Although the ball will spike to like 15 or 20 sometimes, it usually only takes 2 or 3 loops to get it within +- .5 of 7, which is good enough for me! Hope this helps anyone else looking for the same thing.