{"id":1655,"date":"2016-09-27T15:49:48","date_gmt":"2016-09-27T07:49:48","guid":{"rendered":"http:\/\/www.clonefactor.com\/wordpress\/?p=1655"},"modified":"2016-11-02T12:17:59","modified_gmt":"2016-11-02T04:17:59","slug":"unity3d-mouse-to-touch-simulator","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/program\/unity3d\/1655\/","title":{"rendered":"Unity3D Mouse to Touch Simulator"},"content":{"rendered":"<p>&nbsp;<\/p>\n<p>For develop my own Hand gesture script, I&#8217;m tried to keep testing the touch logic on device,<br \/>\nalso the unity team had\u00a0so many dark logic hidden behind the document,<br \/>\nand the\u00a0official Touch to Mouse Simulator (<span style=\"color: #008080;\">Input.simulateMouseWithTouches<\/span>) basically cause you trouble to debug.<\/p>\n<p>Therefore I started to develop my Mouse simulate touch script.<br \/>\nthe concept\u00a0is simple, to simulate 1 finger drag and 2 finger\u00a0stretch in\/out &amp; rotate based on mouse input.<br \/>\ngiving the\u00a0benefit to actually test the Touch script on editor instead of write another system,<\/p>\n<p><span style=\"color: #ff0000;\"><strong>(of course with limitation, only LEFT\u00a0CLICK\u00a0are accurate)<\/strong><\/span>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-1657\" src=\"http:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2016\/09\/m2t-1.jpg\" alt=\"m2t\" width=\"947\" height=\"541\" srcset=\"https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2016\/09\/m2t-1.jpg 947w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2016\/09\/m2t-1-300x171.jpg 300w, https:\/\/www.clonefactor.com\/wordpress\/wp-content\/uploads\/2016\/09\/m2t-1-768x439.jpg 768w\" sizes=\"auto, (max-width: 947px) 100vw, 947px\" \/><\/p>\n<p>the feature list follow.<\/p>\n<ul>\n<li>Detect Mouse <strong>Click &amp; Drag<\/strong> and identify: record position, delta position, touch phase<\/li>\n<li>Simulate 2nd finger <strong>stretch in\/out<\/strong> based on Mouse Wheel input value.<\/li>\n<li>Simulate 2nd finger <strong>rotate<\/strong> around mouse position<\/li>\n<\/ul>\n<p>if anyone feel useful, please feel free to use it. and welcome advise.<\/p>\n<p>&nbsp;<\/p>\n<p>CTouch.cs<br \/>\nImportant : the position and delta position was changed to viewport scale<\/p>\n<pre class=\"brush:csharp\">using UnityEngine;\r\nusing System.Collections.Generic;\r\n\r\npublic class CTouch : MonoBehaviour\r\n{\r\n\t#region Variables\r\n\t[SerializeField]\r\n\tbool m_Debug = true;\r\n\t[SerializeField]\r\n\tCamera m_Camera;\r\n\t[SerializeField]\r\n\teRotateMethod m_RotateMethod = eRotateMethod.click_n_Wheel;\r\n\t[SerializeField]\r\n\tfloat m_IdleActionTimeout = .5f;\r\n\tpublic enum eRotateMethod\r\n\t{\r\n\t\tclick_n_Wheel = 0,\r\n\t\tx_Wheel\r\n\t}\r\n\r\n\tpublic class TouchCache\r\n\t{\r\n\t\tpublic int fingerId = -1;\r\n\t\tpublic Vector2 position = Vector2.zero;\r\n\t\tpublic Vector2 deltaPosition = Vector2.zero;\r\n\t\t\r\n\t\tpublic TouchPhase phase = TouchPhase.Ended;\r\n\t\tpublic float deltaTime = 0f;\r\n\t\tprivate object _touch;\r\n\t\tpublic bool IsMouse { get { return _touch == null; } }\r\n\t\tpublic Touch GetTouch() { return IsMouse ? new Touch() : (Touch)_touch; }\r\n\t\tpublic Camera m_Camera = null;\r\n\r\n\t\tpublic void ConvertFrom(Touch touch)\r\n\t\t{\r\n\t\t\tfingerId = touch.fingerId;\r\n\t\t\tposition = touch.position;\r\n\t\t\tdeltaPosition = touch.deltaPosition;\r\n\t\t\tphase = touch.phase;\r\n\t\t\tdeltaTime = touch.deltaTime;\r\n\t\t\t_touch = touch;\r\n\t\t}\r\n\r\n\t\tpublic Vector2 GetViewPosition()\r\n\t\t{\r\n\t\t\treturn m_Camera.ScreenToViewportPoint(position);\r\n\t\t}\r\n\r\n\t\tpublic Vector2 GetViewDeltaPosition()\r\n\t\t{\r\n\t\t\treturn m_Camera.ScreenToViewportPoint(deltaPosition);\r\n\t\t}\r\n\r\n\t\tpublic Ray GetRay()\r\n\t\t{\r\n\t\t\treturn m_Camera.ScreenPointToRay(new Vector3(position.x, position.y, m_Camera.nearClipPlane));\r\n\t\t}\r\n\r\n\t\tpublic TouchCache(Camera camera)\r\n\t\t{\r\n\t\t\tm_Camera = camera;\r\n\t\t\tReset();\r\n\t\t}\r\n\r\n\t\tpublic void Reset()\r\n\t\t{\r\n\t\t\tfingerId = -1;\r\n\t\t\tposition = Vector2.one * 0.5f;\r\n\t\t\tdeltaPosition = Vector2.zero;\r\n\t\t\tphase = TouchPhase.Ended;\r\n\t\t\tdeltaTime = 0f;\r\n\t\t\t_touch = null;\r\n\t\t}\r\n\t}\r\n\tprivate List&lt;TouchCache&gt; m_Cache;\r\n\tfloat m_RotateDelta, m_ZoomDelta;\r\n\r\n\tconst int _MaxCacheNum = 3;\r\n\t#endregion\r\n\r\n\t#region Getter\r\n\tpublic TouchCache[] touches { get { return m_Cache.ToArray(); } }\r\n\tpublic int touchCount { get; private set; }\r\n\t#endregion\r\n\r\n\t#region System\r\n\tvoid OnEnable()\r\n\t{\r\n\t\tif (m_Camera == null)\r\n\t\t\tm_Camera = Camera.main;\r\n\r\n\t\tm_Cache = new List&lt;TouchCache&gt;(_MaxCacheNum)\r\n\t\t{\r\n\t\t\tnew TouchCache(m_Camera),\r\n\t\t\tnew TouchCache(m_Camera),\r\n\t\t\tnew TouchCache(m_Camera),\r\n\t\t};\r\n\t\ttouchCount = 0;\r\n\t\tm_RotateDelta = m_ZoomDelta = 0f;\r\n\t\tif (DevManager.Instance != null)\r\n\t\t\tDevManager.Instance.Register(this);\r\n\t}\r\n\r\n\tvoid OnDisable()\r\n\t{\r\n\t\tif (DevManager.Instance != null)\r\n\t\t\tDevManager.Instance.UnRegister(this);\r\n\t}\r\n\t\r\n\tvoid FixedUpdate()\r\n\t{\r\n\t\tAnalyticInput();\r\n\t}\r\n\r\n\tvoid OnGUI()\r\n\t{\r\n\t\tif (!m_Debug)\r\n\t\t\treturn;\r\n\t\tDebugGUI();\r\n\t}\r\n\r\n\t[DevGUICallBack(\"CTouch\")]\r\n\tpublic void DebugGUI()\r\n\t{\r\n\t\tfor (int i = 0; i &lt; m_Cache.Count; i++)\r\n\t\t{\r\n\t\t\tGUILayout.Label(string.Format(\"Touch[{0,3} - {1,15}] on Position{2}, delta {3}\", i, m_Cache[i].phase.ToString(\"F\"), m_Cache[i].position, m_Cache[i].deltaPosition));\r\n\r\n\t\t\tRay ray = m_Cache[i].GetRay();\r\n\t\t\tColor color = i == 0 ? Color.red : i == 1 ? Color.green : Color.yellow;\r\n\t\t\tif (m_Cache[i].phase &lt; TouchPhase.Ended)\r\n\t\t\t\tDebug.DrawRay(ray.origin, ray.direction, color);\r\n\t\t}\r\n\t\tGUILayout.Label(\"ZoomDelta :\" + m_ZoomDelta);\r\n\t\tGUILayout.Label(\"RotateDelta :\" + m_RotateDelta);\r\n\t\tGUILayout.Label(\"TouchCount :\" + touchCount);\r\n\t}\r\n\r\n\t#endregion\r\n\r\n\t#region Main\r\n\tvoid AnalyticInput()\r\n\t{\r\n\t\tif (Input.touchSupported)\r\n\t\t\tDeviceTouchInput();\r\n\t\telse if (Input.mousePresent)\r\n\t\t\tMouseTouchSimulator();\r\n\t\telse\r\n\t\t{\r\n\t\t\tDebug.LogWarning(\"Unknow device: CTouch disable.\");\r\n\t\t\ttouchCount = 0;\r\n\t\t}\r\n\t}\r\n\t#endregion\r\n\r\n\t#region Mouse Simulator\r\n\tvoid MouseTouchSimulator()\r\n\t{\r\n\t\tVector3 mousePosition = Input.mousePosition;\r\n\t\tVector2 mouseScrollDelta = Input.mouseScrollDelta;\r\n\r\n\t\t\/\/ 1st Touch - mapping drag &amp; click\r\n\t\tMouseTouchDragSimulator((Vector2)mousePosition);\r\n\t\ttouchCount = m_Cache[0].phase &lt; TouchPhase.Ended ? 1 : 0;\r\n\r\n\t\t\/\/ 2nd Touch - mapping wheel to zoom\r\n\t\tMouseTouchZoomSimulator(mousePosition, mouseScrollDelta);\r\n\t\tif (m_Cache[1].phase &lt; TouchPhase.Ended)\r\n\t\t\ttouchCount++;\r\n\r\n\t\t\/\/ 3rd Touch - mapping click &amp; wheel to rotation\r\n\t\tMouseTouchRotateHelper(mouseScrollDelta);\r\n\t\tif (m_Cache[2].phase &lt; TouchPhase.Ended)\r\n\t\t\ttouchCount++;\r\n\t}\r\n\r\n\tvoid MouseTouchDragSimulator(Vector2 mousePosition)\r\n\t{\r\n\t\tif (Input.GetMouseButtonDown(0))\r\n\t\t{\r\n\t\t\tm_Cache[0].fingerId = 0;\r\n\t\t\tm_Cache[0].phase = TouchPhase.Began;\r\n\t\t\tm_Cache[0].position = mousePosition;\r\n\t\t\tm_Cache[0].deltaPosition = Vector2.zero;\r\n\t\t}\r\n\t\telse if (Input.GetMouseButtonUp(0))\r\n\t\t{\r\n\t\t\tm_Cache[0].Reset();\r\n\t\t}\r\n\t\telse if (m_Cache[0].phase &lt; TouchPhase.Ended)\r\n\t\t{\r\n\t\t\t\/\/ touch state\r\n\t\t\tm_Cache[0].deltaTime = Time.deltaTime;\r\n\t\t\tm_Cache[0].deltaPosition = mousePosition - m_Cache[0].position;\r\n\t\t\tm_Cache[0].phase = TouchPhase.Stationary;\r\n\t\t\t\/\/ not allow elseif here, touch &amp; move can happen at same time.\r\n\t\t\tif (m_Cache[0].position != mousePosition)\r\n\t\t\t{\r\n\t\t\t\tm_Cache[0].phase = TouchPhase.Moved;\r\n\t\t\t\tm_Cache[0].position = mousePosition;\r\n\t\t\t}\r\n\t\t\telse if (!Input.GetMouseButton(0))\r\n\t\t\t{\r\n\t\t\t\t\/\/ special case: on mouse release click off screen.\r\n\t\t\t\t\/\/ e.g. change application, lose force..etc\r\n\t\t\t\tm_Cache[0].Reset();\r\n\t\t\t\tm_Cache[0].phase = TouchPhase.Canceled;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (m_Cache[0].phase == TouchPhase.Canceled)\r\n\t\t{\r\n\t\t\t\/\/ special case: back to End state after one frame.\r\n\t\t\tm_Cache[0].Reset();\r\n\t\t}\r\n\t}\r\n\r\n\tvoid MouseTouchZoomSimulator(Vector3 mousePosition, Vector2 mouseScrollDelta)\r\n\t{\r\n\t\tfloat scroll = Input.GetAxis(\"Mouse ScrollWheel\");\r\n\t\tif (!Mathf.Approximately(scroll, 0f))\r\n\t\t{\r\n\t\t\t\/\/ calculate\r\n\t\t\tm_ZoomDelta = Mathf.Clamp(Mathf.Abs(scroll), 0f, .5f) * Mathf.Sign(scroll);\r\n\t\t\tif (m_RotateMethod == eRotateMethod.x_Wheel)\r\n\t\t\t\tm_RotateDelta += mouseScrollDelta.x * 10f;\r\n\t\t\telse if (m_RotateMethod == eRotateMethod.click_n_Wheel &amp;&amp; Input.GetMouseButton(1))\r\n\t\t\t\tm_RotateDelta += scroll * 1f;\r\n\r\n\t\t\tVector3 anchor = new Vector3(.5f, .5f, m_Camera.nearClipPlane);\r\n\t\t\tif (m_Cache[0].phase &lt; TouchPhase.Ended)\r\n\t\t\t{\r\n\t\t\t\tVector3 view = m_Camera.ScreenToViewportPoint(m_Cache[0].position);\r\n\t\t\t\tanchor = new Vector3(view.x, view.y, m_Camera.nearClipPlane);\r\n\t\t\t}\r\n\r\n\t\t\tm_Cache[1].fingerId = 1;\r\n\t\t\tm_Cache[1].deltaTime = Time.deltaTime;\r\n\r\n\t\t\tVector3 vCenter = m_Camera.ViewportToWorldPoint(anchor);\r\n\t\t\tVector3 vRadius = m_Camera.ViewportToWorldPoint(new Vector3(anchor.x + m_ZoomDelta, anchor.y, m_Camera.nearClipPlane));\r\n\t\t\tVector3 vRotate = m_Camera.ViewportToWorldPoint(new Vector3(anchor.x + m_ZoomDelta * Mathf.Cos(m_RotateDelta), anchor.y * Mathf.Sin(m_RotateDelta), m_Camera.nearClipPlane));\r\n\t\t\tVector3 offset3D = m_Camera.WorldToViewportPoint(Input.GetMouseButton(1) ? vRotate : vRadius);\r\n\r\n\t\t\tDebug.DrawLine(vCenter, offset3D, Color.magenta, 0.1f);\r\n\t\t\tm_Cache[1].deltaPosition = m_Cache[1].position - new Vector2(offset3D.x, offset3D.y);\r\n\t\t\tm_Cache[1].position = offset3D;\r\n\r\n\t\t\t\/\/ touch state\r\n\t\t\tif (m_Cache[1].phase &gt;= TouchPhase.Ended)\r\n\t\t\t{\r\n\t\t\t\tm_Cache[1].phase = TouchPhase.Began;\r\n\t\t\t\tm_Cache[1].deltaPosition = Vector2.zero;\r\n\t\t\t}\r\n\t\t\telse if (m_Cache[1].phase == TouchPhase.Began)\r\n\t\t\t{\r\n\t\t\t\tm_Cache[1].phase = TouchPhase.Moved;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if (m_Cache[1].phase &lt; TouchPhase.Ended)\r\n\t\t{\r\n\t\t\tm_Cache[1].deltaTime += Time.deltaTime;\r\n\t\t\tif (m_Cache[1].deltaTime &gt; m_IdleActionTimeout)\r\n\t\t\t{\r\n\t\t\t\tm_Cache[1].Reset();\r\n\t\t\t\tm_ZoomDelta = 0f;\r\n\t\t\t\tm_RotateDelta = 0f;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tvoid MouseTouchRotateHelper(Vector2 mouseScrollDelta)\r\n\t{\r\n\t\t\/\/ this touch only for rotate without hold down button\r\n\t\tif (m_Cache[0].phase &gt;= TouchPhase.Ended &amp;&amp;\r\n\t\t\tm_Cache[1].phase &lt; TouchPhase.Ended &amp;&amp;\r\n\t\t\t!Input.GetMouseButton(0))\r\n\t\t{\r\n\t\t\t\/\/ touch\r\n\t\t\tm_Cache[2].fingerId = 2;\r\n\t\t\tm_Cache[2].deltaTime = Time.deltaTime;\r\n\t\t\tm_Cache[2].deltaPosition = Vector2.zero;\r\n\t\t\tm_Cache[2].position = Vector2.one * .5f; \/\/ viewport center\r\n\r\n\t\t\t\/\/ touch state\r\n\t\t\tif (m_Cache[2].phase &gt;= TouchPhase.Ended)\r\n\t\t\t\tm_Cache[2].phase = TouchPhase.Began;\r\n\t\t\telse if (m_Cache[2].phase == TouchPhase.Began)\r\n\t\t\t\tm_Cache[2].phase = TouchPhase.Stationary;\r\n\t\t}\r\n\t\telse if (m_Cache[1].phase == TouchPhase.Ended &amp;&amp; m_Cache[2].phase &lt; TouchPhase.Ended)\r\n\t\t{\r\n\t\t\tm_Cache[2].Reset();\r\n\t\t}\r\n\t}\r\n\t#endregion\r\n\r\n\t#region Device input\r\n\tvoid DeviceTouchInput()\r\n\t{\r\n\t\tint filled = 0;\r\n\t\tfor (int x = 0; x &lt; Input.touchCount &amp;&amp; filled &lt; m_Cache.Count - 1; x++)\r\n\t\t{\r\n\t\t\tif (Input.touches[x].phase != TouchPhase.Ended)\r\n\t\t\t{\r\n\t\t\t\tm_Cache[filled].ConvertFrom(Input.touches[x]);\r\n\t\t\t\tfilled++;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tm_Cache[x].Reset();\r\n\t\t\t}\r\n\t\t}\r\n\t\ttouchCount = filled;\r\n\t}\r\n\t#endregion\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>CCameraOrbit.cs<\/p>\n<pre class=\"brush:csharp\">using UnityEngine;\r\n\r\n[RequireComponent(typeof(CTouch))]\r\npublic class CCameraOrbit : MonoBehaviour\r\n{\r\n\t[SerializeField] CTouch m_Input;\r\n\t[SerializeField] float m_Speed = 30f;\r\n\t[SerializeField] float m_InputMulipler = 200f;\r\n\t[SerializeField] float m_MobileInputMulipler = 600f;\r\n\t[SerializeField] float m_AngleCap = 90f;\r\n\t[SerializeField] float m_Distance = 10f;\r\n\t[SerializeField] LayerMask m_LayerMask = 0;\r\n\t[SerializeField] Collider m_DetectArea = null;\r\n\tprivate Quaternion m_TargetRotate;\r\n\tprivate Quaternion m_DefaultRotate;\r\n\tprivate bool m_IsRotate = false;\r\n\tprivate int m_FingerId = -1;\r\n\tprivate bool m_CheckDelectArea = false;\r\n\r\n\tvoid OnValidate()\r\n\t{\r\n\t\tif (m_Input)\r\n\t\t\tm_Input = GetComponent&lt;CTouch&gt;();\r\n\t}\r\n\r\n\tvoid Awake()\r\n\t{\r\n\t\tm_TargetRotate = m_DefaultRotate = transform.rotation;\r\n\t}\r\n\r\n\tvoid OnEnable()\r\n\t{\r\n\t\ttransform.rotation = m_TargetRotate = m_DefaultRotate;\r\n\t\tif (DevManager.Instance != null)\r\n\t\t\tDevManager.Instance.Register(this);\r\n\r\n\t\t\/\/ ensure the component status, since NGUI will fuck this up.\r\n\t\tm_DetectArea.gameObject.layer = Mathf.CeilToInt(Mathf.Log(m_LayerMask.value, 2));\r\n\t\tm_DetectArea.enabled = true;\r\n\t}\r\n\r\n\tvoid OnDisable()\r\n\t{\r\n\t\tif (DevManager.Instance != null)\r\n\t\t\tDevManager.Instance.UnRegister(this);\r\n\t}\r\n\r\n\t[DevGUICallBack(\"CTouch\")]\r\n\tpublic\tvoid OnDebugGUI()\r\n\t{\r\n\t\tGUILayout.Label(\"Status : \" + (m_IsRotate ? \"Rotating\" : \"Idle\"));\r\n\t\tGUILayout.Label(\"Finger Id: \" + m_FingerId);\r\n\t\t\r\n\t\tGUILayout.Space(10f);\r\n\t\t\/\/ check if detect area fuckup.\r\n\t\tm_CheckDelectArea = GUILayout.Toggle(m_CheckDelectArea, \"Check detect area.\");\r\n\t\tif (m_CheckDelectArea)\r\n\t\t{\r\n\t\t\tforeach (CTouch.TouchCache touch in m_Input.touches)\r\n\t\t\t{\r\n\t\t\t\tif (touch.phase &lt; TouchPhase.Ended)\r\n\t\t\t\t\tGUILayout.Label(\"Finger : \" + touch.phase.ToString(\"F\") + \" pos:\" + touch.position);\r\n\t\t\t}\r\n\t\t\tGUILayout.Label(\"Detecting Object: \" + m_DetectArea.name + \", enable=\" + m_DetectArea.enabled + \", activeSelf=\" + m_DetectArea.gameObject.activeInHierarchy + \", localScale=\" + m_DetectArea.transform.localScale);\r\n\t\t\tif (GUILayout.Button(\"make that collider fucking big \" + m_DetectArea.transform.lossyScale))\r\n\t\t\t\tm_DetectArea.transform.localScale = Vector3.one * 1000;\r\n\t\t\tGUILayout.Space(10f);\r\n\t\t\tGUILayout.Label(\"Layer=\" + LayerMask.LayerToName(m_DetectArea.gameObject.layer) + \", vs. Camera Mask=\" + LayerMask.LayerToName(Mathf.CeilToInt(Mathf.Log(m_LayerMask.value, 2))));\r\n\t\t\tm_DetectArea.isTrigger = GUILayout.Toggle(m_DetectArea.isTrigger, \"Toggle IsTrigger\");\r\n\t\t}\r\n\r\n\t\tGUILayout.Space(10f);\r\n\t\tGUILayout.Label(\"Speed \"+ m_Speed);\r\n\t\tm_Speed = float.Parse(GUILayout.TextField(m_Speed.ToString()));\r\n\r\n\t\tGUILayout.Label(\"Input Mulipler \"+ m_InputMulipler);\r\n\t\tm_InputMulipler = float.Parse(GUILayout.TextField(m_InputMulipler.ToString()));\r\n\r\n\t\tGUILayout.Label(\"Distance \"+ m_Distance);\r\n\t\tm_Distance = GUILayout.HorizontalSlider(m_Distance, 0f, float.MaxValue);\r\n\r\n\t\tGUILayout.Label(\"Angle Cap \" + m_AngleCap);\r\n\t\tm_AngleCap = GUILayout.HorizontalSlider(m_AngleCap, 1f, 90f);\r\n\t}\r\n\t\r\n\tvoid FixedUpdate()\r\n\t{\r\n\t\tif(!m_IsRotate) \/\/ don't combine this checking, otherwise \"else\" will keep running even no input\r\n\t\t{\r\n\t\t\tif (m_Input.touchCount &gt; 0)\r\n\t\t\t{\r\n\t\t\t\tfor (int i =0; !m_IsRotate &amp;&amp; i &lt; m_Input.touches.Length; i++)\r\n\t\t\t\t{\r\n\t\t\t\t\tif (m_Input.touches[i].phase &lt; TouchPhase.Ended)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tforeach(RaycastHit hit in Physics.RaycastAll(m_Input.touches[i].GetRay(), m_Distance, m_LayerMask))\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\tif (hit.collider == m_DetectArea)\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\tm_IsRotate = true;\r\n\t\t\t\t\t\t\t\tm_TargetRotate = transform.rotation;\r\n\t\t\t\t\t\t\t\tm_FingerId = m_Input.touches[i].fingerId;\r\n\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tbool alive = false;\r\n\t\t\tforeach (CTouch.TouchCache touch in m_Input.touches)\r\n\t\t\t{\r\n\t\t\t\tif(touch.fingerId == m_FingerId &amp;&amp; touch.phase &lt; TouchPhase.Ended)\r\n\t\t\t\t{\r\n\t\t\t\t\talive = true;\r\n\t\t\t\t\tif(Mathf.Abs(touch.deltaPosition.x) &gt; 0f)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tfloat diff = touch.GetViewDeltaPosition().x * (Application.isMobilePlatform ? m_MobileInputMulipler : m_InputMulipler);\r\n\t\t\t\t\t\tfloat speed = OverTurnAngleAccuracy(diff);\r\n\t\t\t\t\t\tm_TargetRotate *= Quaternion.AngleAxis(speed, Vector3.up); \/\/ Accumulate angle\r\n\r\n\t\t\t\t\t\t\/\/Debug.LogFormat(\"diff={0:F2}, signAngle={1:F2}, predictAngle={2:F2} overflowAngle={3:F2}\",\r\n\t\t\t\t\t\t\/\/\tdiff, signAngle, predictAngle, overflowAngle);\r\n\t\t\t\t\t\tDebug.DrawRay(transform.position, Quaternion.AngleAxis(speed, Vector3.up) * Vector3.forward, Color.red);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (!alive)\r\n\t\t\t{\r\n\t\t\t\tm_FingerId = -1;\r\n\t\t\t\tm_IsRotate = false;\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t\tDebug.DrawRay(transform.position, m_TargetRotate * Vector3.forward, Color.cyan);\r\n\t\tDebug.DrawRay(transform.position, transform.forward, Color.yellow);\r\n\t\ttransform.rotation = Quaternion.Lerp(transform.rotation, m_TargetRotate, Time.deltaTime * m_Speed);\r\n\t\t\/\/ Debug.DrawRay(transform.position, m_TargetRotate * Vector3.forward, Color.red, 0.1f);\r\n\t}\r\n\r\n\tprivate float OverTurnAngleAccuracy(float amount)\r\n\t{\r\n\t\t\/\/ amount = Mathf.Clamp(amount, -360f, 360f);\r\n\t\tQuaternion predict = m_TargetRotate * Quaternion.AngleAxis(amount, Vector3.up);\r\n\t\tfloat\r\n\t\t\tangleDiff = Mathf.Abs(AngleBetweenDirectionSigned(transform.forward, predict * Vector3.forward, Vector3.up)),\r\n\t\t\toverTurnHotfix = Mathf.Lerp(1f, 0.1f, ((angleDiff &gt;= m_AngleCap) ? 1f : angleDiff \/ m_AngleCap));\r\n\r\n\t\treturn amount * overTurnHotfix;\r\n\t}\r\n\r\n\tpublic float AngleBetweenDirectionSigned(Vector3 direction1, Vector3 direction2, Vector3 normal)\r\n\t{\r\n\t\treturn Mathf.Rad2Deg * Mathf.Atan2(Vector3.Dot(normal, Vector3.Cross(direction1, direction2)), Vector3.Dot(direction1, direction2));\r\n\t}\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>&nbsp; For develop my own Hand gesture script, I&#038;# &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[78,11],"tags":[94,43],"class_list":["post-1655","post","type-post","status-publish","format-standard","hentry","category-math","category-unity3d","tag-input","tag-unity3d-2"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1655","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/comments?post=1655"}],"version-history":[{"count":0,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1655\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1655"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1655"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1655"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}