前言:

本文主要记录自己在学习过程中遇到的一些几何问题以及其对应的C++实现方法,以作备忘,欢迎交流。

1.判断点到直线距离(平面上):

给定直线上两点java 计算 点到直线的垂直点 编程求点到直线的距离_几何学java 计算 点到直线的垂直点 编程求点到直线的距离_c++_02以及平面上一点java 计算 点到直线的垂直点 编程求点到直线的距离_算法_03,利用点到直线距离公式求出结果,需要先求出直线的一般式方程,再代入公式求解。
输入: N组的三个点坐标java 计算 点到直线的垂直点 编程求点到直线的距离_算法_03java 计算 点到直线的垂直点 编程求点到直线的距离_几何学java 计算 点到直线的垂直点 编程求点到直线的距离_c++_02
输出: 点到直线的距离
代码如下:

#include <iostream>
#include <cmath>
#include <vector>

using namespace std;

//计算点到直线距离首先要算出直线方程,直线方程此处采用一般式: Ax+By+C=0
//假设直线上两点(x1,y1) (x2,y2)
//A=y2-y1 B=x1-x2 C=x2*y1-x1*y2
//求出直线方程后直接带入点到直线距离公式:
//d=|Ax0+By0+C|/sqrt(A^2+B^2)

float getDistanceFromPoint(vector<int>& p0, vector<int>& p1, vector<int>& p2) {
    if (p1[0] == p2[0] && p1[1] == p2[1]) {
        //两点重合,直接按点之间距离计算
        return sqrt((p0[0] - p1[0]) * (p0[0] - p1[0]) + (p0[1] - p1[1]) * (p0[1] - p1[1]));
    }
    //点P0到P1 P2构成直线的距离
    float A = p2[1] - p1[1];
    float B = p1[0] - p2[0];
    float C = p1[1] * p2[0] - p1[0] * p2[1];
    float distance = abs(A * p0[0] + B * p0[1] + C) / sqrt(A * A + B * B);
    return distance;
}

int main()
{
    int n;//多少组数据
    std::cin >> n;
    int p0_x, p0_y, p1_x, p1_y, p2_x, p2_y;
    vector<int> p0, p1, p2;
    float dis = 0.0;
    for (int i = 0; i < n; i++) {
        std::cin >> p0_x >> p0_y;
        std::cin >> p1_x >> p1_y;
        std::cin >> p2_x >> p2_y;
        p0 = { p0_x, p0_y };
        p1 = { p1_x, p1_y };
        p2 = { p2_x, p2_y };
        dis = getDistanceFromPoint(p0, p1, p2);
        cout << dis << endl;
    }
}

2.判断点与三角形的关系(平面上):

给定一个点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07和三角形三个顶点java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_08java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_09java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_10,判断点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07是否在三角形内。

2.1 同向法

主要考虑使用同向法(思路参考自此处,原文是三维情况,这里我只考虑平面)进行计算:假设点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07位于三角形内会有这样的一个规律,当沿着java 计算 点到直线的垂直点 编程求点到直线的距离_ci_13的方向在三条边上行走时,会发现点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07始终位于边java 计算 点到直线的垂直点 编程求点到直线的距离_算法_15java 计算 点到直线的垂直点 编程求点到直线的距离_java 计算 点到直线的垂直点_16java 计算 点到直线的垂直点 编程求点到直线的距离_c++_17的右侧。但是如何判断一个点在线段的左侧还是右侧呢?当选定线段java 计算 点到直线的垂直点 编程求点到直线的距离_算法_15时,点java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_10位于java 计算 点到直线的垂直点 编程求点到直线的距离_算法_15的右侧,同理选定java 计算 点到直线的垂直点 编程求点到直线的距离_java 计算 点到直线的垂直点_16时,点java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_08位于java 计算 点到直线的垂直点 编程求点到直线的距离_java 计算 点到直线的垂直点_16的右侧,最后选定java 计算 点到直线的垂直点 编程求点到直线的距离_c++_17时,点java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_09位于java 计算 点到直线的垂直点 编程求点到直线的距离_c++_17的右侧,所以当选择某一条边时,我们只需验证点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07与该边所对的点在同一侧即可。如何判断两个点在某条线段的同一侧呢?可以通过叉积来实现,连接java 计算 点到直线的垂直点 编程求点到直线的距离_c++_28,将java 计算 点到直线的垂直点 编程求点到直线的距离_c++_28java 计算 点到直线的垂直点 编程求点到直线的距离_算法_15做叉积,再将java 计算 点到直线的垂直点 编程求点到直线的距离_c++_17java 计算 点到直线的垂直点 编程求点到直线的距离_算法_15做叉积,如果两个叉积的结果方向一致,那么两个点在同一测。判断两个向量的是否同向可以用点积实现,如果点积大于0,则两向量夹角是锐角,否则是钝角。
输入: N组的四个点坐标java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_08java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_09java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_10java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07
输出: 点与三角形的关系
代码如下:

#include <iostream>

using namespace std;

class Vector {
public:
    float x, y;
    Vector(int _x, int _y) : x(_x), y(_y) {}

    Vector operator - (Vector v) {
        return Vector(x - v.x, y - v.y);
    }

    float Dot(Vector& v) {
        return x * v.x + y * v.y;
    }

    int Cross(Vector v) {
        return x * v.y - y * v.x;//二维向量叉积等于两向量构成的平行四边形面积
    }
};

bool OnPoint(Vector& A, Vector& B, Vector& C, Vector& P) {
    //判断点P是否在三个顶点上
    if (A.x == P.x && A.y == P.y) {
        return true;//点P与点A重合
    }
    if (B.x == P.x && B.y == P.y) {
        return true;//P与B重合
    }
    if (C.x == P.x && C.y == P.y) {
        return true;//P与C重合
    }
    return false;
}

bool OnEdge(Vector& A, Vector& B, Vector& C, Vector& P) {
    Vector PA = P - A;
    Vector PB = P - B;
    Vector PC = P - C;
    if (PA.Cross(PB) == 0 && min(A.x, B.x) <= P.x && min(A.y, B.y) <= P.y && max(A.x, B.x) >= P.x && max(A.y, B.y) >= P.y) {
        return true;//P在AB上
    }
    if (PB.Cross(PC) == 0 && min(B.x, C.x) <= P.x && min(B.y, C.y) <= P.y && max(B.x, C.x) >= P.x && max(B.y, C.y) >= P.y) {
        return true;//P在BC上
    }
    if (PA.Cross(PC) == 0 && min(A.x, C.x) <= P.x && min(A.y, C.y) <= P.y && max(A.x, C.x) >= P.x && max(A.y, C.y) >= P.y) {
        return true;//P在AC上
    }
    return false;//不在边上
}

bool SameSide(Vector& A, Vector& B, Vector& C, Vector& P) {
    //判断点P是否与点C都在线段AB的同侧
    Vector AB = A - B;
    Vector AP = A - P;
    Vector AC = A - C;

    float v1, v2;
    v1 = AB.Cross(AP);
    v2 = AB.Cross(AC);

    return v1 * v2 > 0;//大于0说明AP与AC在AB的同一侧
}

bool InTriangle(Vector& A, Vector& B, Vector& C, Vector& P) {
    return SameSide(A, B, C, P) && SameSide(B, C, A, P) && SameSide(C, A, B, P);
}


int main()
{
    // 输入格式:n为需要判断的个数
    // 接下来每4行一组分别为三角形的三个点和目标点的横纵坐标
    FILE* stream;
    freopen_s(&stream, "input.txt", "r", stdin);

    int n = 0;
    float ax, ay, bx, by, cx, cy, px, py;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> ax >> ay;
        cin >> bx >> by;
        cin >> cx >> cy;
        cin >> px >> py;
        Vector A(ax, ay);
        Vector B(bx, by);
        Vector C(cx, cy);
        Vector P(px, py);
        if (OnPoint(A, B, C, P)) {
            cout << "点在顶点" << endl;
            continue;
        }
        if (OnEdge(A, B, C, P)) {
            cout << "点在边上" << endl;
            continue;
        }
        if (InTriangle(A, B, C, P)) {
            cout << "点在三角形内" << endl;
            continue;
        }
        else {
            cout << "点在三角形外" << endl;
        }
    }
    return 0;
}

此处需要读输入文件input.txt,格式如下:

5

0 3
4 0
0 0
2 2

0 0
0 3
4 0
2 2

0 3
0 0
4 0
2 2

0 3
4 0
0 0
0 2

0 3
4 0
0 0
2 1

2.2 简洁版

#include <iostream>;
using namespace std;
class Vector2D{
public:
    Vector2D(float x, float y) {
        mx = x;
        my = y;
    }
    Vector2D operator - (Vector2D v) {
        return Vector2D(mx - v.mx, my - v.my);
    }
    float cross(Vector2D v) {
        return mx * v.my - v.mx * my;
    }

private:
    float mx;
    float my;
};


bool isInTriangle(float Ax, float Ay, float Bx, float By, float Cx, float Cy, float Px, float Py) {
    Vector2D A = Vector2D(Ax, Ay);
    Vector2D B = Vector2D(Bx, By);
    Vector2D C = Vector2D(Cx, Cy);
    Vector2D P = Vector2D(Px, Py);
    Vector2D PA = A - P;
    Vector2D PB = B - P;
    Vector2D PC = C - P;
    float t1 = PA.cross(PB);
    float t2 = PB.cross(PC);
    float t3 = PC.cross(PA);

    return t1 * t2 >= 0 && t1 * t3 >= 0;

}



int main() {
    bool res = isInTriangle(0, 0, 0, 3, 4, 0, 2, 2);
    cout << res << endl;
}

3.判断点和多边形的关系(平面):

给定一个点和一组多边形的各顶点,判断该点与多边形的关系。
主要考虑射线法(参考处)时间复杂度:O(n)。以被测点P为端点,向任意方向作射线(一般水平向右作射线),统计该射线与多边形的交点数。如果为奇数,P在多边形内;如果为偶数,P在多边形外。特殊情况可以参考链接。
输入: 多边形的N个点坐标
输出: 点与多边形的关系
代码如下:

#include <iostream>
#include <vector>

using namespace std;

//射线法判断点是否在多边形内

const double eps = 1e-6;//浮点数判断阈值

class Point {
public:
    double x, y;
    Point() : x(0), y(0) {}
    Point(double _x, double _y) : x(_x), y(_y) {}

    Point operator +(Point v) {//向量加法
        return Point(x + v.x, y + v.y);
    }

    Point operator -(Point v) {//向量减法
        return Point(x - v.x, y - v.y);
    }

    double operator *(Point v) {//点积
        return x * v.x + y * v.y;
    }

    double operator ^(Point v) {//叉积
        return x * v.y - v.x * y;
    }
};

int dcmp(double x) {
    //三态函数,判断两个double在eps精度下的大小关系
    if (fabs(x) < eps) return 0;//认为两者相等
    else {
        return x < 0 ? -1 : 1;
    }
}

bool inSegment(Point& q, Point& p1, Point& p2) {
    //判断Q是否在p1和p2的线段上
    //第一个利用向量QP1和QP2叉积为零表明Q与P1 P2共线
    //第二个利用向量点积为负表明Q在两者之间
    return dcmp((p1 - q) ^ (p2 - q)) == 0 && dcmp((p1 - q) * (p2 - q)) <= 0;
}

bool inPolygon(vector<Point>& polygon, Point p) {
    //判断点P在多边形内
    bool flag = false;//相当于记录交点数
    for (int i = 0; i < polygon.size(); i++) {
        Point p1 = polygon[i];
        Point p2 = polygon[(i + 1) % polygon.size()];
        if (inSegment(p, p1, p2)) return true;//点在多边形边上,也认为在多边形内
        //前一个判断: min(P1.y,P2.y) < P.y <= max(P1.y,P2.y)
        //后一个判断被测点p在射线与边交点的左边
        if ((dcmp(p1.y - p.y) > 0 != dcmp(p2.y - p.y) > 0) && dcmp(p.x - (p.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) - p1.x) < 0) {
            flag = !flag;
        }
    }
    return flag;
}

int main()
{
    //测试用例输入:
    // 4
    // 10 10
    // 20 10
    // 20 5
    // 10 5
    // 15 8--测试点(25 8)
    //输出:
    // Inside (Outside)

    int n;
    cin >> n;//顶点个数
    vector<Point> polygon(n);
    for (int i = 0; i < n; i++) {
        cin >> polygon[i].x >> polygon[i].y;//一行输入一个点的坐标x y
    }
    Point p;//测试点
    cin >> p.x >> p.y;
    if (inPolygon(polygon, p)) {
        cout << "Inside" << endl;
    }
    else {
        cout << "Outside" << endl;
    }
}

4.判断两个矩形是否相交(平面):

4.1不考虑矩形旋转

即矩形上下边平行于X轴,左右边平行与Y轴,可以参考 力扣836矩形重叠 问题,但注意,力扣题认为如果相交的面积为正,则称两矩形重叠,只在角或边接触的两个矩形不构成重叠。但对于判断相交的情况应该是算做相交的。
以下为官方题解思路:
我们尝试分析在什么情况下,矩形 rec1 和 rec2 没有重叠。如果矩形 rec1 和 rec2 中至少有一个矩形的面积为 0,则一定没有重叠。当矩形 rec1 和 rec2 的面积都大于 0 时,如果我们在平面中放置一个固定的矩形 rec2,那么矩形 rec1 必须要出现在 rec2 的「四周」,也就是说,矩形 rec1 需要满足以下四种情况中的至少一种:

  • 矩形 rec1 在矩形 rec2 的左侧;
  • 矩形 rec1 在矩形 rec2 的右侧;
  • 矩形 rec1 在矩形 rec2 的上方;
  • 矩形 rec1 在矩形 rec2 的下方。

何为「左侧」?如果矩形 rec1 在矩形 rec2 的左侧,那就表示我们可以找到一条竖直的线(可以与矩形的边重合),使得矩形 rec1 和 rec2 被分在这条竖线的两侧。对于「右侧」、「上方」以及「下方」,它们的定义与「左侧」是类似的。
算法:
首先判断矩形 rec1 和 rec2 的面积是否为 0。

  • 对于矩形 rec1 而言,其面积为 0 当且仅当 rec1[0] == rec1[2] 或 rec1[1] == rec1[3];
  • 对于矩形 rec2 而言,其面积为 0 当且仅当 rec2[0] == rec2[2] 或 rec2[1] == rec2[3]。

如果至少有一个矩形的面积为 0,则一定没有重叠。如果矩形 rec1 和 rec2 的面积都大于 0,则考虑两个矩形的位置。我们将上述四种情况翻译成代码。具体地,我们用 (rec[0], rec[1]) 表示矩形的左下角,(rec[2], rec[3]) 表示矩形的右上角,与题目描述一致。对于「左侧」,即矩形 rec1 在 x 轴上的最大值不能大于矩形 rec2 在 x 轴上最小值。对于「右侧」、「上方」以及「下方」同理。因此代码如下:

class Solution {
public:
    bool isRectangleOverlap(vector<int>& rec1, vector<int>& rec2) {
        if (rec1[0] == rec1[2] || rec1[1] == rec1[3] || rec2[0] == rec2[2] || rec2[1] == rec2[3]) {
            return false;
        }
        return !(rec1[2] <= rec2[0] ||   // left
                 rec1[3] <= rec2[1] ||   // bottom
                 rec1[0] >= rec2[2] ||   // right
                 rec1[1] >= rec2[3]);    // top
    }
};

4.2考虑矩形旋转的情况

第一种不考虑矩形选择的情况,判断方法比较简单,网上也有比较多的解释,我这里主要考虑矩形旋转时判断是否相交。主要使用分离轴定律(参考
输入: 两个矩形的4个点坐标
输出: 两个矩形是否相交
代码如下:

#include <iostream>
#include <vector>

using namespace std;

//求解方法:分离轴定律
//两个凸多边形物体,如果我们能找到一个轴,使得两个在物体在该轴上的投影互不重叠,
//则这两个物体之间没有碰撞发生,该轴为Separating Axis。
//也就是说两个多边形在所有轴上的投影都发生重叠,则判定为碰撞;否则,没有发生碰撞。
//矩形有4条边,那么就有4条轴,由于矩形的对边是平行的,所以有两条轴是重复的,
//我们仅需要检查相邻的两个轴,那么两个矩形就需要检查4个轴。
// 
//检查投影的方法有两种:
// 1.把每个矩形的4个顶点投影到一个轴上,这样算出4个顶点最长的连线距离,然后同样对待第二个矩形,最后判断2个矩形投影距离是否重叠(程序使用)
// 2.把2个矩形的半径距离投影到轴上,以后把2个矩形的中心点连线投影到轴上,以后判断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小
//

class Vector2D {
public:
	double x, y;

	Vector2D(double _x, double _y) {
		x = _x;
		y = _y;
	}

	Vector2D operator -(Vector2D& v) {
		return Vector2D(x - v.x, y - v.y);
	}

	double dot(Vector2D& v) {//与向量v点乘
		return x * v.x + y * v.y;
	}

	double norm() {//向量自身模的平方
		return x * x + y * y;
	}

	Vector2D project(Vector2D& v) {
		// 顶点u向量向轴v投影,返回投影后的新向量
		Vector2D u = Vector2D(x, y);
		double newX = u.dot(v) * v.x / v.norm();
		double newY = u.dot(v) * v.y / v.norm();
		return Vector2D(newX, newY);
	}
};

//矩形定义为按顺时针或逆时针输入的四个点坐标
class Rectangle {
public:
	vector<Vector2D> point;//矩形的四个顶点
	Rectangle(double  _x1, double _y1, double _x2, double _y2, double _x3, double _y3, double _x4, double _y4) {
		point.emplace_back(Vector2D(_x1, _y1));
		point.emplace_back(Vector2D(_x2, _y2));
		point.emplace_back(Vector2D(_x3, _y3));
		point.emplace_back(Vector2D(_x4, _y4));
	}

	Vector2D getSideVec() {//获取(x1,y1)和(x2,y2)所构成的边的向量, 方向(x1,y1)-->(x2,y2)
		return Vector2D(point[1].x - point[0].x, point[1].y - point[0].y);
	}
	Vector2D getNeighborVec() {//获取相邻边即(x2,y2)和(x3,y3)边对应的向量,方向(x3,y3)-->(x2,y2)
		return Vector2D(point[1].x - point[2].x, point[1].y - point[2].y);
	}
};


bool isIntersect(Rectangle& r1, Rectangle& r2) {
	bool res = false;
	// 1.将每个矩形相邻两边的向量作为投影基准
	vector<Vector2D> axis;//基准轴集合
	axis.push_back(r1.getSideVec());
	axis.push_back(r1.getNeighborVec());
	axis.push_back(r2.getSideVec());
	axis.push_back(r2.getNeighborVec());

	// 2.将两个矩形每个顶点分别向投影轴进行投影,
	// 如果所有轴显示重叠,则存在碰撞,如果有其中一个轴显示没有重叠,则没有碰撞可以之间结束判断
	for (int i = 0; i < axis.size(); i++) {
		//将矩形1的四个点(Point[j])投影到轴(axis[i])上
		double r1_max = DBL_MIN;
		double r1_min = DBL_MAX;
		for (int j = 0; j < 4; j++) {
			Vector2D newPoint = r1.point[j].project(axis[i]);
			// 3.计算标量值以识别矩形在轴上的最大和最小投影向量
			// 虽然使用向量的范数(长度)似乎很自然,但这不起作用因为具有负值的坐标将返回正标量值。
			// 因此,采用每个向量和轴的点积,虽然是毫无意义的标量值,但是该值将指示向量在轴上的位置。
			double value = newPoint.dot(axis[i]);
			r1_max = max(r1_max, value);
			r1_min = min(r1_min, value);
		}

		//将矩形2的四个点投影到轴上
		double r2_max = DBL_MIN;
		double r2_min = DBL_MAX;
		for (int j = 0; j < 4; j++) {
			Vector2D newPoint = r2.point[j].project(axis[i]);
			double value = newPoint.dot(axis[i]);
			r2_max = max(r2_max, value);
			r2_min = min(r2_min, value);
		}

		// 4.如果B的最小标量值小于或等于A的最大标量值
		// 并且B的最大标量值是大于或等于A的最小标量值,则两个矩形在投影到该轴时重叠
		if (r2_min <= r1_max && r2_max >= r1_min) {
			res = true;
		}
		else {
			res = false;
			return res;
		}
	}
	return res;
}



int main()
{
	//初始化两个矩形的类
	Rectangle r1 = Rectangle(0.0, 0.0, 3.2, 2.4, 5.0, 0.0, 1.8, -2.4);
	Rectangle r2 = Rectangle(0.0, 0.0, -3.2, 2.4, -5.0, 0.0, -1.8, -2.4);
	bool res = isIntersect(r1, r2);
	if (res) {
		cout << "相交" << endl;
	}
	else {
		cout << "不相交" << endl;
	}
}

或者检查投影时把2个矩形的半径距离投影到轴上,然后把2个矩形的中心点连线投影到轴上,判断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小。
输入: 两个矩形的左上角顶点、长、宽、旋转角度
输出: 两个矩形是否相交
代码如下:

#include <iostream>
#include <vector>

using namespace std;

//求解方法:分离轴定律
//两个凸多边形物体,如果我们能找到一个轴,使得两个在物体在该轴上的投影互不重叠,
//则这两个物体之间没有碰撞发生,该轴为Separating Axis。
//也就是说两个多边形在所有轴上的投影都发生重叠,则判定为碰撞;否则,没有发生碰撞。
//矩形有4条边,那么就有4条轴,由于矩形的对边是平行的,所以有两条轴是重复的,
//我们仅需要检查相邻的两个轴,那么两个矩形就需要检查4个轴。
// 
//检查投影的方法有两种:
// 1.把每个矩形的4个顶点投影到一个轴上,这样算出4个顶点最长的连线距离,然后同样对待第二个矩形,最后判断2个矩形投影距离是否重叠
// 2.把2个矩形的半径距离投影到轴上,以后把2个矩形的中心点连线投影到轴上,以后判断2个矩形的中心连线投影,和2个矩形的半径投影之和的大小
//

class Vector2D {
public:
	double x, y;

	Vector2D(double _x, double _y) {
		x = _x;
		y = _y;
	}
	Vector2D operator -(Vector2D& v) {
		return Vector2D(x - v.x, y - v.y);
	}
	double dot(Vector2D& v) {//向量点乘
		return x * v.x + y * v.y;
	}
	double norm(Vector2D& v) {//向量模
		return sqrt(v.x * v.x + v.y * v.y);

	}
	double project(Vector2D& v) {
		// u * v = |u| |v| cos 
		// |u| cos = u * v / |v|
		// u向v投影
		Vector2D u = Vector2D(x, y);
		return u.dot(v) / norm(v);
	}
};

// (x,y)       x+width
// ---------------
// |             |
// |             |
// |             |
// |             |
// ---------------
// x+length
class Rectangle {
public:
	double x, y, length, width, rotation;

	Rectangle(double _x, double _y, double _length, double _width, double _rotation) {//定义矩形
		x = _x;//x和y为左上角顶点
		y = _y;
		length = _length; // y 长为沿y方向的边的长度
		width = _width; // x 宽为沿x方向的边的长度
		rotation = _rotation;
	}
	Vector2D getWidthVec() {//获取宽边对应的向量
		return Vector2D(cos(rotation), sin(rotation));
	}
	Vector2D getLengthVec() {//获取长边对应的向量,因为是从顶点出发所以纵坐标反向
		return Vector2D(sin(rotation), -cos(rotation));
	}
	Vector2D getCenterPoint() {//获取中心点对应的向量
		double centerX = x + (length * cos(rotation) - width * sin(rotation)) / 2.0;
		double centerY = y + (length * sin(rotation) + width * cos(rotation)) / 2.0;
		return Vector2D(centerX, centerY);
	}
	Vector2D getHalfDiagonalVec() {//获取对角线一半的向量
		return Vector2D((length * cos(rotation) - width * sin(rotation)) / 2.0,
			(length * sin(rotation) + width * cos(rotation)) / 2.0);
	}
};


bool isIntersect(Rectangle& r1, Rectangle& r2) {
	// 1. 以任意一条矩形的边向量作为投影基准
	vector<Vector2D> v;
	v.push_back(r1.getWidthVec());
	v.push_back(r1.getLengthVec());
	v.push_back(r2.getWidthVec());
	v.push_back(r2.getLengthVec());

	// 2. 获得两个矩形的对角线半个向量
	Vector2D d1 = r1.getHalfDiagonalVec();
	Vector2D d2 = r2.getHalfDiagonalVec();

	// 3. 获得两个矩形的中心点向量
	Vector2D c1 = r1.getCenterPoint();
	Vector2D c2 = r2.getCenterPoint();
	cout << c1.x << " " << c1.y << "," << c2.x << " " << c2.y << endl;
	Vector2D c = c2 - c1;

	for (int i = 0; i < 4; i++) {
		// 4. 每次选择一个基准向量,将两个矩形的对角线向量和中心线向量投影
		double p1 = d1.project(v[i]);
		double p2 = d2.project(v[i]);
		double p = c.project(v[i]);

		// 5. 投影和的距离关系
		// cout << p1 << " " << p2 << " " << p << endl;
		if (abs(p1 + p2) < abs(p)) return false;
	}
	return true;

}



int main()
{
	// 初始化两个矩形的类
	Rectangle r1 = Rectangle(1, 0, sqrt(2), sqrt(2), 3.14 / 4);
	Rectangle r2 = Rectangle(3, 0, sqrt(2), sqrt(2), 3.14 / 4);


	bool res = isIntersect(r1, r2);
	if (res) cout << "相交" << endl;
	else cout << "不相交" << endl;

}

5.判断点在线段上的投影点是否在线段内:

输入: N组的线段两端点坐标、点P坐标
输出: 投影点在线段左、右、上。
代码如下:

#include <iostream>;
using namespace std;
class Vector {
public:
	// position: (x, y) --> (v.x, v.y)
	float x;
	float y;
	Vector(float _x, float _y) {
		x = _x;
		y = _y;
	}

	Vector operator - (Vector& v) {
		return Vector(x - v.x, y - v.y);
	}

	float dot(Vector& v) {
		return x * v.x + y * v.y;
	}
};

int PointRelations(Vector A, Vector B, Vector P) {
	Vector AB = B - A;
	Vector AP = P - A;

	float v1 = AB.dot(AP);
	float v2 = AB.dot(AB);
	if (v1 < 0) return 0;
	else {
		if (v1 < v2) return 1;
		else return 2;
	}
	return -1;

}



int main() {
// input:
// n
// ax, ay
// bx, by
// px, py

// output: left/right/middle
	FILE* stream;
	freopen_s(&stream, "input.txt", "r", stdin);
	int n = 0;
	float ax, ay, bx, by, px, py;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> ax >> ay;
		cin >> bx >> by;
		cin >> px >> py;
		Vector A(ax, ay);
		Vector B(bx, by);
		Vector P(px, py);
		int res = PointRelations(A, B, P);

		if (res == 0) {
			cout << "left" << endl;
		}
		else if (res == 1) {
			cout << "middle" << endl;
		}
		else if (res == 2) {
			cout << "right" << endl;
		}
		else {
			cout << "Value Error: Please check the dimension of input parameters." << endl;
		}
	}
}

6.点与直线的关系:

判断点在直线左侧还是右侧。考虑向量叉乘实现。
输入: N组的直线上两点A、B坐标、点P坐标
输出: 点在直线左、右侧。
代码如下:

#include <iostream>;
using namespace std;
class Vector {
public:
	// position: (x, y) --> (v.x, v.y)
	float x;
	float y;
	Vector(float _x, float _y) {
		x = _x;
		y = _y;
	}

	Vector operator - (Vector& v) {
		return Vector(x - v.x, y - v.y);
	}

	float cross(Vector& v) {
		return x * v.y - y * v.x;
	}
};

bool SameSide(Vector A, Vector B, Vector P) {
	Vector AB = B - A;
	Vector AP = P - A;


	float v1 = AB.cross(AP);

	return v1 > 0;//大于0说明P在AB的逆时针方向
}



int main() {
	// input:
	// n
	// ax, ay
	// bx, by
	// px, py

	// output: left/right
	FILE* stream;
	freopen_s(&stream, "input.txt", "r", stdin);
	int n = 0;
	float ax, ay, bx, by, px, py;
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> ax >> ay;
		cin >> bx >> by;
		cin >> px >> py;
		Vector A(ax, ay);
		Vector B(bx, by);
		Vector P(px, py);
		bool res = SameSide(A, B, P);

		if (res) {
			cout << "left" << endl;
		}
		else {
			cout << "right" << endl;
		};
	}

}

7.折线段等分

给定一组折线段,同时给一个整数k,要用k个点将这组折线段等分,求这k个点的坐标。
输入: 一组折线段(按点的形式给出,点与点之间认为是一条线段)
输出: k个点的坐标
思路:先求出这组折线段的总长度,用k个点等分,那就是分成k+1段,得到每段等分的长度。再对每条线段从起点开始,计算等距离的分离点,但需要注意细节(长度如果超出当前线段,需要从下一条线段上接着算)。
代码如下:

#include<iostream>
#include<vector>
using namespace std;

double eps = 1e-8;

double getDistance(vector<double>& point1, vector<double>& point2) {
    // 计算两点间距离
    return sqrt((point1[0] - point2[0]) * (point1[0] - point2[0]) + (point1[1] - point2[1]) * (point1[1] - point2[1]));
}

vector<vector<double>> getSeperatePoints(vector<vector<double>>& points, int k) {
    // k表示要将这组折线段分成k+1段
    vector<vector<double>> res;//最终的k个点坐标
    // 1. 计算总的折线长度,并且计算出每一段的平均长度
    double totalLen = 0;
    for (int i = 1; i < points.size(); i++) {
        totalLen += getDistance(points[i - 1], points[i]);
    }
    double avgLen = totalLen / (k + 1.0); // 等分的长度
     //cout << "avgLen = " << avgLen << endl;
    double curLen = avgLen; // 记录距离当前线段起点的距离
    double x1 = points[0][0];// 取出第一个点作为当前线段起点
    double y1 = points[0][1];
    int count = 0; // 统计累计点的个数
     // 2. 计算每个等分点
    for (int j = 1; j < points.size(); j++) {
        double x2 = points[j][0];
        double y2 = points[j][1];
        double line_distance = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2)); // 计算每一个单独线段的长度
         //cout << "line_distance: " << line_distance << endl;
        while (curLen <= line_distance) {
            //当前长度还没有超出整个线段,继续在当前线段上计算分离点坐标
            double x = x1 + (curLen / line_distance) * (x2 - x1);
            double y = y1 + (curLen / line_distance) * (y2 - y1);
             //cout << "x = " << x << " y = " << y << endl;
            if (abs(x - x2) < eps && abs(y - y2) < eps && j == points.size() - 1) {
                // 此时第k+1个等分点和这组线段的最后一个点重合,不应放在结果中
                break;
            }
            res.push_back({ x, y });//保存分离点
            curLen += avgLen;//加上等分距离方便进行下一个点的计算
        }
        //此时已经超出线段长度,需要从下一段开始计算
        curLen -= line_distance;//减掉线段长度得到超出部分的长度
        x1 = x2;//当前线段起点更新
        y1 = y2;
    }

    return res;
}

int main() {

    //vector<vector<double>> points = { {0,0},{1,0},{2,0},{4,0} };
    //double k = 2;//k个等分点,把一组折线段分成k+1段
    vector<vector<double>> points = { {0, 0}, {-1, 1}, {-2, 2}, {-3, 3}, {-6, 0} };
    int k = 5;
    vector<vector<double>> res = getSeperatePoints(points, k);

    for (int i = 0; i < res.size(); i++) {
        cout << res[i][0] << " " << res[i][1] << endl;
    }

    return 0;
}

8.最小覆盖圆(洛谷P1742):

给出N个点,让你画一个最小的包含所有点的圆。
采用随即增量法,期望复杂度为java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_37,需要先将所有点随机打乱。
输入: 点数N,后N行为各点坐标
输出: 圆心坐标,以及半径
代码如下:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

//最小覆盖圆:
//给出N个点,画一个最小的包含所有点的圆
//输入:点数N,再一行给各点坐标
//输出:圆的半径、圆心坐标

double eps = 1e-8;

class Node {
public:
    double x, y;

    Node() : x(0), y(0) {}
    Node(double _x, double _y) : x(_x), y(_y) {}
};

int sgn(double x) {
    if (abs(x) < eps) return 0;
    return x < 0 ? -1 : 1;
}

double getDistance(Node A, Node B) {//求两点距离
    return sqrt(pow(A.x - B.x, 2) + pow(A.y - B.y, 2));
}

Node circle_center(Node a, Node b, Node c) {
    //求三角形外接圆圆心,两条边的垂直平分线交点就是
    double a1 = b.x - a.x, b1 = b.y - a.y, c1 = (a1 * a1 + b1 * b1) / 2.0;//第一条边的中垂线
    double a2 = c.x - a.x, b2 = c.y - a.y, c2 = (a2 * a2 + b2 * b2) / 2.0;
    double d = a1 * b2 - a2 * b1;
    double x = a.x + (c1 * b2 - c2 * b1) / d;//圆心坐标
    double y = a.y + (a1 * c2 - a2 * c1) / d;
    Node center = Node(x, y);
    return center;
}

Node min_cover_circle(vector<Node>& points) {
    random_shuffle(points.begin(), points.end());//打乱所有点
    double r = 0.0;//半径
    Node center = points[0];//圆心
    for (int i = 1; i < points.size(); i++) {//剩下所有点
        if (sgn(getDistance(points[i], center) - r) > 0) {//pi在园外
            center = points[i];
            r = 0.0;
            for (int j = 0; j < i; j++) {//重新检查前面的点
                if (sgn(getDistance(points[j], center) - r) > 0) {
                    //两点定圆
                    center.x = (points[i].x + points[j].x) / 2.0;
                    center.y = (points[i].y + points[j].y) / 2.0;
                    r = getDistance(points[j], center);
                    for (int k = 0; k < j; k++) {//检查第三个点
                        if (sgn(getDistance(points[k], center) - r) > 0) {
                            center = circle_center(points[i], points[j], points[k]);
                            r = getDistance(points[i], center);
                        }
                    }
                }
            }
        }
    }
    cout << "Radius is: " << r << endl;
    return center;
}

int main()
{
    int n;
    cin >> n;
    vector<Node> points;
    for (int i = 0; i < n; i++) {
        Node node = Node();
        cin >> node.x >> node.y;
        points.push_back(node);
    }

    Node res = min_cover_circle(points);
    cout << "x = " << res.x << " y = " << res.y;
    return 0;
}

9.圆(洛谷P1652)

给出java 计算 点到直线的垂直点 编程求点到直线的距离_c++_38个圆,保证任意两个圆都不相交且不相切。然后给出两个点java 计算 点到直线的垂直点 编程求点到直线的距离_java 计算 点到直线的垂直点_39,保证均不在某个圆上。现在要从java 计算 点到直线的垂直点 编程求点到直线的距离_ci_40画条曲线,问这条曲线最少穿过多少次圆的边界?

输入: 第一行为一个整数n,表示圆的个数;第二行是n个整数,表示n个圆的x坐标;第三行是n个整数,表示n个圆的y坐标;第四行是n个整数,表示n个圆的半径;第五行四个整数java 计算 点到直线的垂直点 编程求点到直线的距离_ci_41
输出: 仅一个整数,表示最少要穿过多少次圆的边界。

思路:答案就是所有把两个点分隔的圆的个数,也就是其中一个在圆内,另一个在圆外的圆的个数。判断一个在圆内,另一个在圆外时,可以用异或,看两点到圆心的距离是否小于(不能等于)半径。
代码如下:

#include <iostream>
#include <vector>

using namespace std;

double getDistance(int x1, int y1, int x2, int y2) {
	return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

int main()
{
	//输入:
	//7
	//1 -3 2 5 -4 12 12
	//1 -1 2 5 5 1 1
	//8 1 2 1 1 1 2
	//-5 1 12 1
	//输出:3
	
	int n;//n个圆
	cin >> n;
	vector<int> x(n);
	vector<int> y(n);
	vector<int> r(n);
	int x1, y1, x2, y2;
	for (int i = 0; i < n; i++) {
		cin >> x[i];
	}
	for (int i = 0; i < n; i++) {
		cin >> y[i];
	}
	for (int i = 0; i < n; i++) {
		cin >> r[i];
	}
	cin >> x1 >> y1 >> x2 >> y2;
	int res = 0;
	for (int i = 0; i < n; i++) {
		if ((getDistance(x1, y1, x[i], y[i]) < r[i]) ^ (getDistance(x2, y2, x[i], y[i]) < r[i])) {
			res++;
		}
	
	}
	cout << res << endl;

	return 0;
}

10.移动的圆(炼码21)

题目将给出两个圆java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_08java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_09的圆心坐标java 计算 点到直线的垂直点 编程求点到直线的距离_c++_44和半径java 计算 点到直线的垂直点 编程求点到直线的距离_c++_45,现给你一个点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07,使圆java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_08圆心沿直线运动至点java 计算 点到直线的垂直点 编程求点到直线的距离_c++_07。请问圆A在运动过程中是否会与圆B相交?(运动过程包括起点和终点),相交返回1,否则返回-1。

输入: 一组点:java 计算 点到直线的垂直点 编程求点到直线的距离_算法_49
输出: 是否相交
代码如下:

class Solution {
public:
    /**
     * @param position: the position of circle A,B and point P.
     * @return: if two circle intersect return 1, otherwise -1.
     */
    int ifIntersect(vector<double> &position) {
        // 这里可以将连线轨迹形成一个矩形,判断矩形和B是否相交。然后在起点和终点特殊判断
        double xA = position[0];//圆A横坐标
        double yA = position[1];//圆A纵坐标
        double rA = position[2];
        double xB = position[3];
        double yB = position[4];
        double rB = position[5];
        double xP = position[6];
        double yP = position[7];

        double disAB = sqrt((xA - xB) * (xA - xB) + (yA - yB) * (yA - yB));//移动前A B圆心的距离
        double disPB = sqrt((xP - xB) * (xP - xB) + (yP - yB) * (yP - yB));//移动后两圆的圆心距离
        // 两个圆相交的充要条件是两圆圆心距大于两半径差的绝对值,小于两半径和,相切就加上等于。
        double minDis = abs(rA - rB);
        double maxDis = rA + rB;

        // 先判断起点和终点是否相交
        if ((minDis <= disAB && disAB <= maxDis) || (minDis <= disPB && disPB <= maxDis)) {
            return 1;
        } 

        // 求B到AP的垂足
        // 先求出AP直线方程
        double a = yP - yA;
        double b = xA - xP;
        double c = xP * yA - xA * yP;
        // B到AP的距离
        double dist = abs(a * xB + b * yB + c) / sqrt(a * a + b * b);
        // 求垂足坐标
        double x0 = (b * b * xB - a * b * yB - a * c) / (a * a + b * b);
        double y0 = (a * a * yB - a * b * xB - b * c) / (a * a + b * b);
        if (minDis <= dist && dist <= maxDis) {
            if (inLine(xA, yA, xP, yP, x0, y0)) {//经过垂足点
                return 1;
            }
        }
        return -1;
    }

    bool inLine(double xA, double yA, double xP, double yP, double x0, double y0) {
        double maxX, minX, maxY, minY;
        maxX = max(xA, xP);
        minX = min(xA, xP);
        maxY = max(yA, yP);
        minY = min(yA, yP);
        if (((x0 - xA) * (yP - yA) - (y0 - yA) * (xP - xA) == 0) &&
            (minX <= x0 && x0 <= maxX) &&
            (minY <= y0 && y0 <= maxY)
        ) {
            return true;
        }
        return false;
    }
};

11.凸多边形(炼码886)

给定一组点的数组,当一个多边形按顺序连接时,发现这个多边形是凸多边形。

输入: 一组点的坐标
输出: 是否为凸多边形

思路:依次求多边形定点连线的向量(不要忘了最后一个定点指向第一个定点的向量),依次计算并判断向量叉乘值的符号是否一致。当java 计算 点到直线的垂直点 编程求点到直线的距离_算法_50时,b在a的顺时针方向,当java 计算 点到直线的垂直点 编程求点到直线的距离_ci_51时,两者共线,当java 计算 点到直线的垂直点 编程求点到直线的距离_几何学_52时,b在a的逆时针方向。但一开始并不知道顺时针还是逆时针方向,所以只用判断依次连成的边向量是否叉乘符号一致,一致就说明点依次顺时针或逆时针连接,即为凸多边形。
代码如下:

class Vector {
    public:
    int x, y;
    Vector() : x(0), y(0) {}
    Vector(int _x, int _y) : x(_x), y(_y) {}
};

class Solution {
public:
    /**
     * @param point: a list of two-tuples
     * @return: a boolean, denote whether the polygon is convex
     */
    bool isConvex(vector<vector<int>> &point) {
        // write your code here
        // 判断顺序的N条边是否叉积同号
        vector<Vector> edges(point.size());//定义边数组
        for (int i = 0; i < point.size(); i++) {
            edges[i].x = point[(i + 1) % point.size()][0] - point[i][0];
            edges[i].y = point[(i + 1) % point.size()][1] - point[i][1];
        }

        int pre = 0;//上一组边的叉积
        int cur = 0;//当前叉积
        for (int i = 0; i < edges.size(); i++) {
            // 依次判断各相邻边向量叉积是否同号
            cur = edges[i].x * edges[(i + 1) % edges.size()].y - edges[(i + 1) % edges.size()].x * edges[i].y;
            if (cur != 0) {
                if (cur * pre < 0) {
                    // 上一次叉积结果与此次异号,无法构成多边形
                    return false;
                }
                pre = cur;
            }
        }
        return true;
    }
};