FPlane相关

FPlane结构体定义在Engine/Source/Runtime/Core/Public/Math/Plane.h
 MS_ALIGN(16) struct FPlane
 	: public FVector
 {
 public:
 	/** The w-component. */
 	float W;
 }
▲这是FPlane的数据成员的构成:由X、Y、Z、W构成。
▼下面的几个典型的构造函数可以看出这四个数据成员的意义:
 FORCEINLINE FPlane::FPlane(float InX, float InY, float InZ, float InW)
 	:	FVector(InX,InY,InZ)
 	,	W(InW)
 {}
 FORCEINLINE FPlane::FPlane(FVector InNormal, float InW)
 	:	FVector(InNormal)
 	,	W(InW)
 {}
 FORCEINLINE FPlane::FPlane(FVector InBase, const FVector &InNormal)
 	:	FVector(InNormal)
 	,	W(InBase | InNormal)
 {}
 FORCEINLINE FPlane::FPlane(FVector A, FVector B, FVector C)
 	:	FVector(((B-A)^(C-A)).GetSafeNormal())
 {
 	W = A | (FVector)(*this);
 }
▲可以清楚的看到,X、Y、Z存储的是Plane的Normal,W存储的是Plane上的一点与Normal的点积。由这个点积除以Normal,就可以还原出Plane上的这个点坐标。 当W=0时,表明该Plane过原点;当W>0时,表明Plane正空间不包含原点;当W<0时,表明Plane正空间包含原点。
▲可以形象认为Plane将空间分成两部分,在Plane上任选一点出发作出其法线,则法线所在的空间规定为正空间,另一半空间规定为负空间。
▲以+Z作为法线举例,则W=0时,平面就是XOY平面;当W>0时,平面沿+Z分布;当W<0时,平面沿-Z分布。
▼下面分析几个FPlane的API的数学原理

plane_dot_1

plane_dot_2

FPlane::PlaneDot

 /**
  * Calculates distance between plane and a point.
  *
  * @param P The other point.
  * @return >0: point is in front of the plane, <0: behind, =0: on the plane.
  */
 FORCEINLINE float FPlane::PlaneDot(const FVector &P) const
 {
 	return X * P.X + Y * P.Y + Z * P.Z - W;
 }
从其注释表明,这是计算点到Plane的距离的函数,同时要知道,Plane将空间分成了两部分,其法线正向所在的部分定义为正,另一部分定义为负。在正空间的点到Plane的距离为正,负空间的点到Plane的距离为负。
上面的图1和图2中,BasePoint为定义Plane时所用的那个点,FPlane里存储的W值已在图中标出,W存储的是Normal.Dot(BasePoint),即BasePoint在Normal上的投影。所以W为如图所示。图1和图2中分别演示了2种Plane的情形,P点各演示了3种可能的情形,最终得出的distance都是N.Dot(P) - W
#189的解释简单直接

PointPlaneProject

 /**
  * Calculate the projection of a point on the given plane.
  *
  * @param Point The point to project onto the plane
  * @param Plane The plane
  * @return Projection of Point onto Plane
  */
 FVector FVector::PointPlaneProject(const FVector& Point, const FPlane& Plane)
 {
 	//Find the distance of X from the plane
 	//Add the distance back along the normal from the point
 	return Point - Plane.PlaneDot(Point) * Plane;
 }
设投影点是Q,有OP = OQ + QP. 因此Q点坐标即OQ = OP - QP
QP = (Point到Plane的有向距离) * (Plane.Normal). 当P在正空间时,有向距离为正,QP结果与Normal同向;当P在负空间时,有向距离为负,QP结果与Normal反向。因此这里的QP计算式是正确的,符合事实。

章节(12.5) 平面

平面方程的隐式定义:
$$ax + by + cz = d\\ \textbf{p * n} = d$$
点到平面的距离
关于#130里FPlane的定义,其实就是n*p=d的形式。其中x、y、z分量存法线n,w存的d。
实际上,n*p就是OP在方向向量n上的投影,对于平面上所有的点P,其OP在n上的投影都是相同的,即具有相同的d。反过来也就是说:对于一个确定的方向向量n,若有OP在n上的投影(即n*p)相同(记为d),则称这些P在同一个平面上。因此可以用n*p = d来表达一个平面。
而点到平面的距离也可以直观的思考:任意点Q都可以求出其在n上的投影n*q,若Q在平面上,则有n*q=d,若Q在平面的正空间,则有n*q>d,反之若Q在平面的负空间,则有n*q<d。因此Q点到平面的有向距离就是:n*q - d
还有一种定义平面的方式:已知平面法线n,平面上一点p0,那么对于平面上的任一点p,都有n*(p-p0) = 0

FVector FMath::LinePlaneIntersection

 inline FVector FMath::LinePlaneIntersection
 	(
 	const FVector &Point1,
 	const FVector &Point2,
 	const FPlane  &Plane
 	)
 {
 	return
 		Point1
 		+	(Point2-Point1)
 		*	((Plane.W - (Point1|Plane))/((Point2 - Point1)|Plane));
 }
该功能还有一个实现,在KismetMathLibrary.h里:
 bool UKismetMathLibrary::LinePlaneIntersection(const FVector& LineStart, const FVector& LineEnd, const FPlane& APlane, float& T, FVector& Intersection)
 {
 	FVector RayDir = LineEnd - LineStart;
 	// Check ray is not parallel to plane
 	if ((RayDir | APlane) == 0.0f)
 	{
 		T = -1.0f;
 		Intersection = FVector::ZeroVector;
 		return false;
 	}
 	T = ((APlane.W - (LineStart | APlane)) / (RayDir | APlane));
 	// Check intersection is not outside line segment
 	if (T < 0.0f || T > 1.0f)
 	{
 		Intersection = FVector::ZeroVector;
 		return false;
 	}
 	// Calculate intersection point
 	Intersection = LineStart + RayDir * T;
 	return true;
 }
这两个实现其实是一样的原理,第二个可以当作第一个实现的详细注释版。
解释一下:其中(Vector | Plane)的操作是合法的,是因为FPlane是FVector的派生类型,因此(Vector | Plane)其实就是向量点乘,具体到这里就是Point点乘(Plane的Normal)。【|】运算符是对FVector.DotProduct的重载
就以Kismet里的蓝图函数来说:

PointPlaneProject

 /**
  * Calculate the projection of a point on the given plane.
  *
  * @param Point The point to project onto the plane
  * @param Plane The plane
  * @return Projection of Point onto Plane
  */
 FVector FVector::PointPlaneProject(const FVector& Point, const FPlane& Plane)
 {
 	//Find the distance of X from the plane
 	//Add the distance back along the normal from the point
 	return Point - Plane.PlaneDot(Point) * Plane;
 }
设投影点是Q,有OP = OQ + QP. 因此Q点坐标即OQ = OP - QP
QP = (Point到Plane的有向距离) * (Plane.Normal). 当P在正空间时,有向距离为正,QP结果与Normal同向;当P在负空间时,有向距离为负,QP结果与Normal反向。因此这里的QP计算式是正确的,符合事实。

VectorPlaneProject

 /**
  * Calculate the projection of a vector on the plane defined by PlaneNormal.
  * 
  * @param  V The vector to project onto the plane.
  * @param  PlaneNormal Normal of the plane (assumed to be unit length).
  * @return Projection of V onto plane.
  */
 FVector FVector::VectorPlaneProject(const FVector& V, const FVector& PlaneNormal)
 {
 	return V - V.ProjectOnToNormal(PlaneNormal);
 }
设O为PlaneNormal上的一点(可以为原点),ON为PlaneNormal,将OV投影到ON为OQ,投影点Q为PlaneNormal上离V最近的点。此时QV即为所求:OV到Plane上的投影。
OQ + QV = OV => QV = OV - OQ
OQ = (V.Dot(Normal)) * Normal
因此,最后所求QV = OV - OQ = V - (V.Dot(Normal))* Normal. 其中,(V.Dot(Normal)) * Normal记为函数V.ProjectOnToNormal(Normal).
ColorCorrectRegionsCommon.usf中的RotateAboutAxis函数,其实现里的ClosestPointOnAxis就是Q点,UAxis就是QV

章节(12.5) 平面

平面方程的隐式定义:
$$ax + by + cz = d\\ \textbf{p * n} = d$$
点到平面的距离
关于#130里FPlane的定义,其实就是n*p=d的形式。其中x、y、z分量存法线n,w存的d。
实际上,n*p就是OP在方向向量n上的投影,对于平面上所有的点P,其OP在n上的投影都是相同的,即具有相同的d。反过来也就是说:对于一个确定的方向向量n,若有OP在n上的投影(即n*p)相同(记为d),则称这些P在同一个平面上。因此可以用n*p = d来表达一个平面。
而点到平面的距离也可以直观的思考:任意点Q都可以求出其在n上的投影n*q,若Q在平面上,则有n*q=d,若Q在平面的正空间,则有n*q>d,反之若Q在平面的负空间,则有n*q<d。因此Q点到平面的有向距离就是:n*q - d
还有一种定义平面的方式:已知平面法线n,平面上一点p0,那么对于平面上的任一点p,都有n*(p-p0) = 0

空间向量基本定理

如果三个向量a,b,c不共面,那么对空间 任一向量,都存在唯一的有序实数组x,y,z,使得 \[\begin{aligned} \textbf{p} = x\textbf{a} + y\textbf{b} + z\textbf{c}.\end{aligned}\]
或者:设O,A,B,C是不共面的四点,则对空间任一点P,都存在唯一的有序实数组x,y,z,使得$$\vec{OP} = x\vec{OA} + y\vec{OB} + z\vec{OC}$$
  1. 空间任意三个不共面向量都可以作为空间向量的一个基底;
  2. 如果三个向量a,b,c不共面,那么所有空间向量所组成的集合就是{p|p=xa+yb+zc, x,y,z∈R},这个集合可看作由向量a,b,c生成的,其中把{a, b, c}叫做空间的一个基底,a,b,c都叫做基向量;
  3. 一个基底是指一个向量组,一个基向量是指基底中的某一个向量,二者是相关联的不同的概念;
  4. 由于0可视为与任意非零向量共线,与任意两个非零向量共面,所以,三个向量不共面就隐含着它们都不是0.

Find Closest Point On Line

// 点投影到直线,或过点作直线的垂线与直线的交点.
// 注意:当点在直线上时,函数获取的就是该点
FVector UKismetMathLibrary::FindClosestPointOnLine(FVector Point, FVector LineOrigin, FVector LineDirection)
{
	const FVector SafeDir = LineDirection.GetSafeNormal();
	const FVector ClosestPoint = LineOrigin + (SafeDir * ((Point-LineOrigin) | SafeDir));
	return ClosestPoint;
}