[unity] model control handle (similar to the editing panel)

[unity] Simple implementation of model control handle (similar to editing panel)

Use GL to draw control handles to achieve positioning, rotation, scaling and deformation, similar to the model editing functions of the editing panel.

Project file download address

Instructions

real-time control

Core method: TransformControl.Control();

set control target

Core method: TransformControl.TargetObjct()

demo

    public TransformControl transformControl;
    public GameObject Target;    
    void Update()
    {
        if (Target)
        {
            transformControl.Control();
        }
        if (Input.GetMouseButton(1))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit))
            {
                Target = hit.collider.gameObject;
                transformControl.TargetObjct = Target.transform;
            }
        }
    }

switch mode

Core method: TransformControl.mode()

demo

 void Update()
    {
        if (Input.GetKey(KeyCode.W))
        {
            //平移
            mode = TransformMode.Translate;
            transformControl.mode = mode;
        }
        if (Input.GetKey(KeyCode.R))
        {
            //旋转
            mode = TransformMode.Rotate;
            transformControl.mode = mode;
        }
        if (Input.GetKey(KeyCode.S))
        {
            //缩放
            mode = TransformMode.Scale;
            transformControl.mode = mode;
        }
    }

drag event

Core method: TransformControl.DragEvent.AddListener(open);

demo

    void Start()
    {
        transformControl.DragEvent.AddListener(open);
    }
    void open()
    {
        Debug.Log("句柄拖拽中");
    }

source code

TransformControl main function method

using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Events;

namespace TransformControlUser {
	public class MyBaseHandle : UnityEvent{ }

	public class TransformControl : MonoBehaviour {

	    [System.Serializable]
	    class TransformData {
	        public Vector3 position;
	        public Quaternion rotation;
	        public Vector3 scale;
			Matrix4x4 matrix;
	        public TransformData(Vector3 p, Quaternion r, Vector3 s) {
	            position = p;
	            rotation = r;
	            scale = s;
				matrix = Matrix4x4.TRS(p, r, s);
	        }
	        public TransformData(Transform tr) : this(tr.position, tr.rotation, tr.localScale) {}

			public Vector3 TransformPoint (Vector3 p) {
				return matrix.MultiplyPoint(p);
			}
	    }

	    public enum TransformMode {
	        None, Translate, Rotate, Scale
	    };

	    public enum TransformDirection {
	        None, X, Y, Z
	    };

	   // protected const string SHADER = "Hidden/Internal-Colored";
		protected const string SHADER = "Battlehub/RTHandles/VertexColorClip";
		protected const float THRESHOLD = 10f;
	    protected const float HANDLER_SIZE = 0.2f;

	    protected Material material {
	        get {
	            if (_material == null)
	            {
	                var shader = Shader.Find(SHADER);
	                if (shader == null) Debug.LogErrorFormat("Shader not found : {0}", SHADER);
	                _material = new Material(shader);
	            }
	            return _material;
	        }
	    }

	    public TransformMode mode = TransformMode.Translate;
	    public bool global, useDistance;
        public float distance = 20f;
		public Transform TargetObjct;
		public MyBaseHandle DragEvent = new MyBaseHandle();

		Color[] colors = new Color[] { Color.red, Color.green, Color.blue, Color.yellow };

		Dictionary<TransformDirection, Vector3> axes = new Dictionary<TransformDirection, Vector3>() {
			{ TransformDirection.X, Vector3.right },
			{ TransformDirection.Y, Vector3.up },
			{ TransformDirection.Z, Vector3.forward }
		};

		Matrix4x4[] matrices = new Matrix4x4[] {
	        Matrix4x4.TRS(Vector3.right, Quaternion.AngleAxis(90f, Vector3.back), Vector3.one),
	        Matrix4x4.TRS(Vector3.up, Quaternion.identity, Vector3.one),
	        Matrix4x4.TRS(Vector3.forward, Quaternion.AngleAxis(90f, Vector3.right), Vector3.one)
	    };

	    Material _material;
	    Vector3 start;
	    bool dragging;
	    TransformData prev;
	    Mesh cone;
	    Mesh cube;
	    TransformDirection selected = TransformDirection.None;
	    #region Circumference
	    const int SPHERE_RESOLUTION = 32;
	    List<Vector3> circumX;
	    List<Vector3> circumY;
	    List<Vector3> circumZ;

	    #endregion

	    void Awake() {
	        cone = CreateCone(5, 0.1f, HANDLER_SIZE);
	        cube = CreateCube(HANDLER_SIZE);
	        GetCircumference(SPHERE_RESOLUTION, out circumX, out circumY, out circumZ);
	    }

		/// <summary>
		/// 实时控制
		/// </summary>
        public void Control () {
	        if (Input.GetMouseButtonDown(0)) {
	            dragging = true;
	            start = Input.mousePosition;
	            prev = new TransformData(TargetObjct);
                Pick();
	        } else if (Input.GetMouseButtonUp(0)) {
	            dragging = false;
				selected = TransformDirection.None;
	        }
            if(dragging) {
                Drag();
            }
        }

	    public bool Pick () {
	        return Pick(Input.mousePosition);
	    }

	    public bool Pick (Vector3 mouse) {
	        selected = TransformDirection.None;
	        switch(mode) {
	            case TransformMode.Translate:
	            case TransformMode.Scale:
	                return PickOrthogonal(mouse);
	            case TransformMode.Rotate:
	                return PickSphere(mouse);
	        }
	        return false;
	    }

        Matrix4x4 GetTranform()
        {
            float scale = 1f;
            if(useDistance)
            {
                var d = (Camera.main.transform.position - TargetObjct.position).magnitude;
                scale = d / distance;
            }
			return Matrix4x4.TRS(TargetObjct.position, global ? Quaternion.identity : TargetObjct.rotation, Vector3.one * scale);
        }

	    bool PickOrthogonal (Vector3 mouse) {
	        var cam = Camera.main;

			var matrix = GetTranform();
			var origin = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.zero)).xy();
	        var right = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.right)).xy() - origin;
	        var rightHead = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.right * (1f + HANDLER_SIZE))).xy() - origin;
	        var up = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.up)).xy() - origin;
	        var upHead = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.up * (1f + HANDLER_SIZE))).xy() - origin;
	        var forward = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.forward)).xy() - origin;
	        var forwardHead = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.forward * (1f + HANDLER_SIZE))).xy() - origin;
	        var v = mouse.xy() - origin;
	        var vl = v.magnitude;

			// 为每个量级添加阈值以忽略方向

			var xl = v.Orth(right).magnitude;
			if(Vector2.Dot(v, right) <= -float.Epsilon || vl > rightHead.magnitude) xl += THRESHOLD;

	        var yl = v.Orth(up).magnitude;
	        if(Vector2.Dot(v, up) <= -float.Epsilon || vl > upHead.magnitude) yl += THRESHOLD;

	        var zl = v.Orth(forward).magnitude;
			if(Vector2.Dot(v, forward) <= -float.Epsilon || vl > forwardHead.magnitude) zl += THRESHOLD;

	        if (xl < yl && xl < zl && xl < THRESHOLD) {
	            selected = TransformDirection.X;
	        } else if (yl < xl && yl < zl && yl < THRESHOLD) {
	            selected = TransformDirection.Y;
	        } else if (zl < xl && zl < yl && zl < THRESHOLD) {
	            selected = TransformDirection.Z;
	        }
	        return selected != TransformDirection.None;
	    }

	    bool PickSphere(Vector3 mouse) {
	        var cam = Camera.main;

			var matrix = GetTranform();

	        var v = mouse.xy();
			var x = circumX.Select(p => cam.WorldToScreenPoint(matrix.MultiplyPoint(p)).xy()).ToList();
	        var y = circumY.Select(p => cam.WorldToScreenPoint(matrix.MultiplyPoint(p)).xy()).ToList();
	        var z = circumZ.Select(p => cam.WorldToScreenPoint(matrix.MultiplyPoint(p)).xy()).ToList();

	        float xl, yl, zl;
	        xl = yl = zl = float.MaxValue;
	        for(int i = 0; i < SPHERE_RESOLUTION; i++) {
	            xl = Mathf.Min(xl, (v - x[i]).magnitude);
	            yl = Mathf.Min(yl, (v - y[i]).magnitude);
	            zl = Mathf.Min(zl, (v - z[i]).magnitude);
	        }

	        if (xl < yl && xl < zl && xl < THRESHOLD) {
	            selected = TransformDirection.X;
	        } else if (yl < xl && yl < zl && yl < THRESHOLD) {
	            selected = TransformDirection.Y;
	        } else if (zl < xl && zl < yl && zl < THRESHOLD) {
	            selected = TransformDirection.Z;
	        }
	        return selected != TransformDirection.None;
	    }
		/// <summary>
		/// 得到旋转句柄圆点
		/// </summary>
		/// <param name="resolution">圆段数</param>
		/// <param name="x"></param>
		/// <param name="y"></param>
		/// <param name="z"></param>
	    void GetCircumference (int resolution, out List<Vector3> x, out List<Vector3> y, out List<Vector3> z) {
	        x = new List<Vector3>();
	        y = new List<Vector3>();
	        z = new List<Vector3>();

	        var pi2 = Mathf.PI * 2f;
	        for(int i = 0; i < resolution; i++) {
	            var r = (float)i / resolution * pi2;
	            x.Add(new Vector3(0f, Mathf.Cos(r), Mathf.Sin(r)));
	            y.Add(new Vector3(Mathf.Cos(r), 0f, Mathf.Sin(r)));
	            z.Add(new Vector3(Mathf.Cos(r), Mathf.Sin(r), 0f));
	        }
	    }
		bool GetStartProj (out Vector3 proj) {
			proj = default(Vector3);

			var plane = new Plane((Camera.main.transform.position - prev.position).normalized, prev.position);
			var ray = Camera.main.ScreenPointToRay(start);
			float distance;
			if(plane.Raycast(ray, out distance)) {
				var point = ray.GetPoint(distance);
				var axis = global ? axes[selected] : prev.rotation * axes[selected];
				var dir = point - prev.position;
				proj = Vector3.Project(dir, axis.normalized);
				return true;
			}
			return false;
		}
		/// <summary>
		/// 得到相机和目标的距离
		/// </summary>
		/// <returns></returns>
		float GetStartDistance () {
			Vector3 proj;
			if(GetStartProj(out proj)) {
				return proj.magnitude;
			}
			return 0f;
		}
        void Drag() {
	        switch (mode) {
	            case TransformMode.Translate:
	                Translate();
	                break;
	            case TransformMode.Rotate:
	                Rotate();
	                break;
	            case TransformMode.Scale:
	                Scale();
	                break;
	        }
        }
		/// <summary>
		/// 执行拖拽事件
		/// </summary>
		void DragEventRun()
		{
			if (DragEvent != null)
			{
				DragEvent.Invoke();
			}
		}
		/// <summary>
		/// 移动控制
		/// </summary>
		void Translate() {
	        if (selected == TransformDirection.None) return;

			var plane = new Plane((Camera.main.transform.position - prev.position).normalized, prev.position);
			var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			float distance;
			if(plane.Raycast(ray, out distance)) {
				var point = ray.GetPoint(distance);
				var axis = global ? axes[selected] : prev.rotation * axes[selected];
				var dir = point - prev.position;
				var proj = Vector3.Project(dir, axis.normalized);
				Vector3 start;
				if(GetStartProj(out start)) {
					var offset = start.magnitude;
					var cur = proj.magnitude;
					if(Vector3.Dot(start, proj) >= 0f) {
						proj = (cur - offset) * proj.normalized;
					} else {
						proj = (cur + offset) * proj.normalized;
					}
				}
				DragEventRun();
				TargetObjct.position = prev.position + proj;
			}
		}
		/// <summary>
		/// 旋转控制
		/// </summary>
		void Rotate() {
			if (selected == TransformDirection.None) return;

			var matrix = Matrix4x4.TRS(prev.position, global ? Quaternion.identity : prev.rotation,  Vector3.one);
			var cur = Input.mousePosition.xy();
			var cam = Camera.main;
			var origin = cam.WorldToScreenPoint(matrix.MultiplyPoint(Vector3.zero)).xy();
			var axis = cam.WorldToScreenPoint(matrix.MultiplyPoint(axes[selected])).xy();
			var perp = (origin - axis).Perp().normalized;
			var dir = (cur - start.xy());
			var proj = dir.Project(perp);
			var rotateAxis = axes[selected];
			if(global) rotateAxis = Quaternion.Inverse(prev.rotation) * rotateAxis;
			TargetObjct.rotation = prev.rotation * Quaternion.AngleAxis(proj.magnitude * (Vector2.Dot(dir, perp) > 0f ? 1f : -1f), rotateAxis);
			DragEventRun();
		}
		/// <summary>
		/// 缩放控制
		/// </summary>
		void Scale() {
	        if (selected == TransformDirection.None) return;
			var plane = new Plane((Camera.main.transform.position - TargetObjct.position).normalized, prev.position);
			var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			float distance;
			if(plane.Raycast(ray, out distance)) {
				var point = ray.GetPoint(distance);
				var axis = global ? axes[selected] : prev.rotation * axes[selected];
				var dir = point - prev.position;
				var proj = Vector3.Project(dir, axis.normalized);
				var offset = GetStartDistance();

				var mag = 0f;
				if(proj.magnitude < offset) {
					mag = 1f - (offset - proj.magnitude) / offset;
				} else {
					mag = proj.magnitude / offset;
				}
				var scale = TargetObjct.localScale;
				switch(selected) {
				case TransformDirection.X:
					scale.x = prev.scale.x * mag;
					break;
				case TransformDirection.Y:
					scale.y = prev.scale.y * mag;
					break;
				case TransformDirection.Z:
					scale.z = prev.scale.z * mag;
					break;
				}
				DragEventRun();
				TargetObjct.localScale = scale;
			}
	    }
		void OnRenderObject() {
	        if (mode == TransformMode.None) return;

	        GL.PushMatrix();

            GL.MultMatrix(GetTranform());

			
	        switch (mode) {
	            case TransformMode.Translate:
	                DrawTranslate();
	                break;

	            case TransformMode.Rotate:
	                DrawRotate();
	                break;

	            case TransformMode.Scale:
	                DrawScale();
	                break;
	        }
	        GL.PopMatrix();
	    }
		/// <summary>
		/// 绘制GL
		/// </summary>
		/// <param name="start"></param>
		/// <param name="end"></param>
		/// <param name="color"></param>
	    void DrawLine (Vector3 start, Vector3 end, Color color) {
	        GL.Begin(GL.LINES);
			GL.Color(color);
	        GL.Vertex(start);
	        GL.Vertex(end);
			GL.End();
	    }
		/// <summary>
		/// mesh转GL
		/// </summary>
		/// <param name="mesh"></param>
		/// <param name="m"></param>
		/// <param name="color"></param>
	    void DrawMesh (Mesh mesh, Matrix4x4 m, Color color) {
	        GL.Begin(GL.TRIANGLES);
	        GL.Color(color);
	        var vertices = mesh.vertices;
	        for (int i = 0, n = vertices.Length; i < n; i++) {
	            vertices[i] = m.MultiplyPoint(vertices[i]);
	        }
	        var triangles = mesh.triangles;
	        for (int i = 0, n = triangles.Length; i < n; i += 3) {
	            int a = triangles[i], b = triangles[i + 1], c = triangles[i + 2];
	            GL.Vertex(vertices[a]);
	            GL.Vertex(vertices[b]);
	            GL.Vertex(vertices[c]);
	        }
			GL.End();
	    }
		/// <summary>
		/// 绘制移动句柄
		/// </summary>
	    void DrawTranslate () {
			material.SetInt("_ZTest", (int)CompareFunction.Always);
			material.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
			material.SetPass(0);
	        var color = selected == TransformDirection.X ? colors[3] : colors[0];
	        DrawLine(Vector3.zero, Vector3.right, color);
	        DrawMesh(cone, matrices[0], color);
	        color = selected == TransformDirection.Y ? colors[3] : colors[1];
	        DrawLine(Vector3.zero, Vector3.up, color);
	        DrawMesh(cone, matrices[1], color);
	        color = selected == TransformDirection.Z ? colors[3] : colors[2];
	        DrawLine(Vector3.zero, Vector3.forward, color);
	        DrawMesh(cone, matrices[2], color);
	    }
		/// <summary>
		/// 绘制旋转句柄
		/// </summary>
	    void DrawRotate () {
			material.SetInt("_ZTest", (int)CompareFunction.Always);
	        material.SetPass(0);
	        GL.Begin(GL.LINES);
	        var color = selected == TransformDirection.X ? colors[3] : colors[0];
	        GL.Color(color);
	        for(int i = 0; i < SPHERE_RESOLUTION; i++) {
	            var cur = circumX[i];
	            var next = circumX[(i + 1) % SPHERE_RESOLUTION];
	            GL.Vertex(cur);
	            GL.Vertex(next);
	        }
	        GL.End();
	        GL.Begin(GL.LINES);
	        color = selected == TransformDirection.Y ? colors[3] : colors[1];
	        GL.Color(color);
	        material.SetPass(0);
	        for(int i = 0; i < SPHERE_RESOLUTION; i++) {
	            var cur = circumY[i];
	            var next = circumY[(i + 1) % SPHERE_RESOLUTION];
	            GL.Vertex(cur);
	            GL.Vertex(next);
	        }
	        GL.End();
	        GL.Begin(GL.LINES);
	        color = selected == TransformDirection.Z ? colors[3] : colors[2];
	        GL.Color(color);
	        material.SetPass(0);
	        for(int i = 0; i < SPHERE_RESOLUTION; i++) {
	            var cur = circumZ[i];
	            var next = circumZ[(i + 1) % SPHERE_RESOLUTION];
	            GL.Vertex(cur);
	            GL.Vertex(next);
	        }
	        GL.End();
	    }
		/// <summary>
		/// 绘制缩放句柄
		/// </summary>
	    void DrawScale () {
			material.SetInt("_ZTest", (int)CompareFunction.Always);
	        material.SetPass(0);
	        var color = selected == TransformDirection.X ? colors[3] : colors[0];
	        DrawLine(Vector3.zero, Vector3.right, color);
	        DrawMesh(cube, matrices[0], color);
	        color = selected == TransformDirection.Y ? colors[3] : colors[1];
	        DrawLine(Vector3.zero, Vector3.up, color);
	        DrawMesh(cube, matrices[1], color);
	        color = selected == TransformDirection.Z ? colors[3] : colors[2];
	        DrawLine(Vector3.zero, Vector3.forward, color);
	        DrawMesh(cube, matrices[2], color);
	    }

	    #region Mesh
		/// <summary>
		/// 创建箭头mesh
		/// </summary>
		/// <param name="subdivisions"></param>
		/// <param name="radius"></param>
		/// <param name="height"></param>
		/// <returns></returns>
	    Mesh CreateCone(int subdivisions, float radius, float height)
	    {
	        Mesh mesh = new Mesh();
	        Vector3[] vertices = new Vector3[subdivisions + 2];
	        int[] triangles = new int[(subdivisions * 2) * 3];
	        vertices[0] = Vector3.zero;
	        for (int i = 0, n = subdivisions - 1; i < subdivisions; i++)
	        {
	            float ratio = (float)i / n;
	            float r = ratio * (Mathf.PI * 2f);
	            float x = Mathf.Cos(r) * radius;
	            float z = Mathf.Sin(r) * radius;
	            vertices[i + 1] = new Vector3(x, 0f, z);
	        }
	        vertices[subdivisions + 1] = new Vector3(0f, height, 0f);
	        for (int i = 0, n = subdivisions - 1; i < n; i++)
	        {
	            int offset = i * 3;
	            triangles[offset] = 0;
	            triangles[offset + 1] = i + 1;
	            triangles[offset + 2] = i + 2;
	        }
	        int bottomOffset = subdivisions * 3;
	        for (int i = 0, n = subdivisions - 1; i < n; i++)
	        {
	            int offset = i * 3 + bottomOffset;
	            triangles[offset] = i + 1;
	            triangles[offset + 1] = subdivisions + 1;
	            triangles[offset + 2] = i + 2;
	        }
	        mesh.vertices = vertices;
	        mesh.triangles = triangles;
	        return mesh;
	    }
		/// <summary>
		/// 创建方块mesh
		/// </summary>
		/// <param name="size"></param>
		/// <returns></returns>
	    Mesh CreateCube(float size) {
	        var mesh = new Mesh();

	        var hsize = size * 0.5f;
	        mesh.vertices = new Vector3[] {
	            new Vector3 (-hsize, -hsize, -hsize),
	            new Vector3 ( hsize, -hsize, -hsize),
	            new Vector3 ( hsize,  hsize, -hsize),
	            new Vector3 (-hsize,  hsize, -hsize),
	            new Vector3 (-hsize,  hsize,  hsize),
	            new Vector3 ( hsize,  hsize,  hsize),
	            new Vector3 ( hsize, -hsize,  hsize),
	            new Vector3 (-hsize, -hsize,  hsize),
	        };
	        mesh.triangles = new int[] {
	            0, 2, 1, //face front
				0, 3, 2,
	            2, 3, 4, //face top
				2, 4, 5,
	            1, 2, 5, //face right
				1, 5, 6,
	            0, 7, 4, //face left
				0, 4, 3,
	            5, 4, 7, //face back
				5, 7, 6,
	            0, 6, 7, //face bottom
				0, 1, 6
	        };
	        return mesh;
	    }
	    #endregion
	}
}

Guess you like

Origin blog.csdn.net/dxs1990/article/details/130926572