Răsfoiți Sursa

换成C#版Recast,项目地址: https://github.com/ikpil/DotRecast

tanghai 2 ani în urmă
părinte
comite
3d377946fd
100 a modificat fișierele cu 4945 adăugiri și 674 ștergeri
  1. 38 53
      DotNet/ThirdParty/DotNet.ThirdParty.csproj
  2. 0 5
      Unity/Assets/Plugins/Android.meta
  3. 0 7
      Unity/Assets/Plugins/Android/libs.meta
  4. BIN
      Unity/Assets/Plugins/Android/libs/arm64-v8a/libRecastDll.so
  5. 0 33
      Unity/Assets/Plugins/Android/libs/arm64-v8a/libRecastDll.so.meta
  6. BIN
      Unity/Assets/Plugins/Android/libs/armeabi-v7a/libRecastDll.so
  7. 0 33
      Unity/Assets/Plugins/Android/libs/armeabi-v7a/libRecastDll.so.meta
  8. BIN
      Unity/Assets/Plugins/Android/libs/x86/libRecastDll.so
  9. 0 71
      Unity/Assets/Plugins/Android/libs/x86/libRecastDll.so.meta
  10. BIN
      Unity/Assets/Plugins/Android/libs/x86_64/libRecastDll.so
  11. 0 71
      Unity/Assets/Plugins/Android/libs/x86_64/libRecastDll.so.meta
  12. 0 8
      Unity/Assets/Plugins/MacOS.meta
  13. 0 8
      Unity/Assets/Plugins/MacOS/x86_64.meta
  14. BIN
      Unity/Assets/Plugins/MacOS/x86_64/libRecastDll.dylib
  15. 0 81
      Unity/Assets/Plugins/MacOS/x86_64/libRecastDll.dylib.meta
  16. 0 5
      Unity/Assets/Plugins/iOS.meta
  17. BIN
      Unity/Assets/Plugins/iOS/libRecastDll.a
  18. 0 33
      Unity/Assets/Plugins/iOS/libRecastDll.a.meta
  19. 0 9
      Unity/Assets/Plugins/x86_64.meta
  20. BIN
      Unity/Assets/Plugins/x86_64/RecastDll.dll
  21. 0 52
      Unity/Assets/Plugins/x86_64/RecastDll.dll.meta
  22. BIN
      Unity/Assets/Plugins/x86_64/libRecastDll.so
  23. 0 76
      Unity/Assets/Plugins/x86_64/libRecastDll.so.meta
  24. 39 119
      Unity/Assets/Scripts/Hotfix/Share/Module/Recast/PathfindingComponentSystem.cs
  25. 13 6
      Unity/Assets/Scripts/Model/Share/Module/Recast/PathfindingComponent.cs
  26. 1 1
      Unity/Assets/Scripts/ThirdParty/DotRecast.meta
  27. 1 1
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core.meta
  28. 49 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/CollectionExtensions.cs
  29. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/CollectionExtensions.cs.meta
  30. 1 1
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Compression.meta
  31. 693 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Compression/FastLZ.cs
  32. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Compression/FastLZ.cs.meta
  33. 24 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/FRand.cs
  34. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/FRand.cs.meta
  35. 30 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/IRcCompressor.cs
  36. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/IRcCompressor.cs.meta
  37. 7 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/IRcRand.cs
  38. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/IRcRand.cs.meta
  39. 132 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Intersections.cs
  40. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Intersections.cs.meta
  41. 33 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Loader.cs
  42. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Loader.cs.meta
  43. 20 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAnonymousDisposable.cs
  44. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAnonymousDisposable.cs.meta
  45. 42 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcArrayUtils.cs
  46. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcArrayUtils.cs.meta
  47. 19 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicBoolean.cs
  48. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicBoolean.cs.meta
  49. 29 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicFloat.cs
  50. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicFloat.cs.meta
  51. 65 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicInteger.cs
  52. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicInteger.cs.meta
  53. 53 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicLong.cs
  54. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicLong.cs.meta
  55. 142 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcByteBuffer.cs
  56. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcByteBuffer.cs.meta
  57. 9 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcByteOrder.cs
  58. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcByteOrder.cs.meta
  59. 102 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcConvexUtils.cs
  60. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcConvexUtils.cs.meta
  61. 9 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcEdge.cs
  62. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcEdge.cs.meta
  63. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcFrequency.cs
  64. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcFrequency.cs.meta
  65. 10 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcHashCodes.cs
  66. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcHashCodes.cs.meta
  67. 83 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Enumerable.cs
  68. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Enumerable.cs.meta
  69. 69 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Listable.cs
  70. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Listable.cs.meta
  71. 19 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Minimal.cs
  72. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Minimal.cs.meta
  73. 48 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.cs
  74. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.cs.meta
  75. 46 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcMath.cs
  76. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcMath.cs.meta
  77. 22 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcMatrix4X4.cs
  78. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcMatrix4X4.cs.meta
  79. 20 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcSegmentVert.cs
  80. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcSegmentVert.cs.meta
  81. 98 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcSortedQueue.cs
  82. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcSortedQueue.cs.meta
  83. 70 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcTelemetry.cs
  84. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcTelemetry.cs.meta
  85. 18 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcTelemetryTick.cs
  86. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcTelemetryTick.cs.meta
  87. 109 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcTimerLabel.cs
  88. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcTimerLabel.cs.meta
  89. 66 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcVec2f.cs
  90. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcVec2f.cs.meta
  91. 645 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcVec3f.cs
  92. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Core/RcVec3f.cs.meta
  93. 1 1
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd.meta
  94. 1349 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowd.cs
  95. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowd.cs.meta
  96. 217 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgent.cs
  97. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgent.cs.meta
  98. 34 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs
  99. 11 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs.meta
  100. 74 0
      Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgentParams.cs

+ 38 - 53
DotNet/ThirdParty/DotNet.ThirdParty.csproj

@@ -7,79 +7,64 @@
         <RootNamespace>ET</RootNamespace>
         <AssemblyName>ThirdParty</AssemblyName>
     </PropertyGroup>
-    
+
     <PropertyGroup>
         <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
         <SatelliteResourceLanguages>en</SatelliteResourceLanguages>
     </PropertyGroup>
-    
+
     <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
-      <DefineConstants>DOTNET;UNITY_DOTSPLAYER</DefineConstants>
-      <OutputPath>..\..\Bin\</OutputPath>
-      <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-      <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-      <Optimize>true</Optimize>
+        <DefineConstants>DOTNET;UNITY_DOTSPLAYER</DefineConstants>
+        <OutputPath>..\..\Bin\</OutputPath>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+        <Optimize>true</Optimize>
     </PropertyGroup>
-    
+
     <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
-      <DefineConstants>DOTNET;UNITY_DOTSPLAYER</DefineConstants>
-      <OutputPath>..\..\Bin\</OutputPath>
-      <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-      <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+        <DefineConstants>DOTNET;UNITY_DOTSPLAYER</DefineConstants>
+        <OutputPath>..\..\Bin\</OutputPath>
+        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+        <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     </PropertyGroup>
 
     <ItemGroup>
 
-    <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\TrueSync\**\*.cs">
-        <Link>TrueSync/%(RecursiveDir)%(FileName)%(Extension)</Link>
-    </Compile>
+        <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\TrueSync\**\*.cs">
+            <Link>TrueSync/%(RecursiveDir)%(FileName)%(Extension)</Link>
+        </Compile>
 
-    <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\ETTask\**\*.cs">
-        <Link>ETTask/%(RecursiveDir)%(FileName)%(Extension)</Link>
-    </Compile>
+        <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\ETTask\**\*.cs">
+            <Link>ETTask/%(RecursiveDir)%(FileName)%(Extension)</Link>
+        </Compile>
 
-    <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\Kcp\**\*.cs">
-      <Link>Kcp/%(RecursiveDir)%(FileName)%(Extension)</Link>
-    </Compile>
+        <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\Kcp\**\*.cs">
+            <Link>Kcp/%(RecursiveDir)%(FileName)%(Extension)</Link>
+        </Compile>
 
-    <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\NativeCollection\**\*.cs">
-        <Link>NativeCollection/%(RecursiveDir)%(FileName)%(Extension)</Link>
-    </Compile>
+        <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\NativeCollection\**\*.cs">
+            <Link>NativeCollection/%(RecursiveDir)%(FileName)%(Extension)</Link>
+        </Compile>
 
-    <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\Recast\Recast.cs">
-      <Link>Recast\Recast.cs</Link>
-    </Compile>
+        <Compile Include="..\..\Unity\Assets\Scripts\ThirdParty\DotRecast\**\*.cs">
+            <Link>DotRecast/%(RecursiveDir)%(FileName)%(Extension)</Link>
+        </Compile>
 
-    <Compile Include="..\..\Unity\Library\PackageCache\com.unity.mathematics*\Unity.Mathematics\**\*.cs">
-        <Link>Unity.Mathematics/$([System.String]::new(%(RecursiveDir)).Substring($([System.String]::new(%(RecursiveDir)).Indexof("Unity.Mathematics"))).Replace("Unity.Mathematics", ""))/%(FileName)%(Extension)</Link>
-    </Compile>
-        
-    </ItemGroup>
+        <Compile Include="..\..\Unity\Library\PackageCache\com.unity.mathematics*\Unity.Mathematics\**\*.cs">
+            <Link>Unity.Mathematics/$([System.String]::new(%(RecursiveDir)).Substring($([System.String]::new(%(RecursiveDir)).Indexof("Unity.Mathematics"))).Replace("Unity.Mathematics", ""))/%(FileName)%(Extension)</Link>
+        </Compile>
 
-    <ItemGroup>
-      <Content Include="..\..\Unity\Assets\Plugins\MacOS\x86_64\libRecastDll.dylib">
-        <Link>runtimes\osx\native\libRecastDll.dylib</Link>
-        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-      </Content>
-      <Content Include="..\..\Unity\Assets\Plugins\x86_64\libRecastDll.so">
-        <Link>runtimes\linux\native\libRecastDll.so</Link>
-        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-      </Content>
-      <Content Include="..\..\Unity\Assets\Plugins\x86_64\RecastDll.dll">
-        <Link>runtimes\win\native\RecastDll.dll</Link>
-        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-      </Content>
     </ItemGroup>
 
     <ItemGroup>
-      <PackageReference Include="CommandLineParser" Version="2.8.0" />
-      <PackageReference Include="EPPlus" Version="5.8.8" />
+        <PackageReference Include="CommandLineParser" Version="2.8.0" />
+        <PackageReference Include="EPPlus" Version="5.8.8" />
 
-      <PackageReference Include="MemoryPack" Version="1.9.13" />
-      <PackageReference Include="MongoDB.Driver" Version="2.17.1" />
-      <PackageReference Include="NLog" Version="4.7.15" />
-      <PackageReference Include="SharpZipLib" Version="1.3.3" />
-      <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.0.1" />
-      <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
+        <PackageReference Include="MemoryPack" Version="1.9.13" />
+        <PackageReference Include="MongoDB.Driver" Version="2.17.1" />
+        <PackageReference Include="NLog" Version="4.7.15" />
+        <PackageReference Include="SharpZipLib" Version="1.3.3" />
+        <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.0.1" />
+        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
     </ItemGroup>
 </Project>

+ 0 - 5
Unity/Assets/Plugins/Android.meta

@@ -1,5 +0,0 @@
-fileFormatVersion: 2
-guid: 0004c0a5ad641d4468ebb65779ee48b2
-folderAsset: yes
-DefaultImporter:
-  userData: 

+ 0 - 7
Unity/Assets/Plugins/Android/libs.meta

@@ -1,7 +0,0 @@
-fileFormatVersion: 2
-guid: 9cd62bafd75e7604daf2b561b80d136d
-folderAsset: yes
-DefaultImporter:
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
Unity/Assets/Plugins/Android/libs/arm64-v8a/libRecastDll.so


+ 0 - 33
Unity/Assets/Plugins/Android/libs/arm64-v8a/libRecastDll.so.meta

@@ -1,33 +0,0 @@
-fileFormatVersion: 2
-guid: 0678f08141eee402eb0deddcc3a7c971
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      Android: Android
-    second:
-      enabled: 1
-      settings:
-        CPU: ARM64
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 0
-      settings:
-        DefaultValueInitialized: true
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
Unity/Assets/Plugins/Android/libs/armeabi-v7a/libRecastDll.so


+ 0 - 33
Unity/Assets/Plugins/Android/libs/armeabi-v7a/libRecastDll.so.meta

@@ -1,33 +0,0 @@
-fileFormatVersion: 2
-guid: 49f1070539edc4a4c9961fc988a56201
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      Android: Android
-    second:
-      enabled: 1
-      settings:
-        CPU: ARMv7
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 0
-      settings:
-        DefaultValueInitialized: true
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
Unity/Assets/Plugins/Android/libs/x86/libRecastDll.so


+ 0 - 71
Unity/Assets/Plugins/Android/libs/x86/libRecastDll.so.meta

@@ -1,71 +0,0 @@
-fileFormatVersion: 2
-guid: b499d59e519ea4d6fabea9dec92a0c21
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      : Any
-    second:
-      enabled: 0
-      settings:
-        Exclude Android: 1
-        Exclude Editor: 1
-        Exclude Linux64: 1
-        Exclude OSXUniversal: 1
-        Exclude WebGL: 1
-        Exclude Win: 1
-        Exclude Win64: 1
-  - first:
-      Android: Android
-    second:
-      enabled: 0
-      settings:
-        CPU: X86
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-        DefaultValueInitialized: true
-        OS: AnyOS
-  - first:
-      Standalone: Linux64
-    second:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-  - first:
-      Standalone: OSXUniversal
-    second:
-      enabled: 0
-      settings:
-        CPU: None
-  - first:
-      Standalone: Win
-    second:
-      enabled: 0
-      settings:
-        CPU: x86
-  - first:
-      Standalone: Win64
-    second:
-      enabled: 0
-      settings:
-        CPU: x86_64
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
Unity/Assets/Plugins/Android/libs/x86_64/libRecastDll.so


+ 0 - 71
Unity/Assets/Plugins/Android/libs/x86_64/libRecastDll.so.meta

@@ -1,71 +0,0 @@
-fileFormatVersion: 2
-guid: a35daf583ab2d41f6b102da65e79b898
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      : Any
-    second:
-      enabled: 0
-      settings:
-        Exclude Android: 0
-        Exclude Editor: 1
-        Exclude Linux64: 1
-        Exclude OSXUniversal: 1
-        Exclude WebGL: 1
-        Exclude Win: 1
-        Exclude Win64: 1
-  - first:
-      Android: Android
-    second:
-      enabled: 1
-      settings:
-        CPU: X86_64
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-        DefaultValueInitialized: true
-        OS: AnyOS
-  - first:
-      Standalone: Linux64
-    second:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-  - first:
-      Standalone: OSXUniversal
-    second:
-      enabled: 0
-      settings:
-        CPU: None
-  - first:
-      Standalone: Win
-    second:
-      enabled: 0
-      settings:
-        CPU: x86
-  - first:
-      Standalone: Win64
-    second:
-      enabled: 0
-      settings:
-        CPU: x86_64
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 8
Unity/Assets/Plugins/MacOS.meta

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

+ 0 - 8
Unity/Assets/Plugins/MacOS/x86_64.meta

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

BIN
Unity/Assets/Plugins/MacOS/x86_64/libRecastDll.dylib


+ 0 - 81
Unity/Assets/Plugins/MacOS/x86_64/libRecastDll.dylib.meta

@@ -1,81 +0,0 @@
-fileFormatVersion: 2
-guid: 8e7dc169c786249d88671b522f0bc9fe
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      : Any
-    second:
-      enabled: 0
-      settings:
-        Exclude Android: 1
-        Exclude Editor: 0
-        Exclude Linux64: 1
-        Exclude OSXUniversal: 0
-        Exclude WebGL: 1
-        Exclude Win: 1
-        Exclude Win64: 1
-        Exclude iOS: 1
-  - first:
-      Android: Android
-    second:
-      enabled: 0
-      settings:
-        CPU: ARMv7
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 1
-      settings:
-        CPU: AnyCPU
-        DefaultValueInitialized: true
-        OS: OSX
-  - first:
-      Standalone: Linux64
-    second:
-      enabled: 0
-      settings:
-        CPU: AnyCPU
-  - first:
-      Standalone: OSXUniversal
-    second:
-      enabled: 1
-      settings:
-        CPU: AnyCPU
-  - first:
-      Standalone: Win
-    second:
-      enabled: 0
-      settings:
-        CPU: x86
-  - first:
-      Standalone: Win64
-    second:
-      enabled: 0
-      settings:
-        CPU: x86_64
-  - first:
-      iPhone: iOS
-    second:
-      enabled: 0
-      settings:
-        AddToEmbeddedBinaries: false
-        CPU: AnyCPU
-        CompileFlags: 
-        FrameworkDependencies: 
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 5
Unity/Assets/Plugins/iOS.meta

@@ -1,5 +0,0 @@
-fileFormatVersion: 2
-guid: 5a4ab2e5e00054b03a7bf96d03e5b4e1
-folderAsset: yes
-DefaultImporter:
-  userData: 

BIN
Unity/Assets/Plugins/iOS/libRecastDll.a


+ 0 - 33
Unity/Assets/Plugins/iOS/libRecastDll.a.meta

@@ -1,33 +0,0 @@
-fileFormatVersion: 2
-guid: 33a2929f9ae7aa946adfa13476e5c5d6
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 0
-      settings:
-        DefaultValueInitialized: true
-  - first:
-      iPhone: iOS
-    second:
-      enabled: 1
-      settings:
-        AddToEmbeddedBinaries: false
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 0 - 9
Unity/Assets/Plugins/x86_64.meta

@@ -1,9 +0,0 @@
-fileFormatVersion: 2
-guid: 8dba1dfb80e5b7d40bd214fc4ed2ed6b
-folderAsset: yes
-timeCreated: 1451020766
-licenseType: Pro
-DefaultImporter:
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
Unity/Assets/Plugins/x86_64/RecastDll.dll


+ 0 - 52
Unity/Assets/Plugins/x86_64/RecastDll.dll.meta

@@ -1,52 +0,0 @@
-fileFormatVersion: 2
-guid: e3bdfcbf9ac795b49b376e78a3b584f5
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      Any: 
-    second:
-      enabled: 1
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 0
-      settings:
-        CPU: x86_64
-        DefaultValueInitialized: true
-  - first:
-      Standalone: Linux64
-    second:
-      enabled: 1
-      settings:
-        CPU: AnyCPU
-  - first:
-      Standalone: OSXUniversal
-    second:
-      enabled: 0
-      settings:
-        CPU: x86_64
-  - first:
-      Standalone: Win
-    second:
-      enabled: 0
-      settings:
-        CPU: None
-  - first:
-      Standalone: Win64
-    second:
-      enabled: 1
-      settings:
-        CPU: AnyCPU
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

BIN
Unity/Assets/Plugins/x86_64/libRecastDll.so


+ 0 - 76
Unity/Assets/Plugins/x86_64/libRecastDll.so.meta

@@ -1,76 +0,0 @@
-fileFormatVersion: 2
-guid: 455b6d98e2d48ae6b999fb92ea1dd130
-PluginImporter:
-  externalObjects: {}
-  serializedVersion: 2
-  iconMap: {}
-  executionOrder: {}
-  defineConstraints: []
-  isPreloaded: 0
-  isOverridable: 0
-  isExplicitlyReferenced: 0
-  validateReferences: 1
-  platformData:
-  - first:
-      : Any
-    second:
-      enabled: 0
-      settings:
-        Exclude Android: 1
-        Exclude Editor: 0
-        Exclude Linux64: 0
-        Exclude OSXUniversal: 0
-        Exclude WebGL: 1
-        Exclude Win: 0
-        Exclude Win64: 0
-  - first:
-      Android: Android
-    second:
-      enabled: 0
-      settings:
-        CPU: ARMv7
-  - first:
-      Any: 
-    second:
-      enabled: 0
-      settings: {}
-  - first:
-      Editor: Editor
-    second:
-      enabled: 1
-      settings:
-        CPU: AnyCPU
-        DefaultValueInitialized: true
-        OS: AnyOS
-  - first:
-      Standalone: Linux64
-    second:
-      enabled: 1
-      settings:
-        CPU: None
-  - first:
-      Standalone: OSXUniversal
-    second:
-      enabled: 1
-      settings:
-        CPU: None
-  - first:
-      Standalone: Win
-    second:
-      enabled: 1
-      settings:
-        CPU: None
-  - first:
-      Standalone: Win64
-    second:
-      enabled: 1
-      settings:
-        CPU: None
-  - first:
-      WebGL: WebGL
-    second:
-      enabled: 0
-      settings: {}
-  userData: 
-  assetBundleName: 
-  assetBundleVariant: 

+ 39 - 119
Unity/Assets/Scripts/Hotfix/Share/Module/Recast/PathfindingComponentSystem.cs

@@ -1,5 +1,9 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
+using DotRecast.Core;
+using DotRecast.Detour;
+using DotRecast.Detour.Io;
 using Unity.Mathematics;
 
 namespace ET
@@ -13,156 +17,72 @@ namespace ET
         {
             self.Name = name;
             byte[] buffer = NavmeshComponent.Instance.Get(name);
-            self.navMesh = Recast.RecastLoad(buffer, buffer.Length);
-            if (self.navMesh == IntPtr.Zero)
+            
+            DtMeshSetReader reader = new();
+            using MemoryStream ms = new(buffer);
+            using BinaryReader br = new(ms);
+            self.navMesh = reader.Read32Bit(br, 6); // cpp recast导出来的要用Read32Bit读取,DotRecast导出来的还没试过
+            
+            if (self.navMesh == null)
             {
                 throw new Exception($"nav load fail: {name}");
             }
+            
+            self.filter = new DtQueryDefaultFilter();
+            self.query = new DtNavMeshQuery(self.navMesh);
         }
 
         [EntitySystem]
         private static void Destroy(this PathfindingComponent self)
         {
             self.Name = string.Empty;
-            
-            Recast.RecastClear(self.navMesh);
-            
-            self.navMesh = IntPtr.Zero;
+            self.navMesh = null;
         }
         
         public static void Find(this PathfindingComponent self, float3 start, float3 target, List<float3> result)
         {
-            if (self.navMesh == IntPtr.Zero)
+            if (self.navMesh == null)
             {
                 self.Fiber().Debug("寻路| Find 失败 pathfinding ptr is zero");
                 throw new Exception($"pathfinding ptr is zero: {self.Scene().Name}");
             }
 
-            self.StartPos[0] = -start.x;
-            self.StartPos[1] = start.y;
-            self.StartPos[2] = start.z;
-
-            self.EndPos[0] = -target.x;
-            self.EndPos[1] = target.y;
-            self.EndPos[2] = target.z;
-            //Log.Debug($"start find path: {self.GetParent<Unit>().Id}");
-            int n = Recast.RecastFind(self.navMesh, self.extents, self.StartPos, self.EndPos, self.Result);
-            if (n < 0)
-            {
-                throw new Exception($"find path error: {n}");
-            }
-            
-            for (int i = 0; i < n; ++i)
-            {
-                int index = i * 3;
-                result.Add(new float3(-self.Result[index], self.Result[index + 1], self.Result[index + 2]));
-            }
-            //Log.Debug($"finish find path: {self.GetParent<Unit>().Id} {result.ListToString()}");
-        }
-
-        public static void FindWithAdjust(this PathfindingComponent self, float3 start, float3 target, List<float3> result,float adjustRaduis)
-        {
-            self.Find(start, target, result);
-            for (int i = 0; i < result.Count; i++)
-            {
-                float3 adjust = self.FindRandomPointWithRaduis(result[i], adjustRaduis);
-                result[i] = adjust;
-            }
-        }
-        
-        public static float3 FindRandomPointWithRaduis(this PathfindingComponent self, float3 pos, float raduis)
-        {
-            if (self.navMesh == IntPtr.Zero)
-            {
-                throw new Exception($"pathfinding ptr is zero: {self.Scene().Name}");
-            }
+            RcVec3f startPos = new(-start.x, start.y, start.z);
+            RcVec3f endPos = new(-target.x, target.y, target.z);
 
-            if (raduis > PathfindingComponent.FindRandomNavPosMaxRadius * 0.001f)
-            {
-                throw new Exception($"pathfinding raduis is too large,cur: {raduis}, max: {PathfindingComponent.FindRandomNavPosMaxRadius}");
-            }
+            long startRef;
+            long endRef;
+            RcVec3f startPt;
+            RcVec3f endPt;
             
-            int degrees = RandomGenerator.RandomNumber(0, 360);
-            float r = RandomGenerator.RandomNumber(0, (int) (raduis * 1000)) / 1000f;
-
-            float x = r * math.cos(math.radians(degrees)); 
-            float z = r * math.sin(math.radians(degrees));
-
-            float3 findpos = new float3(pos.x + x, pos.y, pos.z + z);
-
-            return self.RecastFindNearestPoint(findpos);
-        }
-        
-        /// <summary>
-        /// 以pos为中心各自在宽和高的左右 前后两个方向延伸
-        /// </summary>
-        /// <param name="self"></param>
-        /// <param name="pos"></param>
-        /// <param name="width"></param>
-        /// <param name="height"></param>
-        /// <returns></returns>
-        /// <exception cref="Exception"></exception>
-        public static float3 FindRandomPointWithRectangle(this PathfindingComponent self, float3 pos, int width, int height)
-        {
-            if (self.navMesh == IntPtr.Zero)
-            {
-                throw new Exception($"pathfinding ptr is zero: {self.Scene().Name}");
-            }
-
-            if (width > PathfindingComponent.FindRandomNavPosMaxRadius * 0.001f || height > PathfindingComponent.FindRandomNavPosMaxRadius * 0.001f)
-            {
-                throw new Exception($"pathfinding rectangle is too large,width: {width} height: {height}, max: {PathfindingComponent.FindRandomNavPosMaxRadius}");
-            }
+            self.query.FindNearestPoly(startPos, self.extents, self.filter, out startRef, out startPt, out _);
+            self.query.FindNearestPoly(endPos, self.extents, self.filter, out endRef, out endPt, out _);
             
-            float x = RandomGenerator.RandomNumber(-width, width);
-            float z = RandomGenerator.RandomNumber(-height, height);
-
-            float3 findpos = new float3(pos.x + x, pos.y, pos.z + z);
-
-            return self.RecastFindNearestPoint(findpos);
-        }
-        
-        public static float3 FindRandomPointWithRaduis(this PathfindingComponent self, float3 pos, float minRadius, float maxRadius)
-        {
-            if (self.navMesh == IntPtr.Zero)
-            {
-                throw new Exception($"pathfinding ptr is zero: {self.Scene().Name}");
-            }
+            self.query.FindPath(startRef, endRef, startPt, endPt, self.filter, ref self.polys, new DtFindPathOption(0, float.MaxValue));
 
-            if (maxRadius > PathfindingComponent.FindRandomNavPosMaxRadius * 0.001f)
+            if (0 >= self.polys.Count)
             {
-                throw new Exception($"pathfinding raduis is too large,cur: {maxRadius}, max: {PathfindingComponent.FindRandomNavPosMaxRadius}");
+                return;
             }
             
-            int degrees = RandomGenerator.RandomNumber(0, 360);
-            float r = RandomGenerator.RandomNumber((int) (minRadius * 1000), (int) (maxRadius * 1000)) / 1000f;
-
-            float x = r * math.cos(math.radians(degrees));
-            float z = r * math.sin(math.radians(degrees));
-
-            float3 findpos = new float3(pos.x + x, pos.y, pos.z + z);
-
-            return self.RecastFindNearestPoint(findpos);
-        }
-
-        public static float3 RecastFindNearestPoint(this PathfindingComponent self, float3 pos)
-        {
-            if (self.navMesh == IntPtr.Zero)
+            // In case of partial path, make sure the end point is clamped to the last polygon.
+            RcVec3f epos = RcVec3f.Of(endPt.x, endPt.y, endPt.z);
+            if (self.polys[^1] != endRef)
             {
-                throw new Exception($"pathfinding ptr is zero: {self.Scene().Name}");
+                DtStatus dtStatus = self.query.ClosestPointOnPoly(self.polys[^1], endPt, out RcVec3f closest, out bool _);
+                if (dtStatus.Succeeded())
+                {
+                    epos = closest;
+                }
             }
 
-            self.StartPos[0] = -pos.x;
-            self.StartPos[1] = pos.y;
-            self.StartPos[2] = pos.z;
+            self.query.FindStraightPath(startPt, epos, self.polys, ref self.straightPath, PathfindingComponent.MAX_POLYS, DtNavMeshQuery.DT_STRAIGHTPATH_ALL_CROSSINGS);
 
-            int ret = Recast.RecastFindNearestPoint(self.navMesh, self.extents, self.StartPos, self.EndPos);
-            if (ret == 0)
+            for (int i = 0; i < self.straightPath.Count; ++i)
             {
-                throw new Exception($"RecastFindNearestPoint fail, 可能是位置配置有问题: sceneName:{self.Scene().Name} {pos} {self.Name} {self.GetParent<Unit>().Id} {self.GetParent<Unit>().ConfigId} {self.EndPos.ArrayToString()}");
+                RcVec3f pos = self.straightPath[i].pos;
+                result.Add(new float3(-pos.x, pos.y, pos.z));
             }
-            
-            return new float3(-self.EndPos[0], self.EndPos[1], self.EndPos[2]);
         }
     }
 }

+ 13 - 6
Unity/Assets/Scripts/Model/Share/Module/Recast/PathfindingComponent.cs

@@ -1,4 +1,7 @@
 using System;
+using System.Collections.Generic;
+using DotRecast.Core;
+using DotRecast.Detour;
 
 namespace ET
 {
@@ -8,18 +11,22 @@ namespace ET
     [ComponentOf(typeof(Unit))]
     public class PathfindingComponent: Entity, IAwake<string>, IDestroy
     {
+        public const int MAX_POLYS = 256;
+        
         public const int FindRandomNavPosMaxRadius = 15000;  // 随机找寻路点的最大半径
         
-        public float[] extents = {15, 10, 15};
+        public RcVec3f extents = new(15, 10, 15);
         
         public string Name;
         
-        public IntPtr navMesh;
-
-        public float[] StartPos = new float[3];
+        public DtNavMesh navMesh;
+        
+        public List<long> polys = new(MAX_POLYS);
 
-        public float[] EndPos = new float[3];
+        public IDtQueryFilter filter;
+        
+        public List<StraightPathItem> straightPath = new();
 
-        public float[] Result = new float[Recast.MAX_POLYS * 3];
+        public DtNavMeshQuery query;
     }
 }

+ 1 - 1
Unity/Assets/Plugins/Android/libs/x86.meta → Unity/Assets/Scripts/ThirdParty/DotRecast.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 079ea0ed741ff194a80cce029630b5ac
+guid: 5aaea4ae92be1594f8eb20d6db2ddf82
 folderAsset: yes
 DefaultImporter:
   externalObjects: {}

+ 1 - 1
Unity/Assets/Plugins/Android/libs/x86_64.meta → Unity/Assets/Scripts/ThirdParty/DotRecast/Core.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 8aaaa46b74dc44812bc3f7b082cd8af1
+guid: 2211866454efbd848bf9c7f52ffd4e5b
 folderAsset: yes
 DefaultImporter:
   externalObjects: {}

+ 49 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/CollectionExtensions.cs.meta

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

+ 1 - 1
Unity/Assets/Plugins/Android/libs/arm64-v8a.meta → Unity/Assets/Scripts/ThirdParty/DotRecast/Core/Compression.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: 084116138e3349d48bdf50214aebefc5
+guid: a7f1c0a5d19958641a51189a19e3a90a
 folderAsset: yes
 DefaultImporter:
   externalObjects: {}

+ 693 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/Compression/FastLZ.cs.meta

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

+ 24 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/FRand.cs.meta

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

+ 30 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/IRcCompressor.cs.meta

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

+ 7 - 0
Unity/Assets/Scripts/ThirdParty/DotRecast/Core/IRcRand.cs

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

+ 11 - 0
Unity/Assets/Scripts/ThirdParty/DotRecast/Core/IRcRand.cs.meta

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

+ 132 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/Intersections.cs.meta

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

+ 33 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/Loader.cs.meta

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

+ 20 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcAnonymousDisposable.cs.meta

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

+ 42 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcArrayUtils.cs.meta

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

+ 19 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicBoolean.cs.meta

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

+ 29 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicFloat.cs.meta

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

+ 65 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicInteger.cs.meta

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

+ 53 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcAtomicLong.cs.meta

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

+ 142 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcByteBuffer.cs.meta

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

+ 9 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcByteOrder.cs.meta

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

+ 102 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcConvexUtils.cs.meta

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

+ 9 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcEdge.cs.meta

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

+ 11 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcFrequency.cs.meta

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

+ 10 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcHashCodes.cs.meta

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

+ 83 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Enumerable.cs.meta

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

+ 69 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Listable.cs.meta

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

+ 19 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.Minimal.cs.meta

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

+ 48 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcImmutableArray.cs.meta

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

+ 46 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcMath.cs.meta

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

+ 22 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcMatrix4X4.cs.meta

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

+ 20 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcSegmentVert.cs.meta

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

+ 98 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcSortedQueue.cs.meta

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

+ 70 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcTelemetry.cs.meta

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

+ 18 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcTelemetryTick.cs.meta

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

+ 109 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcTimerLabel.cs.meta

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

+ 66 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcVec2f.cs.meta

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

+ 645 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Core/RcVec3f.cs.meta

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

+ 1 - 1
Unity/Assets/Plugins/Android/libs/armeabi-v7a.meta → Unity/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd.meta

@@ -1,5 +1,5 @@
 fileFormatVersion: 2
-guid: b8d0cd0f5702f0144af2498bce3ee3e9
+guid: 961dbb12e0e6c384090d6cf7ec8a14e6
 folderAsset: yes
 DefaultImporter:
   externalObjects: {}

+ 1349 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowd.cs.meta

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

+ 217 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgent.cs.meta

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

+ 34 - 0
Unity/Assets/Scripts/ThirdParty/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/Assets/Scripts/ThirdParty/DotRecast/Detour.Crowd/DtCrowdAgentAnimation.cs.meta

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

+ 74 - 0
Unity/Assets/Scripts/ThirdParty/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;
+    }
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff