Edit: Set a breakpoint, stepped through, grabbed a sheet of paper and plotted what was happening. I was surprised to find my two "futurePoints" weren't where I expected to be... in fact, they were parallel with ballPoint instead of priorPoint. Which is, of course, because I mistakenly had written down "ballPoint" instead of "priorPoint". I've edited my code to show what was wrong.

Original Post: I'm trying to make a 2D game from scratch (Using mostly C with some Quartz(?) for graphics... some cocoa/obj-C to handle inputs.)

Thus far my game consists entirely of a "paddle" (line, defined with two CGPoints for endpoints, startPoint and endPoint) and a "ball" (circle, defined with a CGPoint ballPosition and a second CGPoint ballSpeed.)

Here's all the code for moving the ball, detecting collisions, and calculating what the rebound x and y should be. I've marked the code that I suspect is causing the problem in red:

Code:

float distanceBetweenTwoPoints(CGPoint p0, CGPoint p1)
{
return sqrtf(((p0.x-p1.x)*(p0.x-p1.x))+((p0.y-p1.y)*(p0.y-p1.y)));
}
float distanceBetweenPointAndLineEndpoints(CGPoint p0, CGPoint p1, CGPoint p2, bool segment)
{
//Is it a line segment or an infinite line?
if (segment)
{
CGPoint bp1 = CGPointMake(p1.x+((p1.x-p2.x)*0.01), p1.y+((p1.y-p2.y)*0.01));
CGPoint bp2 = CGPointMake(p2.x+((p2.x-p1.x)*0.01), p2.y+((p2.y-p1.y)*0.01));
//Verify it is closer to each endpoint than its cooresponding beyond point.
if (distanceBetweenTwoPoints(p0, p1) > distanceBetweenTwoPoints(p0, bp1))
{
//It's beyond p1, so find its distance to p1.
return distanceBetweenTwoPoints(p0, p1);
}
else if (distanceBetweenTwoPoints(p0, p2) > distanceBetweenTwoPoints(p0, bp2))
{
//It's beyond p2, so find its distance to p2.
return distanceBetweenTwoPoints(p0, p2);
}
}
//Either we didn't care about the segment or we're in the right range if we're here:
float top = fabs(((p2.x-p1.x)*(p1.y-p0.y))-((p1.x-p0.x)*(p2.y-p1.y)));
float bottom = sqrtf(((p2.x-p1.x)*(p2.x-p1.x))+((p2.y-p1.y)*(p2.y-p1.y)));
return top/bottom;
}
- (void)updateBall
{
if (updateTimer && [updateTimer isValid])
{
[updateTimer invalidate];
}
updateTimer = [NSTimer scheduledTimerWithTimeInterval:0.02 target:self selector:@selector(updateBall) userInfo:nil repeats:NO];
float height = self.frame.size.height;
[self setNeedsDisplayInRect:NSRectFromCGRect(CGRectMake((ballPoint.x-.01)*height, (ballPoint.y-0.01)*height, 0.02*height, 0.02*height))];
ballPoint.x += (ballSpeed.x*0.02);
ballPoint.y += (ballSpeed.y*0.02);
redBall = false;
//Has the ball collided with the paddle?
if (0.01 >= distanceBetweenPointAndLineEndpoints(ballPoint, startPoint, endPoint, TRUE))
{
NSLog(@"Collision!");
redBall = true;
//Is the line either horizontal or vertical?
if (endPoint.x == startPoint.x)
{
ballSpeed.y *= -1.0;
}
else if (endPoint.y == startPoint.y)
{
ballSpeed.x *= -1.0;
}
else
{
[COLOR=RED]//Find the prior point.
CGPoint priorPoint = CGPointMake(ballPoint.x-(ballSpeed.x*0.02), ballPoint.y-(ballSpeed.y*0.02));
//Find the distance from the prior point to perpendicular bisector.
float slope = ((endPoint.y-startPoint.y)/(endPoint.x-startPoint.x));
float perp = (-1.0)/slope;
CGPoint perpPoint = CGPointMake(ballPoint.x+1, ballPoint.y+perp);
float priorPerpD = distanceBetweenPointAndLineEndpoints(priorPoint, ballPoint, perpPoint, FALSE);
//Move twice this distance along a line parallel to the slope.
float offsetX = 2.0*(priorPerpD/sqrt(1+(slope*slope)));
CGPoint futurePoint1 = CGPointMake([COLOR=GREEN]priorPoint.x /*was incorrectly ballPoint.x before*/[/COLOR]+offsetX, [COLOR=GREEN]priorPoint.y /*was incorrectly ballPoint.y before*/[/COLOR]+(offsetX*slope));
CGPoint futurePoint2 = CGPointMake([COLOR=GREEN]priorPoint.x /*was incorrectly ballPoint.x before*/[/COLOR]-offsetX, [COLOR=GREEN]priorPoint.y /*was incorrectly ballPoint.y before*/[/COLOR]-(offsetX*slope));
//Figure out which of the two future points are correct:
float error1 = fabs(priorPerpD-distanceBetweenPointAndLineEndpoints(futurePoint1, ballPoint, perpPoint, FALSE));
float error2 = fabs(priorPerpD-distanceBetweenPointAndLineEndpoints(futurePoint2, ballPoint, perpPoint, FALSE));
CGPoint futurePoint = (error1<error2)?futurePoint1:futurePoint2;
//Finally, determine a trajectory that heads towards the correct point.
ballSpeed.x = (futurePoint.x - ballPoint.x)*50.0;
ballSpeed.y = (futurePoint.y - ballPoint.y)*50.0;
}
}[/COLOR]
if (ballPoint.y > 0.99)
{
ballPoint.y = 0.99;
ballSpeed.y = -ballSpeed.y;
}
else if (ballPoint.y < 0.01)
{
ballPoint.y = 0.01;
ballSpeed.y = -ballSpeed.y;
}
float screenWidth = self.frame.size.width/self.frame.size.height;
if (ballPoint.x > screenWidth-0.01)
{
ballPoint.x = screenWidth-0.01;
ballSpeed.x = -ballSpeed.x;
}
else if (ballPoint.x < 0.01)
{
ballPoint.x = 0.01;
ballSpeed.x = -ballSpeed.x;
}
[self setNeedsDisplayInRect:NSRectFromCGRect(CGRectMake((ballPoint.x-.01)*height, (ballPoint.y-0.01)*height, 0.02*height, 0.02*height))];
}

My problem is... the speed of the ball seems to be all wrong after it rebounds off of the paddle. At some angles, it'll leave the paddle much faster than the speed it comes in at; at other angles it will leave much slower than the angle it came in at (it's even possible to stop the ball altogether.)

Edit now that the above is all solved:

My next issue is that the ball can sometimes pass straight through the paddle... this is likely an issue with the fact that the ball moves too quickly between each calculation... possibly I should find a way to check if the line formed by the old ball point and the new ball point passes through the paddle instead...