Ball to Ball Collision - Detection and Handling

前端 未结 14 1420
逝去的感伤
逝去的感伤 2020-11-22 05:29

With the help of the Stack Overflow community I\'ve written a pretty basic-but fun physics simulator.

\"alt

相关标签:
14条回答
  • 2020-11-22 05:53

    I would consider using a quadtree if you have a large number of balls. For deciding the direction of bounce, just use simple conservation of energy formulas based on the collision normal. Elasticity, weight, and velocity would make it a bit more realistic.

    0 讨论(0)
  • 2020-11-22 05:55

    One thing I see here to optimize.

    While I do agree that the balls hit when the distance is the sum of their radii one should never actually calculate this distance! Rather, calculate it's square and work with it that way. There's no reason for that expensive square root operation.

    Also, once you have found a collision you have to continue to evaluate collisions until no more remain. The problem is that the first one might cause others that have to be resolved before you get an accurate picture. Consider what happens if the ball hits a ball at the edge? The second ball hits the edge and immediately rebounds into the first ball. If you bang into a pile of balls in the corner you could have quite a few collisions that have to be resolved before you can iterate the next cycle.

    As for the O(n^2), all you can do is minimize the cost of rejecting ones that miss:

    1) A ball that is not moving can't hit anything. If there are a reasonable number of balls lying around on the floor this could save a lot of tests. (Note that you must still check if something hit the stationary ball.)

    2) Something that might be worth doing: Divide the screen into a number of zones but the lines should be fuzzy--balls at the edge of a zone are listed as being in all the relevant (could be 4) zones. I would use a 4x4 grid, store the zones as bits. If an AND of the zones of two balls zones returns zero, end of test.

    3) As I mentioned, don't do the square root.

    0 讨论(0)
  • 2020-11-22 05:59

    To detect whether two balls collide, just check whether the distance between their centers is less than two times the radius. To do a perfectly elastic collision between the balls, you only need to worry about the component of the velocity that is in the direction of the collision. The other component (tangent to the collision) will stay the same for both balls. You can get the collision components by creating a unit vector pointing in the direction from one ball to the other, then taking the dot product with the velocity vectors of the balls. You can then plug these components into a 1D perfectly elastic collision equation.

    Wikipedia has a pretty good summary of the whole process. For balls of any mass, the new velocities can be calculated using the equations (where v1 and v2 are the velocities after the collision, and u1, u2 are from before):

    v_{1} = \frac{u_{1}(m_{1}-m_{2})+2m_{2}u_{2}}{m_{1}+m_{2}}

    v_{2} = \frac{u_{2}(m_{2}-m_{1})+2m_{1}u_{1}}{m_{1}+m_{2}}

    If the balls have the same mass then the velocities are simply switched. Here's some code I wrote which does something similar:

    void Simulation::collide(Storage::Iterator a, Storage::Iterator b)
    {
        // Check whether there actually was a collision
        if (a == b)
            return;
    
        Vector collision = a.position() - b.position();
        double distance = collision.length();
        if (distance == 0.0) {              // hack to avoid div by zero
            collision = Vector(1.0, 0.0);
            distance = 1.0;
        }
        if (distance > 1.0)
            return;
    
        // Get the components of the velocity vectors which are parallel to the collision.
        // The perpendicular component remains the same for both fish
        collision = collision / distance;
        double aci = a.velocity().dot(collision);
        double bci = b.velocity().dot(collision);
    
        // Solve for the new velocities using the 1-dimensional elastic collision equations.
        // Turns out it's really simple when the masses are the same.
        double acf = bci;
        double bcf = aci;
    
        // Replace the collision velocity components with the new ones
        a.velocity() += (acf - aci) * collision;
        b.velocity() += (bcf - bci) * collision;
    }
    

    As for efficiency, Ryan Fox is right, you should consider dividing up the region into sections, then doing collision detection within each section. Keep in mind that balls can collide with other balls on the boundaries of a section, so this may make your code much more complicated. Efficiency probably won't matter until you have several hundred balls though. For bonus points, you can run each section on a different core, or split up the processing of collisions within each section.

    0 讨论(0)
  • 2020-11-22 06:00

    You have two easy ways to do this. Jay has covered the accurate way of checking from the center of the ball.

    The easier way is to use a rectangle bounding box, set the size of your box to be 80% the size of the ball, and you'll simulate collision pretty well.

    Add a method to your ball class:

    public Rectangle getBoundingRect()
    {
       int ballHeight = (int)Ball.Height * 0.80f;
       int ballWidth = (int)Ball.Width * 0.80f;
       int x = Ball.X - ballWidth / 2;
       int y = Ball.Y - ballHeight / 2;
    
       return new Rectangle(x,y,ballHeight,ballWidth);
    }
    

    Then, in your loop:

    // Checks every ball against every other ball. 
    // For best results, split it into quadrants like Ryan suggested. 
    // I didn't do that for simplicity here.
    for (int i = 0; i < balls.count; i++)
    {
        Rectangle r1 = balls[i].getBoundingRect();
    
        for (int k = 0; k < balls.count; k++)
        {
    
            if (balls[i] != balls[k])
            {
                Rectangle r2 = balls[k].getBoundingRect();
    
                if (r1.Intersects(r2))
                {
                     // balls[i] collided with balls[k]
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 06:02

    As a clarification to the suggestion by Ryan Fox to split the screen into regions, and only checking for collisions within regions...

    e.g. split the play area up into a grid of squares (which will will arbitrarily say are of 1 unit length per side), and check for collisions within each grid square.

    That's absolutely the correct solution. The only problem with it (as another poster pointed out) is that collisions across boundaries are a problem.

    The solution to this is to overlay a second grid at a 0.5 unit vertical and horizontal offset to the first one.

    Then, any collisions that would be across boundaries in the first grid (and hence not detected) will be within grid squares in the second grid. As long as you keep track of the collisions you've already handled (as there is likely to be some overlap) you don't have to worry about handling edge cases. All collisions will be within a grid square on one of the grids.

    0 讨论(0)
  • 2020-11-22 06:02

    I implemented this code in JavaScript using the HTML Canvas element, and it produced wonderful simulations at 60 frames per second. I started the simulation off with a collection of a dozen balls at random positions and velocities. I found that at higher velocities, a glancing collision between a small ball and a much larger one caused the small ball to appear to STICK to the edge of the larger ball, and moved up to around 90 degrees around the larger ball before separating. (I wonder if anyone else observed this behavior.)

    Some logging of the calculations showed that the Minimum Translation Distance in these cases was not large enough to prevent the same balls from colliding in the very next time step. I did some experimenting and found that I could solve this problem by scaling up the MTD based on the relative velocities:

    dot_velocity = ball_1.velocity.dot(ball_2.velocity);
    mtd_factor = 1. + 0.5 * Math.abs(dot_velocity * Math.sin(collision_angle));
    mtd.multplyScalar(mtd_factor);
    

    I verified that before and after this fix, the total kinetic energy was conserved for every collision. The 0.5 value in the mtd_factor was the approximately the minumum value found to always cause the balls to separate after a collision.

    Although this fix introduces a small amount of error in the exact physics of the system, the tradeoff is that now very fast balls can be simulated in a browser without decreasing the time step size.

    0 讨论(0)
提交回复
热议问题