游戏数学专项整理

最后更新于 2024-08-28 1634 次阅读


数学概念

点乘 Dot Product

又称"点积","数量积”,"内积",a*b = |a||b|Cos(θ)

几何意义:是一条边向另一条边的投影乘以另一条边的长度.

v1v2向量的点乘运算:

  • 相应元素的乘积的和:v1( x1, y1,z1) * v2(x2, y2,z2) = x1*x2 + y1*y2+z1*z2;
  • 其结果不是一个向量,而是标量
  1. 根据点乘计算两个向量的夹角。= arccos(a·b / (|a|·|b|))
  2. 根据点乘的正负值,得到夹角大小范围,>0,则夹角(0,90)<0,则夹角(90,180),可以利用这点判断一个多边形是面向摄像机还是背向摄像机。
  3. 根据点乘的大小,得到向量的投影长度,反应了向量的长度关系
  4. 向量的点积与它们夹角的余弦成正比,因此在聚光灯的效果计算中,可以根据点积来得到光照效果,如果点积越大,说明夹角越小,则物理离光照的轴线越近,光照越强。
  5. 物理中,点积可以用来计算合力和功。若b为单位矢量,则点积即为a在方向b的投影,即给出了力在这个方向上的分解。功即是力和位移的点积。
  6. 计算机图形学常用来进行方向性判断,如两矢量点积大于0,则它们的方向朝向相近;如果小于0,则方向相反。矢量内积是人工智能领域中的神经网络技术的数学基础之一,此方法还被用于动画渲染(Animation-Rendering)。

叉乘 Cross Product

又称"叉积","向量积","外积"。

v1v2向量的叉乘运算:相应元素的乘积的和:v1( x1, y1,z1) x v2(x2, y2, z2) = (y1*z2 - y2*z1)i+(x2*z1 - x1*z2)j+(x1*y2-x2*y1)k;

  • c⊥a,c⊥b,即向量c与向量a,b所在平面垂直
  • 模长|c| = |a||b| sin<a,b>
  • (数学上)满足右手法则a x b = -b x a,所以我们可以使用叉乘的正负值来判断a,b的相对位置,即b是处于a的顺时针还是逆时针方向。叉乘的右手定则是用来确定叉乘积的方向的。
  1. 根据叉乘得到a,b向量的相对位置,和顺时针或逆时针方位。
  2. 简单的说: 点乘判断角度,叉乘判断方向。
    形象的说: 当一个敌人在你身后的时候,叉乘可以判断你是往左转还是往右转更好的转向敌人,点乘得到你当前的面朝向的方向和你到敌人的方向的所成的角度大小。
  3. 得到a,b夹角的正弦值,计算向量的夹角(0,90),可以配合点乘和Angle方法计算出含正负的方向。
  4. 根据叉乘大小,得到a,b向量所形成的平行四边形的面积大小,根据面积大小得到向量的相对大小

实例

得到两人的相对角度

FVector HeadLocation = A->GetActorLocation();
FVector HeadForward = A->GetActorForwardVector();
FVector TargetLocation = B->GetActorLocation();
FVector TargetDirection = (TargetLocation - HeadLocation);
TargetDirection.Normalize();

float AngleDot = FVector::DotProduct(HeadForward, TargetDirection);
float Angle =  FMath::RadiansToDegrees(FMath::Acos(AngleDot)); 

平面判断一个点在三角形内

使用同向法,如点P在三角形内,则计算各点和P的向量与三边的叉积,如果结果一致则在三角形内。

//空间三角形
//按照逆时针顺序插入值并计算法向量
template <class T>
class Triangle
{
public:
    Vec3<T> v0;
    Vec3<T> v1;
    Vec3<T> v2;

    Triangle()
    {

    }

    Triangle(Vec3<T> v0, Vec3<T> v1, Vec3<T> v2)
    {
        this->v0 = v0;
        this->v1 = v1;
        this->v2 = v2;     
    }

    // v1 = Cross(AB, AC)
    // v2 = Cross(AB, AP)
    // 判断矢量v1和v2是否同向
    bool SameSide(Vec3<T>& A, Vec3<T>& B, Vec3<T>& C, Vec3<T>& P)
    {
        Vec3<T> AB = B - A ;
        Vec3<T> AC = C - A ;
        Vec3<T> AP = P - A ;

        Vec3<T> v1 = AB ^ AC;
        Vec3<T> v2 = AB ^ AP;

        // v1 and v2 should point to the same direction
        return v1*v2 >= 0 ;
        //return v1 * v2 > 0 ;
    }

    // 判断平面点P是否在平面三角形内
    bool PointInTriangle2D(Vec3<T>& P)
    {
        Vec3<T> A(v0.x(), v0.y(), 0);
        Vec3<T> B(v1.x(), v1.y(), 0);
        Vec3<T> C(v2.x(), v2.y(), 0);
        return SameSide(A, B, C, P) && SameSide(B, C, A, P) && SameSide(C, A, B, P);
    }
};

空间或平面判断两线段相交

同侧法

如果两条线段相交,那么一条线段的两端点必然位于另一条线段的两端点的异侧。那么问题就可以转换成点是否在一条线段的同侧。同侧判断可以通过向量叉乘的方法来实现,即判断最后叉乘的方向是否相同。

不过这个算法可以判断定性判断,无法定量判断准确的交点。

向量方程法

已知空间中线段的起点O和终点E,那么显然方向向量D为:

[D = E - O ]

这时,可以确定线段上某一点P为:

[P = O + tD ]

其中,t为范围满足 0<=t<=1的标量。

那么这个问题就转换成了求解2行2列的线性方程组,如果有解,说明存在交点并直接求出。2行2列线性方程组直接使用克莱姆法则求解即可。

//空间直线
template <class T>
class LineSegment
{
public:
    Vec3<T> startPoint;
    Vec3<T> endPoint;
    Vec3<T> direction;

    Vec3<T> min;
    Vec3<T> max;

    LineSegment()
    {
    }

    LineSegment(Vec3<T> start, Vec3<T> end)
    {
        startPoint = start;
        endPoint = end;
        direction = end - start;
        CalMinMax();
    }

    inline void Set(Vec3<T> start, Vec3<T> end)
    {
        startPoint = start;
        endPoint = end;
        direction = end - start;
        CalMinMax();
    }

    inline void CalMinMax()
    {
        min.x() = std::min<T>(startPoint.x(), endPoint.x());
        min.y() = std::min<T>(startPoint.y(), endPoint.y());
        min.z() = std::min<T>(startPoint.z(), endPoint.z());

        max.x() = std::max<T>(startPoint.x(), endPoint.x());
        max.y() = std::max<T>(startPoint.y(), endPoint.y());
        max.z() = std::max<T>(startPoint.z(), endPoint.z());
    }

    //两条线段相交
    inline static bool Intersection2D(LineSegment & line1, LineSegment & line2, Vec3<T>& insPoint)
    {
        double D = -line1.direction.x() * line2.direction.y() + line1.direction.y() * line2.direction.x();
        if(D == 0.0)
        {
            return false;
        }

        auto O12 = line2.startPoint - line1.startPoint;
        T D1 = -O12.x() * line2.direction.y() + O12.y() * line2.direction.x();
        T D2 = line1.direction.x() * O12.y() - line1.direction.y() * O12.x();

        T t1 = D1 / D;
        if(t1<0 || t1 > 1)
        {
            return false;
        }

        T t2 = D2 / D;
        if(t2<0 || t2 > 1)
        {
            return false;
        }

        insPoint = line1.startPoint + line1.direction * t1;     //这样计算得到的Z值是不准确的

        return true;
    }  
};