1、前言
前段时间接到一个开发任务,在unity中做一个仿真模拟的功能,因为不是简单的直线标准工件,一般来说简单的直线工件利用三角函数就能解决相关的计算,从而实现仿真模拟。本次模拟涉及到圆、圆弧以及直线,需要求解直线与圆相交的直线与直线相交,以及求解线段在圆内的折射、反射,思来想去就使用了向量计算,分享下开发过程中使用到的关于向量的计算。
2、向量的基本运算与运用
向量的基本运算及运用不过多介绍,网上文章较多,可参考向量运算与应用,本次主要分享在unity中向量的加减、向量点乘及向量旋转。
3、unity中向量的运用
3.1向量的加减与坐标点计算
在unity三维坐标系中一个坐标用类Vector3表示,在二维坐标系中用类Vector2表示,若已知点A坐标为(x1,y1,z1),B点坐标为(x2,y2,z2),向量AB=B-A(箭头由A指向B),向量在unity中也是用Vector3来表示,向量不能简单理解为一个坐标点,这点要注意。
//点A
var A = new Vector3(x1, y1, z1);
//点B
var B= new Vector3(x2, y2, z2);
//向量AB
var AB = B - A;
已知向量AB,如果这个时候有一个点C距离点A或点B的距离是已知的(点C在向量AB的方向上),怎么求这个坐标点的坐标呢?很简单,先求向量AB的单位向量,C点坐标就等于点A或点B坐标加上方向向量乘以对应的距离。需要判断的是C在AB向量的正方形还是反方向,这个结合实际情况判断,为正就A点加上单位向量乘以距离,反之则减去。
//假设点A距离点C距离为10
var distance = 10;
//点C的坐标,若在向量AB的正方向上则
var C = A + AB.normalized * distance;
//若在向量AB的负方向上则
C = A - AB.normalized * distance;
3.2坐标点之间距离的计算
unity中两个坐标点之间距离的计算利用Vector3的函数Distance即可得到,非常简单,例如求A点、C点之前的距离:
//点A到点C的距离
var distanceAToC = Vector3.Distance(A, C);
3.3向量的点乘
向量的点乘在unity中用类Vector3的静态函数Dot(d1,d2)表示,即向量d1与d2进行点乘计算,数学意义就是向量d1与向量d2围成的平行四边形的面积。
接着上面的代码,存在点D,向量AB与向量CD的夹角为r,通过向量点乘公式可以求的夹角的弧度值。
//点D
var D = new Vector3(x3, y3, z3);
//向量CD
var CD = D - C;
//向量AB与向量CD的夹角的余弦值
var cos = Vector3.Dot(AB, CD) / (AB.magnitude * CD.magnitude);
//夹角的弧度值
var radians = Mathf.Acos(cos);
//夹角的角度值
float degrees = Mathf.Rad2Deg * radians;
由单位向量的定义,可以先求每个向量的单位向量,再求夹角,上述代码也可描述为:
var D = new Vector3(x3, y3, z3);
var CD = D - C;
var cos = Vector3.Dot(AB.normalized, CD.normalized) / (AB.normalized.magnitude * CD.normalized.magnitude);
//夹角的弧度值
var radians = Mathf.Acos(cos);
//夹角的角度值
float degrees = Mathf.Rad2Deg * radians;
var incidentPoint = new Vector3(x, refPoint.y, z);
利用向量的加减、距离计算,加上向量的点乘这些知识点,再运用二分法,可以轻松求解直线与圆的交点,直线与直线的交点等数学问题。下面放一段利用二分法求直线与圆相交点的代码,仅供参考:
//直线的点
tempIncident = new Vector3(x, centerElem.y, z);
//求直线在圆表面的交点
xLeft = tempIncident.x;
xRight = 0;
x = (xLeft + xRight) / 2;
//由圆的方程知x得z
z = Mathf.Sqrt((radius * radius) - Mathf.Pow(x + (wedgeLength / 2), 2)) - radius;
//由直线方程式(x-x1)/(X2-x1)=(z-z1)/(z2-z1)可得
difference = (x - tempIncident.x) / (centerElem.x - tempIncident.x)
- (z - tempIncident.z) / (centerElem.z - tempIncident.z);
--------利用二分法求解交点----------
count = 0;
//当误差小于0.00001或迭代计算次数超过20次即退出循环
while (Mathf.Abs(difference) > 0.00001 && count < 20)
{
if (difference > 0)
{
xLeft = x;
}
else
{
xRight = x;
x = (xLeft + xRight) / 2;
// 计算新的中点坐标和函数值
z = Mathf.Sqrt((radius * radius) - Mathf.Pow(x + (wedgeLength / 2), 2)) - radius;
difference = (x - tempIncident.x) / (centerElem.x - tempIncident.x)
- (z - tempIncident.z) / (centerElem.z - tempIncident.z);
count++;
}
//交点坐标
incidentPoint = new Vector3(x, centerE
3.4向量的旋转
什么情况下能用到向量的转转呢?就是已知一条向量在物体上反射之后,求反射后的向量,会用到向量的旋转,已知条件就是向量、反射点的法向量与向量的夹角。还是以上面的简单例子为例子进行说明,已知向量AB和旋转角R,求向量AB以A点为旋转点,顺时针旋转R后的向量A'B'。
//反射角(弧度值)
var refractAngle = R;
//我这里假设所有向量都在X-Z平面,旋转轴就是Y轴,旋转轴的单位向量为 Vector3.up
var rotationAxis = Vector3.up;
//利用unity中Quaternion的AngleAxis来实现旋转,方法里面角度需转换为角度值,得到一个四元数
var rotationQuaternion = Quaternion.AngleAxis(-2 * refractAngle2 * 180 / Mathf.PI, rotationAxis);
//求解旋转后的向量
var A'B' = rotationQuaternion * AB;
4、总结
1、利用二分法求解直线与直线相交点、直线与圆弧的相交点非常方便,但是过程中要注意二分法的取值范围及指针移动的条件判断正确,否则就无法得到正确的交点。
2、已知夹角,利用向量的点乘加上二分法可以方便求解直线与圆的交点,同样需要注意的是圆的方程对于一个任何的x,y,z值都有对应的两个交点,需要根据条件判断你所求的点。
3、向量的旋转对于求解圆的折射和反射时非常方便,需要注意向量的正负与旋转角度的正负值,顺时针为负,逆时针选择则取正值。
4、unity三维坐标系中,常用的坐标计算相关的类就是vector3以及四元数Quaternion,掌握这两个及相关的类的使用比较重要。