简体   繁体   中英

Projectile bouncing off curved surface - Pygame, Python 3

I am currently working on a (top-down) game that requires a code to bounce a projectile off of a circular surface.

The way I would like this to work is that when the projectile hits the surface 在此输入图像描述

a hypothetical line is drawn from the center of the circle 在此输入图像描述

which is then used as the normal to calculate the angle of reflection of the new trajectory. 在此输入图像描述

Physics (such as gravity) are completely unnecessary. Is there an more or less simple way of doing that?

Reflect a line segment off a circle.

This will get the reflexed line segment off a circle. The length of reflected line segment and incoming line segment to the circle intercept will be equal to the length of the original line segment. If you just want the reflected ray just stop the calculations when the reflected vector has been worked out. This also assumes that the incoming line segment starts outside the circle. If the line starts inside the line then it fails. You can check if the line starts inside the circle by getting the distance from the circle center to the line start if that is less than the circle radius then the line starts inside the circle.

This is in pseudo code as I don't do PHP. sqrt() is a function to get the sqrt of a number.

The incoming ray as a line segment. Where line.x1, line.y1 are the start and x2,y2 are the end

line x1,y1,x2,y2

The circle with x,y position and r radius

circle x,y,r

First you need to get the line segment circle intercept if any. So find the closest point on the line to the circle center and see if the distance from that point to the circle center is less than the circle radius.If it is then the line segment might intercept. If the distance is greater than there is no intercept.

Convert the line segment to a vector

vx = line.x2 - line.x1;
vy = line.y2 - line.y1;

Get the length of the line segment squared

len2 = (vx * vx + vy * vy);

Get the normalised distance from the start of the line to the point that is closest to the circle center

unitDist =  ((circle.x - x1) * vx + (circle.y - y1) * vy) / len2;

Then using the normalised distance get the point closest to the circle in absolute position.

x = x1 + vx * unitDist;
y = y1 + vy * unitDist;

Now get the distance from that point to the circle center

dist =  sqrt((x - circle.x)*(x - circle.x)+ (y - circle.y)*(y - circle.y));    

If the distance is less than the circle radius then there is possibility of an intercept. If not then there is no intercept so exit as there is no reflection.

if( dist > circle.r) exit;

Now we know that the line will intercept the circle, we need to check if the line segment intercepts, so we find the intercept point towards the line start and see if that point is on the line segment or not.

Thus From the closest point on the line to the circle center is length `dist' that we just worked out then out from there to the intercept point is length circle.r and then the distance back to the closest point is unknown, But we know that the triangle the three lengths make is a right triangle the missing side is

lenToIntercept = sqrt(circle.r * circle.r - dist * dist);

This gives us the distance from the closest point on the line to the circle center, back along the line toward the start to get to the intercept point. For convenience I will convert that distance to a unit scaled distance (normalised) by dividing by the length of the line segment

lenToIntercept = lenToIntercept / sqrt(len2)

Subtract that normalised length from the normalised distance from the start of the incoming line to the closest point on the line to the circle center.

unitDist = unitDist - lenToIntercept;

If the new unitDist to the intercept point is >= 0 and <= 1 then we know that the line segment has intercepted the circle, otherwise the the line segment has not intercepted the circle and we can exit.

if(unitDist < 0 or unitDist > 1) exit; // no intercept

Now we can calculate the absolute position of the intercept point by adding the vector multiplied by the normalised distance from the start of the incoming line to intercept point

x = line.x1 + unitDist * vx;
y = line.y1 + unitDist * vy;

Now we can work out the reflection.

Get the vector from the circle center to the intercept point

cx = x - circle.x;
cy = y - circle.y;

We need the tangent to make the calculations easier so get the tangent by rotating clockwise 90 deg

tx = -cy;
ty = cx;

Normalise the tangent vector. We know it is the same length as the circle radius because it is just the rotated line from the circle center to the intercept point so we can normalise the tangent vector using the circle radius

tx = tx / circle.r;
ty = ty / circle.r;

We also need to normalise the incoming line vector so divide it by its length

vx = vx / sqrt(len2);
vy = vy / sqrt(len2);

Now get the dot product of the tangent and the line seg. It is the distance from the incoming vector end to the tangent line, squared

dot = vx * tx + vy * ty;

Double it because we want to get the reflection which is on the other side of the tangent line.

dot = dot * 2;

Now lengthen the normalised tangent vector by the dot amount

tx = tx * dot;
ty = ty * dot;

and subtract the incoming line vector to give us the vector of the outgoing line

reflectedX = tx - vx;
reflectedY = ty - vy;

Normalise that vector by getting its length

lengR = sqrt(reflectedX * reflectedX + reflectedY * reflectedY);

and dividing the reflected line by that length

reflectedX = reflectedX / lengR;
reflectedY = reflectedY / lengR;

Now to calculate the reflected part of the line which is the distance form the circle intercept point to the end of the incoming line. We already have the normalised distance along the line to the intercept point so the remaining distance is

remainDist = 1-unitDist;

Multiply the normalised distance by the incoming line length

remainDist = remainDist * sqrt(len2);

now multiply the reflected normalised vector by this length

reflectedX = reflectedX * remainDist;
reflectedY = reflectedY * remainDist;

A lastly create the new reflected line segment which is the line from the intercept point to that point plus the reflected vector

reflectedLine.x1 = x;
reflectedLine.y1 = y;
reflectedLine.x2 = x + reflectedX;
reflectedLine.y2 = y + reflectedY;

Assuming you know the origin of the circle (x0,y0) , the collision point (x1,y1) , and the equation of the line, all you have to do is calculate the angle difference between the line and the radius:

  • equation of the line: y = a*x+B (you know this one)
    -> angle of the line = theta0 = arctg(a)
  • angle of the radius: theta1 = arctg((y1-y0)/(x1-x0))
  • angle of the second line = theta2 = 2*theta0 - theta1
  • equation of the bounced line = y = y1 + tg(theta2)*(x-x1)

This should work although you might need a math lib

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM