整理一下,很重要。做个总结,留个脚印。
目录
文章目录
1.什么是向量?
向量,指具有大小和方向的量。
2.加减法
------ 1.加法:
数学意义:
几何意义:
------ 2.减法:
数学意义:
几何意义:
------ 3.数学规律:
满足交换律
3.长度,单位向量和正规化
向量表示了长度和方向。所以我们可以求向量的长度和它的方向。
长度:长度可以理解为三维坐标点到坐标原点的距离,长度计算公式为:
计算平方根是相对昂贵的计算,如果你的需求只是比较两个向量长度的大小,直接用它们长度的平方比较就可以了。
方向:方向可以理解为坐标原点到三维坐标点的方向。
向量长度为1的向量叫做单位向量。
将非单位向量转化为单位向量的过程叫做正规化。
正规化的时候,只要每个分量除以向量长度就可以。
4.与标量相乘
向量可以与标量进行乘积,你只需将每个分量乘以这个标量即可,向量的正规化就是其应用之一。
如果向量乘以正数,那么只改变向量长度。如果乘以负数,会得到反方向的向量。
5.点乘
字面意思,就是两个向量各个分量分别相乘最后相加得到的一个标量。
数学意义:
几何意义:
数学规律:
满足交换律:
满足分配率:
不满足结合律:
但满足带有实数的结合律
6.叉乘
两个向量叉乘会得到第三个向量,给定两个向量,可以确定一个平面,叉乘得到的向量会垂直于这个向量,称之为平面的法线。
数学意义:(xyzzy)
几何意义:
需要注意|c|的值是向量a,b构成的平行四边形的面积。
右手螺旋定则:
在右手坐标系下,将两向量移动到同一起点,右手四指从a旋转到b,则拇指所指方向,即为结果向量的方向。
由于Unity是左手坐标系,所以结果向量的方向与之相反。
数学规律:
叉乘不满足交换律,但是满足反交换律:
7.应用:
------------- 1.两点间距离计算:
题目:求坐标点a与坐标点b之间的距离?
思路:坐标点b减去坐标点a得到的向量ab的长度值就是点a到点b的距离。
实现:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VecAddFrist : MonoBehaviour
{
void Start()
{
Vector3 a = new Vector3(0,5,3);
Vector3 b = new Vector3(6,2,1);
//求vec1和vec2的距离
//
//思路:先求出向量ab,在求出向量ab长度
//
//ab=b-a;
Vector3 ab =new Vector3(b.x-a.x,b.y-a.y,b.z-a.z);
//
//ab的长度就是a点到b点的距离
float abdis =Mathf.Sqrt(ab.x*ab.x+ab.y*ab.y+ab.z*ab.z);
//
//使用系统自带函数来验证
float sysdis = Vector3.Distance(a,b);
if (abdis== sysdis)
{
Debug.Log("Success");
}
}
}
------------- 2.第三人称控制器:
1.题目:字面意思,做一个第三人称控制器。
2.新的概念:四元素与向量的右乘操作返回一个将向量做旋转操作后的向量。因此Quaternion.Euler(0,90,0)*Vector3(0,0,-10)表示将向量Vector3(0,0,-10)做绕y轴90度旋转后的结果.因该等于Vector3(-10,0,0)
3.思路:
首先第三人称控制器的意思是摄像机围绕目标做圆周运动,所以摄像机到目标物体的距离是不变的。
也就是说 摄像机坐标VecCamera 减去 目标物体的坐标VecTarget 得到的向量Veca的长度值是不会变得。
VecCamera-VecTarget=Veca.
无论摄像机和目标物体怎么变,Veca的长度值是不会变的。
摄像机如果旋转,那Veca也会跟着旋转。
我们可以假设向量Veca的值为(0,0,-distance),摄像机在目标物体的正后方。
然后根据四元数与向量的右乘操作得到旋转后的向量Veca的值,即旋转后的新的角度四元素值与(0,0,-distance)的右乘得到新的向量VecNewa。
然后 VecCamera=VecNewa+VecTarget.
新的摄像机坐标算出来了,这样就完成了第三人称控制器,是的简单的加减运算就解决了,是不是很神奇。
4.实现:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//第三人称控制器
//思路:
//使用四元素与向量的右乘操作
//首先第三人称控制器的意思是摄像机围绕目标做圆周运动,所以摄像机到目标运动的距离是不变的。
//也就是说 摄像机vec 减去 目标物体的vec 得到的向量的值是不会变得。
//我们使用四元素与向量(0,0,-distance)的右乘操作得到的就是这个向量,再用这个值加上目标物体的坐标就是做圆周运动后位置改变的摄像机的坐标
//这里取负值是因为我们想要初始状态下摄像机在目标物体的后边。
public class VecAddScecond : MonoBehaviour
{
public float xSpeed = 1;//x轴速度
public float ySpeed = 1.5F;//y轴速度
public float yMinLimit = 10;//y轴最低角度
public float yMaxLimit = 80;//y轴最高角度
//到目标的距离
public float ScrollWheelScaling = 3f;//滑轮速度
public float Targetdistance = 5;//摄像机到目标距离
public float MAXdistance = 10;//距离最大值
public float Mindistance = 2;//距离最小值
//
private Camera mainCamera;//主摄像机
private GameObject Gotarget;//围绕目标
//
private float xAngle;//x角度
private float yAngle;//y角度
void Start()
{
mainCamera = Camera.main;
Gotarget = GameObject.CreatePrimitive(PrimitiveType.Cube);
mainCamera.transform.LookAt(Gotarget.transform);
}
private void LateUpdate()
{
if (Input.GetMouseButton(1))
{
MouseControl();
}
MouseScrollWheelMethod();
}
private void MouseControl()
{
float a = Input.GetAxis("Mouse X");
float b = Input.GetAxis("Mouse Y");
xAngle = mainCamera.transform.eulerAngles.y;
yAngle = mainCamera.transform.eulerAngles.x;
xAngle += a * xSpeed;
yAngle -= b * ySpeed;
yAngle = ClampAngle(yAngle, yMinLimit, yMaxLimit);
Quaternion newEuler = Quaternion.Euler(yAngle, xAngle, 0);
//四元素与向量的右乘操作
//四元素与向量的右乘操作返回一个将向量做旋转操作后的向量。
//因此Quaternion.Euler(0,90,0)*Vector3(0,0,-10)表示将向量Vector3(0,0,-10)做绕y轴90度旋转后的结果.因该等于Vector3(-10,0,0)
mainCamera.transform.position = newEuler * new Vector3(0.0f, 0.0f, -Targetdistance) + Gotarget.transform.position;
mainCamera.transform.rotation = newEuler;
}
//鼠标滑轮拉近拉远
private void MouseScrollWheelMethod()
{
float MouseScrollWheel = Input.GetAxis("Mouse ScrollWheel");
Targetdistance -= MouseScrollWheel * ScrollWheelScaling;
Targetdistance = Mathf.Clamp(Targetdistance, Mindistance, MAXdistance);
mainCamera.transform.position = mainCamera.transform.rotation * new Vector3(0.0f, 0.0f, -Targetdistance) + Gotarget.transform.position;
}
static float ClampAngle(float angle, float min, float max)
{
if (angle < -360)
{
angle += 360;
}
if (angle > 360)
{
angle -= 360;
}
return Mathf.Clamp(angle, min, max);
}
}
------------- 3.粗略判断两向量夹角:
点乘的几何意义:
\vec a*\vec b=||\vec a||||\vec b||cos\theta
Cos函数图:
从函数图中我们可以得知:
1.如果值为0,说明两向量垂直。
2.如果值为1,说明两向量平行且同向。
3.如果值为-1,说明两向量平行且反向。
4.如果值大于0,方向相同,夹角为锐角。
5.如果值小于0,反向相反,夹角为钝角。
------------- 4.向量反弹:
题目:假设你有一颗球朝着墙壁运动,当这颗球撞到墙上的时候,你想计算反射方向。现在假设墙壁是平行于坐标系中的某一个轴的,那么问题就比较简单,如果墙体平行于x轴,球从墙上弹开,只要将y轴速度取反就行了。但是如果墙体与坐标轴不平行的情况下就不起作用了,这种常见的反射问题可以用向量来解决。如果可以正确计算出向量的反射,那么在墙体任意朝向下都能使用。
已知小球的速度向量v,墙壁的法线n,求小球反射后的速度向量v’。
如图:
思路:
从图中我们可得到
将其带入得到:
ok 完美解决问题。
这里要说明一下点乘的另一个含义是如果是一个向量乘以某个方向的法向量得到的长度是该向量在该方向上的投影,这也是向量a求值依据。
实现:
这里我做一个封闭的2维空间,给小球一个向右的力,让小球在这个空间里无限的反弹。
代码由两部分组成,一部分是墙的脚步,通过计算获取墙到小球方向的墙的法线,这里计算也挺有意思的,这里就不赘述了。简单说一下,通过叉乘判断小球与墙壁的方位,然后决定让墙壁顺时针还是逆时针旋转90度获得朝向小球方位的墙壁的法线,如图:
具体计算就不赘述了。
代码如下:
挂在墙壁上的脚本,有多个墙壁:
using UnityEngine;
//墙壁
//墙壁的法向量算法
/*
* 首先是求出墙壁的朝向向量,使用墙壁的四元数偏转角度*Vector3.right得到WallDir
* 然后求WallDir和WallToPoint的叉乘来判断小球在墙壁的前方还是后方。来让WallDir顺时针旋转或
* 逆时针旋转90度得到朝向小球方向的法向量。如图
*/
//
public class Wall : MonoBehaviour
{
private VecDotFirst dotfirst;
private Vector3 WallNomal;
private void Start()
{
dotfirst = GameObject.FindObjectOfType<VecDotFirst>();
Vector3 WallToPoint = dotfirst.GoSphere.transform.position - this.transform.position;
Vector3 angle = this.transform.eulerAngles;
// Debug.Log(z);
Vector3 WallDir = Quaternion.Euler(new Vector3(0, 0, angle.z)) * Vector3.right;
//叉乘
Vector3 cross = Vector3.Cross(WallDir,WallToPoint);
if (cross.z < 0)
{
WallNomal = Quaternion.Euler(new Vector3(0, 0, -90)) * WallDir.normalized;
}
else
{
WallNomal = Quaternion.Euler(new Vector3(0, 0, 90)) * WallDir.normalized;
}
// this.transform.rotation*Vector3.zero
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Player")
{
dotfirst.Notify(WallNomal);
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if (collision.tag == "Player")
{
dotfirst.Count();
}
}
}
挂在小球上的脚本:
using UnityEngine;
//题目:向量反弹。
//思路:看图片。
public class VecDotFirst : MonoBehaviour
{
public GameObject GoSphere;//小球
private Vector3 VecSpeed;
private Vector3 _WallNomal;
public event Action Trigger;
//
public int count;
void Start()
{
VecSpeed = Vector3.right;
Trigger += VecSpeedhandleMethod;
}
private void VecSpeedhandleMethod()
{
if (count == 2)
{
Debug.Log("出bug了");
VecSpeed = _WallNomal;
}
else
{
//Debug.Log(count);
Vector3 newVecSpeed = VecSpeed - 2 * _WallNomal * Vector3.Dot(_WallNomal, VecSpeed);
VecSpeed = newVecSpeed;
}
}
public void Notify(Vector3 wallNomal)
{
count++;
_WallNomal = wallNomal;
Trigger();
}
public void Count()
{
count--;
}
void Update()
{
GoSphere.transform.Translate(VecSpeed * 0.7f);
}
}
这里有个bug,有时候小球弹到墙角的时候,会同时触发两次触发器事件,导致小球反弹出错,逃出墙壁。这个bug出现频率还是很频繁的。一旦有这个bug,我直接让小球反弹方向等于墙壁法线方向。如图:
------------- 5. 求一条直线穿过平面的交点:
这个我之前博客里有做过研究,因为我之前写博客用的不是markdown,所有代码公式一点都不清晰,所以这里我重新在整理一遍。
首先我们先把这个问题转换成数学问题。
题目:如图,已知一个平面上的一点P0和法向量n,一条直线上的点L0和方向L,求该直线与该平面的交点P。
思路:
我们知道p是直线L上的一点,所以我们可以得出一个公式
P和P0都是平面上的一点。所以P和P0组成的直线与平面的法线的点乘一定为0.得到第二个公式。
将第一个公式带入第二个公式。
由于点乘满足分配率得到
我们求出d的值,带入第一个式子,就能求出p点的值。
实现:
using System.Collections.Generic;
using UnityEngine;
public class VecDotSecond : MonoBehaviour
{
public Material mat;//三角形材质
private Mesh mesh;//三角形mesh
private GameObject GoSphere;//交点
private Vector3 vecLinePoint;//直线的方向
//面需要的点
private List<Vector3> vertices = new List<Vector3>();
//生成三边面时用到的vertices和index
private List<int> triangles = new List<int>();
//直线
public List<Vector3> veclines;
void OnDrawGizmos()
{
if (veclines.Count > 1)
{
Gizmos.DrawLine(veclines[0], veclines[1]);
}
}
private void Awake()
{
this.gameObject.AddComponent<MeshFilter>();
this.gameObject.AddComponent<MeshRenderer>();
mesh = new Mesh();
AddFrontFace();
//为点和index赋值
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
//重新计算顶点和法线
mesh.RecalculateBounds();
mesh.RecalculateNormals();
//将生成的面赋值给组件
GetComponent<MeshFilter>().mesh = mesh;
GetComponent<MeshRenderer>().material = mat;
print(mesh.normals[0]);
//直线的两个点
veclines.Add(new Vector3(0,1,-10));
veclines.Add(new Vector3(0.11f,0.36f,3));
GoSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
GoSphere.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
float i = 0;
void Update()
{
if (i > 2)
{
vecLinePoint = (veclines[1] - veclines[0]);//计算直线的方向
Vector3 vec = GetIntersectWithLineAndPlane(veclines[0], vecLinePoint, mesh.normals[0], mesh.vertices[0]);
//print(vec.x+"*******"+vec.y+"*********"+vec.z);
GoSphere.transform.position = vec;//赋值
bool k = IsVecPosPlane(mesh.vertices, vec);//交点是否在多边形内部
if (k)//如果在范围内是红色,在范围外是蓝色
{
GoSphere.GetComponent<Renderer>().material.color = Color.red;
}
else
{
GoSphere.GetComponent<Renderer>().material.color = Color.blue;
}
print(k);
//计算位置
i = 0;
}
else
{
i += Time.deltaTime;
}
}
/// <summary>
/// 计算直线与平面的交点
/// </summary>
/// <param name="point">直线上某一点</param>
/// <param name="direct">直线的方向</param>
/// <param name="planeNormal">垂直于平面的的向量</param>
/// <param name="planePoint">平面上的任意一点</param>
/// <returns></returns>
private Vector3 GetIntersectWithLineAndPlane(Vector3 point, Vector3 direct, Vector3 planeNormal, Vector3 planePoint)
{
float d = Vector3.Dot(planePoint - point, planeNormal) / Vector3.Dot(direct.normalized, planeNormal);
//print(d);
return d * direct.normalized + point;
}
/// <summary>
/// 确定坐标是否在平面内
/// </summary>
/// <returns></returns>
private bool IsVecPosPlane(Vector3[] vecs, Vector3 pos)
{
float RadianValue = 0;
Vector3 vecOld = Vector3.zero;
Vector3 vecNew = Vector3.zero;
for (int i = 0; i < vecs.Length; i++)
{
if (i == 0)
{
vecOld = vecs[i] - pos;
}
if (i == vecs.Length - 1)
{
vecNew = vecs[0] - pos;
}
else
{
vecNew = vecs[i + 1] - pos;
}
RadianValue += Mathf.Acos(Vector3.Dot(vecOld.normalized, vecNew.normalized)) * Mathf.Rad2Deg;
vecOld = vecNew;
}
if (Mathf.Abs(RadianValue - 360) < 0.1f)
{
return true;
}
else
{
return false;
}
//vecOld = vecs[0] - pos;
//vecNew = vecs[1] - pos;
//RadianValue += Mathf.Acos(Vector3.Dot(vecOld.normalized, vecNew.normalized)) * Mathf.Rad2Deg;
////
//vecOld = vecs[1] - pos;
//vecNew = vecs[2] - pos;
//RadianValue += Mathf.Acos(Vector3.Dot(vecOld.normalized, vecNew.normalized)) * Mathf.Rad2Deg;
////
//vecOld = vecs[2] - pos;
//vecNew = vecs[0] - pos;
//RadianValue += Mathf.Acos(Vector3.Dot(vecOld.normalized, vecNew.normalized)) * Mathf.Rad2Deg;
}
void AddFrontFace()
{
//添加4个点
vertices.Add(new Vector3(0, 0, 0));
vertices.Add(new Vector3(0, 1, 0));
vertices.Add(new Vector3(1, 0, 0));
triangles.Add(0);
triangles.Add(1);
triangles.Add(2);
}
}
------------- 6.让2D图片指向鼠标点击的方向:
题目:字面意思,不使用unity自带api来旋转一个2d图片指向鼠标点击的方向:
思路:
根据点乘的几何意义:
得到角的弧度值:
由于得到的弧度值只能在[0,π],得到的角度值只能是[0,180],所以需要判断是顺时针转还是逆时针转。
这里可以使用叉乘来解决,根据叉乘的右手螺旋定则,如果是顺时针转,那么图片正方向和点击方向得到的向量叉乘的z值肯定是小于0的(由于Unity是左手坐标系,是与右手坐标系下右手螺旋定则上得到的结果向量方向相反的),所以选择的时候使用Vectpr3.back作为旋转轴,反之则使用forword。
让图片旋转到指定角度完美解决问题。
实现:
using UnityEngine;
public class VecDotFirst : MonoBehaviour
{
// public GameObject Gosprite;
public GameObject GoImage;//ugui里面的Image
public GameObject GoSprite;//2D GameObject里的Sprite
void Start()
{
}
//Sprite2D图片旋转
private void GoSpriteRotateMethod(Vector3 mousePosition, Transform trans)
{
//这里我们先要把sprite的坐标转换成屏幕坐标
Vector3 GoVector3 = Camera.main.WorldToScreenPoint(GoSprite.transform.position);
Vector3 GoSpriteToPoint = mousePosition - GoVector3;
//Debug.Log(GoSpriteToPoint);
Vector3 CameraToPoint = mousePosition - Camera.main.transform.position;
//GoSpriteToPoint = CameraToPoint - GoVector3;没有必要呵呵呵呵呵呵23333
// Debug.Log(GoSpriteToPoint);
//通过点乘我们得到角度0-180
float Angle = Mathf.Acos(Vector3.Dot(trans.up.normalized, GoSpriteToPoint.normalized)) * Mathf.Rad2Deg;
//由于得到的角度只能是0-180,所以我们还得利用叉乘判断是向左转还是向右转
Vector3 axis;
Vector3 cross = Vector3.Cross(trans.up, GoSpriteToPoint);
if (cross.z < 0)
{
axis = Vector3.back;
}
else
{
axis = Vector3.forward;
}
trans.RotateAround(trans.position, axis, Angle);
}
//UGUI Image图片旋转
private void GoImageRotateMethod(Vector3 mousePosition, Transform trans)
{
//由于UGUI里的坐标本来就是屏幕坐标,所以不需要转换
//首先是获得图片指向点击方向的向量
Vector3 GoToPoint = mousePosition - trans.position;
//然后求图片与该向量的角度值
float Angle = Mathf.Acos(Vector3.Dot(trans.up.normalized, GoToPoint.normalized)) * Mathf.Rad2Deg;
//由于得到的角度值是0-180,所以需要判断是顺时针转还是逆时针转
Vector3 cross = Vector3.Cross(trans.up, GoToPoint);
Vector3 axis;
if (cross.z < 0)
{
axis = Vector3.back;
}
else
{
axis = Vector3.forward;
}
trans.RotateAround(trans.position, axis, Angle);
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
Vector3 mousePosition = Input.mousePosition;//鼠标点击的坐标,是屏幕坐标系下的
GoSpriteRotateMethod(mousePosition, GoSprite.transform);
GoImageRotateMethod(mousePosition, GoImage.transform);
}
}
总结:
向量点乘,叉乘,四元素与向量相乘,向量加减的应用,以后出现新的向量运算应用问题我也会继续这方面的博客。