来复刻下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]