A point the Bullet is moving toward: x2, y2, and z2
And speed of Bullet in units/frame: n
Frame is define as the time increment between updates.
Sphere's center point: sx, sy, and sz
A point the Sphere is moving toward: sx2, sy2, and sz
Sphere's radius: r
And speed of Sphere in units/frame: ns
Asked to Write Code for following:
Will the projectile intersect the sphere?
If so, where in xyz space will the collision take place?
At what angle will the projectile ricochet off the sphere?
Give the new velocity vector of bullet.
Assumptions:
Bullet and Sphere move in stright lines.
But code could be modify for paths that follow mathematical paths defined as a function of time or space, I belive.
Like the Sphere traveling in a circle.
Bullet's current point is its starting point. It is assumed before this point it doesn't matter.
Bullet can't move backwards along its path unless it hits something maybe.
Bullet can't start inside sphere. Wouldn't make sense to test for hit when inside.
Bullet is a point, so rotation about its center is not factored in.
Bullet has no mass, so momentum is not factored in.
Ricochet angle (in radians) is to the tangent plain's surface
at hit point, not to the normal of the plain.
Frame or how many frames (time) to hit, or distance to hit point was not wanted but I provide.
Structure for the answer was not set so had to create one.
With use of floats there is some error due to imprecision
in values of floats. See Considerations
I did this so that there would be a place were people could come and look at ways
to do hit test.
It then boiled down to a couple of unit vectors, the distance formula
between two points in 3D, a derivative and some sin and cos stuff of a
right triangle. This solution lets you check if there will ever be a hit
and find out were the hit happens all with out having to check each frame.
You can find out when easily but that wasn't a requirement to find.
Had to add test before acos in java because I was getting a number
1.000000004 which acos (inverse cos) doesn't like, set to 1.
Take a look, I added out put for other data along the way.
(Javascript example );
Bullet's & Sphere's unit
vectors. Bullet's unit vector is used to define the bullet's path at any point
along it.
U = Ux + Uy + Uz
Find distances form point 1 to point 2 in X, Y, & Z directions.
X = x2 - x1
Y = y2 - y1
Z = z2 - z1
Find distance between to points.
D = (X^2 + Y^2 + Z^2)^.5
Find Unit Vector by dividing each component by D.
Ux = X/D
Uy = Y/D
Uz = Z/D
Sphere's unit vector is used to define the bullet's path at any point along it.
S = Sx + Sy + Sz
Find distances form point 1 to point 2 in X, Y, & Z directions.
X = sx2 - sx1
Y = sy2 - sy1
Z = sz2 - sz1
Find distance between to points.
D = (X^2 + Y^2 + Z^2)^.5
Find Unit Vector by dividing each component by D.
Sx = X/D
Sy = Y/D
Sz = Z/D
Get line equations
for X, Y, & Z using ds as distance along line from point 1. Bullet's:
Xt = x1 + Ux*ds
Yt = y1 + Uy*ds
Zt = z1 + Uz*ds
Sphere's:
SXt = sx1 + Sx*ds2
SYt = sy1 + Sy*ds2
SZt = sz1 + Sz*ds2
Get distance
equation from any point on bullet's line to center of sphere. Distance equation is D = (X^2 + Y^2 + Z^2)^.5, but it can be kept in
the form of D^2 = X^2 + Y^2 + Z^2
because we can compare to R^2 which is radius of sphere squared.
X = SXt - Xt = (sx1 + Sx*ds2) - (x1 + Ux*ds)
Y = SYt - Yt = (sy1 + Sy*ds2) - (y1 + Uy*ds)
Z = SZt - Zt = (sz1 + Sz*ds2) - (z1 + Uz*ds)
D^2 = (sx1 + Sx*ds2 - x1 - Ux*ds)^2 + (sy + Sy*ds2 - y1 - Uy*ds)^2 + (sz + Sz*ds2 - z1 - Uz*ds)^2
We know the speed of both the bullet and sphere. So we can find out how much bullet moves compare to sphere as a single ds.
ds = n*t and ds2 = ns*t; t = ds/n = ds2/ns so for any given time interval ds2 = ds*(ns/n).
We do this to make math a lot easiler by only solving with one varible, use N=ns/n so ds2 = N*ds.
Note: Ux^2 + Uy^2 + Uz^2 = 1 because U is a unit vector.
Sx^2 + Sy^2 + Sz^2 = 1 because S is also a unit vector
So the ds^2 terms can be rewritten to:
(Sx^2 + Sy^2 + Sz^2)N^2 -2(Sx*Ux + Sy*Uy + Sz*Uz)N + (Ux^2 + Uy^2 + Uz^2)
and simplified to:
let C10 = N - 2*N(Sx*Ux + Sy*Uy + Sz*Uz) + 1
So final D^2 is:
D^2 = C1 + C3 + C5 - 2(C2 + C4 + C6 - (C7 + C8 + C9))*ds + C10*ds^2
Solve for
D and take derivative with respect to ds. D = (C1 + C3 + C5 - 2(C2 + C4 + C6 - (C7 + C8 + C9))*ds + C10*ds^2)^.5
Let C11 = C1 + C3 + C5 and C12 = C2 + C4 + C6 - (C7 + C8 + C9)
dD = (.5(C11 - 2(C12)*ds + C10*ds^2)^-.5)
*(-2(C12) + 2*C10*ds)
dD = ((-C12 + C10*ds) / ((C11 - 2(C12)*ds + C10*ds^2)^.5)
Solve for
ds by setting dD equal to zero. We set dD to zero because that is were the distance form line to Sphere's
center is smallest
and the only way dD can be zero is if -C12 + C10*ds = 0
So ds = C12 / C10
Test if ds
>= 0 then continue, if ds is neg. means bullet has to go backwards to hit
sphere. ds could be negative which would mean the line is closest behind point
1.
Since the bullet starts at point 1 and moves to pt 2 there can be no
hit.
Put ds back
into distance equations and get smallest distance form line to sphere. Put ds back into Distance square: D2 = C11 - 2(C12)*ds + C10*ds^2
D2 = C11 - 2(C12)*(C12/C10) + C10*(C12/C10)*(C12/C10)
D2 = C11 - C12^2/C10
Test if D
<= R then continue Hit happen, where D is distance squared & R is
radius squared. So if D2 which is distance squared (D^2) form center to closest point
on line is less than R2 = r^2 then we must have hit the Sphere.
Use distance
equation again with D = R and solve for ds, ds will have two solutions. Now set D2 the value of R2 and solve for the two ds's that will solve
the equitation.
Use the aX^2 + bX + c = 0 solution of
X = (-b - (b^2 + 4ac)^.5)/2a
X = (-b + (b^2 + 4ac)^.5)/2a
Set a variable sq to (b^2 + 4ac)^.5
R2 = C11 - 2(C12)*ds + C10*ds^2
C10*ds^2 -2(C12)ds + C11 - R2; a=C10, b=-2*(C12), and c = C11 - R2
sq = (4(C12)*(C12) + 4*C10*(C11 - R2))^.5
Solve or two ds's
ds = (-2*(C12) - sq )/(2*C10)
or
ds = (-2*(C12) + sq)/(2*C10)
The correct
ds is the smallest non negative answer. This is for a check just in case point one might have been in Sphere
to begin with.
Put this
ds into line equations for X, Y, & Z to get point were bullet hits the sphere. Xt = x1 + Ux*ds
Yt = y1 + Uy*ds
Zt = z1 + Uz*ds
So hit point is (Xt, Yt, Zt)
time to hit is time = ds/n and frames till hit is frames = time*fps
Now find Sphere's center at hit
SXt = sx + Sx*N*ds
SYt = sy + Sy*N*ds
SZt = sz + Sz*N*ds
Find unit
vector form point on sphere to center. Now we need to find the angle the bullet has to the tangent plain of
the sphere at the hit point.
First we need the normal to this tangent plain which is the unit vector
form sphere's center to hit point.
T = Tx + Ty + Tz
X = SXt - Xt
Y = SXt - Yt
Z = SXt - Zt
D = (X^2 + Y^2 + Z^2)^.5
Tx = X/D
Ty = Y/D
Tz = Z/D
Use Dot
product with lines unit vector to find angle between lines vector and sphere
vector. Now that we have unit vector of tangent plain we can get the angle
between the bullet's unit vector and plains.
This is done by use of the Dot product.
cos(theta) = (U dot T)/(|U||T|)
since U and T are unit vectors |U| = 1 and |T| = 1
So cos(theta) = U dot V solve for theta
and U dot T = Ux*Tx + Uy*Ty + Uz*Tz
theta = acos(Ux*Tx + Uy*Ty + Uz*Tz) might be different depending on
complier.
Take 90
- angle found to get ricochet angle. The theta found is the angle from bullet's path to the normal to tangent
plain.
The ricochet angle is pi_half - theta.
theta is given in terms of radians and pi_half = 1.570796326795.
ricochet angle = pi_half - theta in radians.
Calculate
the two vectors normal to and parallel to tangent plain on sphere though
hit point. Now we need the two components perpendicular and parallel to the tangent
plain.
Uprep = U sin(ricochet angle)
Upara = U - Uperp
Reverse
normal vector and add to parallel vector to get new ricochet vector of
bullet. Bullet's Upara will stay the same but its Uperp will point in opposite
direction which means we take negative of it.
Then add the two unit vector back together to get new unit vector in
direction of ricochet.
U = Upara - Uperp
Multiply
by speed of bullet to get new velocity vector. Take this new U and multiply by n the speed of bullet in frames to
get new velocity vector.
Done
(JavaScript example )
You now know if the Bullet hit the stationary Sphere.
Were in 3D space it hit the Sphere.
The angle of ricochet of the Bullet to tangent plain surface
and the velocity vector of Bullet's new path.
Good
points about this method This is a good way to test for a hit because you don't need to check
each frame for a hit and you
can easily find out what frame the hit occurs by take distance form
point 1 to sphere (ds) divid by speed
of bullet (n). and added to your current frame.
Considerations Some things to consider about this are:
With the use of floats there is some error due to floats not being
precise ie( 0 could be 1.453e-23).
Hit could happen between frames.
This could be change for cylinder and/or box test also.
CODE Code uses:
4 square root calls
1 acos (arccos) call
1 sin call
Every thing else is adds, subtractions, multiplies and divides.
At every possible chance test for possible hit is done and if not possible
return FALSE.
Code is in C and all variables are global for speed with two structs
created for 3d points and the answer.
// Start Code
// must include at least these
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
typedef struct fpoint_tag
{
float x;
float y;
float z;
} fpoint;
struct answer_tag
{
fpoint hit;
float r_angle;
fpoint new_v;
float frs2hit;
float dis2hit;
} answer;
// global stuff needed of solving
#define TRUE 1
#define FALSE 0
// two points given for bullet
float bx1, by1, bz1;
float bx2, by2, bz2;
// two points for sphere's center point and at hit
float sx1, sy1, sz1;
float sx2, sy2, sz2;
float cen_x, cen_y, cen_z;
// sphere radius
float r;
// speed of bullet in units/frame
float n;
// speed of sphere in units/frame
float ns;
// unit vector of Bullet
float Ux, Uy, Uz;
// unit vector of Sphere
float Sx, Sy, Sz;
// unit vector of tangent plain
float Tx, Ty, Tz;
// temporary hold values
float X, Y, Z;
// used for arccos test and sin
float t;
float perp_mag;
// distance hold values
float D, D2;
// radius squared hold value
float R2;
// Constants Hold Values
float C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, N;
// for ricochet angle calculation
float half_pi = 3.14159265359 / 2;
// for ds values
float ds, ds1, ds2;
// for ds solution
float sq;
// for new velocity vector calculation
float Uperpx, Uperpy, Uperpz;
float Uparax, Uparay, Uparaz;
// check_hit returns true if there is a hit and false if there is none
// pt1 is starting point of bullet
// pt2 is point bullet is moving to
// cpt1 is starting point of sphere's center
// cpt2 is point sphere's center is moving to
// raduis is radius of sphere
// vel is velocity of bullet
// svel is velocity of sphere
int check_hit(fpoint pt1, fpoint pt2, fpoint cpt1, fpoint cpt2, float radius, float vel, float svel)
{
bx1 = pt1.x;
by1 = pt1.y;
bz1 = pt1.z;
bx2 = pt2.x;
by2 = pt2.y;
bz2 = pt2.z;
sz1 = cpt1.x;
sy1 = cpt1.y;
sz1 = cpt1.z;
sz2 = cpt2.x;
sy2 = cpt2.y;
sz2 = cpt2.z;
r = radius;
n = vel;
ns = svel;
R2 = r*r;
// Set answer struct to all 0 so it is emtpy
answer.hit.x = 0;
answer.hit.y = 0;
answer.hit.z = 0;
answer.r_angle = 0;
answer.new_v.x = 0;
answer.new_v.y = 0;
answer.new_v.z = 0;
answer.frs2hit = 0;
answer.dis2hit = 0;
// Check input vel and radius
// if bullet velocity is zero or neg return FALSE
// radius can not be negitive return FALSE
if(!(n>0)) return FALSE;
if(!(r>0)) return FALSE;
// check to make sure bullet next point is not first point
if(bx1 == bx2 && by1 == by2 && bz1 == bz2) return FALSE;
// Check to see if bullet starts inside sphere shouldn't happen
// if so return FALSE won't check for hit
X = sz1 - bx1;
Y = sy1 - by1;
Z = sz1 - bz1;
D2 = X*X+Y*Y+Z*Z;
if( D2 < R2) return FALSE;
// Bullet's unit vector
X = bx2 - bx1;
Y = by2 - by1;
Z = bz2 - bz1;
D = sqrt(X*X+Y*Y+Z*Z);
Ux = X/D;
Uy = Y/D;
Uz = Z/D;
// Sphere's unit vector
X = sx2 - sx1;
Y = sy2 - sy1;
Z = sz2 - sz1;
D = sqrt(X*X+Y*Y+Z*Z);
Sx = X/D;
Sy = Y/D;
Sz = Z/D;
// find smallest ds
// Calculate Constants
// D = C1 + C3 + C5 - 2(C2 + C4 + C6 -(C7 + C8 + C9))*ds + C10ds^2
C1 = sx*sx - 2*sx*bx1 + bx1*bx1;
C3 = sy*sy - 2*sy*by1 + by1*by1;
C5 = sz*sz - 2*sz*bz1 + bz1*bz1;
C2 = sx*Ux - bx1*Ux;
C4 = sy*Uy - by1*Uy;
C6 = sz*Uz - bz1*Uz;
N=ns/n;
C7 = (sx1*Sx*N - x1*Sx*N);
C8 = (sy1*Sy*N - y1*Sy*N);
C9 = (sz1*Sz*N - z1*Sz*N);
C10 = (N*N) - 2*(Sx*Ux+Sy*Uy+Sz*Uz)*N + 1);
C11 = C1 + C3 + C5;
C12 = C2 + C4 + C6 - (C7 + C8 + C9)
// Calculate ds by setting derivative of D to zero
ds = C12/C10;
// first major test if neg no hit return false
if(ds < 0) return FALSE;
D2 = D = C11 - 2*C12*ds + C10*ds*ds;
// second test D less then or equal to r squared
if(D2 > R2) return FALSE;
// D2 = R2 = C11 - 2*C12*ds + C10ds^2
// 0 = C11 - R2 - 2*C12*ds + C10ds^2
// uses ax2 + bx + c = 0
// x = (-b (-/+)(b^2 - 4ac)^.5)/2a
b = -2*C12;
sq = sqrt(b*b - 4*C10*(C11 - R2));
ds1 = (2*C12 - sq)/2;
ds2 = (2*C12 + sq)/2;
// ds1 should always be smallest non-negitive but check to make sure
if(ds1 < ds) ds = ds1;
if(ds < 0) ds = ds2;
// record hit location
answer.hit.x = bx1+Ux*ds;
answer.hit.y = by1+Uy*ds;
answer.hit.z = bz1+Uz*ds;
// additional info not ask for but nice to know maybe
answer.frs2hit = ds/n;
answer.dis2hit = ds;
// new center of sphere
cen_x = sx1 + Sx*N*ds;
cen_y = sy1 + Sy*N*ds;
cen_z = sz1 + Sz*N*ds;
// unit vector of tangent plain
X = cen_x - answer.hit.x;
Y = cen_y - answer.hit.y;
Z = cen_z - answer.hit.z;
D = sqrt(X*X+Y*Y+Z*Z);
Tx = X/D;
Ty = Y/D;
Tz = Z/D;
// had to put this in becuse of use of floats
t = Ux*Tx + Uy*Ty + Uz*Tz;
if(t > 1) t = 1;
// find ricochet angle
answer.r_angle = half_pi - acos(t);
// Bullets vector componant prependicular to plain is
// sin of ricochet angle times plain's Normal vector(T)
perp_mag = sin(answer.r_angle);
Uperpx = perp_mag*Tx;
Uperpy = perp_mag*Ty;
Uperpz = perp_mag*Tz;
// U = Upara + Uperp so Upara = U - Uperp
// Bullet's new velocity vector is Upara - Uprep
// So Unew = Upara - Uperp so Unew = U - Uperp - Uperp = U - 2Uperp
answer.new_v.x = (Ux - 2*Uperpx)*n;
answer.new_v.y = (Uy - 2*Uperpy)*n;
answer.new_v.z = (Uz - 2*Uperpz)*n;
// return TRUE have a hit answer is in answer struct
return TRUE;
}
//simple comand line program ask for input and out puts hit or not
//note no checking for input errors like letters instead of numbers as input
int main(void)
{
int test = 0;
char test_char = 'n';
fpoint start={0.0,0,0.0,0};
fpoint next={4.0,4.0,4.0};
fpoint sstart={4.0,4.0,0.0};
fpoint snext={0.0,0.0,2.0};
float radius = 4.0;
float speed = 3.0;
float sspeed = 3.0;
while(!test)
{
test = 1;
printf(Enter Bullet's start point x,y,z\n"
scanf("%f%f%f",&start.x,&start.y,&start.z);
printf(Enter next point x,y,z\n"
scanf("%f%f%f",&next.x,&next.y,&next.z);
printf("Enter Bullet's speed\n")
scanf("%f",&speed);
printf(Enter Sphere's center starting point x,y,z\n"
scanf("%f%f%f",&sstart.x,&sstart.y,&sstart.z);
printf(Enter Sphere's center starting point x,y,z\n"
scanf("%f%f%f",&snext.x,&snext.y,&snext.z);
printf("Enter radius\n")
scanf("%f",&radius);
printf("Enter Sphere's speed\n")
scanf("%f",&sspeed);
if(check_hit(start,next,sstart,snext,radius,speed,sspeed)) printf("Hit\n");
else printf("No hit\n");
printf("Do another test? enter y/Y or will exit");
getchar();
scanf("%c",&test_char);
if(test_char == 'y' || test_char == 'Y') test = 0;
}
printf("Done\n");
return;
}
// End Code