redhat9i 发表于 2022-1-16 15:28

来复刻下Blender视图操作逻辑

前言:看过我之前文章的话,你就会知道我是一个Blender粉。我喜欢它的方方面面,而Blender的视图操作就是其中之一——纯快捷键操作,界面清爽,令人心情愉悦。而最近在玩VAM的过程中,我就萌生了一个想要在其中加入Blender视图操作逻辑的想法。通过昨天的一顿操作,基本效果总算出来了。



Blender中的



VAM中

废话不多说,先来将下一些稍微关键点的地方。

[*]旋转与缩放
为方便理解,先放上代码段:
// 缩放逻辑
if (currentState == OperateState.SCALE)
{
    // 缩放 = 当前距离 / 初始距离
    // operationBeginDistance:进入缩放前记录的屏幕坐标距离,currentOperationDistance :当前屏幕坐标距离
    float currentOperationDistance = HitObjectScreenPosDistanceToMousePosV2;
    float scaleRadio = currentOperationDistance / operationBeginDistance;
    hitObject.localScale = targetObjectBeginTransInfo.localScale * scaleRadio;
}
...
// 旋转逻辑
if (currentState == OperateState.ROTATE)
{
    // operationBeginAngle:进入旋转前记录的屏幕空间旋转角,currentOperationAngle :当前屏幕空间旋转角
    // 注意Quaternion相乘的顺序:从右往左
    float currentOperationAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);
    Quaternion quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetCamera.transform.forward);
    hitObject.rotation = quater * targetObjectBeginTransInfo.rotation;
}

// 求得向量夹角,并以0-360的形式给出,方便使用
private float GetVectorAngleRange360(Vector3 from, Vector3 to)
{
    // 判断叉乘的向量是否指向屏幕外判断旋转角度是否大于180
    Vector3 crossVec = Vector3.Cross(from, to);
    float dotVal = Vector3.Dot(crossVec, Vector3.back);
    float angle = Vector3.Angle(from, to);

    if (dotVal < 0)
    {
      angle = 360 - angle;
    }
    return angle;
}
从上边的演示不难看出,旋转和缩放的输入都来源于鼠标指针在屏幕上的滑动。
缩放比较简单:其实就是在进入缩放状态前,先记录一下鼠标屏幕坐标与目标物体屏幕坐标的距离。进入缩放状态后,实时获取鼠标屏幕坐标与目标物体屏幕坐标的距离。两者做一个比值就是当前物体的相对缩放了。
旋转稍微麻烦点:其它逻辑和缩放相似,也是进入旋转前记录一次。进入后实时计算当前角度。但其中涉及到一个转换的过程:Vector3.Angle方法算出的角度是0-180的。为了后面方便使用,需要将它转换为0-360的范围。这里就用到了点乘和叉乘:叉乘分出大于180和小于180的部分。点乘来进行具体分区的判断(叉乘后的向量是否指向屏幕外);

[*]移动
简单拖拽部分就不讲了。这里主要提下沿轴移动的部分,先放上代码段:
// 全局坐标系的沿轴移动
if (axis == Axis.AXIS_X)
{
    // 得到鼠标位置发出的射线与平面(由平面上的一个点和平面的法线确定)的交点
    intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.back, ray);
    // 直接取交点的x分量
    hitObject.position = new Vector3(intersectPoint.x, targetObjectBeginTransInfo.position.y, targetObjectBeginTransInfo.position.z);
}
...
//局部坐标系的沿轴移动
if (axis == Axis.AXIS_X)
{
    // 同上
    intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.forward, ray);
    Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
    // 这里取交点在局部坐标对应轴上的投影向量
    offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.right);
}
// 从开始位置进行偏移
hitObject.position = targetObjectBeginTransInfo.position + offsetVec;

// 射线与平面交点
private Vector3 GetIntersectPointWithRay(Vector3 planePoint, Vector3 n, Ray ray)
{
    float d = Vector3.Dot(planePoint, n);
    Vector3 p0 = ray.origin;
    Vector3 d0 = ray.direction.normalized;
    float t0 = (d - Vector3.Dot(p0, n)) / Vector3.Dot(d0, n);
    return p0 + d0 * t0;
}

// src在target方向上的投影
public static Vector3 VecProject(Vector3 src, Vector3 target)
{
    return Vector3.Dot(src, target) * target.normalized;
}
从上边的代码可以看到。全部坐标系的沿轴移动比较简单,求得交点后直接取对应轴的分量就行了。而局部坐标系的稍微麻烦点,需要再做一次向量投影操作。

[*]画线
这里直接使用了Unity自带的GL类在OnPostRender()中进行画线操作,简单有效,哈哈。在Unity中直接将脚本挂上相机就能使用了。在VAM中则要额外在窗口相机上挂一个钩子脚本来触发回调,才好使用。下面是部分代码片段:
Vector2 beginPosition = targetCamera.WorldToScreenPoint(targetObjectBeginTransInfo.position);

if (currentState == OperateState.MOVE || currentState == OperateState.MOVE_X || currentState == OperateState.MOVE_Y || currentState == OperateState.MOVE_Z)
{
    Vector2 currentPosition = HitObjectScreenPosition;
    lines2D.Add(new GLLine2D() { from = MapScreenPosTo01(beginPosition), to = MapScreenPosTo01(currentPosition) });
    glDraw.DrawLine2D(lines2D, Color.white);
}

// 将屏幕坐标映射到0-1
private Vector2 MapScreenPosTo01(Vector3 screenPosition)
{
    return new Vector2(screenPosition.x / (float)Screen.currentResolution.width, screenPosition.y / (float)Screen.currentResolution.height);
}

// 具体画线
public void DrawLine2D(List<GLLine2D> lines, Color color)
{
    GL.PushMatrix();

    LineMaterial.SetPass(0);
    GL.LoadOrtho();
    GL.Color(color);

    // line
    GL.Begin(GL.LINES);

    for (int i = 0; i < lines.Count; i++)
    {
      GLLine2D line = lines;
      GL.Vertex3(line.from.x, line.from.y, 0);
      GL.Vertex3(line.to.x, line.to.y, 0);
    }

    GL.End();
    GL.PopMatrix();
}
详细代码请移步我的仓库:
页: [1]
查看完整版本: 来复刻下Blender视图操作逻辑