所以我们得到: t2 * (d DOT d) + 2t( f DOT d ) + ( f DOT f - r2 ) = 0* 所以求解二次方程:
float a = d.Dot( d ) ;
float b = 2*f.Dot( d ) ;
float c = f.Dot( f ) - r*r ;
float discriminant = b*b-4*a*c;
if( discriminant < 0 )
{
// no intersection
}
else
{
// ray didn't totally miss sphere,
// so there is a solution to
// the equation.
discriminant = sqrt( discriminant );
// either solution may be on or off the ray so need to test both
// t1 is always the smaller value, because BOTH discriminant and
// a are nonnegative.
float t1 = (-b - discriminant)/(2*a);
float t2 = (-b + discriminant)/(2*a);
// 3x HIT cases:
// -o-> --|--> | | --|->
// Impale(t1 hit,t2 hit), Poke(t1 hit,t2>1), ExitWound(t1<0, t2 hit),
// 3x MISS cases:
// -> o o -> | -> |
// FallShort (t1>1,t2>1), Past (t1<0,t2<0), CompletelyInside(t1<0, t2>1)
if( t1 >= 0 && t1 <= 1 )
{
// t1 is the intersection, and it's closer than t2
// (since t1 uses -b - discriminant)
// Impale, Poke
return true ;
}
// here t1 didn't intersect so we are either started
// inside the sphere or completely past it
if( t2 >= 0 && t2 <= 1 )
{
// ExitWound
return true ;
}
// no intn: FallShort, Past, CompletelyInside
return false ;
}
181
似乎没有人考虑投影,我完全偏离这里吗?
将矢量 AC 投影到 AB . 投影向量 AD 给出新点 D . 如果 D 和 C 之间的距离小于(或等于) R ,则我们有一个交点 .
// compute the euclidean distance between A and B
LAB = sqrt( (Bx-Ax)²+(By-Ay)² )
// compute the direction vector D from A to B
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB
// the equation of the line AB is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= LAB.
// compute the distance between the points A and E, where
// E is the point of AB closest the circle center (Cx, Cy)
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)
// compute the coordinates of the point E
Ex = t*Dx+Ax
Ey = t*Dy+Ay
// compute the euclidean distance between E and C
LEC = sqrt((Ex-Cx)²+(Ey-Cy)²)
// test if the line intersects the circle
if( LEC < R )
{
// compute distance from t to circle intersection point
dt = sqrt( R² - LEC²)
// compute first intersection point
Fx = (t-dt)*Dx + Ax
Fy = (t-dt)*Dy + Ay
// compute second intersection point
Gx = (t+dt)*Dx + Ax
Gy = (t+dt)*Dy + Ay
}
// else test if the line is tangent to circle
else if( LEC == R )
// tangent point to circle is E
else
// line doesn't touch circle
// compute the triangle area times 2 (area = area2/2)
area2 = abs( (Bx-Ax)*(Cy-Ay) - (Cx-Ax)(By-Ay) )
// compute the AB segment length
LAB = sqrt( (Bx-Ax)² + (By-Ay)² )
// compute the triangle height
h = area2/LAB
// if the line intersects the circle
if( h < R )
{
...
}
// compute the line AB direction vector components
Dx = (Bx-Ax)/LAB
Dy = (By-Ay)/LAB
// compute the distance from A toward B of closest point to C
t = Dx*(Cx-Ax) + Dy*(Cy-Ay)
// t should be equal to sqrt( (Cx-Ax)² + (Cy-Ay)² - h² )
// compute the intersection point distance from t
dt = sqrt( R² - h² )
// compute first intersection point coordinate
Ex = Ax + (t-dt)*Dx
Ey = Ay + (t-dt)*Dy
// compute second intersection point coordinate
Fx = Ax + (t+dt)*Dx
Fy = Ay + (t+dt)*Dy
p1 and p2 as the points for the line, and
c as the center point for the circle and r for the radius
我将以斜率截距形式求解线的方程 . 但是,我不想用 c 作为一个点处理困难的方程式,所以我只是将坐标系移动到圆形位于 0,0
p3 = p1 - c
p4 = p2 - c
顺便说一下,每当我从彼此减去点数时,我减去 x 然后减去 y 's, and putting them into a new point, just in case someone didn't知道 .
无论如何,我现在用 p3 和 p4 求解线的等式:
m = (p4_y - p3_y) / (p4_x - p3) (the underscore is an attempt at subscript)
y = mx + b
y - mx = b (just put in a point for x and y, and insert the m we found)
然后只需将这两个方程的结果插入 mx + b 中的 x 即可 . 为清楚起见,我写了一些JavaScript代码来说明如何使用它:
function interceptOnCircle(p1,p2,c,r){
//p1 is the first line point
//p2 is the second line point
//c is the circle's center
//r is the circle's radius
var p3 = {x:p1.x - c.x, y:p1.y - c.y} //shifted line points
var p4 = {x:p2.x - c.x, y:p2.y - c.y}
var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
var b = p3.y - m * p3.x; //y-intercept of line
var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2)); //the value under the square root sign
if (underRadical < 0){
//line completely missed
return false;
} else {
var t1 = (-2*m*b+2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //one of the intercept x's
var t2 = (-2*m*b-2*Math.sqrt(underRadical))/(2 * Math.pow(m,2) + 2); //other intercept's x
var i1 = {x:t1,y:m*t1+b} //intercept point 1
var i2 = {x:t2,y:m*t2+b} //intercept point 2
return [i1,i2];
}
}
{
dX = bX - aX;
dY = bY - aY;
if ((dX == 0) && (dY == 0))
{
// A and B are the same points, no way to calculate intersection
return;
}
dl = (dX * dX + dY * dY);
t = ((cX - aX) * dX + (cY - aY) * dY) / dl;
// point on a line nearest to circle center
nearestX = aX + t * dX;
nearestY = aY + t * dY;
dist = point_dist(nearestX, nearestY, cX, cY);
if (dist == R)
{
// line segment touches circle; one intersection point
iX = nearestX;
iY = nearestY;
if (t < 0 || t > 1)
{
// intersection point is not actually within line segment
}
}
else if (dist < R)
{
// two possible intersection points
dt = sqrt(R * R - dist * dist) / sqrt(dl);
// intersection point nearest to A
t1 = t - dt;
i1X = aX + t1 * dX;
i1Y = aY + t1 * dY;
if (t1 < 0 || t1 > 1)
{
// intersection point is not actually within line segment
}
// intersection point farthest from A
t2 = t + dt;
i2X = aX + t2 * dX;
i2Y = aY + t2 * dY;
if (t2 < 0 || t2 > 1)
{
// intersection point is not actually within line segment
}
}
else
{
// no intersection
}
}
var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2));
但:
var underRadical = Math.pow(r,2)*(Math.pow(m,2)+1)) - Math.pow(b,2);
在最后的坐标中,他忘记了将解决方案转移回去 . 所以不是:
var i1 = {x:t1,y:m*t1+b}
但:
var i1 = {x:t1+c.x, y:m*t1+b+c.y};
整个功能变为:
function interceptOnCircle(p1, p2, c, r) {
//p1 is the first line point
//p2 is the second line point
//c is the circle's center
//r is the circle's radius
var p3 = {x:p1.x - c.x, y:p1.y - c.y}; //shifted line points
var p4 = {x:p2.x - c.x, y:p2.y - c.y};
var m = (p4.y - p3.y) / (p4.x - p3.x); //slope of the line
var b = p3.y - m * p3.x; //y-intercept of line
var underRadical = Math.pow(r,2)*Math.pow(m,2) + Math.pow(r,2) - Math.pow(b,2); //the value under the square root sign
if (underRadical < 0) {
//line completely missed
return false;
} else {
var t1 = (-m*b + Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //one of the intercept x's
var t2 = (-m*b - Math.sqrt(underRadical))/(Math.pow(m,2) + 1); //other intercept's x
var i1 = {x:t1+c.x, y:m*t1+b+c.y}; //intercept point 1
var i2 = {x:t2+c.x, y:m*t2+b+c.y}; //intercept point 2
return [i1, i2];
}
}
1
你需要一些数学:
假设A =(Xa,Ya),B =(Xb,Yb)和C =(Xc,Yc) . 从A到B的线上的任何点都有坐标(alpha * Xa(1-alpha)Xb,alphaYa(1-alpha)* Yb)= P
// Small epsilon value
var EPS = 0.0000001;
// point (x, y)
function Point(x, y) {
this.x = x;
this.y = y;
}
// Circle with center at (x,y) and radius r
function Circle(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
}
// A line segment (x1, y1), (x2, y2)
function LineSegment(x1, y1, x2, y2) {
var d = Math.sqrt( (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) );
if (d < EPS) throw 'A point is not a line segment';
this.x1 = x1; this.y1 = y1;
this.x2 = x2; this.y2 = y2;
}
// An infinite line defined as: ax + by = c
function Line(a, b, c) {
this.a = a; this.b = b; this.c = c;
// Normalize line for good measure
if (Math.abs(b) < EPS) {
c /= a; a = 1; b = 0;
} else {
a = (Math.abs(a) < EPS) ? 0 : a / b;
c /= b; b = 1;
}
}
// Given a line in standard form: ax + by = c and a circle with
// a center at (x,y) with radius r this method finds the intersection
// of the line and the circle (if any).
function circleLineIntersection(circle, line) {
var a = line.a, b = line.b, c = line.c;
var x = circle.x, y = circle.y, r = circle.r;
// Solve for the variable x with the formulas: ax + by = c (equation of line)
// and (x-X)^2 + (y-Y)^2 = r^2 (equation of circle where X,Y are known) and expand to obtain quadratic:
// (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
// Then use quadratic formula X = (-b +- sqrt(a^2 - 4ac))/2a to find the
// roots of the equation (if they exist) and this will tell us the intersection points
// In general a quadratic is written as: Ax^2 + Bx + C = 0
// (a^2 + b^2)x^2 + (2abY - 2ac + - 2b^2X)x + (b^2X^2 + b^2Y^2 - 2bcY + c^2 - b^2r^2) = 0
var A = a*a + b*b;
var B = 2*a*b*y - 2*a*c - 2*b*b*x;
var C = b*b*x*x + b*b*y*y - 2*b*c*y + c*c - b*b*r*r;
// Use quadratic formula x = (-b +- sqrt(a^2 - 4ac))/2a to find the
// roots of the equation (if they exist).
var D = B*B - 4*A*C;
var x1,y1,x2,y2;
// Handle vertical line case with b = 0
if (Math.abs(b) < EPS) {
// Line equation is ax + by = c, but b = 0, so x = c/a
x1 = c/a;
// No intersection
if (Math.abs(x-x1) > r) return [];
// Vertical line is tangent to circle
if (Math.abs((x1-r)-x) < EPS || Math.abs((x1+r)-x) < EPS)
return [new Point(x1, y)];
var dx = Math.abs(x1 - x);
var dy = Math.sqrt(r*r-dx*dx);
// Vertical line cuts through circle
return [
new Point(x1,y+dy),
new Point(x1,y-dy)
];
// Line is tangent to circle
} else if (Math.abs(D) < EPS) {
x1 = -B/(2*A);
y1 = (c - a*x1)/b;
return [new Point(x1,y1)];
// No intersection
} else if (D < 0) {
return [];
} else {
D = Math.sqrt(D);
x1 = (-B+D)/(2*A);
y1 = (c - a*x1)/b;
x2 = (-B-D)/(2*A);
y2 = (c - a*x2)/b;
return [
new Point(x1, y1),
new Point(x2, y2)
];
}
}
// Converts a line segment to a line in general form
function segmentToGeneralForm(x1,y1,x2,y2) {
var a = y1 - y2;
var b = x2 - x1;
var c = x2*y1 - x1*y2;
return new Line(a,b,c);
}
// Checks if a point 'pt' is inside the rect defined by (x1,y1), (x2,y2)
function pointInRectangle(pt,x1,y1,x2,y2) {
var x = Math.min(x1,x2), X = Math.max(x1,x2);
var y = Math.min(y1,y2), Y = Math.max(y1,y2);
return x - EPS <= pt.x && pt.x <= X + EPS &&
y - EPS <= pt.y && pt.y <= Y + EPS;
}
// Finds the intersection(s) of a line segment and a circle
function lineSegmentCircleIntersection(segment, circle) {
var x1 = segment.x1, y1 = segment.y1, x2 = segment.x2, y2 = segment.y2;
var line = segmentToGeneralForm(x1,y1,x2,y2);
var pts = circleLineIntersection(circle, line);
// No intersection
if (pts.length === 0) return [];
var pt1 = pts[0];
var includePt1 = pointInRectangle(pt1,x1,y1,x2,y2);
// Check for unique intersection
if (pts.length === 1) {
if (includePt1) return [pt1];
return [];
}
var pt2 = pts[1];
var includePt2 = pointInRectangle(pt2,x1,y1,x2,y2);
// Check for remaining intersections
if (includePt1 && includePt2) return [pt1, pt2];
if (includePt1) return [pt1];
if (includePt2) return [pt2];
return [];
}
/// <summary>
/// Intersects a line and a circle.
/// </summary>
/// <param name="location">the location of the circle</param>
/// <param name="radius">the radius of the circle</param>
/// <param name="lineFrom">the starting point of the line</param>
/// <param name="lineTo">the ending point of the line</param>
/// <returns>true if the line and circle intersect each other</returns>
public static bool IntersectLineCircle(Vector2 location, float radius, Vector2 lineFrom, Vector2 lineTo)
{
float ab2, acab, h2;
Vector2 ac = location - lineFrom;
Vector2 ab = lineTo - lineFrom;
Vector2.Dot(ref ab, ref ab, out ab2);
Vector2.Dot(ref ac, ref ab, out acab);
float t = acab / ab2;
if (t < 0)
t = 0;
else if (t > 1)
t = 1;
Vector2 h = ((ab * t) + lineFrom) - location;
Vector2.Dot(ref h, ref h, out h2);
return (h2 <= (radius * radius));
}
1
' VB.NET - Code
Function CheckLineSegmentCircleIntersection(x1 As Double, y1 As Double, x2 As Double, y2 As Double, xc As Double, yc As Double, r As Double) As Boolean
Static xd As Double = 0.0F
Static yd As Double = 0.0F
Static t As Double = 0.0F
Static d As Double = 0.0F
Static dx_2_1 As Double = 0.0F
Static dy_2_1 As Double = 0.0F
dx_2_1 = x2 - x1
dy_2_1 = y2 - y1
t = ((yc - y1) * dy_2_1 + (xc - x1) * dx_2_1) / (dy_2_1 * dy_2_1 + dx_2_1 * dx_2_1)
If 0 <= t And t <= 1 Then
xd = x1 + t * dx_2_1
yd = y1 + t * dy_2_1
d = Math.Sqrt((xd - xc) * (xd - xc) + (yd - yc) * (yd - yc))
Return d <= r
Else
d = Math.Sqrt((xc - x1) * (xc - x1) + (yc - y1) * (yc - y1))
If d <= r Then
Return True
Else
d = Math.Sqrt((xc - x2) * (xc - x2) + (yc - y2) * (yc - y2))
If d <= r Then
Return True
Else
Return False
End If
End If
End If
End Function
1
我按照 chmike 给出的答案为iOS创建了这个函数
+ (NSArray *)intersectionPointsOfCircleWithCenter:(CGPoint)center withRadius:(float)radius toLinePoint1:(CGPoint)p1 andLinePoint2:(CGPoint)p2
{
NSMutableArray *intersectionPoints = [NSMutableArray array];
float Ax = p1.x;
float Ay = p1.y;
float Bx = p2.x;
float By = p2.y;
float Cx = center.x;
float Cy = center.y;
float R = radius;
// compute the euclidean distance between A and B
float LAB = sqrt( pow(Bx-Ax, 2)+pow(By-Ay, 2) );
// compute the direction vector D from A to B
float Dx = (Bx-Ax)/LAB;
float Dy = (By-Ay)/LAB;
// Now the line equation is x = Dx*t + Ax, y = Dy*t + Ay with 0 <= t <= 1.
// compute the value t of the closest point to the circle center (Cx, Cy)
float t = Dx*(Cx-Ax) + Dy*(Cy-Ay);
// This is the projection of C on the line from A to B.
// compute the coordinates of the point E on line and closest to C
float Ex = t*Dx+Ax;
float Ey = t*Dy+Ay;
// compute the euclidean distance from E to C
float LEC = sqrt( pow(Ex-Cx, 2)+ pow(Ey-Cy, 2) );
// test if the line intersects the circle
if( LEC < R )
{
// compute distance from t to circle intersection point
float dt = sqrt( pow(R, 2) - pow(LEC,2) );
// compute first intersection point
float Fx = (t-dt)*Dx + Ax;
float Fy = (t-dt)*Dy + Ay;
// compute second intersection point
float Gx = (t+dt)*Dx + Ax;
float Gy = (t+dt)*Dy + Ay;
[intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Fx, Fy)]];
[intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Gx, Gy)]];
}
// else test if the line is tangent to circle
else if( LEC == R ) {
// tangent point to circle is E
[intersectionPoints addObject:[NSValue valueWithCGPoint:CGPointMake(Ex, Ey)]];
}
else {
// line doesn't touch circle
}
return intersectionPoints;
}
public static DVec2 CircLine(DVec2 C, double r, Line line)
{
DVec2 A = line.p1;
DVec2 B = line.p2;
DVec2 P;
DVec2 AC = new DVec2( C );
AC.sub(A);
DVec2 AB = new DVec2( B );
AB.sub(A);
double ab2 = AB.dot(AB);
double acab = AC.dot(AB);
double t = acab / ab2;
if (t < 0.0)
t = 0.0;
else if (t > 1.0)
t = 1.0;
//P = A + t * AB;
P = new DVec2( AB );
P.mul( t );
P.add( A );
DVec2 H = new DVec2( P );
H.sub( C );
double h2 = H.dot(H);
double r2 = r * r;
if(h2 > r2)
return null;
else
return P;
}
2
c#中的另一个(部分Circle类) . 经过测试和工作就像一个魅力 .
public class Circle : IEquatable<Circle>
{
// ******************************************************************
// The center of a circle
private Point _center;
// The radius of a circle
private double _radius;
// ******************************************************************
/// <summary>
/// Find all intersections (0, 1, 2) of the circle with a line defined by its 2 points.
/// Using: http://math.stackexchange.com/questions/228841/how-do-i-calculate-the-intersections-of-a-straight-line-and-a-circle
/// Note: p is the Center.X and q is Center.Y
/// </summary>
/// <param name="linePoint1"></param>
/// <param name="linePoint2"></param>
/// <returns></returns>
public List<Point> GetIntersections(Point linePoint1, Point linePoint2)
{
List<Point> intersections = new List<Point>();
double dx = linePoint2.X - linePoint1.X;
if (dx.AboutEquals(0)) // Straight vertical line
{
if (linePoint1.X.AboutEquals(Center.X - Radius) || linePoint1.X.AboutEquals(Center.X + Radius))
{
Point pt = new Point(linePoint1.X, Center.Y);
intersections.Add(pt);
}
else if (linePoint1.X > Center.X - Radius && linePoint1.X < Center.X + Radius)
{
double x = linePoint1.X - Center.X;
Point pt = new Point(linePoint1.X, Center.Y + Math.Sqrt(Radius * Radius - (x * x)));
intersections.Add(pt);
pt = new Point(linePoint1.X, Center.Y - Math.Sqrt(Radius * Radius - (x * x)));
intersections.Add(pt);
}
return intersections;
}
// Line function (y = mx + b)
double dy = linePoint2.Y - linePoint1.Y;
double m = dy / dx;
double b = linePoint1.Y - m * linePoint1.X;
double A = m * m + 1;
double B = 2 * (m * b - m * _center.Y - Center.X);
double C = Center.X * Center.X + Center.Y * Center.Y - Radius * Radius - 2 * b * Center.Y + b * b;
double discriminant = B * B - 4 * A * C;
if (discriminant < 0)
{
return intersections; // there is no intersections
}
if (discriminant.AboutEquals(0)) // Tangeante (touch on 1 point only)
{
double x = -B / (2 * A);
double y = m * x + b;
intersections.Add(new Point(x, y));
}
else // Secant (touch on 2 points)
{
double x = (-B + Math.Sqrt(discriminant)) / (2 * A);
double y = m * x + b;
intersections.Add(new Point(x, y));
x = (-B - Math.Sqrt(discriminant)) / (2 * A);
y = m * x + b;
intersections.Add(new Point(x, y));
}
return intersections;
}
// ******************************************************************
// Get the center
[XmlElement("Center")]
public Point Center
{
get { return _center; }
set
{
_center = value;
}
}
// ******************************************************************
// Get the radius
[XmlElement]
public double Radius
{
get { return _radius; }
set { _radius = value; }
}
//// ******************************************************************
//[XmlArrayItemAttribute("DoublePoint")]
//public List<Point> Coordinates
//{
// get { return _coordinates; }
//}
// ******************************************************************
// Construct a circle without any specification
public Circle()
{
_center.X = 0;
_center.Y = 0;
_radius = 0;
}
// ******************************************************************
// Construct a circle without any specification
public Circle(double radius)
{
_center.X = 0;
_center.Y = 0;
_radius = radius;
}
// ******************************************************************
// Construct a circle with the specified circle
public Circle(Circle circle)
{
_center = circle._center;
_radius = circle._radius;
}
// ******************************************************************
// Construct a circle with the specified center and radius
public Circle(Point center, double radius)
{
_center = center;
_radius = radius;
}
// ******************************************************************
// Construct a circle based on one point
public Circle(Point center)
{
_center = center;
_radius = 0;
}
// ******************************************************************
// Construct a circle based on two points
public Circle(Point p1, Point p2)
{
Circle2Points(p1, p2);
}
需要:
using System;
namespace Mathematic
{
public static class DoubleExtension
{
// ******************************************************************
// Base on Hans Passant Answer on:
// http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre
/// <summary>
/// Compare two double taking in account the double precision potential error.
/// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
public static bool AboutEquals(this double value1, double value2)
{
if (double.IsPositiveInfinity(value1))
return double.IsPositiveInfinity(value2);
if (double.IsNegativeInfinity(value1))
return double.IsNegativeInfinity(value2);
if (double.IsNaN(value1))
return double.IsNaN(value2);
double epsilon = Math.Max(Math.Abs(value1), Math.Abs(value2)) * 1E-15;
return Math.Abs(value1 - value2) <= epsilon;
}
// ******************************************************************
// Base on Hans Passant Answer on:
// http://stackoverflow.com/questions/2411392/double-epsilon-for-equality-greater-than-less-than-less-than-or-equal-to-gre
/// <summary>
/// Compare two double taking in account the double precision potential error.
/// Take care: truncation errors accumulate on calculation. More you do, more you should increase the epsilon.
/// You get really better performance when you can determine the contextual epsilon first.
/// </summary>
/// <param name="value1"></param>
/// <param name="value2"></param>
/// <param name="precalculatedContextualEpsilon"></param>
/// <returns></returns>
public static bool AboutEquals(this double value1, double value2, double precalculatedContextualEpsilon)
{
if (double.IsPositiveInfinity(value1))
return double.IsPositiveInfinity(value2);
if (double.IsNegativeInfinity(value1))
return double.IsNegativeInfinity(value2);
if (double.IsNaN(value1))
return double.IsNaN(value2);
return Math.Abs(value1 - value2) <= precalculatedContextualEpsilon;
}
// ******************************************************************
public static double GetContextualEpsilon(this double biggestPossibleContextualValue)
{
return biggestPossibleContextualValue * 1E-15;
}
// ******************************************************************
/// <summary>
/// Mathlab equivalent
/// </summary>
/// <param name="dividend"></param>
/// <param name="divisor"></param>
/// <returns></returns>
public static double Mod(this double dividend, double divisor)
{
return dividend - System.Math.Floor(dividend / divisor) * divisor;
}
// ******************************************************************
}
}
bool fat_point_collides_segment(const hvec2f& s0qp, const hvec2f& s0s1, const float& rSqr) {
auto a = dot(s0s1, s0s1);
//if( a != 0 ) // if you haven't zero-length segments omit this, as it would save you 1 _mm_comineq_ss() instruction and 1 memory fetch
{
auto b = dot(s0s1, s0qp);
auto t = b / a; // length of projection of s0qp onto s0s1
//std::cout << "t = " << t << "\n";
if ((t >= 0) && (t <= 1)) //
{
auto c = dot(s0qp, s0qp);
auto r2 = c - a * t * t;
return (r2 <= rSqr); // true if collides
}
}
return false;
}
(n-et)(n-et) + (m-dt)(m-dt) = rr
nn - 2etn + etet + mm - 2mdt + dtdt = rr
(ee+dd)tt - 2(en + dm)t + nn + mm - rr = 0
因此A = ee dd,B = -2(en dm),C = nn mm - rr .
这是函数的golang代码:
package geom
import (
"math"
)
// SegmentCircleIntersection return points of intersection between a circle and
// a line segment. The Boolean intersects returns true if one or
// more solutions exist. If only one solution exists,
// x1 == x2 and y1 == y2.
// s1x and s1y are coordinates for one end point of the segment, and
// s2x and s2y are coordinates for the other end of the segment.
// cx and cy are the coordinates of the center of the circle and
// r is the radius of the circle.
func SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r float64) (x1, y1, x2, y2 float64, intersects bool) {
// (n-et) and (m-dt) are expressions for the x and y coordinates
// of a parameterized line in coordinates whose origin is the
// center of the circle.
// When t = 0, (n-et) == s1x - cx and (m-dt) == s1y - cy
// When t = 1, (n-et) == s2x - cx and (m-dt) == s2y - cy.
n := s2x - cx
m := s2y - cy
e := s2x - s1x
d := s2y - s1y
// lineFunc checks if the t parameter is in the segment and if so
// calculates the line point in the unshifted coordinates (adds back
// cx and cy.
lineFunc := func(t float64) (x, y float64, inBounds bool) {
inBounds = t >= 0 && t <= 1 // Check bounds on closed segment
// To check bounds for an open segment use t > 0 && t < 1
if inBounds { // Calc coords for point in segment
x = n - e*t + cx
y = m - d*t + cy
}
return
}
// Since we want the points on the line distance r from the origin,
// (n-et)(n-et) + (m-dt)(m-dt) = rr.
// Expanding and collecting terms yeilds the following quadratic equation:
A, B, C := e*e+d*d, -2*(e*n+m*d), n*n+m*m-r*r
D := B*B - 4*A*C // discriminant of quadratic
if D < 0 {
return // No solution
}
D = math.Sqrt(D)
var p1In, p2In bool
x1, y1, p1In = lineFunc((-B + D) / (2 * A)) // First root
if D == 0.0 {
intersects = p1In
x2, y2 = x1, y1
return // Only possible solution, quadratic has one root.
}
x2, y2, p2In = lineFunc((-B - D) / (2 * A)) // Second root
intersects = p1In || p2In
if p1In == false { // Only x2, y2 may be valid solutions
x1, y1 = x2, y2
} else if p2In == false { // Only x1, y1 are valid solutions
x2, y2 = x1, y1
}
return
}
我用这个函数测试了它,它确认了解决点在线段和圆上 . 它会创建一个测试段并围绕给定的圆圈进行扫描:
package geom_test
import (
"testing"
. "**put your package path here**"
)
func CheckEpsilon(t *testing.T, v, epsilon float64, message string) {
if v > epsilon || v < -epsilon {
t.Error(message, v, epsilon)
t.FailNow()
}
}
func TestSegmentCircleIntersection(t *testing.T) {
epsilon := 1e-10 // Something smallish
x1, y1 := 5.0, 2.0 // segment end point 1
x2, y2 := 50.0, 30.0 // segment end point 2
cx, cy := 100.0, 90.0 // center of circle
r := 80.0
segx, segy := x2-x1, y2-y1
testCntr, solutionCntr := 0, 0
for i := -100; i < 100; i++ {
for j := -100; j < 100; j++ {
testCntr++
s1x, s2x := x1+float64(i), x2+float64(i)
s1y, s2y := y1+float64(j), y2+float64(j)
sc1x, sc1y := s1x-cx, s1y-cy
seg1Inside := sc1x*sc1x+sc1y*sc1y < r*r
sc2x, sc2y := s2x-cx, s2y-cy
seg2Inside := sc2x*sc2x+sc2y*sc2y < r*r
p1x, p1y, p2x, p2y, intersects := SegmentCircleIntersection(s1x, s1y, s2x, s2y, cx, cy, r)
if intersects {
solutionCntr++
//Check if points are on circle
c1x, c1y := p1x-cx, p1y-cy
deltaLen1 := (c1x*c1x + c1y*c1y) - r*r
CheckEpsilon(t, deltaLen1, epsilon, "p1 not on circle")
c2x, c2y := p2x-cx, p2y-cy
deltaLen2 := (c2x*c2x + c2y*c2y) - r*r
CheckEpsilon(t, deltaLen2, epsilon, "p2 not on circle")
// Check if points are on the line through the line segment
// "cross product" of vector from a segment point to the point
// and the vector for the segment should be near zero
vp1x, vp1y := p1x-s1x, p1y-s1y
crossProd1 := vp1x*segy - vp1y*segx
CheckEpsilon(t, crossProd1, epsilon, "p1 not on line ")
vp2x, vp2y := p2x-s1x, p2y-s1y
crossProd2 := vp2x*segy - vp2y*segx
CheckEpsilon(t, crossProd2, epsilon, "p2 not on line ")
// Check if point is between points s1 and s2 on line
// This means the sign of the dot prod of the segment vector
// and point to segment end point vectors are opposite for
// either end.
wp1x, wp1y := p1x-s2x, p1y-s2y
dp1v := vp1x*segx + vp1y*segy
dp1w := wp1x*segx + wp1y*segy
if (dp1v < 0 && dp1w < 0) || (dp1v > 0 && dp1w > 0) {
t.Error("point not contained in segment ", dp1v, dp1w)
t.FailNow()
}
wp2x, wp2y := p2x-s2x, p2y-s2y
dp2v := vp2x*segx + vp2y*segy
dp2w := wp2x*segx + wp2y*segy
if (dp2v < 0 && dp2w < 0) || (dp2v > 0 && dp2w > 0) {
t.Error("point not contained in segment ", dp2v, dp2w)
t.FailNow()
}
if s1x == s2x && s2y == s1y { //Only one solution
// Test that one end of the segment is withing the radius of the circle
// and one is not
if seg1Inside && seg2Inside {
t.Error("Only one solution but both line segment ends inside")
t.FailNow()
}
if !seg1Inside && !seg2Inside {
t.Error("Only one solution but both line segment ends outside")
t.FailNow()
}
}
} else { // No intersection, check if both points outside or inside
if (seg1Inside && !seg2Inside) || (!seg1Inside && seg2Inside) {
t.Error("No solution but only one point in radius of circle")
t.FailNow()
}
}
}
}
t.Log("Tested ", testCntr, " examples and found ", solutionCntr, " solutions.")
}
以下是测试的输出:
=== RUN TestSegmentCircleIntersection
--- PASS: TestSegmentCircleIntersection (0.00s)
geom_test.go:105: Tested 40000 examples and found 7343 solutions.
fn projectPoint p1 p2 p3 = --project p1 perpendicular to the line p2-p3
(
local v= normalize (p3-p2)
local p= (p1-p2)
p2+((dot v p)*v)
)
fn findIntersectionLineCircle CircleCenter CircleRadius LineP1 LineP2=
(
pp=projectPoint CircleCenter LineP1 LineP2
sideA=distance pp CircleCenter
--use pythagoras to solve the third side
sideB=sqrt(CircleRadius^2-sideA^2) -- this will return NaN if they don't intersect
IntersectV=normalize (pp-CircleCenter)
perpV=[IntersectV.y,-IntersectV.x,IntersectV.z]
--project the point to both sides to find the solutions
solution1=pp+(sideB*perpV)
solution2=pp-(sideB*perpV)
return #(solution1,solution2)
)
23 回答
以
E 是光线的起点,
L 是光线的终点,
C 是您正在测试的球体的中心
r 是该球体的半径
计算:
d = L - E(射线的方向矢量,从开始到结束)
f = E - C(从中心球到射线开始的矢量)
然后通过..找到交叉点 .
堵:
P = E + t * d
这是一个参数方程:
Px = Ex tdx
Py = Ey tdy
成
(x - h)2 + (y - k)2 = r2
(h,k)=圆心 .
to get:
Expand
x2 - 2xh h2 y2 - 2yk k2 - r2 = 0
Plug
x = ex tdx
y = ey tdy
(ex tdx)2 - 2(ex tdx)h h2(ey tdy)2 - 2(ey tdy)k k2 - r2 = 0
爆炸
ex2 2extdx t2dx2 - 2exh - 2tdxh h2 ey2 2eytdy t2dy2 - 2eyk - 2tdyk k2 - r2 = 0
Group
t2(dx2 dy2)2t(exdx eydy - dxh - dyk)ex2 ey2 - 2exh - 2eyk h2 k2 - r2 = 0
Finally,
t2(_d * _d)2t(_e * _d - _d * _c)_e * _e - 2(_e * _c)_c * _c - r2 = 0
*其中_d是向量d,*是点积 . *
And then,
t2(_d * _d)2t(d *( e - c))( e - c)*( e - _c) - r2 = 0
Letting _f = _e - _c
t2(_d * _d)2t(_d * _f)_f * _f - r2 = 0
所以我们得到:
t2 * (d DOT d) + 2t( f DOT d ) + ( f DOT f - r2 ) = 0*
所以求解二次方程:
似乎没有人考虑投影,我完全偏离这里吗?
将矢量
AC
投影到AB
. 投影向量AD
给出新点D
.如果
D
和C
之间的距离小于(或等于)R
,则我们有一个交点 .像这样:
我会用这个算法计算一个点(圆心)和一个线(AB线)之间的距离 . 然后,这可以用于确定线与圆的交点 .
假设我们有点A,B,C . Ax和Ay是A点的x和y分量 . B和C相同 . 标量R是圆半径 .
该算法要求A,B和C是不同的点,并且R不是0 .
这是算法
好的,我不会给你代码,但既然你已经标记了这个algorithm,我认为这对你不重要 . 首先,您必须得到垂直于线的矢量 .
你将在
y = ax + c
(c
中有一个未知变量 )要解决这个问题,请在线穿过圆心时计算它的值 .
那是,
将圆心的位置插入线方程并求解
c
.然后计算原始线与其法线的交点 .
这将为您提供圆圈线上的最近点 .
计算此点与圆心之间的距离(使用矢量的大小) .
如果这小于圆的半径 - 瞧,我们有一个交叉点!
另一种方法使用三角形ABC区域公式 . 相交测试比投影方法更简单,更有效,但找到交点的坐标需要更多的工作 . 至少它会被延迟到需要的程度 .
计算三角形区域的公式为:area = bh / 2
其中b是基本长度,h是高度 . 我们选择AB段作为基础,因此h是从C,圆心到线的最短距离 .
由于三角形区域也可以通过矢量点积计算,我们可以确定h .
UPDATE 1 :
您可以使用here描述的快速平方根计算来优化代码,以获得1 / LAB的良好近似值 .
计算交叉点并不困难 . 在这里
如果h = R,那么线AB与圆相切并且值dt = 0且E = F.点坐标是E和F的坐标 .
如果在您的应用程序中可能发生这种情况,则应检查A是否与B不同,并且段长度不为空 .
我写了一个小脚本,通过将圆的中心点投射到线上来测试交叉点 .
http://jsfiddle.net/ercang/ornh3594/1/
如果您需要检查与段的碰撞,还需要考虑圆心与起点和终点的距离 .
https://jsfiddle.net/ercang/menp0991/
我发现这个解决方案似乎比其他一些更容易 .
考虑:
我将以斜率截距形式求解线的方程 . 但是,我不想用
c
作为一个点处理困难的方程式,所以我只是将坐标系移动到圆形位于0,0
顺便说一下,每当我从彼此减去点数时,我减去
x
然后减去y
's, and putting them into a new point, just in case someone didn't知道 .无论如何,我现在用
p3
和p4
求解线的等式:好 . 现在我需要将这些方程设置为相等 . 首先,我需要解决
x
的圆的等式然后我将它们设置为相等:
并求解二次方程(
0 = ax^2 + bx + c
):现在我有
a
,b
和c
.所以我把它放到二次公式中:
然后用值替换然后尽可能地简化:
这几乎就是简化 . 最后,用±分离出方程式:
然后只需将这两个方程的结果插入
mx + b
中的x
即可 . 为清楚起见,我写了一些JavaScript代码来说明如何使用它:我希望这有帮助!
附:如果有人发现任何错误或有任何建议,请发表评论 . 我很新,欢迎所有帮助/建议 .
通过将矢量AC投影到矢量AB上,可以在无限直线上找到最接近圆心的点 . 计算该点与圆心之间的距离 . 如果R大于R,则没有交叉点 . 如果距离等于R,则线是圆的切线,最接近圆心的点实际上是交点 . 如果距离小于R,则有2个交叉点 . 它们与距离圆心最近的点位于相同的距离 . 使用毕达哥拉斯定理可以很容易地计算出这个距离 . 这是伪代码中的算法:
编辑:添加代码以检查找到的交叉点是否实际在线段内 .
奇怪的是我可以回答但不是评论......我喜欢Multitaskpro的方法,即将所有东西都移动到圆圈的中心 . 不幸的是,他的代码存在两个问题 . 首先在平方根部分中,您需要移除双倍功率 . 所以不是:
var underRadical = Math.pow((Math.pow(r,2)*(Math.pow(m,2)+1)),2)-Math.pow(b,2));
但:
var underRadical = Math.pow(r,2)*(Math.pow(m,2)+1)) - Math.pow(b,2);
在最后的坐标中,他忘记了将解决方案转移回去 . 所以不是:
var i1 = {x:t1,y:m*t1+b}
但:
var i1 = {x:t1+c.x, y:m*t1+b+c.y};
整个功能变为:
你需要一些数学:
假设A =(Xa,Ya),B =(Xb,Yb)和C =(Xc,Yc) . 从A到B的线上的任何点都有坐标(alpha * Xa(1-alpha)Xb,alphaYa(1-alpha)* Yb)= P
如果点P的距离为R到C,则它必须在圆上 . 你想要的是解决
那是
如果您将ABC公式应用于此公式以将其求解为alpha,并使用alpha的解决方案计算P的坐标,则会得到交点(如果存在) .
如果你找到球体中心之间的距离(因为它是3D我假设你的意思是球体而不是圆圈)和线条,那么检查该距离是否小于将要做的技巧的半径 .
碰撞点显然是直线和球体之间的最近点(当你计算球体和直线之间的距离时会计算出来)
点与线之间的距离:
http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
这是Javascript中的一个实现 . 我的方法是首先将线段转换为无限线,然后找到交叉点 . 从那里我检查找到的点是否在线段上 . 代码有详细记录,您应该能够遵循 .
你可以在这个live demo上试试这里的代码 . 代码取自我的algorithms repo .
在该后圆线中,将通过检查圆心和线段(Ipoint)之间的距离来检查线圈,该线段表示从圆心到线段的法线N(图像2)之间的交点 .
(https://i.stack.imgur.com/3o6do.png)
在图像1上显示一个圆和一个线,矢量A点到线起点,矢量B点到线 endpoints ,矢量C点到圆心 . 现在我们必须找到矢量E(从行起点到圆心)和矢量D(从行起点到行终点),这个计算显示在图像1上 .
(https://i.stack.imgur.com/7098a.png)
在图像2中,我们可以看到矢量E通过矢量E和单位矢量D的“点积”投影在矢量D上,点积的结果是标量Xp,表示线起点和交点(Ipoint)之间的距离 . 向量N和向量D.通过乘以单位向量D和标量Xp找到下一个向量X.
现在我们需要找到向量Z(向量到Ipoint),它很简单,它的向量A(起始点在线)和向量X的简单向量加法 . 接下来我们需要处理特殊情况,我们必须检查线段上的Ipoint,如果它不是我们必须发现它是它的左侧还是右侧,我们将使用最接近的矢量来确定哪个点最接近圆 .
(https://i.stack.imgur.com/p9WIr.png)
当投影Xp为负时,Ipoint为线段左侧,向量最近等于线起始点的向量,当投影Xp大于矢量D的幅度时,则Ipoint在线段右侧,则最接近的矢量等于线 endpoints 的矢量,在任何其他情况下,最接近的矢量等于矢量Z.
现在当我们有最接近的向量时,我们需要找到从圆心到Ipoint(dist向量)的向量,其简单我们只需要从中心向量中减去最接近的向量 . 接下来只检查矢量dist幅度是否小于圆半径,如果它是碰撞,如果它没有碰撞 .
(https://i.stack.imgur.com/QJ63q.png)
最后,我们可以返回一些用于解决碰撞的值,最简单的方法是返回碰撞重叠(从矢量dist幅度减去半径)和返回碰撞轴,它的矢量D.如果需要,交点也是矢量Z.
如果线的坐标是A.x,A.y和B.x,B.y和圆心是C.x,C.y那么线公式是:
x = A.x * t B.x *(1 - t)
y = A.y * t B.y *(1 - t)
其中0 <= t <= 1
而圆圈是
(C.x-x)^ 2(C.y-y)^ 2 = R ^ 2
如果将线的x和y公式替换为圆公式,则得到t的二阶方程,其解是交点(如果有的话) . 如果你得到一个小于0或大于1的t,那么它不是一个解决方案,但它表明该线“指向”圆的方向 .
只是这个帖子的补充...下面是pahlevan发布的代码版本,但对于C#/ XNA并整理了一点:
我按照
chmike
给出的答案为iOS创建了这个函数此Java函数返回DVec2对象 . 它的圆心,圆的半径和直线需要DVec2 .
c#中的另一个(部分Circle类) . 经过测试和工作就像一个魅力 .
需要:
这是JavaScript中的好解决方案(包含所有必需的数学和实时插图)https://bl.ocks.org/milkbread/11000965
虽然该解决方案中的
is_on
功能需要修改:圈子真的是个坏人:)所以一个好方法是避免真正的圈子,如果可以的话 . 如果你正在对游戏进行碰撞检查,你可以进行一些简化,只有3个点产品和一些比较 .
我称之为“胖点”或“薄圈” . 它是一种在平行于一个段的方向上具有零半径的椭圆 . 但在垂直于一个段的方向上的全半径
首先,我会考虑重命名和切换坐标系以避免过多的数据:
其次,hvec2f中的索引h意味着比矢量必须支持水平操作,如dot()/ det() . 这意味着它的组件将被放置在一个单独的xmm寄存器中,以避免混乱/充电/ hsub'ing . 在这里,我们为2D游戏提供最简单的最简单的碰撞检测版本:
我怀疑你可以进一步优化它 . 我正在将它用于神经网络驱动的赛车碰撞检测,以处理数亿次迭代步骤 .
这是一个用golang编写的解决方案 . 该方法类似于此处发布的其他一些答案,但不完全相同 . 它易于实施,并已经过测试 . 以下是步骤:
平移坐标,使圆圈位于原点 .
将线段表示为x和y坐标的t的参数化函数 . 如果t为0,则函数's values are one end point of the segment, and if t is 1, the function' s值是另一个 endpoints .
如果可能,求解由约束t的值产生的二次方程,该值产生x,y坐标,其距离原点的距离等于圆的半径 .
抛出t为<0或> 1的解(对于开放段,<= 0或> = 1) . 这些点不包含在该段中 .
转换回原始坐标 .
这里导出了二次曲线的A,B和C的值,其中(n-et)和(m-dt)分别是线的x和y坐标的等式 . r是圆的半径 .
因此A = ee dd,B = -2(en dm),C = nn mm - rr .
这是函数的golang代码:
我用这个函数测试了它,它确认了解决点在线段和圆上 . 它会创建一个测试段并围绕给定的圆圈进行扫描:
以下是测试的输出:
最后,该方法很容易扩展到射线从一个点开始,穿过另一个并延伸到无穷大的情况,只测试t> 0或t <1但不是两者 .
我只需要那个,所以我提出了这个解决方案 . 语言是maxscript,但应该很容易翻译成任何其他语言 . sideA,sideB和CircleRadius是标量,其余变量是[x,y,z] . 我假设z = 0在XY平面上求解