Geometric vectors: RuntimeTransformMovement

      I have been using the runtime transform gizmos plug-in before. Today, I said that there is a problem with the operation, and I need to change it. The effect is as follows:
insert image description here
      Can you see the problem? The three-dimensional and two-dimensional movement operations of the plug-in do not match, resulting in misalignment of the mouse and the axis (we didn’t care about this problem before, but now we say we need to change it) The RTG plug-in is really good, it supports
      TRS operation and rollback function, I really don’t want to change the plug-in. Early in the morning, Baidu looked for different versions of RTG and found that it was the same.
      Fortunately, I only need the world coordinates to control the mobile operation for the time being, so I can only write one myself.
      First make a moving axis, as follows:
insert image description here
      Blender made it, it is necessary to learn some simple modeling operations, and then assemble it into an xyz moving axis, as follows:
insert image description here
      Of course, the operation axis drawn by GL.Draw used in the RTG plug-in is as follows:

void DrawLines(List<Vector3> lines, Color color)
		{
    
    
			if(lines.Count == 0) return;

			GL.Begin(GL.LINES);
			GL.Color(color);

			for(int i = 0; i < lines.Count; i += 2)
			{
    
    
				GL.Vertex(lines[i]);
				GL.Vertex(lines[i + 1]);
			}

			GL.End();
		}

		void DrawTriangles(List<Vector3> lines, Color color)
		{
    
    
			if(lines.Count == 0) return;

			GL.Begin(GL.TRIANGLES);
			GL.Color(color);

			for(int i = 0; i < lines.Count; i += 3)
			{
    
    
				GL.Vertex(lines[i]);
				GL.Vertex(lines[i + 1]);
				GL.Vertex(lines[i + 2]);
			}

			GL.End();
		}

		void DrawQuads(List<Vector3> lines, Color color)
		{
    
    
			if(lines.Count == 0) return;

			GL.Begin(GL.QUADS);
			GL.Color(color);

			for(int i = 0; i < lines.Count; i += 4)
			{
    
    
				GL.Vertex(lines[i]);
				GL.Vertex(lines[i + 1]);
				GL.Vertex(lines[i + 2]);
				GL.Vertex(lines[i + 3]);
			}

			GL.End();
		}

		void DrawFilledCircle(List<Vector3> lines, Color color)
		{
    
    
			if(lines.Count == 0) return;

			Vector3 center = Vector3.zero;
			for(int i = 0; i < lines.Count; i++)
			{
    
    
				center += lines[i];
			}
			center /= lines.Count;

			GL.Begin(GL.TRIANGLES);
			GL.Color(color);

			for(int i = 0; i + 1 < lines.Count; i++)
			{
    
    
				GL.Vertex(lines[i]);
				GL.Vertex(lines[i + 1]);
				GL.Vertex(center);
			}

			GL.End();
		}

      Here we use GL to draw when we have time, or make a model if we don’t have time.
      Next, analyze the moving operation of 2D and 3D matching, taking the X-axis as an example, as follows:
insert image description here
      In fact, it is also very simple. We calculate the plane F where the X-axis is located, and then perform 2D and 3D coordinate mapping through the camera mouse ray.
      Next, write the code implementation, as follows:
      We need to write three classes in total:
      1. RTMXYZAxis (responsible for axis generation, movement, release, etc.)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UnityPlugin.RTM
{
    
    
    public enum EAxisType
    {
    
    
        Null,
        X,
        Y,
        Z
    }

    public class RTMXYZAxis : MonoBehaviour
    {
    
    
        [Header("X轴")]
        public Transform xAxis;
        [Header("Y轴")]
        public Transform yAxis;
        [Header("Z轴")]
        public Transform zAxis;
        [Header("选中轴类型")]
        public EAxisType axisType;          //选中轴类型

        private Material xMat;
        private Material yMat;
        private Material zMat;

        private RTMGo attachRtmGo;          //附着的物体
        private bool isAttaching = false;   //是否附着

        public bool IsAttaching {
    
     get {
    
     return isAttaching; } }

        private Vector3 panelCenter;        //选中平面中心
        private Vector3 panelNormal;        //选中平面单位法向量
        private Vector3 rayInitPoint;       //选中平面坐标点

        private void Awake()
        {
    
    
            xMat = xAxis.GetComponent<MeshRenderer>().sharedMaterial;
            yMat = yAxis.GetComponent<MeshRenderer>().sharedMaterial;
            zMat = zAxis.GetComponent<MeshRenderer>().sharedMaterial;
        }

        void Start()
        {
    
    

        }

        /// <summary>
        /// 生成
        /// </summary>
        public void Create(RTMGo go)
        {
    
    
            attachRtmGo = go;
            transform.position = attachRtmGo.GetWorldPos();
            xAxis.gameObject.SetActive(true);
            yAxis.gameObject.SetActive(true);
            zAxis.gameObject.SetActive(true);
            isAttaching = true;
#if UNITY_EDITOR
            Debug.LogWarningFormat("RTMXYZAxis Create go = {0}", go.GetName());
#endif
        }

        /// <summary>
        /// 选择轴
        /// </summary>
        /// <param name="ray"></param>
        public bool RaycastAxis(Ray ray)
        {
    
    
            bool israycast = true;
            RaycastHit hitinfo;
            if (Physics.Raycast(ray, out hitinfo))
            {
    
    
                if (hitinfo.transform == xAxis)
                {
    
    
                    axisType = EAxisType.X;
                }
                else if (hitinfo.transform == yAxis)
                {
    
    
                    axisType = EAxisType.Y;
                }
                else if (hitinfo.transform == zAxis)
                {
    
    
                    axisType = EAxisType.Z;
                }
                else
                {
    
    
                    axisType = EAxisType.Null;
                    israycast = false;
                }
#if UNITY_EDITOR
                Debug.LogWarningFormat("RTMXYZAxis RaycastAxis");
#endif
            }
            else
            {
    
    
                axisType = EAxisType.Null;
                israycast = false;
            }
            ChangeMat(axisType);
            GetPanelParams(axisType, ray);
            return israycast;
        }

        /// <summary>
        /// 改变选中轴颜色
        /// </summary>
        /// <param name="atype"></param>
        private void ChangeMat(EAxisType atype)
        {
    
    
            switch (atype)
            {
    
    
                case EAxisType.X:
                    {
    
    
                        xMat.SetColor("_Color", Color.yellow);
                        yMat.SetColor("_Color", Color.green);
                        zMat.SetColor("_Color", Color.blue);
                    }
                    break;
                case EAxisType.Y:
                    {
    
    
                        xMat.SetColor("_Color", Color.red);
                        yMat.SetColor("_Color", Color.yellow);
                        zMat.SetColor("_Color", Color.blue);
                    }
                    break;
                case EAxisType.Z:
                    {
    
    
                        xMat.SetColor("_Color", Color.red);
                        yMat.SetColor("_Color", Color.green);
                        zMat.SetColor("_Color", Color.yellow);
                    }
                    break;
                case EAxisType.Null:
                    {
    
    
                        xMat.SetColor("_Color", Color.red);
                        yMat.SetColor("_Color", Color.green);
                        zMat.SetColor("_Color", Color.blue);
                    }
                    break;
            }
        }
        /// <summary>
        /// 获取选中平面参数
        /// 中心点和单位法向量
        /// 计算射线与平面初次交点
        /// </summary>
        /// <param name="atype"></param>
        private void GetPanelParams(EAxisType atype, Ray ray)
        {
    
    
            switch (atype)
            {
    
    
                case EAxisType.X:
                case EAxisType.Y:
                    {
    
    
                        panelCenter = transform.position;
                        panelNormal = Vector3.back;
                        rayInitPoint = RayCrossPanel(panelCenter, panelNormal, ray);
                    }
                    break;
                case EAxisType.Z:
                    {
    
    
                        panelCenter = transform.position;
                        panelNormal = Vector3.right;
                        rayInitPoint = RayCrossPanel(panelCenter, panelNormal, ray);
                    }
                    break;
                case EAxisType.Null:
                    {
    
    
                        panelCenter = Vector3.zero;
                        panelNormal = Vector3.zero;
                        rayInitPoint = Vector3.zero;
                    }
                    break;
            }
        }

        /// <summary>
        /// 移动轴
        /// </summary>
        /// <param name="ray"></param>
        public void MovementAxis(Ray ray)
        {
    
    
            if (axisType != EAxisType.Null)
            {
    
    
                Vector3 mpos = RayCrossPanel(panelCenter, panelNormal, ray);
                Vector3 bias = mpos - rayInitPoint;
                switch (axisType)
                {
    
    
                    case EAxisType.X:
                        transform.position = panelCenter + new Vector3(bias.x, 0, 0);
                        break;
                    case EAxisType.Y:
                        transform.position = panelCenter + new Vector3(0, bias.y, 0);
                        break;
                    case EAxisType.Z:
                        transform.position = panelCenter + new Vector3(0, 0, bias.z);
                        break;
                }
                attachRtmGo.SetWorldPos(transform.position);
            }
        }

        /// <summary>
        /// 求射线与平面交点
        /// </summary>
        /// <param name="center"></param>
        /// <param name="norm"></param>
        /// <param name="ray"></param>
        /// <returns></returns>
        private Vector3 RayCrossPanel(Vector3 center, Vector3 norm, Ray ray)
        {
    
    
            float a = center.x, b = center.y, c = center.z;
            float u = norm.x, v = norm.y, w = norm.z;

            Vector3 start = ray.origin;
            Vector3 dir = ray.direction;

            float sx = start.x, sy = start.y, sz = start.z;
            float dx = dir.x, dy = dir.y, dz = dir.z;

            float n = ((u * a + v * b + w * c) - (u * sx + v * sy + w * sz)) / (u * dx + v * dy + w * dz);
            Vector3 ret = start + n * dir;
            return ret;
        }

        /// <summary>
        /// 放下轴
        /// </summary>
        public void DisposeAxis()
        {
    
    
            axisType = EAxisType.Null;
            panelCenter = Vector3.zero;
            panelNormal = Vector3.zero;
            rayInitPoint = Vector3.zero;
            ChangeMat(axisType);
#if UNITY_EDITOR
            Debug.LogWarningFormat("RTMXYZAxis DisposeAxis");
#endif
        }

        /// <summary>
        /// 释放
        /// </summary>
        public void Release()
        {
    
    
            isAttaching = false;
            attachRtmGo = null;
            DisposeAxis();
            xAxis.gameObject.SetActive(false);
            yAxis.gameObject.SetActive(false);
            zAxis.gameObject.SetActive(false);
#if UNITY_EDITOR
            Debug.LogWarningFormat("RTMXYZAxis Release");
#endif
        }
    }
}

      2.RTMGo (responsible for filtering operable objects)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UnityPlugin.RTM
{
    
    
    public class RTMGo : MonoBehaviour
    {
    
    

        void Start()
        {
    
    
            RTMController.GetInstance().AddRtmGo(this);
        }

        public Vector3 GetWorldPos()
        {
    
    
            return transform.position;
        }

        public void SetWorldPos(Vector3 wpos)
        {
    
    
            transform.position = wpos;
        }

        public string GetName()
        {
    
    
            return transform.name;
        }
    }
}

      3.RTMController (RTM controller, manages operable objects, handles operation logic, etc.)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace UnityPlugin.RTM
{
    
    
    public enum EMouseHandleType
    {
    
    
        Left,
        Right,
    }

    public class RTMController : MonoSingleton<RTMController>
    {
    
    
        [Header("操作轴")]
        public RTMXYZAxis rtmAxis;
        [Header("鼠标操作类型")]
        public EMouseHandleType mouseHandleType;

        private List<RTMGo> rtmGoList = new List<RTMGo>();

        private RTMGo selectRtmGo;                  //选中rtmGo

        private Camera mainCamera;

        void Start()
        {
    
    
            mainCamera = Camera.main;
        }

        public void AddRtmGo(RTMGo rtmgo)
        {
    
    
            if (!rtmGoList.Contains(rtmgo))
            {
    
    
                rtmGoList.Add(rtmgo);
            }
        }

        public void RemoveRtmGo(RTMGo rtmgo)
        {
    
    
            if (rtmGoList.Contains(rtmgo))
            {
    
    
                rtmGoList.Remove(rtmgo);
            }
        }

        /// <summary>
        /// 操作逻辑
        /// 首先确认选中RTMGo,生成RTMAxis
        /// 再确认选中Axis,操作移动
        /// 空选则释放
        /// </summary>
        void Update()
        {
    
    
            int mid = (mouseHandleType == EMouseHandleType.Left ? 0 : 1);
            if (Input.GetMouseButtonDown(mid))
            {
    
    
                Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                if (Physics.Raycast(ray, out hit))
                {
    
    
                    RTMGo rtmgo = hit.transform.GetComponent<RTMGo>();
                    if (rtmgo != null)
                    {
    
    
                        if (selectRtmGo == rtmgo)
                        {
    
    
                            return;
                        }
                        else
                        {
    
    
                            selectRtmGo = rtmgo;
                            rtmAxis.Create(selectRtmGo);
                            return;
                        }
                    }
                    if (rtmAxis.RaycastAxis(ray))
                    {
    
    
                        return;
                    }
                    rtmAxis.Release();
                    selectRtmGo = null;
                }
                else
                {
    
    
                    rtmAxis.Release();
                    selectRtmGo = null;
                }
            }
            if (Input.GetMouseButton(mid))
            {
    
    
                if (rtmAxis.IsAttaching)
                {
    
    
                    Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
                    rtmAxis.MovementAxis(ray);
                }
            }
            if (Input.GetMouseButtonUp(mid))
            {
    
    
                if (rtmAxis.IsAttaching)
                {
    
    
                    rtmAxis.DisposeAxis();
                }
            }
        }
    }
}

      The effect is as follows:
insert image description here
      the effect is good, the 2D and 3D mapping matches, and the mobile operation is more accurate.
      Of course, the core function of RTM is the intersection of rays and planes, plus some operation logic control processing, just understand the principle.
      Of course, there is still a small problem that may need to be improved, that is, because our XYZ axis is a model, so when we move far away, the XYZ axis becomes very small, and it is a bit difficult to select the axis. We need to add a corresponding scaling function according to the distance from the XYZ axis to the camera, as follows:

        private void MoveDistanceScale()
        {
    
    
            float distance = Vector3.Distance(mainCamera.position, transform.position);
            transform.localScale = Vector3.one * distance * distanceScale;
        }

      Adjust the parameters by yourself:
insert image description here
      the effect is as follows:
insert image description here
      Of course, there is another problem that if the volume boundary of the RTMGo collider exceeds the three axis colliders of the RTM axis, then the RTMGo cannot operate the axis after selecting this RTMGo, as follows:
insert image description here

      There are two methods for this modification:
      1. Select RTMGo to disable its Collider, which may affect other collision business logic of RTMGo.
      2. According to the raycastlayer layered ray judgment, it is more suitable to
      modify the code as follows:

if (Input.GetMouseButtonDown(mid))
            {
    
    
                Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
                RaycastHit hit;
                int rtmlayer = LayerMask.NameToLayer("RTM");
                if (Physics.Raycast(ray, out hit, mainCamera.farClipPlane, 1 << rtmlayer))
                {
    
    
                    if (hit.transform != null)
                    {
    
    
                        if (rtmAxis.RaycastAxis(ray))
                        {
    
    
                            return;
                        }
                    }
                }
                if (Physics.Raycast(ray, out hit))
                {
    
    
                    RTMGo rtmgo = hit.transform.GetComponent<RTMGo>();
                    if (rtmgo != null)
                    {
    
    
                        if (selectRtmGo == rtmgo)
                        {
    
    
                            return;
                        }
                        else
                        {
    
    
                            selectRtmGo = rtmgo;
                            rtmAxis.Create(selectRtmGo);
                            return;
                        }
                    }
                    if (rtmAxis.RaycastAxis(ray))
                    {
    
    
                        return;
                    }
                    rtmAxis.Release();
                    selectRtmGo = null;
                }
                else
                {
    
    
                    rtmAxis.Release();
                    selectRtmGo = null;
                }
            }

      Press the controller to determine whether to select the "RTM" Layer axis first, and add the "RTM" Layer filter to the RaycastAxis judgment, as follows: By the way, as
insert image description here
      for the most foreground rendering of the XYZ axis, you only need to close ZTest ZWrite in the shader, as follows:

        ZTest off
        ZWrite off

      Function completed, holiday.

Guess you like

Origin blog.csdn.net/yinhun2012/article/details/122121569