【unity】模型控制句柄(类似编辑面板)

【unity】简单实现模型控制句柄(类似编辑面板)

使用GL绘制控制句柄,实现定位、旋转、伸缩变形,类似编辑面板的模型编辑的功能。

工程文件下载地址

使用方法

实时控制

核心方法: TransformControl.Control();

设置控制目标

核心方法: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;
            }
        }
    }

切换模式

核心方法: 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;
        }
    }

拖拽事件

核心方法: TransformControl.DragEvent.AddListener(open);

demo

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

源码

TransformControl 主功能方法

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
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小生云木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值