We are the Normal and the Bounce Equation
Hello, it's been a while since I last wrote something for Pete. The last time I wrote for QBE was an intro into OpenGL. For those who are expecting another 3d tute, sorry, this is 2d. But be sure to understand the concepts here for the algos and equations presented here could he used in a 3d engine.
"How do I bounce?". I've always seen this question being asked on all the forums I have been to. Since I've always have something to say on the topic, I decided to make this tute. This tute has a long time coming. I envisioned it before I went AWOL from the community after my good friend, DR_D, showed me an awesome 2d demo of a ball colliding and "sticking" to lines that it collided to (see appendix). I thought this demo could be extended to make a pinball like engine. So I "searched" the net for the bounce-to-an-arbiraty-line-algorithm but lo and behold, I never thought there isn't a single tutorial covering it, at least in 2d, but most likely, I suck at using google. :*)
So I sat on a table one day and with a pen and a paper drew some vectors and the vectors I want to get. After about 15 minutes(maybe less, but I draw slow) of work, I figured the damn thing out. I was sure of the equations I came to but when I translated it to code, something went wrong. There are some issues/errors with the direction and after 2 days of trying to figure it out, I gave up and posted my problems here: vector secret.... It took like 3 seconds for DJ Peters to figure out what was wrong. I was using integers instead of singles! The equations were fine, the code was fine, but the coder(me) wasn't. Then for a double whammy, after I made the equations and the code, figured out the vector math, etc., Blitz and Shadowolf, or was it ShiftLynx?, pointed me to the standard equation o get the reflection vector (see appendix). It felt good knowing that I did the vector math myself without resorting to any ready made equation though. Enough rant. Let's start.
Before actually being able to bounce, we need to have:
I. The Normal
Number 3 is easy, all you have to do is read this: <vector tutorial>. With all the vector brouhaha out of the way, we could now code our 2d normal algo. A normal to a line is a vector perpendicular to the given line. It means that the normal has a union of 90 degrees to the said line. Ie. Their intersection has a 90 degree angle in it. read the vector tute if you're still getting blanks. :*) Now for every line, their are always 2 normals and what you use depends on where you want to bounce. Here's the figure:
The black line is our line segment and the blue line or the red line are 2 normals to the line. Here's the code to calculate the normal:
<code>
private sub get_2dnormal (seg as
segment_type, s as integer)
if s then
seg.normal.x = -(seg.y2-seg.y1)
'negate to get the other normal
seg.normal.y = (seg.x2-seg.x1)
'erase negation here if you want the other
'normal
else
seg.normal.x = (seg.y2-seg.y1)
'negate to get the other normal
seg.normal.y = -(seg.x2-seg.x1)
'erase negation here if you want the other
'normal
end if
normalize (seg.normal)
end sub
</code>
"s" as a parameter is a "switch" to give you the option of what normal to use. "seg" is the line segment.
II. Closest point on line
To be able to collide a ball to a line segment, we need to have a closest point to a line function and check whether the ball's radius intersects the line. To get the closest point...
Say we have a point P and a line segment defined by AB. We need to find a way to find out the distance of P from the line defined by AB. AS some of you have already known (from Geometry) that the closest point on a line is located on a line perpendicular to it. Now the problem is we know where P is, we know where A and B are be we don't know the perpendicular vector/line that intersects AB which also intersects P. The answer? The dot product. The dot product returns the Cosine of the angle between 2 vectors but it also gives us the "projection" or the "length of projection" of one vector onto another. Now to get the dot product we need to define a vector from A to P and from A to B. To define a vector from A to P you only have to subtract A from P.
ie.
<code>
v1.x = P.x - A.x
v1.y = P.y - A.y
</code>
Doing the same with A and B...
<code>
v2.x = B.x - A.x
v2.y = B.y - A.y
</code>
Now we need to get the projection by using the dot product...
We normalize v2 because we need to project v1 onto it using the dot product. Projecting v1 onto v2 gives us the component if v1 that is parallel to v2. Check the vector tute for reference. The dot product essentially "drops" a perpendicular from P to a point in AB. What you get as a result is the length of projection.
Just dropping a perpendicular does not necessarily results in P being in the in between the line AB. There are 3 cases.
Case 1
This is the first case where the angle is greater than 90 but less than 270 where out dot is negative. How can I be sure that it's sign is negative?
Proof:
Let: R = Radius(length of A to P)
cos(angle) = dot(v2,v1)/|v1||v2|
cos(angle) = A/H or X/R
sign of X is negative when 90<angle<270 since point P would be in Quadrants II or III.
cos(angle) = -/+ = (-) :*)
For this case we return A since P does not have a projection within the line. How do we check for this case? Easy check if the dot is less than 0. :*)
Proof(clue):
cos(90) = 0; figure it out yet. :*)
If dot<=0 Then Return a
Case 2
In this case the projection of P is still outside the boundaries of A and B but it's also located on the right of B so we return B. How do we check? We check if the length of projection is greater than the distance from A to B. :*)
If dot>=distance(a,b) Then Return b
The distance formula is just the magnitude of the vector from A to B (v2)
<code>
private function get_magnitude ( v as vector2d ) as single
return sqr( ( v.x * v.x ) + ( v.y * v.y ) )
end function
</code>
Now for the generic case...
case 3
In this case the projection is within the lines boundary so we return an arbitrary vector that is also the same vector as v2 but scaled according to the projection length. :*)
<code>
vreturn.x = a.x + (v2.x * t)
vreturn.y = a.y + (v2.y * t)
return vreturn
</code>
The whole function is in the sources called
<code>
function closest_point_on_line(a as vector2d, b as vector2d, p as vector2d) as vector2d
:
:
end function
<code>
III. The bounce equation
Given a ball with a direction vector a colliding on a plane with a normal b, how do we get vector c? (the bounce vector is c) Answer: basic vector arithmetic. All the vectors I'll be discussing here are called "free" vectors. Free vectors are vectors that could be placed anywhere you want. :*)
The above picture is the same as the first one but with another vector d. vector d is the negative of vector a. Hence it points in the direction opposite vector a. Magnitude of d is also equal to a.
Important!!! In the following figure, our d vector above becomes the b vector below and not the normal b.
Since we substituted the letter b instead of d, b is the negative of a in this case.
Now I subtract a from b
[b + (-a)] to get a vector pointing opposite a but
twice the length since mag(a) = mag(b). We'll call it
impact vector
. I
drop a perpendicular from impact to the normal to get the length of the
projection which is:
Code: |
|
We multiply this length to our original normal to get the new vector we'll
call it impulse.
Now that we have the
impulse
vector, all we need to do is to add it to our
original vector a to get c.
See it's sooo easy!
Here's the demo of this tutorial. :*)
<bounceline.bas/bounceline.exe>
Richard Eric M. Lope BSN RN
Relsoft 2006
Special thanks to:
Dr. D
DJ Peters
Blitz
Shadowwolf or Shiftlynx
Appendix:
a. Dr. D's line collision demo
<drbounce.bas/exe>
b. The general equation for the reflection vector is:
-2*(V dot N)*N + V
c. As an exercise, why not make multiple balls bouncing on the screen.
Bouncing from ball to ball is almost the same as our equation. Here's a demo of
if in QB that I wrote: Ball to ball collision and response. It is based on this tute by Hugo Elias. Hah! I ported the ball to ball demo to FB.
<ball2ball.bas/exe>