Reputation: 16577
I've been trying from hours to setup gravity and relate it to time or what we call frame independent bounce ball. I did everything correct I guess, and I tried to implement the system where height of ball would decrease after every bounce. I did not even start that, and my code is creating something absurd I don't understand why. Here's my code:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentFrame = System.currentTimeMillis();
dt = currentFrame - lastFrame;
dt = dt/1000;
lastFrame = currentFrame;
myFreakinRect.set(0,0, canvas.getWidth(), canvas.getHeight());
freakinRed.setColor(Color.RED);
freakinRed.setStyle(Paint.Style.FILL);
canvas.drawRect(myFreakinRect, freakinRed);
//
// o yuea
if(goingDown) {
//velocityY = Math.sqrt(100 + 2*gravity*(posY));
velocityY = gravity*(currentFrame - runTime);
} else {
velocityY = downV - gravity*(currentFrame - runTime);
}
if(posX > w - ballRadius*2) {
goingRight = false;
}
if(posX < 0) {
goingRight = true;
}
if(posY > h - ballRadius*2) {
//initY = initY - 0.25F;
//if(initY < 0) initY = 0;
Log.i("xxx", String.valueOf(initY));
runTime = currentFrame;
downV = velocityY;
goingDown = false;
}
if(velocityY <= 0) {
goingDown = true;
runTime = currentFrame;
}
if(goingDown) posY += velocityY*dt;
else posY -= velocityY*dt;
if(goingRight) posX += velocityX*dt;
else posX -= velocityX*dt;
canvas.drawText(String.valueOf(posX)+" "+String.valueOf(posY), 10, 10, new Paint());
canvas.drawBitmap(rBall, (float)posX, (float)posY, myFreakingFaintPaint);
invalidate();
}
Here's a GIF what is happening:
UPDATE:
Here's my updated code which is clean, understandable and works perfect:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
currentFrame = System.currentTimeMillis();
dt = currentFrame - lastFrame;
dt = dt/1000;
lastFrame = currentFrame;
velocityY = downV + gravity*(currentFrame - runTime);
posY += velocityY*dt;
posX += velocityX*dt;
if(posX > w - ballRadius*2 || posX < 0) {
velocityX = -velocityX;
}
if(posY >= h - ballRadius*2) {
posY = h - ballRadius*2 - 2;
runTime = currentFrame;
downV = -0.8*velocityY;
}
canvas.drawBitmap(rBall, (float)posX, (float)posY, null);
invalidate();
}
Upvotes: 1
Views: 125
Reputation:
In general:
Split up into model and view. In that case the rendering still runs fine, because the calculations are pretty light-weight, but you shouldn't run code inside the rendering-routine that isn't directly related to painting something.
Next point:
Stay as close to reality as possible, if you simulate physics. You can always optimize afterwards, but first make sure your code is actually doing what it's supposed to do. I'm currently playing a bit around with projectile-motion, so I've got a basic idea of what the code is supposed to do. I've been attempting to understand yout code for 10 mins so far. Interim result: I'm confused and don't quite get it.
My suggestion:
Start off with clearer code and try to stick as close to physical rules as possible. This code isn't optimized as far as it could be, but it's readable, understandable and simulates close enough to the real life. That makes it a lot simpler to debug:
final double GRAVITY = -9.81;
final double BALL_ELASTICITY = 0.95;
double vx, vy;
double x, y;
//dt is delta-time in seconds!!!
void simulateBall(double dt){
//calculate when the ball will touch the floor the next time
double next_floor_touch = (-vy + Math.sqrt(vy * vy - 2 * GRAVITY * y)) / GRAVITY;
double after_floor_touch = dt - next_floor_touch;
boolean touches_floor = (next_floor_touch <= dt);
//calculate new y
if(touches_floor){
//predict the speed the ball will have, after it bounces from the floor
double vy_at_floor = vy + GRAVITY * next_floor_touch;
double vy_from_floor = vy_at_floor * (-1) * BALL_ELASTICITY;
//predict y starting from the floor at t = next_floor_touch until dt
y = 0 + vy_from_floor * after_floor_touch + 0.5 * GRAVITY * after_floor_touch * after_floor_touch;
}else{
//uniform acceleration
y = y + vy * dt + 0.5 * GRAVITY * dt * dt;
}
//calculate vy
if(touches_floor){
//see above
double vy_after_floor = (vy + GRAVITY * next_floor_touch) * (-1) * BALL_ELASTICITY;
vy = vy_after_floor + GRAVITY * after_floor_touch;
}else{
vy = vy + GRAVITY * dt;
}
... //that's already the hardest part
}
This uses the quadratic equation to predict when the ball will hit the floor and uniform acceleration to calculate the position from a given position, speed and acceleration. Unless I've made any mistakes in my calculation (this code is not tested), this should be physically precise. BALL_ELASTICITY
represents how much of the speed is left, after the ball hits the floor. That's not physically precise - might be, IDK - , but should do for this purpose.
Upvotes: 1
Reputation: 180286
Here ...
if(goingDown) { //velocityY = Math.sqrt(100 + 2*gravity*(posY)); velocityY = gravity*(currentFrame - runTime); } else { velocityY = downV - gravity*(currentFrame - runTime); }
... you update the velocity (speed, actually) assuming that the ball will not bounce during this frame.
Then here ...
if(posY > h - ballRadius*2) { //initY = initY - 0.25F; //if(initY < 0) initY = 0; Log.i("xxx", String.valueOf(initY)); runTime = currentFrame; downV = velocityY; goingDown = false; }
... you have not yet updated posY
, so you are determining whether the ball hit the floor as a result of the previous update. If it did, you reverse the direction of motion, but keep the speed you already computed for this frame. As a result, each time the ball bounces, its initial upward speed is one frame's worth of acceleration greater than the speed it was traveling when it hit the floor.
You have a similar effect at the top of the ball's motion, but it's smaller because the speed is small there.
There are a couple of ways you might solve this problem. The simplest is probably to perform the bounce check after the position update instead of before.
Additional notes:
use the signs of your X and Y speeds instead of separate direction-of-motion flags (thus making the names velocityY
etc. accurate). Your code will be simpler, and you'll need to handle only one change of vertical direction, not two, because the equations of motion will handle the other automatically.
you have a bit of a precision problem because you assume that the ball travels in the same direction for a whole frame. This may become noticeable if you allow the ball to reach high speeds: it will appear to penetrate the floor before bouncing back up.
this computation is suspicious: dt = dt/1000
. Since dt
seems to be computed from System.currentTimeMillis()
, I am inclined to guess that it, too, has type long
. In that case, you are performing an integer division and thereby losing precision.
Upvotes: 2