[UE]常见数学和插值

UE常见数学和插值

  • 参考
  • unreal中的Transform基础
    • FMatrix、FMatrix44f、FMatrix44d
    • FTranslationMatrix
    • FRotationMatrix
    • TRotator
    • FQuat
    • FScaleMatrix
    • FTransform
  • 关于旋转的几个常见示例
    • 1. 将不限范围的角度转换到[0, 360)
    • 2.A位置的actor看向B位置
    • 3.RotateVector和UnrotateVector
  • 插值相关
    • 旋转插值Math.RInterpTo
    • 标量插值Math.FInterpTo
    • FMath::Lerp/FMath::FInterpConstantTo
    • UKismetMathLibrary::Ease
  • Debug相关
  • KismetMathLibrary
  • GameplayStatics
  • Python的Math
    • atan2和atan

参考

Unreal Transformation
游戏中的旋转变换——四元数和欧拉角
UE4 三种旋转 (一)
UE4_源码浅析篇_矩阵

unreal中的Transform基础

Unreal Transformation
UE4 三种旋转 (一)

父空间坐标转局部空间坐标。
FTransform.InverseTransformLocation(FVector Location)

局部空间坐标转父空间坐标。
FTransform.TransformLocation(FVector Location)

局部空间旋转转父空间旋转。
FTransform.TransformRotation(FRotator Rotator)

获取使当前坐标系X轴旋转到X_V向量的Rotator。
MakeRotFromX(FVector X_V)
template<typename T>
TMatrix<T> TRotationMatrix<T>::MakeFromX(TVector<T> const& XAxis)
{
	TVector<T> const NewX = XAxis.GetSafeNormal();

	// try to use up if possible
	TVector<T> const UpVector = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);

	const TVector<T> NewY = (UpVector ^ NewX).GetSafeNormal();
	const TVector<T> NewZ = NewX ^ NewY;

	return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}


实际上就是MakeRotFromX(Target-Start)FindLookAtRotation(FVector Start,FVector Target)

// RotationMatrix.h
// 保证FRotationMatrix的X轴指向给定方向X时,也保证了FRotationMatrix的Y轴约束在给定的方向X和方向Y的平面上,而TRotationMatrix::MakeFromX只指定X轴指向给定方向X,但是Y的方向并没有约束
FRotationMatrix::MakeFromXY

template<typename T>
TMatrix<T> TRotationMatrix<T>::MakeFromXY(TVector<T> const& XAxis, TVector<T> const& YAxis)
{
    // 1. 计算单位向量
	TVector<T> NewX = XAxis.GetSafeNormal();
	TVector<T> Norm = YAxis.GetSafeNormal();

	// if they're almost same, we need to find arbitrary vector
	if (FMath::IsNearlyEqual(FMath::Abs(NewX | Norm), T(1.f)))
	{
		// make sure we don't ever pick the same as NewX
		Norm = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);
	}

    // 2. NewZ = NewX 叉乘 Norm
    //    NewY = NewZ 叉乘 NewX 
	const TVector<T> NewZ = (NewX ^ Norm).GetSafeNormal();
	const TVector<T> NewY = NewZ ^ NewX;

	return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}

FRotationMatrix::MakeFromXZ
template<typename T>
TMatrix<T> TRotationMatrix<T>::MakeFromXZ(TVector<T> const& XAxis, TVector<T> const& ZAxis)
{
	TVector<T> const NewX = XAxis.GetSafeNormal();
	TVector<T> Norm = ZAxis.GetSafeNormal();

	// if they're almost same, we need to find arbitrary vector
	if (FMath::IsNearlyEqual(FMath::Abs(NewX | Norm), T(1.f)))
	{
		// make sure we don't ever pick the same as NewX
		Norm = (FMath::Abs(NewX.Z) < (1.f - UE_KINDA_SMALL_NUMBER)) ? TVector<T>(0, 0, 1.f) : TVector<T>(1.f, 0, 0);
	}

	const TVector<T> NewY = (Norm ^ NewX).GetSafeNormal();
	const TVector<T> NewZ = NewX ^ NewY;

	return TMatrix<T>(NewX, NewY, NewZ, TVector<T>::ZeroVector);
}

FMatrix、FMatrix44f、FMatrix44d

// Matrix.h, Matrix.inl
TMatrix<T>

// MathFwd.h
using FMatrix 		= UE::Math::TMatrix<double>;		// UE_DECLARE_LWC_TYPE(Matrix, 44);

FMatrix是unreal中表示3D变换的一个4x4的浮点数矩阵,其中前三列代表旋转和缩放,第四列代表平移。
FMatrix的元素排列方式为列主序(column-major order),即按列顺序依次填写。
下面是一个示例,展示了如何填写一个包含平移、旋转和缩放的变换矩阵:

float ScaleX = 1;
float ScaleY = 2;
float ScaleZ = 3;
float RotationPitch = 4;
float RotationYaw = 5;
float RotationRoll = 6;
float TranslationX = 7;
float TranslationY = 8;
float TranslationZ = 9;

FMatrix/FMatrix44f/FMatrix44d TransformMatrix;
TransformMatrix.M[0][0] = ScaleX * FMath::Cos(RotationYaw) * FMath::Cos(RotationRoll);
TransformMatrix.M[0][1] = ScaleX * FMath::Sin(RotationYaw) * FMath::Cos(RotationRoll);
TransformMatrix.M[0][2] = -ScaleX * FMath::Sin(RotationRoll);
TransformMatrix.M[0][3] = 0.0f;
TransformMatrix.M[1][0] = ScaleY * (FMath::Cos(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Sin(RotationPitch) - FMath::Sin(RotationYaw) * FMath::Cos(RotationPitch));
TransformMatrix.M[1][1] = ScaleY * (FMath::Sin(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Sin(RotationPitch) + FMath::Cos(RotationYaw) * FMath::Cos(RotationPitch));
TransformMatrix.M[1][2] = ScaleY * FMath::Cos(RotationRoll) * FMath::Sin(RotationPitch);
TransformMatrix.M[1][3] = 0.0f;
TransformMatrix.M[2][0] = ScaleZ * (FMath::Cos(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Cos(RotationPitch) + FMath::Sin(RotationYaw) * FMath::Sin(RotationPitch));
TransformMatrix.M[2][1] = ScaleZ * (FMath::Sin(RotationYaw) * FMath::Sin(RotationRoll) * FMath::Cos(RotationPitch) - FMath::Cos(RotationYaw) * FMath::Sin(RotationPitch));
TransformMatrix.M[2][2] = ScaleZ * FMath::Cos(RotationRoll) * FMath::Cos(RotationPitch);
TransformMatrix.M[2][3] = 0.0f;
TransformMatrix.M[3][0] = TranslationX;
TransformMatrix.M[3][1] = TranslationY;
TransformMatrix.M[3][2] = TranslationZ;
TransformMatrix.M[3][3] = 1.0f;
其中,TransformMatrix.M[i][j]表示矩阵中第i列第j行的元素值。

FMatrix、FMatrix44f、FMatrix44d

相同点

三者都可以用于表示3D空间中的变换。
都支持矩阵乘法、加法、减法、转置、求逆等操作。
都可以相互转换。

不同点:

数据类型不同:FMatrix使用单精度浮点数,FMatrix44f使用单精度浮点数,FMatrix44d使用双精度浮点数。
计算精度不同:FMatrix使用单精度浮点数,精度较低;FMatrix44f使用单精度浮点数,精度较高但是内存占用较大;FMatrix44d使用双精度浮点数,精度最高但是内存占用最大。
相互转换时需要进行类型转换。
可以使用强制类型转换来相互转换

FTranslationMatrix

平移矩阵,用于表示沿x、y、z轴的平移变换,一个只有平移变换的矩阵。
构造函数接受一个FVector类型的参数,表示需要进行平移的向量

FRotationMatrix

一个只有旋转变换的矩阵

// RotationMatrix.h
TRotationMatrix<T>

FRotationMatrix比较特殊,只能在纯C++中使用,不能用UFUNCTION等暴露给蓝图,见:Unable to find ‘class’, ‘delegate’, ‘enum’, or ‘struct’ with name ‘FRotationMatrix’

// 编译报错
UFUNCTION(BlueprintCallable)
FRotationMatrix Convert(const FRotator& Rot);

// 修改成FMatrix
UFUNCTION(BlueprintCallable)
FMatrix Convert(const FRotator& Rot);
// 函数实现内部可以按照如下方式转换
FMatrix myRotationMatrix=FRotationMatrix::Make(FRotator(0,0,0));

FRotationMatrix与FRotator、FQuat的转换

// 1.FRotator==>FRotationMatrix
FRotator Rotation(roll, pitch, yaw); //其中roll、pitch和yaw分别表示绕x、y和z轴的旋转角度。
FMatrix RotationMatrix = FRotationMatrix(Rotation);
// 2.FRotationMatrix==>FRotator
FRotationMatrix RotationMatrix = ...;
FRotator Rotation = RotationMatrix.Rotator();

// 3.FQuat==>FRotationMatrix
FQuat QuatRotation = ...;
FRotationMatrix RotationMatrix = FRotationMatrix(QuatRotation.Rotator());
// 4.FRotationMatrix==>FQuat
FRotationMatrix RotationMatrix = ...;
FQuat RotationQuat = RotationMatrix.ToQuat();
FQuat QuatRotation = FQuat(RotationMatrix);

TRotator

欧拉角,FRotator是一个包含三个浮点数Pitch、Yaw、Roll的结构体,用于表示3D旋转。

TRotator<T>: Rotator.h
// 注意FRotator的构造函数中参数顺序是 Pitch, Yaw, Roll
// Pitch:俯仰角,即绕Y轴旋转;
// Yaw:偏角,即绕Z轴旋转;
// Roll:滚角,即绕X轴旋转。
FORCEINLINE TRotator( T InPitch, T InYaw, T InRoll );
// UKismetMathLibrary::MakeRotator中的顺序是Roll, Pitch, Yaw
FRotator UKismetMathLibrary::MakeRotator(float Roll, float Pitch, float Yaw)
{
	return FRotator(Pitch,Yaw,Roll);
}

// MathFwd.h
using FRotator 		= UE::Math::TRotator<double>;		// UE_DECLARE_LWC_TYPE(Rotator, 3);

// 使用示例
//绕`z`轴旋转10度
FRotator rotator(0, 10, 0); 
AActorT->SetActorRotation(rotator);

FQuat

四元数,x、y、z、w。用于表示3D旋转。四元数比欧拉角更加高效. FQuat通常用于表示游戏角色的旋转。避免万向锁,以及更方便做差值计算。

FQuat(FVector Axis, float AngleRad)

struct FQuat 
{
	public:
		/** The quaternion's X-component. */
		float X;
		/** The quaternion's Y-component. */
		float Y;
		/** The quaternion's Z-component. */
		float Z;
		/** The quaternion's W-component. */
		float W;
}

// 构造函数: 创建和初始化一个新的四元数(根据给定轴旋转 a 弧度)
FQuat(FVector Axis, float AngleRad)

//绕z轴旋转45度
FQuat quat = FQuat(FVector(0, 0, 1), PI / 4.f); 
GetOwner()->SetActorRotation(quat);

FQuat axisRot(FVector::RightVector, FMath::DegreesToRadians(90));
SetActorRotation((GetActorRotation().Quaternion() * axisRot).Rotator());

FQuat axisRot(FVector::UpVector, FMath::DegreesToRadians(90);
SetActorRotation((axisRot * GetActorRotation().Quaternion()).Rotator());

FScaleMatrix

缩放矩阵,用于表示沿x、y、z轴的缩放变换,一个只有缩放变换的矩阵。

FTransform

FTranslationMatrix、FRotationMatrix、FScaleMatrix来构建一个FTransform

FVector ScaleVector = FVector(2.f, 3.f, 4.f);
FScaleMatrix ScaleMatrix(ScaleVector);

FRotator Rotation(0.f, 45.f, 0.f);
FRotationMatrix RotationMatrix = FRotationMatrix(Rotation);

FVector Translation(100.0f, 200.0f, 300.0f);
FTranslationMatrix TranslationMatrix(Translation);

FTransform Transform(RotationMatrix.ToQuat(), TranslationMatrix.GetOrigin(), ScaleMatrix.GetScaleVector());

关于旋转的几个常见示例

1. 将不限范围的角度转换到[0, 360)

UKismetMathLibrary::ClampAxis

// KismetMathLibrary.cpp
float UKismetMathLibrary::ClampAxis(float Angle)
{
	return FRotator::ClampAxis(Angle);
}
// Rotator.h
template<typename T>
FORCEINLINE T TRotator<T>::ClampAxis( T Angle )
{
	// returns Angle in the range (-360,360)
	Angle = FMath::Fmod(Angle, (T)360.0);

	if (Angle < (T)0.0)
	{
		// shift to [0,360) range
		Angle += (T)360.0;
	}

	return Angle;
}

2.A位置的actor看向B位置

A位置的actor看向B位置,实际就是将A位置的actor的forward vector转向A->B
2.1 KismetMathLibrary.FindLookAtRotation/UKismetMathLibrary::MakeRotFromX/KismetMathLibrary.MakeRotFromXY

// 解释
KismetMathLibrary.FindLookAtRotation 等价于 UKismetMathLibrary::MakeRotFromX
// MakeRotFromXY相比MakeRotFromX,除了使forward vector指向X外,还约束了right vector在XY平面上,
KismetMathLibrary.MakeRotFromXY
KismetMathLibrary.MakeRotFromXZ

// 示例
FVector LookDirection = target->GetActorLocation() - GetOwner()->GetActorLocation(); 
FMatrix LookRotationMatrix = FRotationMatrix::MakeFromXZ(LookDirection, GetOwner()->GetActorUpVector()); 
GetOwner()->SetActorRotation(LookRotationMatrix.Rotator());


// 源码KismetMathLibrary.inl
FRotator UKismetMathLibrary::FindLookAtRotation(const FVector& Start, const FVector& Target)
{
	return MakeRotFromX(Target - Start);
}
FRotator UKismetMathLibrary::MakeRotFromX(const FVector& X)
{
	return FRotationMatrix::MakeFromX(X).Rotator();
}

FRotator UKismetMathLibrary::MakeRotFromXY(const FVector& X, const FVector& Y)
{
	return FRotationMatrix::MakeFromXY(X, Y).Rotator();
}


FRotator UKismetMathLibrary::MakeRotFromXZ(const FVector& X, const FVector& Z)
{
	return FRotationMatrix::MakeFromXZ(X, Z).Rotator();
}

2.2 UKismetMathLibrary::Conv_VectorToRotator

// 求出令forward vector和InVec同向的FRotator
FRotator UKismetMathLibrary::Conv_VectorToRotator(FVector InVec)

// KismetMathLibrary.inl
KISMET_MATH_FORCEINLINE
FRotator UKismetMathLibrary::Conv_VectorToRotator(FVector InVec)
{
	return InVec.ToOrientationRotator();
}
// UnrealMath.cpp
template<typename T>
UE::Math::TRotator<T> UE::Math::TVector<T>::ToOrientationRotator() const
{
	UE::Math::TRotator<T> R;
	// Find yaw.
	R.Yaw = FMath::RadiansToDegrees(FMath::Atan2(Y, X));
	// Find pitch.
	R.Pitch = FMath::RadiansToDegrees(FMath::Atan2(Z, FMath::Sqrt(X*X + Y*Y)));
	// Find roll.
	R.Roll = 0;
#if ENABLE_NAN_DIAGNOSTIC || (DO_CHECK && !UE_BUILD_SHIPPING)
	if (R.ContainsNaN())
	{
		logOrEnsureNanError(TEXT("TVector::Rotation(): Rotator result %s contains NaN! Input FVector = %s"), *R.ToString(), *this->ToString());
		R = UE::Math::TRotator<T>::ZeroRotator;
	}
#endif

	return R;
}

3.RotateVector和UnrotateVector

旋转矩阵是正交矩阵,逆等于转置, 旋转矩阵(Rotate Matrix)的性质分析

RotateVector: 将世界坐标系下的向量转换到旋转矩阵表示的局部坐标系下
UnrotateVector: 将向量从旋转矩阵表示的局部坐标系下转换到世界坐标系下
使用示例1:让A点绕B点旋转:UE4之A点绕B点旋转
使用示例2:ALS中相机镜头控制部分的插值逻辑 AALSPlayerCameraManager::CalculateAxisIndependentLag

// 关于RotateVector和UnrotateVector的作用,在ALS的相机镜头控制中使用示例如下
// 先用RotateVector转换到相机旋转矩阵表示的局部坐标系下,然后在局部坐标系下的三个轴分别插值
// 然后将插值结果用UnrotateVector局部坐标系下转换到世界坐标系下
// 区别直接在世界坐标系下的三个轴插值分别插值,
FVector AALSPlayerCameraManager::CalculateAxisIndependentLag(FVector CurrentLocation, FVector TargetLocation,
                                                             FRotator CameraRotation, FVector LagSpeeds,
                                                             float DeltaTime)
{
	CameraRotation.Roll = 0.0f;
	CameraRotation.Pitch = 0.0f;
	const FVector UnrotatedCurLoc = CameraRotation.UnrotateVector(CurrentLocation);
	const FVector UnrotatedTargetLoc = CameraRotation.UnrotateVector(TargetLocation);

	const FVector ResultVector(
		FMath::FInterpTo(UnrotatedCurLoc.X, UnrotatedTargetLoc.X, DeltaTime, LagSpeeds.X),
		FMath::FInterpTo(UnrotatedCurLoc.Y, UnrotatedTargetLoc.Y, DeltaTime, LagSpeeds.Y),
		FMath::FInterpTo(UnrotatedCurLoc.Z, UnrotatedTargetLoc.Z, DeltaTime, LagSpeeds.Z));

	return CameraRotation.RotateVector(ResultVector);
}
dir_rot.RotateVector(ue.Vector(-150, 30, 20))
// Rotator.h
CORE_API TVector<T> RotateVector( const UE::Math::TVector<T>& V ) const;
// UnrealMath.cpp
template<typename T>
UE::Math::TVector<T> UE::Math::TRotator<T>::UnrotateVector(const UE::Math::TVector<T>& V) const
{
	return UE::Math::TRotationMatrix<T>(*this).GetTransposed().TransformVector( V );
}	

template<typename T>
UE::Math::TVector<T> UE::Math::TRotator<T>::RotateVector(const UE::Math::TVector<T>& V) const
{
	return UE::Math::TRotationMatrix<T>(*this).TransformVector( V );
}	

// Quat.h
template<typename T>
FORCEINLINE TVector<T> TQuat<T>::RotateVector(TVector<T> V) const
{	
	// http://people.csail.mit.edu/bkph/articles/Quaternions.pdf
	// V' = V + 2w(Q x V) + (2Q x (Q x V))
	// refactor:
	// V' = V + w(2(Q x V)) + (Q x (2(Q x V)))
	// T = 2(Q x V);
	// V' = V + w*(T) + (Q x T)

	const TVector<T> Q(X, Y, Z);
	const TVector<T> TT = 2.f * TVector<T>::CrossProduct(Q, V);
	const TVector<T> Result = V + (W * TT) + TVector<T>::CrossProduct(Q, TT);
	return Result;
}

template<typename T>
FORCEINLINE TVector<T> TQuat<T>::UnrotateVector(TVector<T> V) const
{	
	const TVector<T> Q(-X, -Y, -Z); // Inverse
	const TVector<T> TT = 2.f * TVector<T>::CrossProduct(Q, V);
	const TVector<T> Result = V + (W * TT) + TVector<T>::CrossProduct(Q, TT);
	return Result;
}


插值相关

旋转插值Math.RInterpTo

// UnrealMath.cpp
// 注意,插值速度小于等于0时,直接返回的是Target
[UE]常见数学和插值_第1张图片

标量插值Math.FInterpTo

// 注意,插值速度小于等于0时,直接返回的是Target

// UnrealMathUtility.h

    /** Interpolate float from Current to Target. Scaled by distance to Target, so it has a strong start speed and ease out. */
	template<typename T1, typename T2 = T1, typename T3 = T2, typename T4 = T3>
	UE_NODISCARD static auto FInterpTo( T1  Current, T2 Target, T3 DeltaTime, T4 InterpSpeed )
	{
		using RetType = decltype(T1() * T2() * T3() * T4());
	
		// If no interp speed, jump to target value
		if( InterpSpeed <= 0.f )
		{
			return static_cast<RetType>(Target);
		}

		// Distance to reach
		const RetType Dist = Target - Current;

		// If distance is too small, just set the desired location
		if( FMath::Square(Dist) < UE_SMALL_NUMBER )
		{
			return static_cast<RetType>(Target);
		}

		// Delta Move, Clamp so we do not over shoot.
		const RetType DeltaMove = Dist * FMath::Clamp<RetType>(DeltaTime * InterpSpeed, 0.f, 1.f);

		return Current + DeltaMove;				
	}

FMath::Lerp/FMath::FInterpConstantTo

TickComponent: 中
	TargetYaw = 90
	FRotator OpenDoor(0.f,TargetYaw,0.f);
	OpenDoor.Yaw = FMath::Lerp(CurrentYaw, TargetYaw,0.02f);
	// 插值的速度与帧率无关
    OpenDoor.Yaw = FMath::FInterpConstantTo(CurrentYaw, TargetYaw,DeltaTime,45);

FMath::Lerp线性插值的问题。OpenDoor.Yaw会一直接近90度,但是不会到达90度。同时电脑帧率快慢会影响OpenDoor.Yaw插值的速度

UKismetMathLibrary::Ease

UKismetMathLibrary::Ease
提供了多种内置的缓动插值方式

Debug相关

Unreal engine 4 C 一些调试用的绘制函数

// 头文件
#include "DrawDebugHelpers.h"

// 几个示例
点:  DrawDebugPoint(GetWorld(), LocationOne, 200, FColor(52,220,239), true, 999);
球体:DrawDebugSphere(GetWorld(), LocationTwo, 200, 26, FColor(181,0,0), true, 999, 0, 2);
圆:  DrawDebugCircle(GetWorld(), CircleMatrix, 200, 50, FColor(0,104,167), true, 999, 0, 10);
	  DrawDebugCircle(GetWorld(), LocationFour, 200, 50, FColor(0,0,0), true, 999, 0, 10);
	  DrawDebugSolidBox(GetWorld(), MyBox, FColor(20, 100, 240), MyTransform, true, 999);
盒:  DrawDebugBox(GetWorld(), LocationFive, FVector(100,100,100), FColor::Purple, true, 999, 0, 10);
线:  DrawDebugLine(GetWorld(), LocationTwo, LocationThree, FColor::Emerald, true, 999, 0, 10);
方向箭头:DrawDebugDirectionalArrow(GetWorld(), FVector(-300, 600, 600), FVector(-300, -600, 600), 120.f, FColor::Magenta, true, 999, 0, 5.f);
交叉准星:DrawDebugCrosshairs(GetWorld(), FVector(0,0,1000), FRotator(0,0,0), 500.f, FColor::White, true, 999, 0);


KismetMathLibrary

KismetMathLibrary库为常用的数学使用库,包含了对向量、矩阵等数学变量的常规操作;

// 头文件
#include "Kismet/KismetMathLibrary.h"

// 一些常用示例
KismetMathLibrary.ClampAxis

KismetMathLibrary.FindLookAtRotation
KismetMathLibrary.Conv_VectorToRotator
KismetMathLibrary.MakeRotFromZX
KismetMathLibrary.MakeRotFromZY
KismetMathLibrary.MakeRotFromZX

KismetMathLibrary.DegAcos
KismetMathLibrary.DegreesToRadians
KismetMathLibrary.FMod

GameplayStatics

UGameplayStatics类实用分析

GameplayStatics库为常用的Gameplay操作库,包含Gameplay操作的各类静态函数

// 头文件
#include "Kismet/GameplayStatics.h"


Python的Math

atan2和atan

python中 math模块下 atan 和 atan2的区别
[UE]常见数学和插值_第2张图片

atan2(y, x) 返回射线从原点到点 (x, y) 与正 x 轴之间的角度 θ,限制为 (−π, π]。

[UE]常见数学和插值_第3张图片

[UE]常见数学和插值_第4张图片
从 −π 到 +π 的切函数图,带有相应的 y/x 符号。绿色箭头指向 atan2(−1, −1) 和 atan2(1, 1) 的结果。

如果 x > 0,则所需的角度测量值为 atan2(y, x)=arctan(y/x),但是,当 x < 0 时,角度与所需角度 arctan(y/x)截然相反,并且必须添加±π(半圈)才能将点放置在正确的象限中。 [1] 使用该 atan2 函数可以消除这种更正,简化代码和数学公式

你可能感兴趣的:(UE,学习记录,游戏引擎)