Sporadic Collision Detection - c++

I've been working on detecting collision between to object in my game. Right now everything tavels vertically, but would like to keep the option for other movement open. It's classic 2d vertical space shooter.
Right now I loop through every object, checking for collisions:
for(std::list<Object*>::iterator iter = mObjectList.begin(); iter != mObjectList.end();) {
Object *m = (*iter);
for(std::list<Object*>::iterator innerIter = ++iter; innerIter != mObjectList.end(); innerIter++ ) {
Object *s = (*innerIter);
if(m->getType() == s->getType()) {
break;
}
if(m->checkCollision(s)) {
m->onCollision(s);
s->onCollision(m);
}
}
}
Here is how I check for a collision:
bool checkCollision(Object *other) {
float radius = mDiameter / 2.f;
float theirRadius = other->getDiameter() / 2.f;
Vector<float> ourMidPoint = getAbsoluteMidPoint();
Vector<float> theirMidPoint = other->getAbsoluteMidPoint();
// If the other object is in between our path on the y axis
if(std::min(getAbsoluteMidPoint().y - radius, getPreviousAbsoluteMidPoint().y - radius) <= theirMidPoint.y &&
theirMidPoint.y <= std::max(getAbsoluteMidPoint().y + radius, getPreviousAbsoluteMidPoint().y + radius)) {
// Get the distance between the midpoints on the x axis
float xd = abs(ourMidPoint.x - theirMidPoint.x);
// If the distance between the two midpoints
// is greater than both of their radii together
// then they are too far away to collide
if(xd > radius+theirRadius) {
return false;
} else {
return true;
}
}
return false;
}
The problem is it will randomly detect collisions correctly, but other times does not detect it at all. It's not the if statement breaking away from the object loop because the objects do have different types. The closer the object is to the top of the screen, the better chance it has of collision getting detected correctly. Closer to the bottom of the screen, the less chance it has of getting detected correctly or even at all. However, these situations don't always occur. The diameter for the objects are massive (10 and 20) to see if that was the problem, but it doesn't help much at all.
EDIT - Updated Code
bool checkCollision(Object *other) {
float radius = mDiameter / 2.f;
float theirRadius = other->getDiameter() / 2.f;
Vector<float> ourMidPoint = getAbsoluteMidPoint();
Vector<float> theirMidPoint = other->getAbsoluteMidPoint();
// Find the distance between the two points from the center of the object
float a = theirMidPoint.x - ourMidPoint.x;
float b = theirMidPoint.y - ourMidPoint.y;
// Find the hypotenues
double c = (a*a)+(b*b);
double radii = pow(radius+theirRadius, 2.f);
// If the distance between the points is less than or equal to the radius
// then the circles intersect
if(c <= radii*radii) {
return true;
} else {
return false;
}
}

Two circular objects collide when the distance between their centers is small enough. You can use the following code to check this:
double distanceSquared =
pow(ourMidPoint.x - theirMidPoint.x, 2.0) +
pow(ourMidPoint.x - theirMidPoint.x, 2.0);
bool haveCollided = (distanceSquared <= pow(radius + theirRadius, 2.0));
In order to check whether there was a collision between two points in time, you can check for collision at the start of the time interval and at the end of it; however, if the objects move very fast, the collision detection can fail (i guess you have encountered this problem for falling objects that have the fastest speed at the bottom of the screen).
The following might make the collision detection more reliable (though still not perfect). Suppose the objects move with constant speed; then, their position is a linear function of time:
our_x(t) = our_x0 + our_vx * t;
our_y(t) = our_y0 + our_vy * t;
their_x(t) = their_x0 + their_vx * t;
their_y(t) = their_y0 + their_vy * t;
Now you can define the (squared) distance between them as a quadratic function of time. Find at which time it assumes its minimum value (i.e. its derivative is 0); if this time belongs to current time interval, calculate the minimum value and check it for collision.
This must be enough to detect collisions almost perfectly; if your application works heavily with free-falling objects, you might want to refine the movement functions to be quadratic:
our_x(t) = our_x0 + our_v0x * t;
our_y(t) = our_y0 + our_v0y * t + g/2 * t^2;

This logic is wrong:
if(std::min(getAbsoluteMidPoint().y - radius, getPreviousAbsoluteMidPoint().y - radius) <= theirMidPoint.y &&
theirMidPoint.y <= std::max(getAbsoluteMidPoint().y + radius, getPreviousAbsoluteMidPoint().y + radius))
{
// then a collision is possible, check x
}
(The logic inside the braces is wrong too, but that should produce false positives, not false negatives.) Checking whether a collision has occurred during a time interval can be tricky; I'd suggest checking for a collision at the present time, and getting that to work first. When you check for a collision (now) you can't check x and y independently, you must look at the distance between the object centers.
EDIT:
The edited code is still not quite right.
// Find the hypotenues
double c = (a*a)+(b*b); // actual hypotenuse squared
double radii = pow(radius+theirRadius, 2.f); // critical hypotenuse squared
if(c <= radii*radii) { // now you compare a distance^2 to a distance^4
return true; // collision
}
It should be either this:
double c2 = (a*a)+(b*b); // actual hypotenuse squared
double r2 = pow(radius+theirRadius, 2.f); // critical hypotenuse squared
if(c2 <= r2) {
return true; // collision
}
or this:
double c2 = (a*a)+(b*b); // actual hypotenuse squared
double c = pow(c2, 0.5); // actual hypotenuse
double r = radius + theirRadius; // critical hypotenuse
if(c <= r) {
return true; // collision
}

Your inner loop needs to start at mObjectList.begin() instead of iter.
The inner loop needs to iterate over the entire list otherwise you miss collision candidates the further you progress in the outer loop.

Related

C++ / SDL2 - Ball bouncing/glitching together

I was trying to write some ball bouncing program in C++ using SDL2. I had a hard time getting the velocity exchange correct, but it works pretty neat so far. The only problem I have right now is that the balls are sometimes glitching/stucking together and after some seconds they release themself again.
That is my update() function which gets called every frame:
void Game::update() {
updateFPS();
checkBallCollision();
updateCanCollide();
int newtime = SDL_GetTicks();
int diff = newtime - lasttime;
if (diff > 10)
diff = 10;
for (Ball *ball : balls) {
ball->x = ball->x + ball->velocity->x * (float) diff / 100;
ball->y = ball->y + ball->velocity->y * (float) diff / 100;
checkBorderCollision(ball);
}
lasttime = newtime;
}
I guess that the balls are getting to close and don't bounce at the border of the balls. Therefore I tried to give every ball a boolean canCollide which is always true except a ball is colliding. Then it stays false until the two balls aren't overlapping anymore.
Here are my checkBallCollision() and updateCanCollide() functions:`
void Game::updateCanCollide() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (ballArr[i]->canCollide)
continue;
bool updatedCollide = true;
for (int k = i + 1; k < length; k++) {
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
updatedCollide = false;
}
}
ballArr[i]->canCollide = updatedCollide;
}
}
// do all collision checks and update the velocity
void Game::checkBallCollision() {
Ball **ballArr = &balls[0];
int length = balls.size();
for (int i = 0; i < length; i++) {
if (!ballArr[i]->canCollide)
continue;
for (int k = i + 1; k < length; k++) {
if (!ballArr[k]->canCollide)
continue;
Ball *ball1 = ballArr[i];
Ball *ball2 = ballArr[k];
int xdiff = abs(ball1->x - ball2->x);
int ydiff = abs(ball1->y - ball2->y);
float distance = sqrt(xdiff * xdiff + ydiff * ydiff);
if (distance <= ball1->radius + ball2->radius) {
// ball1 and ball2 are colliding
// update the velocity of both balls
float m1 = ball1->radius * ball1->radius * 3.14159;
float m2 = ball2->radius * ball2->radius * 3.14159;
Vector2D *v1 = new Vector2D(ball1->velocity->x, ball1->velocity->x);
Vector2D *v2 = new Vector2D(ball2->velocity->x, ball2->velocity->x);
ball1->velocity->x = ((v1->x * (m1 - m2) + 2 * m2 * v2->x) / (m1 + m2));
ball1->velocity->y = ((v1->y * (m1 - m2) + 2 * m2 * v2->y) / (m1 + m2));
ball2->velocity->x = ((v2->x * (m2 - m1) + 2 * m1 * v1->x) / (m1 + m2));
ball2->velocity->y = ((v2->y * (m2 - m1) + 2 * m1 * v1->y) / (m1 + m2));
ball1->canCollide = false;
ball2->canCollide = false;
}
}
}
}
The proper fix
The main problem is that you are letting the balls overlap each other, then update their velocities. However, if the next time step is shorter than the previous one, it can be that after updating their positions, they are still overlapping. Then you think they are colliding again, and update their velocities, but this will most likely cause then to move closer together again. This explains why they get stuck.
The proper wait to solve this is to calculate the exact point in time that two moving balls collide. This can be done analytically, for example by treating time as a third dimension, and then calculating a line-sphere intersection. If this happens during the time step, you advance the time up to the point that the collision happens, then update the velocities, and then perform the rest of the step. If you have more than two balls, then be aware that you can have more than two balls colliding all with each other in the same timestep. This is also solvable, just calculate all the time points that collisions happen, select the earliest one, update velocities at that point, and then recalculate the collision times, and so on until there are no collisions in the time step.
The workaround
Your workaround might fix two balls sticking to each other, but the result is not physically accurate. It breaks down when you start increasing the density of balls, since at some point the chance will be very high that at least one ball of a pair that should collide was in a collision in the previous timestep, and then they will all just start passing through each other all the time.
Another issue is that you have to check every possible pair of balls in updateCanCollide(), which is not efficient. There is a simpler and more common workaround to this problem: when two balls collide, after updating their velocities, immediately update their positions as well such that the balls are no longer colliding. You can try to calculate exactly how much to move them so they no longer overlap, or if you don't want to involve mathematics, you can just have a while loop to do a small step until they no longer overlap.
Other issues in your code
Note that there are also some other thing in your code that you could improve:
Don't new a temporary Vector2D, just declare it on the stack. If for some reason this is not possible, at least delete v1 and v2 afterwards.
You don't need to call abs() if you are going to square the result anyway.
Use std::hypot() to calculate the distance.
Did you write Vector2D yourself or is it from a library? If the latter, maybe it already has functions to reflect two 2D vectors? If the former, consider using a library like GLM, even if you are not using OpenGL.
Use a proper value of π. A simple, portable solution is to declare static constexpr pi = std::atan(1) * 4.

Pre-Collision Object Staging

I am making a billiards game. Currently, when one ball collides with another at high speed, the collision is not always calculated correctly. I know what the issue is, but I'm not 100% sure how to fix it.
Say two balls are traveling with these velocities:
More often than not, when the collision is detected, the balls will have some overlap between them that looks like this:
Currently, my physics engine will handle the collision at this moment in time. This will not give the desired result since this is NOT where the balls collide in reality - balls don't go through one another. So, we need back up the balls to where they really collide. That would look like this:
I am looking for an efficient algorithm that would help me do this. Currently, I have a very naive and inefficient method - I move both balls to their locations just before the collision and take very small steps toward the moment of collision. Of course, this is very inefficient. Here is what it looks like:
void CBallCollision::StageCollision()
{
double sumOfRadii = mBall1->GetRadius() + mBall2->GetRadius();
mBall1->SetCenter(mBall1->GetLastLocationOnTable().first, mBall1->GetLastLocationOnTable().second);
mBall2->SetCenter(mBall2->GetLastLocationOnTable().first, mBall2->GetLastLocationOnTable().second);
double timeStep = 0.008;
double tolerance = 0.1 * min(mBall1->GetRadius(), mBall2->GetRadius());
int iter = 0;
while (GetDistance() > sumOfRadii)
{
double xGoal1 = mBall1->GetX() + mBall1->GetVelocityX() * timeStep;
double yGoal1 = mBall1->GetY() + mBall1->GetVelocityY() * timeStep;
pair<double, double> newCoords1 = mBall1->LinearInterpolate(xGoal1, yGoal1);
double xGoal2 = mBall2->GetX() + mBall2->GetVelocityX() * timeStep;
double yGoal2 = mBall2->GetY() + mBall2->GetVelocityY() * timeStep;
pair<double, double> newCoords2 = mBall2->LinearInterpolate(xGoal2, yGoal2);
double dist = (pow(newCoords1.first - newCoords2.first, 2) + pow(newCoords1.second - newCoords2.second, 2));
if (abs(dist - sumOfRadii) > tolerance)
{
timeStep *= 0.5;
}
else
{
mBall1->SetX(newCoords1.first);
mBall1->SetY(newCoords1.second);
mBall2->SetX(newCoords2.first);
mBall2->SetY(newCoords2.second);
}
iter++;
if (iter > 1000)
{
break;
}
}
}
If I don't put an upper bound on the number of iterations, the program crashes. I'm sure there is a much more efficient way of going about this. Any help is appreciated.

How to Implement Collision Detection

I'm creating a program which simulates a robot moving around a map. I have an environment class which holds the robot and obstacles the robot could run into. At the moment I have class objects for my robot, and obstacles and I have a function which tells me if they collide (returns true/false). I am just not sure how to put it into the movement function for the robot.
The robot is a square and has a center point (x,y) a width, length and some orientation in degrees (fyi, the environment class is a friend of the robot class). The obstacles are circles with a center point (x,y) and a radius.
class Environment{
Robot robot;
vector<Obstacle> obstacles;
//random obstacle generation function
bool collision_circle(Obstacle obstacle) {
//Check if the circle intersects any of the corners of the robot
std::vector<Point> points;
points.push_back(robot.top_right);
points.push_back(robot.top_left);
points.push_back(robot.bottom_right);
points.push_back(robot.bottom_left);
Point obst_center(obstacle.return_x(), obstacle.return_y());
for (int i = 0; i < points.size(); i++) {
points[i].set_distance(obst_center);
if (points[i].distance <= obstacle.return_radius()) { return true; }
}
//Sort the points by distance away from the obstacle
std::sort(points.begin(), points.end(), less_than());
//Use the two closest to the obstacle to create a line
double m = (points[0].x - points[1].x) / (points[0].y -
points[1].y);
double b = points[0].y - (m * points[0].x);
//Determine a line perpendicular which intersects the obstacle's
center
double m_perp = 1 / m;
double b_perp = obst_center.y - (m * obst_center.x);
Point on Robot closest to obstacle
double new_x = (b - b_perp) / (m_perp - m);
double new_y = m_perp * new_x + b_perp;
distance between points
double diff_x = obst_center.x - new_x;
double diff_y = obst_center.y - new_y;
double distance = sqrt(pow(diff_x, 2) + pow(diff_y, 2));
if (distance <= obstacle.return_radius()) { return true; }
else { return false; }
}
Environment(Robot& t_robot): robot(t_robot) {}
void forward(double num_inches){
robot.y += num_inches * sin(robot.orientation * convert_deg);
//Convert_deg is a global variable = PI/180
robot.x += num_inches * cos(robot.orientation * convert_deg);
}
//void backward, left, right, etc.
}
I tried having the forward function check for intersection with each obstacle (up to 15 on the map) after a certain increment of distance, but that froze up my program or would have required thousands of calculations for each inch covered. Am I even on the right track in how to execute this? I am also using SFML for graphics, but, from what I know, it only supports bounding box collision detection. Also, I want the graphics to be something secondary to the program. I am writing this so that I can create and test an program for the robot's movement and would eventually like to just run the sample and be told if it worked and watch the replay if I want.

Help with Inverse Kinematics Algorithm

I'm trying to implement CCD Inverse Kinematics in 2D
This function is supposed to do 1 iteration of CCD
Right now as a test case I start it on a left foot and have it stop at the pelvis.
every time this function is called, the skeleton's bones are updated.
The way my bones work is:
getFrameX,Y,Angle return the absolute positions of the end of the bone / effector. These are updated every iteraton of CCD.
getAngle,X, Y returns the relative values.
Same for setters.
Right now it never stays in one spot, every time I giggle the mouse a bit it moves the bones randomly counterclockwise.
I was wondering if there was something bluntly obviously wrong that could point me in the right direction for debugging.
void inverseKinematics(float targetX, float targetY, skl::Bone* targetBone)
{
std::string stopBone = "Pelvis";
//===
// Track the end effector position (the final bone)
double endX = targetBone->getFrameX();
double endY = targetBone->getFrameY();
//===
// Perform CCD on the bones by optimizing each bone in a loop
// from the final bone to the root bone
bool modifiedBones = false;
targetBone = targetBone->getParent();
while(targetBone->getName() != stopBone)
{
// Get the vector from the current bone to the end effector position.
double curToEndX = endX - targetBone->getFrameX();
double curToEndY = endY - targetBone->getFrameY();
double curToEndMag = sqrt( curToEndX*curToEndX + curToEndY*curToEndY );
// Get the vector from the current bone to the target position.
double curToTargetX = targetX - targetBone->getFrameX();
double curToTargetY = targetY - targetBone->getFrameY();
double curToTargetMag = sqrt( curToTargetX*curToTargetX
+ curToTargetY*curToTargetY );
// Get rotation to place the end effector on the line from the current
// joint position to the target position.
double cosRotAng;
double sinRotAng;
double endTargetMag = (curToEndMag*curToTargetMag);
if( endTargetMag <= 0.1f )
{
cosRotAng = 1.0f;
sinRotAng = 0.0f;
}
else
{
cosRotAng = (curToEndX*curToTargetX + curToEndY*curToTargetY) / endTargetMag;
sinRotAng = (curToEndX*curToTargetY - curToEndY*curToTargetX) / endTargetMag;
}
// Clamp the cosine into range when computing the angle (might be out of range
// due to floating point error).
double rotAng = acosf( max(-1.0f, min(1.0f,cosRotAng) ) );
if( sinRotAng < 0.0f )
rotAng = -rotAng;
// Rotate the end effector position.
endX = targetBone->getFrameX() + cosRotAng*curToEndX - sinRotAng*curToEndY;
endY = targetBone->getFrameY() + sinRotAng*curToEndX + cosRotAng*curToEndY;
// Rotate the current bone in local space (this value is output to the user)
targetBone->setAngle(SimplifyAngle(targetBone->getAngle() + rotAng));
// Check for termination
double endToTargetX = (targetX-endX);
double endToTargetY = (targetY-endY);
if( endToTargetX*endToTargetX + endToTargetY*endToTargetY <= 1.0f )
{
// We found a valid solution.
return;
}
// Track if the arc length that we moved the end effector was
// a nontrivial distance.
if( !modifiedBones && fabs(rotAng)*curToEndMag > 0.0001f )
{
modifiedBones = true;
}
targetBone = targetBone->getParent();
}
Thanks
No, there is nothing obviously wrong in the program listing you have given. You are correctly computing the change of angle rotAng and the new position (endX, endY) of the end-effector.
You can compute rotAng more simply as
double rotAng =
atan2(curToTargetY, curToTargetX) - atan2(curToEndY, curToEndX);
which gives identical results (assuming the vectors are non-zero).
I suspect the error is somewhere outside of the program listing you have given. Maybe there is a discrepancy between the forward kinematics assumed in inverseKinematics() and the actual forward kinematics used in the display routines and elsewhere. Try recomputing the forward kinematics at the end of the procedure to see if the rest of the system agrees that the end-effector is at (endX, endY).

Trying to optimize line vs cylinder intersection

My brain has been melting over a line segment-vs-cylinder intersection routine I've been working on.
/// Line segment VS <cylinder>
// - cylinder (A, B, r) (start point, end point, radius)
// - line has starting point (x0, y0, z0) and ending point (x0+ux, y0+uy, z0+uz) ((ux, uy, uz) is "direction")
// => start = (x0, y0, z0)
// dir = (ux, uy, uz)
// A
// B
// r
// optimize? (= don't care for t > 1)
// <= t = "time" of intersection
// norm = surface normal of intersection point
void CollisionExecuter::cylinderVSline(const Ogre::Vector3& start, const Ogre::Vector3& dir, const Ogre::Vector3& A, const Ogre::Vector3& B, const double r,
const bool optimize, double& t, Ogre::Vector3& normal) {
t = NaN;
// Solution : http://www.gamedev.net/community/forums/topic.asp?topic_id=467789
double cxmin, cymin, czmin, cxmax, cymax, czmax;
if (A.z < B.z) { czmin = A.z - r; czmax = B.z + r; } else { czmin = B.z - r; czmax = A.z + r; }
if (A.y < B.y) { cymin = A.y - r; cymax = B.y + r; } else { cymin = B.y - r; cymax = A.y + r; }
if (A.x < B.x) { cxmin = A.x - r; cxmax = B.x + r; } else { cxmin = B.x - r; cxmax = A.x + r; }
if (optimize) {
if (start.z >= czmax && (start.z + dir.z) > czmax) return;
if (start.z <= czmin && (start.z + dir.z) < czmin) return;
if (start.y >= cymax && (start.y + dir.y) > cymax) return;
if (start.y <= cymin && (start.y + dir.y) < cymin) return;
if (start.x >= cxmax && (start.x + dir.x) > cxmax) return;
if (start.x <= cxmin && (start.x + dir.x) < cxmin) return;
}
Ogre::Vector3 AB = B - A;
Ogre::Vector3 AO = start - A;
Ogre::Vector3 AOxAB = AO.crossProduct(AB);
Ogre::Vector3 VxAB = dir.crossProduct(AB);
double ab2 = AB.dotProduct(AB);
double a = VxAB.dotProduct(VxAB);
double b = 2 * VxAB.dotProduct(AOxAB);
double c = AOxAB.dotProduct(AOxAB) - (r*r * ab2);
double d = b * b - 4 * a * c;
if (d < 0) return;
double time = (-b - sqrt(d)) / (2 * a);
if (time < 0) return;
Ogre::Vector3 intersection = start + dir * time; /// intersection point
Ogre::Vector3 projection = A + (AB.dotProduct(intersection - A) / ab2) * AB; /// intersection projected onto cylinder axis
if ((projection - A).length() + (B - projection).length() > AB.length()) return; /// THIS IS THE SLOW SAFE WAY
//if (projection.z > czmax - r || projection.z < czmin + r ||
// projection.y > cymax - r || projection.y < cymin + r ||
// projection.x > cxmax - r || projection.x < cxmin + r ) return; /// THIS IS THE FASTER BUGGY WAY
normal = (intersection - projection);
normal.normalise();
t = time; /// at last
}
I have thought of this way to speed up the discovery of whether the projection of the intersection point lies inside the cylinder's length. However, it doesn't work and I can't really get it because it seems so logical :
if the projected point's x, y or z co-ordinates are not within the cylinder's limits, it should be considered outside. It seems though that this doesn't work in practice.
Any help would be greatly appreciated!
Cheers,
Bill Kotsias
Edit : It seems that the problems rise with boundary-cases, i.e when the cylinder is parallel to one of the axis. Rounding errors come into the equation and the "optimization" stops working correctly.
Maybe, if the logic is correct, the problems will go away by inserting a bit of tolerance like :
if (projection.z > czmax - r + 0.001 || projection.z < czmin + r - 0.001 || ... etc...
The cylinder is circular, right? You could transform coordinates so that the center line of the cylinder functions as the Z axis. Then you have a 2D problem of intersecting a line with a circle. The intersection points will be in terms of a parameter going from 0 to 1 along the length of the line, so you can calculate their positions in that coordinate system and compare to the top and bottom of the cylinder.
You should be able to do it all in closed form. No tolerances. And sure, you will get singularities and imaginary solutions. You seem to have thought of all this, so I guess I'm not sure what the question is.
This is what I use, it may help:
bool d3RayCylinderIntersection(const DCylinder &cylinder,const DVector3 &org,const DVector3 &dir,float &lambda,DVector3 &normal,DVector3 &newPosition)
// Ray and cylinder intersection
// If hit, returns true and the intersection point in 'newPosition' with a normal and distance along
// the ray ('lambda')
{
DVector3 RC;
float d;
float t,s;
DVector3 n,D,O;
float ln;
float in,out;
RC=org; RC.Subtract(&cylinder.position);
n.Cross(&dir,&cylinder.axis);
ln=n.Length();
// Parallel? (?)
if((ln<D3_EPSILON)&&(ln>-D3_EPSILON))
return false;
n.Normalize();
d=fabs(RC.Dot(n));
if (d<=cylinder.radius)
{
O.Cross(&RC,&cylinder.axis);
//TVector::cross(RC,cylinder._Axis,O);
t=-O.Dot(n)/ln;
//TVector::cross(n,cylinder._Axis,O);
O.Cross(&n,&cylinder.axis);
O.Normalize();
s=fabs( sqrtf(cylinder.radius*cylinder.radius-d*d) / dir.Dot(O) );
in=t-s;
out=t+s;
if (in<-D3_EPSILON)
{
if(out<-D3_EPSILON)
return false;
else lambda=out;
} else if(out<-D3_EPSILON)
{
lambda=in;
} else if(in<out)
{
lambda=in;
} else
{
lambda=out;
}
// Calculate intersection point
newPosition=org;
newPosition.x+=dir.x*lambda;
newPosition.y+=dir.y*lambda;
newPosition.z+=dir.z*lambda;
DVector3 HB;
HB=newPosition;
HB.Subtract(&cylinder.position);
float scale=HB.Dot(&cylinder.axis);
normal.x=HB.x-cylinder.axis.x*scale;
normal.y=HB.y-cylinder.axis.y*scale;
normal.z=HB.z-cylinder.axis.z*scale;
normal.Normalize();
return true;
}
return false;
}
Have you thought about it this way?
A cylinder is essentially a "fat" line segment so a way to do this would be to find the closest point on line segment (the cylinder's center line) to line segment (the line segment you are testing for intersection).
From there, you check the distance between this closest point and the other line segment, and compare it to the radius.
At this point, you have a "Pill vs Line Segment" test, but you could probably do some plane tests to "chop off" the caps on the pill to make a cylinder.
Shooting from the hip a bit though so hope it helps (:
Mike's answer is good. For any tricky shape you're best off finding the transformation matrix T that maps it into a nice upright version, in this case an outright cylinder with radius 1. height 1, would do the job nicely. Figure out your new line in this new space, perform the calculation, convert back.
However, if you are looking to optimise (and it sounds like you are), there is probably loads you can do.
For example, you can calculate the shortest distance between two lines -- probably using the dot product rule -- imagine joining two lines by a thread. Then if this thread is the shortest of all possible threads, then it will be perpendicular to both lines, so Thread.LineA = Thread.LineB = 0
If the shortest distance is greater than the radius of the cylinder, it is a miss.
You could define the locus of the cylinder using x,y,z, and thrash the whole thing out as some horrible quadratic equation, and optimise by calculating the discriminant first, and returning no-hit if this is negative.
To define the locus, take any point P=(x,y,z). drop it as a perpendicular on to the centre line of your cylinder, and look at its magnitude squared. if that equals R^2 that point is in.
Then you throw your line {s = U + lamda*V} into that mess, and you would end up with some butt ugly quadratic in lamda. but that would probably be faster than fiddling matrices, unless you can get the hardware to do it (I'm guessing OpenGL has some function to get the hardware to do this superfast).
It all depends on how much optimisation you want; personally I would go with Mike's answer unless there was a really good reason not to.
PS You might get more help if you explain the technique you use rather than just dumping code, leaving it to the reader to figure out what you're doing.