{"id":1729,"date":"2017-02-24T03:40:24","date_gmt":"2017-02-23T19:40:24","guid":{"rendered":"http:\/\/www.clonefactor.com\/wordpress\/?p=1729"},"modified":"2017-02-25T03:24:51","modified_gmt":"2017-02-24T19:24:51","slug":"detection-for-bisection-obstacle-in-unity3d","status":"publish","type":"post","link":"https:\/\/www.clonefactor.com\/wordpress\/program\/c\/1729\/","title":{"rendered":"\u4e8c\u5206\u6cd5, \u969c\u7919\u7269\u908a\u6cbf\u67e5\u627e &#038; \u8cc7\u6e90\u7ba1\u7406"},"content":{"rendered":"<p>\u672c\u6b21\u9805\u76ee\uff0c\u4e3b\u8981\u6982\u5ff5\u5f9e\u9019\u88e1\u5f97\u5230\u555f\u767c :\u00a0<a href=\"https:\/\/tedsieblog.wordpress.com\/2017\/02\/22\/field-of-view\/\">https:\/\/tedsieblog.wordpress.com\/2017\/02\/22\/field-of-view\/<\/a><\/p>\n<p>\u5c0d Ray \u53ca RaycastHit \u9032\u884c\u64f4\u5efa, \u4e26\u6574\u5408\u81ea\u5df1\u5e38\u7528\u7684\u5de5\u5177\uff0c\u9054\u5230\u6709\u6548\u958b\u767c\u70ba\u524d\u984c\uff0c\u4f86\u5be6\u73fe\u9019\u6b21\u7684\u9805\u76ee\u3002<\/p>\n<p>\u7562\u7adf\uff0c\u4e8c\u5206\u6cd5\u5c04\u7dda\u9019\u7a2e\u73fe\u5be6\u53ef\u7528\u7684\u6280\u5de7\u9084\u662f\u6709\u5176\u5be6\u9a57\u50f9\u503c\u3002<\/p>\n<p>&nbsp;<\/p>\n<p>\u4e8c\u5206\u6cd5\u5f88\u7c21\u55ae\u7684\u9054\u6210\uff0c\u4f46\u554f\u984c\u662f\u5982\u4f55\u5feb\u901f\u9032\u884c\u904b\u7b97\uff0c \u5be6\u4f5c\u7684\u6642\u5019\u53c8\u9032\u4e00\u6b65\u4e86\u89e3\u5230 Raycast \u7684\u904b\u4f5c\uff0c\u5176\u5be6\u771f\u6b63\u8981\u8655\u7406\u662f\u5982\u4f55\u5728\u5927\u91cf\u8cc7\u6599\u8655\u7406\u6642\u6709\u6548\u5206\u914d\u8cc7\u6e90\u3002<\/p>\n<p>\u7c21\u55ae\u8aaa\u5c31\u662f\u8cc7\u6e90\u5206\u914d\u3002<\/p>\n<p>\u4e8c\u5206\u6cd5\u5c04\u7dda\u4e26\u4e0d\u61c9\u8a72\u76f4\u63a5\u7528\u5728\u904a\u6232\u4e2d\uff0c\u5728 Unity \u4e2d\u9084\u63d0\u4f9b\u66f4\u5feb\u7684 SphereCast, SphereCastAll, BoxCast &#8230;\u7b49\u7b49\uff0c<br \/>\nRef :\u00a0<a href=\"https:\/\/docs.unity3d.com\/ScriptReference\/Physics.SphereCastAll.html\">https:\/\/docs.unity3d.com\/ScriptReference\/Physics.SphereCastAll.html<\/a><\/p>\n<p>&nbsp;<\/p>\n<p>\u76f4\u63a5\u4e0a\u5f71\u50cf.<\/p>\n<p><iframe loading=\"lazy\" title=\"Raycast locate edge of object\" width=\"1260\" height=\"945\" src=\"https:\/\/www.youtube.com\/embed\/Ha7YSyzAjRo?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen><\/iframe><\/p>\n<p>\u4ee5\u4e0b\u662f\u6574\u5408 Ray \u53ca RaycastHit \u7684 Struct \u9019\u90e8\u4efd\u9700\u7559\u610f(\u4e26\u4e0d\u662fclass) \uff0c\u9019\u6709\u52a9\u5927\u91cf\u8cc7\u8a0a\u64cd\u4f5c\u7684\u8655\u7406\u3002<\/p>\n<h2>RayData.cs<\/h2>\n<pre class=\"brush:csharp\">using System;\r\nusing UnityEngine;\r\n\r\nnamespace Kit.Physic\r\n{\r\n\tpublic struct RayData : IEquatable&lt;RayData&gt;\r\n\t{\r\n\t\tpublic static RayData NONE { get { return default(RayData); } }\r\n\r\n\t\t#region Core &amp; constructor\r\n\t\tprivate Ray m_Ray;\r\n\t\tpublic Vector3 origin { get { return m_Ray.origin; } set { m_Ray.origin = value; } }\r\n\t\tpublic Vector3 direction { get { return m_Ray.direction; } set { m_Ray.direction = value; } }\r\n\t\tpublic Quaternion rotation\r\n\t\t{\r\n\t\t\tget { return Quaternion.Euler(m_Ray.direction); }\r\n\t\t\tset { m_Ray.direction = value * Vector3.forward; }\r\n\t\t}\r\n\t\tpublic float distance;\r\n\t\tpublic RaycastHit hit;\r\n\t\tpublic bool hitted { get { return hit.transform != null; } }\r\n\t\t\r\n\t\tpublic RayData(Ray ray, float distance)\r\n\t\t{\r\n\t\t\tm_Ray = ray;\r\n\t\t\tthis.distance = distance;\r\n\t\t\thit = new RaycastHit();\r\n\t\t}\r\n\t\t\r\n\t\tpublic RayData(Vector3 origin, Vector3 direction, float distance)\r\n\t\t\t: this(new Ray(origin, direction), distance)\r\n\t\t{ }\r\n\t\t#endregion\r\n\r\n\t\t#region Util tools\r\n\t\tpublic static bool IsNullOrEmpty(RayData obj)\r\n\t\t{\r\n\t\t\treturn ReferenceEquals(null, obj) || obj.Equals(RayData.NONE);\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Reuse the RayData for something else instead of alloc new memory&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"_origin\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"_direction\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"_distance\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"reset\"&gt;&lt;\/param&gt;\r\n\t\tpublic void Update(Vector3 _origin, Vector3 _direction, float _distance)\r\n\t\t{\r\n\t\t\torigin = _origin;\r\n\t\t\tdirection = _direction;\r\n\t\t\tdistance = _distance;\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Reuse the RayData for something else instead of alloc new memory&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"other\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"hitReferences\"&gt;include the RaycastHit reference.&lt;\/param&gt;\r\n\t\tpublic void Update(RayData other, bool hitReferences = false)\r\n\t\t{\r\n\t\t\tUpdate(other.origin, other.direction, other.distance);\r\n\t\t\tif (hitReferences)\r\n\t\t\t\thit = other.hit;\r\n\t\t}\r\n\r\n\t\tpublic void Reset()\r\n\t\t{\r\n\t\t\torigin = direction = Vector3.zero;\r\n\t\t\tdistance = 0f;\r\n\t\t\thit = new RaycastHit();\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Physics.Raycast&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"layerMask\"&gt;default = Everything&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"queryTriggerInteraction\"&gt;default = UseGlobal&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;returns&gt;return true when hitted.&lt;\/returns&gt;\r\n\t\tpublic bool Raycast(int layerMask = Physics.DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal)\r\n\t\t{\r\n\t\t\treturn Physics.Raycast(m_Ray, out hit, distance, layerMask, queryTriggerInteraction);\r\n\t\t}\r\n\t\t\r\n\t\t\/\/\/ &lt;summary&gt;Compare with hit result are hitting same object&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"raycastHit\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"includeNull\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;returns&gt;&lt;\/returns&gt;\r\n\t\tpublic bool IsHittingSameObject(RaycastHit raycastHit, bool includeNull = false)\r\n\t\t{\r\n\t\t\treturn (includeNull || hitted) ? hit.transform == raycastHit.transform : false;\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Compare with hit result are hitting same object&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"raycastHit\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"includeNull\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;returns&gt;&lt;\/returns&gt;\r\n\t\tpublic bool IsHittingSameObject(RayData raydata, bool includeNull = false)\r\n\t\t{\r\n\t\t\treturn IsHittingSameObject(raydata.hit, includeNull);\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Compare angle based on normal&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"normal\"&gt;World Up&lt;\/param&gt;\r\n\t\tpublic float AngleBetweenDirectionSigned(RayData rayB, Vector3 normal = default(Vector3))\r\n\t\t{\r\n\t\t\treturn AngleBetweenDirectionSigned(rayB.m_Ray, normal);\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Compare angle based on normal&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"normal\"&gt;World Up&lt;\/param&gt;\r\n\t\tpublic float AngleBetweenDirectionSigned(Ray otherRay, Vector3 normal = default(Vector3))\r\n\t\t{\r\n\t\t\tif (normal == default(Vector3))\r\n\t\t\t\tnormal = Vector3.up;\r\n\t\t\treturn Mathf.Rad2Deg * Mathf.Atan2(Vector3.Dot(normal, Vector3.Cross(m_Ray.direction, otherRay.direction)), Vector3.Dot(m_Ray.direction, otherRay.direction));\r\n\t\t}\r\n\r\n\t\tpublic void DrawGizmos(Color color = default(Color), Color hitColor = default(Color))\r\n\t\t{\r\n\t\t\tif (color == default(Color))\r\n\t\t\t\treturn;\r\n\t\t\tColor cache = Gizmos.color;\r\n\t\t\tif (hitted)\r\n\t\t\t{\r\n\t\t\t\tif (hitColor == default(Color))\r\n\t\t\t\t{\r\n\t\t\t\t\thitColor = color;\r\n\t\t\t\t\tcolor.a = Mathf.Lerp(0f, color.a, .5f);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tGizmos.color = color;\r\n\t\t\tGizmos.DrawLine(m_Ray.origin, m_Ray.origin + m_Ray.direction * distance);\r\n#if UNITY_EDITOR\r\n\t\t\tGizmos.DrawWireCube(m_Ray.origin, Vector3.one * 0.1f * UnityEditor.HandleUtility.GetHandleSize(m_Ray.origin));\r\n#endif\r\n\t\t\tif (hitted)\r\n\t\t\t{\r\n\t\t\t\tGizmos.color = hitColor;\r\n\t\t\t\tGizmos.DrawLine(m_Ray.origin, hit.point);\r\n\t\t\t}\r\n\t\t\tGizmos.color = cache;\r\n\t\t}\r\n\t\t#endregion\r\n\r\n\t\t#region overload methods\r\n\t\tpublic override string ToString()\r\n\t\t{\r\n\t\t\treturn (hitted) ?\r\n\t\t\t\tstring.Format(\"{0}, Distance: {1:F2}, Hit: {2} ({3:F2})\", m_Ray.ToString(\"F2\"), distance, hit.transform.name, hit.point) :\r\n\t\t\t\tstring.Format(\"{0}, Distance: {1:F2}, Hit: None\", m_Ray.ToString(\"F2\"), distance);\r\n\t\t}\r\n\r\n\t\tpublic override int GetHashCode()\r\n\t\t{\r\n\t\t\treturn m_Ray.GetHashCode();\r\n\t\t}\r\n\t\tpublic override bool Equals(object obj)\r\n\t\t{\r\n\t\t\treturn Equals(NONE) ? obj == null : Equals((RayData)obj);\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Approximately Equal, based on Ray and distance, exclude hit result.&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"other\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;returns&gt;&lt;\/returns&gt;\r\n\t\tpublic bool Equals(RayData other)\r\n\t\t{\r\n\t\t\tif (GetHashCode() != other.GetHashCode())\r\n\t\t\t\treturn false;\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\treturn\r\n\t\t\t\t\tMathf.Approximately(distance, other.distance) &amp;&amp;\r\n\t\t\t\t\tMathf.Approximately(m_Ray.origin.x, other.origin.x) &amp;&amp;\r\n\t\t\t\t\tMathf.Approximately(m_Ray.origin.y, other.origin.y) &amp;&amp;\r\n\t\t\t\t\tMathf.Approximately(m_Ray.origin.z, other.origin.z) &amp;&amp;\r\n\t\t\t\t\tMathf.Approximately(m_Ray.direction.x, other.direction.x) &amp;&amp;\r\n\t\t\t\t\tMathf.Approximately(m_Ray.direction.y, other.direction.y) &amp;&amp;\r\n\t\t\t\t\tMathf.Approximately(m_Ray.direction.z, other.direction.z);\r\n\t\t\t}\r\n\t\t}\r\n\t\t#endregion\r\n\t}\r\n}<\/pre>\n<p>\u4ee5\u4e0a\u7684 RayData.cs, \u7528\u4f86\u5b58 \u00a0position, direction, distance, raycasthit \u7b49\u7684\u8cc7\u6599.<\/p>\n<p>\u5b83\u662f\u4e00\u500b struct \u67b6\u69cb\uff0c\u512a\u52e2\u5247\u5728\u65bc\u8cc7\u6e90\u7ba1\u7406.<\/p>\n<p>Ref: Choosing Between Class and Struct<br \/>\n<a href=\"https:\/\/msdn.microsoft.com\/en-us\/library\/ms229017(v=vs.110).aspx\">https:\/\/msdn.microsoft.com\/en-us\/library\/ms229017(v=vs.110).aspx<\/a><\/p>\n<p>\u7528\u5716\u7247\u770b\u5c31\u5f88\u7c21\u55ae\u6613\u660e, \u9019\u662f Array of Class<br \/>\n<img decoding=\"async\" src=\"https:\/\/i.stack.imgur.com\/6VIub.png\" alt=\"Array of a reference type\" \/><\/p>\n<p>\u4ee5\u4e0b\u5247\u662f Array of struct<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/i.stack.imgur.com\/AxcNa.png\" alt=\"Array of a value type\" \/><\/p>\n<p>\u56e0\u70ba\u8655\u7406\u7269\u7406\u908a\u6cbf\u6642\u9700\u8981\u5927\u91cf\u8655\u7406\u9019\u985e\u7684\u7269\u7406\u8cc7\u8a0a\uff0c\u5982\u679c\u5927\u91cf\u7684\u5efa\u7acb object \u5fc5\u7136\u9700\u8981\u5403\u6389\u76f8\u5c0d\u61c9\u7684\u5167\u5b58\u3002<\/p>\n<p>\u9019\u6642\u5019\u5982\u679c\u91cd\u7528\u8b8a\u91cf (reuse variables) \u5c31\u8b8a\u5f97\u76f8\u5c0d\u56b4\u683c\u3002<\/p>\n<p>&nbsp;<\/p>\n<p>\u63a5\u4e0b\u4f86\u5c31\u662f\u672c\u6b21\u7684\u4e3b\u89d2 EdgeFinder, \u4f7f\u7528\u4e8c\u5206\u6cd5\u641c\u5c0b\u5c0d\u8c61\u7684\u908a\u6cbf\u3002<\/p>\n<h2>EdgeFinder.cs<\/h2>\n<pre class=\"brush:csharp\">\/\/#define BULLET_TIME\r\n\/\/ ^ enable this if you want to visualize the progress with delay.\r\n\r\nusing UnityEngine;\r\nusing System.Collections;\r\n\r\nnamespace Kit.Physic\r\n{\r\n\tpublic class EdgeFinder : MonoBehaviour\r\n\t{\r\n\t\t[Header(\"Ray Setting\")]\r\n\t\t[SerializeField]\r\n\t\tint m_Density = 150;\r\n\t\t[SerializeField]\r\n\t\tfloat m_Distance = 10f;\r\n\t\t[SerializeField]\r\n\t\tfloat m_Threshold = float.Epsilon;\r\n\t\tconst float MAX_THRESHOLD = 1f;\r\n\r\n\t\t[Header(\"Physics\")]\r\n\t\t[SerializeField]\r\n\t\tLayerMask m_LayerMask = Physics.DefaultRaycastLayers;\r\n\t\t[SerializeField]\r\n\t\tQueryTriggerInteraction m_QueryTriggerInteraction = QueryTriggerInteraction.UseGlobal;\r\n\r\n\t\t[Header(\"Debug\")]\r\n\t\t[SerializeField]\r\n\t\tColor m_Color = Color.white;\r\n\t\t[SerializeField]\r\n\t\tColor m_HitColor = Color.red;\r\n#if BULLET_TIME\r\n\t[SerializeField] bool m_PreviewInEditor = false;\r\n\t[Range(-float.Epsilon, 0.1f)]\r\n\t[SerializeField] float m_WaitSec = 0.1f;\r\n#endif\r\n\r\n\t\tRayData[] m_Sensors;\r\n\t\tRayData m_Finder;\r\n\r\n\t\tvoid OnValidate()\r\n\t\t{\r\n\t\t\tm_Density = Mathf.Clamp(m_Density, 0, int.MaxValue);\r\n\t\t\tm_Distance = Mathf.Clamp(m_Distance, 0f, float.MaxValue);\r\n\t\t\tm_Threshold = Mathf.Clamp(m_Threshold, float.Epsilon, MAX_THRESHOLD);\r\n\t\t\tInit();\r\n\t\t}\r\n\r\n\t\tvoid Awake()\r\n\t\t{\r\n\t\t\tInit();\r\n\t\t}\r\n\r\n\t\tvoid Init()\r\n\t\t{\r\n\t\t\tm_Sensors = new RayData[m_Density];\r\n#if BULLET_TIME &amp;&amp; UNITY_EDITOR\r\n\t\tif (m_PreviewInEditor)\r\n\t\t{\r\n\t\t\tStopAllCoroutines();\r\n\t\t\tStart();\r\n\t\t}\r\n#else\r\n\t\t\tGetBisectionEdge(transform.position, transform.forward, transform.up, m_Threshold);\r\n#endif\r\n\t\t}\r\n\r\n\t\tpublic void OnDrawGizmos()\r\n\t\t{\r\n\t\t\tif (!Application.isPlaying)\r\n\t\t\t\tInit();\r\n\r\n\t\t\tfor (int i = 0; i &lt; m_Sensors.Length; i++)\r\n\t\t\t{\r\n\t\t\t\tif (!RayData.IsNullOrEmpty(m_Sensors[i]))\r\n\t\t\t\t\tm_Sensors[i].DrawGizmos(m_Color, m_HitColor);\r\n\t\t\t}\r\n\t\t}\r\n\r\n#if BULLET_TIME\r\n\tpublic void Start()\r\n\t{\r\n\t\tStartCoroutine(IntervalTrigger());\r\n\t}\r\n\tprivate IEnumerator IntervalTrigger()\r\n\t{\r\n\t\twhile (true)\r\n\t\t{\r\n\t\t\tyield return GetBisectionEdge(transform.position, transform.forward, transform.up, m_Threshold);\r\n\t\t}\r\n\t}\r\n#else\r\n\t\tvoid FixedUpdate()\r\n\t\t{\r\n\t\t\tGetBisectionEdge(transform.position, transform.forward, transform.up, m_Threshold);\r\n\t\t}\r\n#endif\r\n\r\n\t\tprivate void CacheRayResult(Vector3 point, Vector3 startDirection, Vector3 normal, bool raycastTest = false)\r\n\t\t{\r\n\t\t\tVector3 dir;\r\n\t\t\tfloat currAngle = 0f;\r\n\t\t\tfloat angleStep = 360f \/ m_Density;\r\n\t\t\tfor (int i = 0; i &lt; m_Density; i++)\r\n\t\t\t{\r\n\t\t\t\tdir = Quaternion.AngleAxis(currAngle, normal) * startDirection;\r\n\t\t\t\tm_Sensors[i].Update(point, dir, m_Distance);\r\n\t\t\t\tif (raycastTest)\r\n\t\t\t\t\tm_Sensors[i].Raycast(m_LayerMask, m_QueryTriggerInteraction);\r\n\t\t\t\tcurrAngle += angleStep;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\r\n#if BULLET_TIME\r\n\tprivate IEnumerator GetBisectionEdge(Vector3 point, Vector3 startDirection, Vector3 normal, float threshold)\r\n#else\r\n\t\tprivate void GetBisectionEdge(Vector3 point, Vector3 startDirection, Vector3 normal, float threshold)\r\n#endif\r\n\t\t{\r\n\t\t\tCacheRayResult(point, startDirection, normal, true);\r\n\r\n\t\t\tif (m_Density &lt;= 1)\r\n\t\t\t{\r\n\t\t\t\tDebug.Log(GetType().Name + \" at least two Raycast are required.\");\r\n\t\t\t}\r\n\t\t\telse if (m_Density &gt; 2)\r\n\t\t\t{\r\n\t\t\t\tint pt = m_Density, nextPt;\r\n\t\t\t\twhile (pt-- &gt; 0)\r\n\t\t\t\t{\r\n\t\t\t\t\tnextPt = Mathf.CeilToInt(Mathf.Repeat(pt - 1, m_Density));\r\n\t\t\t\t\tif (!m_Sensors[pt].hitted)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tm_Sensors[pt] = RayData.NONE;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (!RayData.IsNullOrEmpty(m_Sensors[nextPt]))\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\t\/\/ let's find the edge in between\r\n\t\t\t\t\t\tTryLocateBisectionEdge(ref m_Sensors[pt], ref m_Sensors[nextPt], ref m_Finder, m_LayerMask, m_QueryTriggerInteraction, threshold);\r\n\t\t\t\t\t}\r\n\r\n#if BULLET_TIME\r\n\t\t\t\tif (m_WaitSec &gt; 0f)\r\n\t\t\t\t\tyield return new WaitForSeconds(m_WaitSec);\r\n#endif\r\n\t\t\t\t}\r\n\t\t\t}\r\n#if BULLET_TIME\r\n\t\tyield return null;\r\n#endif\r\n\t\t}\r\n\r\n\t\t\/\/\/ &lt;summary&gt;Locate edge within two ray. limit by angle threshold&lt;\/summary&gt;\r\n\t\t\/\/\/ &lt;param name=\"start\"&gt;Start arc direction&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"end\"&gt;End arc direction&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"finder\"&gt;Cache Helper&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"layerMask\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"queryTriggerInteraction\"&gt;&lt;\/param&gt;\r\n\t\t\/\/\/ &lt;param name=\"threshold\"&gt;min angle&lt;\/param&gt;\r\n\t\tprivate void TryLocateBisectionEdge(ref RayData start, ref RayData end, ref RayData finder,\r\n\t\t\tint layerMask = Physics.DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal,\r\n\t\t\tfloat threshold = float.Epsilon)\r\n\t\t{\r\n\t\t\tif (start.origin != end.origin)\r\n\t\t\t{\r\n\t\t\t\tDebug.LogWarning(\"Start origin should always equal.\", this);\r\n\t\t\t\tstart.origin = end.origin;\r\n\t\t\t}\r\n\r\n\t\t\tif (start.IsHittingSameObject(end, includeNull: true))\r\n\t\t\t{\r\n\t\t\t\t\/\/ both hit same thing || nothing, may cause by m_Density too low, we don't care at this point.\r\n\t\t\t\tfinder.Reset();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tif (!start.hitted &amp;&amp; end.hitted)\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ hit something, and one of it are Empty.\r\n\t\t\t\t\t\/\/ to combine case, alway making start as Empty. (locate direction will change)\r\n\t\t\t\t\tfinder.Update(start, hitReferences: false);\r\n\t\t\t\t\tstart.Update(end, hitReferences: false);\r\n\t\t\t\t\tend.Update(finder, hitReferences: false);\r\n\t\t\t\t}\r\n\t\t\t\t\/\/ else hit different object, no change.\r\n\r\n\t\t\t\t\/\/ reach angle threshold.\r\n\t\t\t\tthreshold = Mathf.Clamp(threshold, float.Epsilon, MAX_THRESHOLD);\r\n\t\t\t\tif (threshold &gt; Vector3.Angle(start.direction, end.direction))\r\n\t\t\t\t{\r\n\t\t\t\t\tfinder.Reset();\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t\/\/ Bisection\r\n\t\t\t\t\/\/ recursive logic : assume origin point of start &amp; end are equal.\r\n\t\t\t\tfinder.Update(start.origin, Vector3.LerpUnclamped(start.direction, end.direction, .5f), start.distance);\r\n\t\t\t\tif (finder.Raycast(layerMask, queryTriggerInteraction))\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ found the object, Move \"end\" closer\r\n\t\t\t\t\tend.Update(finder, hitReferences: true);\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\t\/\/ not found, Move \"start\" closer\r\n\t\t\t\t\tend.Update(finder, hitReferences: true);\r\n\t\t\t\t}\r\n\t\t\t\tTryLocateBisectionEdge(ref start, ref end, ref finder, layerMask, queryTriggerInteraction, threshold);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>\u4f7f\u7528 Recursive \u53ca pass by reference \u7684\u65b9\u5f0f\uff0c\u63a5\u8fd1 Zero Garbage collect \u4f86\u9054\u5230\u76ee\u7684. \u6e2c\u8a66\u53ef\u898b 360 \u652f\u5c04\u7dda\u5916\u52a0\u4e8c\u5206\u641c\u5c0b\u5e73\u5747\u82b1 1.17ms \u7522\u751f \u00a0100kb \u62c9\u573e.<\/p>\n<p>\u4e3b\u8981\u7684 GC \u554f\u984c\u5b8c\u5168\u7522\u751f\u5728 GetHashCode() \u7684\u914d\u5c0d\u4e0a(\u55ef&#8230;\u6216\u8a31\u6211\u53ef\u4ee5\u5229\u7528 cache \u518d\u6e1b\u5c11\u4e00 30% ? \u9019\u6709\u5f85\u8a66\u9a57)<\/p>\n<p>\u57fa\u672c\u7684\u4e8c\u5206\u6cd5\u5df2\u7d93\u662f 0 gc.<\/p>\n<p>\u800c\u672c\u6b21\u7684\u6e2c\u8a66\u5927\u6982\u5728 \u5bc6\u5ea6(Density) \u8a2d\u5728 75, 180, 360 \u6709\u76f8\u5c0d\u4f73\u7684 C\/P \u503c.<\/p>\n<p>\u66f4\u591a\u7684\u5bc6\u5ea6\u5c0d\u6548\u679c\u6c92\u6709\u592a\u5927\u63d0\u5347\u800c\u4e14\u6d6a\u8cbb\u5927\u91cf\u7684 CPU \u8cc7\u6e90\u3002<\/p>\n<p>\u5982\u679c\u6709\u66f4\u597d\u6216\u53ef\u6539\u9032\u7684\u5730\u65b9\uff0c\u6b61\u8fce\u7559\u8a00\u8a0e\u8ad6\u3002<\/p>\n<p>Enjoy, happy coding<\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u672c\u6b21\u9805\u76ee\uff0c\u4e3b\u8981\u6982\u5ff5\u5f9e\u9019\u88e1\u5f97\u5230\u555f\u767c :\u00a0https:\/\/tedsieblog.wordpress.co &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,78,11],"tags":[16,20,43],"class_list":["post-1729","post","type-post","status-publish","format-standard","hentry","category-c","category-math","category-unity3d","tag-c-2","tag-editor","tag-unity3d-2"],"_links":{"self":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1729","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=1729"}],"version-history":[{"count":0,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/posts\/1729\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/media?parent=1729"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/categories?post=1729"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.clonefactor.com\/wordpress\/wp-json\/wp\/v2\/tags?post=1729"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}