本案例中移动模型所采用的方案为:移动摄像机; 那么要限制模型的移动范围(该教程中,只允许模型在可视范围内移动,防止模型被移动丢失),可以考虑采用限制摄像机的范围。
来回忆以下知识点试试~
- 向量的相加
- 向量的相减
- 向量的叉乘、点乘
- 向量的投影
- 平面公式
- 射线公式
参考资料 :http://www.cnblogs.com/graphics/archive/2009/10/17/1585281.html
在同一平面的线段的交点
看图说话~
首先明白,空间的点也可以看做一个向量! 什么叫向量(矢量)呢?(有大小、方向的量)
平面可以由法向量和平面内的一点来确定,因为过一点,有且只有一个平面与已知直线垂直。
上图为射线与平面的交点
有了射线和平面的参数方程,那么求二者的交点相当于解下面的方程组
注意这里两个方程中的p0是不同的,为区别彼此,将平面方程中的p0改为p1,并将射线方程代入平面方程,整理得到
若t >= 0, 则射线与平面相交,且交点为p0 + tu,若t < 0,则不相交。(注意这里,n不可约去,因为做的是点积,而不是普通乘法)
** 说明: n是法向量的单位向量; u 是射线的单位向量;p1 为平面上的一点,p0为交点**
其中的重点知识来了: 求在同一平面的两线段的交点~~
分为两步骤:
判断两线段是否相交
求交点
** 判断是否相交**,用到跨立实验-- 传送门:http://www.cnblogs.com/dwdxdy/p/3230485.html
如果两线段相交,则两线段必然相互跨立对方。
若P1P2跨立Q1Q2,则矢量(P1-Q1)和(P2-Q1)位于矢量(Q2-Q1)的两侧,即( P1 - Q1 ) × ( Q2 - Q1 ) * ( P2 - Q1 ) × ( Q2 - Q1 ) < 0。
若Q1Q2跨立P1P2,则矢量(Q1-P1)和(Q2-P1)位于矢量(P2-P1)的两侧,即( Q1 - P1 ) × ( P2 - P1 ) * ( Q2 - P1 ) × ( P2 - P1 ) < 0。
排斥实验和跨立实验的示例如下图所示
求交点
空间线段的交点算法可以参照二维线段的交点算法:
绿色线段的两个点分别投影到蓝色的向量上,则得到两个投影点(x5, y5)和(x6, y6).交点(x, y)距离两点的比例则和d1, d2的比例相同那么交点就可以表示成两个点坐标的线性组合:
来点回忆--点到线段的投影:
实战
需求: 限制模型在一定范围内移动,即图2中彩色范围内。
分析: 由于程序采用摄像机移动的方法来实现模型的移动, 那么限制模型的移动范围就是限制摄像机的移动范围。
解题思路:
- 创建一垂直于摄像机正方向且过模型中心点的一个面,如图2彩色方框;
- 将摄像机 p 投影于平面内得到点 p0,p0连接模型(模型中心)t 得到线段 p0t 判断p0t是否与彩色线框各条边框相交,如果有交点,则表示模型已出限制范围。
解读:
P表示摄像机的位置,平面 abcd 表示上面我所创建的面,P0表示摄像机在面上的投影,t 表示模型中心点,Q表示 P0t 与线段 bd的交点;s表示超出距离;P`P 表示摄像机应该回退的方向。
代码解读
限定摄像机的移动范围,返回摄像机的边缘位置
///
<summary>
///限定摄像机的移动范围
///</summary>
///<param name="originDesPosition"></param>
///<param name="offset"></param>
private Vector3 RetrictMove() {
// 1.求摄像机在平面上的投影点 p
Plane plane = CreatePlaneThatCrossModelCenterAndPerpendicularToCameraForward();
Vector3 projectDot = ProjectDotOfCameraToPlane(plane); // p
Vector3[] corner = caculateCameraBounds(projectDot,Math.Abs(plane.GetDistanceToPoint(transform.position)));// 计算摄像机的可移动范围
// 2.求投影点与模型中心所组成的线段与各个视口边缘的交点
Vector3 crossDot = Vector3.zero;
Vector3 modelCenter = Vector3.zero;// 模型中心 m
if (AvatarLoader.Instance.thisSceneStat.modelStatusMgr.IsSelected())
{
modelCenter = AvatarLoader.Instance.thisSceneStat.modelStatusMgr.GetSelectedCenter(); // 获取被选中模型的中心 (由于篇幅问题,此教程未讲解此算法)
}
else
{
modelCenter = AvatarShowCtrl.Instance.GetCurrentModleCenter(); // 获取所有所有模型的平均中心(由于篇幅问题,此教程未讲解此算法)
}
crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[0], corner[1]); // 顶 线段与 pm 线段的交点
if (crossDot == Vector3.zero)
{ // 表示线段与ab线段未相交
crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[1], corner[3]); // 右 线段与 pm 线段的交点
}
if (crossDot == Vector3.zero)// 表示线段与bc线段未相交
{
crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[2], corner[3]);// 底 线段与 pm 线段的交点
}
if (crossDot == Vector3.zero)// 表示线段与dc线段未相交
{
crossDot = IntersectionDotOfTwoSegmentInSamePlane(projectDot, modelCenter, corner[0], corner[2]);// 左 线段与 pm 线段的交点
}
if (crossDot != Vector3.zero && crossDot != projectDot)
{// 表示得到pm线段与视口边缘的交点
//Debug.DrawLine(transform.position, crossDot, Color.red);
// 让摄像机反向移动 获取反方移动距离长度(交点与模型中心的距离)
float distance = Vector3.Distance(crossDot, modelCenter);
if (distance != 0)
{
Vector3 cameralMoveDirection = (modelCenter - crossDot).normalized; // 摄像机移动的方向
Vector3 cameraDest = transform.position + distance * cameralMoveDirection; // 获取摄像机移动后的位置 射线公式 p = p0+tu p0为起点t可看做长度u可看做单位向量
return cameraDest;
}
}
return Vector3.zero;
}
创建一垂直于摄像机正方向向量、并且过模型中心点的面
/// <summary>
/// 创建一垂直于摄像机正方向向量、并且过模型中心点的面
/// </summary>
/// <returns></returns>
private Plane CreatePlaneThatCrossModelCenterAndPerpendicularToCameraForward() {
Vector3 normal = CameraCtrl.Instance.transform.forward; // 摄像机的正反向
normal = normal.normalized;
Vector3 center = AvatarShowCtrl.Instance.GetCurrentModleCenter();
Plane plane = new Plane(-normal, center); // 以摄像机的正方向为法向量、并且过模型中心点做一平面
return plane;
}
求摄像机在对应平面的投影点
/// <summary>
/// 求摄像机在对应平面的投影点
/// </summary>
/// <param name="plane"></param>
/// <returns></returns>
private Vector3 ProjectDotOfCameraToPlane(Plane plane) {
float distance = plane.GetDistanceToPoint(transform.position);
Vector3 projectDot = Math.Abs(distance) * transform.forward + transform.position;
return projectDot;
}
计算摄像机的移动范围
/// <summary>
/// 计算摄像机的移动范围
/// </summary>
/// <param name="projectDotOfCamera">摄像机在平面(过模型中心与摄像机正方向垂直的平面)的投影点</param>
/// <returns></returns>
private Vector3[] caculateCameraBounds(Vector3 projectDotOfCamera,float distance) {
Vector3[] corners = new Vector3[4];
float aspect = Camera.main.aspect; // 长宽比
float WindowHeight = WindowFactorK * distance + WindowFactorB; // 线性公式
float height = WindowHeight;
float width = height * aspect;
// 利用摄像方程
// Top Left dot
corners[0] = (projectDotOfCamera + width * transform.right) + height * transform.up;
// Top Right dot
corners[1] = (projectDotOfCamera - width * transform.right) + height * transform.up;
// Bottom Left dot
corners[2] = (projectDotOfCamera + width * transform.right) - height * transform.up;
// Bottom Right dot
corners[3] = (projectDotOfCamera - width * transform.right) - height * transform.up;
return corners;
}
计算在同一平面相交两条线段的交点
/// <summary>
/// 计算在同一平面相交两条线段的交点
/// </summary>
/// <param name="a">线段A的起点</param>
/// <param name="b">线段A的终点</param>
/// <param name="c">线段B的起点</param>
/// <param name="d">线段B的终点</param>
/// <returns></returns>
private Vector3 IntersectionDotOfTwoSegmentInSamePlane(Vector3 a, Vector3 b,Vector3 c, Vector3 d) {
// 先判断两射线是否相交
if(!IsCrossedToTwoSegmentInSamePlane(a,b,c,d)){
return Vector3.zero;
}
// 分别求点c、点d在向量ab(A)上的投影点,由此可以分别求得点c、点d距离线段ab的距离s1、s2
Vector3 projectDotC = ProjectDotOfVector(a,c,b); // c 在ab上的投影点
Vector3 projectDotD = ProjectDotOfVector(a,d,b);// d 在ab上的投影点
float s1 = Vector3.Distance(projectDotC,c); // 点c 在线段的距离 s1
float s2 = Vector3.Distance(projectDotD,d); // 点d 在线段的距离 s2
// 设交点为p(x0,y0,z0)|cp|/|pd| = s1/s2
Vector3 p = s1 / (s1 + s2) * projectDotD + s2 / (s1 + s2) * projectDotC;
return p;
}
潜水简书很长时间了,一直没有分享技术、或是值得分享的东西,感觉无法何处开始。直到看了《自创四维》中的一句话 “人生永远都是测试版”;我才幡然醒悟,那我也试着写写自己所做的beta吧。
内容如有不足之处,请指正