1. 程式人生 > 遊戲 >喜加一:《默途》為奧運健兒慶功,將開啟38%限時折扣

喜加一:《默途》為奧運健兒慶功,將開啟38%限時折扣

點、線段、多邊形

計算幾何

點積

向量的內積(點乘/數量積)

\[a=[a_1,a_2,...a_n] \quad b=[b_1,b_2,....,b_n]\\ a\cdot b=a_1b_1+a_2b_2+....a_nb_n \]

注意:點乘的結果是一個標量

a·b = |a||b|cos∠(a, b)a ,b正交,則 a.b=0

內積的幾何意義

  • 表徵或計算兩個向量之間的夾角
  • b向量在a向量方向上的投影
def inner_prod(point1, point2):
    # 計算 向量內積
    # point1 (x,y)
    # point2 (x,y)
    # x1*x2+y1*y2+...

    return point1[0] * point2[0] + point1[1] * point2[1]

叉積

向量的外積,又稱叉積,是向量代數(解析幾何)中的一個概念

向量叉乘(行列式計算):向量a(x1,y1),向量b(x2,y2) axb=A||B|Sin(θ)

a^b=x1y2-x2y1

若結果大於0,表示向量b在向量a的逆時針方向;若等於0,表示向量a與向量b平行。**

(順逆時針是指兩向量平移至起點相連,從某個方向旋轉到另一個向量小於180度)

計算向量叉積是與直線和線段相關演算法的核心部分

  • 向量的叉積的模表示這兩個向量圍成的平行四邊形的面積
  • a×b(×為向量叉乘),若結果小於0,表示向量b在向量a的順時針方向
  • axb 結果大於0,表示向量b在向量a的逆時針方向
  • 若結果等於 0 則說明 a b 共線
    可能同向,也可能反向
double PointOffLine( Point pt, Point start, Point end )
{
return ((pt.x - end.x)*(start.y - end.y)-(start.x - end.x)*(pt.y - end.y))*1.;
}

def cross(p0, p1, p2):
	"""
	向量a×向量b   (×為向量叉乘)
	向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
	:param p1: 起始點 (x,y)
	:param p2: 終點1  (x,y)
	:param p3: 終點2  (x,y)
	:return:
	"""
	# 計算叉積
	# 若結果小於0,表示向量b在向量a的順時針方向;
	# 若結果大於0,表示向量b在向量a的逆時針方向;
	# 若等於0,表示向量a與向量b 方向重合

	x1 = p1[0] - p0[0]
	y1 = p1[1] - p0[1]
	x2 = p2[0] - p0[0]
	y2 = p2[1] - p0[1]
	return x1 * y2 - x2 * y1

點與點距離

計算平方根

點到直線的距離

  • 方法一、通過點到直線的公式

過點P向線段AB上畫垂線,判斷垂足有沒有落線上段上。如果落線上段上,ok,距離就是垂線段的長度;如果沒有,則距離轉化為點到線段兩端點的距離。

原理

求解直線方程

\[\frac{y_2-y_1}{x_2-x_1}=\frac{y_2-y_1}{x-x_1} \]\[A=y_2-y_1\\ B=x_1-x_2\\ C=x_1*(y_1-y_2) \]

點到直線的距離公式

\[\frac{|A*x0+B*y0+C|}{\sqrt{A^2+B^2}} \]
def get_distance_from_point_to_line(point, line_point1, line_point2):
    """
    :param point:           點座標  (x,y)
    :param line_point1:     線段座標1 (x,y)
    :param line_point2:     線段座標2 (x,y)
    :return: 
    """
    # 對於兩點座標為同一點時,返回點與點的距離
    if line_point1 == line_point2:
        point_array = np.array(point)
        point1_array = np.array(line_point1)
        return np.linalg.norm(point_array - point1_array)
    # 計算直線的三個引數
    A = line_point2[1] - line_point1[1]
    B = line_point1[0] - line_point2[0]
    C = (line_point1[1] - line_point2[1]) * line_point1[0] + \
        (line_point2[0] - line_point1[0]) * line_point1[1]
    # 根據點到直線的距離公式計算距離
    distance = np.abs(A * point[0] + B * point[1] + C) / (np.sqrt(A ** 2 + B ** 2))
    return distance

點到線段的最小距離

  • 方法二、向量法、判斷為位置
  • 先計算AD
\[\overrightarrow{AD}=\frac{\overrightarrow{AP}.\overrightarrow{AB}}{|\overrightarrow{AB}|^2}.|\overrightarrow{AB}|=\frac{\overrightarrow{AP}.\overrightarrow{AB}}{|\overrightarrow{AB}|}.\frac{|\overrightarrow{AB}|}{|\overrightarrow{AB}|} \]

當AD計算結束後,可以根據AD的相應的座標值得到D的座標,在計算CD的長度

判斷投影點D的位置

\[r=\frac{\overrightarrow{AP}.\overrightarrow{AB}}{|\overrightarrow{AB}|^2} \]
如果情況是上圖1所示,那麼0<r<1;
圖2所示的情況,那麼r ≥1;
圖3所示的情況,那麼得到r ≤0;

根據r不同,最短距離也不同

\[d=\begin{cases} |\overrightarrow{AP}|, r<=0\\ |\overrightarrow{BP}|, r>=1\\|\overrightarrow{DP}|, 0<r<1 \end{cases} \]
double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
{
    
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
if (cross <= 0) return Math.Sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
  
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (cross >= d2) return Math.Sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
  
double r = cross / d2;
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return Math.Sqrt((x - px) * (x - px) + (py - y) * (py - y));
}


def inner_prod(point1, point2):
    # 計算 向量內積
    # point1 (x,y)
    # point2 (x,y)
    # x1*x2+y1*y2+...

    return point1[0] * point2[0] + point1[1] * point2[1]


def pointToSegDist(point, l1, l2):
    # 計算點到線段的最小距離 (注意是線段)
    # point (x,y)
    # l1    (x,y)
    # l2    (x,y)
    # r ≤0,         最小長度為 l1point
    # 0 < r < 1;    最小長度為 l2point
    # r ≥1;         point在 l1l2 上的投影

    import math

    x, y = l2[0] - l1[0], l2[1] - l1[1]
    l1l2 = [x, y]
    x, y = point[0] - l1[0], point[1] - l1[1]
    l1point = [x, y]
    cross = inner_prod(l1l2, l1point)       # 計算內積(向量l1l2,向量l1point)
    if (cross <= 0):
        return math.sqrt((point[0] - l1[0]) ** 2 + (point[1] - l1[1]) ** 2)
    d2 = (l2[0] - l1[0]) ** 2 + (l2[1] - l1[1]) ** 2
    if cross >= d2:
        return math.sqrt((point[0] - l2[0]) ** 2 + (point[1] - l2[1]) ** 2)

    r = cross / d2
    px = l1[0] + (l2[0] - l1[0]) * r
    py = l1[1] + (l2[1] - l1[1]) * r

    return np.sqrt((point[0] - px) ** 2 + (point[1] - py) ** 2)


point = [5, -5]
l1 = [0, 0]
l2 = [5, 0]
res = pointToSegDist(point, l1, l2)
print(res)

參考部落格: https://blog.csdn.net/angelazy/article/details/38489293

點線上段上

方法1,計算點到線段的最小距離

點與線段的關係點與線段只有兩種關係

  • 線上段上面
  • 線上段外面

方法2

判斷方法(滿足兩個條件)

  • 點P與線段AB任意端點間的斜率與AB線段斜率相等,點P在直線AB上
  • 確保給定點P的Y座標線上段AB兩個端點的Y座標之間, 確保線上段AB上

注意:當點P為AB端點時,不存在斜率,直接線上段上

def point_isonline(point, l1, l2):
    D = 0
    if point == l1 or point == l2:
        return 1
    try:
        # 防止出現斜率不存在的情況
        slope_pointl1 = round((l1[1] - point[1]) / (l1[0] - point[0]), 7)
        slope_pointl2 = round((l2[1] - point[1]) / (l2[0] - point[0]), 7)

        if slope_pointl1 == slope_pointl2 != 0:
            if l1[0] < point[0] < l2[0] or l1[0] > point[0] > point[0]:
                D = 1
            else:
                D = 0
        elif slope_pointl1 == slope_pointl2 == 0:
            if l1[1] < point[1] < l2[1] or l1[1] > point[1] > point[1]:
                D = 1
            else:
                D = 0
        else:
            D = 0
    except:
        # 斜率不存在
        if point[0] == l1[0] == l2[0] and l1[1] < point[1] < l2[1] or l1[1] > point[1] > point[1]:
            D = 1
        else:
            D = 0
    return D

# point = [0, 5]
# l1 = [0, 0]
# l2 = [5, 5]
# res = point_isonline(point, l1, l2)
# print(res)

線段和線段

假設有兩條線段AB,CD,若AB,CD相交,我們可以確定:

1.線段AB與CD所在的直線相交,即點A和點B分別在直線CD的兩邊;

2.線段CD與AB所在的直線相交,即點C和點D分別在直線AB的兩邊;

同時滿足是兩線段相交的充要條件,可以證明線段AB與CD相交

針對向量AB,CD 相交的衝要條件:

AB×AC > 0;向量AD在向量AB的順勢針方向,AB×AD < 0,兩叉乘結果異號

特殊情況

  • AB CD 共線
  • AB CD 共點(AB CD 共點、或者其中一點在 另一段線段上面)

AB×AD=0, 或者 AB×AC = 0

def segment(l1, l2, p3, p4):
    '''
    :param l1,l2:  線段AB 的起點和終點 l1(x,y) l2(x,y)
    :param p3,p4:  線段CD 的起點和終點 p3(x,y) p4(x,y)
    :return:
    '''
    # 判斷兩線段是否相交
    # 依次判斷CD在AB的兩側,AB在CD的兩側, 需要叉乘的結果異號
    cross_l1l2_l1p3 = cross(l1, l2, p3)
    cross_l1l2_l1p4 = cross(l1, l2, p4)

    cross_p3p4_p3l1 = cross(p3, p4, l1)
    cross_p3p4_p3l2 = cross(p3, p4, l2)

    if cross_l1l2_l1p3 * cross_l1l2_l1p4 <= 0 and cross_p3p4_p3l1 * cross_p3p4_p3l2 <= 0:
        D = 1
    else:
        D = 0
    return D


def cross(p0, p1, p2):
    """
    向量a×向量b   (×為向量叉乘)
    向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
    :param p0: 起始點
    :param p1: 終點1
    :param p2: 終點2
    :return:
    """
    # 計算叉積
    # 若結果小於0,表示向量b在向量a的順時針方向;
    # 若結果大於0,表示向量b在向量a的逆時針方向;
    # 若等於0,表示向量a與向量b 方向重合

    x1 = p1[0] - p0[0]
    y1 = p1[1] - p0[1]
    x2 = p2[0] - p0[0]
    y2 = p2[1] - p0[1]
    return x1 * y2 - x2 * y1

l1=[0,0]
l2=[5,0]
p3=[2,-2]
p4=[2,2]
res=segment(l1, l2, p3, p4)
print(res)

參考文獻:https://www.cnblogs.com/tuyang1129/p/9390376.html

點與多邊形關係

判斷點是否處於多邊形內方法

方法一

叉乘判別法(只適用於凸多邊形)

將凸多邊形中每一個邊AB,與被測點P,求PA×PB判斷結果的符號是否發生變化,如果沒有變化,P在多邊形內;反之點處於凸多邊形外。

def cross(p0, p1, p2):
    """
    向量a×向量b   (×為向量叉乘)
    向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
    :param p0: 起始點
    :param p1: 終點1
    :param p2: 終點2
    :return:
    """
    # 計算叉積
    # 若結果小於0,表示向量b在向量a的順時針方向;
    # 若結果大於0,表示向量b在向量a的逆時針方向;
    # 若等於0,表示向量a與向量b 方向重合

    x1 = p1[0] - p0[0]
    y1 = p1[1] - p0[1]
    x2 = p2[0] - p0[0]
    y2 = p2[1] - p0[1]
    return x1 * y2 - x2 * y1

def point_isin_poly(l1, l2, x1y1, x2y2, x3y3, x4y4):
    # 檢測點 是否在多邊形內部
    # 計算內部的一點和每個多邊形 線段的叉積 判斷叉積的符號是否相同
    # 叉積=0在多邊形邊界上
    cross1 = cross(l1, x1y1, x2y2)
    cross2 = cross(l1, x2y2, x3y3)
    cross3 = cross(l1, x3y3, x4y4)
    cross4 = cross(l1, x4y4, x1y1)
    print(cross1,cross2,cross3,cross4)
    if (cross1 >= 0 and cross2 >= 0 and cross3 >= 0 and cross4 >= 0) or (
            cross1 <= 0 and cross2 <= 0 and cross3 <= 0 and cross4 <= 0):
        D = 1
    else:
        D = 0
    return D

# l1=[5,1.5]
# l2=[5,0]
# x1y1, x2y2, x3y3, x4y4=[0,0],[0,2],[8,2],[8,0]
# res=point_isin_poly(l1, l2, x1y1, x2y2, x3y3, x4y4)
# print(res)

線段與多邊形的關係

class Line_Polygon_Relations():
    def __init__(self):

        pass

    def check_line_poly(self, l1, l2, x1y1, x2y2, x3y3, x4y4):
        '''
        :param l1,l2:  線段 L 的 座標
        :param x1y1:   多邊形 x1234 的座標 座標 順時針方向旋轉
        :param x2y2:
        :param x3y3:
        :param x4y4:
        :return:
        '''

        D = 0

        pxy12 = self.segment(l1, l2, x1y1, x2y2)  # 判斷線段是否和 多邊形線段相交
        pxy23 = self.segment(l1, l2, x2y2, x3y3)
        pxy34 = self.segment(l1, l2, x3y3, x4y4)
        pxy41 = self.segment(l1, l2, x4y4, x1y1)

        # 只能判斷是 線段和多邊形是否相交,如果在多邊形內部需要再次判斷
        if pxy12 or pxy23 or pxy34 or pxy41:
            D = 1
        else:

            # 是否在多邊形內部判斷
            # 計算內部的一點和每個多邊形 線段的叉積 判斷叉積的符號是否相同
            cross1 = self.cross(l1, x1y1, x2y2)
            cross2 = self.cross(l1, x2y2, x3y3)
            cross3 = self.cross(l1, x3y3, x4y4)
            cross4 = self.cross(l1, x4y4, x1y1)

            if (cross1 > 0 and cross2 > 0 and cross3 > 0 and cross4 > 0) or (
                    cross1 < 0 and cross2 < 0 and cross3 < 0 and cross4 < 0):
                D = 1
            else:
                D = 0
        return D

    def segment(self, l1, l2, p3, p4):
        '''
        :param l1,l2:  線段AB 的起點和終點 l1(x,y) l2(x,y)
        :param p3,p4:  線段CD 的起點和終點 p3(x,y) p4(x,y)
        :return:
        '''
        # 判斷兩線段是否相交
        # 依次判斷CD在AB的兩側,AB在CD的兩側
        cross_l1p3_l2p3 = self.cross(l1, l2, p3)
        cross_l1p4_l2p4 = self.cross(l1, l2, p4)
        cross_p3l1_p4l1 = self.cross(p3, p4, l1)
        cross_p3l2_p3l2 = self.cross(p3, p4, l2)

        if cross_l1p3_l2p3 * cross_l1p4_l2p4 <= 0 and cross_p3l1_p4l1 * cross_p3l2_p3l2 <= 0:
            D = 1
        else:
            D = 0

        return D

    def cross(self, p0, p1, p2):
        """

        向量a×向量b   (×為向量叉乘)
        向量 a (p1x-p0x,p1y-p0y) p1->p0    向量 b (p2x-p0x,p2y-p0y) p2->p0
        :param p1:
        :param p2:
        :param p3:
        :return:
        """
        # 計算叉積
        # 若結果小於0,表示向量b在向量a的順時針方向;
        # 若結果大於0,表示向量b在向量a的逆時針方向;
        # 若等於0,表示向量a與向量b 方向重合

        x1 = p1[0] - p0[0]
        y1 = p1[1] - p0[1]
        x2 = p2[0] - p0[0]
        y2 = p2[1] - p0[1]
        return x1 * y2 - x2 * y1



line_poly=Line_Polygon_Relations()
res = line_poly.check_line_poly((5, 4), (10, 2), (0, 0), (10, 10), (20, 5), (10, 0))