0
\$\begingroup\$

enter image description here

From the image you can see that the ball fired on the left that fire behind it, does not match the calculated trajectory. Im drawing the ball trajectory using an equation from a SO question, this is modified to take into consideration the box2d steps of 30 frames per second. This does calculate a valid trajectory but it does not match the actual trajectory of the ball, the ball has a smaller trajectory. I am applying a box2d force to the ball, this also has a density set and a shape. The shape radius varies depending on the type of ball. Im setting the start velocity in the touchdown event.

public class ProjectileEquation {  

    public float gravity;  
    public Vector2 startVelocity = new Vector2();  
    public Vector2 startPoint = new Vector2();  
    public Vector2 gravityVec = new Vector2(0,-10f);

    public float getX(float n) {  
        return startVelocity.x * (n * 1/30f) + startPoint.x;  
    }  

    public float getY(float n) {
        float t = 1/30f * n;
        return 0.5f * gravity * t * t + startVelocity.y * t + startPoint.y;  
    }  



} 

@Override  
    public void draw(SpriteBatch batch, float parentAlpha) {  
        float t = 0f;  
        float width = this.getWidth();  
        float height = this.getHeight();  

        float timeSeparation = this.timeSeparation;  

        for (int i = 0; i < trajectoryPointCount; i+=timeSeparation) {  
            //projectileEquation.getTrajectoryPoint(this.getX(), this.getY(), i);
            float x = this.getX() + projectileEquation.getX(i);  
            float y = this.getY() + projectileEquation.getY(i);  

            batch.setColor(this.getColor());  
            if(trajectorySprite != null) batch.draw(trajectorySprite, x, y, width, height);  

           // t += timeSeparation;  
        }  
    } 



public boolean touchDown (InputEvent event, float x, float y, int pointer, int button) {
                if(button==1 || world.showingDialog)return false;
                touchPos.set(x, y);
                float angle = touchPos.sub(playerCannon.position).angle();
                if(angle > 270 ) {
                    angle = 0;
                }
                else if(angle >70) {
                    angle = 70;
                }
                playerCannon.setAngle(angle);
                world.trajPath.controller.angle = angle;
                float radians =  (float) angle * MathUtils.degreesToRadians;
                float ballSpeed = touchPos.sub(playerCannon.position).len()*12;
                world.trajPath.projectileEquation.startVelocity.x = (float) (Math.cos(radians) * ballSpeed);
                world.trajPath.projectileEquation.startVelocity.y = (float) (Math.sin(radians) * ballSpeed);
                return true;
            }



public CannonBall(float x, float y, float width, float height, float damage, World world,  Cannon cannonOwner) {
        super(x, y, width, height, damage, world);
        active = false;
        shape = new CircleShape();
        shape.setRadius(width/2);

        FixtureDef fd = new FixtureDef();
        fd.shape = shape;
        fd.density = 4.5f;
        if(cannonOwner.isEnemy) { //Enemy cannon balls cannot hit other enemy cannons just the player
            fd.filter.groupIndex = -16;
        }
        bodyDef.type = BodyType.DynamicBody;
        bodyDef.position.set(this.position);

        body = world.createBody(bodyDef);
        body.createFixture(fd);
        body.setUserData(this);
        body.setBullet(true);
        this.cannonOwner = cannonOwner; 
        this.hitByBall = null;
        this.particleEffect = null;
    }

  private CannonBall createCannonBall(float radians, float ballSpeed, float radius, float damage)
    {
        CannonBall cannonBall =  new CannonBall(CannonEnd().x, CannonEnd().y, radius * ballSizeMultiplier, radius * ballSizeMultiplier, damage, this.world, this);
        cannonBall.velocity.x = (float) (Math.cos(radians) * ballSpeed);
        //cannonBall.velocity.x = (float) ((Math.sqrt(10) * Math.sqrt(29) *
            //  Math.sqrt((Math.tan(cannon.angle)*Math.tan(cannon.angle))+1)) / Math.sqrt(2 * Math.tan(cannon.angle) - (2 * 10 * 2)/29))* -1f;
        cannonBall.velocity.y = (float) (Math.sin(radians) * ballSpeed);
        cannonBall.active = true;
        //cannonBall.body.applyLinearImpulse(cannonBall.velocity, cannonBall.position);
        cannonBall.body.applyForce(cannonBall.velocity, cannonBall.position );
        return cannonBall;
    }


trajPath = new TrajectoryActor(-10f);
        trajPath.setX(playerCannon.CannonEnd().x);
        trajPath.setY(playerCannon.CannonEnd().y);
        trajPath.setWidth(10f);
        trajPath.setHeight(10f);
        stage.addActor(trajPath);
\$\endgroup\$
9
  • 1
    \$\begingroup\$ It's probably better to use Box2D itself to form the trajectory, rather than attempt to replicate its calculations. \$\endgroup\$ Sep 12, 2013 at 4:16
  • \$\begingroup\$ wouldn't that be slow, how would that be done? \$\endgroup\$
    – tsukimi
    Sep 12, 2013 at 4:19
  • \$\begingroup\$ I don't want to know how the ball interacts with other objects just draw a trajectory.Stepping the world i would have to store the state of all the world bodies , there are alot of them, particles etc.Then restore them after stepping the world ahead.Ive read some articles on it and they normally suggest doing the trajectory calculation when you dont need to take other world objects into account. \$\endgroup\$
    – tsukimi
    Sep 12, 2013 at 6:49
  • \$\begingroup\$ @tsukimi: create a new box2d instance world, use it just for trajectory calculations of the ball, which you duplicate in the new instance. One object, no constraints, no interactions. Reset to origin, apply force, sample trajectory. \$\endgroup\$ Sep 12, 2013 at 7:04
  • \$\begingroup\$ Yep, if you don't want interactions, don't put any of the other objects in the duplicate instance. The problem you're running into is Box2D is not simulating the motion exactly as you are. So the best way to simulate Box2D movement is with Box2D. \$\endgroup\$
    – House
    Sep 12, 2013 at 16:16

1 Answer 1

3
\$\begingroup\$

The problem is that your trajectory is built from an analytical form, while your physics engine is using an erroneous numerical approximation. Here are 3 solutions:

  1. Make a kinematic projectile: I don't know Box2D, but I'd guess there's a way to make a rigid body "kinematic," which would allow you to specify its position/velocity as a function of time. You would then just use the same logic you're using to draw the trajectory to place the projectile.
  2. Draw the trajectory using the same numerical integrator Box2D does: A simpler solution is the inverse of that, where you'd recreate the integration scheme Box2D is using to numerically solve for the drawn trajectory. Assuming Box2D uses symplectic Euler integration (I think all the physics engines do), it'd look something like this, in pseudo-code:

    while(ball.y > 0)
        ball.velocity += g * dt
        ball.position += ball.velocity * dt
        // draw ball
  3. Find a closed form solution: The tidiest answer is to find a closed form solution to the Euler integration. I just ran through all the math and come up with:

    def get_pos_at_nth_step(n)
        return x0 + n * ((0.5 * g * dt^2) * (n + 1) + v0 * dt)

    where x0 and v0 are the initial position and velocity, respectively. This allows you to keep your code as it is, but end up with a trajectory equal to what your physics engine will find. Note that the solution won't work if Box2D doesn't use symplectic Euler, but you can take the same approach.

\$\endgroup\$
5
  • \$\begingroup\$ It certainly suggests it if the selected answer is different than the linked duplicate. My answer does not apply to the supposed duplicate. \$\endgroup\$ Sep 18, 2013 at 21:42
  • \$\begingroup\$ Perhaps. I asked the OP to update the question about it. They have not done so. \$\endgroup\$
    – House
    Sep 18, 2013 at 21:44
  • \$\begingroup\$ Yeah, I found OP over on SO with the same question; is definitely not a "tie up loose ends" user, so was just trying to get the duplicate tag removed since it's a decidedly different question. \$\endgroup\$ Sep 18, 2013 at 22:52
  • 1
    \$\begingroup\$ Fair enough. Though, this is the duplicate question to the one posted on SO (since this was posted after), but you did answer before the other person. This is exactly why cross posting sucks. \$\endgroup\$
    – House
    Sep 18, 2013 at 22:57
  • \$\begingroup\$ I went with the other question first because it uses box2d and so was more relevant to my situation. \$\endgroup\$
    – tsukimi
    Sep 19, 2013 at 1:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .