判断点是否在扇形内解法探究 C++实现

面试时遇到了这个问题,在游戏开发中,类似于检测扇形技能是否命中了敌人。

我当时的第一想法是先通过扇形半径平方和玩家和敌人的距离平方大小比较,提前检测出一批不符合条件的敌人。如果玩家和敌人的距离平方小于扇形半径平方的话,再通过一些算法判断。

算法判断的第一想法是求出与x轴的夹角,然后判断夹角是否在扇形的两个边的夹角内。然后想了想觉得可以类比判断点是否在三角形内,通过叉乘判断。

下来以后参考了别人的一些思路,又自己进行了一些总结,并用C++测试了一下,希望帮到读者,有任何问题欢迎批评指正。


首先是最经典的通过给定玩家朝向(即扇形的中心向量)和技能的角度大小来判断是否命中。为了简化问题,我们假设扇形角度大小小于180度。同时为了不在扇形半径平方和玩家和敌人的距离平方大小比较时就筛选了过多的点,而导致之后的判断算法运行时间不易比较,将扇形的半径设置大一点。

#include <iostream>
#include <cmath>

using namespace std;

const int n = 100, m = 100; // 用离散点模拟敌人,n行m列
bool table[n][m];

void func(int x, int y, int r, int orieX, int orieY, float angle) {
	float len = sqrt(orieX * orieX + orieY * orieY);
	int rPow = r * r;
	for (int ex = 0; ex < n; ex++)
		for (int ey = 0; ey < m; ey++) {
			int dx = ex - x;
			int dy = ey - y;
			int lenPow2 = dx * dx + dy * dy;
			if (lenPow2 > rPow)
				continue;
			int dotRes = orieX * dx + orieY * dy; // 获取点乘
			if (dotRes < 0)
				continue;
			float angle2 = acos(dotRes / (len * sqrt(lenPow2))); // 通过点乘获取夹角
			if (angle2 * 2 > angle)
				continue;
			table[ex][ey] = true;
		}
}

int main() {
	int x, y, r; // 玩家位置(x,y)技能半径r
	int orieX, orieY; // 玩家朝向(orieX,orieY)
	float angle; // 技能总角度
	cin >> x >> y >> r; // eg: 20 20 100 1 1 1.57
	cin >> orieX >> orieY >> angle;

	func(x, y, r, orieX, orieY, angle);

	for (int i = 0; i < n; i++) {
		for (int j = 0; j < m; j++)
			cout << (table[i][j] ? "*" : "-");
		cout << endl;
	}
	return 0;
}

输入20 20 100 1 1 1.57表示在(20,20)处有一个玩家朝向为(1,1)角度大小为pi/2,半径100的扇形技能,运行结果如下:

在这里插入图片描述
我们再测试一下性能,10000*10000的点计算在x86 release下耗时1764ms:

#include <iostream>
#include <cmath>
#include <ctime>

using namespace std;

const int n = 10000, m = 10000;
bool table[n][m];

void func(int x, int y, int r, int orieX, int orieY, float angle) {
	float len = sqrt(orieX * orieX + orieY * orieY);
	int rPow = r * r;
	for (int ex = 0; ex < n; ex++)
		for (int ey = 0; ey < m; ey++) {
			int dx = ex - x;
			int dy = ey - y;
			int lenPow2 = dx * dx + dy * dy;
			if (lenPow2 > rPow)
				continue;
			int dotRes = orieX * dx + orieY * dy;
			if (dotRes < 0)
				continue;
			float angle2 = acos(dotRes / (len * sqrt(lenPow2)));
			if (angle2 * 2 > angle)
				continue;
			table[ex][ey] = true;
		}
}

int main() {
	int x, y, r;
	int orieX, orieY;
	float angle;
	
	// 在(20,20)处有一个玩家朝向为(1,1)角度大小为pi/2,半径n的扇形技能
	x = 20,y = 20,r = n;
	orieX = 1,orieY = 1,angle = 1.57;

	clock_t start,end;
	start=clock();		//程序开始计时

	func(x,y,r,orieX,orieY,angle);

	end=clock();		//程序结束用时
	double endtime=(double)(end-start)/CLOCKS_PER_SEC;
	cout<<"Total time:"<<endtime*1000<<"ms"<<endl;	//ms为单位
	return 0;
}

在这里插入图片描述


第二种方法是通过给定玩家的技能的两边方向(需指出左边界和右边界,不能随便给出顺序,左右是相对与玩家朝向定义的),原因就是我们要通过叉乘进行快速判断。

先给出叉乘的右手定则:
在这里插入图片描述
假设左边界为a,敌人方向为b,那么叉乘结果为正,此时表示敌人并不在扇形边界内,同理假设右边界为a,敌人方向为b,那么叉乘结果为负,表示敌人并不在扇形边界内。

#include <iostream>
#include <cmath>

using namespace std;

const int n = 100, m = 100;
bool table[n][m];

void func(int x, int y, int r, int orieXl, int orieYl, int orieXr, int orieYr) {
	int rPow = r * r;
	for (int ex = 0; ex < n; ex++)
		for (int ey = 0; ey < m; ey++) {
			int dx = ex - x;
			int dy = ey - y;
			int lenPow2 = dx * dx + dy * dy;
			if (lenPow2 > rPow)
				continue;
			int crossRes1 = orieXl * dy - dx * orieYl;
			if (crossRes1 > 0)
				continue;
			int crossRes2 = orieXr * dy - dx * orieYr;
			if (crossRes2 < 0)
				continue;
			table[ex][ey] = true;
		}
}

int main() {
	int x, y, r;
	int orieXl, orieYl;
	int orieXr, orieYr;

	cin >> x >> y >> r; // eg: 20 20 100 0 1 1 0
	cin >> orieXl >> orieYl >> orieXr >> orieYr;

	func(x,y,r,orieXl,orieYl,orieXr,orieYr);

	for(int i=0;i<n;i++){
		for(int j=0;j<m;j++)
			cout << (table[i][j]?"*":"-");
		cout << endl;
	}
	return 0;
}

输入20 20 100 0 1 1 0表示在(20,20)处有一个扇形技能,边界为左边向量(0,1)、右边向量(1,0),半径100的扇形技能。结果如下:
在这里插入图片描述
我们再测试一下这个的性能,10000*10000的点计算在x86 release下耗时124ms:

#include <iostream>
#include <cmath>
#include <ctime>

using namespace std;

const int n = 10000, m = 10000;
bool table[n][m];

void func(int x, int y, int r, int orieXl, int orieYl, int orieXr, int orieYr) {
	int rPow = r * r;
	for (int ex = 0; ex < n; ex++)
		for (int ey = 0; ey < m; ey++) {
			int dx = ex - x;
			int dy = ey - y;
			int lenPow2 = dx * dx + dy * dy;
			if (lenPow2 > rPow)
				continue;
			int crossRes1 = orieXl * dy - dx * orieYl;
			if (crossRes1 > 0)
				continue;
			int crossRes2 = orieXr * dy - dx * orieYr;
			if (crossRes2 < 0)
				continue;
			table[ex][ey] = true;
		}
}

int main() {
	int x, y, r;
	int orieXl, orieYl;
	int orieXr, orieYr;
	
	// 在(20,20)处有一个扇形技能,边界为左边向量(0,1)、右边向量(1,0),半径n的扇形技能
	x = 20, y = 20, r = n;
	orieXl = 0, orieYl = 1, orieXr = 1, orieYr = 0;

	clock_t start, end;
	start = clock();		//程序开始计时

	func(x, y, r, orieXl, orieYl, orieXr, orieYr);

	end = clock();		//程序结束用时
	double endtime = (double)(end - start) / CLOCKS_PER_SEC;
	cout << "Total time:" << endtime * 1000 << "ms" << endl;	//ms为单位
	return 0;
}

在这里插入图片描述
通过上面的结果可以看出,在一定的条件下,方法2的速度快不少。但是我想了下有几个限制,第一是游戏开发的话,多给出的是玩家朝向和技能角度大小(应该吧),这样需要在使用方法2时先通过玩家朝向和技能角度大小求出扇形的两个边界,然后再运行,这也引出第二个问题就是方法2没有涉及浮点运算,所以非常快,但是如果给定的数据为浮点数,速度势必会降一点(测试了下,并没有下降多少)。

所以还是需要具体问题具体分析,我认为如果检测目标过多,可以先求出扇形的两个边界然后用方法2,有任何问题欢迎批评指正。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
可以使用向量的方法来判断一个是否扇形范围内。首先,将扇形的起始和结束分别与目标构成两个向量,然后计算这两个向量的夹角,如果夹角小于等于扇形的角度,则目标扇形范围内。具体实现可以参考以下代码: ```c #include <stdio.h> #include <math.h> #define PI 3.14159265358979323846 typedef struct { double x; double y; } Point; double getAngle(Point p1, Point p2) { return atan2(p2.y - p1.y, p2.x - p1.x); } double getAngleDiff(double angle1, double angle2) { double diff = angle2 - angle1; while (diff > PI) { diff -= 2 * PI; } while (diff < -PI) { diff += 2 * PI; } return diff; } int isPointInSector(Point center, Point start, Point end, double radius, double angle) { double dist = sqrt(pow(center.x - start.x, 2) + pow(center.y - start.y, 2)); if (dist > radius) { return 0; } double angle1 = getAngle(center, start); double angle2 = getAngle(center, end); double diff = getAngleDiff(angle1, angle2); if (diff > angle) { return 0; } return 1; } int main() { Point center = {0, 0}; Point start = {1, 0}; Point end = {0, 1}; double radius = 1; double angle = PI / 4; Point target = {0.5, 0.5}; if (isPointInSector(center, start, end, radius, angle)) { printf("Target point is in sector\n"); } else { printf("Target point is not in sector\n"); } return 0; } ``` 这段代码实现了一个判断是否扇形范围内的函数 isPointInSector,其中 center 表示扇形的中心,start 和 end 表示扇形的起始和结束,radius 表示扇形的半径,angle 表示扇形的角度。函数返回值为 1 表示目标扇形范围内,返回值为 0 表示目标不在扇形范围内。在 main 函数中,我们调用了 isPointInSector 函数来判断一个目标是否在一个半径为 1,角度为 45 度的扇形范围内。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值