利用向量判斷線段和圓是否相交

判斷扇形和圓是否相交

面試的時候被問到如何判斷扇形和圓是否相交,當時也是比較懵的,刷leetcode不會碰到這種問題。我大概分了幾個情況進行討論,並且用到了求點到直線的距離公式。面試官後來提示說從幾何的角度考慮,最好不要去用方程求解的角度去想,,,大概想到了向量點乘和叉乘,但是當時沒寫出來。後來在網上看博客大部分也都是去解方程,稍微麻煩一些,現在把自己的思路分享一下,有錯誤歡迎指正。

一般大家首先會考慮到分成扇形邊和弧段部分分別判斷吧,我們先看一下如何判斷線段是否和圓形相交:

線段和圓形是否相交

我們用兩個端點表示線段,用一個點和半徑表示圓

typedef pair<float, float> Point;
Point l1(-1.1,0), l2(5, 0.9), c1(0, 0);
float R;

首先點和圓形的拓撲關係很好計算,根據端點和圓形的關係分成三種情況,即兩個端點都在圓形內,一個端點在圓形內,兩個端點都不在圓形內。第一種情況不相交,第二種情況相交,第三種情況需要進一步討論。
對這三種情況進行判別只需要一個點點距離公式:

float pointDis(Point p1, Point p2)
{
    return sqrt((p1.first - p2.first) * (p1.first - p2.first) + (p1.second - p2.second) * (p1.second - p2.second));
}
bool inCircle(Point p, Point c1, float R)
{
    float dis = pointDis(p, c1);
    if (dis < R)
        return true;
    return false;
}

下面對第三種情況進行進一步的討論:
如果圓心到這個直線的距離小於半徑,且垂足落在線段上,那麼相交,否則不相交。爲了完成這個計算,我們引入向量:

typedef pair<float, float> vector2d;

有了向量能做的事情就多了,不需要用點到直線的距離公式,也不需要對直線用公式建模(需要判斷特殊情況),計算角度也很方便。下面看一下叉乘:

float crossProduct(vector2d v1, vector2d v2)
{
    return (v1.first * v2.second - v1.second * v2.first);
}

二維空間下,叉乘的結果是absinθ|a|*|b|*sin\thetaθ\thetaaabb兩個邊的夾角,其實二維空間下叉乘結果是有正負號的(表示沿隱藏的z軸方向還是其反方向),這裏我們不需要用到這個特性,所以可以直接取絕對值。
下面看一下點乘:

float dotProduct(vector2d v1, vector2d v2)
{
    return (v1.first * v2.first + v1.second * v2.second);
}

點乘的結果是abcosθ|a|*|b|*cos\theta,顯然cosθcos\theta的正負可以表示一個角是銳角還是鈍角。
好的我們有了向量、點乘和叉乘,接下來思考如何避免對直線建模後再去計算距離,以及如何去判斷角度:
在這裏插入圖片描述
向量表示:

vector2d vecl2_l1(l1.first - l2.first, l1.second - l2.second);
vector2d vecl1_l2(-vecl2_l1.first, -vecl2_l1.second);
vector2d vecc1_l1(l1.first - c1.first, l1.second - c1.second);
vector2d vecc1_l2(l2.first - c1.first, l2.second - c1.second);

叉乘計算距離:

 float dis = abs(crossProduct(vecc1_l1, vecl2_l1) / pointDis(l1, l2));

沒錯,就是這麼簡單。接下來用點乘判斷夾角:

 if (dis > R) return false;
    bool flag = dotProduct(vecl1_l2, vecc1_l2) && dotProduct(vecl2_l1, vecc1_l1);

如果兩個夾角都是銳角,flag爲true,反之flag爲false,全部代碼如下:


#include <iostream>
#include <algorithm>

using namespace std;
typedef pair<float, float> Point;
typedef pair<float, float> vector2d;
// 點距
float pointDis(Point p1, Point p2)
{
    return sqrt((p1.first - p2.first) * (p1.first - p2.first) + (p1.second - p2.second) * (p1.second - p2.second));
}
// ×乘
float crossProduct(vector2d v1, vector2d v2)
{
    return (v1.first * v2.second - v1.second * v2.first);
}
// 點乘
float dotProduct(vector2d v1, vector2d v2)
{
    return (v1.first * v2.first + v1.second * v2.second);
}
//點是否在圓內
bool inCircle(Point p, Point c1, float R)
{
    float dis = pointDis(p, c1);
    if (dis < R)
        return true;
    return false;
}

//線段與圓是否相交
bool judgeCircleAndLine(Point l1, Point l2, Point c1, float R)
{
    // 兩個端點都在圓內'
    if (inCircle(l1, c1, R) && inCircle(l2, c1, R)) return false;
    // 一個端點在圓內
    if (inCircle(l1, c1, R) || inCircle(l2, c1, R)) return true;
    // 兩個點都不在圓內
    vector2d vecl2_l1(l1.first - l2.first, l1.second - l2.second);
    vector2d vecl1_l2(-vecl2_l1.first, -vecl2_l1.second);
    vector2d vecc1_l1(l1.first - c1.first, l1.second - c1.second);
    vector2d vecc1_l2(l2.first - c1.first, l2.second - c1.second);
    float dis = abs(crossProduct(vecc1_l1, vecl2_l1) / pointDis(l1, l2));
    if (dis > R) return false;
    bool flag = dotProduct(vecl1_l2, vecc1_l2) && dotProduct(vecl2_l1, vecc1_l1);
    return flag;
}
int main()
{
    Point l1(-1.1,0), l2(0,1.1), c1(0, 0);
    std::cout <<judgeCircleAndLine(l1,l2,c1,1);
}

暫時寫到這裏,有問題歡迎指正,後邊會繼續些扇形的問題。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章