几何求交(二):直线和圆的交点

1. 向量复习

因为本节需要向量来计算交点,如果大家对向量不是很熟悉,请见:向量复习(一)。已经很熟悉的童鞋,可以跳过这部分内容。

2. 思路解析

2.1 总体思路

如果看过上一节的直线交点,那么这里求直线和圆的交点思路是非常相似的:

  1. 用直线到圆心的距离和半径相比,判断是否和圆有交点;
  2. 求出圆心在直线上面的投影点(projectPoint);
  3. 算出直线的单位向量e;
  4. 求出一侧交点(Intersection)到projectPoint的长度(sideLength);
  5. 求出sideLength和这侧端点到projectPoint距离的比例(ratio);
  6. projectPoint +/- ratio * e = 两侧交点;

同样,我们还是通过一个图例来讲解。我们给定圆,圆心为C,半径为r,和直线AB,以及它们两个交点E和F:

在这里插入图片描述
现在我们以求交点E为例来进行讲解。使用求直线交点的思路,如果我们首先会考虑从B出发,求得BE和某个向量的比例,即可通过B来计算交点E,但是这样不是很好找比例,所以我们换一种思路,先求C在直线AB上面的投影Pr:

在这里插入图片描述
那么显然,我们可以得到一组向量的比例关系:BPrEPr(加粗表示向量),因为圆和直线可能有两个交点,分别位于Pr的两边,所以我们选Pr作为起点求交点比较方便:

在这里插入图片描述

有了这个比例关系(ratio),我们只要知道BA的单位向量e,用 ratio * e,就可以得到EPrPrF。因为根据垂径定理1,|EPr| = |PrF|。所以我们先假设求得了Pr,那么|EPr|和|BPr|怎么求呢?我们连接BC和EC看看:

在这里插入图片描述

从图中可知,|EPr|不难求,直角三角形CEPr可用勾股定理求得;那么|BPr|呢?现在又需要向量的帮助了,大家还记得向量得点积么?两个向量a和b的点积表示a在b上面的投影,所以|BPr| = BC * BPr。那么到现在,还剩下两个问题就能解决交点了:1)如何求C在直线AB上面的投影;2)如何求C到直线AB的距离;

2.2 点在直线上面的投影

在文章开头,我们曾经提到下面的思路去求解交点:

现在我们以求交点E为例来进行讲解。使用求直线交点的思路,如果我们首先会考虑从B出发,求得BE和某个向量的比例,即可通过B来计算交点E

虽然这个方法求交点不是很好,但是我们可以用它来求解点在直线上面的投影。还是上面圆的图例:

在这里插入图片描述
所以我们只需要求出BPrBA的比例关系,即可以求解Pr,而且|BPr|可以BCBA的点积来求得,因为两者点积是BC在BA上面的投影,通过这样,我们就能求得比例关系了,如下图所示:
在这里插入图片描述
那么,相应的代码也非常容易理解啦哒:

// 这部分是写在Line类里面的:https://github.com/fengkeyleaf/Algorithm/blob/main/ComputationalGeometry/IntersectionOfLineOrCycle/myLibraries/util/geometry/elements/Line.java
/**
 * get projecting point of the point
 * */

public Vector project( P point ) {
    Vector base = getVector();
    float ratio = Vector.dot(
            point.subtract( startPoint ), base )
            / base.normWithoutRadical();
    return startPoint.add( base.multiply( ratio ) );
}

2.3 点到直线的距离

点到直线的距离,我们可以看成平行四边形的高,然后用叉积来求得,还是刚才的图例:

在这里插入图片描述
我们看到C到AB的距离(CPr)其实平行四边形BADC的高,所以我们可以利用BCBA的叉积来求解。

// 详见:https://github.com/fengkeyleaf/Algorithm/blob/main/ComputationalGeometry/IntersectionOfLineOrCycle/myLibraries/util/geometry/elements/Line.java
/**
 * get the linear distance from the point
 * */

public float distance( P point ) {
    Vector vector = getVector();
    float area = Vector.cross( vector, point.subtract( startPoint ) );
    return Math.abs( area / vector.norm() );
}

3. 代码解析

和直线的交点一样,这里的代码也是上面思路的直接实现:

/**
 * get the intersection point of line and cycle with vector, if exists
 *
 * Reference resource:
 * https://blog.csdn.net/qq_40998706/article/details/87521165
 */

public static
Line<Vector> lineCycleIntersect( Line<Vector> line, Cycle cycle ) {
    if ( line == null || cycle == null
            || !cycle.ifIntersectsLine( line ) ) return null;

    Vector projectPoint = line.project( cycle.center );
    Vector lineVector = line.getVector();
    Vector e = lineVector.division( lineVector.norm() );
    float hypotenuse = cycle.radius * cycle.radius -
            projectPoint.subtract( cycle.center ).normWithoutRadical();
    float ratio = ( float ) Math.sqrt( Math.abs( hypotenuse ) );
    Vector distance = e.multiply( ratio );
    return new Line<>( projectPoint.add( distance ), projectPoint.subtract( distance ) );
}

判断直线和圆是否有交点的方法ifIntersectsLine(),我写在Cycle类里面了:

public class Cycle {
    public final Vector center;
    public final float radius;

    /**
     * constructs to create an instance of Cycle
     * */

    public Cycle( Vector center, float radius ) {
        this.center = center;
        this.radius = radius;
    }

    /**
     * intersects with the line?
     * */

    public boolean ifIntersectsLine( Line<Vector> line ) {
    	// 避免计算误差
        return MyMath.doubleCompare( line.distance( center ), radius ) <= 0;    
    }
}

这里的代码和上一节非常的相似,但需要注意一点:

float sideLength = cycle.radius * cycle.radius -
        projectPoint.subtract( cycle.center ).normWithoutRadical();
float ratio = ( float ) Math.sqrt( Math.abs( sideLength ) );

参考资料里面是这样写的2

double base = sqrt(c.r * c.r - norm(pr - c.c)); // C++

这里的base就是上面的ratio,开根里面的计算就是计算sideLength,但是这里没有取绝对值,从数学层面来说,这里不需要的,因为我们知道直角三角形的斜边一定是大于等于两个领边的,所以勾股定理算领边不会出现负数的情况,但是代码不是这样的,一旦斜边的平方刚好等于一条领边的平方,因为浮点数计算误差,会出现结果等于负数的情况(非常小的负数,趋近于0),所以为了保险,这里需要取个绝对值,在开根。

这里我们得到一个很重要的代码细节:

以后在开根的时候,即使不取绝对值,也应该assert一下非负,这样如果出现上面的错误,也不会找不到BUG

这个情况的测试案例,大家同样可以在测试代码中找到:

// public static void testLineCycleIntersection()
System.out.println( lineCycleIntersect( line4, cycle ) ); // -11->-1.0002441|0.99975586	-12->-0.99975586|1.0002441

几何求交暂时讲解到这里啦哒,感谢大家看到这里啦,辛苦哒~

上一节:几何求交(一):直线和直线的交点

4. 附录:项目代码

1.1.3 Geometric Intersection

DescriptionEntry method\File
Line and lineVector lineIntersect( Line l )
Segment and segmentVector segmentIntersect( Line l )
Segment and CircleVector[] segmentCircle( Segment s, Circle c )
Line and CircleLine lineCircleIntersect( Line line, Circle circle )
Brute ForceList<Vector> bruteForce( List<E> S )
Bentley Ottmann’s algrithom( Intersection Of segment, ray, line and Circle )List<EventPoint2D> findIntersection( List<IntersectionShape> S )
Program ( including visualization )CG2017 PA1-2 Crossroad

5. 参考资料

  1. 计算几何学 | 圆与直线的交点 | Cross Points of a Circle and a Line | C/C++实现

6. 免责声明

※ 本文之中如有错误和不准确的地方,欢迎大家指正哒~
※ 此项目仅用于学习交流,请不要用于任何形式的商用用途,谢谢呢;


在这里插入图片描述


  1. 垂径定理 ↩︎

  2. 计算几何学 | 圆与直线的交点 | Cross Points of a Circle and a Line | C/C++实现 ↩︎

  • 10
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Matlab中,可以通过一些数学和几何的方法来直线交点。 首先,需要确定直线的参数方程和的参数方程。对于直线,可以使用两点间的直线方程或参数方程表示。对于,可以使用其心和半径来表示。 假设直线的参数方程为: x = x0 + t*(x1 - x0) y = y0 + t*(y1 - y0) 的参数方程为: (x - a)^2 + (y - b)^2 = r^2 其中,(x0, y0)和(x1, y1)为直线上两点的坐标,(a, b)为心的坐标,r为的半径。 为了直线交点,需要将直线方程代入的方程中,得到一个关于t的方程。然后,通过解这个方程,可以得到直线交点。 假设直线交点为(x, y),则有: (x - a)^2 + (y - b)^2 = r^2 代入直线的参数方程,得到: (x0 + t*(x1 - x0) - a)^2 + (y0 + t*(y1 - y0) - b)^2 = r^2 展开和简化方程,得到一个关于t的次方程。 通过解这个次方程,可以得到t的两个值,然后再将这两个t值代回直线的参数方程中,解得到对应的两个交点坐标。 在Matlab中,可以使用符号计算的工具箱来解这个次方程,例如使用`solve`函数。示例代码如下: syms t % 定义直线的参数 x0 = 1; y0 = 1; x1 = 5; y1 = 3; a = 2; b = 2; r = 2; % 次方程 eq = (x0 + t*(x1 - x0) - a)^2 + (y0 + t*(y1 - y0) - b)^2 == r^2; sol = solve(eq, t); % 交点坐标 x = [x0 + sol(1)*(x1 - x0), x0 + sol(2)*(x1 - x0)]; y = [y0 + sol(1)*(y1 - y0), y0 + sol(2)*(y1 - y0)]; 通过上述代码,可以得到直线的两个交点坐标(x, y)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值