com.wallstop-studios.unity-helpers 2.0.0-rc57 → 2.0.0-rc58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/.config/dotnet-tools.json +9 -9
  2. package/.editorconfig +184 -184
  3. package/.gitattributes +63 -63
  4. package/.github/workflows/npm-publish.yml +66 -66
  5. package/.pre-commit-config.yaml +21 -21
  6. package/Editor/AnimationCopier.cs +181 -181
  7. package/Editor/AnimationCopier.cs.meta +2 -2
  8. package/Editor/AnimationCreator.cs +253 -253
  9. package/Editor/AnimationCreator.cs.meta +11 -11
  10. package/Editor/AnimationEventEditor.cs +887 -887
  11. package/Editor/AnimatorControllerCopier.cs +162 -162
  12. package/Editor/AnimatorControllerCopier.cs.meta +2 -2
  13. package/Editor/CustomEditors/MatchColliderToSpriteEditor.cs +34 -34
  14. package/Editor/CustomEditors/MatchColliderToSpriteEditor.cs.meta +2 -2
  15. package/Editor/CustomEditors.meta +2 -2
  16. package/Editor/FitTextureSizeWizard.cs +147 -147
  17. package/Editor/FitTextureSizeWizard.cs.meta +2 -2
  18. package/Editor/PrefabCheckWizard.cs +170 -170
  19. package/Editor/PrefabCheckWizard.cs.meta +11 -11
  20. package/Editor/SpriteSettingsApplier.cs +272 -272
  21. package/Editor/SpriteSettingsApplier.cs.meta +2 -2
  22. package/Editor/StringInListeDrawer.cs +56 -56
  23. package/Editor/TextureResizerWizard.cs +181 -181
  24. package/Editor/TextureResizerWizard.cs.meta +2 -2
  25. package/Editor/TextureSettingsApplier.cs +178 -178
  26. package/Editor/TextureSettingsApplier.cs.meta +2 -2
  27. package/Editor/Utils/DxReadOnlyPropertyDrawer.cs +26 -26
  28. package/Editor/Utils/DxReadOnlyPropertyDrawer.cs.meta +11 -11
  29. package/Editor/Utils/EditorUtilities.cs +22 -22
  30. package/Editor/Utils/EditorUtilities.cs.meta +11 -11
  31. package/Editor/Utils.meta +8 -8
  32. package/Editor/WShowIfPropertyDrawer.cs +63 -63
  33. package/Editor/WallstopStudios.UnityHelpers.Editor.asmdef +17 -17
  34. package/Editor/WallstopStudios.UnityHelpers.Editor.asmdef.meta +7 -7
  35. package/LICENSE +21 -21
  36. package/LICENSE.md +6 -6
  37. package/LICENSE.meta +7 -7
  38. package/README.md +177 -177
  39. package/Runtime/Binaries/Microsoft.Bcl.AsyncInterfaces.dll.meta +33 -33
  40. package/Runtime/Binaries/Microsoft.Bcl.AsyncInterfaces.xml +223 -223
  41. package/Runtime/Binaries/Microsoft.Bcl.AsyncInterfaces.xml.meta +7 -7
  42. package/Runtime/Binaries/System.Text.Encodings.Web.dll.meta +33 -33
  43. package/Runtime/Binaries/System.Text.Encodings.Web.xml +935 -935
  44. package/Runtime/Binaries/System.Text.Encodings.Web.xml.meta +7 -7
  45. package/Runtime/Binaries/System.Text.Json.dll.meta +33 -33
  46. package/Runtime/Binaries/System.Text.Json.xml +4829 -4829
  47. package/Runtime/Binaries/System.Text.Json.xml.meta +7 -7
  48. package/Runtime/Binaries.meta +8 -8
  49. package/Runtime/Core/Attributes/AnimationEventAttribute.cs +131 -131
  50. package/Runtime/Core/Attributes/ChildComponentAttribute.cs +209 -209
  51. package/Runtime/Core/Attributes/DxReadOnlyAttribute.cs +6 -6
  52. package/Runtime/Core/Attributes/KSerializableAttribute.cs +19 -19
  53. package/Runtime/Core/Attributes/NotNullAttribute.cs +32 -32
  54. package/Runtime/Core/Attributes/ParentComponent.cs +185 -185
  55. package/Runtime/Core/Attributes/RelationalComponentExtensions.cs +14 -14
  56. package/Runtime/Core/Attributes/SiblingComponentAttribute.cs +117 -117
  57. package/Runtime/Core/Attributes/SiblingComponentAttribute.cs.meta +11 -11
  58. package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs +101 -101
  59. package/Runtime/Core/Attributes/ValidateAssignmentAttribute.cs.meta +11 -11
  60. package/Runtime/Core/Attributes/WShowIfAttribute.cs +16 -16
  61. package/Runtime/Core/Attributes.meta +8 -8
  62. package/Runtime/Core/DataStructure/Adapters/FastVector2Int.cs +92 -92
  63. package/Runtime/Core/DataStructure/Adapters/FastVector3Int.cs +185 -185
  64. package/Runtime/Core/DataStructure/Adapters/KGuid.cs +305 -305
  65. package/Runtime/Core/DataStructure/Adapters/KVector2.cs +80 -80
  66. package/Runtime/Core/DataStructure/Circle.cs +50 -50
  67. package/Runtime/Core/DataStructure/CyclicBuffer.cs +153 -153
  68. package/Runtime/Core/DataStructure/ISpatialTree.cs +60 -60
  69. package/Runtime/Core/DataStructure/ISpatialTree.cs.meta +11 -11
  70. package/Runtime/Core/DataStructure/KDTree.cs +292 -292
  71. package/Runtime/Core/DataStructure/KDTree.cs.meta +11 -11
  72. package/Runtime/Core/DataStructure/QuadTree.cs +287 -287
  73. package/Runtime/Core/DataStructure/RTree.cs +346 -346
  74. package/Runtime/Core/DataStructure/RTree.cs.meta +11 -11
  75. package/Runtime/Core/DataStructure/StringWrapper.cs +91 -91
  76. package/Runtime/Core/DataStructure/TimedCache.cs +66 -66
  77. package/Runtime/Core/Extension/AnimatorExtensions.cs +25 -25
  78. package/Runtime/Core/Extension/AsyncOperationExtensions.cs +110 -110
  79. package/Runtime/Core/Extension/CircleExtensions.cs +25 -25
  80. package/Runtime/Core/Extension/ColorExtensions.cs +629 -629
  81. package/Runtime/Core/Extension/DictionaryExtensions.cs +279 -279
  82. package/Runtime/Core/Extension/DirectionExtensions.cs +213 -213
  83. package/Runtime/Core/Extension/EnumExtensions.cs +37 -37
  84. package/Runtime/Core/Extension/EnumExtensions.cs.meta +2 -2
  85. package/Runtime/Core/Extension/HashSetExtensions.cs +12 -12
  86. package/Runtime/Core/Extension/IEnumerableExtensions.cs +122 -122
  87. package/Runtime/Core/Extension/IListExtensions.cs +106 -106
  88. package/Runtime/Core/Extension/LoggingExtensions.cs +258 -258
  89. package/Runtime/Core/Extension/RandomExtensions.cs +109 -109
  90. package/Runtime/Core/Extension/SerializedPropertyExtensions.cs +157 -157
  91. package/Runtime/Core/Extension/StringExtensions.cs +151 -151
  92. package/Runtime/Core/Extension/UnityExtensions.cs +1608 -1608
  93. package/Runtime/Core/Helper/ArrayConverter.cs +39 -39
  94. package/Runtime/Core/Helper/ArrayConverter.cs.meta +2 -2
  95. package/Runtime/Core/Helper/AssignUtilities.cs +14 -14
  96. package/Runtime/Core/Helper/AssignUtilities.cs.meta +11 -11
  97. package/Runtime/Core/Helper/Enumerables.cs +17 -17
  98. package/Runtime/Core/Helper/FormattingHelpers.cs +32 -32
  99. package/Runtime/Core/Helper/Geometry.cs +43 -43
  100. package/Runtime/Core/Helper/Helpers.cs +722 -722
  101. package/Runtime/Core/Helper/Helpers.cs.meta +11 -11
  102. package/Runtime/Core/Helper/IterationHelpers.cs +32 -32
  103. package/Runtime/Core/Helper/IterationHelpers.cs.meta +11 -11
  104. package/Runtime/Core/Helper/LifetimeHelpers.cs +13 -13
  105. package/Runtime/Core/Helper/Objects.cs +769 -769
  106. package/Runtime/Core/Helper/Partials/LogHelpers.cs +13 -13
  107. package/Runtime/Core/Helper/Partials/LogHelpers.cs.meta +2 -2
  108. package/Runtime/Core/Helper/Partials/MathHelpers.cs +30 -30
  109. package/Runtime/Core/Helper/Partials/MathHelpers.cs.meta +2 -2
  110. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs +388 -388
  111. package/Runtime/Core/Helper/Partials/ObjectHelpers.cs.meta +2 -2
  112. package/Runtime/Core/Helper/Partials/TransformHelpers.cs +189 -189
  113. package/Runtime/Core/Helper/Partials/TransformHelpers.cs.meta +2 -2
  114. package/Runtime/Core/Helper/Partials.meta +2 -2
  115. package/Runtime/Core/Helper/ReflectionHelpers.cs +452 -452
  116. package/Runtime/Core/Helper/ReflectionHelpers.cs.meta +2 -2
  117. package/Runtime/Core/Helper/SceneHelper.cs +209 -213
  118. package/Runtime/Core/Helper/SpriteHelpers.cs +41 -41
  119. package/Runtime/Core/Helper/SpriteHelpers.cs.meta +11 -11
  120. package/Runtime/Core/Helper/StringInList.cs +31 -31
  121. package/Runtime/Core/Helper/StringInList.cs.meta +11 -11
  122. package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +82 -82
  123. package/Runtime/Core/Helper/WallMath.cs +166 -166
  124. package/Runtime/Core/Math/Line.cs +55 -55
  125. package/Runtime/Core/Math/Parabola.cs +47 -47
  126. package/Runtime/Core/Math/PointPolygonCheck.cs +36 -36
  127. package/Runtime/Core/Math/PointPolygonCheck.cs.meta +11 -11
  128. package/Runtime/Core/Math/Range.cs +92 -92
  129. package/Runtime/Core/Math/XXHash.cs +310 -310
  130. package/Runtime/Core/Math/XXHash.cs.meta +11 -11
  131. package/Runtime/Core/Model/Direction.cs +43 -43
  132. package/Runtime/Core/OneOf/FastOneOf.cs +152 -152
  133. package/Runtime/Core/OneOf/None.cs +4 -4
  134. package/Runtime/Core/Random/AbstractRandom.cs +585 -585
  135. package/Runtime/Core/Random/DotNetRandom.cs +54 -54
  136. package/Runtime/Core/Random/DotNetRandom.cs.meta +2 -2
  137. package/Runtime/Core/Random/IRandom.cs +161 -161
  138. package/Runtime/Core/Random/LinearCongruentialGenerator.cs +49 -49
  139. package/Runtime/Core/Random/NativePcgRandom.cs +97 -97
  140. package/Runtime/Core/Random/PRNG.cs +7 -7
  141. package/Runtime/Core/Random/PRNG.cs.meta +2 -2
  142. package/Runtime/Core/Random/PcgRandom.cs +149 -149
  143. package/Runtime/Core/Random/PerlinNoise.cs +369 -369
  144. package/Runtime/Core/Random/PerlinNoise.cs.meta +2 -2
  145. package/Runtime/Core/Random/RandomState.cs +131 -131
  146. package/Runtime/Core/Random/RandomUtilities.cs +26 -26
  147. package/Runtime/Core/Random/RandomUtilities.cs.meta +11 -11
  148. package/Runtime/Core/Random/RomuDuo.cs +116 -116
  149. package/Runtime/Core/Random/RomuDuo.cs.meta +2 -2
  150. package/Runtime/Core/Random/SplitMix64.cs +94 -94
  151. package/Runtime/Core/Random/SplitMix64.cs.meta +2 -2
  152. package/Runtime/Core/Random/SquirrelRandom.cs +84 -84
  153. package/Runtime/Core/Random/SystemRandom.cs +162 -162
  154. package/Runtime/Core/Random/ThreadLocalRandom.cs +12 -12
  155. package/Runtime/Core/Random/UnityRandom.cs +57 -57
  156. package/Runtime/Core/Random/UnityRandom.cs.meta +11 -11
  157. package/Runtime/Core/Random/WyRandom.cs +121 -121
  158. package/Runtime/Core/Random/WyRandom.cs.meta +2 -2
  159. package/Runtime/Core/Random/XorShiftRandom.cs +52 -52
  160. package/Runtime/Core/Random/XorShiftRandom.cs.meta +11 -11
  161. package/Runtime/Core/Random/XorShiroRandom.cs +119 -119
  162. package/Runtime/Core/Random/XorShiroRandom.cs.meta +2 -2
  163. package/Runtime/Core/Serialization/JsonConverters/ColorConverter.cs +88 -88
  164. package/Runtime/Core/Serialization/JsonConverters/GameObjectConverter.cs +37 -37
  165. package/Runtime/Core/Serialization/JsonConverters/Matrix4x4Converter.cs +218 -218
  166. package/Runtime/Core/Serialization/JsonConverters/TypeConverter.cs +28 -28
  167. package/Runtime/Core/Serialization/JsonConverters/Vector2Converter.cs +74 -74
  168. package/Runtime/Core/Serialization/JsonConverters/Vector3Converter.cs +81 -81
  169. package/Runtime/Core/Serialization/JsonConverters/Vector4Converter.cs +88 -88
  170. package/Runtime/Core/Serialization/Serializer.cs +195 -195
  171. package/Runtime/Core/Threading/SingleThreadedThreadPool.cs +113 -113
  172. package/Runtime/Protobuf-Net/System.Buffers.dll.meta +33 -33
  173. package/Runtime/Protobuf-Net/System.Collections.Immutable.dll.meta +33 -33
  174. package/Runtime/Protobuf-Net/System.Collections.Immutable.xml +5379 -5379
  175. package/Runtime/Protobuf-Net/System.Collections.Immutable.xml.meta +7 -7
  176. package/Runtime/Protobuf-Net/System.Numerics.Vectors.dll.meta +33 -33
  177. package/Runtime/Protobuf-Net/System.Runtime.CompilerServices.Unsafe.dll.meta +33 -33
  178. package/Runtime/Protobuf-Net/System.Runtime.CompilerServices.Unsafe.xml +290 -290
  179. package/Runtime/Protobuf-Net/System.Runtime.CompilerServices.Unsafe.xml.meta +7 -7
  180. package/Runtime/Protobuf-Net/protobuf-net.Core.dll.meta +33 -33
  181. package/Runtime/Protobuf-Net/protobuf-net.dll.meta +33 -33
  182. package/Runtime/UI/LayeredImage.cs +364 -364
  183. package/Runtime/UI/LayeredImage.cs.meta +2 -2
  184. package/Runtime/UI.meta +2 -2
  185. package/Runtime/Utils/AnimationEventEqualityComparer.cs +161 -161
  186. package/Runtime/Utils/AnimatorEnumStateMachine.cs +88 -88
  187. package/Runtime/Utils/Buffers.cs +33 -33
  188. package/Runtime/Utils/CenterPointOffset.cs +30 -30
  189. package/Runtime/Utils/CenterPointOffset.cs.meta +2 -2
  190. package/Runtime/Utils/CircleLineRenderer.cs +134 -134
  191. package/Runtime/Utils/CoroutineHandler.cs +4 -4
  192. package/Runtime/Utils/CoroutineHandler.cs.meta +2 -2
  193. package/Runtime/Utils/DeferredDisposalResult.cs +23 -23
  194. package/Runtime/Utils/MatchColliderToSprite.cs +94 -94
  195. package/Runtime/Utils/MatchColliderToSprite.cs.meta +2 -2
  196. package/Runtime/Utils/Oscillator.cs +27 -27
  197. package/Runtime/Utils/RuntimeSingleton.cs +69 -69
  198. package/Runtime/Utils/RuntimeSingleton.cs.meta +11 -11
  199. package/Runtime/Utils/SetTextureImportData.cs +69 -69
  200. package/Runtime/Utils/SpriteRendererMetadata.cs +374 -374
  201. package/Runtime/Utils/SpriteRendererMetadata.cs.meta +2 -2
  202. package/Runtime/Utils/SpriteRendererSyncer.cs +100 -100
  203. package/Runtime/Utils/SpriteRendererSyncer.cs.meta +2 -2
  204. package/Runtime/Utils/TextureScale.cs +179 -179
  205. package/Runtime/Utils/TextureScale.cs.meta +2 -2
  206. package/Runtime/WallstopStudios.UnityHelpers.asmdef +13 -13
  207. package/Tests/Runtime/Attributes/ChildComponentTests.cs +81 -81
  208. package/Tests/Runtime/Attributes/Components/ExpectChildSpriteRenderers.cs +28 -28
  209. package/Tests/Runtime/Attributes/Components/ExpectParentSpriteRenderers.cs +28 -28
  210. package/Tests/Runtime/Attributes/ParentComponentTests.cs +68 -68
  211. package/Tests/Runtime/Components/RelationalComponentTesterComplex.cs +34 -34
  212. package/Tests/Runtime/Components/RelationalComponentTesterComplex.cs.meta +2 -2
  213. package/Tests/Runtime/Components/RelationalComponentsTesterSimple.cs +40 -40
  214. package/Tests/Runtime/Components.meta +2 -2
  215. package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs +14 -14
  216. package/Tests/Runtime/DataStructures/BalancedKDTreeTests.cs.meta +11 -11
  217. package/Tests/Runtime/DataStructures/CyclicBufferTests.cs +324 -324
  218. package/Tests/Runtime/DataStructures/QuadTreeTests.cs +14 -14
  219. package/Tests/Runtime/DataStructures/QuadTreeTests.cs.meta +11 -11
  220. package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +130 -130
  221. package/Tests/Runtime/DataStructures/SpatialTreeTests.cs.meta +11 -11
  222. package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs +14 -14
  223. package/Tests/Runtime/DataStructures/UnbalancedKDTreeTests.cs.meta +11 -11
  224. package/Tests/Runtime/DataStructures.meta +8 -8
  225. package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs +439 -439
  226. package/Tests/Runtime/Extensions/DictionaryExtensionTests.cs.meta +2 -2
  227. package/Tests/Runtime/Extensions/EnumExtensionTests.cs +128 -128
  228. package/Tests/Runtime/Extensions/EnumExtensionTests.cs.meta +2 -2
  229. package/Tests/Runtime/Extensions/IListExtensionTests.cs +104 -104
  230. package/Tests/Runtime/Extensions/RandomExtensionTests.cs +27 -27
  231. package/Tests/Runtime/Extensions/RandomExtensionTests.cs.meta +2 -2
  232. package/Tests/Runtime/Extensions/StringExtensionTests.cs +31 -31
  233. package/Tests/Runtime/Extensions/StringExtensionTests.cs.meta +2 -2
  234. package/Tests/Runtime/Extensions.meta +2 -2
  235. package/Tests/Runtime/Helper/ArrayConverterTests.cs +19 -19
  236. package/Tests/Runtime/Helper/ArrayConverterTests.cs.meta +2 -2
  237. package/Tests/Runtime/Helper/FormattingHelperTests.cs +129 -129
  238. package/Tests/Runtime/Helper/FormattingHelperTests.cs.meta +2 -2
  239. package/Tests/Runtime/Helper/ObjectHelperTests.cs +402 -402
  240. package/Tests/Runtime/Helper/ObjectHelperTests.cs.meta +2 -2
  241. package/Tests/Runtime/Helper/ReflectionHelperTests.cs +536 -536
  242. package/Tests/Runtime/Helper/SceneHelperTests.cs +94 -94
  243. package/Tests/Runtime/Helper/WallMathTests.cs +233 -233
  244. package/Tests/Runtime/Helper/WallMathTests.cs.meta +2 -2
  245. package/Tests/Runtime/Helper.meta +2 -2
  246. package/Tests/Runtime/Performance/KDTreePerformanceTests.cs +14 -14
  247. package/Tests/Runtime/Performance/KDTreePerformanceTests.cs.meta +11 -11
  248. package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +14 -14
  249. package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs.meta +11 -11
  250. package/Tests/Runtime/Performance/RandomPerformanceTests.cs +157 -157
  251. package/Tests/Runtime/Performance/RandomPerformanceTests.cs.meta +11 -11
  252. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs +61 -61
  253. package/Tests/Runtime/Performance/RelationComponentPerformanceTests.cs.meta +2 -2
  254. package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +154 -154
  255. package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs.meta +11 -11
  256. package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs +14 -14
  257. package/Tests/Runtime/Performance/UnbalancedKDTreeTests.cs.meta +11 -11
  258. package/Tests/Runtime/Performance.meta +8 -8
  259. package/Tests/Runtime/Random/DotNetRandomTests.cs +9 -9
  260. package/Tests/Runtime/Random/DotNetRandomTests.cs.meta +2 -2
  261. package/Tests/Runtime/Random/LinearCongruentialGeneratorTests.cs +12 -12
  262. package/Tests/Runtime/Random/PcgRandomTests.cs +9 -9
  263. package/Tests/Runtime/Random/PcgRandomTests.cs.meta +11 -11
  264. package/Tests/Runtime/Random/RandomTestBase.cs +787 -787
  265. package/Tests/Runtime/Random/RandomTestBase.cs.meta +11 -11
  266. package/Tests/Runtime/Random/RomuDuoRandomTests.cs +9 -9
  267. package/Tests/Runtime/Random/RomuDuoRandomTests.cs.meta +2 -2
  268. package/Tests/Runtime/Random/SplitMix64RandomTests.cs +9 -9
  269. package/Tests/Runtime/Random/SplitMix64RandomTests.cs.meta +2 -2
  270. package/Tests/Runtime/Random/SquirrelRandomTests.cs +14 -14
  271. package/Tests/Runtime/Random/SquirrelRandomTests.cs.meta +11 -11
  272. package/Tests/Runtime/Random/SystemRandomTests.cs +10 -10
  273. package/Tests/Runtime/Random/SystemRandomTests.cs.meta +11 -11
  274. package/Tests/Runtime/Random/UnityRandomTests.cs +9 -9
  275. package/Tests/Runtime/Random/UnityRandomTests.cs.meta +11 -11
  276. package/Tests/Runtime/Random/WyRandomTests.cs +9 -9
  277. package/Tests/Runtime/Random/WyRandomTests.cs.meta +2 -2
  278. package/Tests/Runtime/Random/XorShiftRandomTests.cs +9 -9
  279. package/Tests/Runtime/Random/XorShiftRandomTests.cs.meta +11 -11
  280. package/Tests/Runtime/Random/XorShiroRandomTests.cs +9 -9
  281. package/Tests/Runtime/Random/XorShiroRandomTests.cs.meta +2 -2
  282. package/Tests/Runtime/Random.meta +8 -8
  283. package/Tests/Runtime/Serialization/JsonSerializationTest.cs +156 -156
  284. package/Tests/Runtime/Serialization/JsonSerializationTest.cs.meta +2 -2
  285. package/Tests/Runtime/Serialization.meta +2 -2
  286. package/Tests/Runtime/Utils/SpriteRendererMetadataTests.cs +399 -399
  287. package/Tests/Runtime/Utils/SpriteRendererMetadataTests.cs.meta +2 -2
  288. package/Tests/Runtime/Utils.meta +2 -2
  289. package/Tests/Runtime/WallstopStudios.UnityHelpers.Tests.Runtime.asmdef +22 -22
  290. package/Tests/Runtime/WallstopStudios.UnityHelpers.Tests.Runtime.asmdef.meta +7 -7
  291. package/Tests/Runtime.meta +8 -8
  292. package/package.json +38 -38
@@ -1,1608 +1,1608 @@
1
- namespace UnityHelpers.Core.Extension
2
- {
3
- using System;
4
- using System.Collections.Generic;
5
- using System.Linq;
6
- using System.Threading.Tasks;
7
- using DataStructure;
8
- using DataStructure.Adapters;
9
- using Helper;
10
- using Random;
11
- using UnityEditor;
12
- using UnityEngine;
13
- using UnityEngine.UI;
14
- using Utils;
15
-
16
- public static class UnityExtensions
17
- {
18
- public static Vector2 GetCenter(this GameObject gameObject)
19
- {
20
- if (gameObject.TryGetComponent(out CenterPointOffset centerPointOffset))
21
- {
22
- return centerPointOffset.CenterPoint;
23
- }
24
-
25
- return gameObject.transform.position;
26
- }
27
-
28
- public static Bounds Bounds(this Rect rect)
29
- {
30
- return new Bounds(rect.center, rect.size);
31
- }
32
-
33
- public static Rect Rect(this Bounds bounds)
34
- {
35
- return new Rect(bounds.center - bounds.extents, bounds.size);
36
- }
37
-
38
- public static Rect GetWorldRect(this RectTransform transform)
39
- {
40
- Vector3[] fourCorners = new Vector3[4];
41
- transform.GetWorldCorners(fourCorners);
42
-
43
- float[] xValues = fourCorners.Select(vector => vector.x).ToArray();
44
- float[] yValues = fourCorners.Select(vector => vector.y).ToArray();
45
- float minX = Mathf.Min(xValues);
46
- float maxX = Mathf.Max(xValues);
47
- float minY = Mathf.Min(yValues);
48
- float maxY = Mathf.Max(yValues);
49
-
50
- return new Rect(minX, minY, maxX - minX, maxY - minY);
51
- }
52
-
53
- public static Bounds OrthographicBounds(this Camera camera)
54
- {
55
- float screenAspect = (float)Screen.width / Screen.height;
56
- float cameraHeight = camera.orthographicSize * 2;
57
- Bounds bounds = new(
58
- (Vector2)camera.transform.position,
59
- new Vector3(cameraHeight * screenAspect, cameraHeight, 1)
60
- );
61
- return bounds;
62
- }
63
-
64
- public static string ToJsonString(this Vector3 vector)
65
- {
66
- return $"{{{vector.x}, {vector.y}, {vector.z}}}";
67
- }
68
-
69
- public static string ToJsonString(this Vector2 vector)
70
- {
71
- return $"{{{vector.x}, {vector.y}}}";
72
- }
73
-
74
- public static bool IsNoise(this Vector2 inputVector)
75
- {
76
- return Mathf.Abs(inputVector.x) <= 0.2f && Mathf.Abs(inputVector.y) <= 0.2f;
77
- }
78
-
79
- public static void Stop(this Rigidbody2D rigidBody)
80
- {
81
- if (rigidBody == null)
82
- {
83
- return;
84
- }
85
-
86
- rigidBody.velocity = Vector2.zero;
87
- rigidBody.angularVelocity = 0;
88
- rigidBody.Sleep();
89
- }
90
-
91
- public static BoundsInt ExpandBounds(this BoundsInt source, BoundsInt other)
92
- {
93
- int xMin = Math.Min(source.xMin, other.xMin);
94
- int xMax = Math.Max(source.xMax, other.xMax);
95
- int yMin = Math.Min(source.yMin, other.yMin);
96
- int yMax = Math.Max(source.yMax, other.yMax);
97
- int zMin = Math.Min(source.zMin, other.zMin);
98
- int zMax = Math.Max(source.zMax, other.zMax);
99
- return new BoundsInt(xMin, yMin, zMin, xMax - xMin, yMax - yMin, zMax - zMin);
100
- }
101
-
102
- public static BoundsInt? GetBounds(this IEnumerable<Vector3Int> positions)
103
- {
104
- bool any = false;
105
- int xMin = int.MaxValue;
106
- int xMax = int.MinValue;
107
- int yMin = int.MaxValue;
108
- int yMax = int.MinValue;
109
- int zMin = int.MaxValue;
110
- int zMax = int.MinValue;
111
- foreach (Vector3Int position in positions)
112
- {
113
- any = true;
114
- xMin = Math.Min(xMin, position.x);
115
- xMax = Math.Max(xMax, position.x);
116
- yMin = Math.Min(yMin, position.y);
117
- yMax = Math.Max(yMax, position.y);
118
- zMin = Math.Min(zMin, position.z);
119
- zMax = Math.Max(zMax, position.z);
120
- }
121
-
122
- if (!any)
123
- {
124
- return null;
125
- }
126
- return new BoundsInt(
127
- xMin,
128
- yMin,
129
- zMin,
130
- (xMax - xMin) + 1,
131
- (yMax - yMin) + 1,
132
- (zMax - zMin) + 1
133
- );
134
- }
135
-
136
- public static BoundsInt? GetBounds(this IEnumerable<FastVector3Int> positions)
137
- {
138
- bool any = false;
139
- int xMin = int.MaxValue;
140
- int xMax = int.MinValue;
141
- int yMin = int.MaxValue;
142
- int yMax = int.MinValue;
143
- int zMin = int.MaxValue;
144
- int zMax = int.MinValue;
145
- foreach (FastVector3Int position in positions)
146
- {
147
- any = true;
148
- xMin = Math.Min(xMin, position.x);
149
- xMax = Math.Max(xMax, position.x);
150
- yMin = Math.Min(yMin, position.y);
151
- yMax = Math.Max(yMax, position.y);
152
- zMin = Math.Min(zMin, position.z);
153
- zMax = Math.Max(zMax, position.z);
154
- }
155
-
156
- if (!any)
157
- {
158
- return null;
159
- }
160
- return new BoundsInt(
161
- xMin,
162
- yMin,
163
- zMin,
164
- (xMax - xMin) + 1,
165
- (yMax - yMin) + 1,
166
- (zMax - zMin) + 1
167
- );
168
- }
169
-
170
- public static Bounds? GetBounds(this IEnumerable<Vector2> positions)
171
- {
172
- bool any = false;
173
- float xMin = float.MaxValue;
174
- float xMax = float.MinValue;
175
- float yMin = float.MaxValue;
176
- float yMax = float.MinValue;
177
- foreach (Vector2 position in positions)
178
- {
179
- any = true;
180
- xMin = Math.Min(xMin, position.x);
181
- xMax = Math.Max(xMax, position.x);
182
- yMin = Math.Min(yMin, position.y);
183
- yMax = Math.Max(yMax, position.y);
184
- }
185
-
186
- if (!any)
187
- {
188
- return null;
189
- }
190
-
191
- Vector3 center = new((xMax + xMin) / 2f, (yMax + yMin) / 2f);
192
- Vector3 size = new(xMax - xMin, yMax - yMin);
193
- return new Bounds(center, size);
194
- }
195
-
196
- public static Bounds? GetBounds(this IEnumerable<Bounds> boundaries)
197
- {
198
- float minX = float.MaxValue;
199
- float minY = float.MaxValue;
200
- float maxX = float.MinValue;
201
- float maxY = float.MinValue;
202
- bool any = false;
203
- foreach (Bounds boundary in boundaries)
204
- {
205
- any = true;
206
- Vector3 min = boundary.min;
207
- Vector3 max = boundary.max;
208
- minX = Math.Min(minX, min.x);
209
- maxX = Math.Max(maxX, max.x);
210
- minY = Math.Min(minY, min.y);
211
- maxY = Math.Max(maxY, max.y);
212
- }
213
-
214
- if (!any)
215
- {
216
- return null;
217
- }
218
-
219
- return new Bounds(
220
- new Vector3(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2),
221
- new Vector3(maxX - minX, maxY - minY)
222
- );
223
- }
224
-
225
- // https://www.habrador.com/tutorials/math/8-convex-hull/
226
- public static List<Vector3Int> BuildConvexHull(
227
- this IEnumerable<Vector3Int> pointsSet,
228
- Grid grid,
229
- IRandom random = null,
230
- bool includeColinearPoints = true
231
- )
232
- {
233
- List<Vector3Int> points = pointsSet.ToList();
234
- if (points.Count <= 3)
235
- {
236
- return points;
237
- }
238
-
239
- random ??= PRNG.Instance;
240
-
241
- Vector2 CellToWorld(Vector3Int position) => grid.CellToWorld(position);
242
-
243
- Vector3Int startPoint = points[0];
244
- Vector2 startPointWorldPosition = CellToWorld(startPoint);
245
- for (int i = 1; i < points.Count; ++i)
246
- {
247
- Vector3Int testPoint = points[i];
248
- Vector2 testPointWorldPosition = CellToWorld(testPoint);
249
- // ReSharper disable once CompareOfFloatsByEqualityOperator
250
- if (
251
- testPointWorldPosition.x < startPointWorldPosition.x
252
- || (
253
- Mathf.Approximately(testPointWorldPosition.x, startPointWorldPosition.x)
254
- && testPointWorldPosition.y < startPointWorldPosition.y
255
- )
256
- )
257
- {
258
- startPoint = testPoint;
259
- startPointWorldPosition = testPointWorldPosition;
260
- }
261
- }
262
-
263
- List<Vector3Int> convexHull = new() { startPoint };
264
- _ = points.Remove(startPoint);
265
- Vector3Int currentPoint = convexHull[0];
266
- List<Vector3Int> colinearPoints = new();
267
- int counter = 0;
268
- while (true)
269
- {
270
- if (counter == 2)
271
- {
272
- points.Add(convexHull[0]);
273
- }
274
-
275
- if (points.Count <= 0)
276
- {
277
- return convexHull;
278
- }
279
-
280
- Vector3Int nextPoint = random.NextOf(points);
281
- Vector2 currentPointWorldPosition = CellToWorld(currentPoint);
282
- Vector2 nextPointWorldPosition = CellToWorld(nextPoint);
283
- foreach (Vector3Int point in points)
284
- {
285
- if (Equals(point, nextPoint))
286
- {
287
- continue;
288
- }
289
-
290
- float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
291
- currentPointWorldPosition,
292
- nextPointWorldPosition,
293
- CellToWorld(point)
294
- );
295
- if (Mathf.Approximately(relation, 0))
296
- {
297
- colinearPoints.Add(point);
298
- }
299
- else if (relation < 0)
300
- {
301
- nextPoint = point;
302
- nextPointWorldPosition = CellToWorld(nextPoint);
303
- colinearPoints.Clear();
304
- }
305
- }
306
-
307
- if (0 < colinearPoints.Count)
308
- {
309
- colinearPoints.Add(nextPoint);
310
- colinearPoints.Sort(
311
- (lhs, rhs) =>
312
- (CellToWorld(lhs) - currentPointWorldPosition).sqrMagnitude.CompareTo(
313
- (CellToWorld(rhs) - currentPointWorldPosition).sqrMagnitude
314
- )
315
- );
316
-
317
- if (includeColinearPoints)
318
- {
319
- convexHull.AddRange(colinearPoints);
320
- }
321
- else
322
- {
323
- convexHull.Add(colinearPoints[^1]);
324
- _ = points.Remove(colinearPoints[^1]);
325
- }
326
-
327
- currentPoint = colinearPoints[^1];
328
- _ = points.RemoveAll(colinearPoints.Contains);
329
- colinearPoints.Clear();
330
- }
331
- else
332
- {
333
- convexHull.Add(nextPoint);
334
- _ = points.Remove(nextPoint);
335
- currentPoint = nextPoint;
336
- }
337
-
338
- if (Equals(currentPoint, convexHull[0]))
339
- {
340
- convexHull.RemoveAt(convexHull.Count - 1);
341
- break;
342
- }
343
-
344
- ++counter;
345
- }
346
-
347
- return convexHull;
348
- }
349
-
350
- public static List<FastVector3Int> BuildConvexHull(
351
- this IEnumerable<FastVector3Int> pointsSet,
352
- Grid grid,
353
- IRandom random = null,
354
- bool includeColinearPoints = false
355
- )
356
- {
357
- List<FastVector3Int> points = pointsSet.ToList();
358
- if (points.Count <= 3)
359
- {
360
- return points;
361
- }
362
-
363
- random ??= PRNG.Instance;
364
-
365
- Vector2 CellToWorld(FastVector3Int position) => grid.CellToWorld(position);
366
-
367
- FastVector3Int startPoint = points[0];
368
- Vector2 startPointWorldPosition = CellToWorld(startPoint);
369
- for (int i = 1; i < points.Count; ++i)
370
- {
371
- FastVector3Int testPoint = points[i];
372
- Vector2 testPointWorldPosition = CellToWorld(testPoint);
373
- if (
374
- testPointWorldPosition.x < startPointWorldPosition.x
375
- || (
376
- Mathf.Approximately(testPointWorldPosition.x, startPointWorldPosition.x)
377
- && testPointWorldPosition.y < startPointWorldPosition.y
378
- )
379
- )
380
- {
381
- startPoint = testPoint;
382
- startPointWorldPosition = testPointWorldPosition;
383
- }
384
- }
385
-
386
- List<FastVector3Int> convexHull = new() { startPoint };
387
- _ = points.Remove(startPoint);
388
- FastVector3Int currentPoint = convexHull[0];
389
- List<FastVector3Int> colinearPoints = new();
390
- int counter = 0;
391
- while (true)
392
- {
393
- if (counter == 2)
394
- {
395
- points.Add(convexHull[0]);
396
- }
397
-
398
- if (points.Count <= 0)
399
- {
400
- return convexHull;
401
- }
402
-
403
- FastVector3Int nextPoint = random.NextOf(points);
404
- Vector2 currentPointWorldPosition = CellToWorld(currentPoint);
405
- Vector2 nextPointWorldPosition = CellToWorld(nextPoint);
406
- foreach (FastVector3Int point in points)
407
- {
408
- if (point.Equals(nextPoint))
409
- {
410
- continue;
411
- }
412
-
413
- float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
414
- currentPointWorldPosition,
415
- nextPointWorldPosition,
416
- CellToWorld(point)
417
- );
418
- if (Mathf.Approximately(relation, 0))
419
- {
420
- colinearPoints.Add(point);
421
- }
422
- else if (relation < 0)
423
- {
424
- nextPoint = point;
425
- nextPointWorldPosition = CellToWorld(nextPoint);
426
- colinearPoints.Clear();
427
- }
428
- }
429
-
430
- if (0 < colinearPoints.Count)
431
- {
432
- colinearPoints.Add(nextPoint);
433
- colinearPoints.Sort(
434
- (lhs, rhs) =>
435
- (CellToWorld(lhs) - currentPointWorldPosition).sqrMagnitude.CompareTo(
436
- (CellToWorld(rhs) - currentPointWorldPosition).sqrMagnitude
437
- )
438
- );
439
-
440
- if (includeColinearPoints)
441
- {
442
- convexHull.AddRange(colinearPoints);
443
- }
444
- else
445
- {
446
- convexHull.Add(colinearPoints[^1]);
447
- _ = points.Remove(colinearPoints[^1]);
448
- }
449
-
450
- currentPoint = colinearPoints[^1];
451
- _ = points.RemoveAll(colinearPoints.Contains);
452
- colinearPoints.Clear();
453
- }
454
- else
455
- {
456
- convexHull.Add(nextPoint);
457
- _ = points.Remove(nextPoint);
458
- currentPoint = nextPoint;
459
- }
460
-
461
- if (currentPoint.Equals(convexHull[0]))
462
- {
463
- convexHull.RemoveAt(convexHull.Count - 1);
464
- break;
465
- }
466
-
467
- ++counter;
468
- }
469
-
470
- return convexHull;
471
- }
472
-
473
- public static bool IsConvexHullInsideConvexHull(
474
- this List<FastVector3Int> convexHull,
475
- Grid grid,
476
- List<FastVector3Int> maybeInside
477
- )
478
- {
479
- foreach (FastVector3Int point in maybeInside)
480
- {
481
- if (!IsPointInsideConvexHull(convexHull, grid, point))
482
- {
483
- return false;
484
- }
485
- }
486
-
487
- return true;
488
- }
489
-
490
- public static bool IsPointInsideConvexHull(
491
- this List<Vector3Int> convexHull,
492
- Grid grid,
493
- Vector3Int point
494
- )
495
- {
496
- for (int i = 0; i < convexHull.Count; ++i)
497
- {
498
- Vector3Int lhs = convexHull[i];
499
- Vector3Int rhs = convexHull[(i + 1) % convexHull.Count];
500
- float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
501
- grid.CellToWorld(lhs),
502
- grid.CellToWorld(rhs),
503
- grid.CellToWorld(point)
504
- );
505
- if (relation < 0)
506
- {
507
- return false;
508
- }
509
- }
510
- return true;
511
- }
512
-
513
- public static bool IsPointInsideConvexHull(
514
- this List<FastVector3Int> convexHull,
515
- Grid grid,
516
- FastVector3Int point
517
- )
518
- {
519
- for (int i = 0; i < convexHull.Count; ++i)
520
- {
521
- FastVector3Int lhs = convexHull[i];
522
- FastVector3Int rhs = convexHull[(i + 1) % convexHull.Count];
523
- float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
524
- grid.CellToWorld(lhs),
525
- grid.CellToWorld(rhs),
526
- grid.CellToWorld(point)
527
- );
528
- if (relation < 0)
529
- {
530
- return false;
531
- }
532
- }
533
- return true;
534
- }
535
-
536
- public static bool IsConvexHullInsideConvexHull(
537
- this List<Vector3Int> convexHull,
538
- Grid grid,
539
- List<Vector3Int> maybeInside
540
- )
541
- {
542
- foreach (Vector3Int point in maybeInside)
543
- {
544
- if (!IsPointInsideConvexHull(convexHull, grid, point))
545
- {
546
- return false;
547
- }
548
- }
549
-
550
- return true;
551
- }
552
-
553
- private readonly struct HullEdge
554
- {
555
- public readonly float edgeLength;
556
-
557
- public readonly FastVector3Int from;
558
- public readonly FastVector3Int to;
559
-
560
- public readonly Vector2 fromWorld;
561
- public readonly Vector2 toWorld;
562
-
563
- private readonly Grid _grid;
564
-
565
- public HullEdge(FastVector3Int from, FastVector3Int to, Grid grid)
566
- {
567
- this.from = from;
568
- this.to = to;
569
- _grid = grid;
570
- fromWorld = grid.CellToWorld(from);
571
- toWorld = grid.CellToWorld(to);
572
- edgeLength = (fromWorld - toWorld).sqrMagnitude;
573
- }
574
-
575
- public bool Intersects(HullEdge other)
576
- {
577
- return UnityExtensions.Intersects(
578
- fromWorld,
579
- toWorld,
580
- other.fromWorld,
581
- other.toWorld
582
- );
583
- }
584
-
585
- public float LargestAngle(FastVector3Int point)
586
- {
587
- Vector2 worldPoint = _grid.CellToWorld(point);
588
- float angleFrom = Vector2.Angle((toWorld - fromWorld), (worldPoint - fromWorld));
589
- float angleTo = Vector2.Angle((fromWorld - toWorld), (worldPoint - toWorld));
590
- return Math.Max(angleFrom, angleTo);
591
- }
592
- }
593
-
594
- private sealed class ConcaveHullComparer : IComparer<HullEdge>
595
- {
596
- public static readonly ConcaveHullComparer Instance = new();
597
-
598
- private ConcaveHullComparer() { }
599
-
600
- public int Compare(HullEdge lhs, HullEdge rhs)
601
- {
602
- int comparison = lhs.edgeLength.CompareTo(rhs.edgeLength);
603
- if (comparison != 0)
604
- {
605
- return comparison;
606
- }
607
-
608
- comparison = lhs.from.CompareTo(rhs.from);
609
- if (comparison != 0)
610
- {
611
- return comparison;
612
- }
613
-
614
- return lhs.to.CompareTo(rhs.to);
615
- }
616
- }
617
-
618
- public static List<FastVector3Int> BuildConcaveHull3(
619
- this IReadOnlyCollection<FastVector3Int> gridPositions,
620
- Grid grid,
621
- IRandom random = null,
622
- int bucketSize = 40,
623
- float angleThreshold = 90f
624
- )
625
- {
626
- List<FastVector3Int> convexHull = gridPositions.BuildConvexHull(grid, random);
627
- List<HullEdge> concaveHullEdges = new();
628
-
629
- SortedSet<HullEdge> data = new(ConcaveHullComparer.Instance);
630
- for (int i = 0; i < convexHull.Count; ++i)
631
- {
632
- FastVector3Int lhs = convexHull[i];
633
- FastVector3Int rhs = convexHull[(i + 1) % convexHull.Count];
634
- HullEdge edge = new(lhs, rhs, grid);
635
- _ = data.Add(edge);
636
- }
637
-
638
- HashSet<FastVector3Int> remainingPoints = gridPositions.ToHashSet();
639
- remainingPoints.ExceptWith(convexHull);
640
-
641
- Vector2 CellToWorld(FastVector3Int cell) => grid.CellToWorld(cell);
642
-
643
- Bounds? maybeBounds = gridPositions.Select(CellToWorld).GetBounds();
644
- if (maybeBounds == null)
645
- {
646
- throw new ArgumentException(nameof(gridPositions));
647
- }
648
-
649
- QuadTree<FastVector3Int> NewQuadTree() =>
650
- new(gridPositions, CellToWorld, maybeBounds.Value, bucketSize: bucketSize);
651
-
652
- QuadTree<FastVector3Int> quadTree = NewQuadTree();
653
- List<FastVector3Int> neighbors = Buffers<FastVector3Int>.List;
654
- while (0 < data.Count)
655
- {
656
- HullEdge edge = data.Max;
657
- _ = data.Remove(edge);
658
-
659
- Vector2 edgeCenter = edge.fromWorld + (edge.toWorld - edge.fromWorld) / 2;
660
- quadTree.GetApproximateNearestNeighbors(edgeCenter, bucketSize, neighbors);
661
- float localMaximumDistance = float.MinValue;
662
- foreach (FastVector3Int neighbor in neighbors)
663
- {
664
- if (neighbor == edge.to || neighbor == edge.from)
665
- {
666
- continue;
667
- }
668
-
669
- localMaximumDistance = Math.Max(
670
- localMaximumDistance,
671
- (CellToWorld(neighbor) - edgeCenter).sqrMagnitude
672
- );
673
- }
674
-
675
- if (edge.edgeLength <= localMaximumDistance)
676
- {
677
- concaveHullEdges.Add(edge);
678
- continue;
679
- }
680
-
681
- float smallestAngle = float.MaxValue;
682
- FastVector3Int? maybeChosenPoint = null;
683
- foreach (FastVector3Int remainingPoint in remainingPoints)
684
- {
685
- float maximumAngle = edge.LargestAngle(remainingPoint);
686
- if (maximumAngle < smallestAngle)
687
- {
688
- maybeChosenPoint = remainingPoint;
689
- smallestAngle = maximumAngle;
690
- }
691
- }
692
-
693
- if (angleThreshold < smallestAngle)
694
- {
695
- concaveHullEdges.Add(edge);
696
- continue;
697
- }
698
-
699
- if (maybeChosenPoint == null)
700
- {
701
- concaveHullEdges.Add(edge);
702
- continue;
703
- }
704
-
705
- FastVector3Int chosenPoint = maybeChosenPoint.Value;
706
- HullEdge e2 = new(edge.from, chosenPoint, grid);
707
- HullEdge e3 = new(chosenPoint, edge.to, grid);
708
- bool intersects = false;
709
- foreach (HullEdge convexHullEdge in data)
710
- {
711
- if (convexHullEdge.Intersects(e2))
712
- {
713
- intersects = true;
714
- break;
715
- }
716
-
717
- if (convexHullEdge.Intersects(e3))
718
- {
719
- intersects = true;
720
- break;
721
- }
722
- }
723
-
724
- if (!intersects)
725
- {
726
- foreach (HullEdge concaveHullEdge in concaveHullEdges)
727
- {
728
- if (concaveHullEdge.Intersects(e2))
729
- {
730
- intersects = true;
731
- break;
732
- }
733
-
734
- if (concaveHullEdge.Intersects(e3))
735
- {
736
- intersects = true;
737
- break;
738
- }
739
- }
740
- }
741
-
742
- if (!intersects)
743
- {
744
- _ = data.Add(e2);
745
- _ = data.Add(e3);
746
- _ = remainingPoints.Remove(maybeChosenPoint.Value);
747
- }
748
- else
749
- {
750
- concaveHullEdges.Add(edge);
751
- }
752
- }
753
-
754
- List<FastVector3Int> concaveHull = new(concaveHullEdges.Count);
755
- HullEdge current = concaveHullEdges[0];
756
- concaveHullEdges.RemoveAtSwapBack(0);
757
- concaveHull.Add(current.from);
758
- while (0 < concaveHullEdges.Count)
759
- {
760
- FastVector3Int to = current.to;
761
- int nextIndex = concaveHullEdges.FindIndex(edge => edge.from == to);
762
- current = concaveHullEdges[nextIndex];
763
- concaveHullEdges.RemoveAtSwapBack(nextIndex);
764
- concaveHull.Add(current.from);
765
- }
766
-
767
- return concaveHull;
768
- }
769
-
770
- // https://www.researchgate.net/publication/220868874_Concave_hull_A_k-nearest_neighbours_approach_for_the_computation_of_the_region_occupied_by_a_set_of_points
771
-
772
- public static List<FastVector3Int> BuildConcaveHull2(
773
- this IReadOnlyCollection<FastVector3Int> gridPositions,
774
- Grid grid,
775
- IRandom random = null,
776
- int nearestNeighbors = 3
777
- )
778
- {
779
- const int minimumNearestNeighbors = 3;
780
- nearestNeighbors = Math.Max(minimumNearestNeighbors, nearestNeighbors);
781
- List<FastVector3Int> dataSet = gridPositions.Distinct().ToList();
782
- if (dataSet.Count <= 3)
783
- {
784
- return dataSet;
785
- }
786
-
787
- nearestNeighbors = Math.Min(dataSet.Count, nearestNeighbors);
788
-
789
- IComparer<FastVector3Int> comparison = Comparer<FastVector3Int>.Create(
790
- (lhs, rhs) => grid.CellToWorld(lhs).y.CompareTo(grid.CellToWorld(rhs).y)
791
- );
792
-
793
- FastVector3Int? maybeFirst = null;
794
- foreach (FastVector3Int gridPosition in dataSet)
795
- {
796
- if (maybeFirst == null || comparison.Compare(gridPosition, maybeFirst.Value) < 0)
797
- {
798
- maybeFirst = gridPosition;
799
- }
800
- }
801
-
802
- if (maybeFirst == null)
803
- {
804
- return dataSet;
805
- }
806
-
807
- FastVector3Int first = maybeFirst.Value;
808
- List<FastVector3Int> hull = new(dataSet.Count) { first };
809
- int step = 2;
810
- float previousAngle = 0f;
811
- FastVector3Int current = first;
812
- _ = dataSet.Remove(current);
813
-
814
- float CalculateAngle(Vector2 lhs, Vector2 rhs)
815
- {
816
- return Mathf.Atan2(rhs.y - lhs.y, rhs.x - lhs.x);
817
- }
818
-
819
- // https://github.com/merowech/java-concave-hull/blob/master/ConcaveHull.java
820
- float AngleDifference(float lhsAngle, float rhsAngle)
821
- {
822
- if (0 < lhsAngle && 0 <= rhsAngle && rhsAngle < lhsAngle)
823
- {
824
- return Math.Abs(lhsAngle - rhsAngle);
825
- }
826
-
827
- if (0 <= lhsAngle && 0 < rhsAngle && lhsAngle < rhsAngle)
828
- {
829
- return 2 * Mathf.PI + lhsAngle - rhsAngle;
830
- }
831
-
832
- if (lhsAngle < 0 && rhsAngle <= 0 && lhsAngle < rhsAngle)
833
- {
834
- return 2 * Mathf.PI + lhsAngle + Math.Abs(rhsAngle);
835
- }
836
-
837
- if (lhsAngle <= 0 && rhsAngle < 0 && rhsAngle < lhsAngle)
838
- {
839
- return Math.Abs(lhsAngle - rhsAngle);
840
- }
841
-
842
- if (lhsAngle <= 0 && 0 < rhsAngle)
843
- {
844
- return 2 * Mathf.PI + lhsAngle - rhsAngle;
845
- }
846
-
847
- if (0 <= lhsAngle && rhsAngle <= 0)
848
- {
849
- return lhsAngle + Math.Abs(rhsAngle);
850
- }
851
-
852
- return 0f;
853
- }
854
-
855
- // Order by descending right hand turns
856
- int RightHandTurnComparison(FastVector3Int lhs, FastVector3Int rhs)
857
- {
858
- // TODO: I think this is fucked
859
- Vector2 currentPoint = grid.CellToWorld(current);
860
- Vector2 lhsPoint = grid.CellToWorld(lhs);
861
- Vector2 rhsPoint = grid.CellToWorld(rhs);
862
-
863
- float lhsAngle = AngleDifference(
864
- previousAngle,
865
- CalculateAngle(currentPoint, lhsPoint)
866
- );
867
- float rhsAngle = AngleDifference(
868
- previousAngle,
869
- CalculateAngle(currentPoint, rhsPoint)
870
- );
871
- return rhsAngle.CompareTo(lhsAngle);
872
- }
873
-
874
- List<FastVector3Int> clockwisePoints = Buffers<FastVector3Int>.List;
875
- void FindNearestNeighborsAndPutInClockwisePoints()
876
- {
877
- clockwisePoints.Clear();
878
- clockwisePoints.AddRange(dataSet);
879
- Vector2 currentPoint = grid.CellToWorld(current);
880
- clockwisePoints.Sort(
881
- (lhs, rhs) =>
882
- {
883
- Vector2 lhsPoint = grid.CellToWorld(lhs);
884
- Vector2 rhsPoint = grid.CellToWorld(rhs);
885
- return (lhsPoint - currentPoint).sqrMagnitude.CompareTo(
886
- (rhsPoint - currentPoint).sqrMagnitude
887
- );
888
- }
889
- );
890
- if (nearestNeighbors < clockwisePoints.Count)
891
- {
892
- clockwisePoints.RemoveRange(
893
- nearestNeighbors,
894
- clockwisePoints.Count - nearestNeighbors
895
- );
896
- }
897
- }
898
-
899
- while (0 < dataSet.Count)
900
- {
901
- if (step == 5)
902
- {
903
- dataSet.Add(first);
904
- }
905
-
906
- FindNearestNeighborsAndPutInClockwisePoints();
907
- clockwisePoints.Sort(RightHandTurnComparison);
908
-
909
- bool intersects = true;
910
- int i = -1;
911
- while (intersects && i < clockwisePoints.Count - 1)
912
- {
913
- ++i;
914
-
915
- FastVector3Int indexedPoint = clockwisePoints[i];
916
- int lastPoint = indexedPoint == first ? 1 : 0;
917
- int j = 2;
918
- intersects = false;
919
- Vector2 lhsTo = grid.CellToWorld(indexedPoint);
920
- while (!intersects && j < hull.Count - lastPoint)
921
- {
922
- Vector2 lhsFrom = grid.CellToWorld(hull[step - 2]);
923
- Vector2 rhsFrom = grid.CellToWorld(hull[step - 2 - j]);
924
- Vector2 rhsTo = grid.CellToWorld(hull[step - 1 - j]);
925
- intersects = Intersects(lhsFrom, lhsTo, rhsFrom, rhsTo);
926
- ++j;
927
- }
928
- }
929
-
930
- if (intersects)
931
- {
932
- for (i = dataSet.Count - 1; 0 <= i; --i)
933
- {
934
- if (!IsPositionInside(hull, dataSet[i], grid))
935
- {
936
- return BuildConcaveHull2(
937
- gridPositions,
938
- grid,
939
- random,
940
- nearestNeighbors + 1
941
- );
942
- }
943
- }
944
-
945
- return hull;
946
- }
947
-
948
- current = clockwisePoints[i];
949
- if (current != first)
950
- {
951
- hull.Add(current);
952
- }
953
- else
954
- {
955
- break;
956
- }
957
-
958
- int currentIndex = dataSet.IndexOf(current);
959
- if (0 <= currentIndex)
960
- {
961
- dataSet.RemoveAtSwapBack(currentIndex);
962
- }
963
-
964
- previousAngle = CalculateAngle(
965
- grid.CellToWorld(hull[step - 1]),
966
- grid.CellToWorld(hull[step - 2])
967
- );
968
- ++step;
969
- }
970
-
971
- for (int i = dataSet.Count - 1; 0 <= i; --i)
972
- {
973
- if (!IsPositionInside(hull, dataSet[i], grid))
974
- {
975
- return BuildConcaveHull2(gridPositions, grid, random, nearestNeighbors + 1);
976
- }
977
- }
978
-
979
- return hull;
980
- }
981
-
982
- // This one has bugs, user beware
983
- // https://github.com/Liagson/ConcaveHullGenerator/tree/master
984
- #region ConcaveHull Functions
985
-
986
- private readonly struct Line
987
- {
988
- public readonly double sqrMagnitude;
989
-
990
- public readonly Vector2 from;
991
- public readonly Vector2 to;
992
-
993
- public Line(Vector2 from, Vector2 to)
994
- {
995
- this.from = from;
996
- this.to = to;
997
- sqrMagnitude = (from - to).sqrMagnitude;
998
- }
999
- }
1000
-
1001
- public static List<FastVector3Int> BuildConcaveHull(
1002
- this IEnumerable<FastVector3Int> gridPositions,
1003
- Grid grid,
1004
- IRandom random = null,
1005
- float scaleFactor = 1,
1006
- float concavity = 0f
1007
- )
1008
- {
1009
- if (concavity < -1 || 1 < concavity)
1010
- {
1011
- throw new ArgumentException($"Concavity must be between [-1, 1], was {concavity}");
1012
- }
1013
-
1014
- List<FastVector3Int> originalGridPositions = gridPositions.ToList();
1015
- if (originalGridPositions.Count <= 3)
1016
- {
1017
- return originalGridPositions;
1018
- }
1019
-
1020
- List<FastVector3Int> convexHull = originalGridPositions.BuildConvexHull(grid, random);
1021
- HashSet<FastVector3Int> unusedNodes = originalGridPositions.ToHashSet();
1022
- unusedNodes.ExceptWith(convexHull);
1023
- List<Line> concaveHullLines = new(convexHull.Count);
1024
- for (int i = 0; i < convexHull.Count; ++i)
1025
- {
1026
- FastVector3Int lhsGridPoint = convexHull[i];
1027
- FastVector3Int rhsGridPoint = convexHull[(i + 1) % convexHull.Count];
1028
- Vector2 lhs = grid.CellToWorld(lhsGridPoint);
1029
- Vector2 rhs = grid.CellToWorld(rhsGridPoint);
1030
- Line line = new(lhs, rhs);
1031
- concaveHullLines.Add(line);
1032
- }
1033
-
1034
- bool aLineWasDividedInTheIteration;
1035
- do
1036
- {
1037
- // Order by descending
1038
- concaveHullLines.Sort((lhs, rhs) => rhs.sqrMagnitude.CompareTo(lhs.sqrMagnitude));
1039
-
1040
- aLineWasDividedInTheIteration = false;
1041
- for (int i = 0; i < concaveHullLines.Count; ++i)
1042
- {
1043
- Line line = concaveHullLines[i];
1044
- IEnumerable<FastVector3Int> nearbyPoints = GetNearbyPoints(
1045
- line,
1046
- unusedNodes,
1047
- grid,
1048
- scaleFactor
1049
- );
1050
- List<Line> dividedLine = GetDividedLine(
1051
- line,
1052
- nearbyPoints,
1053
- concaveHullLines,
1054
- grid,
1055
- concavity
1056
- );
1057
- if (0 < dividedLine.Count)
1058
- {
1059
- aLineWasDividedInTheIteration = true;
1060
- FastVector3Int toRemove = grid.WorldToCell(dividedLine[0].to);
1061
- _ = unusedNodes.Remove(toRemove);
1062
- concaveHullLines.AddRange(dividedLine);
1063
- concaveHullLines.RemoveAtSwapBack(i);
1064
- break;
1065
- }
1066
- }
1067
- } while (aLineWasDividedInTheIteration);
1068
-
1069
- List<FastVector3Int> concaveHull = new(concaveHullLines.Count);
1070
- if (concaveHullLines.Count <= 0)
1071
- {
1072
- return concaveHull;
1073
- }
1074
-
1075
- Line currentlyConsideredLine = concaveHullLines[0];
1076
- FastVector3Int from = grid.WorldToCell(currentlyConsideredLine.from);
1077
- FastVector3Int to = grid.WorldToCell(currentlyConsideredLine.to);
1078
- concaveHull.Add(from);
1079
- concaveHull.Add(to);
1080
- concaveHullLines.RemoveAtSwapBack(0);
1081
- while (0 < concaveHullLines.Count)
1082
- {
1083
- int index = concaveHullLines.FindIndex(line =>
1084
- {
1085
- FastVector3Int lineFrom = grid.WorldToCell(line.from);
1086
- return lineFrom == to;
1087
- });
1088
-
1089
- currentlyConsideredLine = concaveHullLines[index];
1090
- to = grid.WorldToCell(currentlyConsideredLine.to);
1091
- if (to == from)
1092
- {
1093
- break;
1094
- }
1095
- concaveHull.Add(to);
1096
- concaveHullLines.RemoveAtSwapBack(index);
1097
- }
1098
-
1099
- return concaveHull;
1100
- }
1101
-
1102
- public static bool IsPositionInside(
1103
- List<FastVector3Int> hull,
1104
- FastVector3Int gridPosition,
1105
- Grid grid
1106
- )
1107
- {
1108
- bool isPositionInside = false;
1109
- Vector2 position = grid.CellToWorld(gridPosition);
1110
- for (int i = 0; i < hull.Count; ++i)
1111
- {
1112
- Vector2 oldVector = grid.CellToWorld(hull[i]);
1113
- int nextIndex = (i + 1) % hull.Count;
1114
- Vector2 newVector = grid.CellToWorld(hull[nextIndex]);
1115
-
1116
- Vector2 lhs;
1117
- Vector2 rhs;
1118
- if (oldVector.x < newVector.x)
1119
- {
1120
- lhs = oldVector;
1121
- rhs = newVector;
1122
- }
1123
- else
1124
- {
1125
- lhs = newVector;
1126
- rhs = oldVector;
1127
- }
1128
-
1129
- if (
1130
- (newVector.x < position.x) == (position.x <= oldVector.x)
1131
- && (position.y - (long)lhs.y) * (rhs.x - lhs.x)
1132
- < (rhs.y - (long)lhs.y) * (position.x - lhs.x)
1133
- )
1134
- {
1135
- isPositionInside = !isPositionInside;
1136
- }
1137
- }
1138
-
1139
- return isPositionInside;
1140
- }
1141
-
1142
- private static List<Line> GetDividedLine(
1143
- Line line,
1144
- IEnumerable<FastVector3Int> nearbyPoints,
1145
- List<Line> concaveHull,
1146
- Grid grid,
1147
- float concavity
1148
- )
1149
- {
1150
- return GetDividedLine(line.from, line.to, nearbyPoints, concaveHull, grid, concavity);
1151
- }
1152
-
1153
- private static List<Line> GetDividedLine(
1154
- Vector2 from,
1155
- Vector2 to,
1156
- IEnumerable<FastVector3Int> nearbyPoints,
1157
- List<Line> concaveHull,
1158
- Grid grid,
1159
- float concavity
1160
- )
1161
- {
1162
- List<Line> dividedLine = new(2);
1163
- Dictionary<Vector2, double> okMiddlePoints = new();
1164
- foreach (FastVector3Int gridPoint in nearbyPoints)
1165
- {
1166
- Vector2 point = grid.CellToWorld(gridPoint);
1167
- double cosine = GetCosine(from, to, point);
1168
- if (cosine < concavity)
1169
- {
1170
- Line newLineA = new(from, point);
1171
- Line newLineB = new(point, to);
1172
- if (
1173
- !LineCollidesWithHull(newLineA, concaveHull)
1174
- && !LineCollidesWithHull(newLineB, concaveHull)
1175
- )
1176
- {
1177
- okMiddlePoints[point] = cosine;
1178
- }
1179
- }
1180
- }
1181
-
1182
- if (0 < okMiddlePoints.Count)
1183
- {
1184
- Vector2 middlePoint = new();
1185
- double minCosine = double.MaxValue;
1186
- foreach (KeyValuePair<Vector2, double> entry in okMiddlePoints)
1187
- {
1188
- double cosine = entry.Value;
1189
- if (cosine < minCosine)
1190
- {
1191
- minCosine = cosine;
1192
- middlePoint = entry.Key;
1193
- }
1194
- }
1195
-
1196
- dividedLine.Add(new Line(from, middlePoint));
1197
- dividedLine.Add(new Line(middlePoint, to));
1198
- }
1199
-
1200
- return dividedLine;
1201
- }
1202
-
1203
- private static bool LineCollidesWithHull(Line line, List<Line> concaveHull)
1204
- {
1205
- return LineCollidesWithHull(line.from, line.to, concaveHull);
1206
- }
1207
-
1208
- private static bool LineCollidesWithHull(Vector2 from, Vector2 to, List<Line> concaveHull)
1209
- {
1210
- foreach (Line line in concaveHull)
1211
- {
1212
- Vector2 lhs = line.from;
1213
- Vector2 rhs = line.to;
1214
-
1215
- if (from != lhs && from != rhs && to != lhs && to != rhs)
1216
- {
1217
- if (Intersects(from, to, lhs, rhs))
1218
- {
1219
- return true;
1220
- }
1221
- }
1222
- }
1223
-
1224
- return false;
1225
- }
1226
-
1227
- public static double GetCosine(Vector2 a, Vector3 b, Vector3 o)
1228
- {
1229
- /* Law of cosines */
1230
- double aPow2 = (a.x - o.x) * (a.x - o.x) + (a.y - o.y) * (a.y - o.y);
1231
- double bPow2 = (b.x - o.x) * (b.x - o.x) + (b.y - o.y) * (b.y - o.y);
1232
- double cPow2 = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
1233
- double cos = (aPow2 + bPow2 - cPow2) / (2 * Math.Sqrt(aPow2 * bPow2));
1234
- return Math.Round(cos, 4);
1235
- }
1236
-
1237
- private static IEnumerable<FastVector3Int> GetNearbyPoints(
1238
- Line line,
1239
- ICollection<FastVector3Int> points,
1240
- Grid grid,
1241
- float scaleFactor
1242
- )
1243
- {
1244
- return GetNearbyPoints(line.from, line.to, points, grid, scaleFactor);
1245
- }
1246
-
1247
- private static IEnumerable<FastVector3Int> GetNearbyPoints(
1248
- Vector2 from,
1249
- Vector2 to,
1250
- ICollection<FastVector3Int> points,
1251
- Grid grid,
1252
- float scaleFactor
1253
- )
1254
- {
1255
- const int maxTries = 2;
1256
- for (int tries = 0; tries < maxTries; ++tries)
1257
- {
1258
- bool foundAnyPoints = false;
1259
- Bounds boundary = GetBoundary(from, to, scaleFactor);
1260
- foreach (FastVector3Int gridPoint in points)
1261
- {
1262
- Vector2 point = grid.CellToWorld(gridPoint);
1263
- if (point != from && point != to)
1264
- {
1265
- if (boundary.FastContains2D(point))
1266
- {
1267
- foundAnyPoints = true;
1268
- yield return gridPoint;
1269
- }
1270
- }
1271
- }
1272
-
1273
- if (foundAnyPoints)
1274
- {
1275
- yield break;
1276
- }
1277
-
1278
- scaleFactor *= (4 / 3f);
1279
- }
1280
- }
1281
-
1282
- private static Bounds GetBoundary(Vector2 from, Vector2 to, float scaleFactor)
1283
- {
1284
- float xMin = Math.Min(from.x, to.x);
1285
- float yMin = Math.Min(from.y, to.y);
1286
- float xMax = Math.Max(from.x, to.x);
1287
- float yMax = Math.Max(from.y, to.y);
1288
-
1289
- float width = xMax - xMin;
1290
- float height = yMax - yMin;
1291
- return new Bounds(
1292
- new Vector3(xMin + width / 2, yMin + height / 2),
1293
- new Vector3(width, height) * scaleFactor + new Vector3(0.001f, 0.001f)
1294
- );
1295
- }
1296
-
1297
- // https://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/#
1298
-
1299
- /// <summary>
1300
- /// Returns true if a line segment 'lhsFrom->lhsTo' intersects the line segment
1301
- /// 'rhsFrom->rhsTo'
1302
- /// </summary>
1303
- /// <param name="lhsFrom">LineSegmentA start point.</param>
1304
- /// <param name="lhsTo">LineSegmentA end point.</param>
1305
- /// <param name="rhsFrom">LineSegmentB start point.</param>
1306
- /// <param name="rhsTo">LineSegmentB end point.</param>
1307
- /// <returns>True if the line segments intersect.</returns>
1308
- public static bool Intersects(
1309
- Vector2 lhsFrom,
1310
- Vector2 lhsTo,
1311
- Vector2 rhsFrom,
1312
- Vector2 rhsTo
1313
- )
1314
- {
1315
- if (lhsFrom == rhsFrom)
1316
- {
1317
- return false;
1318
- }
1319
-
1320
- if (lhsFrom == rhsTo)
1321
- {
1322
- return false;
1323
- }
1324
-
1325
- if (lhsTo == rhsFrom)
1326
- {
1327
- return false;
1328
- }
1329
-
1330
- if (lhsTo == rhsTo)
1331
- {
1332
- return false;
1333
- }
1334
-
1335
- OrientationType orientation1 = Orientation(lhsFrom, lhsTo, rhsFrom);
1336
- OrientationType orientation2 = Orientation(lhsFrom, lhsTo, rhsTo);
1337
- OrientationType orientation3 = Orientation(rhsFrom, rhsTo, lhsFrom);
1338
- OrientationType orientation4 = Orientation(rhsFrom, rhsTo, lhsTo);
1339
-
1340
- if (orientation1 != orientation2 && orientation3 != orientation4)
1341
- {
1342
- return true;
1343
- }
1344
-
1345
- if (orientation1 == OrientationType.Colinear && LiesOnSegment(lhsFrom, rhsFrom, lhsTo))
1346
- {
1347
- return true;
1348
- }
1349
-
1350
- if (orientation2 == OrientationType.Colinear && LiesOnSegment(lhsFrom, rhsTo, lhsTo))
1351
- {
1352
- return true;
1353
- }
1354
-
1355
- if (orientation3 == OrientationType.Colinear && LiesOnSegment(rhsFrom, lhsFrom, rhsTo))
1356
- {
1357
- return true;
1358
- }
1359
-
1360
- if (orientation4 == OrientationType.Colinear && LiesOnSegment(rhsFrom, lhsTo, rhsTo))
1361
- {
1362
- return true;
1363
- }
1364
-
1365
- return false;
1366
- }
1367
-
1368
- /// <summary>
1369
- /// Given three colinear points p, q, r, returns whether the
1370
- /// point q lines on the line segment pr.
1371
- /// </summary>
1372
- /// <param name="p">Beginning of line segment.</param>
1373
- /// <param name="q">Check if on line segment.</param>
1374
- /// <param name="r">End of line segment.</param>
1375
- /// <returns>True if q lies on the line segment pr.</returns>
1376
- public static bool LiesOnSegment(Vector2 p, Vector2 q, Vector2 r)
1377
- {
1378
- return q.x <= Math.Max(p.x, r.x)
1379
- && Math.Min(p.x, r.x) <= q.x
1380
- && q.y <= Math.Max(p.y, r.y)
1381
- && Math.Min(p.y, r.y) <= q.y;
1382
- }
1383
-
1384
- public enum OrientationType
1385
- {
1386
- Colinear = 0,
1387
- Clockwise = 1,
1388
- Counterclockwise = 2,
1389
- }
1390
-
1391
- /// <summary>
1392
- /// Finds the orientation of an ordered triplet (p, q, r).
1393
- /// </summary>
1394
- /// <param name="p">Triplet element 1.</param>
1395
- /// <param name="q">Triplet element 2.</param>
1396
- /// <param name="r">Triplet element 3.</param>
1397
- /// <returns>The orientation of the triplet</returns>
1398
- public static OrientationType Orientation(Vector2 p, Vector2 q, Vector2 r)
1399
- {
1400
- float value = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1401
- if (Mathf.Approximately(value, 0))
1402
- {
1403
- return OrientationType.Colinear;
1404
- }
1405
-
1406
- return 0 < value ? OrientationType.Clockwise : OrientationType.Counterclockwise;
1407
- }
1408
-
1409
- #endregion
1410
-
1411
- public static Vector2 Rotate(this Vector2 v, float degrees)
1412
- {
1413
- float sin = Mathf.Sin(degrees * Mathf.Deg2Rad);
1414
- float cos = Mathf.Cos(degrees * Mathf.Deg2Rad);
1415
-
1416
- float tx = v.x;
1417
- float ty = v.y;
1418
-
1419
- Vector2 rotatedVector;
1420
- rotatedVector.x = (cos * tx) - (sin * ty);
1421
- rotatedVector.y = (sin * tx) + (cos * ty);
1422
-
1423
- return rotatedVector;
1424
- }
1425
-
1426
- public static bool FastIntersects(this Bounds bounds, Bounds other)
1427
- {
1428
- Vector3 boundsMin = bounds.min;
1429
- Vector3 otherMax = other.max;
1430
- if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y || otherMax.z < boundsMin.z)
1431
- {
1432
- return false;
1433
- }
1434
-
1435
- Vector3 boundsMax = bounds.max;
1436
- Vector3 otherMin = other.min;
1437
- return boundsMax.x >= otherMin.x
1438
- && boundsMax.y >= otherMin.y
1439
- && boundsMax.z >= otherMin.z;
1440
- }
1441
-
1442
- public static bool FastContains2D(this BoundsInt bounds, FastVector3Int position)
1443
- {
1444
- return position.x >= bounds.xMin
1445
- && position.y >= bounds.yMin
1446
- && position.x < bounds.xMax
1447
- && position.y < bounds.yMax;
1448
- }
1449
-
1450
- public static bool FastIntersects2D(this BoundsInt bounds, BoundsInt other)
1451
- {
1452
- if (other.xMax < bounds.xMin || other.yMax < bounds.yMin)
1453
- {
1454
- return false;
1455
- }
1456
-
1457
- return bounds.xMax >= other.xMin && bounds.yMax >= other.yMin;
1458
- }
1459
-
1460
- public static bool FastContains2D(this Bounds bounds, Vector2 position)
1461
- {
1462
- Vector3 min = bounds.min;
1463
- if (position.x < min.x || position.y < bounds.min.y)
1464
- {
1465
- return false;
1466
- }
1467
- Vector3 max = bounds.max;
1468
- return position.x < max.x && position.y < max.y;
1469
- }
1470
-
1471
- public static bool FastIntersects2D(this Bounds bounds, Bounds other)
1472
- {
1473
- Vector3 boundsMin = bounds.min;
1474
- Vector3 otherMax = other.max;
1475
- if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y)
1476
- {
1477
- return false;
1478
- }
1479
-
1480
- Vector3 boundsMax = bounds.max;
1481
- Vector3 otherMin = other.min;
1482
- return boundsMax.x >= otherMin.x && boundsMax.y >= otherMin.y;
1483
- }
1484
-
1485
- public static bool Overlaps2D(this Bounds bounds, Bounds other)
1486
- {
1487
- Vector3 boundsMin = bounds.min;
1488
- Vector3 otherMin = other.min;
1489
- if (otherMin.x < boundsMin.x || otherMin.y < boundsMin.y)
1490
- {
1491
- return false;
1492
- }
1493
-
1494
- Vector3 boundsMax = bounds.max;
1495
- Vector3 otherMax = other.max;
1496
- return otherMax.x <= boundsMax.x && otherMax.y <= boundsMax.y;
1497
- }
1498
-
1499
- public static BoundsInt WithPadding(this BoundsInt bounds, int xPadding, int yPadding)
1500
- {
1501
- Vector3Int size = bounds.size;
1502
- return new BoundsInt(
1503
- bounds.xMin - xPadding,
1504
- bounds.yMin - yPadding,
1505
- bounds.zMin,
1506
- size.x + 2 * xPadding,
1507
- size.y + 2 * yPadding,
1508
- size.z
1509
- );
1510
- }
1511
-
1512
- public static void SetColors(this UnityEngine.UI.Slider slider, Color color)
1513
- {
1514
- ColorBlock block = slider.colors;
1515
-
1516
- block.normalColor = color;
1517
- block.highlightedColor = color;
1518
- block.pressedColor = color;
1519
- block.selectedColor = color;
1520
- block.disabledColor = color;
1521
-
1522
- slider.colors = block;
1523
- }
1524
-
1525
- public static void SetLeft(this RectTransform rt, float left)
1526
- {
1527
- rt.offsetMin = new Vector2(left, rt.offsetMin.y);
1528
- }
1529
-
1530
- public static void SetRight(this RectTransform rt, float right)
1531
- {
1532
- rt.offsetMax = new Vector2(-right, rt.offsetMax.y);
1533
- }
1534
-
1535
- public static void SetTop(this RectTransform rt, float top)
1536
- {
1537
- rt.offsetMax = new Vector2(rt.offsetMax.x, -top);
1538
- }
1539
-
1540
- public static void SetBottom(this RectTransform rt, float bottom)
1541
- {
1542
- rt.offsetMin = new Vector2(rt.offsetMin.x, bottom);
1543
- }
1544
-
1545
- public static IEnumerable<FastVector3Int> AllFastPositionsWithin(this BoundsInt bounds)
1546
- {
1547
- Vector3Int min = bounds.min;
1548
- Vector3Int max = bounds.max;
1549
- for (int x = min.x; x < max.x; ++x)
1550
- {
1551
- for (int y = min.y; y < max.y; ++y)
1552
- {
1553
- for (int z = min.z; z < max.z; ++z)
1554
- {
1555
- yield return new FastVector3Int(x, y, z);
1556
- }
1557
- }
1558
- }
1559
- }
1560
-
1561
- public static bool Contains(this BoundsInt bounds, FastVector3Int position)
1562
- {
1563
- return bounds.Contains(position);
1564
- }
1565
-
1566
- public static bool IsOnEdge2D(this FastVector3Int position, BoundsInt bounds)
1567
- {
1568
- if (bounds.xMin == position.x || (bounds.xMax - 1) == position.x)
1569
- {
1570
- return bounds.yMin <= position.y && position.y < bounds.yMax;
1571
- }
1572
-
1573
- if (bounds.yMin == position.y || (bounds.yMax - 1) == position.y)
1574
- {
1575
- return bounds.xMin <= position.x && position.x < bounds.xMax;
1576
- }
1577
-
1578
- return false;
1579
- }
1580
-
1581
- #if UNITY_EDITOR
1582
- public static IEnumerable<Sprite> GetSpritesFromClip(this AnimationClip clip)
1583
- {
1584
- if (clip == null)
1585
- {
1586
- yield break;
1587
- }
1588
-
1589
- foreach (
1590
- EditorCurveBinding binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)
1591
- )
1592
- {
1593
- ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve(
1594
- clip,
1595
- binding
1596
- );
1597
- foreach (ObjectReferenceKeyframe frame in keyframes)
1598
- {
1599
- if (frame.value is Sprite sprite)
1600
- {
1601
- yield return sprite;
1602
- }
1603
- }
1604
- }
1605
- }
1606
- #endif
1607
- }
1608
- }
1
+ namespace UnityHelpers.Core.Extension
2
+ {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Linq;
6
+ using System.Threading.Tasks;
7
+ using DataStructure;
8
+ using DataStructure.Adapters;
9
+ using Helper;
10
+ using Random;
11
+ using UnityEditor;
12
+ using UnityEngine;
13
+ using UnityEngine.UI;
14
+ using Utils;
15
+
16
+ public static class UnityExtensions
17
+ {
18
+ public static Vector2 GetCenter(this GameObject gameObject)
19
+ {
20
+ if (gameObject.TryGetComponent(out CenterPointOffset centerPointOffset))
21
+ {
22
+ return centerPointOffset.CenterPoint;
23
+ }
24
+
25
+ return gameObject.transform.position;
26
+ }
27
+
28
+ public static Bounds Bounds(this Rect rect)
29
+ {
30
+ return new Bounds(rect.center, rect.size);
31
+ }
32
+
33
+ public static Rect Rect(this Bounds bounds)
34
+ {
35
+ return new Rect(bounds.center - bounds.extents, bounds.size);
36
+ }
37
+
38
+ public static Rect GetWorldRect(this RectTransform transform)
39
+ {
40
+ Vector3[] fourCorners = new Vector3[4];
41
+ transform.GetWorldCorners(fourCorners);
42
+
43
+ float[] xValues = fourCorners.Select(vector => vector.x).ToArray();
44
+ float[] yValues = fourCorners.Select(vector => vector.y).ToArray();
45
+ float minX = Mathf.Min(xValues);
46
+ float maxX = Mathf.Max(xValues);
47
+ float minY = Mathf.Min(yValues);
48
+ float maxY = Mathf.Max(yValues);
49
+
50
+ return new Rect(minX, minY, maxX - minX, maxY - minY);
51
+ }
52
+
53
+ public static Bounds OrthographicBounds(this Camera camera)
54
+ {
55
+ float screenAspect = (float)Screen.width / Screen.height;
56
+ float cameraHeight = camera.orthographicSize * 2;
57
+ Bounds bounds = new(
58
+ (Vector2)camera.transform.position,
59
+ new Vector3(cameraHeight * screenAspect, cameraHeight, 1)
60
+ );
61
+ return bounds;
62
+ }
63
+
64
+ public static string ToJsonString(this Vector3 vector)
65
+ {
66
+ return $"{{{vector.x}, {vector.y}, {vector.z}}}";
67
+ }
68
+
69
+ public static string ToJsonString(this Vector2 vector)
70
+ {
71
+ return $"{{{vector.x}, {vector.y}}}";
72
+ }
73
+
74
+ public static bool IsNoise(this Vector2 inputVector)
75
+ {
76
+ return Mathf.Abs(inputVector.x) <= 0.2f && Mathf.Abs(inputVector.y) <= 0.2f;
77
+ }
78
+
79
+ public static void Stop(this Rigidbody2D rigidBody)
80
+ {
81
+ if (rigidBody == null)
82
+ {
83
+ return;
84
+ }
85
+
86
+ rigidBody.velocity = Vector2.zero;
87
+ rigidBody.angularVelocity = 0;
88
+ rigidBody.Sleep();
89
+ }
90
+
91
+ public static BoundsInt ExpandBounds(this BoundsInt source, BoundsInt other)
92
+ {
93
+ int xMin = Math.Min(source.xMin, other.xMin);
94
+ int xMax = Math.Max(source.xMax, other.xMax);
95
+ int yMin = Math.Min(source.yMin, other.yMin);
96
+ int yMax = Math.Max(source.yMax, other.yMax);
97
+ int zMin = Math.Min(source.zMin, other.zMin);
98
+ int zMax = Math.Max(source.zMax, other.zMax);
99
+ return new BoundsInt(xMin, yMin, zMin, xMax - xMin, yMax - yMin, zMax - zMin);
100
+ }
101
+
102
+ public static BoundsInt? GetBounds(this IEnumerable<Vector3Int> positions)
103
+ {
104
+ bool any = false;
105
+ int xMin = int.MaxValue;
106
+ int xMax = int.MinValue;
107
+ int yMin = int.MaxValue;
108
+ int yMax = int.MinValue;
109
+ int zMin = int.MaxValue;
110
+ int zMax = int.MinValue;
111
+ foreach (Vector3Int position in positions)
112
+ {
113
+ any = true;
114
+ xMin = Math.Min(xMin, position.x);
115
+ xMax = Math.Max(xMax, position.x);
116
+ yMin = Math.Min(yMin, position.y);
117
+ yMax = Math.Max(yMax, position.y);
118
+ zMin = Math.Min(zMin, position.z);
119
+ zMax = Math.Max(zMax, position.z);
120
+ }
121
+
122
+ if (!any)
123
+ {
124
+ return null;
125
+ }
126
+ return new BoundsInt(
127
+ xMin,
128
+ yMin,
129
+ zMin,
130
+ (xMax - xMin) + 1,
131
+ (yMax - yMin) + 1,
132
+ (zMax - zMin) + 1
133
+ );
134
+ }
135
+
136
+ public static BoundsInt? GetBounds(this IEnumerable<FastVector3Int> positions)
137
+ {
138
+ bool any = false;
139
+ int xMin = int.MaxValue;
140
+ int xMax = int.MinValue;
141
+ int yMin = int.MaxValue;
142
+ int yMax = int.MinValue;
143
+ int zMin = int.MaxValue;
144
+ int zMax = int.MinValue;
145
+ foreach (FastVector3Int position in positions)
146
+ {
147
+ any = true;
148
+ xMin = Math.Min(xMin, position.x);
149
+ xMax = Math.Max(xMax, position.x);
150
+ yMin = Math.Min(yMin, position.y);
151
+ yMax = Math.Max(yMax, position.y);
152
+ zMin = Math.Min(zMin, position.z);
153
+ zMax = Math.Max(zMax, position.z);
154
+ }
155
+
156
+ if (!any)
157
+ {
158
+ return null;
159
+ }
160
+ return new BoundsInt(
161
+ xMin,
162
+ yMin,
163
+ zMin,
164
+ (xMax - xMin) + 1,
165
+ (yMax - yMin) + 1,
166
+ (zMax - zMin) + 1
167
+ );
168
+ }
169
+
170
+ public static Bounds? GetBounds(this IEnumerable<Vector2> positions)
171
+ {
172
+ bool any = false;
173
+ float xMin = float.MaxValue;
174
+ float xMax = float.MinValue;
175
+ float yMin = float.MaxValue;
176
+ float yMax = float.MinValue;
177
+ foreach (Vector2 position in positions)
178
+ {
179
+ any = true;
180
+ xMin = Math.Min(xMin, position.x);
181
+ xMax = Math.Max(xMax, position.x);
182
+ yMin = Math.Min(yMin, position.y);
183
+ yMax = Math.Max(yMax, position.y);
184
+ }
185
+
186
+ if (!any)
187
+ {
188
+ return null;
189
+ }
190
+
191
+ Vector3 center = new((xMax + xMin) / 2f, (yMax + yMin) / 2f);
192
+ Vector3 size = new(xMax - xMin, yMax - yMin);
193
+ return new Bounds(center, size);
194
+ }
195
+
196
+ public static Bounds? GetBounds(this IEnumerable<Bounds> boundaries)
197
+ {
198
+ float minX = float.MaxValue;
199
+ float minY = float.MaxValue;
200
+ float maxX = float.MinValue;
201
+ float maxY = float.MinValue;
202
+ bool any = false;
203
+ foreach (Bounds boundary in boundaries)
204
+ {
205
+ any = true;
206
+ Vector3 min = boundary.min;
207
+ Vector3 max = boundary.max;
208
+ minX = Math.Min(minX, min.x);
209
+ maxX = Math.Max(maxX, max.x);
210
+ minY = Math.Min(minY, min.y);
211
+ maxY = Math.Max(maxY, max.y);
212
+ }
213
+
214
+ if (!any)
215
+ {
216
+ return null;
217
+ }
218
+
219
+ return new Bounds(
220
+ new Vector3(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2),
221
+ new Vector3(maxX - minX, maxY - minY)
222
+ );
223
+ }
224
+
225
+ // https://www.habrador.com/tutorials/math/8-convex-hull/
226
+ public static List<Vector3Int> BuildConvexHull(
227
+ this IEnumerable<Vector3Int> pointsSet,
228
+ Grid grid,
229
+ IRandom random = null,
230
+ bool includeColinearPoints = true
231
+ )
232
+ {
233
+ List<Vector3Int> points = pointsSet.ToList();
234
+ if (points.Count <= 3)
235
+ {
236
+ return points;
237
+ }
238
+
239
+ random ??= PRNG.Instance;
240
+
241
+ Vector2 CellToWorld(Vector3Int position) => grid.CellToWorld(position);
242
+
243
+ Vector3Int startPoint = points[0];
244
+ Vector2 startPointWorldPosition = CellToWorld(startPoint);
245
+ for (int i = 1; i < points.Count; ++i)
246
+ {
247
+ Vector3Int testPoint = points[i];
248
+ Vector2 testPointWorldPosition = CellToWorld(testPoint);
249
+ // ReSharper disable once CompareOfFloatsByEqualityOperator
250
+ if (
251
+ testPointWorldPosition.x < startPointWorldPosition.x
252
+ || (
253
+ Mathf.Approximately(testPointWorldPosition.x, startPointWorldPosition.x)
254
+ && testPointWorldPosition.y < startPointWorldPosition.y
255
+ )
256
+ )
257
+ {
258
+ startPoint = testPoint;
259
+ startPointWorldPosition = testPointWorldPosition;
260
+ }
261
+ }
262
+
263
+ List<Vector3Int> convexHull = new() { startPoint };
264
+ _ = points.Remove(startPoint);
265
+ Vector3Int currentPoint = convexHull[0];
266
+ List<Vector3Int> colinearPoints = new();
267
+ int counter = 0;
268
+ while (true)
269
+ {
270
+ if (counter == 2)
271
+ {
272
+ points.Add(convexHull[0]);
273
+ }
274
+
275
+ if (points.Count <= 0)
276
+ {
277
+ return convexHull;
278
+ }
279
+
280
+ Vector3Int nextPoint = random.NextOf(points);
281
+ Vector2 currentPointWorldPosition = CellToWorld(currentPoint);
282
+ Vector2 nextPointWorldPosition = CellToWorld(nextPoint);
283
+ foreach (Vector3Int point in points)
284
+ {
285
+ if (Equals(point, nextPoint))
286
+ {
287
+ continue;
288
+ }
289
+
290
+ float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
291
+ currentPointWorldPosition,
292
+ nextPointWorldPosition,
293
+ CellToWorld(point)
294
+ );
295
+ if (Mathf.Approximately(relation, 0))
296
+ {
297
+ colinearPoints.Add(point);
298
+ }
299
+ else if (relation < 0)
300
+ {
301
+ nextPoint = point;
302
+ nextPointWorldPosition = CellToWorld(nextPoint);
303
+ colinearPoints.Clear();
304
+ }
305
+ }
306
+
307
+ if (0 < colinearPoints.Count)
308
+ {
309
+ colinearPoints.Add(nextPoint);
310
+ colinearPoints.Sort(
311
+ (lhs, rhs) =>
312
+ (CellToWorld(lhs) - currentPointWorldPosition).sqrMagnitude.CompareTo(
313
+ (CellToWorld(rhs) - currentPointWorldPosition).sqrMagnitude
314
+ )
315
+ );
316
+
317
+ if (includeColinearPoints)
318
+ {
319
+ convexHull.AddRange(colinearPoints);
320
+ }
321
+ else
322
+ {
323
+ convexHull.Add(colinearPoints[^1]);
324
+ _ = points.Remove(colinearPoints[^1]);
325
+ }
326
+
327
+ currentPoint = colinearPoints[^1];
328
+ _ = points.RemoveAll(colinearPoints.Contains);
329
+ colinearPoints.Clear();
330
+ }
331
+ else
332
+ {
333
+ convexHull.Add(nextPoint);
334
+ _ = points.Remove(nextPoint);
335
+ currentPoint = nextPoint;
336
+ }
337
+
338
+ if (Equals(currentPoint, convexHull[0]))
339
+ {
340
+ convexHull.RemoveAt(convexHull.Count - 1);
341
+ break;
342
+ }
343
+
344
+ ++counter;
345
+ }
346
+
347
+ return convexHull;
348
+ }
349
+
350
+ public static List<FastVector3Int> BuildConvexHull(
351
+ this IEnumerable<FastVector3Int> pointsSet,
352
+ Grid grid,
353
+ IRandom random = null,
354
+ bool includeColinearPoints = false
355
+ )
356
+ {
357
+ List<FastVector3Int> points = pointsSet.ToList();
358
+ if (points.Count <= 3)
359
+ {
360
+ return points;
361
+ }
362
+
363
+ random ??= PRNG.Instance;
364
+
365
+ Vector2 CellToWorld(FastVector3Int position) => grid.CellToWorld(position);
366
+
367
+ FastVector3Int startPoint = points[0];
368
+ Vector2 startPointWorldPosition = CellToWorld(startPoint);
369
+ for (int i = 1; i < points.Count; ++i)
370
+ {
371
+ FastVector3Int testPoint = points[i];
372
+ Vector2 testPointWorldPosition = CellToWorld(testPoint);
373
+ if (
374
+ testPointWorldPosition.x < startPointWorldPosition.x
375
+ || (
376
+ Mathf.Approximately(testPointWorldPosition.x, startPointWorldPosition.x)
377
+ && testPointWorldPosition.y < startPointWorldPosition.y
378
+ )
379
+ )
380
+ {
381
+ startPoint = testPoint;
382
+ startPointWorldPosition = testPointWorldPosition;
383
+ }
384
+ }
385
+
386
+ List<FastVector3Int> convexHull = new() { startPoint };
387
+ _ = points.Remove(startPoint);
388
+ FastVector3Int currentPoint = convexHull[0];
389
+ List<FastVector3Int> colinearPoints = new();
390
+ int counter = 0;
391
+ while (true)
392
+ {
393
+ if (counter == 2)
394
+ {
395
+ points.Add(convexHull[0]);
396
+ }
397
+
398
+ if (points.Count <= 0)
399
+ {
400
+ return convexHull;
401
+ }
402
+
403
+ FastVector3Int nextPoint = random.NextOf(points);
404
+ Vector2 currentPointWorldPosition = CellToWorld(currentPoint);
405
+ Vector2 nextPointWorldPosition = CellToWorld(nextPoint);
406
+ foreach (FastVector3Int point in points)
407
+ {
408
+ if (point.Equals(nextPoint))
409
+ {
410
+ continue;
411
+ }
412
+
413
+ float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
414
+ currentPointWorldPosition,
415
+ nextPointWorldPosition,
416
+ CellToWorld(point)
417
+ );
418
+ if (Mathf.Approximately(relation, 0))
419
+ {
420
+ colinearPoints.Add(point);
421
+ }
422
+ else if (relation < 0)
423
+ {
424
+ nextPoint = point;
425
+ nextPointWorldPosition = CellToWorld(nextPoint);
426
+ colinearPoints.Clear();
427
+ }
428
+ }
429
+
430
+ if (0 < colinearPoints.Count)
431
+ {
432
+ colinearPoints.Add(nextPoint);
433
+ colinearPoints.Sort(
434
+ (lhs, rhs) =>
435
+ (CellToWorld(lhs) - currentPointWorldPosition).sqrMagnitude.CompareTo(
436
+ (CellToWorld(rhs) - currentPointWorldPosition).sqrMagnitude
437
+ )
438
+ );
439
+
440
+ if (includeColinearPoints)
441
+ {
442
+ convexHull.AddRange(colinearPoints);
443
+ }
444
+ else
445
+ {
446
+ convexHull.Add(colinearPoints[^1]);
447
+ _ = points.Remove(colinearPoints[^1]);
448
+ }
449
+
450
+ currentPoint = colinearPoints[^1];
451
+ _ = points.RemoveAll(colinearPoints.Contains);
452
+ colinearPoints.Clear();
453
+ }
454
+ else
455
+ {
456
+ convexHull.Add(nextPoint);
457
+ _ = points.Remove(nextPoint);
458
+ currentPoint = nextPoint;
459
+ }
460
+
461
+ if (currentPoint.Equals(convexHull[0]))
462
+ {
463
+ convexHull.RemoveAt(convexHull.Count - 1);
464
+ break;
465
+ }
466
+
467
+ ++counter;
468
+ }
469
+
470
+ return convexHull;
471
+ }
472
+
473
+ public static bool IsConvexHullInsideConvexHull(
474
+ this List<FastVector3Int> convexHull,
475
+ Grid grid,
476
+ List<FastVector3Int> maybeInside
477
+ )
478
+ {
479
+ foreach (FastVector3Int point in maybeInside)
480
+ {
481
+ if (!IsPointInsideConvexHull(convexHull, grid, point))
482
+ {
483
+ return false;
484
+ }
485
+ }
486
+
487
+ return true;
488
+ }
489
+
490
+ public static bool IsPointInsideConvexHull(
491
+ this List<Vector3Int> convexHull,
492
+ Grid grid,
493
+ Vector3Int point
494
+ )
495
+ {
496
+ for (int i = 0; i < convexHull.Count; ++i)
497
+ {
498
+ Vector3Int lhs = convexHull[i];
499
+ Vector3Int rhs = convexHull[(i + 1) % convexHull.Count];
500
+ float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
501
+ grid.CellToWorld(lhs),
502
+ grid.CellToWorld(rhs),
503
+ grid.CellToWorld(point)
504
+ );
505
+ if (relation < 0)
506
+ {
507
+ return false;
508
+ }
509
+ }
510
+ return true;
511
+ }
512
+
513
+ public static bool IsPointInsideConvexHull(
514
+ this List<FastVector3Int> convexHull,
515
+ Grid grid,
516
+ FastVector3Int point
517
+ )
518
+ {
519
+ for (int i = 0; i < convexHull.Count; ++i)
520
+ {
521
+ FastVector3Int lhs = convexHull[i];
522
+ FastVector3Int rhs = convexHull[(i + 1) % convexHull.Count];
523
+ float relation = Geometry.IsAPointLeftOfVectorOrOnTheLine(
524
+ grid.CellToWorld(lhs),
525
+ grid.CellToWorld(rhs),
526
+ grid.CellToWorld(point)
527
+ );
528
+ if (relation < 0)
529
+ {
530
+ return false;
531
+ }
532
+ }
533
+ return true;
534
+ }
535
+
536
+ public static bool IsConvexHullInsideConvexHull(
537
+ this List<Vector3Int> convexHull,
538
+ Grid grid,
539
+ List<Vector3Int> maybeInside
540
+ )
541
+ {
542
+ foreach (Vector3Int point in maybeInside)
543
+ {
544
+ if (!IsPointInsideConvexHull(convexHull, grid, point))
545
+ {
546
+ return false;
547
+ }
548
+ }
549
+
550
+ return true;
551
+ }
552
+
553
+ private readonly struct HullEdge
554
+ {
555
+ public readonly float edgeLength;
556
+
557
+ public readonly FastVector3Int from;
558
+ public readonly FastVector3Int to;
559
+
560
+ public readonly Vector2 fromWorld;
561
+ public readonly Vector2 toWorld;
562
+
563
+ private readonly Grid _grid;
564
+
565
+ public HullEdge(FastVector3Int from, FastVector3Int to, Grid grid)
566
+ {
567
+ this.from = from;
568
+ this.to = to;
569
+ _grid = grid;
570
+ fromWorld = grid.CellToWorld(from);
571
+ toWorld = grid.CellToWorld(to);
572
+ edgeLength = (fromWorld - toWorld).sqrMagnitude;
573
+ }
574
+
575
+ public bool Intersects(HullEdge other)
576
+ {
577
+ return UnityExtensions.Intersects(
578
+ fromWorld,
579
+ toWorld,
580
+ other.fromWorld,
581
+ other.toWorld
582
+ );
583
+ }
584
+
585
+ public float LargestAngle(FastVector3Int point)
586
+ {
587
+ Vector2 worldPoint = _grid.CellToWorld(point);
588
+ float angleFrom = Vector2.Angle((toWorld - fromWorld), (worldPoint - fromWorld));
589
+ float angleTo = Vector2.Angle((fromWorld - toWorld), (worldPoint - toWorld));
590
+ return Math.Max(angleFrom, angleTo);
591
+ }
592
+ }
593
+
594
+ private sealed class ConcaveHullComparer : IComparer<HullEdge>
595
+ {
596
+ public static readonly ConcaveHullComparer Instance = new();
597
+
598
+ private ConcaveHullComparer() { }
599
+
600
+ public int Compare(HullEdge lhs, HullEdge rhs)
601
+ {
602
+ int comparison = lhs.edgeLength.CompareTo(rhs.edgeLength);
603
+ if (comparison != 0)
604
+ {
605
+ return comparison;
606
+ }
607
+
608
+ comparison = lhs.from.CompareTo(rhs.from);
609
+ if (comparison != 0)
610
+ {
611
+ return comparison;
612
+ }
613
+
614
+ return lhs.to.CompareTo(rhs.to);
615
+ }
616
+ }
617
+
618
+ public static List<FastVector3Int> BuildConcaveHull3(
619
+ this IReadOnlyCollection<FastVector3Int> gridPositions,
620
+ Grid grid,
621
+ IRandom random = null,
622
+ int bucketSize = 40,
623
+ float angleThreshold = 90f
624
+ )
625
+ {
626
+ List<FastVector3Int> convexHull = gridPositions.BuildConvexHull(grid, random);
627
+ List<HullEdge> concaveHullEdges = new();
628
+
629
+ SortedSet<HullEdge> data = new(ConcaveHullComparer.Instance);
630
+ for (int i = 0; i < convexHull.Count; ++i)
631
+ {
632
+ FastVector3Int lhs = convexHull[i];
633
+ FastVector3Int rhs = convexHull[(i + 1) % convexHull.Count];
634
+ HullEdge edge = new(lhs, rhs, grid);
635
+ _ = data.Add(edge);
636
+ }
637
+
638
+ HashSet<FastVector3Int> remainingPoints = gridPositions.ToHashSet();
639
+ remainingPoints.ExceptWith(convexHull);
640
+
641
+ Vector2 CellToWorld(FastVector3Int cell) => grid.CellToWorld(cell);
642
+
643
+ Bounds? maybeBounds = gridPositions.Select(CellToWorld).GetBounds();
644
+ if (maybeBounds == null)
645
+ {
646
+ throw new ArgumentException(nameof(gridPositions));
647
+ }
648
+
649
+ QuadTree<FastVector3Int> NewQuadTree() =>
650
+ new(gridPositions, CellToWorld, maybeBounds.Value, bucketSize: bucketSize);
651
+
652
+ QuadTree<FastVector3Int> quadTree = NewQuadTree();
653
+ List<FastVector3Int> neighbors = Buffers<FastVector3Int>.List;
654
+ while (0 < data.Count)
655
+ {
656
+ HullEdge edge = data.Max;
657
+ _ = data.Remove(edge);
658
+
659
+ Vector2 edgeCenter = edge.fromWorld + (edge.toWorld - edge.fromWorld) / 2;
660
+ quadTree.GetApproximateNearestNeighbors(edgeCenter, bucketSize, neighbors);
661
+ float localMaximumDistance = float.MinValue;
662
+ foreach (FastVector3Int neighbor in neighbors)
663
+ {
664
+ if (neighbor == edge.to || neighbor == edge.from)
665
+ {
666
+ continue;
667
+ }
668
+
669
+ localMaximumDistance = Math.Max(
670
+ localMaximumDistance,
671
+ (CellToWorld(neighbor) - edgeCenter).sqrMagnitude
672
+ );
673
+ }
674
+
675
+ if (edge.edgeLength <= localMaximumDistance)
676
+ {
677
+ concaveHullEdges.Add(edge);
678
+ continue;
679
+ }
680
+
681
+ float smallestAngle = float.MaxValue;
682
+ FastVector3Int? maybeChosenPoint = null;
683
+ foreach (FastVector3Int remainingPoint in remainingPoints)
684
+ {
685
+ float maximumAngle = edge.LargestAngle(remainingPoint);
686
+ if (maximumAngle < smallestAngle)
687
+ {
688
+ maybeChosenPoint = remainingPoint;
689
+ smallestAngle = maximumAngle;
690
+ }
691
+ }
692
+
693
+ if (angleThreshold < smallestAngle)
694
+ {
695
+ concaveHullEdges.Add(edge);
696
+ continue;
697
+ }
698
+
699
+ if (maybeChosenPoint == null)
700
+ {
701
+ concaveHullEdges.Add(edge);
702
+ continue;
703
+ }
704
+
705
+ FastVector3Int chosenPoint = maybeChosenPoint.Value;
706
+ HullEdge e2 = new(edge.from, chosenPoint, grid);
707
+ HullEdge e3 = new(chosenPoint, edge.to, grid);
708
+ bool intersects = false;
709
+ foreach (HullEdge convexHullEdge in data)
710
+ {
711
+ if (convexHullEdge.Intersects(e2))
712
+ {
713
+ intersects = true;
714
+ break;
715
+ }
716
+
717
+ if (convexHullEdge.Intersects(e3))
718
+ {
719
+ intersects = true;
720
+ break;
721
+ }
722
+ }
723
+
724
+ if (!intersects)
725
+ {
726
+ foreach (HullEdge concaveHullEdge in concaveHullEdges)
727
+ {
728
+ if (concaveHullEdge.Intersects(e2))
729
+ {
730
+ intersects = true;
731
+ break;
732
+ }
733
+
734
+ if (concaveHullEdge.Intersects(e3))
735
+ {
736
+ intersects = true;
737
+ break;
738
+ }
739
+ }
740
+ }
741
+
742
+ if (!intersects)
743
+ {
744
+ _ = data.Add(e2);
745
+ _ = data.Add(e3);
746
+ _ = remainingPoints.Remove(maybeChosenPoint.Value);
747
+ }
748
+ else
749
+ {
750
+ concaveHullEdges.Add(edge);
751
+ }
752
+ }
753
+
754
+ List<FastVector3Int> concaveHull = new(concaveHullEdges.Count);
755
+ HullEdge current = concaveHullEdges[0];
756
+ concaveHullEdges.RemoveAtSwapBack(0);
757
+ concaveHull.Add(current.from);
758
+ while (0 < concaveHullEdges.Count)
759
+ {
760
+ FastVector3Int to = current.to;
761
+ int nextIndex = concaveHullEdges.FindIndex(edge => edge.from == to);
762
+ current = concaveHullEdges[nextIndex];
763
+ concaveHullEdges.RemoveAtSwapBack(nextIndex);
764
+ concaveHull.Add(current.from);
765
+ }
766
+
767
+ return concaveHull;
768
+ }
769
+
770
+ // https://www.researchgate.net/publication/220868874_Concave_hull_A_k-nearest_neighbours_approach_for_the_computation_of_the_region_occupied_by_a_set_of_points
771
+
772
+ public static List<FastVector3Int> BuildConcaveHull2(
773
+ this IReadOnlyCollection<FastVector3Int> gridPositions,
774
+ Grid grid,
775
+ IRandom random = null,
776
+ int nearestNeighbors = 3
777
+ )
778
+ {
779
+ const int minimumNearestNeighbors = 3;
780
+ nearestNeighbors = Math.Max(minimumNearestNeighbors, nearestNeighbors);
781
+ List<FastVector3Int> dataSet = gridPositions.Distinct().ToList();
782
+ if (dataSet.Count <= 3)
783
+ {
784
+ return dataSet;
785
+ }
786
+
787
+ nearestNeighbors = Math.Min(dataSet.Count, nearestNeighbors);
788
+
789
+ IComparer<FastVector3Int> comparison = Comparer<FastVector3Int>.Create(
790
+ (lhs, rhs) => grid.CellToWorld(lhs).y.CompareTo(grid.CellToWorld(rhs).y)
791
+ );
792
+
793
+ FastVector3Int? maybeFirst = null;
794
+ foreach (FastVector3Int gridPosition in dataSet)
795
+ {
796
+ if (maybeFirst == null || comparison.Compare(gridPosition, maybeFirst.Value) < 0)
797
+ {
798
+ maybeFirst = gridPosition;
799
+ }
800
+ }
801
+
802
+ if (maybeFirst == null)
803
+ {
804
+ return dataSet;
805
+ }
806
+
807
+ FastVector3Int first = maybeFirst.Value;
808
+ List<FastVector3Int> hull = new(dataSet.Count) { first };
809
+ int step = 2;
810
+ float previousAngle = 0f;
811
+ FastVector3Int current = first;
812
+ _ = dataSet.Remove(current);
813
+
814
+ float CalculateAngle(Vector2 lhs, Vector2 rhs)
815
+ {
816
+ return Mathf.Atan2(rhs.y - lhs.y, rhs.x - lhs.x);
817
+ }
818
+
819
+ // https://github.com/merowech/java-concave-hull/blob/master/ConcaveHull.java
820
+ float AngleDifference(float lhsAngle, float rhsAngle)
821
+ {
822
+ if (0 < lhsAngle && 0 <= rhsAngle && rhsAngle < lhsAngle)
823
+ {
824
+ return Math.Abs(lhsAngle - rhsAngle);
825
+ }
826
+
827
+ if (0 <= lhsAngle && 0 < rhsAngle && lhsAngle < rhsAngle)
828
+ {
829
+ return 2 * Mathf.PI + lhsAngle - rhsAngle;
830
+ }
831
+
832
+ if (lhsAngle < 0 && rhsAngle <= 0 && lhsAngle < rhsAngle)
833
+ {
834
+ return 2 * Mathf.PI + lhsAngle + Math.Abs(rhsAngle);
835
+ }
836
+
837
+ if (lhsAngle <= 0 && rhsAngle < 0 && rhsAngle < lhsAngle)
838
+ {
839
+ return Math.Abs(lhsAngle - rhsAngle);
840
+ }
841
+
842
+ if (lhsAngle <= 0 && 0 < rhsAngle)
843
+ {
844
+ return 2 * Mathf.PI + lhsAngle - rhsAngle;
845
+ }
846
+
847
+ if (0 <= lhsAngle && rhsAngle <= 0)
848
+ {
849
+ return lhsAngle + Math.Abs(rhsAngle);
850
+ }
851
+
852
+ return 0f;
853
+ }
854
+
855
+ // Order by descending right hand turns
856
+ int RightHandTurnComparison(FastVector3Int lhs, FastVector3Int rhs)
857
+ {
858
+ // TODO: I think this is fucked
859
+ Vector2 currentPoint = grid.CellToWorld(current);
860
+ Vector2 lhsPoint = grid.CellToWorld(lhs);
861
+ Vector2 rhsPoint = grid.CellToWorld(rhs);
862
+
863
+ float lhsAngle = AngleDifference(
864
+ previousAngle,
865
+ CalculateAngle(currentPoint, lhsPoint)
866
+ );
867
+ float rhsAngle = AngleDifference(
868
+ previousAngle,
869
+ CalculateAngle(currentPoint, rhsPoint)
870
+ );
871
+ return rhsAngle.CompareTo(lhsAngle);
872
+ }
873
+
874
+ List<FastVector3Int> clockwisePoints = Buffers<FastVector3Int>.List;
875
+ void FindNearestNeighborsAndPutInClockwisePoints()
876
+ {
877
+ clockwisePoints.Clear();
878
+ clockwisePoints.AddRange(dataSet);
879
+ Vector2 currentPoint = grid.CellToWorld(current);
880
+ clockwisePoints.Sort(
881
+ (lhs, rhs) =>
882
+ {
883
+ Vector2 lhsPoint = grid.CellToWorld(lhs);
884
+ Vector2 rhsPoint = grid.CellToWorld(rhs);
885
+ return (lhsPoint - currentPoint).sqrMagnitude.CompareTo(
886
+ (rhsPoint - currentPoint).sqrMagnitude
887
+ );
888
+ }
889
+ );
890
+ if (nearestNeighbors < clockwisePoints.Count)
891
+ {
892
+ clockwisePoints.RemoveRange(
893
+ nearestNeighbors,
894
+ clockwisePoints.Count - nearestNeighbors
895
+ );
896
+ }
897
+ }
898
+
899
+ while (0 < dataSet.Count)
900
+ {
901
+ if (step == 5)
902
+ {
903
+ dataSet.Add(first);
904
+ }
905
+
906
+ FindNearestNeighborsAndPutInClockwisePoints();
907
+ clockwisePoints.Sort(RightHandTurnComparison);
908
+
909
+ bool intersects = true;
910
+ int i = -1;
911
+ while (intersects && i < clockwisePoints.Count - 1)
912
+ {
913
+ ++i;
914
+
915
+ FastVector3Int indexedPoint = clockwisePoints[i];
916
+ int lastPoint = indexedPoint == first ? 1 : 0;
917
+ int j = 2;
918
+ intersects = false;
919
+ Vector2 lhsTo = grid.CellToWorld(indexedPoint);
920
+ while (!intersects && j < hull.Count - lastPoint)
921
+ {
922
+ Vector2 lhsFrom = grid.CellToWorld(hull[step - 2]);
923
+ Vector2 rhsFrom = grid.CellToWorld(hull[step - 2 - j]);
924
+ Vector2 rhsTo = grid.CellToWorld(hull[step - 1 - j]);
925
+ intersects = Intersects(lhsFrom, lhsTo, rhsFrom, rhsTo);
926
+ ++j;
927
+ }
928
+ }
929
+
930
+ if (intersects)
931
+ {
932
+ for (i = dataSet.Count - 1; 0 <= i; --i)
933
+ {
934
+ if (!IsPositionInside(hull, dataSet[i], grid))
935
+ {
936
+ return BuildConcaveHull2(
937
+ gridPositions,
938
+ grid,
939
+ random,
940
+ nearestNeighbors + 1
941
+ );
942
+ }
943
+ }
944
+
945
+ return hull;
946
+ }
947
+
948
+ current = clockwisePoints[i];
949
+ if (current != first)
950
+ {
951
+ hull.Add(current);
952
+ }
953
+ else
954
+ {
955
+ break;
956
+ }
957
+
958
+ int currentIndex = dataSet.IndexOf(current);
959
+ if (0 <= currentIndex)
960
+ {
961
+ dataSet.RemoveAtSwapBack(currentIndex);
962
+ }
963
+
964
+ previousAngle = CalculateAngle(
965
+ grid.CellToWorld(hull[step - 1]),
966
+ grid.CellToWorld(hull[step - 2])
967
+ );
968
+ ++step;
969
+ }
970
+
971
+ for (int i = dataSet.Count - 1; 0 <= i; --i)
972
+ {
973
+ if (!IsPositionInside(hull, dataSet[i], grid))
974
+ {
975
+ return BuildConcaveHull2(gridPositions, grid, random, nearestNeighbors + 1);
976
+ }
977
+ }
978
+
979
+ return hull;
980
+ }
981
+
982
+ // This one has bugs, user beware
983
+ // https://github.com/Liagson/ConcaveHullGenerator/tree/master
984
+ #region ConcaveHull Functions
985
+
986
+ private readonly struct Line
987
+ {
988
+ public readonly double sqrMagnitude;
989
+
990
+ public readonly Vector2 from;
991
+ public readonly Vector2 to;
992
+
993
+ public Line(Vector2 from, Vector2 to)
994
+ {
995
+ this.from = from;
996
+ this.to = to;
997
+ sqrMagnitude = (from - to).sqrMagnitude;
998
+ }
999
+ }
1000
+
1001
+ public static List<FastVector3Int> BuildConcaveHull(
1002
+ this IEnumerable<FastVector3Int> gridPositions,
1003
+ Grid grid,
1004
+ IRandom random = null,
1005
+ float scaleFactor = 1,
1006
+ float concavity = 0f
1007
+ )
1008
+ {
1009
+ if (concavity < -1 || 1 < concavity)
1010
+ {
1011
+ throw new ArgumentException($"Concavity must be between [-1, 1], was {concavity}");
1012
+ }
1013
+
1014
+ List<FastVector3Int> originalGridPositions = gridPositions.ToList();
1015
+ if (originalGridPositions.Count <= 3)
1016
+ {
1017
+ return originalGridPositions;
1018
+ }
1019
+
1020
+ List<FastVector3Int> convexHull = originalGridPositions.BuildConvexHull(grid, random);
1021
+ HashSet<FastVector3Int> unusedNodes = originalGridPositions.ToHashSet();
1022
+ unusedNodes.ExceptWith(convexHull);
1023
+ List<Line> concaveHullLines = new(convexHull.Count);
1024
+ for (int i = 0; i < convexHull.Count; ++i)
1025
+ {
1026
+ FastVector3Int lhsGridPoint = convexHull[i];
1027
+ FastVector3Int rhsGridPoint = convexHull[(i + 1) % convexHull.Count];
1028
+ Vector2 lhs = grid.CellToWorld(lhsGridPoint);
1029
+ Vector2 rhs = grid.CellToWorld(rhsGridPoint);
1030
+ Line line = new(lhs, rhs);
1031
+ concaveHullLines.Add(line);
1032
+ }
1033
+
1034
+ bool aLineWasDividedInTheIteration;
1035
+ do
1036
+ {
1037
+ // Order by descending
1038
+ concaveHullLines.Sort((lhs, rhs) => rhs.sqrMagnitude.CompareTo(lhs.sqrMagnitude));
1039
+
1040
+ aLineWasDividedInTheIteration = false;
1041
+ for (int i = 0; i < concaveHullLines.Count; ++i)
1042
+ {
1043
+ Line line = concaveHullLines[i];
1044
+ IEnumerable<FastVector3Int> nearbyPoints = GetNearbyPoints(
1045
+ line,
1046
+ unusedNodes,
1047
+ grid,
1048
+ scaleFactor
1049
+ );
1050
+ List<Line> dividedLine = GetDividedLine(
1051
+ line,
1052
+ nearbyPoints,
1053
+ concaveHullLines,
1054
+ grid,
1055
+ concavity
1056
+ );
1057
+ if (0 < dividedLine.Count)
1058
+ {
1059
+ aLineWasDividedInTheIteration = true;
1060
+ FastVector3Int toRemove = grid.WorldToCell(dividedLine[0].to);
1061
+ _ = unusedNodes.Remove(toRemove);
1062
+ concaveHullLines.AddRange(dividedLine);
1063
+ concaveHullLines.RemoveAtSwapBack(i);
1064
+ break;
1065
+ }
1066
+ }
1067
+ } while (aLineWasDividedInTheIteration);
1068
+
1069
+ List<FastVector3Int> concaveHull = new(concaveHullLines.Count);
1070
+ if (concaveHullLines.Count <= 0)
1071
+ {
1072
+ return concaveHull;
1073
+ }
1074
+
1075
+ Line currentlyConsideredLine = concaveHullLines[0];
1076
+ FastVector3Int from = grid.WorldToCell(currentlyConsideredLine.from);
1077
+ FastVector3Int to = grid.WorldToCell(currentlyConsideredLine.to);
1078
+ concaveHull.Add(from);
1079
+ concaveHull.Add(to);
1080
+ concaveHullLines.RemoveAtSwapBack(0);
1081
+ while (0 < concaveHullLines.Count)
1082
+ {
1083
+ int index = concaveHullLines.FindIndex(line =>
1084
+ {
1085
+ FastVector3Int lineFrom = grid.WorldToCell(line.from);
1086
+ return lineFrom == to;
1087
+ });
1088
+
1089
+ currentlyConsideredLine = concaveHullLines[index];
1090
+ to = grid.WorldToCell(currentlyConsideredLine.to);
1091
+ if (to == from)
1092
+ {
1093
+ break;
1094
+ }
1095
+ concaveHull.Add(to);
1096
+ concaveHullLines.RemoveAtSwapBack(index);
1097
+ }
1098
+
1099
+ return concaveHull;
1100
+ }
1101
+
1102
+ public static bool IsPositionInside(
1103
+ List<FastVector3Int> hull,
1104
+ FastVector3Int gridPosition,
1105
+ Grid grid
1106
+ )
1107
+ {
1108
+ bool isPositionInside = false;
1109
+ Vector2 position = grid.CellToWorld(gridPosition);
1110
+ for (int i = 0; i < hull.Count; ++i)
1111
+ {
1112
+ Vector2 oldVector = grid.CellToWorld(hull[i]);
1113
+ int nextIndex = (i + 1) % hull.Count;
1114
+ Vector2 newVector = grid.CellToWorld(hull[nextIndex]);
1115
+
1116
+ Vector2 lhs;
1117
+ Vector2 rhs;
1118
+ if (oldVector.x < newVector.x)
1119
+ {
1120
+ lhs = oldVector;
1121
+ rhs = newVector;
1122
+ }
1123
+ else
1124
+ {
1125
+ lhs = newVector;
1126
+ rhs = oldVector;
1127
+ }
1128
+
1129
+ if (
1130
+ (newVector.x < position.x) == (position.x <= oldVector.x)
1131
+ && (position.y - (long)lhs.y) * (rhs.x - lhs.x)
1132
+ < (rhs.y - (long)lhs.y) * (position.x - lhs.x)
1133
+ )
1134
+ {
1135
+ isPositionInside = !isPositionInside;
1136
+ }
1137
+ }
1138
+
1139
+ return isPositionInside;
1140
+ }
1141
+
1142
+ private static List<Line> GetDividedLine(
1143
+ Line line,
1144
+ IEnumerable<FastVector3Int> nearbyPoints,
1145
+ List<Line> concaveHull,
1146
+ Grid grid,
1147
+ float concavity
1148
+ )
1149
+ {
1150
+ return GetDividedLine(line.from, line.to, nearbyPoints, concaveHull, grid, concavity);
1151
+ }
1152
+
1153
+ private static List<Line> GetDividedLine(
1154
+ Vector2 from,
1155
+ Vector2 to,
1156
+ IEnumerable<FastVector3Int> nearbyPoints,
1157
+ List<Line> concaveHull,
1158
+ Grid grid,
1159
+ float concavity
1160
+ )
1161
+ {
1162
+ List<Line> dividedLine = new(2);
1163
+ Dictionary<Vector2, double> okMiddlePoints = new();
1164
+ foreach (FastVector3Int gridPoint in nearbyPoints)
1165
+ {
1166
+ Vector2 point = grid.CellToWorld(gridPoint);
1167
+ double cosine = GetCosine(from, to, point);
1168
+ if (cosine < concavity)
1169
+ {
1170
+ Line newLineA = new(from, point);
1171
+ Line newLineB = new(point, to);
1172
+ if (
1173
+ !LineCollidesWithHull(newLineA, concaveHull)
1174
+ && !LineCollidesWithHull(newLineB, concaveHull)
1175
+ )
1176
+ {
1177
+ okMiddlePoints[point] = cosine;
1178
+ }
1179
+ }
1180
+ }
1181
+
1182
+ if (0 < okMiddlePoints.Count)
1183
+ {
1184
+ Vector2 middlePoint = new();
1185
+ double minCosine = double.MaxValue;
1186
+ foreach (KeyValuePair<Vector2, double> entry in okMiddlePoints)
1187
+ {
1188
+ double cosine = entry.Value;
1189
+ if (cosine < minCosine)
1190
+ {
1191
+ minCosine = cosine;
1192
+ middlePoint = entry.Key;
1193
+ }
1194
+ }
1195
+
1196
+ dividedLine.Add(new Line(from, middlePoint));
1197
+ dividedLine.Add(new Line(middlePoint, to));
1198
+ }
1199
+
1200
+ return dividedLine;
1201
+ }
1202
+
1203
+ private static bool LineCollidesWithHull(Line line, List<Line> concaveHull)
1204
+ {
1205
+ return LineCollidesWithHull(line.from, line.to, concaveHull);
1206
+ }
1207
+
1208
+ private static bool LineCollidesWithHull(Vector2 from, Vector2 to, List<Line> concaveHull)
1209
+ {
1210
+ foreach (Line line in concaveHull)
1211
+ {
1212
+ Vector2 lhs = line.from;
1213
+ Vector2 rhs = line.to;
1214
+
1215
+ if (from != lhs && from != rhs && to != lhs && to != rhs)
1216
+ {
1217
+ if (Intersects(from, to, lhs, rhs))
1218
+ {
1219
+ return true;
1220
+ }
1221
+ }
1222
+ }
1223
+
1224
+ return false;
1225
+ }
1226
+
1227
+ public static double GetCosine(Vector2 a, Vector3 b, Vector3 o)
1228
+ {
1229
+ /* Law of cosines */
1230
+ double aPow2 = (a.x - o.x) * (a.x - o.x) + (a.y - o.y) * (a.y - o.y);
1231
+ double bPow2 = (b.x - o.x) * (b.x - o.x) + (b.y - o.y) * (b.y - o.y);
1232
+ double cPow2 = (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
1233
+ double cos = (aPow2 + bPow2 - cPow2) / (2 * Math.Sqrt(aPow2 * bPow2));
1234
+ return Math.Round(cos, 4);
1235
+ }
1236
+
1237
+ private static IEnumerable<FastVector3Int> GetNearbyPoints(
1238
+ Line line,
1239
+ ICollection<FastVector3Int> points,
1240
+ Grid grid,
1241
+ float scaleFactor
1242
+ )
1243
+ {
1244
+ return GetNearbyPoints(line.from, line.to, points, grid, scaleFactor);
1245
+ }
1246
+
1247
+ private static IEnumerable<FastVector3Int> GetNearbyPoints(
1248
+ Vector2 from,
1249
+ Vector2 to,
1250
+ ICollection<FastVector3Int> points,
1251
+ Grid grid,
1252
+ float scaleFactor
1253
+ )
1254
+ {
1255
+ const int maxTries = 2;
1256
+ for (int tries = 0; tries < maxTries; ++tries)
1257
+ {
1258
+ bool foundAnyPoints = false;
1259
+ Bounds boundary = GetBoundary(from, to, scaleFactor);
1260
+ foreach (FastVector3Int gridPoint in points)
1261
+ {
1262
+ Vector2 point = grid.CellToWorld(gridPoint);
1263
+ if (point != from && point != to)
1264
+ {
1265
+ if (boundary.FastContains2D(point))
1266
+ {
1267
+ foundAnyPoints = true;
1268
+ yield return gridPoint;
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ if (foundAnyPoints)
1274
+ {
1275
+ yield break;
1276
+ }
1277
+
1278
+ scaleFactor *= (4 / 3f);
1279
+ }
1280
+ }
1281
+
1282
+ private static Bounds GetBoundary(Vector2 from, Vector2 to, float scaleFactor)
1283
+ {
1284
+ float xMin = Math.Min(from.x, to.x);
1285
+ float yMin = Math.Min(from.y, to.y);
1286
+ float xMax = Math.Max(from.x, to.x);
1287
+ float yMax = Math.Max(from.y, to.y);
1288
+
1289
+ float width = xMax - xMin;
1290
+ float height = yMax - yMin;
1291
+ return new Bounds(
1292
+ new Vector3(xMin + width / 2, yMin + height / 2),
1293
+ new Vector3(width, height) * scaleFactor + new Vector3(0.001f, 0.001f)
1294
+ );
1295
+ }
1296
+
1297
+ // https://www.geeksforgeeks.org/how-to-check-if-a-given-point-lies-inside-a-polygon/#
1298
+
1299
+ /// <summary>
1300
+ /// Returns true if a line segment 'lhsFrom->lhsTo' intersects the line segment
1301
+ /// 'rhsFrom->rhsTo'
1302
+ /// </summary>
1303
+ /// <param name="lhsFrom">LineSegmentA start point.</param>
1304
+ /// <param name="lhsTo">LineSegmentA end point.</param>
1305
+ /// <param name="rhsFrom">LineSegmentB start point.</param>
1306
+ /// <param name="rhsTo">LineSegmentB end point.</param>
1307
+ /// <returns>True if the line segments intersect.</returns>
1308
+ public static bool Intersects(
1309
+ Vector2 lhsFrom,
1310
+ Vector2 lhsTo,
1311
+ Vector2 rhsFrom,
1312
+ Vector2 rhsTo
1313
+ )
1314
+ {
1315
+ if (lhsFrom == rhsFrom)
1316
+ {
1317
+ return false;
1318
+ }
1319
+
1320
+ if (lhsFrom == rhsTo)
1321
+ {
1322
+ return false;
1323
+ }
1324
+
1325
+ if (lhsTo == rhsFrom)
1326
+ {
1327
+ return false;
1328
+ }
1329
+
1330
+ if (lhsTo == rhsTo)
1331
+ {
1332
+ return false;
1333
+ }
1334
+
1335
+ OrientationType orientation1 = Orientation(lhsFrom, lhsTo, rhsFrom);
1336
+ OrientationType orientation2 = Orientation(lhsFrom, lhsTo, rhsTo);
1337
+ OrientationType orientation3 = Orientation(rhsFrom, rhsTo, lhsFrom);
1338
+ OrientationType orientation4 = Orientation(rhsFrom, rhsTo, lhsTo);
1339
+
1340
+ if (orientation1 != orientation2 && orientation3 != orientation4)
1341
+ {
1342
+ return true;
1343
+ }
1344
+
1345
+ if (orientation1 == OrientationType.Colinear && LiesOnSegment(lhsFrom, rhsFrom, lhsTo))
1346
+ {
1347
+ return true;
1348
+ }
1349
+
1350
+ if (orientation2 == OrientationType.Colinear && LiesOnSegment(lhsFrom, rhsTo, lhsTo))
1351
+ {
1352
+ return true;
1353
+ }
1354
+
1355
+ if (orientation3 == OrientationType.Colinear && LiesOnSegment(rhsFrom, lhsFrom, rhsTo))
1356
+ {
1357
+ return true;
1358
+ }
1359
+
1360
+ if (orientation4 == OrientationType.Colinear && LiesOnSegment(rhsFrom, lhsTo, rhsTo))
1361
+ {
1362
+ return true;
1363
+ }
1364
+
1365
+ return false;
1366
+ }
1367
+
1368
+ /// <summary>
1369
+ /// Given three colinear points p, q, r, returns whether the
1370
+ /// point q lines on the line segment pr.
1371
+ /// </summary>
1372
+ /// <param name="p">Beginning of line segment.</param>
1373
+ /// <param name="q">Check if on line segment.</param>
1374
+ /// <param name="r">End of line segment.</param>
1375
+ /// <returns>True if q lies on the line segment pr.</returns>
1376
+ public static bool LiesOnSegment(Vector2 p, Vector2 q, Vector2 r)
1377
+ {
1378
+ return q.x <= Math.Max(p.x, r.x)
1379
+ && Math.Min(p.x, r.x) <= q.x
1380
+ && q.y <= Math.Max(p.y, r.y)
1381
+ && Math.Min(p.y, r.y) <= q.y;
1382
+ }
1383
+
1384
+ public enum OrientationType
1385
+ {
1386
+ Colinear = 0,
1387
+ Clockwise = 1,
1388
+ Counterclockwise = 2,
1389
+ }
1390
+
1391
+ /// <summary>
1392
+ /// Finds the orientation of an ordered triplet (p, q, r).
1393
+ /// </summary>
1394
+ /// <param name="p">Triplet element 1.</param>
1395
+ /// <param name="q">Triplet element 2.</param>
1396
+ /// <param name="r">Triplet element 3.</param>
1397
+ /// <returns>The orientation of the triplet</returns>
1398
+ public static OrientationType Orientation(Vector2 p, Vector2 q, Vector2 r)
1399
+ {
1400
+ float value = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1401
+ if (Mathf.Approximately(value, 0))
1402
+ {
1403
+ return OrientationType.Colinear;
1404
+ }
1405
+
1406
+ return 0 < value ? OrientationType.Clockwise : OrientationType.Counterclockwise;
1407
+ }
1408
+
1409
+ #endregion
1410
+
1411
+ public static Vector2 Rotate(this Vector2 v, float degrees)
1412
+ {
1413
+ float sin = Mathf.Sin(degrees * Mathf.Deg2Rad);
1414
+ float cos = Mathf.Cos(degrees * Mathf.Deg2Rad);
1415
+
1416
+ float tx = v.x;
1417
+ float ty = v.y;
1418
+
1419
+ Vector2 rotatedVector;
1420
+ rotatedVector.x = (cos * tx) - (sin * ty);
1421
+ rotatedVector.y = (sin * tx) + (cos * ty);
1422
+
1423
+ return rotatedVector;
1424
+ }
1425
+
1426
+ public static bool FastIntersects(this Bounds bounds, Bounds other)
1427
+ {
1428
+ Vector3 boundsMin = bounds.min;
1429
+ Vector3 otherMax = other.max;
1430
+ if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y || otherMax.z < boundsMin.z)
1431
+ {
1432
+ return false;
1433
+ }
1434
+
1435
+ Vector3 boundsMax = bounds.max;
1436
+ Vector3 otherMin = other.min;
1437
+ return boundsMax.x >= otherMin.x
1438
+ && boundsMax.y >= otherMin.y
1439
+ && boundsMax.z >= otherMin.z;
1440
+ }
1441
+
1442
+ public static bool FastContains2D(this BoundsInt bounds, FastVector3Int position)
1443
+ {
1444
+ return position.x >= bounds.xMin
1445
+ && position.y >= bounds.yMin
1446
+ && position.x < bounds.xMax
1447
+ && position.y < bounds.yMax;
1448
+ }
1449
+
1450
+ public static bool FastIntersects2D(this BoundsInt bounds, BoundsInt other)
1451
+ {
1452
+ if (other.xMax < bounds.xMin || other.yMax < bounds.yMin)
1453
+ {
1454
+ return false;
1455
+ }
1456
+
1457
+ return bounds.xMax >= other.xMin && bounds.yMax >= other.yMin;
1458
+ }
1459
+
1460
+ public static bool FastContains2D(this Bounds bounds, Vector2 position)
1461
+ {
1462
+ Vector3 min = bounds.min;
1463
+ if (position.x < min.x || position.y < bounds.min.y)
1464
+ {
1465
+ return false;
1466
+ }
1467
+ Vector3 max = bounds.max;
1468
+ return position.x < max.x && position.y < max.y;
1469
+ }
1470
+
1471
+ public static bool FastIntersects2D(this Bounds bounds, Bounds other)
1472
+ {
1473
+ Vector3 boundsMin = bounds.min;
1474
+ Vector3 otherMax = other.max;
1475
+ if (otherMax.x < boundsMin.x || otherMax.y < boundsMin.y)
1476
+ {
1477
+ return false;
1478
+ }
1479
+
1480
+ Vector3 boundsMax = bounds.max;
1481
+ Vector3 otherMin = other.min;
1482
+ return boundsMax.x >= otherMin.x && boundsMax.y >= otherMin.y;
1483
+ }
1484
+
1485
+ public static bool Overlaps2D(this Bounds bounds, Bounds other)
1486
+ {
1487
+ Vector3 boundsMin = bounds.min;
1488
+ Vector3 otherMin = other.min;
1489
+ if (otherMin.x < boundsMin.x || otherMin.y < boundsMin.y)
1490
+ {
1491
+ return false;
1492
+ }
1493
+
1494
+ Vector3 boundsMax = bounds.max;
1495
+ Vector3 otherMax = other.max;
1496
+ return otherMax.x <= boundsMax.x && otherMax.y <= boundsMax.y;
1497
+ }
1498
+
1499
+ public static BoundsInt WithPadding(this BoundsInt bounds, int xPadding, int yPadding)
1500
+ {
1501
+ Vector3Int size = bounds.size;
1502
+ return new BoundsInt(
1503
+ bounds.xMin - xPadding,
1504
+ bounds.yMin - yPadding,
1505
+ bounds.zMin,
1506
+ size.x + 2 * xPadding,
1507
+ size.y + 2 * yPadding,
1508
+ size.z
1509
+ );
1510
+ }
1511
+
1512
+ public static void SetColors(this UnityEngine.UI.Slider slider, Color color)
1513
+ {
1514
+ ColorBlock block = slider.colors;
1515
+
1516
+ block.normalColor = color;
1517
+ block.highlightedColor = color;
1518
+ block.pressedColor = color;
1519
+ block.selectedColor = color;
1520
+ block.disabledColor = color;
1521
+
1522
+ slider.colors = block;
1523
+ }
1524
+
1525
+ public static void SetLeft(this RectTransform rt, float left)
1526
+ {
1527
+ rt.offsetMin = new Vector2(left, rt.offsetMin.y);
1528
+ }
1529
+
1530
+ public static void SetRight(this RectTransform rt, float right)
1531
+ {
1532
+ rt.offsetMax = new Vector2(-right, rt.offsetMax.y);
1533
+ }
1534
+
1535
+ public static void SetTop(this RectTransform rt, float top)
1536
+ {
1537
+ rt.offsetMax = new Vector2(rt.offsetMax.x, -top);
1538
+ }
1539
+
1540
+ public static void SetBottom(this RectTransform rt, float bottom)
1541
+ {
1542
+ rt.offsetMin = new Vector2(rt.offsetMin.x, bottom);
1543
+ }
1544
+
1545
+ public static IEnumerable<FastVector3Int> AllFastPositionsWithin(this BoundsInt bounds)
1546
+ {
1547
+ Vector3Int min = bounds.min;
1548
+ Vector3Int max = bounds.max;
1549
+ for (int x = min.x; x < max.x; ++x)
1550
+ {
1551
+ for (int y = min.y; y < max.y; ++y)
1552
+ {
1553
+ for (int z = min.z; z < max.z; ++z)
1554
+ {
1555
+ yield return new FastVector3Int(x, y, z);
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+
1561
+ public static bool Contains(this BoundsInt bounds, FastVector3Int position)
1562
+ {
1563
+ return bounds.Contains(position);
1564
+ }
1565
+
1566
+ public static bool IsOnEdge2D(this FastVector3Int position, BoundsInt bounds)
1567
+ {
1568
+ if (bounds.xMin == position.x || (bounds.xMax - 1) == position.x)
1569
+ {
1570
+ return bounds.yMin <= position.y && position.y < bounds.yMax;
1571
+ }
1572
+
1573
+ if (bounds.yMin == position.y || (bounds.yMax - 1) == position.y)
1574
+ {
1575
+ return bounds.xMin <= position.x && position.x < bounds.xMax;
1576
+ }
1577
+
1578
+ return false;
1579
+ }
1580
+
1581
+ #if UNITY_EDITOR
1582
+ public static IEnumerable<Sprite> GetSpritesFromClip(this AnimationClip clip)
1583
+ {
1584
+ if (clip == null)
1585
+ {
1586
+ yield break;
1587
+ }
1588
+
1589
+ foreach (
1590
+ EditorCurveBinding binding in AnimationUtility.GetObjectReferenceCurveBindings(clip)
1591
+ )
1592
+ {
1593
+ ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve(
1594
+ clip,
1595
+ binding
1596
+ );
1597
+ foreach (ObjectReferenceKeyframe frame in keyframes)
1598
+ {
1599
+ if (frame.value is Sprite sprite)
1600
+ {
1601
+ yield return sprite;
1602
+ }
1603
+ }
1604
+ }
1605
+ }
1606
+ #endif
1607
+ }
1608
+ }