Unity 限制摄像机在一定区域内移动

本案例中移动模型所采用的方案为:移动摄像机; 那么要限制模型的移动范围(该教程中,只允许模型在可视范围内移动,防止模型被移动丢失),可以考虑采用限制摄像机的范围。

来回忆以下知识点试试~

  • 向量的相加

看图说话~

首先明白,空间的点也可以看做一个向量! 什么叫向量(矢量)呢?(有大小、方向的量)

图1 向量的标识(点B可以看做 向量b,向量b=点o-点B ;其中点o是原点)
图2 向量的相加
图3 向量的相减
图4 向量的叉乘
图5 向量的点乘
图6 射线方程 p=p0+tu; 其中p0是射线的起点, u是射线的方向向量(单位向量),t >= 0(可看做p0到起点的距离),根据t的取值不同,可得射线上不同的点,所有这些点便构成了整个射线。
图7 平面方程

平面可以由法向量和平面内的一点来确定,因为过一点,有且只有一个平面与已知直线垂直。


图8 向量的点积 (n*(p-p0)= 0)

上图为射线与平面的交点
有了射线和平面的参数方程,那么求二者的交点相当于解下面的方程组


图9 射线与平面的交点

注意这里两个方程中的p0是不同的,为区别彼此,将平面方程中的p0改为p1,并将射线方程代入平面方程,整理得到

t 可以粗略的看做倍数

若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中彩色范围内。

分析: 由于程序采用摄像机移动的方法来实现模型的移动, 那么限制模型的移动范围就是限制摄像机的移动范围。

解题思路:

  1. 创建一垂直于摄像机正方向且过模型中心点的一个面,如图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吧。

内容如有不足之处,请指正

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

推荐阅读更多精彩内容

  • 111. [动画系统]如何将其他类型的动画转换成关键帧动画? 动画->点缓存->关键帧 112. [动画]Unit...
    胤醚貔貅阅读 12,870评论 3 90
  • OpenGL本身没有摄像机的概念,但我们可以通过把场景中的所有物体往相反方向移动的方式来模拟出摄像机,这样感觉就像...
    IceMJ阅读 2,463评论 0 7
  • 一前言 特征值 奇异值 二奇异值计算 三PCA 1)数据的向量表示及降维问题 2)向量的表示及基变换 3)基向量 ...
    Arya鑫阅读 10,443评论 2 43
  • 想想你自己需要什么,把所学的转化成自己的东西,这样的话就很难忘掉和不理解
    绪语者阅读 166评论 0 0
  • 古诗二首,喜欢哪一首? 入秋天渐凉,小假人变懒。喝杯酒壮胆,明天去上班。 小假过得懒洋洋,使不出劲骨头软。喝杯老酒...
    徐克惜愚兄弟阅读 293评论 0 1