3d数学

在游戏开发中,需要使用到向量,三角函数之类的知识。先学会里面的概念,后续应用才会容易制作。

1.笛卡尔坐标系

2d坐标系:x,y

3d坐标系:x,y,z

在3d坐标系里面有左手坐标系和右手坐标系。这个可能对人来说有直观认知上的区别,其实是不相悖。

右手坐标系

左手坐标系

附带:

极坐标系(polar coordinates)是指在平面内由极点、极轴和极径组成的坐标系。在平面上取定一点O,称为极点。从O出发引一条射线Ox,称为极轴。再取定一个单位长度,通常规定角度取逆时针方向为正。这样,平面上任一点P的位置就可以用线段OP的长度ρ以及从Ox到OP的角度θ来确定,有序数对(ρ,θ)就称为P点的极坐标,记为P(ρ,θ);ρ称为P点的极径,θ称为P点的极角。

极坐标系用于定位和导航。极坐标通常被用于导航,作为旅行的目的地或方向可以作为从所考虑的物体的距离和角度。

2.三角学

1.直角三角形三角函数概念

a:对边
b:邻边
c:斜边

勾股定理(毕达哥拉斯定理)

c=\sqrt{a^2+b^2}

5=\sqrt{3^2+4^2}

∠A 为\theta

sin(\theta) = \frac{a}{c}

正弦:对边比斜边。

cos(\theta) = \frac{b}{c}

余弦:邻边比斜边。

正切:对边比邻边。

tan(\theta) = \frac{a}{b}

余切:邻边比对边。

ctan(\theta) = \frac{b}{a}

2.角度弧度

半径为1的园,全弧长为2πr。

radian=angle*(\pi/180)

angle = radian*(180/\pi)

角度是两条线段的夹角,弧度是两条线段和园相交的点,在圆弧上走过的距离。

3.诱导公式

1.二倍角

\sin(2\theta)=2\sin(\theta)\cos(\theta)

在阅读Detour源码里面有这样的应用。里面将会读取一个sin cos的一半做乘法。

3.向量

向量计算应用于游戏中来计算位置,里面和三角函数也有关系。

向量和标量不一样,

标量(scale)只表示数值大小;

向量(矢量、vector)包含方向和数值大小。

举例:

速度、位移是向量

速率、长度是标量

零向量是指的长度为0,无方向的向量。

1.加法

将两个向量拼接成平行四边形,对角向量就是加法的结果。两个相同的向量相加,等于将向量长度增加一倍。

\vec{u} + \vec{v} = \vec{a}

2.减法

u向量-v向量,就是指的从u向量目的点指向v向量目标点

\vec{v} - \vec{u} = \vec{w} = \vec{a}

3.向量与标量乘

向量与标量乘法,将向量按照某个长度缩放,一般用于单位向量向前行进、缩回多少距离。

4.获取长度

获取向量从开始到结束的距离。从向量得到标量。利用勾股定理,向量记录的就是直角三角形斜边在x,y轴上的投影长度,斜边长度就是x,y的平方和的开方。

数学公式里面向量长度使用双竖线引用。

\left||\vec{v}\right||=\sqrt{a^2+b^2}

5.normalized

归一化需要将向量长度计算出来,然后将向量在各个维度的分量都除以长度。这样就能得到一个单位向量。归一化用一条竖线。

\vec{v}_{norm} = \frac{\vec{v}} {\left||\vec{v}\right||}

单位化的向量分量的几何意义

x=\cos(\theta)

y=\sin(\theta)

这个特性将会应用于计算位置。

6. Dot Product

\cos\theta=\frac{\vec{u} \cdot \vec{v}} {\left||\vec{u}\right||\left||\vec{v}\right||}

点乘能计算两向量的夹角的cos值。cos有一个特点,在取值±90°的值域都是>0。在游戏中,这种计算能很快判断一个怪物是否在玩家身后。这个函数不能判断左右,但是能判断前后。

7.cross produce

\sin\theta=\frac{\vec{u} \times \vec{v}} {\left||\vec{u}\right||\left||\vec{v}\right||}

叉乘用于算左右。sin有个特点,取值在0~179°都是>0。用找个特点能判定向量是在自己的左边还是右边。

叉乘需要有3个维度才有意义。

\vec{u}\times\vec{v}=\left|\vec{u}\right|\left|\vec{v}\right|\sin(\theta)n

u叉乘v之后结果是sin*u、v向量的分量。n就是垂直于u、v构成平面的垂直法线向量。

8.获取角度

将向量转换成弧度,向量无需归一化。

\angle\theta=\arctan(y,z)

9.常用函数


// 将弧度转换成角度
float radian2angle(float radian) {
    return radian * (180.0f / mathfu::kPi);
}

// 将角度转换成弧度
float angle2radian(float angle) {
    return angle * (mathfu::kPi / 180.0f);
}

// 将向量转换成角度
float vector2angle(mathfu::Vector<float, 2> a) {
    return radian2angle(std::atan2(a.y, a.x));
}

// 角度转换成向量
void angle2vector(float angle, mathfu::Vector<float, 2>& a) {
    auto radian = angle2radian(angle);
    a.y = std::sin(radian);
    a.x = std::cos(radian);
}

// 按照角度,长度,转换一个位置
void movepos(float angle,mathfu::Vector<float,2>& rawPos, float len, mathfu::Vector<float, 2>& out) {
    mathfu::Vector<float, 2> dir;
    angle2vector(angle, dir);
    dir.x *= len;
    dir.y *= len;
    out = rawPos + dir;
}

// 输出一个向量
void outputvector(const char* tag, mathfu::Vector<float, 2>& a) {
    std::cout << tag << " "<< a.x << "," << a.y << "\n";
}

实例

1.计算围绕role的怪物

先检查是否和其他怪物重合

按照±小角度开始偏移尝试是否能站

#include "mathfu/vector.h"
#include "mathfu/constants.h"
#include <iostream>

float radian2angle(float radian) {
    return radian * (180.0f / mathfu::kPi);
}

float angle2radian(float angle) {
    return angle * (mathfu::kPi / 180.0f);
}

float vector2angle(mathfu::Vector<float, 2> a) {
    return std::atan2(a.y, a.x)*(180.0f / mathfu::kPi);
}

void angle2vector(float angle, mathfu::Vector<float, 2>& a) {
    auto radian = angle2radian(angle);
    a.y = std::sin(radian);
    a.x = std::cos(radian);
}

void outputvector(const char* tag, mathfu::Vector<float, 2>& a) {
    std::cout << tag << "=(" << a.x << "," << a.y << ")\n";
}

// rawBattleCircleFix posSelf: linmath.Vector3{X:11424.3, Y:-311.48605, Z:17336.395}, 
// posEnemy: linmath.Vector3{X:11330.254, Y:-311.48605, Z:17465.838},
// newPos: linmath.Vector3{X:11245.467, Y:-311.48605, Z:17601.525},
// angle: -32, e2sLen: 160.00067
// 测试怪物按照弧形排布在玩家周围
void test_monster_battle_cricle()
{
    float dst = 160.0f; // 怪物距离
    float bodyRadius = 80.0f;// 怪物的宽度

    mathfu::Vector<float, 2> monsterPos(11424.3f, 17336.395f);
    mathfu::Vector<float, 2> rolePos(11330.254f, 17465.838f);
    auto dir = monsterPos - rolePos;
    auto angle = vector2angle(dir) + 20.0f;

    mathfu::Vector<float, 2> finalDir;
    angle2vector(angle, finalDir);

    finalDir.x *= dst;
    finalDir.y *= dst;

    mathfu::Vector<float, 2> newPos = rolePos + finalDir;
    
    outputvector("monsterPos", monsterPos);
    outputvector("rolePos", rolePos);
    outputvector("newPos",newPos);
}

cmake定义文件

cmake_minimum_required (VERSION 3.2)
project(math_base)

IF (CMAKE_SYSTEM_NAME MATCHES "Windows")
    add_definitions(-DWIN32)
    add_definitions(-DWIN32_LEAN_AND_MEAN)
    add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS)
    add_definitions(-D_CRT_SECURE_NO_WARNINGS)
    add_definitions(-D_USE_MATH_DEFINES)
ENDIF (CMAKE_SYSTEM_NAME MATCHES "Windows")

include_directories( $ENV{MATHFU_PATH}/include )

file(GLOB_RECURSE all_SRC "src/*.cpp" 
    "src/*.hpp" "src/*.h" 
    "src/*.cc" )

add_executable(test_math ${all_SRC})

target_link_libraries(test_math)

计算的位置,在坐标系上的位置

2.计算园外切线

利用三角函数来计算点对于圆的切线;

void test_fun_fix_circle()
{
    // 圆半径
    float radius = 8.0f;
    // 圆心位置
    mathfu::Vector<float, 2> circlePos(0, 0);
    // 园外一点
    mathfu::Vector<float, 2> checkPos(10, 12);
    // 园外点指向圆心向量
    mathfu::Vector<float, 2> l = circlePos - checkPos;

    // 计算距离
    auto len = l.Length();

    // 计算出园外点指向圆心向量角度
    float cos = radius / len;
    float radian = std::acos(cos);
    
    // 直角三角形,计算另一角度
    float offsetangle = 90 - radian2angle(radian);
    
    // 将园外点指向圆心向量归一化,将向量转换成角度
    auto dir = l.Normalized();
    auto oldAngle = vector2angle(l);

    // 将 园外点指向圆心向量角度 + 通过直角三角形方式计算出来的夹角
    // 这个夹角就切线方向
    auto finalAngle = oldAngle + offsetangle;

    // 将角度换算成为向量
    mathfu::Vector<float, 2> finalDir;
    angle2vector(finalAngle, finalDir);

    outputvector("finalDir", finalDir);

    // 计算出切线点离 园外点 的距离,将向量长度设置成这个距离
    auto al = std::sqrt(len * len - radius * radius);
    finalDir.x = finalDir.x * al;
    finalDir.y = finalDir.y * al;

    // 使用 园外点 + 偏移向量就能得到切线过圆边的点
    auto finalPos = checkPos + finalDir;

    outputvector("finalPos", finalPos);
}

效果:

3.计算某个点是否为三角形内

原理在 b站 GAMES101-现代计算机图形学入门-闫令琪 38分钟处讲解了。

叉积是用于控制左右。如果获取的值域是正数左边,负数为右边。

利用的是,三角形三点按照顺时针的向量,以及p点的向量的叉乘永远是相同的象限的。


void test_triangle_inner()
{
    mathfu::Vector<float, 2> A(8.66992, 6.79278);
    mathfu::Vector<float, 2> B(4.96974, 2.1609);
    mathfu::Vector<float, 2> C(12.31686, 1.78822);
    mathfu::Vector<float, 2> P(8.98936, 4.07754);

    mathfu::Vector<float, 2> P2(11, 5);

    auto u = B - A;
    auto v = C - B;
    auto w = A - C;

    std::cout << "start check P\n";
    auto t = P - A;
    std::cout << t.DotProduct(t, u) << "\n";
    t = P - B;
    std::cout << t.DotProduct(t, v) << "\n";
    t = P - C;
    std::cout << t.DotProduct(t, w) << "\n";


    std::cout << "start check P2\n";
    t = P2 - A;
    std::cout << t.DotProduct(t, u) << "\n";
    t = P2 - B;
    std::cout << t.DotProduct(t, v) << "\n";
    t = P2 - C;
    std::cout << t.DotProduct(t, w) << "\n";

}

//output:
//start check P
//11.3947
//28.8183
//23.5922
//start check P2
//- 0.317775
//43.247
//20.8761
// 如果旋转方向相同,这些向量的sin值的符号都是一致的。
//
// 这个计算不会涉及到开方,所以很好用。
//static inline T DotProductHelper(const Vector<T, 2>& v1,
//                                  const Vector<T, 2>& v2) {
//   return v1[0] * v2[0] + v1[1] * v2[1];
// }

4.计算矩形内的一点

原理和三角形检查一样。

先将一个矩形做偏移,旋转:

取两个点开始计算:

void test_rect_inner()
{
    mathfu::Vector<float, 2> r1(-4, -5);
    mathfu::Vector<float, 2> r2(-1.401923789, -3.5);
    mathfu::Vector<float, 2> r3(-4.901923789, 2.562177826);
    mathfu::Vector<float, 2> r4(-7.5, 1.062177826);


    mathfu::Vector<float, 2> i(-4.88, -1.49);
    mathfu::Vector<float, 2> j(-8.26, -3.77);

    std::cout << "计算i点\n";
    auto t1 = r2 - r1;
    auto t2 = i - r1;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1,t2) << "\n";
    t1 = r3 - r2;
    t2 = i - r2;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r4 - r3;
    t2 = i - r3;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r1 - r4;
    t2 = i - r4;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";

    std::cout << "计算j点\n";

    t1 = r2 - r1;
    t2 = j - r1;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r3 - r2;
    t2 = j - r2;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r4 - r3;
    t2 = j - r3;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
    t1 = r1 - r4;
    t2 = j - r4;
    std::cout << mathfu::Vector<float, 2>::DotProduct(t1, t2) << "\n";
}

4.矩阵

1.概念

\left|\begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{matrix}\right| \tag{A}

矩阵是按照行列方式排列的数字。是线性代数里面中重要的数学概念。

描述矩阵一般都是说

r \times c

的矩阵。r是rows行(横着的条目算1个),c是column(竖着的条目算1个)

方阵就是行和列数目都是相同的。在3d运算中经常使用这种方阵

单位矩阵,对角线都是1,其余都是0。

\left|\begin{matrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{matrix}\right| \tag{M}

书写的时候,矩阵都是写成大写。M,A,R。手写的时候,矩阵的括号其实要写成圆括号(),印刷体中都是[]表示。

向量转换成矩阵可以成为 行矩阵、列矩阵。

2.矩阵运算

单位矩阵

1.转置

\left| \begin{matrix} 1 \\ 2 \\ 3 \end{matrix}\right|\tag{M}

\left| \begin{matrix} 1 & 2 & 3\\ \end{matrix} \tag{A}\right|

记作:

M^t=A

2.矩阵与标量乘

M*k= k *\left| \begin{matrix} m11 & m12 & m13\\ m21 & m22 & m23\\ m31 & m32 & m33\\ \end{matrix} \right| = k \left| \begin{matrix} k*m11 & k*m12 & k*m13\\ k*m21 & k*m22 & k*m23\\ k*m31 & k*m32 & k*m33\\ \end{matrix} \right|

3.矩阵乘法

公式定义:

(AB)_{ij}=\sum_{k=1}^p a_{i1}b_{1j}+a_{i2}b_{2j}+...+a_{ip}b_{pj}

公式分解:

A*B= \left| \begin{matrix} a11 & a12 & a13\\ a21 & a22 & a23\\ \end{matrix} \right| * \left| \begin{matrix} b11 & b12 \\ b21 & b22 \\ b31 & b32 \\ \end{matrix} \right|=\left| \begin{matrix} a11b11+a12b21+a13b31 & a11b21+ a12b22+a13b23\\ a21b11+a22+b21+a23b31 & a21b12+a22b22+a23b32\\ \end{matrix} \right|

A*B=C

1、当矩阵A的列数(column)等于矩阵B的行数(row)时,A与B可以相乘。

2、矩阵C的行数等于矩阵A的行数,C的列数等于B的列数。

3、乘积C的第m行第n列的元素等于矩阵A的第m行的元素与矩阵B的第n列对应元素乘积之和。

wps矩阵计算

使用矩阵来做位移,旋转,缩放操作:

坐标系上的位置:

4.克罗内克积(Kronecker Product)

克罗内克积是两个任意大小的矩阵间的运算,符号记作 。克罗内克积也被称为直积或张量积.以德国数学家利奥波德·克罗内克命名。

\left| \begin{matrix} a11 & a12 \\ a21 & a22 \\ \end{matrix} \right| \bigotimes \left| \begin{matrix} b11 & b12 \\ b12 & b22 \\ \end{matrix} \right| = \left| \begin{matrix} a11b11 & a11b12 & a12b11 & a12b12 \\ a11b21 & a11b22 & a12b21 & a12b22 \\ a21b11 & a11b12 & a22b21 & a22b22 \\ a21b21 & a21b22 & a22b21 & a22b22 \\ \end{matrix} \right|

5.四元数

1.概述

四元数是1843年发明的。爱尔兰数学家哈密顿(William Rowan Hamilton,1805-1865)。

四元数运算在电动力学与广义相对论中有广泛的应用。四元数可以用来取代张量表示。有时候采用带有复数元素之四元数会比较容易,导得结果不为除法代数之形式。然而亦可结合共轭运算以达到相同的运算结果。

从概念上来看,就是在数学里面定义对于-1开方最后获取的值。

i=\sqrt{-1}

复数是对实数集合的一种扩展。

在游戏开发应用里面,四元数用于做旋转计算。所以最好先将矩阵搞清楚。复数已经是一种数学工具了,在实际世界里面不能表示什么意义。

四元数不是专门给3D图形学设计的,但是能用在3D图形学里面:

  • 3D相机控制
  • 压缩存储
  • 平滑3D插值

复数定义

z=a+b*i

a是实部,b是虚部;

复数与标量相乘、相除

k*Z_{1}=k*(a+bi)=k*a+(k*b)*i

复数加减

Z_{1}=(a+b*i)

Z_{2}=(c+d*i)

Z_{1}+Z_{2}=(a+b*i)+(c+d*i)=((a+c)+(b+d)*i)

Z_{1}-Z_{2}=(a+bi)-(c+di)=(a-c)+(b-d)i

复数加法恒等元

复数恒等元

(0+0*i)

复数除法

Z_{1}/Z_{2}=\frac{(a+b*i)}{(c+d*i)}

推算的时候,需要分子和分母都乘上分母的共轭复数。

共轭(Conjugate)

两个实部相等,虚部互为相反数的复数互为共轭复数(conjugate complex number)。(当虚部不等于0时也叫共轭虚数)复数z的共轭复数记作 (z上加一横,英文中可读作Conjugate z,z conjugate or z bar),有时也可表示为

Z^*=\overline{Z}

Z=(a+bi)

\overline{Z}=(a-bi)

计算负数的模

\left||p\right||=\sqrt{p\overline{p}}

中划线

\underline{\text{下划线}}

\overline{\text{上划线}}

2.欧拉恒等式

我还没有理解到这个意义。

\cos\varphi+i\sin\varphi=e^{i\varphi}

\varphi=\pi

推导

e^{i\pi}+1=0

3.欧拉角

4.万向节死锁

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容