【UE5 C++】判断两点连线是否穿过球体

目录

前言

方法一

原理

代码

测试

结果

方法二

原理

一、检查连线与球体的相交情况

二、检查距离与球体半径的关系

三、检查连线与球体的相交

代码


前言

通过数学原理判断空间中任意两点的连线是否穿过球体,再通过射线检测检验算法的正确性。

方法一

原理

(1)设球体球心的坐标为 ,半径为r;

(2)设线段中A点的坐标为,B点的坐标为

(3)计算

(4)计算点到 线段的最短距离

(5) 如果,则线段穿过球体;如果,则线段不穿过球体。

代码

定义一个函数"IsCrossSphere"来判断线段是否穿过球体,函数需要传入点A、B的坐标以及球心坐标和球体半径。

再定义一个结构体作为函数返回值

函数"IsCrossSphere"的实现如下

头文件:

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "LineIsCrossSphere.generated.h"

USTRUCT(BlueprintType)
struct FStruct_Result_IsLineCrossSphere
{
	GENERATED_BODY();
public:
	UPROPERTY(BlueprintReadWrite)
	float distanceOfLineAndSphereCenter;
	UPROPERTY(BlueprintReadWrite)
	bool isCrossSphere;
};

UCLASS()
class STUDY_API ALineIsCrossSphere : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ALineIsCrossSphere();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	UFUNCTION(BlueprintCallable)
	FStruct_Result_IsLineCrossSphere IsCrossSphere(FVector pointA, FVector pointB, FVector sphereOrginPoint, float sphereRadius);   //计算线段AB到球心的距离并判断AB是否穿过球体

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;

};

源文件:

cpp 复制代码
// Fill out your copyright notice in the Description page of Project Settings.


#include "Test/LineIsCrossSphere.h"

// Sets default values
ALineIsCrossSphere::ALineIsCrossSphere()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ALineIsCrossSphere::BeginPlay()
{
	Super::BeginPlay();
	
}

FStruct_Result_IsLineCrossSphere ALineIsCrossSphere::IsCrossSphere(FVector pointA, FVector pointB, FVector sphereOrginPoint, float sphereRadius)
{
	bool isCrossSphere;

	FVector OA = pointA - sphereOrginPoint;
	FVector OB = pointB - sphereOrginPoint;
	FVector AB = OB - OA;
	FVector n = OA.Cross(AB);
	float D = n.Size() / AB.Size();

	if (D > sphereRadius)
	{
		isCrossSphere =  false;
	}
	else
	{
		isCrossSphere =  true;
	}

	FStruct_Result_IsLineCrossSphere result;
	result.isCrossSphere = isCrossSphere;
	result.distanceOfLineAndSphereCenter = D;
	return result;
}

// Called every frame
void ALineIsCrossSphere::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

测试

在Tick中每帧去发射射线检测并调用函数 "IsCrossSphere",通过观察射线碰撞结果以及函数 "IsCrossSphere"的打印是否相等来判断算法是否有误。(这里设置球体半径固定为50,球体坐标为(0,0,0))

结果

可以看到不论是线段在球体表面,穿过球体,还是在球体外,函数 "IsCrossSphere"与射线检测的结果都是一致的。

方法二

原理

一、检查连线与球体的相交情况

使用距离公式计算点 到球体中心 的距离,以及点 到地球中心 的距离,设点,点

二、检查距离与球体半径的关系

如果 ,则两点都在球体内部,它们的连线显然穿过球体;

如果 ,则两点都在球体外部,需要进一步检查它们的连线是否与球体相交。

三、检查连线与球体的相交

当两点都在球体外部时,我们可以计算直线 AB 的参数方程,并尝试找到与球体表面(即半径为 R 的球体)的交点。将直线的参数方程代入球体的方程,并解出一个关于参数的二次方程。如果这个二次方程有实数解,并且解对应的参数值在 0 和 1 之间(对于参数化的线段 AB),则连线与球体相交。

(1)表示直线参数方程

对于线段的参数方程可以表示为

其中是参数,表示线段上的点

(2)表示球体方程

设球体球心的坐标为 ,半径为,则球体方程可表示为

(3)将直线的参数方程代入球体的方程中,得到一个关于的二次方程

展开并整理后,可得到一个标准的二次方程形式:

(4)使用求根公式可以得到二次方程的解

(5)如果二次方程没有实数解(),则直线不与球体相交;

如果二次方程有一个实数解,并且解在 0≤t≤1 的范围内,则线段 AB 与球体相交于一点;

如果二次方程有两个不同的实数解,并且至少有一个解在 0≤t≤1 的范围内,则直线段 AB 与球体相交于两点(即线段穿过球体)。

代码

cpp 复制代码
bool ALineIsCrossSphere::IsCrossSphere2(FVector pointA, FVector pointB, FVector sphereOrginPoint, float sphereRadius)
{
	float D_A = sqrt(pow(pointA.X - sphereOrginPoint.X, 2) + pow(pointA.Y - sphereOrginPoint.Y, 2) + pow(pointA.Z - sphereOrginPoint.Z, 2));  //计算点A到球体中心的距离
	float D_B = sqrt(pow(pointB.X - sphereOrginPoint.X, 2) + pow(pointB.Y - sphereOrginPoint.Y, 2) + pow(pointB.Z - sphereOrginPoint.Z, 2));  //计算点B到球体中心的距离
	if (D_A <= sphereRadius && D_B <= sphereRadius)  //两点都在球体内部,它们的连线显然穿过球体
	{
		return true;
	}
	else if (D_A > sphereRadius && D_B > sphereRadius)  //两点都在球体外部
	{
		//将直线的参数方程代入球体的方程,得到标准二次方程的a、b、c
		float a = pow(pointB.X - pointA.X, 2) + pow(pointB.Y - pointA.Y, 2) + pow(pointB.Z - pointA.Z, 2);
		float b = 2 * ((pointB.X - pointA.X) * (pointA.X - sphereOrginPoint.X) + (pointB.Y - pointA.Y) * (pointA.Y - sphereOrginPoint.Y) + (pointB.Z - pointA.Z) * (pointA.Z - sphereOrginPoint.Z));
		float c = pow(pointA.X - sphereOrginPoint.X, 2) + pow(pointA.Y - sphereOrginPoint.Y, 2) + pow(pointA.Z - sphereOrginPoint.Z, 2) - pow(sphereRadius, 2);
		float discriminant = b * b - 4 * a * c;
		if (discriminant > 0.0f)  //△>0
		{
			// 有两个不同的实数解
			float t1 = (-1*b + sqrt(pow(b, 2) - 4 * a * c)) / (2 * a);
			float t2 = (-1*b - sqrt(pow(b, 2) - 4 * a * c)) / (2 * a);

			if ((0 <= t1 && t1 <= 1) || (0 <= t2 && t2 <= 1))
			{
				return true;  //至少有一个解在0~1,则线段 AB 与球体相交于两点
			}
			else 
			{
				return false;  //直线与球体在无限远处相交,即线段没有穿过球体
			}
		}
		else if (discriminant == 0.0f)  //△=0
		{
			// 有两个相等的实数解(或说是一个重根)
			float t = (-1 * b) / (2 * a);
			if (t >= 0 && t <= 1)
			{
				return true;  //直线段 AB 与球体相交于一点
			}
			else
			{
				return false; //虽然直线在无限延伸的情况下会与球体相交,但交点并不在连接点A和点B的线段上,因此没有相交
			}
		}
		else  //△<0
		{
			// 没有实数解,直线不与球体相交
			return false;
		}
	}
	else  //一点在球体内部,另一点在球体外部,则它们的连线一定穿过球体
	{
		return true;
	}
}
相关推荐
霖大侠1 小时前
Adversarial Learning forSemi-Supervised Semantic Segmentation
人工智能·算法·机器学习
阿华的代码王国2 小时前
【算法】——前缀和(矩阵区域和详解,文末附)
java·开发语言·算法·前缀和
Sunyanhui12 小时前
力扣 LCR训练计划2(剑指 Offer 22. 链表中倒数第k个节点)-140
算法·leetcode·链表
yours_Gabriel2 小时前
【力扣】3274. 检查棋盘方格颜色是否相同
算法·leetcode
Chandler242 小时前
蓝桥杯经验分享
经验分享·算法·蓝桥杯
我的老子姓彭2 小时前
C++学习笔记
c++·笔记·学习
是老余2 小时前
算法基础之链表:移除链表元素leetcode203
数据结构·算法·链表
hefaxiang2 小时前
【C++】数组
开发语言·c++
CQU_JIAKE2 小时前
3.29【机器学习】第五章作业&实现
人工智能·算法·机器学习
CQU_JIAKE3 小时前
3.27【机器学习】第五章作业&代码实现
人工智能·算法