c# 直线和椭圆弧的交点坐标算法
程序员文章站
2022-03-31 11:41:03
...
在求直线和椭圆弧的交点坐标的时候,我们同样和我上一个求直线和圆弧交点坐标一样,先来分析一下,主要分为
第一步: 求直线和椭圆弧交点,我们可以先求直线和椭圆弧的交点。
第二步:由于椭圆的可能是有角度的,椭圆圆心不在坐标轴的原点上,比如长轴不在X轴上,也不在Y轴上,这样的椭圆就是有角度的椭圆,这样的椭圆我们不好进行计算。对此,我们需要将圆弧旋转,然后再平移,使长轴在X轴上,是椭圆圆心在原点上,但是你会发现这样操作椭圆还是比较麻烦,因此,我们可以操作直线,想象椭圆圆心已经移动到了原点,因为移动椭圆只需要移动椭圆圆心就好,这样我们也根据移动椭圆圆心一样的步伐移动直线,然后求直线和假设移动后的椭圆的交点,然后根据椭圆圆心一样的步伐反向移动回去,得到的点就是直线和有角度的椭圆交点。(有点绕,可以画图理解哦)
第三步:最终通过判断交点是否在椭圆弧上。
下面开始展示代码,伸手党有福了,可能有些地方写的不好,望谅解,创作不易,喜欢给个好评吧。
主干
/// <summary>
/// 线段和椭圆弧的交点
/// </summary>
/// <param name="line">线段对象</param>
/// <param name="ht">存储交点和error信息</param>
/// <returns>返回交点和error信息</returns>
internal static Hashtable LineIntersectEllipticArc(Line line, Hashtable ht)
{
//线段的终点
Vector2 LineendPoint = line.endPoint;
//线段的起点
Vector2 LinestartPoint = line.startPoint;
//椭圆弧的长轴的一半
double maxAxis = 262.39051820125013 / 2;
//椭圆弧的短轴一半
double minAxis = 135.76528231694766 / 2;
//椭圆弧的起点
//椭圆弧的起始角度
double startAngle = DegreeToRadian(132.0438345714015);
//椭圆弧的终点
//椭圆弧的终止角度 //默认设置为90度对应的弧度
double endAngle = DegreeToRadian(258.13766538237763);
//椭圆弧的圆心
Vector2 center = new Vector2(17.7894639270817, 15.0254309579905);
//设置原点坐标(0,0)
Vector2 centerZero = new Vector2(0, 0);
//椭圆弧的导入角度'
double angle = DegreeToRadian(191.75357833501226);
//将直线顺时针旋转angle度
Vector2[] ve = Anticlockwise(LineendPoint, LinestartPoint, center, angle);
//假设默认交点坐标
Vector2 ptInter1 = new Vector2(0, 0);
Vector2 ptInter2 = new Vector2(0, 0);
//直线和椭圆的交点坐标
LineIntersectEllipse(MoveOne(ve[0], center), MoveOne(ve[1], center), maxAxis, minAxis, ref ptInter1, ref ptInter2);
//用于存储交点
List<Vector2> node = new List<Vector2>();
//用于判断是否有交点
bool judgeNode = false;
//存储的版本号
int version = 0;
double a = NormalizeRadianAngle(startAngle);
double b = NormalizeRadianAngle(endAngle);
if (Zero(ptInter1))
{
JudgeDotQualified(centerZero, ptInter1, a, b, node);
if (node.Count == 1)
{
if (node[0] != null)
{
//表示第一次有一个值
//将交点逆时针旋转
Vector2[] ve1 = Anticlockwise(ptInter1, ptInter2, centerZero, angle, false);
//再将交点平移得到最后真正的交点
ve1[0] = MoveTwo(ve1[0], center);
node[0] = ve1[0];
version = 1;
}
}
}
if (Zero(ptInter2))
{
if (version == 0)
{
JudgeDotQualified(centerZero, ptInter2, a, b, node);
if (node.Count == 1)
{
if (node[0] != null)
{
//表示第一次没有一个值
//将交点逆时针旋转
Vector2[] ve1 = Anticlockwise(ptInter1, ptInter2, centerZero, angle, false);
//再将交点平移得到最后真正的交点
ve1[1] = MoveTwo(ve1[1], center);
node[0] = ve1[1];
}
}
}
else if (version == 1)
{
JudgeDotQualified(centerZero, ptInter2, a, b, node);
if (node.Count == 2)
{
if (node[1] != null)
{
//表示第一次有一个值
//将交点逆时针旋转
Vector2[] ve1 = Anticlockwise(ptInter1, ptInter2, centerZero, angle, false);
//再将交点平移得到最后真正的交点
ve1[1] = MoveTwo(ve1[1], center);
node[1] = ve1[1];
}
}
}
}
ht.Add("node", node);
return ht;
}
/// <summary>
/// 角度转弧度
/// </summary>
/// <param name="angle">角度</param>
/// <returns>弧度</returns>
public static double DegreeToRadian(double angle)
{
return ((angle * System.Math.PI) / 180.0);
}
public const double EPSILON = 1E-12;
public static bool IsEqual(double x, double y, double epsilon = EPSILON)
{
return IsEqualZero(x - y, epsilon);
}
public static bool IsEqualZero(double x, double epsilon = EPSILON)
{
return (System.Math.Abs(x) < epsilon);
}
/// <summary>
/// 点的绕椭圆弧弧心旋转
/// </summary>
/// <param name="LineendPoint">线段终点</param>
/// <param name="LinestartPoint">线段起点</param>
/// <param name="center">椭圆弧弧心</param>
/// <param name="angle">椭圆弧的导入角度'</param>
/// /// <param name="isClockwise">顺逆旋转,默认为顺时针旋转</param>
internal static Vector2[] Anticlockwise(Vector2 LineendPoint, Vector2 LinestartPoint, Vector2 center, double angle, bool isClockwise = true)
{
Vector2[] ve = new Vector2[2];
if (isClockwise)
{
angle = -angle;
}
double cos = System.Math.Cos(angle);
double sin = System.Math.Sin(angle);
if (IsEqual(cos, 0))
{
cos = 0;
}
if (IsEqual(sin, 0))
{
sin = 0;
}
double x = ((LineendPoint.x - center.x) * cos - (LineendPoint.y - center.y) * sin + center.x);
double y = ((LineendPoint.x - center.x) * sin + (LineendPoint.y - center.y) * cos + center.y);
ve[0].x = x;
ve[0].y = y;
double x1 = ((LinestartPoint.x - center.x) * cos - (LinestartPoint.y - center.y) * sin + center.x);
double y1 = ((LinestartPoint.x - center.x) * sin + (LinestartPoint.y - center.y) * cos + center.y);
ve[1].x = x1;
ve[1].y = y1;
return ve;
}
/// <summary>
/// 第一次平移
/// </summary>
/// <param name="dot">交点坐标</param>
/// <param name="center">圆心坐标</param>
/// <returns>返回平移后的点坐标</returns>
internal static Vector2 MoveOne(Vector2 dot, Vector2 center)
{
Vector2 returnDot = new Vector2();
if (center.x >= 0 && center.y >= 0)
{
//圆心在第一象限
returnDot.x = dot.x - center.x;
returnDot.y = dot.y - center.y;
}
else if (center.x <= 0 && center.y >= 0)
{
//圆心在第二象限
returnDot.x = dot.x + center.x;
returnDot.y = dot.y - center.y;
}
else if (center.x <= 0 && center.y <= 0)
{
//圆心在第三象限
returnDot.x = dot.x + center.x;
returnDot.y = dot.y + center.y;
}
else if (center.x >= 0 && center.y <= 0)
{ //圆心在第四象限
returnDot.x = dot.x - center.x;
returnDot.y = dot.y + center.y;
}
return returnDot;
}
/// <summary>
/// 第二次平移
/// </summary>
/// <param name="dot">点坐标</param>
/// <param name="center">圆心坐标</param>
/// <returns>返回平移后的点</returns>
internal static Vector2 MoveTwo(Vector2 dot, Vector2 center)
{
Vector2 returnDot = new Vector2();
if (center.x >= 0 && center.y >= 0)
{
//圆心在第一象限
returnDot.x = dot.x + center.x;
returnDot.y = dot.y + center.y;
}
else if (center.x <= 0 && center.y >= 0)
{
//圆心在第二象限
returnDot.x = dot.x - center.x;
returnDot.y = dot.y + center.y;
}
else if (center.x <= 0 && center.y <= 0)
{
//圆心在第三象限
returnDot.x = dot.x - center.x;
returnDot.y = dot.y - center.y;
}
else if (center.x >= 0 && center.y <= 0)
{ //圆心在第四象限
returnDot.x = dot.x + center.x;
returnDot.y = dot.y - center.y;
}
return returnDot;
}
/// <summary>
/// 判断点是否为(0,0)
/// </summary>
/// <param name="dot">交点</param>
/// <returns>是(0,0)返回false</returns>
internal static bool Zero(Vector2 dot)
{
if (dot.x == 0)
{
if (dot.y == 0)
{
return false;
}
else
{
return true;
}
}
else
{
return true;
}
}
/// <summary>
/// 规整化弧度
/// 返回值范围:[0, 2*PI)
/// </summary>
internal static double NormalizeRadianAngle(double rad)
{
double value = rad % (2 * System.Math.PI);
if (value < 0)
value += 2 * System.Math.PI;
return value;
}
计算线段和椭圆的
/// <summary>
/// 计算线段和椭圆的交点
/// </summary>
/// <param name="LineendPoint">线段的终点</param>
/// <param name="LinestartPoint">线段的起始点</param>
/// <param name="maxAxis">长轴的长度的一半</param>
/// <param name="minAxis">短轴的长度的一半</param>
/// <param name="angle">椭圆弧的导入角度'</param>
/// <param name="ptInter1">交点一</param>
/// <param name="ptInter2">交点二</param>
/// <returns>返回是否有交点</returns>
internal static bool LineIntersectEllipse(Vector2 LineendPoint, Vector2 LinestartPoint, double maxAxis, double minAxis, ref Vector2 ptInter1, ref Vector2 ptInter2)
{
//直线方程为y=kx+b 求出k,c
double k, c;
k = (LineendPoint.y - LinestartPoint.y) / (LineendPoint.x - LinestartPoint.x);
c = LinestartPoint.y - k * LinestartPoint.x;
//maxAxis为x轴上的长轴 ,minAxis为y轴上的短轴
//判断是否有交点
double media = ((2 * maxAxis * maxAxis * k * c) * (2 * maxAxis * maxAxis * k * c) - 4 * (minAxis * minAxis + k * k * maxAxis * maxAxis) * maxAxis * maxAxis * (c * c - minAxis * minAxis));
if (media > 0)
{
//直线和椭圆有两个交点
ptInter1.x = (-2 * maxAxis * maxAxis * k * c + System.Math.Sqrt(media)) / (2 * (minAxis * minAxis + k * k * maxAxis * maxAxis));
ptInter1.y = k * ptInter1.x + c;
ptInter2.x = (-2 * maxAxis * maxAxis * k * c - System.Math.Sqrt(media)) / (2 * (minAxis * minAxis + k * k * maxAxis * maxAxis));
ptInter2.y = k * ptInter2.x + c;
return true;
}
else if (media == 0)
{
//直线和椭圆只有一个交点
ptInter1.x = (-2 * maxAxis * maxAxis * k * c + System.Math.Sqrt(media)) / (2 * (minAxis * minAxis + k * k * maxAxis * maxAxis));
ptInter1.y = k * ptInter1.x + c;
return true;
}
else
{
//直线和椭圆没有交点
return false;
}
}
判断交点是否合格方法,也就是交点是否在椭圆弧上
/// <summary>
/// 判断交点是否合格方法
/// </summary>
/// <param name="vector">中心圆心</param>
/// <param name="ptInter1">交点</param>
/// <param name="startAngle">圆弧起始角度</param>
/// <param name="endAngle">圆弧终止角度</param>
/// <param name="node">存放交点集合</param>
/// <param name="_direction">圆弧得顺逆,默认为逆时针</param>
internal static void JudgeDotQualified(Vector2 vector, Vector2 ptInter1, double startAngle, double endAngle, List<Vector2> node, bool _direction = false)
{
if (!_direction)
{
//画弧为逆时针
//得到一个交点对应的弧度
double radian = Tangent(vector, ptInter1);
if (startAngle > endAngle)
{ //起始角度大于终止角度时,交点角度小于终止角度或者交点角度大于起始角度就会与圆弧相交
if (radian <= endAngle || radian >= startAngle)
{
node.Add(ptInter1);
}
}
else
{ //起始角度小于终止角度时,交点角度在起始角度和终止角度之间就会与圆弧相交
if (startAngle < radian && radian < endAngle)
{
node.Add(ptInter1);
}
else if (startAngle == radian || endAngle == radian)
{
node.Add(ptInter1);
}
}
}
else
{
//画弧为顺时针
//得到一个交点对应的弧度
double radian = MathUtils.Tangent(vector, ptInter1);
if (startAngle < endAngle)
{ //起始角度小于终止角度时,交点角度大于终止角度或者交点角度小于起始角度就会与圆弧相交
if (radian <= startAngle || radian >= endAngle)
{
node.Add(ptInter1);
}
}
else
{ //起始角度大于终止角度时,交点角度在终止角度和起始角度之间就会与圆弧相交
if (endAngle < radian && radian < startAngle)
{
node.Add(ptInter1);
}
else if (startAngle == radian || endAngle == radian)
{
node.Add(ptInter1);
}
}
}
}
/// <summary>
/// 利用正切值
/// 计算交点对应的弧度
/// </summary>
/// <param name="vector">圆心坐标</param>
/// <param name="intersect">交点坐标</param>
/// <returns>弧度</returns>
internal static double Tangent(Vector2 vector, Vector2 intersect)
{
//圆心的x ,y 坐标
double vectorX = vector.x;
double vectorY = vector.y;
//交点的 x ,y 坐标
double intersectX = intersect.x;
double intersectY = intersect.y;
//两点的x轴的差值
double difX = intersectX - vectorX;
//两点的y轴的差值
double difY = intersectY - vectorY;
if (intersectY == vectorY && intersectX > 0)
{
//交点在正x轴上,角度为0,弧度为零
return 0;
}
else if (intersectX == vectorX && intersectY > 0)
{
//交点在正y轴上,角度为90度
return DegreeToRadian(90);
}
else if (intersectY == vectorY && intersectX < 0)
{
//交点在负x轴上,角度为180度
return DegreeToRadian(180);
}
else if (intersectX == vectorX && intersectY < 0)
{
//交点在负数y轴上,角度为270度
return DegreeToRadian(270);
}
else
{
if (difX > 0 && difY > 0)
{
//锐角
double number = difY / difX;
return TangentToDegree(number);
}
else if (difX < 0 && difY > 0)
{
//小于180度的钝角
double number = difY / (-difX);
return DegreeToRadian(180) - TangentToDegree(number);
}
else if (difX < 0 && difY < 0)
{
//大于180度小于270度的钝角
double number = (-difY) / (-difX);
return TangentToDegree(number) + DegreeToRadian(180);
}
else
{
//(difX > 0 && difY < 0)
//大于270度小于360度的钝角
double number = (-difY) / difX;
return DegreeToRadian(360) - TangentToDegree(number);
}
}
}
/// <summary>
/// 正切值转弧度
/// </summary>
/// <returns>返回一个弧度</returns>
public static double TangentToDegree(double tangent)
{
//得到正切值对应的弧度
return System.Math.Atan(tangent);
}
总结: 由上面的分析可以看出,在分析第二步的时候,有点绕,但是你可以画图理解,再结合代码学习,会发现也不是很难,当然最后不要忘记,点赞,好评加关注哦。