向量:既有方向又有大小
点向量:不记录方向,只记录位置
方向向量:基于原点记录了一个方向
零向量:zero
创建一个结构体
// 自定义封装一个向量的结构体
public struct Vector3
{
// 分别标识三维向量的x,y,z
public float x;
public float y;
public float z;
// 构造方法赋初值
public Vector3(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
// 后序添加一些属性和方法...
}
在 Unity 中 Vector3 是一个标识三维向量的类,同时结构体对于值类型的运算效率较高,所以我通过结构体来封装一个自定义的 Vector3。
添加方向向量属性
// 零向量
public static Vector3 zero
{
get
{
return new Vector3(0, 0, 0);
}
}
// 前进向量
public static Vector3 forward
{
get
{
return new Vector3(0, 0, 1);
}
}
// 后退向量
public static Vector3 back
{
get
{
return new Vector3(0, 0, -1);
}
}
// 左移动向量
public static Vector3 left
{
get
{
return new Vector3(-1, 0, 0);
}
}
// 右移动向量
public static Vector3 right
{
get
{
return new Vector3(1, 0, 0);
}
}
// 跳起向量
public static Vector3 up
{
get
{
return new Vector3(0, 1, 0);
}
}
// 向下向量
public static Vector3 down
{
get
{
return new Vector3(0, -1, 0);
}
}
计算向量的长度(模长)/对应的单位向量
- 计算向量的模长:
- 计算对应的单位向量:
// 向量的长度 模长
public float magnitude
{
get
{
return (float)Math.Sqrt(x * x + y * y + z * z);
}
}
// 单位向量 单位为1的
public Vector3 normalized
{
get
{
if(x == 0 && y == 0 && z == 0)
{
throw new Exception("零向量没有单位向量!");
}
return new Vector3(x / magnitude, y / magnitude, z / magnitude);
}
}
向量的加法/减法
- 加法:
- 减法:
取反
// 向量的加法,通过运算符重写
public static Vector3 operator +(Vector3 v1, Vector3 v2)
{
return new Vector3(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
}
// 向量的减法
// 取反
public static Vector3 operator -(Vector3 v)
{
return new Vector3(-v.x, -v.y, -v.z);
}
// 减法换成 加法
public static Vector3 operator -(Vector3 v1, Vector3 v2)
{
return v1+(-v2);
}
向量的数乘
向量的数乘就是为了将向量放大或者缩小多少倍。
:
// 向量的 数乘
public static Vector3 operator *(Vector3 v, float n)
{
return new Vector3(v.x * n,v.y*n,v.z*n);
}
// 重载:防止 数字在前,向量在后的情况
public static Vector3 operator *(float n, Vector3 v)
{
return new Vector3(v.x * n, v.y * n, v.z * n);
}
后续内容需要了解的前置知识:
- 勾股定理(直角三角形)
- 三角函数
推导三角和差公式:
已知半径为1(OP2 = OP1 = 1),求 OM 的公式?
OM = OB + BM
- OB = cosβ * OA; OA = cosα * OP2; OB = cosβ * cosα;
- BM = CP2; CP2 = sinθ * AP2; sinθ = sinβ; CP2 = sinβ * AP2; AP2 = OP2 * sinα; CP2 = sinβ * OP2 * sinα = sinβ * sinα; BM = sinβ * sinα;
- OM = OB + BM = cosβ * cosα + sinβ * sinα;
- OM = OP2 * cos(β-α) = cos(β-α);
- cos(β-α) = cosβ * cosα + sinβ * sinα;
向量的点乘
点乘的结果是一个标量,点乘的目的就是用来求夹角用的。
- 点乘公式:
- 点乘的几何意义:
接下来推导一下点乘的几何意义的公式:x1 = |v1| * cosα
x2 = |v2| * cosβ
y1 = |v1| * sinα
y2 = |v2| * sinβ
v1 · v2 = x1 * x2 + y1 * y2
v1 · v2 = |v1| * cosα * |v2| * cosβ + |v1| * sinα * |v2| * sinβ = |v1||v2|(cosα * cosβ + sinα * sinβ)
通过上面推导的,我们可以知道:
cosθ:就是两个向量之间的夹角。
最终就得到了: v1·v2 = |v1| * |v2| * cosθ(两个向量之间的夹角)
// 向量的点乘
// 点乘的结果就是一个标量
// v1 · v2 = x1 * x2 + y1 * y2;
public static float Dot(Vector3 v1, Vector3 v2)
{
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
}
求向量之间的夹角
通过上面的向量点乘几何意义的公式,我们可以很轻易的求出来:
// 求夹角
public static float Angle(Vector3 v1, Vector3 v2)
{
// 一种方式
//return Dot(v1, v2) / v1.magnitude * v2.magnitude;
// 另一种方式:两个向量之间的夹角 和 它们所对应的单位向量的夹角 一样。
float dot = Dot(v1.normalized, v2.normalized);
// 返回弧度表示的角度
float tmp = (float)Math.Acos(dot);
// 转化为角度
return (float)(tmp * 180 / Math.PI);
}
static void Main(string[] args)
{
// 测试方法的正确性
Vector3 v1 = new Vector3(1, 0, 0);
Vector3 v2 = new Vector3(0, 1, 0);
float result = Vector3.Angle(v1, v2);
// 夹角为 90°
Console.WriteLine(result);
}
投影向量
如图: 投影向量就是求 v1 在 v3 投影的模长,也就是 v2 的模长。
|v2| = |v1| * cosα(v1和v3之间的夹角,通过三角和差公式得来)
cosα = v1 · v3 / |v1| * |v3|
|v2| = |v1| * (v1 · v3) / |v1| * |v3| = (v1 · v3) / |v3|
v2 = |v2| * v3的单位向量
// 投影向量
// |v2| = |v1| * (v1 · v3) / |v1| * |v3| = (v1 · v3) / |v3|
// v2 = |v2| * v3.normalized
public static Vector3 Project(Vector3 v1, Vector3 v3)
{
// 返回的结果就是 v2 , |v2| = (Dot(v1, v3) / v3.magnitude)
return v3.normalized * (Dot(v1, v3) / v3.magnitude);
}
求两个向量之间的距离
// 求两个位置之间的长度
public static float Distance(Vector3 v1, Vector3 v2)
{
return (v1 - v2).magnitude;
}
两个向量的叉乘
两个向量的叉乘就是求得法线向量。
public static Vector3 Cross(Vector3 v1, Vector3 v2)
{
return new Vector3(v1.y * v2.z - v1.z * v2.y, v1.z * v2.x - v1.x * v2.z, v1.x * v2.y - v1.y * v2.x);
}
插值
// 插值
public static Vector3 Lerp(Vector3 v1, Vector3 v2, float time)
{
if(time < 0)
{
time = 0;
}
return v1+((v2-v1)*time);
}
插值的使用场景:
使用移动变得平滑(开始会很快,离目标越近就越慢,最后无限接近于目标)
void Update()
{
// 参数分别是: 起始点,终点,时间
transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);
}