Эх сурвалжийг харах

提交statesync demo部分代码,暂未编译通过

tanghai 1 жил өмнө
parent
commit
d9247f0913
100 өөрчлөгдсөн 6019 нэмэгдсэн , 4 устгасан
  1. 15 0
      ET.sln
  2. 2 1
      Unity/Assets/Scripts/Hotfix/Unity.Hotfix.asmdef
  3. 2 1
      Unity/Assets/Scripts/Model/Unity.Model.asmdef
  4. 1 1
      Unity/Packages/com.et.core/Scripts/Model~/Share/TimerInvokeType.cs
  5. 1 1
      Unity/Packages/com.et.dotrecast/Scripts.meta
  6. 8 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast.meta
  7. 8 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core.meta
  8. 49 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/CollectionExtensions.cs
  9. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/CollectionExtensions.cs.meta
  10. 8 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Compression.meta
  11. 693 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Compression/FastLZ.cs
  12. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Compression/FastLZ.cs.meta
  13. 24 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/FRand.cs
  14. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/FRand.cs.meta
  15. 30 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcCompressor.cs
  16. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcCompressor.cs.meta
  17. 7 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcRand.cs
  18. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcRand.cs.meta
  19. 132 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Intersections.cs
  20. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Intersections.cs.meta
  21. 33 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Loader.cs
  22. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Loader.cs.meta
  23. 20 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAnonymousDisposable.cs
  24. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAnonymousDisposable.cs.meta
  25. 42 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcArrayUtils.cs
  26. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcArrayUtils.cs.meta
  27. 19 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicBoolean.cs
  28. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicBoolean.cs.meta
  29. 29 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicFloat.cs
  30. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicFloat.cs.meta
  31. 65 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicInteger.cs
  32. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicInteger.cs.meta
  33. 53 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicLong.cs
  34. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicLong.cs.meta
  35. 142 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteBuffer.cs
  36. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteBuffer.cs.meta
  37. 9 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteOrder.cs
  38. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteOrder.cs.meta
  39. 102 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcConvexUtils.cs
  40. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcConvexUtils.cs.meta
  41. 9 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcEdge.cs
  42. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcEdge.cs.meta
  43. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcFrequency.cs
  44. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcFrequency.cs.meta
  45. 10 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcHashCodes.cs
  46. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcHashCodes.cs.meta
  47. 83 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Enumerable.cs
  48. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Enumerable.cs.meta
  49. 69 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Listable.cs
  50. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Listable.cs.meta
  51. 19 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Minimal.cs
  52. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Minimal.cs.meta
  53. 48 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.cs
  54. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.cs.meta
  55. 46 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMath.cs
  56. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMath.cs.meta
  57. 22 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMatrix4X4.cs
  58. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMatrix4X4.cs.meta
  59. 20 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSegmentVert.cs
  60. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSegmentVert.cs.meta
  61. 98 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSortedQueue.cs
  62. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSortedQueue.cs.meta
  63. 70 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetry.cs
  64. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetry.cs.meta
  65. 18 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetryTick.cs
  66. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetryTick.cs.meta
  67. 109 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTimerLabel.cs
  68. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTimerLabel.cs.meta
  69. 66 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec2f.cs
  70. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec2f.cs.meta
  71. 645 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec3f.cs
  72. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec3f.cs.meta
  73. 8 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd.meta
  74. 1349 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowd.cs
  75. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowd.cs.meta
  76. 217 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgent.cs
  77. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgent.cs.meta
  78. 34 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs
  79. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs.meta
  80. 74 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentParams.cs
  81. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentParams.cs.meta
  82. 15 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentState.cs
  83. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentState.cs.meta
  84. 78 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdConfig.cs
  85. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdConfig.cs.meta
  86. 20 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdNeighbour.cs
  87. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdNeighbour.cs.meta
  88. 102 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTelemetry.cs
  89. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTelemetry.cs.meta
  90. 27 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTimerLabel.cs
  91. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTimerLabel.cs.meta
  92. 172 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtLocalBoundary.cs
  93. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtLocalBoundary.cs.meta
  94. 13 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtMoveRequestState.cs
  95. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtMoveRequestState.cs.meta
  96. 51 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceParams.cs
  97. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceParams.cs.meta
  98. 501 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceQuery.cs
  99. 11 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceQuery.cs.meta
  100. 26 0
      Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleCircle.cs

+ 15 - 0
ET.sln

@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.YooAssets", "Unity\Un
 EndProject
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Core.Editor", "Unity\Unity.Core.Editor.csproj", "{F0862CDB-B856-8D29-9197-5F73E6754E3A}"
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.Core.Editor", "Unity\Unity.Core.Editor.csproj", "{F0862CDB-B856-8D29-9197-5F73E6754E3A}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unity.DotRecast", "Unity\Unity.DotRecast.csproj", "{272DFC1D-7896-1A26-40B1-310C03278670}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -271,6 +273,18 @@ Global
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A}.Release|x64.Build.0 = Debug|Any CPU
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A}.Release|x64.Build.0 = Debug|Any CPU
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A}.Release|x86.ActiveCfg = Debug|Any CPU
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A}.Release|x86.ActiveCfg = Debug|Any CPU
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A}.Release|x86.Build.0 = Debug|Any CPU
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A}.Release|x86.Build.0 = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Debug|x64.Build.0 = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Debug|x86.Build.0 = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Release|Any CPU.Build.0 = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Release|x64.ActiveCfg = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Release|x64.Build.0 = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Release|x86.ActiveCfg = Debug|Any CPU
+		{272DFC1D-7896-1A26-40B1-310C03278670}.Release|x86.Build.0 = Debug|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -294,6 +308,7 @@ Global
 		{7053CEF4-1559-47C5-80FF-FBE7B02E50E1} = {1272AF7B-A962-4BA4-8A9C-FFA7E131A0AC}
 		{7053CEF4-1559-47C5-80FF-FBE7B02E50E1} = {1272AF7B-A962-4BA4-8A9C-FFA7E131A0AC}
 		{200AF2E9-4594-CE27-E3C1-E8C7A27AEDA4} = {914C77C9-212A-4DD0-8D9A-074620E77FAA}
 		{200AF2E9-4594-CE27-E3C1-E8C7A27AEDA4} = {914C77C9-212A-4DD0-8D9A-074620E77FAA}
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A} = {914C77C9-212A-4DD0-8D9A-074620E77FAA}
 		{F0862CDB-B856-8D29-9197-5F73E6754E3A} = {914C77C9-212A-4DD0-8D9A-074620E77FAA}
+		{272DFC1D-7896-1A26-40B1-310C03278670} = {914C77C9-212A-4DD0-8D9A-074620E77FAA}
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {EABC01E3-3EB5-47EF-B46E-AAD8BB3585F1}
 		SolutionGuid = {EABC01E3-3EB5-47EF-B46E-AAD8BB3585F1}

+ 2 - 1
Unity/Assets/Scripts/Hotfix/Unity.Hotfix.asmdef

@@ -6,7 +6,8 @@
         "Unity.Mathematics",
         "Unity.Mathematics",
         "MemoryPack",
         "MemoryPack",
         "Unity.Model",
         "Unity.Model",
-        "Unity.ThirdParty"
+        "Unity.ThirdParty",
+        "Unity.Loader"
     ],
     ],
     "includePlatforms": [],
     "includePlatforms": [],
     "excludePlatforms": [],
     "excludePlatforms": [],

+ 2 - 1
Unity/Assets/Scripts/Model/Unity.Model.asmdef

@@ -5,7 +5,8 @@
         "Unity.ThirdParty",
         "Unity.ThirdParty",
         "Unity.Core",
         "Unity.Core",
         "Unity.Mathematics",
         "Unity.Mathematics",
-        "MemoryPack"
+        "MemoryPack",
+        "Unity.DotRecast"
     ],
     ],
     "includePlatforms": [],
     "includePlatforms": [],
     "excludePlatforms": [],
     "excludePlatforms": [],

+ 1 - 1
Unity/Packages/com.et.core/Scripts/Model~/Share/TimerInvokeType.cs

@@ -1,7 +1,7 @@
 namespace ET
 namespace ET
 {
 {
     [UniqueId(100, 10000)]
     [UniqueId(100, 10000)]
-    public static class TimerInvokeType
+    public static partial class TimerInvokeType
     {
     {
         // 框架层100-200,逻辑层的timer type从200起
         // 框架层100-200,逻辑层的timer type从200起
         public const int WaitTimer = 100;
         public const int WaitTimer = 100;

+ 1 - 1
Unity/Packages/com.et.statesync/Scripts/StateSync.meta → Unity/Packages/com.et.dotrecast/Scripts.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
 fileFormatVersion: 2
-guid: e85dfef56fb0a5e4c87bd2e2cb354509
+guid: ba5062b11e8561f4390b4e4effc3b32b
 folderAsset: yes
 folderAsset: yes
 DefaultImporter:
 DefaultImporter:
   externalObjects: {}
   externalObjects: {}

+ 8 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 11cab9500b7f85949b91fdfefb25716a
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 98e3e71e3ee14c943be8da6c29b2741b
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 49 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/CollectionExtensions.cs

@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+
+namespace DotRecast.Core
+{
+    public static class CollectionExtensions
+    {
+        /// Sorts the given data in-place using insertion sort.
+        ///
+        /// @param	data		The data to sort
+        /// @param	dataLength	The number of elements in @p data
+        public static void InsertSort(this int[] data)
+        {
+            for (int valueIndex = 1; valueIndex < data.Length; valueIndex++)
+            {
+                int value = data[valueIndex];
+                int insertionIndex;
+                for (insertionIndex = valueIndex - 1; insertionIndex >= 0 && data[insertionIndex] > value; insertionIndex--)
+                {
+                    // Shift over values
+                    data[insertionIndex + 1] = data[insertionIndex];
+                }
+		
+                // Insert the value in sorted order.
+                data[insertionIndex + 1] = value;
+            }
+        }
+            
+        public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action)
+        {
+            foreach (var item in collection)
+            {
+                action.Invoke(item);
+            }
+        }
+
+        public static void Shuffle<T>(this IList<T> list)
+        {
+            Random random = new Random();
+            int n = list.Count;
+            while (n > 1)
+            {
+                n--;
+                int k = random.Next(n + 1);
+                (list[k], list[n]) = (list[n], list[k]);
+            }
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/CollectionExtensions.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9e57c133f13bcf945868ae5479bf6d54
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Compression.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c51fed769e56d954b9eecda10305d42d
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 693 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Compression/FastLZ.cs

@@ -0,0 +1,693 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+using System;
+
+namespace DotRecast.Core.Compression
+{
+    /**
+     * Core of FastLZ compression algorithm.
+     *
+     * This class provides methods for compression and decompression of buffers and saves
+     * constants which use by FastLzFrameEncoder and FastLzFrameDecoder.
+     *
+     * This is refactored code of <a href="https://code.google.com/p/jfastlz/">jfastlz</a>
+     * library written by William Kinney.
+     */
+    public static class FastLZ
+    {
+        private const int MAX_DISTANCE = 8191;
+        private const int MAX_FARDISTANCE = 65535 + MAX_DISTANCE - 1;
+
+        private const int HASH_LOG = 13;
+        private const int HASH_SIZE = 1 << HASH_LOG; // 8192
+        private const int HASH_MASK = HASH_SIZE - 1;
+
+        private const int MAX_COPY = 32;
+        private const int MAX_LEN = 256 + 8;
+
+        private const int MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 = 1024 * 64;
+
+        private const int MAGIC_NUMBER = 'F' << 16 | 'L' << 8 | 'Z';
+
+        private const byte BLOCK_TYPE_NON_COMPRESSED = 0x00;
+        private const byte BLOCK_TYPE_COMPRESSED = 0x01;
+        private const byte BLOCK_WITHOUT_CHECKSUM = 0x00;
+        private const byte BLOCK_WITH_CHECKSUM = 0x10;
+
+        private const int OPTIONS_OFFSET = 3;
+        private const int CHECKSUM_OFFSET = 4;
+
+        private const int MAX_CHUNK_LENGTH = 0xFFFF;
+
+        /**
+     * Do not call {@link #Compress(byte[], int, int, byte[], int, int)} for input buffers
+     * which length less than this value.
+     */
+        private const int MIN_LENGTH_TO_COMPRESSION = 32;
+
+        /**
+     * In this case {@link #Compress(byte[], int, int, byte[], int, int)} will choose level
+     * automatically depending on the length of the input buffer. If length less than
+     * {@link #MIN_RECOMENDED_LENGTH_FOR_LEVEL_2} {@link #LEVEL_1} will be choosen,
+     * otherwise {@link #LEVEL_2}.
+     */
+        private const int LEVEL_AUTO = 0;
+
+        /**
+     * Level 1 is the fastest compression and generally useful for short data.
+     */
+        private const int LEVEL_1 = 1;
+
+        /**
+     * Level 2 is slightly slower but it gives better compression ratio.
+     */
+        private const int LEVEL_2 = 2;
+
+        /**
+     * The output buffer must be at least 6% larger than the input buffer and can not be smaller than 66 bytes.
+     * @param inputLength length of input buffer
+     * @return Maximum output buffer length
+     */
+        public static int CalculateOutputBufferLength(int inputLength)
+        {
+            int tempOutputLength = (int)(inputLength * 1.06);
+            return Math.Max(tempOutputLength, 66);
+        }
+
+        /**
+     * Compress a block of data in the input buffer and returns the size of compressed block.
+     * The size of input buffer is specified by length. The minimum input buffer size is 32.
+     *
+     * If the input is not compressible, the return value might be larger than length (input buffer size).
+     */
+        public static int Compress(byte[] input, int inOffset, int inLength,
+            byte[] output, int outOffset, int proposedLevel)
+        {
+            int level;
+            if (proposedLevel == LEVEL_AUTO)
+            {
+                level = inLength < MIN_RECOMENDED_LENGTH_FOR_LEVEL_2 ? LEVEL_1 : LEVEL_2;
+            }
+            else
+            {
+                level = proposedLevel;
+            }
+
+            int ip = 0;
+            int ipBound = ip + inLength - 2;
+            int ipLimit = ip + inLength - 12;
+
+            int op = 0;
+
+            // const flzuint8* htab[HASH_SIZE];
+            int[] htab = new int[HASH_SIZE];
+            // const flzuint8** hslot;
+            int hslot;
+            // flzuint32 hval;
+            // int OK b/c address starting from 0
+            int hval;
+            // flzuint32 copy;
+            // int OK b/c address starting from 0
+            int copy;
+
+            /* sanity check */
+            if (inLength < 4)
+            {
+                if (inLength != 0)
+                {
+                    // *op++ = length-1;
+                    output[outOffset + op++] = (byte)(inLength - 1);
+                    ipBound++;
+                    while (ip <= ipBound)
+                    {
+                        output[outOffset + op++] = input[inOffset + ip++];
+                    }
+
+                    return inLength + 1;
+                }
+
+                // else
+                return 0;
+            }
+
+            /* initializes hash table */
+            //  for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
+            for (hslot = 0; hslot < HASH_SIZE; hslot++)
+            {
+                //*hslot = ip;
+                htab[hslot] = ip;
+            }
+
+            /* we start with literal copy */
+            copy = 2;
+            output[outOffset + op++] = (byte)(MAX_COPY - 1);
+            output[outOffset + op++] = input[inOffset + ip++];
+            output[outOffset + op++] = input[inOffset + ip++];
+
+            /* main loop */
+            while (ip < ipLimit)
+            {
+                int refs = 0;
+
+                long distance = 0;
+
+                /* minimum match length */
+                // flzuint32 len = 3;
+                // int OK b/c len is 0 and octal based
+                int len = 3;
+
+                /* comparison starting-point */
+                int anchor = ip;
+
+                bool matchLabel = false;
+
+                /* check for a run */
+                if (level == LEVEL_2)
+                {
+                    //If(ip[0] == ip[-1] && FASTLZ_READU16(ip-1)==FASTLZ_READU16(ip+1))
+                    if (input[inOffset + ip] == input[inOffset + ip - 1] &&
+                        ReadU16(input, inOffset + ip - 1) == ReadU16(input, inOffset + ip + 1))
+                    {
+                        distance = 1;
+                        ip += 3;
+                        refs = anchor - 1 + 3;
+
+                        /*
+                         * goto match;
+                         */
+                        matchLabel = true;
+                    }
+                }
+
+                if (!matchLabel)
+                {
+                    /* find potential match */
+                    // HASH_FUNCTION(hval,ip);
+                    hval = HashFunction(input, inOffset + ip);
+                    // hslot = htab + hval;
+                    hslot = hval;
+                    // refs = htab[hval];
+                    refs = htab[hval];
+
+                    /* calculate distance to the match */
+                    distance = anchor - refs;
+
+                    /* update hash table */
+                    //*hslot = anchor;
+                    htab[hslot] = anchor;
+
+                    /* is this a match? check the first 3 bytes */
+                    if (distance == 0
+                        || (level == LEVEL_1 ? distance >= MAX_DISTANCE : distance >= MAX_FARDISTANCE)
+                        || input[inOffset + refs++] != input[inOffset + ip++]
+                        || input[inOffset + refs++] != input[inOffset + ip++]
+                        || input[inOffset + refs++] != input[inOffset + ip++])
+                    {
+                        /*
+                         * goto literal;
+                         */
+                        output[outOffset + op++] = input[inOffset + anchor++];
+                        ip = anchor;
+                        copy++;
+                        if (copy == MAX_COPY)
+                        {
+                            copy = 0;
+                            output[outOffset + op++] = (byte)(MAX_COPY - 1);
+                        }
+
+                        continue;
+                    }
+
+                    if (level == LEVEL_2)
+                    {
+                        /* far, needs at least 5-byte match */
+                        if (distance >= MAX_DISTANCE)
+                        {
+                            if (input[inOffset + ip++] != input[inOffset + refs++]
+                                || input[inOffset + ip++] != input[inOffset + refs++])
+                            {
+                                /*
+                                 * goto literal;
+                                 */
+                                output[outOffset + op++] = input[inOffset + anchor++];
+                                ip = anchor;
+                                copy++;
+                                if (copy == MAX_COPY)
+                                {
+                                    copy = 0;
+                                    output[outOffset + op++] = (byte)(MAX_COPY - 1);
+                                }
+
+                                continue;
+                            }
+
+                            len += 2;
+                        }
+                    }
+                } // end If(!matchLabel)
+
+                /*
+                 * match:
+                 */
+                /* last matched byte */
+                ip = anchor + len;
+
+                /* distance is biased */
+                distance--;
+
+                if (distance == 0)
+                {
+                    /* zero distance means a run */
+                    //flzuint8 x = ip[-1];
+                    byte x = input[inOffset + ip - 1];
+                    while (ip < ipBound)
+                    {
+                        if (input[inOffset + refs++] != x)
+                        {
+                            break;
+                        }
+                        else
+                        {
+                            ip++;
+                        }
+                    }
+                }
+                else
+                {
+                    for (;;)
+                    {
+                        /* safe because the outer check against ip limit */
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        if (input[inOffset + refs++] != input[inOffset + ip++])
+                        {
+                            break;
+                        }
+
+                        while (ip < ipBound)
+                        {
+                            if (input[inOffset + refs++] != input[inOffset + ip++])
+                            {
+                                break;
+                            }
+                        }
+
+                        break;
+                    }
+                }
+
+                /* if we have copied something, adjust the copy count */
+                if (copy != 0)
+                {
+                    /* copy is biased, '0' means 1 byte copy */
+                    // *(op-copy-1) = copy-1;
+                    output[outOffset + op - copy - 1] = (byte)(copy - 1);
+                }
+                else
+                {
+                    /* back, to overwrite the copy count */
+                    op--;
+                }
+
+                /* reset literal counter */
+                copy = 0;
+
+                /* length is biased, '1' means a match of 3 bytes */
+                ip -= 3;
+                len = ip - anchor;
+
+                /* encode the match */
+                if (level == LEVEL_2)
+                {
+                    if (distance < MAX_DISTANCE)
+                    {
+                        if (len < 7)
+                        {
+                            output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
+                            output[outOffset + op++] = (byte)(distance & 255);
+                        }
+                        else
+                        {
+                            output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
+                            for (len -= 7; len >= 255; len -= 255)
+                            {
+                                output[outOffset + op++] = (byte)255;
+                            }
+
+                            output[outOffset + op++] = (byte)len;
+                            output[outOffset + op++] = (byte)(distance & 255);
+                        }
+                    }
+                    else
+                    {
+                        /* far away, but not yet in the another galaxy... */
+                        if (len < 7)
+                        {
+                            distance -= MAX_DISTANCE;
+                            output[outOffset + op++] = (byte)((len << 5) + 31);
+                            output[outOffset + op++] = (byte)255;
+                            output[outOffset + op++] = (byte)((ulong)distance >> 8);
+                            output[outOffset + op++] = (byte)(distance & 255);
+                        }
+                        else
+                        {
+                            distance -= MAX_DISTANCE;
+                            output[outOffset + op++] = (byte)((7 << 5) + 31);
+                            for (len -= 7; len >= 255; len -= 255)
+                            {
+                                output[outOffset + op++] = (byte)255;
+                            }
+
+                            output[outOffset + op++] = (byte)len;
+                            output[outOffset + op++] = (byte)255;
+                            output[outOffset + op++] = (byte)((ulong)distance >> 8);
+                            output[outOffset + op++] = (byte)(distance & 255);
+                        }
+                    }
+                }
+                else
+                {
+                    if (len > MAX_LEN - 2)
+                    {
+                        while (len > MAX_LEN - 2)
+                        {
+                            output[outOffset + op++] = (byte)((7 << 5) + ((ulong)distance >> 8));
+                            output[outOffset + op++] = (byte)(MAX_LEN - 2 - 7 - 2);
+                            output[outOffset + op++] = (byte)(distance & 255);
+                            len -= MAX_LEN - 2;
+                        }
+                    }
+
+                    if (len < 7)
+                    {
+                        output[outOffset + op++] = (byte)((len << 5) + (int)((ulong)distance >> 8));
+                        output[outOffset + op++] = (byte)(distance & 255);
+                    }
+                    else
+                    {
+                        output[outOffset + op++] = (byte)((7 << 5) + (int)((ulong)distance >> 8));
+                        output[outOffset + op++] = (byte)(len - 7);
+                        output[outOffset + op++] = (byte)(distance & 255);
+                    }
+                }
+
+                /* update the hash at match boundary */
+                //HASH_FUNCTION(hval,ip);
+                hval = HashFunction(input, inOffset + ip);
+                htab[hval] = ip++;
+
+                //HASH_FUNCTION(hval,ip);
+                hval = HashFunction(input, inOffset + ip);
+                htab[hval] = ip++;
+
+                /* assuming literal copy */
+                output[outOffset + op++] = (byte)(MAX_COPY - 1);
+
+                continue;
+
+                // Moved to be inline, with a 'continue'
+                /*
+                 * literal:
+                 *
+                  output[outOffset + op++] = input[inOffset + anchor++];
+                  ip = anchor;
+                  copy++;
+                  If(copy == MAX_COPY){
+                    copy = 0;
+                    output[outOffset + op++] = MAX_COPY-1;
+                  }
+                */
+            }
+
+            /* left-over as literal copy */
+            ipBound++;
+            while (ip <= ipBound)
+            {
+                output[outOffset + op++] = input[inOffset + ip++];
+                copy++;
+                if (copy == MAX_COPY)
+                {
+                    copy = 0;
+                    output[outOffset + op++] = (byte)(MAX_COPY - 1);
+                }
+            }
+
+            /* if we have copied something, adjust the copy length */
+            if (copy != 0)
+            {
+                //*(op-copy-1) = copy-1;
+                output[outOffset + op - copy - 1] = (byte)(copy - 1);
+            }
+            else
+            {
+                op--;
+            }
+
+            if (level == LEVEL_2)
+            {
+                /* marker for fastlz2 */
+                output[outOffset] |= 1 << 5;
+            }
+
+            return op;
+        }
+
+        /**
+     * Decompress a block of compressed data and returns the size of the decompressed block.
+     * If error occurs, e.g. the compressed data is corrupted or the output buffer is not large
+     * enough, then 0 (zero) will be returned instead.
+     *
+     * Decompression is memory safe and guaranteed not to write the output buffer
+     * more than what is specified in outLength.
+     */
+        public static int Decompress(byte[] input, int inOffset, int inLength,
+            byte[] output, int outOffset, int outLength)
+        {
+            //int level = ((*(const flzuint8*)input) >> 5) + 1;
+            int level = (input[inOffset] >> 5) + 1;
+            if (level != LEVEL_1 && level != LEVEL_2)
+            {
+                throw new Exception($"invalid level: {level} (expected: {LEVEL_1} or {LEVEL_2})");
+            }
+
+            // const flzuint8* ip = (const flzuint8*) input;
+            int ip = 0;
+            // flzuint8* op = (flzuint8*) output;
+            int op = 0;
+            // flzuint32 ctrl = (*ip++) & 31;
+            long ctrl = input[inOffset + ip++] & 31;
+
+            int loop = 1;
+            do
+            {
+                //  const flzuint8* refs = op;
+                int refs = op;
+                // flzuint32 len = ctrl >> 5;
+                long len = ctrl >> 5;
+                // flzuint32 ofs = (ctrl & 31) << 8;
+                long ofs = (ctrl & 31) << 8;
+
+                if (ctrl >= 32)
+                {
+                    len--;
+                    // refs -= ofs;
+                    refs -= (int)ofs;
+
+                    int code;
+                    if (len == 6)
+                    {
+                        if (level == LEVEL_1)
+                        {
+                            // len += *ip++;
+                            len += input[inOffset + ip++] & 0xFF;
+                        }
+                        else
+                        {
+                            do
+                            {
+                                code = input[inOffset + ip++] & 0xFF;
+                                len += code;
+                            } while (code == 255);
+                        }
+                    }
+
+                    if (level == LEVEL_1)
+                    {
+                        //  refs -= *ip++;
+                        refs -= input[inOffset + ip++] & 0xFF;
+                    }
+                    else
+                    {
+                        code = input[inOffset + ip++] & 0xFF;
+                        refs -= code;
+
+                        /* match from 16-bit distance */
+                        // If(FASTLZ_UNEXPECT_CONDITIONAL(code==255))
+                        // If(FASTLZ_EXPECT_CONDITIONAL(ofs==(31 << 8)))
+                        if (code == 255 && ofs == 31 << 8)
+                        {
+                            ofs = (input[inOffset + ip++] & 0xFF) << 8;
+                            ofs += input[inOffset + ip++] & 0xFF;
+
+                            refs = (int)(op - ofs - MAX_DISTANCE);
+                        }
+                    }
+
+                    // if the output index + length of Block(?) + 3(?) is over the output limit?
+                    if (op + len + 3 > outLength)
+                    {
+                        return 0;
+                    }
+
+                    // if (FASTLZ_UNEXPECT_CONDITIONAL(refs-1 < (flzuint8 *)output))
+                    // if the address space of refs-1 is < the address of output?
+                    // if we are still at the beginning of the output address?
+                    if (refs - 1 < 0)
+                    {
+                        return 0;
+                    }
+
+                    if (ip < inLength)
+                    {
+                        ctrl = input[inOffset + ip++] & 0xFF;
+                    }
+                    else
+                    {
+                        loop = 0;
+                    }
+
+                    if (refs == op)
+                    {
+                        /* optimize copy for a run */
+                        // flzuint8 b = refs[-1];
+                        byte b = output[outOffset + refs - 1];
+                        output[outOffset + op++] = b;
+                        output[outOffset + op++] = b;
+                        output[outOffset + op++] = b;
+                        while (len != 0)
+                        {
+                            output[outOffset + op++] = b;
+                            --len;
+                        }
+                    }
+                    else
+                    {
+                        /* copy from reference */
+                        refs--;
+
+                        // *op++ = *refs++;
+                        output[outOffset + op++] = output[outOffset + refs++];
+                        output[outOffset + op++] = output[outOffset + refs++];
+                        output[outOffset + op++] = output[outOffset + refs++];
+
+                        while (len != 0)
+                        {
+                            output[outOffset + op++] = output[outOffset + refs++];
+                            --len;
+                        }
+                    }
+                }
+                else
+                {
+                    ctrl++;
+
+                    if (op + ctrl > outLength)
+                    {
+                        return 0;
+                    }
+
+                    if (ip + ctrl > inLength)
+                    {
+                        return 0;
+                    }
+
+                    //*op++ = *ip++;
+                    output[outOffset + op++] = input[inOffset + ip++];
+
+                    for (--ctrl; ctrl != 0; ctrl--)
+                    {
+                        // *op++ = *ip++;
+                        output[outOffset + op++] = input[inOffset + ip++];
+                    }
+
+                    loop = ip < inLength ? 1 : 0;
+                    if (loop != 0)
+                    {
+                        //  ctrl = *ip++;
+                        ctrl = input[inOffset + ip++] & 0xFF;
+                    }
+                }
+
+                // While(FASTLZ_EXPECT_CONDITIONAL(loop));
+            } while (loop != 0);
+
+            //  return op - (flzuint8*)output;
+            return op;
+        }
+
+        private static int HashFunction(byte[] p, int offset)
+        {
+            int v = ReadU16(p, offset);
+            v ^= ReadU16(p, offset + 1) ^ v >> 16 - HASH_LOG;
+            v &= HASH_MASK;
+            return v;
+        }
+
+        private static int ReadU16(byte[] data, int offset)
+        {
+            if (offset + 1 >= data.Length)
+            {
+                return data[offset] & 0xff;
+            }
+
+            return (data[offset + 1] & 0xff) << 8 | data[offset] & 0xff;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Compression/FastLZ.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9d47e3fc558e44c4f99792680b29d819
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 24 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/FRand.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace DotRecast.Core
+{
+    public class FRand : IRcRand
+    {
+        private readonly Random _r;
+
+        public FRand()
+        {
+            _r = new Random();
+        }
+
+        public FRand(long seed)
+        {
+            _r = new Random((int)seed); // TODO : 랜덤 시드 확인 필요
+        }
+
+        public float Next()
+        {
+            return (float)_r.NextDouble();
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/FRand.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5b6f6e61e150d0847b39bd89468d783c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 30 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcCompressor.cs

@@ -0,0 +1,30 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+namespace DotRecast.Core
+{
+    public interface IRcCompressor
+    {
+        byte[] Decompress(byte[] data);
+        byte[] Decompress(byte[] buf, int offset, int len, int outputlen);
+
+        byte[] Compress(byte[] buf);
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcCompressor.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0d704211145117b48bacd1234f3b2639
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 7 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcRand.cs

@@ -0,0 +1,7 @@
+namespace DotRecast.Core
+{
+    public interface IRcRand
+    {
+        float Next();
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/IRcRand.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4ac469eb5c44f2b43b4c925d3fab86f3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 132 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Intersections.cs

@@ -0,0 +1,132 @@
+/*
+recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+
+namespace DotRecast.Core
+{
+    public static class Intersections
+    {
+        public static bool IntersectSegmentTriangle(RcVec3f sp, RcVec3f sq, RcVec3f a, RcVec3f b, RcVec3f c, out float t)
+        {
+            t = 0;
+            float v, w;
+            RcVec3f ab = b.Subtract(a);
+            RcVec3f ac = c.Subtract(a);
+            RcVec3f qp = sp.Subtract(sq);
+
+            // Compute triangle normal. Can be precalculated or cached if
+            // intersecting multiple segments against the same triangle
+            RcVec3f norm = RcVec3f.Cross(ab, ac);
+
+            // Compute denominator d. If d <= 0, segment is parallel to or points
+            // away from triangle, so exit early
+            float d = RcVec3f.Dot(qp, norm);
+            if (d <= 0.0f)
+            {
+                return false;
+            }
+
+            // Compute intersection t value of pq with plane of triangle. A ray
+            // intersects iff 0 <= t. Segment intersects iff 0 <= t <= 1. Delay
+            // dividing by d until intersection has been found to pierce triangle
+            RcVec3f ap = sp.Subtract(a);
+            t = RcVec3f.Dot(ap, norm);
+            if (t < 0.0f)
+            {
+                return false;
+            }
+
+            if (t > d)
+            {
+                return false; // For segment; exclude this code line for a ray test
+            }
+
+            // Compute barycentric coordinate components and test if within bounds
+            RcVec3f e = RcVec3f.Cross(qp, ap);
+            v = RcVec3f.Dot(ac, e);
+            if (v < 0.0f || v > d)
+            {
+                return false;
+            }
+
+            w = -RcVec3f.Dot(ab, e);
+            if (w < 0.0f || v + w > d)
+            {
+                return false;
+            }
+
+            // Segment/ray intersects triangle. Perform delayed division
+            t /= d;
+
+            return true;
+        }
+
+        public static bool IsectSegAABB(RcVec3f sp, RcVec3f sq, RcVec3f amin, RcVec3f amax, out float tmin, out float tmax)
+        {
+            const float EPS = 1e-6f;
+
+            RcVec3f d = new RcVec3f();
+            d.x = sq.x - sp.x;
+            d.y = sq.y - sp.y;
+            d.z = sq.z - sp.z;
+            tmin = 0.0f;
+            tmax = float.MaxValue;
+
+            for (int i = 0; i < 3; i++)
+            {
+                if (Math.Abs(d[i]) < EPS)
+                {
+                    if (sp[i] < amin[i] || sp[i] > amax[i])
+                    {
+                        return false;
+                    }
+                }
+                else
+                {
+                    float ood = 1.0f / d[i];
+                    float t1 = (amin[i] - sp[i]) * ood;
+                    float t2 = (amax[i] - sp[i]) * ood;
+
+                    if (t1 > t2)
+                    {
+                        (t1, t2) = (t2, t1);
+                    }
+
+                    if (t1 > tmin)
+                    {
+                        tmin = t1;
+                    }
+
+                    if (t2 < tmax)
+                    {
+                        tmax = t2;
+                    }
+
+                    if (tmin > tmax)
+                    {
+                        return false;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Intersections.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 711c28cb0e9039d4083f401af3ab2769
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 33 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Loader.cs

@@ -0,0 +1,33 @@
+using System.IO;
+
+namespace DotRecast.Core
+{
+    public static class Loader
+    {
+        public static byte[] ToBytes(string filename)
+        {
+            var filepath = FindParentPath(filename);
+            using var fs = new FileStream(filepath, FileMode.Open, FileAccess.Read);
+            byte[] buffer = new byte[fs.Length];
+            fs.Read(buffer, 0, buffer.Length);
+
+            return buffer;
+        }
+
+        public static string FindParentPath(string filename)
+        {
+            string filePath = Path.Combine("resources", filename);
+            for (int i = 0; i < 10; ++i)
+            {
+                if (File.Exists(filePath))
+                {
+                    return Path.GetFullPath(filePath);
+                }
+
+                filePath = Path.Combine("..", filePath);
+            }
+
+            return Path.GetFullPath(filename);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/Loader.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 47143e341bad39f4e8b37ed01a663af6
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 20 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAnonymousDisposable.cs

@@ -0,0 +1,20 @@
+using System;
+
+namespace DotRecast.Core
+{
+    public struct RcAnonymousDisposable : IDisposable
+    {
+        private Action _dispose;
+
+        public RcAnonymousDisposable(Action dispose)
+        {
+            _dispose = dispose;
+        }
+
+        public void Dispose()
+        {
+            _dispose?.Invoke();
+            _dispose = null;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAnonymousDisposable.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 44f61679764465e4fb558a9cbc62036c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 42 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcArrayUtils.cs

@@ -0,0 +1,42 @@
+using System;
+
+namespace DotRecast.Core
+{
+    public static class RcArrayUtils
+    {
+        public static T[] CopyOf<T>(T[] source, int startIdx, int length)
+        {
+            var deatArr = new T[length];
+            for (int i = 0; i < length; ++i)
+            {
+                deatArr[i] = source[startIdx + i];
+            }
+
+            return deatArr;
+        }
+
+        public static T[] CopyOf<T>(T[] source, int length)
+        {
+            var deatArr = new T[length];
+            var count = Math.Max(0, Math.Min(source.Length, length));
+            for (int i = 0; i < count; ++i)
+            {
+                deatArr[i] = source[i];
+            }
+
+            return deatArr;
+        }
+
+        public static T[][] Of<T>(int len1, int len2)
+        {
+            var temp = new T[len1][];
+
+            for (int i = 0; i < len1; ++i)
+            {
+                temp[i] = new T[len2];
+            }
+
+            return temp;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcArrayUtils.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ec035cb79baa5954b92d303b1a624024
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicBoolean.cs

@@ -0,0 +1,19 @@
+using System.Threading;
+
+namespace DotRecast.Core
+{
+    public class RcAtomicBoolean
+    {
+        private volatile int _location;
+
+        public bool Set(bool v)
+        {
+            return 0 != Interlocked.Exchange(ref _location, v ? 1 : 0);
+        }
+
+        public bool Get()
+        {
+            return 0 != _location;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicBoolean.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3e851cf20ab5c22418be1c30014e8589
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 29 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicFloat.cs

@@ -0,0 +1,29 @@
+using System.Threading;
+
+namespace DotRecast.Core
+{
+    public class RcAtomicFloat
+    {
+        private volatile float _location;
+
+        public RcAtomicFloat(float location)
+        {
+            _location = location;
+        }
+
+        public float Get()
+        {
+            return _location;
+        }
+
+        public float Exchange(float exchange)
+        {
+            return Interlocked.Exchange(ref _location, exchange);
+        }
+
+        public float CompareExchange(float value, float comparand)
+        {
+            return Interlocked.CompareExchange(ref _location, value, comparand);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicFloat.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2bd673ef6d3fe3c4cbe17f7b30eb6187
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 65 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicInteger.cs

@@ -0,0 +1,65 @@
+using System.Threading;
+
+namespace DotRecast.Core
+{
+    public class RcAtomicInteger
+    {
+        private volatile int _location;
+
+        public RcAtomicInteger() : this(0)
+        {
+        }
+
+        public RcAtomicInteger(int location)
+        {
+            _location = location;
+        }
+
+        public int IncrementAndGet()
+        {
+            return Interlocked.Increment(ref _location);
+        }
+
+        public int GetAndIncrement()
+        {
+            var next = Interlocked.Increment(ref _location);
+            return next - 1;
+        }
+
+
+        public int DecrementAndGet()
+        {
+            return Interlocked.Decrement(ref _location);
+        }
+
+        public int Read()
+        {
+            return _location;
+        }
+
+        public int GetSoft()
+        {
+            return _location;
+        }
+
+        public int Exchange(int exchange)
+        {
+            return Interlocked.Exchange(ref _location, exchange);
+        }
+
+        public int Decrease(int value)
+        {
+            return Interlocked.Add(ref _location, -value);
+        }
+
+        public int CompareExchange(int value, int comparand)
+        {
+            return Interlocked.CompareExchange(ref _location, value, comparand);
+        }
+
+        public int Add(int value)
+        {
+            return Interlocked.Add(ref _location, value);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicInteger.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9e8435f1687bfe242827427a58d399a9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 53 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicLong.cs

@@ -0,0 +1,53 @@
+using System.Threading;
+
+namespace DotRecast.Core
+{
+    public class RcAtomicLong
+    {
+        private long _location;
+
+        public RcAtomicLong() : this(0)
+        {
+        }
+
+        public RcAtomicLong(long location)
+        {
+            _location = location;
+        }
+
+        public long IncrementAndGet()
+        {
+            return Interlocked.Increment(ref _location);
+        }
+
+        public long DecrementAndGet()
+        {
+            return Interlocked.Decrement(ref _location);
+        }
+
+        public long Read()
+        {
+            return Interlocked.Read(ref _location);
+        }
+
+        public long Exchange(long exchange)
+        {
+            return Interlocked.Exchange(ref _location, exchange);
+        }
+
+        public long Decrease(long value)
+        {
+            return Interlocked.Add(ref _location, -value);
+        }
+
+        public long CompareExchange(long value, long comparand)
+        {
+            return Interlocked.CompareExchange(ref _location, value, comparand);
+        }
+
+        public long AddAndGet(long value)
+        {
+            return Interlocked.Add(ref _location, value);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcAtomicLong.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 151080065e8643740a2021cea9ecb574
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 142 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteBuffer.cs

@@ -0,0 +1,142 @@
+using System;
+using System.Buffers.Binary;
+
+namespace DotRecast.Core
+{
+    public class RcByteBuffer
+    {
+        private RcByteOrder _order;
+        private byte[] _bytes;
+        private int _position;
+
+        public RcByteBuffer(byte[] bytes)
+        {
+            _order = BitConverter.IsLittleEndian
+                ? RcByteOrder.LITTLE_ENDIAN
+                : RcByteOrder.BIG_ENDIAN;
+
+            _bytes = bytes;
+            _position = 0;
+        }
+
+        public RcByteOrder Order()
+        {
+            return _order;
+        }
+
+        public void Order(RcByteOrder order)
+        {
+            _order = order;
+        }
+
+        public int Limit()
+        {
+            return _bytes.Length - _position;
+        }
+
+        public int Remaining()
+        {
+            int rem = Limit();
+            return rem > 0 ? rem : 0;
+        }
+
+
+        public void Position(int pos)
+        {
+            _position = pos;
+        }
+
+        public int Position()
+        {
+            return _position;
+        }
+
+        public Span<byte> ReadBytes(int length)
+        {
+            var nextPos = _position + length;
+            (nextPos, _position) = (_position, nextPos);
+
+            return _bytes.AsSpan(nextPos, length);
+        }
+
+        public byte Get()
+        {
+            var span = ReadBytes(1);
+            return span[0];
+        }
+
+        public short GetShort()
+        {
+            var span = ReadBytes(2);
+            if (_order == RcByteOrder.BIG_ENDIAN)
+            {
+                return BinaryPrimitives.ReadInt16BigEndian(span);
+            }
+            else
+            {
+                return BinaryPrimitives.ReadInt16LittleEndian(span);
+            }
+        }
+
+
+        public int GetInt()
+        {
+            var span = ReadBytes(4);
+            if (_order == RcByteOrder.BIG_ENDIAN)
+            {
+                return BinaryPrimitives.ReadInt32BigEndian(span);
+            }
+            else
+            {
+                return BinaryPrimitives.ReadInt32LittleEndian(span);
+            }
+        }
+
+        public float GetFloat()
+        {
+            var span = ReadBytes(4);
+            if (_order == RcByteOrder.BIG_ENDIAN && BitConverter.IsLittleEndian)
+            {
+                span.Reverse();
+            }
+            else if (_order == RcByteOrder.LITTLE_ENDIAN && !BitConverter.IsLittleEndian)
+            {
+                span.Reverse();
+            }
+
+            return BitConverter.ToSingle(span);
+        }
+
+        public long GetLong()
+        {
+            var span = ReadBytes(8);
+            if (_order == RcByteOrder.BIG_ENDIAN)
+            {
+                return BinaryPrimitives.ReadInt64BigEndian(span);
+            }
+            else
+            {
+                return BinaryPrimitives.ReadInt64LittleEndian(span);
+            }
+        }
+
+        public void PutFloat(float v)
+        {
+            // if (_order == ByteOrder.BIG_ENDIAN)
+            // {
+            //     BinaryPrimitives.WriteInt32BigEndian(_bytes[_position]);
+            // }
+            // else
+            // {
+            //     BinaryPrimitives.ReadInt64LittleEndian(span);
+            // }
+
+            // ?
+        }
+
+        public void PutInt(int v)
+        {
+            // ?
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteBuffer.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fc56ab5a6dc4ed04cb0b10d653912d8a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteOrder.cs

@@ -0,0 +1,9 @@
+namespace DotRecast.Core
+{
+    public enum RcByteOrder
+    {
+        /// <summary>Default on most Windows systems</summary>
+        LITTLE_ENDIAN,
+        BIG_ENDIAN,
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcByteOrder.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: df7f0204b0c31ab44b5bbdf7f56278ed
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 102 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcConvexUtils.cs

@@ -0,0 +1,102 @@
+/*
+recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System.Collections.Generic;
+
+namespace DotRecast.Core
+{
+    public static class RcConvexUtils
+    {
+        // Calculates convex hull on xz-plane of points on 'pts',
+        // stores the indices of the resulting hull in 'out' and
+        // returns number of points on hull.
+        public static List<int> Convexhull(List<RcVec3f> pts)
+        {
+            int npts = pts.Count;
+            List<int> @out = new List<int>();
+            // Find lower-leftmost point.
+            int hull = 0;
+            for (int i = 1; i < npts; ++i)
+            {
+                if (Cmppt(pts[i], pts[hull]))
+                {
+                    hull = i;
+                }
+            }
+
+            // Gift wrap hull.
+            int endpt = 0;
+            do
+            {
+                @out.Add(hull);
+                endpt = 0;
+                for (int j = 1; j < npts; ++j)
+                {
+                    RcVec3f a = pts[hull];
+                    RcVec3f b = pts[endpt];
+                    RcVec3f c = pts[j];
+                    if (hull == endpt || Left(a, b, c))
+                    {
+                        endpt = j;
+                    }
+                }
+
+                hull = endpt;
+            } while (endpt != @out[0]);
+
+            return @out;
+        }
+
+        // Returns true if 'a' is more lower-left than 'b'.
+        private static bool Cmppt(RcVec3f a, RcVec3f b)
+        {
+            if (a.x < b.x)
+            {
+                return true;
+            }
+
+            if (a.x > b.x)
+            {
+                return false;
+            }
+
+            if (a.z < b.z)
+            {
+                return true;
+            }
+
+            if (a.z > b.z)
+            {
+                return false;
+            }
+
+            return false;
+        }
+
+        // Returns true if 'c' is left of line 'a'-'b'.
+        private static bool Left(RcVec3f a, RcVec3f b, RcVec3f c)
+        {
+            float u1 = b.x - a.x;
+            float v1 = b.z - a.z;
+            float u2 = c.x - a.x;
+            float v2 = c.z - a.z;
+            return u1 * v2 - v1 * u2 < 0;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcConvexUtils.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e2c3b70aaf4d86a4d956d18f26831e1c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 9 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcEdge.cs

@@ -0,0 +1,9 @@
+namespace DotRecast.Core
+{
+    public class RcEdge
+    {
+        public int[] vert = new int[2];
+        public int[] polyEdge = new int[2];
+        public int[] poly = new int[2];
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcEdge.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f6a689bd905aa174ba81535b6a1d558c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcFrequency.cs

@@ -0,0 +1,11 @@
+using System;
+using System.Diagnostics;
+
+namespace DotRecast.Core
+{
+    public static class RcFrequency
+    {
+        public static readonly double Frequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+        public static long Ticks => unchecked((long)(Stopwatch.GetTimestamp() * Frequency));
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcFrequency.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e30a62d8d6a937448a406aaf5fd98f4c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 10 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcHashCodes.cs

@@ -0,0 +1,10 @@
+namespace DotRecast.Core
+{
+    public static class RcHashCodes
+    {
+        public static int CombineHashCodes(int h1, int h2)
+        {
+            return (((h1 << 5) + h1) ^ h2);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcHashCodes.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b33c1027f27ecab4eaf9ad473e3b6d2c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 83 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Enumerable.cs

@@ -0,0 +1,83 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace DotRecast.Core
+{
+    public readonly partial struct RcImmutableArray<T>
+    {
+        public IEnumerator<T> GetEnumerator()
+        {
+            return EnumeratorObject.Create(_array);
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return EnumeratorObject.Create(_array);
+        }
+
+        private sealed class EnumeratorObject : IEnumerator<T>
+        {
+            private static readonly IEnumerator<T> EmptyEnumerator = new EnumeratorObject(Empty._array!);
+            private readonly T[] _array;
+            private int _index;
+
+            internal static IEnumerator<T> Create(T[] array)
+            {
+                if (array.Length != 0)
+                {
+                    return new EnumeratorObject(array);
+                }
+                else
+                {
+                    return EmptyEnumerator;
+                }
+            }
+
+
+            private EnumeratorObject(T[] array)
+            {
+                _index = -1;
+                _array = array;
+            }
+
+            public T Current
+            {
+                get
+                {
+                    if (unchecked((uint)_index) < (uint)_array.Length)
+                    {
+                        return _array[_index];
+                    }
+
+                    throw new InvalidOperationException();
+                }
+            }
+
+            object IEnumerator.Current => this.Current;
+
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                int newIndex = _index + 1;
+                int length = _array.Length;
+
+                if ((uint)newIndex <= (uint)length)
+                {
+                    _index = newIndex;
+                    return (uint)newIndex < (uint)length;
+                }
+
+                return false;
+            }
+
+            void IEnumerator.Reset()
+            {
+                _index = -1;
+            }
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Enumerable.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d4f269b778262fb4d85e36c7c68a2759
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 69 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Listable.cs

@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+
+namespace DotRecast.Core
+{
+    public readonly partial struct RcImmutableArray<T> : IList<T>
+    {
+        public int Count => Length;
+        public bool IsReadOnly => true;
+
+        T IList<T>.this[int index]
+        {
+            get
+            {
+                var self = this;
+                return self[index];
+            }
+            set => throw new NotSupportedException();
+        }
+
+
+        public int IndexOf(T item)
+        {
+            for (int i = 0; i < Count; ++i)
+            {
+                if (_array![i].Equals(item))
+                    return i;
+            }
+
+            return -1;
+        }
+
+        public bool Contains(T item)
+        {
+            return IndexOf(item) >= 0;
+        }
+
+        public void CopyTo(T[] array, int arrayIndex)
+        {
+            var self = this;
+            Array.Copy(self._array!, 0, array, arrayIndex, self.Length);
+        }
+
+        public void Add(T item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Clear()
+        {
+            throw new NotSupportedException();
+        }
+
+        public bool Remove(T item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void Insert(int index, T item)
+        {
+            throw new NotSupportedException();
+        }
+
+        public void RemoveAt(int index)
+        {
+            throw new NotSupportedException();
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Listable.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 500e406af65da7b41a7faa90a44917f0
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Minimal.cs

@@ -0,0 +1,19 @@
+namespace DotRecast.Core
+{
+    public readonly partial struct RcImmutableArray<T>
+    {
+#pragma warning disable CA1825
+        public static readonly RcImmutableArray<T> Empty = new RcImmutableArray<T>(new T[0]);
+#pragma warning restore CA1825
+
+        private readonly T[] _array;
+
+        internal RcImmutableArray(T[] items)
+        {
+            _array = items;
+        }
+
+        public T this[int index] => _array![index];
+        public int Length => _array!.Length;
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.Minimal.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 36f8c9e6aaad02f439a8cd714428b932
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 48 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.cs

@@ -0,0 +1,48 @@
+using System;
+
+namespace DotRecast.Core
+{
+    public static class RcImmutableArray
+    {
+        public static RcImmutableArray<T> Create<T>()
+        {
+            return RcImmutableArray<T>.Empty;
+        }
+
+        public static RcImmutableArray<T> Create<T>(T item1)
+        {
+            T[] array = new[] { item1 };
+            return new RcImmutableArray<T>(array);
+        }
+
+        public static RcImmutableArray<T> Create<T>(T item1, T item2)
+        {
+            T[] array = new[] { item1, item2 };
+            return new RcImmutableArray<T>(array);
+        }
+
+        public static RcImmutableArray<T> Create<T>(T item1, T item2, T item3)
+        {
+            T[] array = new[] { item1, item2, item3 };
+            return new RcImmutableArray<T>(array);
+        }
+
+        public static RcImmutableArray<T> Create<T>(T item1, T item2, T item3, T item4)
+        {
+            T[] array = new[] { item1, item2, item3, item4 };
+            return new RcImmutableArray<T>(array);
+        }
+
+        public static RcImmutableArray<T> Create<T>(params T[] items)
+        {
+            if (items == null || items.Length == 0)
+            {
+                return RcImmutableArray<T>.Empty;
+            }
+
+            var tmp = new T[items.Length];
+            Array.Copy(items, tmp, items.Length);
+            return new RcImmutableArray<T>(tmp);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcImmutableArray.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 08016d32d342aa44ba8beeab240c7d9f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 46 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMath.cs

@@ -0,0 +1,46 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace DotRecast.Core
+{
+    public static class RcMath
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Sqr(float f)
+        {
+            return f * f;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Clamp(float v, float min, float max)
+        {
+            return Math.Max(Math.Min(v, max), min);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static int Clamp(int v, int min, int max)
+        {
+            return Math.Max(Math.Min(v, max), min);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMath.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a273c9d6ea11a4647be3579e37227951
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 22 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMatrix4X4.cs

@@ -0,0 +1,22 @@
+namespace DotRecast.Core
+{
+    public struct RcMatrix4X4
+    {
+        public float m11;
+        public float m12;
+        public float m13;
+        public float m14;
+        public float m21;
+        public float m22;
+        public float m23;
+        public float m24;
+        public float m31;
+        public float m32;
+        public float m33;
+        public float m34;
+        public float m41;
+        public float m42;
+        public float m43;
+        public float m44;
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcMatrix4X4.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f7e406a0c810e5b46942bf2958205567
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 20 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSegmentVert.cs

@@ -0,0 +1,20 @@
+namespace DotRecast.Core
+{
+    public struct RcSegmentVert
+    {
+        public RcVec3f vmin;
+        public RcVec3f vmax;
+
+        public RcSegmentVert(float v0, float v1, float v2, float v3, float v4, float v5)
+        {
+            vmin.x = v0;
+            vmin.y = v1;
+            vmin.z = v2;
+            
+            vmax.x = v3;
+            vmax.y = v4;
+            vmax.z = v5;
+        }
+
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSegmentVert.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 35f7e5968d957a545b7e0bcfa6789b31
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 98 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSortedQueue.cs

@@ -0,0 +1,98 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Collections.Generic;
+
+namespace DotRecast.Core
+{
+    public class RcSortedQueue<T>
+    {
+        private bool _dirty;
+        private readonly List<T> _items;
+        private readonly Comparison<T> _comparison;
+
+        public RcSortedQueue(Comparison<T> comparison)
+        {
+            _items = new List<T>();
+            _comparison = (x, y) => comparison.Invoke(x, y) * -1; // reverse
+        }
+
+        public int Count()
+        {
+            return _items.Count;
+        }
+
+        public void Clear()
+        {
+            _items.Clear();
+        }
+
+        private void Balance()
+        {
+            if (_dirty)
+            {
+                _items.Sort(_comparison); // reverse
+                _dirty = false;
+            }
+        }
+
+        public T Peek()
+        {
+            Balance();
+            return _items[_items.Count - 1];
+        }
+
+        public T Dequeue()
+        {
+            var node = Peek();
+            _items.Remove(node);
+            return node;
+        }
+
+        public void Enqueue(T item)
+        {
+            _items.Add(item);
+            _dirty = true;
+        }
+
+        public void Remove(T item)
+        {
+            int idx = _items.FindLastIndex(x => item.Equals(x));
+            if (0 > idx)
+                return;
+
+            _items.RemoveAt(idx);
+        }
+
+        public bool IsEmpty()
+        {
+            return 0 == _items.Count;
+        }
+
+        public List<T> ToList()
+        {
+            Balance();
+            var temp = new List<T>(_items);
+            temp.Reverse();
+            return temp;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcSortedQueue.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 711ca00ae8eae3443a8147ead47dcfa5
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 70 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetry.cs

@@ -0,0 +1,70 @@
+/*
+recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace DotRecast.Core
+{
+    public class RcTelemetry
+    {
+        private readonly ThreadLocal<Dictionary<string, RcAtomicLong>> _timerStart;
+        private readonly ConcurrentDictionary<string, RcAtomicLong> _timerAccum;
+
+        public RcTelemetry()
+        {
+            _timerStart = new ThreadLocal<Dictionary<string, RcAtomicLong>>(() => new Dictionary<string, RcAtomicLong>());
+            _timerAccum = new ConcurrentDictionary<string, RcAtomicLong>();
+        }
+
+        public IDisposable ScopedTimer(RcTimerLabel label)
+        {
+            StartTimer(label);
+            return new RcAnonymousDisposable(() => StopTimer(label));
+        }
+        
+        public void StartTimer(RcTimerLabel label)
+        {
+            _timerStart.Value[label.Name] = new RcAtomicLong(RcFrequency.Ticks);
+        }
+
+
+        public void StopTimer(RcTimerLabel label)
+        {
+            _timerAccum
+                .GetOrAdd(label.Name, _ => new RcAtomicLong(0))
+                .AddAndGet(RcFrequency.Ticks - _timerStart.Value?[label.Name].Read() ?? 0);
+        }
+
+        public void Warn(string message)
+        {
+            Console.WriteLine(message);
+        }
+
+        public List<RcTelemetryTick> ToList()
+        {
+            return _timerAccum
+                .Select(x => new RcTelemetryTick(x.Key, x.Value.Read()))
+                .ToList();
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetry.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c9126197c5df2034c92b434d1a0f2c12
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 18 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetryTick.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace DotRecast.Core
+{
+    public readonly struct RcTelemetryTick
+    {
+        public readonly string Key;
+        public readonly long Ticks;
+        public long Millis => Ticks / TimeSpan.TicksPerMillisecond;
+        public long Micros => Ticks / 10; // TimeSpan.TicksPerMicrosecond;
+
+        public RcTelemetryTick(string key, long ticks)
+        {
+            Key = key;
+            Ticks = ticks;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTelemetryTick.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 04fe3506c378ab44c9d7a4d200171368
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 109 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTimerLabel.cs

@@ -0,0 +1,109 @@
+namespace DotRecast.Core
+{
+    /// Recast performance timer categories.
+    /// @see rcContext
+    public class RcTimerLabel
+    {
+        /// The user defined total time of the build.
+        public static readonly RcTimerLabel RC_TIMER_TOTAL = new RcTimerLabel(nameof(RC_TIMER_TOTAL));
+
+        /// A user defined build time.
+        public static readonly RcTimerLabel RC_TIMER_TEMP = new RcTimerLabel(nameof(RC_TIMER_TEMP));
+
+        /// The time to rasterize the triangles. (See: #rcRasterizeTriangle)
+        public static readonly RcTimerLabel RC_TIMER_RASTERIZE_TRIANGLES = new RcTimerLabel(nameof(RC_TIMER_RASTERIZE_TRIANGLES));
+        public static readonly RcTimerLabel RC_TIMER_RASTERIZE_SPHERE = new RcTimerLabel(nameof(RC_TIMER_RASTERIZE_SPHERE));
+        public static readonly RcTimerLabel RC_TIMER_RASTERIZE_CAPSULE = new RcTimerLabel(nameof(RC_TIMER_RASTERIZE_CAPSULE));
+        public static readonly RcTimerLabel RC_TIMER_RASTERIZE_CYLINDER = new RcTimerLabel(nameof(RC_TIMER_RASTERIZE_CYLINDER));
+        public static readonly RcTimerLabel RC_TIMER_RASTERIZE_BOX = new RcTimerLabel(nameof(RC_TIMER_RASTERIZE_BOX));
+        public static readonly RcTimerLabel RC_TIMER_RASTERIZE_CONVEX = new RcTimerLabel(nameof(RC_TIMER_RASTERIZE_CONVEX));
+
+        /// The time to build the compact heightfield. (See: #rcBuildCompactHeightfield)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_COMPACTHEIGHTFIELD = new RcTimerLabel(nameof(RC_TIMER_BUILD_COMPACTHEIGHTFIELD));
+
+        /// The total time to build the contours. (See: #rcBuildContours)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_CONTOURS = new RcTimerLabel(nameof(RC_TIMER_BUILD_CONTOURS));
+
+        /// The time to trace the boundaries of the contours. (See: #rcBuildContours)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_CONTOURS_TRACE = new RcTimerLabel(nameof(RC_TIMER_BUILD_CONTOURS_TRACE));
+
+        public static readonly RcTimerLabel RC_TIMER_BUILD_CONTOURS_WALK = new RcTimerLabel(nameof(RC_TIMER_BUILD_CONTOURS_WALK));
+        
+        /// The time to simplify the contours. (See: #rcBuildContours)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_CONTOURS_SIMPLIFY = new RcTimerLabel(nameof(RC_TIMER_BUILD_CONTOURS_SIMPLIFY));
+
+        /// The time to filter ledge spans. (See: #rcFilterLedgeSpans)
+        public static readonly RcTimerLabel RC_TIMER_FILTER_BORDER = new RcTimerLabel(nameof(RC_TIMER_FILTER_BORDER));
+
+        /// The time to filter low height spans. (See: #rcFilterWalkableLowHeightSpans)
+        public static readonly RcTimerLabel RC_TIMER_FILTER_WALKABLE = new RcTimerLabel(nameof(RC_TIMER_FILTER_WALKABLE));
+
+        /// The time to apply the median filter. (See: #rcMedianFilterWalkableArea)
+        public static readonly RcTimerLabel RC_TIMER_MEDIAN_AREA = new RcTimerLabel(nameof(RC_TIMER_MEDIAN_AREA));
+
+        /// The time to filter low obstacles. (See: #rcFilterLowHangingWalkableObstacles)
+        public static readonly RcTimerLabel RC_TIMER_FILTER_LOW_OBSTACLES = new RcTimerLabel(nameof(RC_TIMER_FILTER_LOW_OBSTACLES));
+
+        /// The time to build the polygon mesh. (See: #rcBuildPolyMesh)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_POLYMESH = new RcTimerLabel(nameof(RC_TIMER_BUILD_POLYMESH));
+
+        /// The time to merge polygon meshes. (See: #rcMergePolyMeshes)
+        public static readonly RcTimerLabel RC_TIMER_MERGE_POLYMESH = new RcTimerLabel(nameof(RC_TIMER_MERGE_POLYMESH));
+
+        /// The time to erode the walkable area. (See: #rcErodeWalkableArea)
+        public static readonly RcTimerLabel RC_TIMER_ERODE_AREA = new RcTimerLabel(nameof(RC_TIMER_ERODE_AREA));
+
+        /// The time to mark a box area. (See: #rcMarkBoxArea)
+        public static readonly RcTimerLabel RC_TIMER_MARK_BOX_AREA = new RcTimerLabel(nameof(RC_TIMER_MARK_BOX_AREA));
+
+        /// The time to mark a cylinder area. (See: #rcMarkCylinderArea)
+        public static readonly RcTimerLabel RC_TIMER_MARK_CYLINDER_AREA = new RcTimerLabel(nameof(RC_TIMER_MARK_CYLINDER_AREA));
+
+        /// The time to mark a convex polygon area. (See: #rcMarkConvexPolyArea)
+        public static readonly RcTimerLabel RC_TIMER_MARK_CONVEXPOLY_AREA = new RcTimerLabel(nameof(RC_TIMER_MARK_CONVEXPOLY_AREA));
+
+        /// The total time to build the distance field. (See: #rcBuildDistanceField)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_DISTANCEFIELD = new RcTimerLabel(nameof(RC_TIMER_BUILD_DISTANCEFIELD));
+
+        /// The time to build the distances of the distance field. (See: #rcBuildDistanceField)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_DISTANCEFIELD_DIST = new RcTimerLabel(nameof(RC_TIMER_BUILD_DISTANCEFIELD_DIST));
+
+        /// The time to blur the distance field. (See: #rcBuildDistanceField)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_DISTANCEFIELD_BLUR = new RcTimerLabel(nameof(RC_TIMER_BUILD_DISTANCEFIELD_BLUR));
+
+        /// The total time to build the regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_REGIONS = new RcTimerLabel(nameof(RC_TIMER_BUILD_REGIONS));
+
+        /// The total time to apply the watershed algorithm. (See: #rcBuildRegions)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_REGIONS_WATERSHED = new RcTimerLabel(nameof(RC_TIMER_BUILD_REGIONS_WATERSHED));
+
+        /// The time to expand regions while applying the watershed algorithm. (See: #rcBuildRegions)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_REGIONS_EXPAND = new RcTimerLabel(nameof(RC_TIMER_BUILD_REGIONS_EXPAND));
+
+        /// The time to flood regions while applying the watershed algorithm. (See: #rcBuildRegions)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_REGIONS_FLOOD = new RcTimerLabel(nameof(RC_TIMER_BUILD_REGIONS_FLOOD));
+
+        /// The time to filter out small regions. (See: #rcBuildRegions, #rcBuildRegionsMonotone)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_REGIONS_FILTER = new RcTimerLabel(nameof(RC_TIMER_BUILD_REGIONS_FILTER));
+
+        /// The time to build heightfield layers. (See: #rcBuildHeightfieldLayers)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_LAYERS = new RcTimerLabel(nameof(RC_TIMER_BUILD_LAYERS));
+
+        /// The time to build the polygon mesh detail. (See: #rcBuildPolyMeshDetail)
+        public static readonly RcTimerLabel RC_TIMER_BUILD_POLYMESHDETAIL = new RcTimerLabel(nameof(RC_TIMER_BUILD_POLYMESHDETAIL));
+
+        /// The time to merge polygon mesh details. (See: #rcMergePolyMeshDetails)
+        public static readonly RcTimerLabel RC_TIMER_MERGE_POLYMESHDETAIL = new RcTimerLabel(nameof(RC_TIMER_MERGE_POLYMESHDETAIL));
+
+
+        /// The maximum number of timers.  (Used for iterating timers.)
+        public static readonly RcTimerLabel RC_MAX_TIMERS = new RcTimerLabel(nameof(RC_MAX_TIMERS));
+
+        public readonly string Name;
+
+        private RcTimerLabel(string name)
+        {
+            Name = name;
+        }
+    };
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcTimerLabel.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 85ef802eb27a8894da27dec75c24c85a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 66 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec2f.cs

@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace DotRecast.Core
+{
+    public struct RcVec2f
+    {
+        public float x;
+        public float y;
+
+        public static RcVec2f Zero { get; } = new RcVec2f { x = 0, y = 0 };
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public float Get(int idx)
+        {
+            if (0 == idx)
+                return x;
+
+            if (1 == idx)
+                return y;
+
+            throw new IndexOutOfRangeException("vector2f index out of range");
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is RcVec2f))
+                return false;
+
+            return Equals((RcVec2f)obj);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool Equals(RcVec2f other)
+        {
+            return x.Equals(other.x) &&
+                   y.Equals(other.y);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public override int GetHashCode()
+        {
+            int hash = x.GetHashCode();
+            hash = RcHashCodes.CombineHashCodes(hash, y.GetHashCode());
+            return hash;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool operator ==(RcVec2f left, RcVec2f right)
+        {
+            return left.Equals(right);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool operator !=(RcVec2f left, RcVec2f right)
+        {
+            return !left.Equals(right);
+        }
+        
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public override string ToString()
+        {
+            return $"{x}, {y}";
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec2f.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4edb6c3ebf876444381d0293a8d2f6d4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 645 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec3f.cs

@@ -0,0 +1,645 @@
+/*
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace DotRecast.Core
+{
+    public struct RcVec3f
+    {
+        public float x;
+        public float y;
+        public float z;
+
+        public static RcVec3f Zero { get; } = new RcVec3f(0.0f, 0.0f, 0.0f);
+        public static RcVec3f One { get; } = new RcVec3f(1.0f);
+        public static RcVec3f UnitX { get; } = new RcVec3f(1.0f, 0.0f, 0.0f);
+        public static RcVec3f UnitY { get; } = new RcVec3f(0.0f, 1.0f, 0.0f);
+        public static RcVec3f UnitZ { get; } = new RcVec3f(0.0f, 0.0f, 1.0f);
+
+        public static RcVec3f Of(float[] f)
+        {
+            return Of(f, 0);
+        }
+
+        public static RcVec3f Of(float[] f, int idx)
+        {
+            return Of(f[idx + 0], f[idx + 1], f[idx + 2]);
+        }
+
+        public static RcVec3f Of(float x, float y, float z)
+        {
+            return new RcVec3f(x, y, z);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public RcVec3f(float x, float y, float z)
+        {
+            this.x = x;
+            this.y = y;
+            this.z = z;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public RcVec3f(float f)
+        {
+            x = f;
+            y = f;
+            z = f;
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public RcVec3f(float[] f)
+        {
+            x = f[0];
+            y = f[1];
+            z = f[2];
+        }
+
+        public float this[int index]
+        {
+            get => GetElement(index);
+            set => SetElement(index, value);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public float GetElement(int index)
+        {
+            switch (index)
+            {
+                case 0: return x;
+                case 1: return y;
+                case 2: return z;
+                default: throw new IndexOutOfRangeException($"{index}");
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void SetElement(int index, float value)
+        {
+            switch (index)
+            {
+                case 0:
+                    x = value;
+                    break;
+                case 1:
+                    y = value;
+                    break;
+                case 2:
+                    z = value;
+                    break;
+
+                default: throw new IndexOutOfRangeException($"{index}-{value}");
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Set(float a, float b, float c)
+        {
+            x = a;
+            y = b;
+            z = c;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Set(float[] @in)
+        {
+            Set(@in, 0);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Set(float[] @in, int i)
+        {
+            x = @in[i];
+            y = @in[i + 1];
+            z = @in[i + 2];
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly float Length()
+        {
+            return (float)Math.Sqrt(x * x + y * y + z * z);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly RcVec3f Subtract(RcVec3f right)
+        {
+            return new RcVec3f(
+                x - right.x,
+                y - right.y,
+                z - right.z
+            );
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly RcVec3f Add(RcVec3f v2)
+        {
+            return new RcVec3f(
+                x + v2.x,
+                y + v2.y,
+                z + v2.z
+            );
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly RcVec3f Scale(float scale)
+        {
+            return new RcVec3f(
+                x * scale,
+                y * scale,
+                z * scale
+            );
+        }
+
+
+        /// Derives the dot product of two vectors on the xz-plane. (@p u . @p v)
+        /// @param[in] u A vector [(x, y, z)]
+        /// @param[in] v A vector [(x, y, z)]
+        /// @return The dot product on the xz-plane.
+        ///
+        /// The vectors are projected onto the xz-plane, so the y-values are
+        /// ignored.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly float Dot2D(RcVec3f v)
+        {
+            return x * v.x + z * v.z;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public readonly float Dot2D(float[] v, int vi)
+        {
+            return x * v[vi] + z * v[vi + 2];
+        }
+
+
+        public override bool Equals(object obj)
+        {
+            if (!(obj is RcVec3f))
+                return false;
+
+            return Equals((RcVec3f)obj);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool Equals(RcVec3f other)
+        {
+            return x.Equals(other.x) &&
+                   y.Equals(other.y) &&
+                   z.Equals(other.z);
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public override int GetHashCode()
+        {
+            int hash = x.GetHashCode();
+            hash = RcHashCodes.CombineHashCodes(hash, y.GetHashCode());
+            hash = RcHashCodes.CombineHashCodes(hash, z.GetHashCode());
+            return hash;
+        }
+
+        /// Normalizes the vector.
+        /// @param[in,out] v The vector to normalize. [(x, y, z)]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Normalize()
+        {
+            float d = (float)(1.0f / Math.Sqrt(RcMath.Sqr(x) + RcMath.Sqr(y) + RcMath.Sqr(z)));
+            if (d != 0)
+            {
+                x *= d;
+                y *= d;
+                z *= d;
+            }
+        }
+
+        public const float EPSILON = 1e-6f;
+
+        /// Normalizes the vector if the length is greater than zero.
+        /// If the magnitude is zero, the vector is unchanged.
+        /// @param[in,out]	v	The vector to normalize. [(x, y, z)]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void SafeNormalize()
+        {
+            float sqMag = RcMath.Sqr(x) + RcMath.Sqr(y) + RcMath.Sqr(z);
+            if (sqMag > EPSILON)
+            {
+                float inverseMag = 1.0f / (float)Math.Sqrt(sqMag);
+                x *= inverseMag;
+                y *= inverseMag;
+                z *= inverseMag;
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Min(float[] @in, int i)
+        {
+            x = Math.Min(x, @in[i]);
+            y = Math.Min(y, @in[i + 1]);
+            z = Math.Min(z, @in[i + 2]);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Min(RcVec3f b)
+        {
+            x = Math.Min(x, b.x);
+            y = Math.Min(y, b.y);
+            z = Math.Min(z, b.z);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Max(RcVec3f b)
+        {
+            x = Math.Max(x, b.x);
+            y = Math.Max(y, b.y);
+            z = Math.Max(z, b.z);
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Max(float[] @in, int i)
+        {
+            x = Math.Max(x, @in[i]);
+            y = Math.Max(y, @in[i + 1]);
+            z = Math.Max(z, @in[i + 2]);
+        }
+
+        public override string ToString()
+        {
+            return $"{x}, {y}, {z}";
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool operator ==(RcVec3f left, RcVec3f right)
+        {
+            return left.Equals(right);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool operator !=(RcVec3f left, RcVec3f right)
+        {
+            return !left.Equals(right);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f operator -(RcVec3f left, RcVec3f right)
+        {
+            return left.Subtract(right);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f operator +(RcVec3f left, RcVec3f right)
+        {
+            return left.Add(right);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f operator *(RcVec3f left, RcVec3f right)
+        {
+            return new RcVec3f(
+                left.x * right.x,
+                left.y * right.y,
+                left.z * right.z
+            );
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f operator *(RcVec3f left, float right)
+        {
+            return left * new RcVec3f(right);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f operator *(float left, RcVec3f right)
+        {
+            return right * left;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f Cross(RcVec3f v1, RcVec3f v2)
+        {
+            return new RcVec3f(
+                (v1.y * v2.z) - (v1.z * v2.y),
+                (v1.z * v2.x) - (v1.x * v2.z),
+                (v1.x * v2.y) - (v1.y * v2.x)
+            );
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f Lerp(RcVec3f v1, RcVec3f v2, float t)
+        {
+            return new RcVec3f(
+                v1.x + (v2.x - v1.x) * t,
+                v1.y + (v2.y - v1.y) * t,
+                v1.z + (v2.z - v1.z) * t
+            );
+        }
+
+        /// Returns the distance between two points.
+        /// @param[in] v1 A point. [(x, y, z)]
+        /// @param[in] v2 A point. [(x, y, z)]
+        /// @return The distance between the two points.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Distance(RcVec3f v1, RcVec3f v2)
+        {
+            float dx = v2.x - v1.x;
+            float dy = v2.y - v1.y;
+            float dz = v2.z - v1.z;
+            return (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dot(RcVec3f v1, RcVec3f v2)
+        {
+            return (v1.x * v2.x) + (v1.y * v2.y)
+                                 + (v1.z * v2.z);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dot(float[] v1, float[] v2)
+        {
+            return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dot(float[] v1, RcVec3f v2)
+        {
+            return v1[0] * v2.x + v1[1] * v2.y + v1[2] * v2.z;
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float PerpXZ(RcVec3f a, RcVec3f b)
+        {
+            return (a.x * b.z) - (a.z * b.x);
+        }
+
+        /// Performs a scaled vector addition. (@p v1 + (@p v2 * @p s))
+        /// @param[out] dest The result vector. [(x, y, z)]
+        /// @param[in] v1 The base vector. [(x, y, z)]
+        /// @param[in] v2 The vector to scale and add to @p v1. [(x, y, z)]
+        /// @param[in] s The amount to scale @p v2 by before adding to @p v1.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f Mad(RcVec3f v1, RcVec3f v2, float s)
+        {
+            return new RcVec3f()
+            {
+                x = v1.x + (v2.x * s),
+                y = v1.y + (v2.y * s),
+                z = v1.z + (v2.z * s),
+            };
+        }
+
+        /// Performs a linear interpolation between two vectors. (@p v1 toward @p
+        /// v2)
+        /// @param[out] dest The result vector. [(x, y, x)]
+        /// @param[in] v1 The starting vector.
+        /// @param[in] v2 The destination vector.
+        /// @param[in] t The interpolation factor. [Limits: 0 <= value <= 1.0]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static RcVec3f Lerp(float[] verts, int v1, int v2, float t)
+        {
+            return new RcVec3f(
+                verts[v1 + 0] + (verts[v2 + 0] - verts[v1 + 0]) * t,
+                verts[v1 + 1] + (verts[v2 + 1] - verts[v1 + 1]) * t,
+                verts[v1 + 2] + (verts[v2 + 2] - verts[v1 + 2]) * t
+            );
+        }
+
+
+        /// Returns the distance between two points.
+        /// @param[in] v1 A point. [(x, y, z)]
+        /// @param[in] v2 A point. [(x, y, z)]
+        /// @return The distance between the two points.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float DistSqr(RcVec3f v1, float[] v2, int i)
+        {
+            float dx = v2[i] - v1.x;
+            float dy = v2[i + 1] - v1.y;
+            float dz = v2[i + 2] - v1.z;
+            return dx * dx + dy * dy + dz * dz;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float DistSqr(RcVec3f v1, RcVec3f v2)
+        {
+            float dx = v2.x - v1.x;
+            float dy = v2.y - v1.y;
+            float dz = v2.z - v1.z;
+            return dx * dx + dy * dy + dz * dz;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float DistSqr(float[] v, int i, int j)
+        {
+            float dx = v[i] - v[j];
+            float dy = v[i + 1] - v[j + 1];
+            float dz = v[i + 2] - v[j + 2];
+            return dx * dx + dy * dy + dz * dz;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float DistSqr(float[] v1, float[] v2)
+        {
+            float dx = v2[0] - v1[0];
+            float dy = v2[1] - v1[1];
+            float dz = v2[2] - v1[2];
+            return dx * dx + dy * dy + dz * dz;
+        }
+
+        /// Derives the distance between the specified points on the xz-plane.
+        /// @param[in] v1 A point. [(x, y, z)]
+        /// @param[in] v2 A point. [(x, y, z)]
+        /// @return The distance between the point on the xz-plane.
+        ///
+        /// The vectors are projected onto the xz-plane, so the y-values are
+        /// ignored.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dist2D(float[] v1, float[] v2)
+        {
+            float dx = v2[0] - v1[0];
+            float dz = v2[2] - v1[2];
+            return (float)Math.Sqrt(dx * dx + dz * dz);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dist2D(RcVec3f v1, RcVec3f v2)
+        {
+            float dx = v2.x - v1.x;
+            float dz = v2.z - v1.z;
+            return (float)Math.Sqrt(dx * dx + dz * dz);
+        }
+
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dist2DSqr(float[] v1, float[] v2)
+        {
+            float dx = v2[0] - v1[0];
+            float dz = v2[2] - v1[2];
+            return dx * dx + dz * dz;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dist2DSqr(RcVec3f v1, RcVec3f v2)
+        {
+            float dx = v2.x - v1.x;
+            float dz = v2.z - v1.z;
+            return dx * dx + dz * dz;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Dist2DSqr(RcVec3f p, float[] verts, int i)
+        {
+            float dx = verts[i] - p.x;
+            float dz = verts[i + 2] - p.z;
+            return dx * dx + dz * dz;
+        }
+
+        /// Derives the xz-plane 2D perp product of the two vectors. (uz*vx - ux*vz)
+        /// @param[in] u The LHV vector [(x, y, z)]
+        /// @param[in] v The RHV vector [(x, y, z)]
+        /// @return The dot product on the xz-plane.
+        ///
+        /// The vectors are projected onto the xz-plane, so the y-values are
+        /// ignored.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float Perp2D(RcVec3f u, RcVec3f v)
+        {
+            return u.z * v.x - u.x * v.z;
+        }
+
+        /// Derives the square of the scalar length of the vector. (len * len)
+        /// @param[in] v The vector. [(x, y, z)]
+        /// @return The square of the scalar length of the vector.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static float LenSqr(RcVec3f v)
+        {
+            return v.x * v.x + v.y * v.y + v.z * v.z;
+        }
+
+
+        /// Checks that the specified vector's components are all finite.
+        /// @param[in] v A point. [(x, y, z)]
+        /// @return True if all of the point's components are finite, i.e. not NaN
+        /// or any of the infinities.
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool IsFinite(RcVec3f v)
+        {
+            return float.IsFinite(v.x) && float.IsFinite(v.y) && float.IsFinite(v.z);
+        }
+
+        /// Checks that the specified vector's 2D components are finite.
+        /// @param[in] v A point. [(x, y, z)]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static bool IsFinite2D(RcVec3f v)
+        {
+            return float.IsFinite(v.x) && float.IsFinite(v.z);
+        }
+
+
+        public static void Copy(ref RcVec3f @out, float[] @in, int i)
+        {
+            Copy(ref @out, 0, @in, i);
+        }
+
+        public static void Copy(float[] @out, int n, float[] @in, int m)
+        {
+            @out[n] = @in[m];
+            @out[n + 1] = @in[m + 1];
+            @out[n + 2] = @in[m + 2];
+        }
+
+        public static void Copy(float[] @out, int n, RcVec3f @in, int m)
+        {
+            @out[n] = @in[m];
+            @out[n + 1] = @in[m + 1];
+            @out[n + 2] = @in[m + 2];
+        }
+
+        public static void Copy(ref RcVec3f @out, int n, float[] @in, int m)
+        {
+            @out[n] = @in[m];
+            @out[n + 1] = @in[m + 1];
+            @out[n + 2] = @in[m + 2];
+        }
+
+        public static void Add(ref RcVec3f e0, RcVec3f a, float[] verts, int i)
+        {
+            e0.x = a.x + verts[i];
+            e0.y = a.y + verts[i + 1];
+            e0.z = a.z + verts[i + 2];
+        }
+
+
+        public static void Sub(ref RcVec3f e0, float[] verts, int i, int j)
+        {
+            e0.x = verts[i] - verts[j];
+            e0.y = verts[i + 1] - verts[j + 1];
+            e0.z = verts[i + 2] - verts[j + 2];
+        }
+
+
+        public static void Sub(ref RcVec3f e0, RcVec3f i, float[] verts, int j)
+        {
+            e0.x = i.x - verts[j];
+            e0.y = i.y - verts[j + 1];
+            e0.z = i.z - verts[j + 2];
+        }
+
+
+        public static void Cross(float[] dest, float[] v1, float[] v2)
+        {
+            dest[0] = v1[1] * v2[2] - v1[2] * v2[1];
+            dest[1] = v1[2] * v2[0] - v1[0] * v2[2];
+            dest[2] = v1[0] * v2[1] - v1[1] * v2[0];
+        }
+
+        public static void Cross(float[] dest, RcVec3f v1, RcVec3f v2)
+        {
+            dest[0] = v1.y * v2.z - v1.z * v2.y;
+            dest[1] = v1.z * v2.x - v1.x * v2.z;
+            dest[2] = v1.x * v2.y - v1.y * v2.x;
+        }
+
+        public static void Cross(ref RcVec3f dest, RcVec3f v1, RcVec3f v2)
+        {
+            dest.x = v1.y * v2.z - v1.z * v2.y;
+            dest.y = v1.z * v2.x - v1.x * v2.z;
+            dest.z = v1.x * v2.y - v1.y * v2.x;
+        }
+
+
+        public static void Normalize(float[] v)
+        {
+            float d = (float)(1.0f / Math.Sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]));
+            v[0] *= d;
+            v[1] *= d;
+            v[2] *= d;
+        }
+
+        public static void Normalize(ref RcVec3f v)
+        {
+            float d = (float)(1.0f / Math.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z));
+            v.x *= d;
+            v.y *= d;
+            v.z *= d;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Core/RcVec3f.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8736af9bec85cd24f9e9a1aa5ed11714
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 0a8f82a1ba6d46c4c9070dc978223e50
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 1349 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowd.cs

@@ -0,0 +1,1349 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using DotRecast.Core;
+using DotRecast.Detour.Crowd.Tracking;
+
+
+namespace DotRecast.Detour.Crowd
+{
+    using static DotRecast.Core.RcMath;
+
+    /**
+ * Members in this module implement local steering and dynamic avoidance features.
+ *
+ * The crowd is the big beast of the navigation features. It not only handles a lot of the path management for you, but
+ * also local steering and dynamic avoidance between members of the crowd. I.e. It can keep your agents from running
+ * into each other.
+ *
+ * Main class: Crowd
+ *
+ * The #dtNavMeshQuery and #dtPathCorridor classes provide perfectly good, easy to use path planning features. But in
+ * the end they only give you points that your navigation client should be moving toward. When it comes to deciding
+ * things like agent velocity and steering to avoid other agents, that is up to you to implement. Unless, of course, you
+ * decide to use Crowd.
+ *
+ * Basically, you add an agent to the crowd, providing various configuration settings such as maximum speed and
+ * acceleration. You also provide a local target to move toward. The crowd manager then provides, with every update, the
+ * new agent position and velocity for the frame. The movement will be constrained to the navigation mesh, and steering
+ * will be applied to ensure agents managed by the crowd do not collide with each other.
+ *
+ * This is very powerful feature set. But it comes with limitations.
+ *
+ * The biggest limitation is that you must give control of the agent's position completely over to the crowd manager.
+ * You can update things like maximum speed and acceleration. But in order for the crowd manager to do its thing, it
+ * can't allow you to constantly be giving it overrides to position and velocity. So you give up direct control of the
+ * agent's movement. It belongs to the crowd.
+ *
+ * The second biggest limitation revolves around the fact that the crowd manager deals with local planning. So the
+ * agent's target should never be more than 256 polygons away from its current position. If it is, you risk your agent
+ * failing to reach its target. So you may still need to do long distance planning and provide the crowd manager with
+ * intermediate targets.
+ *
+ * Other significant limitations:
+ *
+ * - All agents using the crowd manager will use the same #dtQueryFilter. - Crowd management is relatively expensive.
+ * The maximum agents under crowd management at any one time is between 20 and 30. A good place to start is a maximum of
+ * 25 agents for 0.5ms per frame.
+ *
+ * @note This is a summary list of members. Use the index or search feature to find minor members.
+ *
+ * @struct dtCrowdAgentParams
+ * @see CrowdAgent, Crowd::AddAgent(), Crowd::UpdateAgentParameters()
+ *
+ * @var dtCrowdAgentParams::obstacleAvoidanceType
+ * @par
+ *
+ * 		#dtCrowd permits agents to use different avoidance configurations. This value is the index of the
+ *      #dtObstacleAvoidanceParams within the crowd.
+ *
+ * @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams()
+ *
+ * @var dtCrowdAgentParams::collisionQueryRange
+ * @par
+ *
+ * 		Collision elements include other agents and navigation mesh boundaries.
+ *
+ *      This value is often based on the agent radius and/or maximum speed. E.g. radius * 8
+ *
+ * @var dtCrowdAgentParams::pathOptimizationRange
+ * @par
+ *
+ * 		Only applicable if #updateFlags includes the #DT_CROWD_OPTIMIZE_VIS flag.
+ *
+ *      This value is often based on the agent radius. E.g. radius * 30
+ *
+ * @see dtPathCorridor::OptimizePathVisibility()
+ *
+ * @var dtCrowdAgentParams::separationWeight
+ * @par
+ *
+ * 		A higher value will result in agents trying to stay farther away from each other at the cost of more difficult
+ *      steering in tight spaces.
+ *
+ */
+    /**
+     * This is the core class of the refs crowd module. See the refs crowd documentation for a summary of the crowd
+     * features. A common method for setting up the crowd is as follows: -# Allocate the crowd -# Set the avoidance
+     * configurations using #SetObstacleAvoidanceParams(). -# Add agents using #AddAgent() and make an initial movement
+     * request using #RequestMoveTarget(). A common process for managing the crowd is as follows: -# Call #Update() to allow
+     * the crowd to manage its agents. -# Retrieve agent information using #GetActiveAgents(). -# Make movement requests
+     * using #RequestMoveTarget() when movement goal changes. -# Repeat every frame. Some agent configuration settings can
+     * be updated using #UpdateAgentParameters(). But the crowd owns the agent position. So it is not possible to update an
+     * active agent's position. If agent position must be fed back into the crowd, the agent must be removed and re-added.
+     * Notes: - Path related information is available for newly added agents only after an #Update() has been performed. -
+     * Agent objects are kept in a pool and re-used. So it is important when using agent objects to check the value of
+     * #dtCrowdAgent::active to determine if the agent is actually in use or not. - This class is meant to provide 'local'
+     * movement. There is a limit of 256 polygons in the path corridor. So it is not meant to provide automatic pathfinding
+     * services over long distances.
+     *
+     * @see DtAllocCrowd(), DtFreeCrowd(), Init(), dtCrowdAgent
+     */
+    public class DtCrowd
+    {
+        /// The maximum number of corners a crowd agent will look ahead in the path.
+        /// This value is used for sizing the crowd agent corner buffers.
+        /// Due to the behavior of the crowd manager, the actual number of useful
+        /// corners will be one less than this number.
+        /// @ingroup crowd
+        public const int DT_CROWDAGENT_MAX_CORNERS = 4;
+
+        /// The maximum number of crowd avoidance configurations supported by the
+        /// crowd manager.
+        /// @ingroup crowd
+        /// @see dtObstacleAvoidanceParams, dtCrowd::SetObstacleAvoidanceParams(), dtCrowd::GetObstacleAvoidanceParams(),
+        /// dtCrowdAgentParams::obstacleAvoidanceType
+        public const int DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS = 8;
+
+        /// The maximum number of query filter types supported by the crowd manager.
+        /// @ingroup crowd
+        /// @see dtQueryFilter, dtCrowd::GetFilter() dtCrowd::GetEditableFilter(),
+        /// dtCrowdAgentParams::queryFilterType
+        public const int DT_CROWD_MAX_QUERY_FILTER_TYPE = 16;
+
+        private readonly RcAtomicInteger _agentId = new RcAtomicInteger();
+        private readonly List<DtCrowdAgent> _agents;
+        private readonly DtPathQueue _pathQ;
+        private readonly DtObstacleAvoidanceParams[] _obstacleQueryParams = new DtObstacleAvoidanceParams[DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS];
+        private readonly DtObstacleAvoidanceQuery _obstacleQuery;
+        private DtProximityGrid _grid;
+        private readonly RcVec3f _ext = new RcVec3f();
+        private readonly IDtQueryFilter[] _filters = new IDtQueryFilter[DT_CROWD_MAX_QUERY_FILTER_TYPE];
+        private DtNavMeshQuery _navQuery;
+        private DtNavMesh _navMesh;
+        private readonly DtCrowdConfig _config;
+        private readonly DtCrowdTelemetry _telemetry = new DtCrowdTelemetry();
+        private int _velocitySampleCount;
+
+        public DtCrowd(DtCrowdConfig config, DtNavMesh nav) :
+            this(config, nav, i => new DtQueryDefaultFilter())
+        {
+        }
+
+        public DtCrowd(DtCrowdConfig config, DtNavMesh nav, Func<int, IDtQueryFilter> queryFilterFactory)
+        {
+            _config = config;
+            _ext.Set(config.maxAgentRadius * 2.0f, config.maxAgentRadius * 1.5f, config.maxAgentRadius * 2.0f);
+
+            _obstacleQuery = new DtObstacleAvoidanceQuery(config.maxObstacleAvoidanceCircles, config.maxObstacleAvoidanceSegments);
+
+            for (int i = 0; i < DT_CROWD_MAX_QUERY_FILTER_TYPE; i++)
+            {
+                _filters[i] = queryFilterFactory.Invoke(i);
+            }
+
+            // Init obstacle query option.
+            for (int i = 0; i < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS; ++i)
+            {
+                _obstacleQueryParams[i] = new DtObstacleAvoidanceParams();
+            }
+
+            // Allocate temp buffer for merging paths.
+            _pathQ = new DtPathQueue(config);
+            _agents = new List<DtCrowdAgent>();
+
+            // The navQuery is mostly used for local searches, no need for large node pool.
+            _navMesh = nav;
+            _navQuery = new DtNavMeshQuery(nav);
+        }
+
+        public void SetNavMesh(DtNavMesh nav)
+        {
+            _navMesh = nav;
+            _navQuery = new DtNavMeshQuery(nav);
+        }
+
+        /// Sets the shared avoidance configuration for the specified index.
+        /// @param[in] idx The index. [Limits: 0 <= value <
+        /// #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
+        /// @param[in] option The new configuration.
+        public void SetObstacleAvoidanceParams(int idx, DtObstacleAvoidanceParams option)
+        {
+            if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+            {
+                _obstacleQueryParams[idx] = new DtObstacleAvoidanceParams(option);
+            }
+        }
+
+        /// Gets the shared avoidance configuration for the specified index.
+        /// @param[in] idx The index of the configuration to retreive.
+        /// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
+        /// @return The requested configuration.
+        public DtObstacleAvoidanceParams GetObstacleAvoidanceParams(int idx)
+        {
+            if (idx >= 0 && idx < DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS)
+            {
+                return _obstacleQueryParams[idx];
+            }
+
+            return null;
+        }
+
+        /// Updates the specified agent's configuration.
+        /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
+        /// @param[in] params The new agent configuration.
+        public void UpdateAgentParameters(DtCrowdAgent agent, DtCrowdAgentParams option)
+        {
+            agent.option = option;
+        }
+
+        /**
+     * Adds a new agent to the crowd.
+     *
+     * @param pos
+     *            The requested position of the agent. [(x, y, z)]
+     * @param params
+     *            The configuration of the agent.
+     * @return The newly created agent object
+     */
+        public DtCrowdAgent AddAgent(RcVec3f pos, DtCrowdAgentParams option)
+        {
+            DtCrowdAgent ag = new DtCrowdAgent(_agentId.GetAndIncrement());
+            _agents.Add(ag);
+            UpdateAgentParameters(ag, option);
+
+            // Find nearest position on navmesh and place the agent there.
+            var status = _navQuery.FindNearestPoly(pos, _ext, _filters[ag.option.queryFilterType], out var refs, out var nearestPt, out var _);
+            if (status.Failed())
+            {
+                nearestPt = pos;
+                refs = 0;
+            }
+
+            ag.corridor.Reset(refs, nearestPt);
+            ag.boundary.Reset();
+            ag.partial = false;
+
+            ag.topologyOptTime = 0;
+            ag.targetReplanTime = 0;
+
+            ag.dvel = RcVec3f.Zero;
+            ag.nvel = RcVec3f.Zero;
+            ag.vel = RcVec3f.Zero;
+            ag.npos = nearestPt;
+
+            ag.desiredSpeed = 0;
+
+            if (refs != 0)
+            {
+                ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING;
+            }
+            else
+            {
+                ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID;
+            }
+
+            ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE;
+
+            return ag;
+        }
+
+        /**
+     * Removes the agent from the crowd.
+     *
+     * @param agent
+     *            Agent to be removed
+     */
+        public void RemoveAgent(DtCrowdAgent agent)
+        {
+            _agents.Remove(agent);
+        }
+
+        private bool RequestMoveTargetReplan(DtCrowdAgent ag, long refs, RcVec3f pos)
+        {
+            ag.SetTarget(refs, pos);
+            ag.targetReplan = true;
+            return true;
+        }
+
+        /// Submits a new move request for the specified agent.
+        /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
+        /// @param[in] ref The position's polygon reference.
+        /// @param[in] pos The position within the polygon. [(x, y, z)]
+        /// @return True if the request was successfully submitted.
+        ///
+        /// This method is used when a new target is set.
+        ///
+        /// The position will be constrained to the surface of the navigation mesh.
+        ///
+        /// The request will be processed during the next #Update().
+        public bool RequestMoveTarget(DtCrowdAgent agent, long refs, RcVec3f pos)
+        {
+            if (refs == 0)
+            {
+                return false;
+            }
+
+            // Initialize request.
+            agent.SetTarget(refs, pos);
+            agent.targetReplan = false;
+            return true;
+        }
+
+        /// Submits a new move request for the specified agent.
+        /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
+        /// @param[in] vel The movement velocity. [(x, y, z)]
+        /// @return True if the request was successfully submitted.
+        public bool RequestMoveVelocity(DtCrowdAgent agent, RcVec3f vel)
+        {
+            // Initialize request.
+            agent.targetRef = 0;
+            agent.targetPos = vel;
+            agent.targetPathQueryResult = null;
+            agent.targetReplan = false;
+            agent.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY;
+
+            return true;
+        }
+
+        /// Resets any request for the specified agent.
+        /// @param[in] idx The agent index. [Limits: 0 <= value < #GetAgentCount()]
+        /// @return True if the request was successfully reseted.
+        public bool ResetMoveTarget(DtCrowdAgent agent)
+        {
+            // Initialize request.
+            agent.targetRef = 0;
+            agent.targetPos = RcVec3f.Zero;
+            agent.dvel = RcVec3f.Zero;
+            agent.targetPathQueryResult = null;
+            agent.targetReplan = false;
+            agent.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE;
+            return true;
+        }
+
+        /**
+     * Gets the active agents int the agent pool.
+     *
+     * @return List of active agents
+     */
+        public IList<DtCrowdAgent> GetActiveAgents()
+        {
+            return _agents;
+        }
+
+        public RcVec3f GetQueryExtents()
+        {
+            return _ext;
+        }
+
+        public IDtQueryFilter GetFilter(int i)
+        {
+            return i >= 0 && i < DT_CROWD_MAX_QUERY_FILTER_TYPE ? _filters[i] : null;
+        }
+
+        public DtProximityGrid GetGrid()
+        {
+            return _grid;
+        }
+
+        public DtPathQueue GetPathQueue()
+        {
+            return _pathQ;
+        }
+
+        public DtCrowdTelemetry Telemetry()
+        {
+            return _telemetry;
+        }
+
+        public DtCrowdConfig Config()
+        {
+            return _config;
+        }
+
+        public DtCrowdTelemetry Update(float dt, DtCrowdAgentDebugInfo debug)
+        {
+            _velocitySampleCount = 0;
+
+            _telemetry.Start();
+
+            IList<DtCrowdAgent> agents = GetActiveAgents();
+
+            // Check that all agents still have valid paths.
+            CheckPathValidity(agents, dt);
+
+            // Update async move request and path finder.
+            UpdateMoveRequest(agents, dt);
+
+            // Optimize path topology.
+            UpdateTopologyOptimization(agents, dt);
+
+            // Register agents to proximity grid.
+            BuildProximityGrid(agents);
+
+            // Get nearby navmesh segments and agents to collide with.
+            BuildNeighbours(agents);
+
+            // Find next corner to steer to.
+            FindCorners(agents, debug);
+
+            // Trigger off-mesh connections (depends on corners).
+            TriggerOffMeshConnections(agents);
+
+            // Calculate steering.
+            CalculateSteering(agents);
+
+            // Velocity planning.
+            PlanVelocity(debug, agents);
+
+            // Integrate.
+            Integrate(dt, agents);
+
+            // Handle collisions.
+            HandleCollisions(agents);
+
+            MoveAgents(agents);
+
+            // Update agents using off-mesh connection.
+            UpdateOffMeshConnections(agents, dt);
+            return _telemetry;
+        }
+
+
+        private void CheckPathValidity(IList<DtCrowdAgent> agents, float dt)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CheckPathValidity);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                ag.targetReplanTime += dt;
+
+                bool replan = false;
+
+                // First check that the current location is valid.
+                RcVec3f agentPos = new RcVec3f();
+                long agentRef = ag.corridor.GetFirstPoly();
+                agentPos = ag.npos;
+                if (!_navQuery.IsValidPolyRef(agentRef, _filters[ag.option.queryFilterType]))
+                {
+                    // Current location is not valid, try to reposition.
+                    // TODO: this can snap agents, how to handle that?
+                    _navQuery.FindNearestPoly(ag.npos, _ext, _filters[ag.option.queryFilterType], out agentRef, out var nearestPt, out var _);
+                    agentPos = nearestPt;
+
+                    if (agentRef == 0)
+                    {
+                        // Could not find location in navmesh, set state to invalid.
+                        ag.corridor.Reset(0, agentPos);
+                        ag.partial = false;
+                        ag.boundary.Reset();
+                        ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID;
+                        continue;
+                    }
+
+                    // Make sure the first polygon is valid, but leave other valid
+                    // polygons in the path so that replanner can adjust the path
+                    // better.
+                    ag.corridor.FixPathStart(agentRef, agentPos);
+                    // ag.corridor.TrimInvalidPath(agentRef, agentPos, m_navquery,
+                    // &m_filter);
+                    ag.boundary.Reset();
+                    ag.npos = agentPos;
+
+                    replan = true;
+                }
+
+                // If the agent does not have move target or is controlled by
+                // velocity, no need to recover the target nor replan.
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    continue;
+                }
+
+                // Try to recover move request position.
+                if (ag.targetState != DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    && ag.targetState != DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED)
+                {
+                    if (!_navQuery.IsValidPolyRef(ag.targetRef, _filters[ag.option.queryFilterType]))
+                    {
+                        // Current target is not valid, try to reposition.
+                        _navQuery.FindNearestPoly(ag.targetPos, _ext, _filters[ag.option.queryFilterType], out ag.targetRef, out var nearestPt, out var _);
+                        ag.targetPos = nearestPt;
+                        replan = true;
+                    }
+
+                    if (ag.targetRef == 0)
+                    {
+                        // Failed to reposition target, fail moverequest.
+                        ag.corridor.Reset(agentRef, agentPos);
+                        ag.partial = false;
+                        ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE;
+                    }
+                }
+
+                // If nearby corridor is not valid, replan.
+                if (!ag.corridor.IsValid(_config.checkLookAhead, _navQuery, _filters[ag.option.queryFilterType]))
+                {
+                    // Fix current path.
+                    // ag.corridor.TrimInvalidPath(agentRef, agentPos, m_navquery,
+                    // &m_filter);
+                    // ag.boundary.Reset();
+                    replan = true;
+                }
+
+                // If the end of the path is near and it is not the requested
+                // location, replan.
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID)
+                {
+                    if (ag.targetReplanTime > _config.targetReplanDelay && ag.corridor.GetPathCount() < _config.checkLookAhead
+                                                                        && ag.corridor.GetLastPoly() != ag.targetRef)
+                    {
+                        replan = true;
+                    }
+                }
+
+                // Try to replan path to goal.
+                if (replan)
+                {
+                    if (ag.targetState != DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE)
+                    {
+                        RequestMoveTargetReplan(ag, ag.targetRef, ag.targetPos);
+                    }
+                }
+            }
+        }
+
+        private void UpdateMoveRequest(IList<DtCrowdAgent> agents, float dt)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateMoveRequest);
+
+            RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.targetReplanTime.CompareTo(a1.targetReplanTime));
+
+            // Fire off new requests.
+            List<long> reqPath = new List<long>();
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state == DtCrowdAgentState.DT_CROWDAGENT_STATE_INVALID)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING)
+                {
+                    List<long> path = ag.corridor.GetPath();
+                    if (0 == path.Count)
+                    {
+                        throw new ArgumentException("Empty path");
+                    }
+
+
+                    // Quick search towards the goal.
+                    _navQuery.InitSlicedFindPath(path[0], ag.targetRef, ag.npos, ag.targetPos,
+                        _filters[ag.option.queryFilterType], 0);
+                    _navQuery.UpdateSlicedFindPath(_config.maxTargetFindPathIterations, out var _);
+
+                    DtStatus status;
+                    if (ag.targetReplan) // && npath > 10)
+                    {
+                        // Try to use existing steady path during replan if possible.
+                        status = _navQuery.FinalizeSlicedFindPathPartial(path, ref reqPath);
+                    }
+                    else
+                    {
+                        // Try to move towards target when goal changes.
+                        status = _navQuery.FinalizeSlicedFindPath(ref reqPath);
+                    }
+
+                    RcVec3f reqPos = new RcVec3f();
+                    if (status.Succeeded() && reqPath.Count > 0)
+                    {
+                        // In progress or succeed.
+                        if (reqPath[reqPath.Count - 1] != ag.targetRef)
+                        {
+                            // Partial path, constrain target position inside the
+                            // last polygon.
+                            var cr = _navQuery.ClosestPointOnPoly(reqPath[reqPath.Count - 1], ag.targetPos, out reqPos, out var _);
+                            if (cr.Failed())
+                            {
+                                reqPath = new List<long>();
+                            }
+                        }
+                        else
+                        {
+                            reqPos = ag.targetPos;
+                        }
+                    }
+                    else
+                    {
+                        // Could not find path, start the request from current
+                        // location.
+                        reqPos = ag.npos;
+                        reqPath = new List<long>();
+                        reqPath.Add(path[0]);
+                    }
+
+                    ag.corridor.SetCorridor(reqPos, reqPath);
+                    ag.boundary.Reset();
+                    ag.partial = false;
+
+                    if (reqPath[reqPath.Count - 1] == ag.targetRef)
+                    {
+                        ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID;
+                        ag.targetReplanTime = 0;
+                    }
+                    else
+                    {
+                        // The path is longer or potentially unreachable, full plan.
+                        ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE;
+                    }
+
+                    ag.targetReplanWaitTime = 0;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE)
+                {
+                    queue.Enqueue(ag);
+                }
+            }
+
+            while (!queue.IsEmpty())
+            {
+                DtCrowdAgent ag = queue.Dequeue();
+                ag.targetPathQueryResult = _pathQ.Request(ag.corridor.GetLastPoly(), ag.targetRef, ag.corridor.GetTarget(), ag.targetPos, _filters[ag.option.queryFilterType]);
+                if (ag.targetPathQueryResult != null)
+                {
+                    ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH;
+                }
+                else
+                {
+                    _telemetry.RecordMaxTimeToEnqueueRequest(ag.targetReplanWaitTime);
+                    ag.targetReplanWaitTime += dt;
+                }
+            }
+
+            // Update requests.
+            using (var timer2 = _telemetry.ScopedTimer(DtCrowdTimerLabel.PathQueueUpdate))
+            {
+                _pathQ.Update(_navMesh);
+            }
+
+            // Process path results.
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_WAITING_FOR_PATH)
+                {
+                    // _telemetry.RecordPathWaitTime(ag.targetReplanTime);
+                    // Poll path queue.
+                    DtStatus status = ag.targetPathQueryResult.status;
+                    if (status.Failed())
+                    {
+                        // Path find failed, retry if the target location is still
+                        // valid.
+                        ag.targetPathQueryResult = null;
+                        if (ag.targetRef != 0)
+                        {
+                            ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
+                        }
+                        else
+                        {
+                            ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
+                        }
+
+                        ag.targetReplanTime = 0;
+                    }
+                    else if (status.Succeeded())
+                    {
+                        List<long> path = ag.corridor.GetPath();
+                        if (0 == path.Count)
+                        {
+                            throw new ArgumentException("Empty path");
+                        }
+
+                        // Apply results.
+                        var targetPos = ag.targetPos;
+
+                        bool valid = true;
+                        List<long> res = ag.targetPathQueryResult.path;
+                        if (status.Failed() || 0 == res.Count)
+                        {
+                            valid = false;
+                        }
+
+                        if (status.IsPartial())
+                        {
+                            ag.partial = true;
+                        }
+                        else
+                        {
+                            ag.partial = false;
+                        }
+
+                        // Merge result and existing path.
+                        // The agent might have moved whilst the request is
+                        // being processed, so the path may have changed.
+                        // We assume that the end of the path is at the same
+                        // location
+                        // where the request was issued.
+
+                        // The last ref in the old path should be the same as
+                        // the location where the request was issued..
+                        if (valid && path[path.Count - 1] != res[0])
+                        {
+                            valid = false;
+                        }
+
+                        if (valid)
+                        {
+                            // Put the old path infront of the old path.
+                            if (path.Count > 1)
+                            {
+                                path.RemoveAt(path.Count - 1);
+                                path.AddRange(res);
+                                res = path;
+                                // Remove trackbacks
+                                for (int j = 1; j < res.Count - 1; ++j)
+                                {
+                                    if (j - 1 >= 0 && j + 1 < res.Count)
+                                    {
+                                        if (res[j - 1] == res[j + 1])
+                                        {
+                                            res.RemoveAt(j + 1);
+                                            res.RemoveAt(j);
+                                            j -= 2;
+                                        }
+                                    }
+                                }
+                            }
+
+                            // Check for partial path.
+                            if (res[res.Count - 1] != ag.targetRef)
+                            {
+                                // Partial path, constrain target position inside
+                                // the last polygon.
+                                var cr = _navQuery.ClosestPointOnPoly(res[res.Count - 1], targetPos, out var nearest, out var _);
+                                if (cr.Succeeded())
+                                {
+                                    targetPos = nearest;
+                                }
+                                else
+                                {
+                                    valid = false;
+                                }
+                            }
+                        }
+
+                        if (valid)
+                        {
+                            // Set current corridor.
+                            ag.corridor.SetCorridor(targetPos, res);
+                            // Force to update boundary.
+                            ag.boundary.Reset();
+                            ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_VALID;
+                        }
+                        else
+                        {
+                            // Something went wrong.
+                            ag.targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
+                        }
+
+                        ag.targetReplanTime = 0;
+                    }
+
+                    _telemetry.RecordMaxTimeToFindPath(ag.targetReplanWaitTime);
+                    ag.targetReplanWaitTime += dt;
+                }
+            }
+        }
+
+        private void UpdateTopologyOptimization(IList<DtCrowdAgent> agents, float dt)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateTopologyOptimization);
+
+            RcSortedQueue<DtCrowdAgent> queue = new RcSortedQueue<DtCrowdAgent>((a1, a2) => a2.topologyOptTime.CompareTo(a1.topologyOptTime));
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    continue;
+                }
+
+                if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_OPTIMIZE_TOPO) == 0)
+                {
+                    continue;
+                }
+
+                ag.topologyOptTime += dt;
+                if (ag.topologyOptTime >= _config.topologyOptimizationTimeThreshold)
+                {
+                    queue.Enqueue(ag);
+                }
+            }
+
+            while (!queue.IsEmpty())
+            {
+                DtCrowdAgent ag = queue.Dequeue();
+                ag.corridor.OptimizePathTopology(_navQuery, _filters[ag.option.queryFilterType], _config.maxTopologyOptimizationIterations);
+                ag.topologyOptTime = 0;
+            }
+        }
+
+        private void BuildProximityGrid(IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildProximityGrid);
+
+            _grid = new DtProximityGrid(_config.maxAgentRadius * 3);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                RcVec3f p = ag.npos;
+                float r = ag.option.radius;
+                _grid.AddItem(ag, p.x - r, p.z - r, p.x + r, p.z + r);
+            }
+        }
+
+        private void BuildNeighbours(IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.BuildNeighbours);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                // Update the collision boundary after certain distance has been passed or
+                // if it has become invalid.
+                float updateThr = ag.option.collisionQueryRange * 0.25f;
+                if (RcVec3f.Dist2DSqr(ag.npos, ag.boundary.GetCenter()) > Sqr(updateThr)
+                    || !ag.boundary.IsValid(_navQuery, _filters[ag.option.queryFilterType]))
+                {
+                    ag.boundary.Update(ag.corridor.GetFirstPoly(), ag.npos, ag.option.collisionQueryRange, _navQuery,
+                        _filters[ag.option.queryFilterType]);
+                }
+
+                // Query neighbour agents
+                GetNeighbours(ag.npos, ag.option.height, ag.option.collisionQueryRange, ag, ref ag.neis, _grid);
+            }
+        }
+
+
+        private int GetNeighbours(RcVec3f pos, float height, float range, DtCrowdAgent skip, ref List<DtCrowdNeighbour> result, DtProximityGrid grid)
+        {
+            result.Clear();
+
+            var proxAgents = new HashSet<DtCrowdAgent>();
+            int nids = grid.QueryItems(pos.x - range, pos.z - range, pos.x + range, pos.z + range, ref proxAgents);
+            foreach (DtCrowdAgent ag in proxAgents)
+            {
+                if (ag == skip)
+                {
+                    continue;
+                }
+
+                // Check for overlap.
+                RcVec3f diff = pos.Subtract(ag.npos);
+                if (Math.Abs(diff.y) >= (height + ag.option.height) / 2.0f)
+                {
+                    continue;
+                }
+
+                diff.y = 0;
+                float distSqr = RcVec3f.LenSqr(diff);
+                if (distSqr > Sqr(range))
+                {
+                    continue;
+                }
+
+                result.Add(new DtCrowdNeighbour(ag, distSqr));
+            }
+
+            result.Sort((o1, o2) => o1.dist.CompareTo(o2.dist));
+            return result.Count;
+        }
+
+        private void FindCorners(IList<DtCrowdAgent> agents, DtCrowdAgentDebugInfo debug)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.FindCorners);
+
+            DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    continue;
+                }
+
+                // Find corners for steering
+                ag.corridor.FindCorners(ref ag.corners, DT_CROWDAGENT_MAX_CORNERS, _navQuery, _filters[ag.option.queryFilterType]);
+
+                // Check to see if the corner after the next corner is directly visible,
+                // and short cut to there.
+                if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_OPTIMIZE_VIS) != 0 && ag.corners.Count > 0)
+                {
+                    RcVec3f target = ag.corners[Math.Min(1, ag.corners.Count - 1)].pos;
+                    ag.corridor.OptimizePathVisibility(target, ag.option.pathOptimizationRange, _navQuery,
+                        _filters[ag.option.queryFilterType]);
+
+                    // Copy data for debug purposes.
+                    if (debugAgent == ag)
+                    {
+                        debug.optStart = ag.corridor.GetPos();
+                        debug.optEnd = target;
+                    }
+                }
+                else
+                {
+                    // Copy data for debug purposes.
+                    if (debugAgent == ag)
+                    {
+                        debug.optStart = RcVec3f.Zero;
+                        debug.optEnd = RcVec3f.Zero;
+                    }
+                }
+            }
+        }
+
+        private void TriggerOffMeshConnections(IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.TriggerOffMeshConnections);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    continue;
+                }
+
+                // Check
+                float triggerRadius = ag.option.radius * 2.25f;
+                if (ag.OverOffmeshConnection(triggerRadius))
+                {
+                    // Prepare to off-mesh connection.
+                    DtCrowdAgentAnimation anim = ag.animation;
+
+                    // Adjust the path over the off-mesh connection.
+                    long[] refs = new long[2];
+                    if (ag.corridor.MoveOverOffmeshConnection(ag.corners[ag.corners.Count - 1].refs, refs, ref anim.startPos,
+                            ref anim.endPos, _navQuery))
+                    {
+                        anim.initPos = ag.npos;
+                        anim.polyRef = refs[1];
+                        anim.active = true;
+                        anim.t = 0.0f;
+                        anim.tmax = (RcVec3f.Dist2D(anim.startPos, anim.endPos) / ag.option.maxSpeed) * 0.5f;
+
+                        ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_OFFMESH;
+                        ag.corners.Clear();
+                        ag.neis.Clear();
+                        continue;
+                    }
+                    else
+                    {
+                        // Path validity check will ensure that bad/blocked connections will be replanned.
+                    }
+                }
+            }
+        }
+
+        private void CalculateSteering(IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.CalculateSteering);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE)
+                {
+                    continue;
+                }
+
+                RcVec3f dvel = new RcVec3f();
+
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    dvel = ag.targetPos;
+                    ag.desiredSpeed = ag.targetPos.Length();
+                }
+                else
+                {
+                    // Calculate steering direction.
+                    if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_ANTICIPATE_TURNS) != 0)
+                    {
+                        dvel = ag.CalcSmoothSteerDirection();
+                    }
+                    else
+                    {
+                        dvel = ag.CalcStraightSteerDirection();
+                    }
+
+                    // Calculate speed scale, which tells the agent to slowdown at the end of the path.
+                    float slowDownRadius = ag.option.radius * 2; // TODO: make less hacky.
+                    float speedScale = ag.GetDistanceToGoal(slowDownRadius) / slowDownRadius;
+
+                    ag.desiredSpeed = ag.option.maxSpeed;
+                    dvel = dvel.Scale(ag.desiredSpeed * speedScale);
+                }
+
+                // Separation
+                if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_SEPARATION) != 0)
+                {
+                    float separationDist = ag.option.collisionQueryRange;
+                    float invSeparationDist = 1.0f / separationDist;
+                    float separationWeight = ag.option.separationWeight;
+
+                    float w = 0;
+                    RcVec3f disp = new RcVec3f();
+
+                    for (int j = 0; j < ag.neis.Count; ++j)
+                    {
+                        DtCrowdAgent nei = ag.neis[j].agent;
+
+                        RcVec3f diff = ag.npos.Subtract(nei.npos);
+                        diff.y = 0;
+
+                        float distSqr = RcVec3f.LenSqr(diff);
+                        if (distSqr < 0.00001f)
+                        {
+                            continue;
+                        }
+
+                        if (distSqr > Sqr(separationDist))
+                        {
+                            continue;
+                        }
+
+                        float dist = (float)Math.Sqrt(distSqr);
+                        float weight = separationWeight * (1.0f - Sqr(dist * invSeparationDist));
+
+                        disp = RcVec3f.Mad(disp, diff, weight / dist);
+                        w += 1.0f;
+                    }
+
+                    if (w > 0.0001f)
+                    {
+                        // Adjust desired velocity.
+                        dvel = RcVec3f.Mad(dvel, disp, 1.0f / w);
+                        // Clamp desired velocity to desired speed.
+                        float speedSqr = RcVec3f.LenSqr(dvel);
+                        float desiredSqr = Sqr(ag.desiredSpeed);
+                        if (speedSqr > desiredSqr)
+                        {
+                            dvel = dvel.Scale(desiredSqr / speedSqr);
+                        }
+                    }
+                }
+
+                // Set the desired velocity.
+                ag.dvel = dvel;
+            }
+        }
+
+        private void PlanVelocity(DtCrowdAgentDebugInfo debug, IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.PlanVelocity);
+
+            DtCrowdAgent debugAgent = debug != null ? debug.agent : null;
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                if ((ag.option.updateFlags & DtCrowdAgentParams.DT_CROWD_OBSTACLE_AVOIDANCE) != 0)
+                {
+                    _obstacleQuery.Reset();
+
+                    // Add neighbours as obstacles.
+                    for (int j = 0; j < ag.neis.Count; ++j)
+                    {
+                        DtCrowdAgent nei = ag.neis[j].agent;
+                        _obstacleQuery.AddCircle(nei.npos, nei.option.radius, nei.vel, nei.dvel);
+                    }
+
+                    // Append neighbour segments as obstacles.
+                    for (int j = 0; j < ag.boundary.GetSegmentCount(); ++j)
+                    {
+                        RcVec3f[] s = ag.boundary.GetSegment(j);
+                        RcVec3f s3 = s[1];
+                        //Array.Copy(s, 3, s3, 0, 3);
+                        if (DtUtils.TriArea2D(ag.npos, s[0], s3) < 0.0f)
+                        {
+                            continue;
+                        }
+
+                        _obstacleQuery.AddSegment(s[0], s3);
+                    }
+
+                    DtObstacleAvoidanceDebugData vod = null;
+                    if (debugAgent == ag)
+                    {
+                        vod = debug.vod;
+                    }
+
+                    // Sample new safe velocity.
+                    bool adaptive = true;
+                    int ns = 0;
+
+                    DtObstacleAvoidanceParams option = _obstacleQueryParams[ag.option.obstacleAvoidanceType];
+
+                    if (adaptive)
+                    {
+                        ns = _obstacleQuery.SampleVelocityAdaptive(ag.npos, ag.option.radius, ag.desiredSpeed,
+                            ag.vel, ag.dvel, out ag.nvel, option, vod);
+                    }
+                    else
+                    {
+                        ns = _obstacleQuery.SampleVelocityGrid(ag.npos, ag.option.radius,
+                            ag.desiredSpeed, ag.vel, ag.dvel, out ag.nvel, option, vod);
+                    }
+
+                    _velocitySampleCount += ns;
+                }
+                else
+                {
+                    // If not using velocity planning, new velocity is directly the desired velocity.
+                    ag.nvel = ag.dvel;
+                }
+            }
+        }
+
+        private void Integrate(float dt, IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.Integrate);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                ag.Integrate(dt);
+            }
+        }
+
+        private void HandleCollisions(IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.HandleCollisions);
+
+            for (int iter = 0; iter < 4; ++iter)
+            {
+                foreach (DtCrowdAgent ag in agents)
+                {
+                    long idx0 = ag.idx;
+                    if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                    {
+                        continue;
+                    }
+
+                    ag.disp = RcVec3f.Zero;
+
+                    float w = 0;
+
+                    for (int j = 0; j < ag.neis.Count; ++j)
+                    {
+                        DtCrowdAgent nei = ag.neis[j].agent;
+                        long idx1 = nei.idx;
+                        RcVec3f diff = ag.npos.Subtract(nei.npos);
+                        diff.y = 0;
+
+                        float dist = RcVec3f.LenSqr(diff);
+                        if (dist > Sqr(ag.option.radius + nei.option.radius))
+                        {
+                            continue;
+                        }
+
+                        dist = (float)Math.Sqrt(dist);
+                        float pen = (ag.option.radius + nei.option.radius) - dist;
+                        if (dist < 0.0001f)
+                        {
+                            // Agents on top of each other, try to choose diverging separation directions.
+                            if (idx0 > idx1)
+                            {
+                                diff.Set(-ag.dvel.z, 0, ag.dvel.x);
+                            }
+                            else
+                            {
+                                diff.Set(ag.dvel.z, 0, -ag.dvel.x);
+                            }
+
+                            pen = 0.01f;
+                        }
+                        else
+                        {
+                            pen = (1.0f / dist) * (pen * 0.5f) * _config.collisionResolveFactor;
+                        }
+
+                        ag.disp = RcVec3f.Mad(ag.disp, diff, pen);
+
+                        w += 1.0f;
+                    }
+
+                    if (w > 0.0001f)
+                    {
+                        float iw = 1.0f / w;
+                        ag.disp = ag.disp.Scale(iw);
+                    }
+                }
+
+                foreach (DtCrowdAgent ag in agents)
+                {
+                    if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                    {
+                        continue;
+                    }
+
+                    ag.npos = ag.npos.Add(ag.disp);
+                }
+            }
+        }
+
+        private void MoveAgents(IList<DtCrowdAgent> agents)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.MoveAgents);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                if (ag.state != DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING)
+                {
+                    continue;
+                }
+
+                // Move along navmesh.
+                ag.corridor.MovePosition(ag.npos, _navQuery, _filters[ag.option.queryFilterType]);
+                // Get valid constrained position back.
+                ag.npos = ag.corridor.GetPos();
+
+                // If not using path, truncate the corridor to just one poly.
+                if (ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_NONE
+                    || ag.targetState == DtMoveRequestState.DT_CROWDAGENT_TARGET_VELOCITY)
+                {
+                    ag.corridor.Reset(ag.corridor.GetFirstPoly(), ag.npos);
+                    ag.partial = false;
+                }
+            }
+        }
+
+        private void UpdateOffMeshConnections(IList<DtCrowdAgent> agents, float dt)
+        {
+            using var timer = _telemetry.ScopedTimer(DtCrowdTimerLabel.UpdateOffMeshConnections);
+
+            foreach (DtCrowdAgent ag in agents)
+            {
+                DtCrowdAgentAnimation anim = ag.animation;
+                if (!anim.active)
+                {
+                    continue;
+                }
+
+                anim.t += dt;
+                if (anim.t > anim.tmax)
+                {
+                    // Reset animation
+                    anim.active = false;
+                    // Prepare agent for walking.
+                    ag.state = DtCrowdAgentState.DT_CROWDAGENT_STATE_WALKING;
+                    continue;
+                }
+
+                // Update position
+                float ta = anim.tmax * 0.15f;
+                float tb = anim.tmax;
+                if (anim.t < ta)
+                {
+                    float u = Tween(anim.t, 0.0f, ta);
+                    ag.npos = RcVec3f.Lerp(anim.initPos, anim.startPos, u);
+                }
+                else
+                {
+                    float u = Tween(anim.t, ta, tb);
+                    ag.npos = RcVec3f.Lerp(anim.startPos, anim.endPos, u);
+                }
+
+                // Update velocity.
+                ag.vel = RcVec3f.Zero;
+                ag.dvel = RcVec3f.Zero;
+            }
+        }
+
+        private float Tween(float t, float t0, float t1)
+        {
+            return Clamp((t - t0) / (t1 - t0), 0.0f, 1.0f);
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowd.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7e6a7f8cce5d12d408924d01e46265b2
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 217 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgent.cs

@@ -0,0 +1,217 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Collections.Generic;
+using DotRecast.Core;
+
+namespace DotRecast.Detour.Crowd
+{
+    /// Represents an agent managed by a #dtCrowd object.
+    /// @ingroup crowd
+    public class DtCrowdAgent
+    {
+        public readonly long idx;
+
+        /// The type of mesh polygon the agent is traversing. (See: #CrowdAgentState)
+        public DtCrowdAgentState state;
+
+        /// True if the agent has valid path (targetState == DT_CROWDAGENT_TARGET_VALID) and the path does not lead to the
+        /// requested position, else false.
+        public bool partial;
+
+        /// The path corridor the agent is using.
+        public DtPathCorridor corridor;
+
+        /// The local boundary data for the agent.
+        public DtLocalBoundary boundary;
+
+        /// Time since the agent's path corridor was optimized.
+        public float topologyOptTime;
+
+        /// The known neighbors of the agent.
+        public List<DtCrowdNeighbour> neis = new List<DtCrowdNeighbour>();
+
+        /// The desired speed.
+        public float desiredSpeed;
+
+        public RcVec3f npos = new RcVec3f();
+
+        /// < The current agent position. [(x, y, z)]
+        public RcVec3f disp = new RcVec3f();
+
+        /// < A temporary value used to accumulate agent displacement during iterative
+        /// collision resolution. [(x, y, z)]
+        public RcVec3f dvel = new RcVec3f();
+
+        /// < The desired velocity of the agent. Based on the current path, calculated
+        /// from
+        /// scratch each frame. [(x, y, z)]
+        public RcVec3f nvel = new RcVec3f();
+
+        /// < The desired velocity adjusted by obstacle avoidance, calculated from scratch each
+        /// frame. [(x, y, z)]
+        public RcVec3f vel = new RcVec3f();
+
+        /// < The actual velocity of the agent. The change from nvel -> vel is
+        /// constrained by max acceleration. [(x, y, z)]
+        /// The agent's configuration parameters.
+        public DtCrowdAgentParams option;
+
+        /// The local path corridor corners for the agent.
+        public List<StraightPathItem> corners = new List<StraightPathItem>();
+
+        public DtMoveRequestState targetState;
+
+        /// < State of the movement request.
+        public long targetRef;
+
+        /// < Target polyref of the movement request.
+        public RcVec3f targetPos = new RcVec3f();
+
+        /// < Target position of the movement request (or velocity in case of
+        /// DT_CROWDAGENT_TARGET_VELOCITY).
+        public DtPathQueryResult targetPathQueryResult;
+
+        /// < Path finder query
+        public bool targetReplan;
+
+        /// < Flag indicating that the current path is being replanned.
+        public float targetReplanTime;
+
+        /// <Time since the agent's target was replanned.
+        public float targetReplanWaitTime;
+
+        public DtCrowdAgentAnimation animation;
+
+        public DtCrowdAgent(int idx)
+        {
+            this.idx = idx;
+            corridor = new DtPathCorridor();
+            boundary = new DtLocalBoundary();
+            animation = new DtCrowdAgentAnimation();
+        }
+
+        public void Integrate(float dt)
+        {
+            // Fake dynamic constraint.
+            float maxDelta = option.maxAcceleration * dt;
+            RcVec3f dv = nvel.Subtract(vel);
+            float ds = dv.Length();
+            if (ds > maxDelta)
+                dv = dv.Scale(maxDelta / ds);
+            vel = vel.Add(dv);
+
+            // Integrate
+            if (vel.Length() > 0.0001f)
+                npos = RcVec3f.Mad(npos, vel, dt);
+            else
+                vel = RcVec3f.Zero;
+        }
+
+        public bool OverOffmeshConnection(float radius)
+        {
+            if (0 == corners.Count)
+                return false;
+
+            bool offMeshConnection = ((corners[corners.Count - 1].flags
+                                       & DtNavMeshQuery.DT_STRAIGHTPATH_OFFMESH_CONNECTION) != 0)
+                ? true
+                : false;
+            if (offMeshConnection)
+            {
+                float distSq = RcVec3f.Dist2DSqr(npos, corners[corners.Count - 1].pos);
+                if (distSq < radius * radius)
+                    return true;
+            }
+
+            return false;
+        }
+
+        public float GetDistanceToGoal(float range)
+        {
+            if (0 == corners.Count)
+                return range;
+
+            bool endOfPath = ((corners[corners.Count - 1].flags & DtNavMeshQuery.DT_STRAIGHTPATH_END) != 0) ? true : false;
+            if (endOfPath)
+                return Math.Min(RcVec3f.Dist2D(npos, corners[corners.Count - 1].pos), range);
+
+            return range;
+        }
+
+        public RcVec3f CalcSmoothSteerDirection()
+        {
+            RcVec3f dir = new RcVec3f();
+            if (0 < corners.Count)
+            {
+                int ip0 = 0;
+                int ip1 = Math.Min(1, corners.Count - 1);
+                var p0 = corners[ip0].pos;
+                var p1 = corners[ip1].pos;
+
+                var dir0 = p0.Subtract(npos);
+                var dir1 = p1.Subtract(npos);
+                dir0.y = 0;
+                dir1.y = 0;
+
+                float len0 = dir0.Length();
+                float len1 = dir1.Length();
+                if (len1 > 0.001f)
+                    dir1 = dir1.Scale(1.0f / len1);
+
+                dir.x = dir0.x - dir1.x * len0 * 0.5f;
+                dir.y = 0;
+                dir.z = dir0.z - dir1.z * len0 * 0.5f;
+                dir.Normalize();
+            }
+
+            return dir;
+        }
+
+        public RcVec3f CalcStraightSteerDirection()
+        {
+            RcVec3f dir = new RcVec3f();
+            if (0 < corners.Count)
+            {
+                dir = corners[0].pos.Subtract(npos);
+                dir.y = 0;
+                dir.Normalize();
+            }
+
+            return dir;
+        }
+
+        public void SetTarget(long refs, RcVec3f pos)
+        {
+            targetRef = refs;
+            targetPos = pos;
+            targetPathQueryResult = null;
+            if (targetRef != 0)
+            {
+                targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_REQUESTING;
+            }
+            else
+            {
+                targetState = DtMoveRequestState.DT_CROWDAGENT_TARGET_FAILED;
+            }
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgent.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 89c61e08c52cd3949bfeffa23b138057
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 34 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs

@@ -0,0 +1,34 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using DotRecast.Core;
+
+namespace DotRecast.Detour.Crowd
+{
+    public class DtCrowdAgentAnimation
+    {
+        public bool active;
+        public RcVec3f initPos = new RcVec3f();
+        public RcVec3f startPos = new RcVec3f();
+        public RcVec3f endPos = new RcVec3f();
+        public long polyRef;
+        public float t, tmax;
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a1e86ad1e06248641b141b6e8d09d39f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 74 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentParams.cs

@@ -0,0 +1,74 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+namespace DotRecast.Detour.Crowd
+{
+    /// Configuration parameters for a crowd agent.
+    /// @ingroup crowd
+    public class DtCrowdAgentParams
+    {
+        /// < Agent radius. [Limit: >= 0]
+        public float radius;
+
+        /// < Agent height. [Limit: > 0]
+        public float height;
+
+        /// < Maximum allowed acceleration. [Limit: >= 0]
+        public float maxAcceleration;
+
+        /// < Maximum allowed speed. [Limit: >= 0]
+        public float maxSpeed;
+                
+        /// Defines how close a collision element must be before it is considered for steering behaviors. [Limits: > 0]
+        public float collisionQueryRange;
+
+        /// < The path visibility optimization range. [Limit: > 0]
+        public float pathOptimizationRange;
+                
+        /// How aggresive the agent manager should be at avoiding collisions with this agent. [Limit: >= 0]
+        public float separationWeight;
+
+        /// Crowd agent update flags.
+        public const int DT_CROWD_ANTICIPATE_TURNS = 1;
+
+        public const int DT_CROWD_OBSTACLE_AVOIDANCE = 2;
+        public const int DT_CROWD_SEPARATION = 4;
+        public const int DT_CROWD_OPTIMIZE_VIS = 8;
+
+        /// < Use #dtPathCorridor::OptimizePathVisibility() to optimize
+        /// the agent path.
+        public const int DT_CROWD_OPTIMIZE_TOPO = 16;
+
+        /// < Use dtPathCorridor::OptimizePathTopology() to optimize
+        /// the agent path.
+        /// Flags that impact steering behavior. (See: #UpdateFlags)
+        public int updateFlags;
+
+        /// The index of the avoidance configuration to use for the agent.
+        /// [Limits: 0 <= value < #DT_CROWD_MAX_OBSTAVOIDANCE_PARAMS]
+        public int obstacleAvoidanceType;
+
+        /// The index of the query filter used by this agent.
+        public int queryFilterType;
+
+        /// User defined data attached to the agent.
+        public object userData;
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentParams.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 812ebe2d1192fc64388c002a68a65971
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 15 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentState.cs

@@ -0,0 +1,15 @@
+namespace DotRecast.Detour.Crowd
+{
+    /// The type of navigation mesh polygon the agent is currently traversing.
+    /// @ingroup crowd
+    public enum DtCrowdAgentState
+    {
+        DT_CROWDAGENT_STATE_INVALID,
+
+        /// < The agent is not in a valid state.
+        DT_CROWDAGENT_STATE_WALKING,
+
+        /// < The agent is traversing a normal navigation mesh polygon.
+        DT_CROWDAGENT_STATE_OFFMESH, /// < The agent is traversing an off-mesh connection.
+    };
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdAgentState.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 04d5fec25112bcb429f2416ba81c3496
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 78 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdConfig.cs

@@ -0,0 +1,78 @@
+/*
+recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+namespace DotRecast.Detour.Crowd
+{
+    public class DtCrowdConfig
+    {
+        public readonly float maxAgentRadius;
+
+        /**
+     * Max number of path requests in the queue
+     */
+        public int pathQueueSize = 32;
+
+        /**
+     * Max number of sliced path finding iterations executed per update (used to handle longer paths and replans)
+     */
+        public int maxFindPathIterations = 100;
+
+        /**
+     * Max number of sliced path finding iterations executed per agent to find the initial path to target
+     */
+        public int maxTargetFindPathIterations = 20;
+
+        /**
+     * Min time between topology optimizations (in seconds)
+     */
+        public float topologyOptimizationTimeThreshold = 0.5f;
+
+        /**
+     * The number of polygons from the beginning of the corridor to check to ensure path validity
+     */
+        public int checkLookAhead = 10;
+
+        /**
+     * Min time between target re-planning (in seconds)
+     */
+        public float targetReplanDelay = 1.0f;
+
+        /**
+     * Max number of sliced path finding iterations executed per topology optimization per agent
+     */
+        public int maxTopologyOptimizationIterations = 32;
+
+        public float collisionResolveFactor = 0.7f;
+
+        /**
+     * Max number of neighbour agents to consider in obstacle avoidance processing
+     */
+        public int maxObstacleAvoidanceCircles = 6;
+
+        /**
+     * Max number of neighbour segments to consider in obstacle avoidance processing
+     */
+        public int maxObstacleAvoidanceSegments = 8;
+
+        public DtCrowdConfig(float maxAgentRadius)
+        {
+            this.maxAgentRadius = maxAgentRadius;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdConfig.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7b5955824d4988345b27f6bd211aa3e9
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 20 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdNeighbour.cs

@@ -0,0 +1,20 @@
+namespace DotRecast.Detour.Crowd
+{
+    /// Provides neighbor data for agents managed by the crowd.
+    /// @ingroup crowd
+    /// @see dtCrowdAgent::neis, dtCrowd
+    public readonly struct DtCrowdNeighbour
+    {
+        public readonly DtCrowdAgent agent;
+
+        /// < The index of the neighbor in the crowd.
+        public readonly float dist;
+
+        /// < The distance between the current agent and the neighbor.
+        public DtCrowdNeighbour(DtCrowdAgent agent, float dist)
+        {
+            this.agent = agent;
+            this.dist = dist;
+        }
+    };
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdNeighbour.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 99e999f2ef09ba2459f0258801b6186c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 102 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTelemetry.cs

@@ -0,0 +1,102 @@
+/*
+recast4j copyright (c) 2021 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection.Emit;
+using DotRecast.Core;
+
+namespace DotRecast.Detour.Crowd
+{
+    public class DtCrowdTelemetry
+    {
+        public const int TIMING_SAMPLES = 10;
+        private float _maxTimeToEnqueueRequest;
+        private float _maxTimeToFindPath;
+
+        private readonly Dictionary<DtCrowdTimerLabel, long> _executionTimings = new Dictionary<DtCrowdTimerLabel, long>();
+        private readonly Dictionary<DtCrowdTimerLabel, List<long>> _executionTimingSamples = new Dictionary<DtCrowdTimerLabel, List<long>>();
+
+        public float MaxTimeToEnqueueRequest()
+        {
+            return _maxTimeToEnqueueRequest;
+        }
+
+        public float MaxTimeToFindPath()
+        {
+            return _maxTimeToFindPath;
+        }
+
+        public List<RcTelemetryTick> ToExecutionTimings()
+        {
+            return _executionTimings
+                .Select(e => new RcTelemetryTick(e.Key.Label, e.Value))
+                .OrderByDescending(x => x.Ticks)
+                .ToList();
+        }
+
+        public void Start()
+        {
+            _maxTimeToEnqueueRequest = 0;
+            _maxTimeToFindPath = 0;
+            _executionTimings.Clear();
+        }
+
+        public void RecordMaxTimeToEnqueueRequest(float time)
+        {
+            _maxTimeToEnqueueRequest = Math.Max(_maxTimeToEnqueueRequest, time);
+        }
+
+        public void RecordMaxTimeToFindPath(float time)
+        {
+            _maxTimeToFindPath = Math.Max(_maxTimeToFindPath, time);
+        }
+
+        public IDisposable ScopedTimer(DtCrowdTimerLabel label)
+        {
+            Start(label);
+            return new RcAnonymousDisposable(() => Stop(label));
+        }
+
+        private void Start(DtCrowdTimerLabel name)
+        {
+            _executionTimings.Add(name, RcFrequency.Ticks);
+        }
+
+        private void Stop(DtCrowdTimerLabel name)
+        {
+            long duration = RcFrequency.Ticks - _executionTimings[name];
+            if (!_executionTimingSamples.TryGetValue(name, out var s))
+            {
+                s = new List<long>();
+                _executionTimingSamples.Add(name, s);
+            }
+
+            if (s.Count == TIMING_SAMPLES)
+            {
+                s.RemoveAt(0);
+            }
+
+            s.Add(duration);
+            _executionTimings[name] = (long)s.Average();
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTelemetry.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3ff0f60602e9ff548b19c4fc277db0ad
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 27 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTimerLabel.cs

@@ -0,0 +1,27 @@
+namespace DotRecast.Detour.Crowd
+{
+    public class DtCrowdTimerLabel
+    {
+        public static readonly DtCrowdTimerLabel CheckPathValidity = new DtCrowdTimerLabel(nameof(CheckPathValidity));
+        public static readonly DtCrowdTimerLabel UpdateMoveRequest = new DtCrowdTimerLabel(nameof(UpdateMoveRequest));
+        public static readonly DtCrowdTimerLabel PathQueueUpdate = new DtCrowdTimerLabel(nameof(PathQueueUpdate));
+        public static readonly DtCrowdTimerLabel UpdateTopologyOptimization = new DtCrowdTimerLabel(nameof(UpdateTopologyOptimization));
+        public static readonly DtCrowdTimerLabel BuildProximityGrid = new DtCrowdTimerLabel(nameof(BuildProximityGrid));
+        public static readonly DtCrowdTimerLabel BuildNeighbours = new DtCrowdTimerLabel(nameof(BuildNeighbours));
+        public static readonly DtCrowdTimerLabel FindCorners = new DtCrowdTimerLabel(nameof(FindCorners));
+        public static readonly DtCrowdTimerLabel TriggerOffMeshConnections = new DtCrowdTimerLabel(nameof(TriggerOffMeshConnections));
+        public static readonly DtCrowdTimerLabel CalculateSteering = new DtCrowdTimerLabel(nameof(CalculateSteering));
+        public static readonly DtCrowdTimerLabel PlanVelocity = new DtCrowdTimerLabel(nameof(PlanVelocity));
+        public static readonly DtCrowdTimerLabel Integrate = new DtCrowdTimerLabel(nameof(Integrate));
+        public static readonly DtCrowdTimerLabel HandleCollisions = new DtCrowdTimerLabel(nameof(HandleCollisions));
+        public static readonly DtCrowdTimerLabel MoveAgents = new DtCrowdTimerLabel(nameof(MoveAgents));
+        public static readonly DtCrowdTimerLabel UpdateOffMeshConnections = new DtCrowdTimerLabel(nameof(UpdateOffMeshConnections));
+
+        public readonly string Label;
+
+        private DtCrowdTimerLabel(string labelName)
+        {
+            Label = labelName;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtCrowdTimerLabel.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e4ec9aeaf2b4e044c8be8f25397f3beb
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 172 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtLocalBoundary.cs

@@ -0,0 +1,172 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using System.Collections.Generic;
+using DotRecast.Core;
+
+
+namespace DotRecast.Detour.Crowd
+{
+    using static RcMath;
+
+    public class DtLocalBoundary
+    {
+        public const int MAX_LOCAL_SEGS = 8;
+
+        private RcVec3f m_center = new RcVec3f();
+        private List<DtSegment> m_segs = new List<DtSegment>();
+        private List<long> m_polys = new List<long>();
+        private List<long> m_parents = new List<long>();
+
+        public DtLocalBoundary()
+        {
+            m_center.x = m_center.y = m_center.z = float.MaxValue;
+        }
+
+        public void Reset()
+        {
+            m_center.x = m_center.y = m_center.z = float.MaxValue;
+            m_polys.Clear();
+            m_segs.Clear();
+        }
+
+        protected void AddSegment(float dist, RcSegmentVert s)
+        {
+            // Insert neighbour based on the distance.
+            DtSegment seg = new DtSegment();
+            seg.s[0] = s.vmin;
+            seg.s[1] = s.vmax;
+            //Array.Copy(s, seg.s, 6);
+            seg.d = dist;
+            if (0 == m_segs.Count)
+            {
+                m_segs.Add(seg);
+            }
+            else if (dist >= m_segs[m_segs.Count - 1].d)
+            {
+                if (m_segs.Count >= MAX_LOCAL_SEGS)
+                {
+                    return;
+                }
+
+                m_segs.Add(seg);
+            }
+            else
+            {
+                // Insert inbetween.
+                int i;
+                for (i = 0; i < m_segs.Count; ++i)
+                {
+                    if (dist <= m_segs[i].d)
+                    {
+                        break;
+                    }
+                }
+
+                m_segs.Insert(i, seg);
+            }
+
+            while (m_segs.Count > MAX_LOCAL_SEGS)
+            {
+                m_segs.RemoveAt(m_segs.Count - 1);
+            }
+        }
+
+        public void Update(long startRef, RcVec3f pos, float collisionQueryRange, DtNavMeshQuery navquery, IDtQueryFilter filter)
+        {
+            if (startRef == 0)
+            {
+                Reset();
+                return;
+            }
+
+            m_center = pos;
+
+            // First query non-overlapping polygons.
+            var status = navquery.FindLocalNeighbourhood(startRef, pos, collisionQueryRange, filter, ref m_polys, ref m_parents);
+            if (status.Succeeded())
+            {
+                // Secondly, store all polygon edges.
+                m_segs.Clear();
+
+                var segmentVerts = new List<RcSegmentVert>();
+                var segmentRefs = new List<long>();
+                
+                for (int j = 0; j < m_polys.Count; ++j)
+                {
+                    var result = navquery.GetPolyWallSegments(m_polys[j], false, filter, ref segmentVerts, ref segmentRefs);
+                    if (result.Succeeded())
+                    {
+                        for (int k = 0; k < segmentRefs.Count; ++k)
+                        {
+                            RcSegmentVert s = segmentVerts[k];
+                            var s0 = s.vmin;
+                            var s3 = s.vmax;
+
+                            // Skip too distant segments.
+                            var distSqr = DtUtils.DistancePtSegSqr2D(pos, s0, s3, out var tseg);
+                            if (distSqr > Sqr(collisionQueryRange))
+                            {
+                                continue;
+                            }
+
+                            AddSegment(distSqr, s);
+                        }
+                    }
+                }
+            }
+        }
+
+        public bool IsValid(DtNavMeshQuery navquery, IDtQueryFilter filter)
+        {
+            if (m_polys.Count == 0)
+            {
+                return false;
+            }
+
+            // Check that all polygons still pass query filter.
+            foreach (long refs in m_polys)
+            {
+                if (!navquery.IsValidPolyRef(refs, filter))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public RcVec3f GetCenter()
+        {
+            return m_center;
+        }
+
+        public RcVec3f[] GetSegment(int j)
+        {
+            return m_segs[j].s;
+        }
+
+        public int GetSegmentCount()
+        {
+            return m_segs.Count;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtLocalBoundary.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 901adc684a312194fbecd2be11128170
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 13 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtMoveRequestState.cs

@@ -0,0 +1,13 @@
+namespace DotRecast.Detour.Crowd
+{
+    public enum DtMoveRequestState
+    {
+        DT_CROWDAGENT_TARGET_NONE,
+        DT_CROWDAGENT_TARGET_FAILED,
+        DT_CROWDAGENT_TARGET_VALID,
+        DT_CROWDAGENT_TARGET_REQUESTING,
+        DT_CROWDAGENT_TARGET_WAITING_FOR_QUEUE,
+        DT_CROWDAGENT_TARGET_WAITING_FOR_PATH,
+        DT_CROWDAGENT_TARGET_VELOCITY,
+    };
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtMoveRequestState.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f85be6ec1498ff43a814904285ddc4b
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 51 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceParams.cs

@@ -0,0 +1,51 @@
+namespace DotRecast.Detour.Crowd
+{
+    public class DtObstacleAvoidanceParams
+    {
+        public float velBias;
+        public float weightDesVel;
+        public float weightCurVel;
+        public float weightSide;
+        public float weightToi;
+        public float horizTime;
+        public int gridSize;
+
+        /// < grid
+        public int adaptiveDivs;
+
+        /// < adaptive
+        public int adaptiveRings;
+
+        /// < adaptive
+        public int adaptiveDepth;
+
+        /// < adaptive
+        public DtObstacleAvoidanceParams()
+        {
+            velBias = 0.4f;
+            weightDesVel = 2.0f;
+            weightCurVel = 0.75f;
+            weightSide = 0.75f;
+            weightToi = 2.5f;
+            horizTime = 2.5f;
+            gridSize = 33;
+            adaptiveDivs = 7;
+            adaptiveRings = 2;
+            adaptiveDepth = 5;
+        }
+
+        public DtObstacleAvoidanceParams(DtObstacleAvoidanceParams option)
+        {
+            velBias = option.velBias;
+            weightDesVel = option.weightDesVel;
+            weightCurVel = option.weightCurVel;
+            weightSide = option.weightSide;
+            weightToi = option.weightToi;
+            horizTime = option.horizTime;
+            gridSize = option.gridSize;
+            adaptiveDivs = option.adaptiveDivs;
+            adaptiveRings = option.adaptiveRings;
+            adaptiveDepth = option.adaptiveDepth;
+        }
+    };
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceParams.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7484d3a7e1ecd08419e08cd76f4ee6cb
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 501 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceQuery.cs

@@ -0,0 +1,501 @@
+/*
+Copyright (c) 2009-2010 Mikko Mononen memon@inside.org
+recast4j copyright (c) 2015-2019 Piotr Piastucki piotr@jtilia.org
+DotRecast Copyright (c) 2023 Choi Ikpil ikpil@naver.com
+
+This software is provided 'as-is', without any express or implied
+warranty.  In no event will the authors be held liable for any damages
+arising from the use of this software.
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+*/
+
+using System;
+using DotRecast.Core;
+using DotRecast.Detour.Crowd.Tracking;
+
+namespace DotRecast.Detour.Crowd
+{
+    using static RcMath;
+
+    public class DtObstacleAvoidanceQuery
+    {
+        public const int DT_MAX_PATTERN_DIVS = 32;
+
+        /// < Max numver of adaptive divs.
+        public const int DT_MAX_PATTERN_RINGS = 4;
+
+
+        private DtObstacleAvoidanceParams m_params;
+        private float m_invHorizTime;
+        private float m_vmax;
+        private float m_invVmax;
+
+        private readonly int m_maxCircles;
+        private readonly DtObstacleCircle[] m_circles;
+        private int m_ncircles;
+
+        private readonly int m_maxSegments;
+        private readonly DtObstacleSegment[] m_segments;
+        private int m_nsegments;
+
+        public DtObstacleAvoidanceQuery(int maxCircles, int maxSegments)
+        {
+            m_maxCircles = maxCircles;
+            m_ncircles = 0;
+            m_circles = new DtObstacleCircle[m_maxCircles];
+            for (int i = 0; i < m_maxCircles; i++)
+            {
+                m_circles[i] = new DtObstacleCircle();
+            }
+
+            m_maxSegments = maxSegments;
+            m_nsegments = 0;
+            m_segments = new DtObstacleSegment[m_maxSegments];
+            for (int i = 0; i < m_maxSegments; i++)
+            {
+                m_segments[i] = new DtObstacleSegment();
+            }
+        }
+
+        public void Reset()
+        {
+            m_ncircles = 0;
+            m_nsegments = 0;
+        }
+
+        public void AddCircle(RcVec3f pos, float rad, RcVec3f vel, RcVec3f dvel)
+        {
+            if (m_ncircles >= m_maxCircles)
+                return;
+
+            DtObstacleCircle cir = m_circles[m_ncircles++];
+            cir.p = pos;
+            cir.rad = rad;
+            cir.vel = vel;
+            cir.dvel = dvel;
+        }
+
+        public void AddSegment(RcVec3f p, RcVec3f q)
+        {
+            if (m_nsegments >= m_maxSegments)
+                return;
+
+            DtObstacleSegment seg = m_segments[m_nsegments++];
+            seg.p = p;
+            seg.q = q;
+        }
+
+        public int GetObstacleCircleCount()
+        {
+            return m_ncircles;
+        }
+
+        public DtObstacleCircle GetObstacleCircle(int i)
+        {
+            return m_circles[i];
+        }
+
+        public int GetObstacleSegmentCount()
+        {
+            return m_nsegments;
+        }
+
+        public DtObstacleSegment GetObstacleSegment(int i)
+        {
+            return m_segments[i];
+        }
+
+        private void Prepare(RcVec3f pos, RcVec3f dvel)
+        {
+            // Prepare obstacles
+            for (int i = 0; i < m_ncircles; ++i)
+            {
+                DtObstacleCircle cir = m_circles[i];
+
+                // Side
+                RcVec3f pa = pos;
+                RcVec3f pb = cir.p;
+
+                RcVec3f orig = new RcVec3f();
+                RcVec3f dv = new RcVec3f();
+                cir.dp = pb.Subtract(pa);
+                cir.dp.Normalize();
+                dv = cir.dvel.Subtract(dvel);
+
+                float a = DtUtils.TriArea2D(orig, cir.dp, dv);
+                if (a < 0.01f)
+                {
+                    cir.np.x = -cir.dp.z;
+                    cir.np.z = cir.dp.x;
+                }
+                else
+                {
+                    cir.np.x = cir.dp.z;
+                    cir.np.z = -cir.dp.x;
+                }
+            }
+
+            for (int i = 0; i < m_nsegments; ++i)
+            {
+                DtObstacleSegment seg = m_segments[i];
+
+                // Precalc if the agent is really close to the segment.
+                float r = 0.01f;
+                var distSqr = DtUtils.DistancePtSegSqr2D(pos, seg.p, seg.q, out var t);
+                seg.touch = distSqr < Sqr(r);
+            }
+        }
+
+        private bool SweepCircleCircle(RcVec3f c0, float r0, RcVec3f v, RcVec3f c1, float r1, out float tmin, out float tmax)
+        {
+            const float EPS = 0.0001f;
+
+            tmin = 0;
+            tmax = 0;
+
+            RcVec3f s = c1.Subtract(c0);
+            float r = r0 + r1;
+            float c = s.Dot2D(s) - r * r;
+            float a = v.Dot2D(v);
+            if (a < EPS)
+                return false; // not moving
+
+            // Overlap, calc time to exit.
+            float b = v.Dot2D(s);
+            float d = b * b - a * c;
+            if (d < 0.0f)
+                return false; // no intersection.
+
+            a = 1.0f / a;
+            float rd = (float)Math.Sqrt(d);
+
+            tmin = (b - rd) * a;
+            tmax = (b + rd) * a;
+
+            return true;
+        }
+
+        private bool IsectRaySeg(RcVec3f ap, RcVec3f u, RcVec3f bp, RcVec3f bq, ref float t)
+        {
+            RcVec3f v = bq.Subtract(bp);
+            RcVec3f w = ap.Subtract(bp);
+            float d = RcVec3f.Perp2D(u, v);
+            if (Math.Abs(d) < 1e-6f)
+                return false;
+
+            d = 1.0f / d;
+            t = RcVec3f.Perp2D(v, w) * d;
+            if (t < 0 || t > 1)
+                return false;
+
+            float s = RcVec3f.Perp2D(u, w) * d;
+            if (s < 0 || s > 1)
+                return false;
+
+            return true;
+        }
+
+        /**
+     * Calculate the collision penalty for a given velocity vector
+     *
+     * @param vcand
+     *            sampled velocity
+     * @param dvel
+     *            desired velocity
+     * @param minPenalty
+     *            threshold penalty for early out
+     */
+        private float ProcessSample(RcVec3f vcand, float cs, RcVec3f pos, float rad, RcVec3f vel, RcVec3f dvel,
+            float minPenalty, DtObstacleAvoidanceDebugData debug)
+        {
+            // penalty for straying away from the desired and current velocities
+            float vpen = m_params.weightDesVel * (RcVec3f.Dist2D(vcand, dvel) * m_invVmax);
+            float vcpen = m_params.weightCurVel * (RcVec3f.Dist2D(vcand, vel) * m_invVmax);
+
+            // find the threshold hit time to bail out based on the early out penalty
+            // (see how the penalty is calculated below to understand)
+            float minPen = minPenalty - vpen - vcpen;
+            float tThresold = (m_params.weightToi / minPen - 0.1f) * m_params.horizTime;
+            if (tThresold - m_params.horizTime > -float.MinValue)
+                return minPenalty; // already too much
+
+            // Find min time of impact and exit amongst all obstacles.
+            float tmin = m_params.horizTime;
+            float side = 0;
+            int nside = 0;
+
+            for (int i = 0; i < m_ncircles; ++i)
+            {
+                DtObstacleCircle cir = m_circles[i];
+
+                // RVO
+                RcVec3f vab = vcand.Scale(2);
+                vab = vab.Subtract(vel);
+                vab = vab.Subtract(cir.vel);
+
+                // Side
+                side += Clamp(Math.Min(cir.dp.Dot2D(vab) * 0.5f + 0.5f, cir.np.Dot2D(vab) * 2), 0.0f, 1.0f);
+                nside++;
+
+                if (!SweepCircleCircle(pos, rad, vab, cir.p, cir.rad, out var htmin, out var htmax))
+                    continue;
+
+                // Handle overlapping obstacles.
+                if (htmin < 0.0f && htmax > 0.0f)
+                {
+                    // Avoid more when overlapped.
+                    htmin = -htmin * 0.5f;
+                }
+
+                if (htmin >= 0.0f)
+                {
+                    // The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
+                    if (htmin < tmin)
+                    {
+                        tmin = htmin;
+                        if (tmin < tThresold)
+                            return minPenalty;
+                    }
+                }
+            }
+
+            for (int i = 0; i < m_nsegments; ++i)
+            {
+                DtObstacleSegment seg = m_segments[i];
+                float htmin = 0;
+
+                if (seg.touch)
+                {
+                    // Special case when the agent is very close to the segment.
+                    RcVec3f sdir = seg.q.Subtract(seg.p);
+                    RcVec3f snorm = new RcVec3f();
+                    snorm.x = -sdir.z;
+                    snorm.z = sdir.x;
+                    // If the velocity is pointing towards the segment, no collision.
+                    if (snorm.Dot2D(vcand) < 0.0f)
+                        continue;
+                    // Else immediate collision.
+                    htmin = 0.0f;
+                }
+                else
+                {
+                    if (!IsectRaySeg(pos, vcand, seg.p, seg.q, ref htmin))
+                        continue;
+                }
+
+                // Avoid less when facing walls.
+                htmin *= 2.0f;
+
+                // The closest obstacle is somewhere ahead of us, keep track of nearest obstacle.
+                if (htmin < tmin)
+                {
+                    tmin = htmin;
+                    if (tmin < tThresold)
+                        return minPenalty;
+                }
+            }
+
+            // Normalize side bias, to prevent it dominating too much.
+            if (nside != 0)
+                side /= nside;
+
+            float spen = m_params.weightSide * side;
+            float tpen = m_params.weightToi * (1.0f / (0.1f + tmin * m_invHorizTime));
+
+            float penalty = vpen + vcpen + spen + tpen;
+            // Store different penalties for debug viewing
+            if (debug != null)
+                debug.AddSample(vcand, cs, penalty, vpen, vcpen, spen, tpen);
+
+            return penalty;
+        }
+
+        public int SampleVelocityGrid(RcVec3f pos, float rad, float vmax, RcVec3f vel, RcVec3f dvel, out RcVec3f nvel,
+            DtObstacleAvoidanceParams option, DtObstacleAvoidanceDebugData debug)
+        {
+            Prepare(pos, dvel);
+            m_params = option;
+            m_invHorizTime = 1.0f / m_params.horizTime;
+            m_vmax = vmax;
+            m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
+
+            nvel = RcVec3f.Zero;
+
+            if (debug != null)
+                debug.Reset();
+
+            float cvx = dvel.x * m_params.velBias;
+            float cvz = dvel.z * m_params.velBias;
+            float cs = vmax * 2 * (1 - m_params.velBias) / (m_params.gridSize - 1);
+            float half = (m_params.gridSize - 1) * cs * 0.5f;
+
+            float minPenalty = float.MaxValue;
+            int ns = 0;
+
+            for (int y = 0; y < m_params.gridSize; ++y)
+            {
+                for (int x = 0; x < m_params.gridSize; ++x)
+                {
+                    RcVec3f vcand = RcVec3f.Of(cvx + x * cs - half, 0f, cvz + y * cs - half);
+                    if (Sqr(vcand.x) + Sqr(vcand.z) > Sqr(vmax + cs / 2))
+                        continue;
+
+                    float penalty = ProcessSample(vcand, cs, pos, rad, vel, dvel, minPenalty, debug);
+                    ns++;
+                    if (penalty < minPenalty)
+                    {
+                        minPenalty = penalty;
+                        nvel = vcand;
+                    }
+                }
+            }
+
+            return ns;
+        }
+
+        // vector normalization that ignores the y-component.
+        void DtNormalize2D(float[] v)
+        {
+            float d = (float)Math.Sqrt(v[0] * v[0] + v[2] * v[2]);
+            if (d == 0)
+                return;
+            d = 1.0f / d;
+            v[0] *= d;
+            v[2] *= d;
+        }
+
+        // vector normalization that ignores the y-component.
+        RcVec3f DtRotate2D(float[] v, float ang)
+        {
+            RcVec3f dest = new RcVec3f();
+            float c = (float)Math.Cos(ang);
+            float s = (float)Math.Sin(ang);
+            dest.x = v[0] * c - v[2] * s;
+            dest.z = v[0] * s + v[2] * c;
+            dest.y = v[1];
+            return dest;
+        }
+
+        static readonly float DT_PI = 3.14159265f;
+
+        public int SampleVelocityAdaptive(RcVec3f pos, float rad, float vmax, RcVec3f vel, RcVec3f dvel, out RcVec3f nvel,
+            DtObstacleAvoidanceParams option,
+            DtObstacleAvoidanceDebugData debug)
+        {
+            Prepare(pos, dvel);
+            m_params = option;
+            m_invHorizTime = 1.0f / m_params.horizTime;
+            m_vmax = vmax;
+            m_invVmax = vmax > 0 ? 1.0f / vmax : float.MaxValue;
+
+            nvel = RcVec3f.Zero;
+
+            if (debug != null)
+                debug.Reset();
+
+            // Build sampling pattern aligned to desired velocity.
+            float[] pat = new float[(DT_MAX_PATTERN_DIVS * DT_MAX_PATTERN_RINGS + 1) * 2];
+            int npat = 0;
+
+            int ndivs = m_params.adaptiveDivs;
+            int nrings = m_params.adaptiveRings;
+            int depth = m_params.adaptiveDepth;
+
+            int nd = Clamp(ndivs, 1, DT_MAX_PATTERN_DIVS);
+            int nr = Clamp(nrings, 1, DT_MAX_PATTERN_RINGS);
+            float da = (1.0f / nd) * DT_PI * 2;
+            float ca = (float)Math.Cos(da);
+            float sa = (float)Math.Sin(da);
+
+            // desired direction
+            float[] ddir = new float[6];
+            ddir[0] = dvel.x;
+            ddir[1] = dvel.y;
+            ddir[2] = dvel.z;
+            DtNormalize2D(ddir);
+            RcVec3f rotated = DtRotate2D(ddir, da * 0.5f); // rotated by da/2
+            ddir[3] = rotated.x;
+            ddir[4] = rotated.y;
+            ddir[5] = rotated.z;
+
+            // Always add sample at zero
+            pat[npat * 2 + 0] = 0;
+            pat[npat * 2 + 1] = 0;
+            npat++;
+
+            for (int j = 0; j < nr; ++j)
+            {
+                float r = (float)(nr - j) / (float)nr;
+                pat[npat * 2 + 0] = ddir[(j % 2) * 3] * r;
+                pat[npat * 2 + 1] = ddir[(j % 2) * 3 + 2] * r;
+                int last1 = npat * 2;
+                int last2 = last1;
+                npat++;
+
+                for (int i = 1; i < nd - 1; i += 2)
+                {
+                    // get next point on the "right" (rotate CW)
+                    pat[npat * 2 + 0] = pat[last1] * ca + pat[last1 + 1] * sa;
+                    pat[npat * 2 + 1] = -pat[last1] * sa + pat[last1 + 1] * ca;
+                    // get next point on the "left" (rotate CCW)
+                    pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
+                    pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
+
+                    last1 = npat * 2;
+                    last2 = last1 + 2;
+                    npat += 2;
+                }
+
+                if ((nd & 1) == 0)
+                {
+                    pat[npat * 2 + 2] = pat[last2] * ca - pat[last2 + 1] * sa;
+                    pat[npat * 2 + 3] = pat[last2] * sa + pat[last2 + 1] * ca;
+                    npat++;
+                }
+            }
+
+            // Start sampling.
+            float cr = vmax * (1.0f - m_params.velBias);
+            RcVec3f res = RcVec3f.Of(dvel.x * m_params.velBias, 0, dvel.z * m_params.velBias);
+            int ns = 0;
+            for (int k = 0; k < depth; ++k)
+            {
+                float minPenalty = float.MaxValue;
+                RcVec3f bvel = new RcVec3f();
+                bvel = RcVec3f.Zero;
+
+                for (int i = 0; i < npat; ++i)
+                {
+                    RcVec3f vcand = RcVec3f.Of(res.x + pat[i * 2 + 0] * cr, 0f, res.z + pat[i * 2 + 1] * cr);
+                    if (Sqr(vcand.x) + Sqr(vcand.z) > Sqr(vmax + 0.001f))
+                        continue;
+
+                    float penalty = ProcessSample(vcand, cr / 10, pos, rad, vel, dvel, minPenalty, debug);
+                    ns++;
+                    if (penalty < minPenalty)
+                    {
+                        minPenalty = penalty;
+                        bvel = vcand;
+                    }
+                }
+
+                res = bvel;
+
+                cr *= 0.5f;
+            }
+
+            nvel = res;
+
+            return ns;
+        }
+    }
+}

+ 11 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleAvoidanceQuery.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b4a80dd93e1a67f449eec7c44e6c826a
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 26 - 0
Unity/Packages/com.et.dotrecast/Scripts/DotRecast/Detour.Crowd/DtObstacleCircle.cs

@@ -0,0 +1,26 @@
+using DotRecast.Core;
+
+namespace DotRecast.Detour.Crowd
+{
+    /// < Max number of adaptive rings.
+    public class DtObstacleCircle
+    {
+        /** Position of the obstacle */
+        public RcVec3f p = new RcVec3f();
+
+        /** Velocity of the obstacle */
+        public RcVec3f vel = new RcVec3f();
+
+        /** Velocity of the obstacle */
+        public RcVec3f dvel = new RcVec3f();
+
+        /** Radius of the obstacle */
+        public float rad;
+
+        /** Use for side selection during sampling. */
+        public RcVec3f dp = new RcVec3f();
+
+        /** Use for side selection during sampling. */
+        public RcVec3f np = new RcVec3f();
+    }
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно