Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mathematical error in physics restitution formula

I admit it. I'm a mathematical imbecile. should I be attempting this at home? Probably not without serious injury. Anyway. Anyone that has a bit of experience in games and physics who can review this code and provide insight into why there is a built in "loss" in velocity in prototype would be hugely admired.

the concept: the actor falls from the sky, at some point intersects a boundary (trampoline.y) after which gravity (in this example a value of 1.6) stops operating and a restitution factor (in this case a value of -6) takes over.

right now I'm trying to create a zero-sum situation where you have the same velocity coming out of the trampoline "bounce" (hero.y >= trampoline.y) as you did going in. That's just not the case as the traces reveal.

I've tried about five different approaches and this is the latest, given the insight that at any given "tic" of my game timer (33 ms) we may have passed the boundary of the trampoline and thus the acceleration rules will have changed. I really thought I had it, but the results show a decrease in velocity after each bounce, which is not what I expected.

I submit the following code to your wizened review:

var newV:Number;
if (hero.y < trampoline.y) { newV = hero.velocityY + gravityAccelerationInPixels }
else { newV = hero.velocityY + TrampolineActor.SPRING_ACCELERATION }

var newY:Number = hero.y + newV;
trace("Coming in: y=" + hero.y + " oldVelocity=" + hero.velocityY + " newVelocity=" + newV + " Change in V: " + (newV - hero.velocityY) + ". Testing for newY=" + newY);

if ((hero.y < trampoline.y && newY < trampoline.y) || (hero.y >= trampoline.y && newY >= trampoline.y)) {
    hero.y = newY;
    hero.velocityY = newV;
} else {
    trace("SPLIT");

    var percent:Number = (trampoline.y - hero.y) / newV; trace("Percent: " + percent);
    var newVV:Number;
    if (hero.y < trampoline.y) {
        // going down!
        newVV = hero.velocityY + percent * gravityAccelerationInPixels; trace("New velocity before split: " + hero.velocityY + " Change in V: " + (newVV - hero.velocityY));
        newVV += (1 - percent) * TrampolineActor.SPRING_ACCELERATION; trace("Percent after split: " + (1 - percent) + " Change in V: " + (newVV - hero.velocityY));
    } else {
        // movin on up!
        newVV = hero.velocityY + percent * TrampolineActor.SPRING_ACCELERATION; trace("New velocity before split: " + hero.velocityY + " Change in V: " + (newVV - hero.velocityY));
        newVV += (1 - percent) * gravityAccelerationInPixels; trace("Percent after split: " + (1 - percent) + " Change in V: " + (newVV - hero.velocityY));
    }
    trace("New velocity: " + newVV + " Change in V: " + (newVV - hero.velocityY));

    hero.velocityY = newVV;

    hero.y += hero.velocityY;
}

Additional INFO: Screen origin is top-left, so y-- = higher up My tic is currently set to 33 ms gravityAccelerationInPixels is currently 1.62 (reasonably arbitrary) SPRING_ACCELERATION is arbitrarily 6, but I hope to tweak that number to control how far below the trampoline the character can go during the deceleration/absorption cycle.

For now I'm hoping to have a solution where the velocity on the tic after the character leaves contact with the trampoline is the same (but negative) of the velocity just prior to going in. Heck I've even tried storing and retrieving the entry position and velocity, but that just looks like a hiccup.

like image 866
Tom Auger Avatar asked Dec 09 '22 06:12

Tom Auger


1 Answers

Here are some debugging techniques for this type of simulation:

  • First: Make sure you know what units every single one of your variables and constants is in. For example, if hero.y is a distance in pixels, then since it is adjusted by hero.y + newV, it must be the case that newV is also in pixels; except that it is supposed to be a speed (distance per time), so it must actually be pixels per frame (where the time step is implicit because you're doing this once per frame).

    For the next debugging steps, it would be better to make sure all your values are expressed in “per second” rather than “per frame”; to do this, introduce a value equal to your timestep in seconds per frame. Then you have hero.y + newV * timestep and similarly newV = hero.velocityY + gravityAccelerationInPixelsPerSecondSquared * timestep.

    It's OK to compute gravityAccelerationInPixelsPerSecondSquared * timestep as a constant in itself, but make sure that all your hardcoded values do not depend on your timestep, so that you can easily:

  • Try decreasing your timestep by some factor k. If doing this reduces your error, then your problem is because your discrete timestep calculations are not matching the perfect integral-over-time answer. If this does not reduce your error, then your simulation is consistent but in some sense “unphysical”.

  • Try computing the total energy of your system. That is, sum

    • the kinetic energy of any moving bodies, 1/2·m·v² (m = mass, v = velocity)
    • the potential energy of any falling bodies, m·g·h (g = gravitational acceleration constant, h = height above an arbitrary reference point)
    • the potential energy of any springs, 1/2·k·d² (k = spring constant, hopefully equal to your "restitution factor" (but positive), d = distance displaced from relaxed state)

    If you let h = hero.y - trampoline.y, then for your particular system you have

    E = 1/2·m·v² + m·g·max(h, 0) + 1/2·k·(min(h, 0))²

    (From my reading of your code, gravity is not applied while the character is on the trampoline; if it is, change max(h, 0) to just h.)

    Make sure you do this in consistent dimensions (you can't add pixels per second to pixels) and units (you can't add pixels per second to pixels per frame or to inches per second). Don't forget that squaring squares the units too.

    For your stated goal — the character bouncing up with the same velocity — you have a frictionless system, and so the system's energy should be identical before, during, and after the bounce. If the energy changes, then note when it changes. For example:

    • If it changes while the character is falling, then your simulation of that case, constant acceleration, is incorrect. A common mistake (which it looks like you've made in your code) is to forget the third term of the formula for position under constant acceleration: newPos = pos + velocity * timestep + 1/2 * acceleration * timestep^2, where the acceleration is gravity. (It is also important that you update the position before you update the velocity, so that you are not using "tomorrow's" velocity with "yesterday's" position in the position calculation.) Try simulating bouncing on a hard surface instead of the trampoline to simplify the situation.

    • If it changes exactly when the character touches or leaves the trampoline, your percent computation is not achieving its goal.

like image 80
Kevin Reid Avatar answered Jan 17 '23 00:01

Kevin Reid