最近在Unity上要写一个东东,功能差不多就是在Unity编辑器上的旋转,移动这些,在手机上也能比较容易操作最好,原来用Axiom3D写过一个类似的,有许多位置并不好用,刚好在研究UE4的源码,在模型操作上,很多位置都解决了,其实大家可以对比下,在UE4与Unity中,UE4的如移动和旋转都要正确和好用。

  如下是根据UE4中简单移植过来的效果图,差不多已经够用,UE4相关源码主要在EditorViewportClient与UnrealWidget。

  移植UE4的模型操作到Unity中

  介绍一下这个组件主要功能。

  1. 模型本地空间与世界空间二种模式。

  2. 根据情况动态生成操作模型,如在移动模型时,选择的轴变色,旋转时,视角与模型的方向产生不同模型。

  3. 移动根据鼠标平面映射到对应移动平面,点保存上轴上距离不变(作为对比,可以看到Unity上长距离移动,鼠标位置在轴上的位置位移会不断拉大)。

  4. 不管模型与摄像机的距离,旋转与移动操作都是合适的大小。

  5. 旋转方向的确定,简单说,就是在旋转时,如果用鼠标移动来确定旋转的方向,这个问题看似简单,我以前就没搞出来。

  6. 在移动本台,我们需要更方便的操作,所以在移动平台会有些操作,如更容易选中,生成的模型会更大等。

  最后,有一些,如箭头模型,选择旋转与移动轴的算法以前考虑过,就没用UE4本身的,如果感觉有问题,自己去移植UE4的。其中旋转因为移动平台易用性,就设定了一个值,如在我这设定的是10,就是每次只旋转10度。

  简单分析一下,UE4里相关思路。

  其中移动的算法思路非常赞,比如我们要移动X轴,那么我们对应在法线为Y或是法线为Z轴上的平面都可以,通过摄像机的方向与这二个平面的夹角,在这如果摄像机的方向与法线Y平面的角大于与法线Z平面的角,那么我们选择法线Y平面的面做映射面,而Z向量作偏向轴方向,什么意思了,我们鼠标是在二维面上移动的,但是对应的只在X轴上移动,那么我们在法线Y平面上的映射向量需要去掉在Z向量上偏向量的影响。如下是移动的主要代码,每步我加了注释,其中一些比较常用如投影,向量减向量在某向量上的投影的意义要记清,当初我也是看到这,就一下想通这个算法的思路了。  

    /// <summary>
    /// FWidget::GetAbsoluteTranslationDelta 
    /// 算法思想,如果移动X轴,选取以Y轴或是Z轴为法线并过模型上的面,鼠标移动映射在这个面上。
    /// 其中,如果选择Y轴面,要去掉Z轴上运动值,参看NormalToRemove
    /// </summary>
    /// <param name="inParams"></param>
    /// <returns></returns>
    public Vector3 GetAbsoluteTranslationDelta(AbsoluteMovementParams inParams)
    {
        //鼠标移动的位置 对应的面,请看GetAxisPlaneNormalAndMask方法
        Plane movementPlane = new Plane(inParams.PlaneNormal, inParams.Position);
        //估算鼠标点击在模型上的位置(点击射线方向)
        Vector3 eyeVector = inParams.EyePos + inParams.PixelDir * (inParams.Position - inParams.EyePos).magnitude;
        //模型的世界位置
        Vector3 requestedPositon = inParams.Position;

        //点击方向与面的夹角
        float dotPlaneNormal = Vector3.Dot(inParams.PixelDir, inParams.PlaneNormal);
        //摄像机方向与面的夹角不为90度
        if (Mathf.Abs(dotPlaneNormal) > 0.00001)
        {
            //摄像机到点击位置 与 面的交点 ,把requestedPositon映射到面上位置
            requestedPositon = LinePlaneIntersection(inParams.EyePos, eyeVector, movementPlane);
        }
        //拖动的增量(都在movementPlane上,二点相差)
        var deltaPosition = requestedPositon - inParams.Position;
        //保存最开始点击下去得到的偏移
        Vector3 offset = GetAbsoluteTranslationInitialOffset(requestedPositon, inParams.Position);
        //去掉最开始本身的偏移
        deltaPosition -= initialOffset;
        //.Log("delta:" + deltaPosition);
        //去掉deltaPosition到NormalToRemove上投影 outDrag与NormalToRemove 互相垂直,outDrag+NormalToRemove = deltaPosition
        float movementAxis = Vector3.Dot(deltaPosition, inParams.NormalToRemove);
        Vector3 outDrag = deltaPosition - inParams.NormalToRemove * movementAxis;
        //Debug.Log("outDrag:" + outDrag);
        //get the distance from the original position to the new proposed position 
        //Vector3 deltaFromStart = inParams.Position + outDrag - initialPosition;
        //模型到摄像机方向
        Vector3 eyeToNewPosition = inParams.Position + outDrag - inParams.EyePos;
        //模型到摄像机方向与摄像机方向 夹角大于90度
        float behindDot = Vector3.Dot(eyeToNewPosition, inParams.CameraDir);
        if (behindDot <= 0)
        {
            outDrag = Vector3.zero;
        }
        return outDrag;
    }
FWidget::GetAbsoluteTranslationDelta

相关文章: