COMP 310
Spring 201 |
## Elastic Rebounding Between Balls of Unequal Radii and Mass |

Here we will attempt a more rigorous treatment of the rebound position between two balls of unequal mass (below, mass is determined by the area of the ball), to exactly calculate the proper position of the rebounded balls without the need for a "nudging" hack.

The basic calculation strategy is backtrack in time wit the pre-collision velocities to the actual collision point and then to forward track in time with the post-collision velocities to the balls proper locations back at the current time.

- Calculate the time when the two balls first collided. Here
is the derivation of
the collision time for two balls of unequal radius.
- In our system, unless you have changed it, the velocity is units of timer ticks, not seconds. Thus the calculated time will be in units of timer ticks.
- The collision time we are interested in is any value that is in the past, i.e. negative, but after the last tick, i.e. greater than -1.0. All other collision times can be ignore.

- For each ball,
- use their current velocity and the negative collision time to find the vector displacement to back to the location of the balls at the moment of the first contact between the balls. It is important to use floating point double values here to maintain accuracy.
- Using the impulse, calculated as normal, to calculate the velocity of the ball after the collision.
- Using the negative of the collision time and the post-collision velocity to add the vector displacement forward in time (back to the current time) to the already calculated backtrack displacement. This will give us the net displacement from the ball's current location to where it should be.
- Translate the balls current location by the calculated net displacement to the proper location. The ball will now be located properly and have the proper velocity.

For best results, have your system update the state of the balls *
after* they have moved in a straight line and bounced off the walls.

Here is the code for this more exact calculation. Note that the impulse() and updateCollision() methods below are not the same as that used when nudging because there is no nudging taking place during the impulse calculation, instead the position is updated at the same time that the velocity is updated.

/** * A physically correct elastic two-body collision model. * @author Stephen Wong * */ public class CollideExactStrategy extends AUpdateStrategy { @Override public void updateState(final Ball context, Dispatcher dispatcher) { // TODO: To be completed by student // The only difference between the code needed here and the code used for the nudging technique is // the conditional to test if the balls have collided within the last timer tick. } /** * Calculates the time of the first contact between two balls. A negative returned time means that the balls * collided in the past. A positive time value means that either the balls will collide in the future, * or if equal to Double.MAX_VALUE, they will never collide, i.e. are traveling parallel to each other. * The contact time is returned in units of timer ticks. * * IMPORTANT NOTE: Only negative time values should be used for collision detection because negative times * indicate that the balls collided in the past. However, any time less than -1.0 means that the collision * occurred during the previous tick cycle and thus should be ignored. * * @param x1 The location of the first ball * @param x2 The location of the second ball * @param v1 The velocity of the first ball * @param v2 The velocity of the second ball * @param minSeparation The minimum separation, i.e. contact distance between the balls. * @return The first possible contact time between the two balls. */ private double collisionTime(Point x1, Point x2, Point v1, Point v2, double minSeparation) { Point deltaX = deltaP(x1, x2); Point deltaV = deltaP(v1, v2); double deltaX2 = deltaX.distanceSq(0.0, 0.0); double deltaV2 = deltaV.distanceSq(0.0, 0.0); double R2 = minSeparation*minSeparation; double dvdx = deltaV.x*deltaX.x + deltaV.y+deltaX.y; double root2 = dvdx*dvdx - deltaV2*(deltaX2-R2); if (root2 < 0.0) { return Double.MAX_VALUE; // no solution for t } double t = (-dvdx - Math.sqrt(root2))/deltaV2; // want most negative time solution, i.e. first contact return t; } /** * Returns the vector that points from point p1 to point p2 * @param p1 The first point * @param p2 The second point * @return The difference vector between p1 and p2 */ private Point deltaP(Point p1, Point p2) { return new Point(p2.x - p1.x, p2.y-p1.y); } /** * Returns the reduced mass of the two balls (m1*m2)/(m1+m2) Gives correct * result if one of the balls has infinite mass. * * @param mSource * Mass of the source ball * @param mTarget * Mass of the target ball */ protected double reducedMass(double mSource, double mTarget) { if (mSource == Double.POSITIVE_INFINITY) return mTarget; if (mTarget == Double.POSITIVE_INFINITY) return mSource; else return (mSource * mTarget) / (mSource + mTarget); } /** * Calculates the impulse (change in momentum) of the collision in the * direction from the source to the target This method calculates the * impulse on the source ball. The impulse on the target ball is the * negative of the result. * * @param lSource * Location of the source ball * @param vSource * Velocity of the source ball * @param lTarget * Location of the target ball * @param vTarget * Velocity of the target ball * @param reducedMass * Reduced mass of the two balls * @param distance * Distance between the two balls. * @param deltaR * The minimum allowed separation(sum of the ball radii) minus the actual separation(distance between ball centers). Should be a * positive value. This is the amount of overlap of the balls as measured along the line between their centers. * @return */ protected Point2D.Double impulse(Point lSource, Point vSource, Point lTarget, Point vTarget, double reducedMass, double distance, double deltaR) { // Calculate the normal vector, from source to target double nx = ((double) (lTarget.x - lSource.x)) / distance; double ny = ((double) (lTarget.y - lSource.y)) / distance; // delta velocity (speed, actually) in normal direction, source to // target double dvn = (vTarget.x - vSource.x) * nx + (vTarget.y - vSource.y) * ny; return new Point2D.Double(2.0 * reducedMass * dvn * nx, 2.0 * reducedMass * dvn * ny); } /** * Updates the velocity and position of the source ball (context), given an impulse, then uses the * context's interactWith method to determine the post collision behavior, from the context * ball's perspective. The change in velocity is the impulse divided by the (source) ball's mass. To change * the velocity of the target ball, switch the source and target input * parameters and negate the impulse values. This will also run the post collision behavior from * the other perspective. * * @param context * The source ball to update * @param target * The ball being collided with * @param impX * x-coordinate of the impulse * @param impY * y-coordinate of the impulse * @param tContact The first ball contact time in ticks. Should be a negative number. */ protected void updateCollision(Ball context, Ball target, double impX, double impY, Dispatcher dispatcher, double tContact) { int mContext = context.getRadius() * context.getRadius(); double dx = context.getVelocity().x*tContact; double dy = context.getVelocity().y*tContact; context.getVelocity().translate((int) Math.round(impX / mContext),(int) Math.round(impY / mContext)); dx += -context.getVelocity().x*tContact; dy += -context.getVelocity().y*tContact; context.getLocation().translate((int)Math.round(dx), (int)Math.round(dy)); context.interactWith(target, dispatcher); } }

Here's an interesting article talking about exact collisions of balls, particularly the issues when multiple balls collide within a single time slice: http://garethrees.org/2009/02/17/physics/

© 2017 by Stephen Wong