| | | 1 | | using FixedMathSharp; |
| | | 2 | | using GridForge.Grids; |
| | | 3 | | using GridForge.Spatial; |
| | | 4 | | using SwiftCollections; |
| | | 5 | | using SwiftCollections.Pool; |
| | | 6 | | using System; |
| | | 7 | | using System.Diagnostics.CodeAnalysis; |
| | | 8 | | using System.Runtime.CompilerServices; |
| | | 9 | | |
| | | 10 | | namespace Trailblazer.Pathing; |
| | | 11 | | |
| | | 12 | | /// <summary> |
| | | 13 | | /// Represents a partition attached to a Voxel that provides additional data used during pathfinding, |
| | | 14 | | /// such as clearance information, movement cost, and neighbor traversal helpers. |
| | | 15 | | /// </summary> |
| | | 16 | | public class SolidChartPartition : IVoxelPartition |
| | | 17 | | { |
| | | 18 | | #region Constants |
| | | 19 | | |
| | | 20 | | /// <summary> |
| | | 21 | | /// Maximum clearance degree allowed for valid traversal. |
| | | 22 | | /// </summary> |
| | 1 | 23 | | public static readonly byte DefaultDegreeCap = 8; |
| | | 24 | | |
| | 1 | 25 | | private static readonly Lazy<SwiftQueuePool<(SolidChartPartition v, byte dist)>> _clearanceQueuePool = |
| | 1 | 26 | | new(() => new SwiftQueuePool<(SolidChartPartition v, byte dist)>()); |
| | | 27 | | |
| | 720 | 28 | | internal static SwiftQueuePool<(SolidChartPartition v, byte dist)> ClearanceQueuePool => _clearanceQueuePool.Value; |
| | | 29 | | |
| | | 30 | | #endregion |
| | | 31 | | |
| | | 32 | | /// <summary> |
| | | 33 | | /// The world-scoped coordinate of the voxel this partition is attached to. |
| | | 34 | | /// </summary> |
| | | 35 | | public WorldVoxelIndex WorldIndex { get; private set; } |
| | | 36 | | |
| | | 37 | | internal PathingWorldState? OwnerState { get; private set; } |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// Gets the voxel associated with this partition. |
| | | 41 | | /// </summary> |
| | | 42 | | public Voxel Voxel |
| | | 43 | | { |
| | | 44 | | get |
| | | 45 | | { |
| | 1774 | 46 | | if (TryGetGridAndVoxel(WorldIndex, out _, out Voxel? voxel) |
| | 1774 | 47 | | && voxel != null) |
| | 1773 | 48 | | return voxel; |
| | 0 | 49 | | throw new InvalidOperationException($"Partition at {WorldIndex} is not attached to a valid voxel!"); |
| | | 50 | | } |
| | | 51 | | } |
| | | 52 | | |
| | | 53 | | /// <summary> |
| | | 54 | | /// The world-space position of the voxel. |
| | | 55 | | /// </summary> |
| | | 56 | | public Vector3d VoxelPosition { get; private set; } |
| | | 57 | | |
| | | 58 | | /// <summary> |
| | | 59 | | /// Gets a value indicating whether the current tile can be traversed. |
| | | 60 | | /// </summary> |
| | | 61 | | public bool IsWalkable { get; private set; } |
| | | 62 | | |
| | | 63 | | /// <summary> |
| | | 64 | | /// Indicates whether the voxel has been partitioned and is in use. |
| | | 65 | | /// </summary> |
| | | 66 | | public bool IsPartitioned { get; set; } |
| | | 67 | | |
| | | 68 | | /// <summary> |
| | | 69 | | /// A cost bias for this partition. Positive values make the partition less desirable. |
| | | 70 | | /// The public setter preserves caller-controlled adjustments, |
| | | 71 | | /// while chart-authored modifiers are aggregated separately. |
| | | 72 | | /// </summary> |
| | | 73 | | public int PathCostModifier |
| | | 74 | | { |
| | | 75 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 13274 | 76 | | get => _manualPathCostModifier + _chartPathCostModifier; |
| | | 77 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 2446 | 78 | | set => _manualPathCostModifier = value; |
| | | 79 | | } |
| | | 80 | | |
| | | 81 | | private int _manualPathCostModifier; |
| | | 82 | | |
| | | 83 | | private int _chartPathCostModifier; |
| | | 84 | | |
| | | 85 | | /// <summary> |
| | | 86 | | /// Gets the neighboring partitions adjacent to this partition. |
| | | 87 | | /// </summary> |
| | | 88 | | /// <remarks> |
| | | 89 | | /// Each element in the array represents a neighboring partition in a specific direction or position. |
| | | 90 | | /// The array may contain null values if a neighbor does not exist in that position. |
| | | 91 | | /// </remarks> |
| | | 92 | | public SolidChartPartition?[]? Neighbors { get; private set; } |
| | | 93 | | |
| | | 94 | | #region Clearance Properties |
| | | 95 | | |
| | | 96 | | /// <summary> |
| | | 97 | | /// The number of traversable connections until the nearest unwalkable voxel. |
| | | 98 | | /// </summary> |
| | | 99 | | private byte _clearanceRadiusInVoxels; |
| | | 100 | | |
| | | 101 | | /// <summary> |
| | | 102 | | /// Indicates whether the clearance degree has been computed and is valid. |
| | | 103 | | /// </summary> |
| | | 104 | | private bool _isClearanceValid; |
| | | 105 | | |
| | | 106 | | #endregion |
| | | 107 | | |
| | | 108 | | #region Reachability Snapshot |
| | | 109 | | |
| | | 110 | | private int _reachabilitySnapshotKey; |
| | | 111 | | |
| | 2393 | 112 | | private int _reachabilityVersion = -1; |
| | | 113 | | |
| | | 114 | | private int _reachabilityComponentId; |
| | | 115 | | |
| | | 116 | | #endregion |
| | | 117 | | |
| | | 118 | | #region Chart Properties |
| | | 119 | | |
| | | 120 | | /// <summary> |
| | | 121 | | /// Maps that currently include this partition as part of their traversable space. |
| | | 122 | | /// </summary> |
| | | 123 | | public SwiftHashSet<string>? ChartOwners { get; private set; } |
| | | 124 | | |
| | | 125 | | /// <summary> |
| | | 126 | | /// The chart whose authored cell currently wins overlap resolution for this voxel. |
| | | 127 | | /// </summary> |
| | | 128 | | public string? EffectiveChartOwner { get; private set; } |
| | | 129 | | |
| | | 130 | | /// <summary> |
| | | 131 | | /// The authored chart flags from the winning effective cell currently applied to this live partition. |
| | | 132 | | /// </summary> |
| | | 133 | | public NavigationChartCellFlags ChartFlags { get; private set; } |
| | | 134 | | |
| | | 135 | | /// <summary> |
| | | 136 | | /// Returns true if any map currently references this partition. |
| | | 137 | | /// </summary> |
| | 5 | 138 | | public bool HasAnyOwners => ChartOwners?.Count > 0; |
| | | 139 | | |
| | | 140 | | #endregion |
| | | 141 | | |
| | | 142 | | /// <summary> |
| | | 143 | | /// Sets the parent index for the current voxel in the world. |
| | | 144 | | /// </summary> |
| | | 145 | | /// <param name="parentIndex">The index to assign as the parent of the current voxel.</param> |
| | 2442 | 146 | | public void SetParentIndex(WorldVoxelIndex parentIndex) => WorldIndex = parentIndex; |
| | | 147 | | |
| | 2442 | 148 | | internal void SetOwner(PathingWorldState ownerState) => OwnerState = ownerState; |
| | | 149 | | |
| | | 150 | | /// <summary> |
| | | 151 | | /// Attaches a partition to a specified <see cref="Voxel"/>, updating its state and invoking initialization logic. |
| | | 152 | | /// </summary> |
| | | 153 | | /// <param name="voxel">The target voxel where the partition will be added.</param> |
| | | 154 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 155 | | public void OnAddToVoxel(Voxel voxel) |
| | | 156 | | { |
| | 2456 | 157 | | voxel.OnObstacleAdded += HandleChange; |
| | 2456 | 158 | | voxel.OnObstacleRemoved += HandleChange; |
| | | 159 | | |
| | 2456 | 160 | | WorldIndex = voxel.WorldIndex; |
| | 2456 | 161 | | VoxelPosition = voxel.WorldPosition; |
| | | 162 | | |
| | 2456 | 163 | | IsWalkable = !voxel.IsBlocked; |
| | | 164 | | |
| | 2456 | 165 | | _clearanceRadiusInVoxels = DefaultDegreeCap; |
| | | 166 | | |
| | 2456 | 167 | | IsPartitioned = true; |
| | 2456 | 168 | | } |
| | | 169 | | |
| | | 170 | | /// <summary> |
| | | 171 | | /// Detaches a partition from a specified <see cref="Voxel"/>, resetting its state and invoking cleanup logic. |
| | | 172 | | /// </summary> |
| | | 173 | | /// <param name="voxel">The target voxel from which the partition will be removed.</param> |
| | | 174 | | /// <remarks> |
| | | 175 | | /// This will call <see cref="Reset"/> as an action on release |
| | | 176 | | /// </remarks> |
| | | 177 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 178 | | public void OnRemoveFromVoxel(Voxel voxel) |
| | | 179 | | { |
| | 2442 | 180 | | voxel.OnObstacleAdded -= HandleChange; |
| | 2442 | 181 | | voxel.OnObstacleRemoved -= HandleChange; |
| | | 182 | | |
| | 2442 | 183 | | PathingWorldState? ownerState = OwnerState; |
| | 2442 | 184 | | if (ownerState != null) |
| | 2442 | 185 | | ownerState.PartitionPool.Release(this); |
| | | 186 | | else |
| | 0 | 187 | | PathManager.PartitionPool.Release(this); |
| | 0 | 188 | | } |
| | | 189 | | |
| | | 190 | | /// <summary> |
| | | 191 | | /// Resets this partition's internal state, preparing it for reuse or reattachment. |
| | | 192 | | /// </summary> |
| | | 193 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 194 | | internal void Reset() |
| | | 195 | | { |
| | 2444 | 196 | | WorldIndex = default; |
| | 2444 | 197 | | OwnerState = null; |
| | | 198 | | |
| | 2444 | 199 | | _isClearanceValid = false; |
| | | 200 | | |
| | 2444 | 201 | | IsWalkable = false; |
| | | 202 | | |
| | 2444 | 203 | | PathCostModifier = 0; |
| | 2444 | 204 | | _chartPathCostModifier = 0; |
| | 2444 | 205 | | ChartFlags = NavigationChartCellFlags.None; |
| | | 206 | | |
| | 2444 | 207 | | Neighbors = null; |
| | | 208 | | |
| | 2444 | 209 | | _clearanceRadiusInVoxels = DefaultDegreeCap; |
| | | 210 | | |
| | 2444 | 211 | | ChartOwners?.Clear(); |
| | 2444 | 212 | | EffectiveChartOwner = null; |
| | | 213 | | |
| | 2444 | 214 | | _reachabilitySnapshotKey = 0; |
| | 2444 | 215 | | _reachabilityVersion = -1; |
| | 2444 | 216 | | _reachabilityComponentId = 0; |
| | | 217 | | |
| | 2444 | 218 | | IsPartitioned = false; |
| | 2444 | 219 | | } |
| | | 220 | | |
| | | 221 | | /// <summary> |
| | | 222 | | /// Handles any obstacle changes on the associated voxel and invalidates clearance as needed. |
| | | 223 | | /// </summary> |
| | | 224 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 225 | | public void HandleChange(ObstacleEventInfo eventInfo) |
| | | 226 | | { |
| | | 227 | | // regardless of change type, we need to update clearance |
| | | 228 | | |
| | 7 | 229 | | IsWalkable = eventInfo.VoxelIndex != default && eventInfo.ObstacleCount == 0; |
| | 7 | 230 | | _clearanceRadiusInVoxels = DefaultDegreeCap; |
| | 7 | 231 | | _isClearanceValid = false; |
| | 7 | 232 | | SolidPartitionReachability.Invalidate(RequireOwnerState()); |
| | 7 | 233 | | } |
| | | 234 | | |
| | | 235 | | /// <summary> |
| | | 236 | | /// Populates the Neighbors array with references to adjacent SolidChartPartition instances based on the current Wor |
| | | 237 | | /// </summary> |
| | | 238 | | /// <remarks> |
| | | 239 | | /// If the grid or voxel corresponding to WorldIndex cannot be found, Neighbors is set to null. |
| | | 240 | | /// Each entry in the Neighbors array corresponds to a spatial direction; |
| | | 241 | | /// entries remain null if a neighbor is blocked or missing. |
| | | 242 | | /// </remarks> |
| | | 243 | | public void BindNeighbors() |
| | | 244 | | { |
| | 2483 | 245 | | Neighbors = new SolidChartPartition?[26]; |
| | | 246 | | |
| | 2483 | 247 | | if (!TryGetGridAndVoxel(WorldIndex, out VoxelGrid? grid, out Voxel? voxel)) |
| | | 248 | | { |
| | 0 | 249 | | TrailblazerLogger.Channel.Warn($"Failed to find grid or voxel for WorldIndex {WorldIndex}. Neighbors will be |
| | 0 | 250 | | Neighbors = null; |
| | 0 | 251 | | return; |
| | | 252 | | } |
| | | 253 | | |
| | | 254 | | // for each of the 26 SpatialDirection values (except None) |
| | 134082 | 255 | | foreach (SpatialDirection dir in SpatialAwareness.AllDirections) |
| | | 256 | | { |
| | | 257 | | // use Voxel’s cached neighbor lookup |
| | 64558 | 258 | | if (voxel!.TryGetNeighborFromDirection(grid!, dir, out Voxel? neighborVoxel, useCache: true) |
| | 64558 | 259 | | && neighborVoxel!.TryGetPartition(out SolidChartPartition? neighborPart)) |
| | | 260 | | { |
| | 11267 | 261 | | Neighbors[(int)dir] = neighborPart; |
| | | 262 | | } |
| | | 263 | | // else leave null = “blocked or missing” |
| | | 264 | | } |
| | 2483 | 265 | | } |
| | | 266 | | |
| | | 267 | | /// <summary> |
| | | 268 | | /// Returns the cached or recalculated clearance value to nearby obstacles. |
| | | 269 | | /// </summary> |
| | | 270 | | public byte GetNeighborClearance() |
| | | 271 | | { |
| | 223 | 272 | | CheckClearance(); |
| | 223 | 273 | | return _clearanceRadiusInVoxels; |
| | | 274 | | } |
| | | 275 | | |
| | | 276 | | /// <summary> |
| | | 277 | | /// If this unit is too fat to fit. |
| | | 278 | | /// </summary> |
| | | 279 | | internal bool IsImpassable(Fixed64 unitSize) |
| | | 280 | | { |
| | 22153 | 281 | | if (unitSize <= Fixed64.Zero) |
| | 2 | 282 | | return false; |
| | | 283 | | |
| | 22151 | 284 | | PathingWorldState ownerState = RequireOwnerState(); |
| | 22151 | 285 | | Fixed64 voxelSize = ownerState.World.VoxelSize; |
| | 22151 | 286 | | if (unitSize <= voxelSize) |
| | 21732 | 287 | | return !IsWalkable; |
| | | 288 | | |
| | | 289 | | // Only evaluates local radial clearance from current voxel. |
| | | 290 | | // Does not account for directional corner blocking |
| | 419 | 291 | | CheckClearance(); |
| | | 292 | | |
| | | 293 | | // How many voxels wide our agent is, in cell terms |
| | 418 | 294 | | int required = (unitSize / voxelSize).CeilToInt(); |
| | | 295 | | // If there aren't at least that many free voxels around, it can't go |
| | | 296 | | |
| | 418 | 297 | | return required > _clearanceRadiusInVoxels; |
| | | 298 | | } |
| | | 299 | | |
| | | 300 | | /// <summary> |
| | | 301 | | /// Validates or recalculates the clearance degree from nearby voxels. |
| | | 302 | | /// </summary> |
| | | 303 | | private void CheckClearance() |
| | | 304 | | { |
| | 642 | 305 | | if (Neighbors == null) |
| | 1 | 306 | | throw new InvalidOperationException("Must call BindNeighbors() before clearance."); |
| | | 307 | | |
| | 641 | 308 | | if (_isClearanceValid) |
| | 279 | 309 | | return; |
| | | 310 | | |
| | 362 | 311 | | _isClearanceValid = true; |
| | | 312 | | |
| | 362 | 313 | | if (!TryGetClearanceOrigin(out Voxel? origin)) |
| | | 314 | | { |
| | 2 | 315 | | _clearanceRadiusInVoxels = origin != null && origin.IsBlocked ? (byte)0 : DefaultDegreeCap; |
| | 2 | 316 | | return; |
| | | 317 | | } |
| | | 318 | | |
| | 360 | 319 | | _clearanceRadiusInVoxels = ComputeClearanceRadius(); |
| | 360 | 320 | | } |
| | | 321 | | |
| | | 322 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 323 | | private bool TryGetClearanceOrigin([MaybeNullWhen(false)] out Voxel origin) |
| | | 324 | | { |
| | 362 | 325 | | return TryGetGridAndVoxel(WorldIndex, out _, out origin) |
| | 362 | 326 | | && IsWalkable; |
| | | 327 | | } |
| | | 328 | | |
| | | 329 | | private bool TryGetGridAndVoxel( |
| | | 330 | | WorldVoxelIndex voxelIndex, |
| | | 331 | | out VoxelGrid? grid, |
| | | 332 | | out Voxel? voxel) |
| | | 333 | | { |
| | 4619 | 334 | | return RequireOwnerState().World.TryGetGridAndVoxel(voxelIndex, out grid, out voxel); |
| | | 335 | | } |
| | | 336 | | |
| | | 337 | | private PathingWorldState RequireOwnerState() => |
| | 26777 | 338 | | OwnerState ?? throw new InvalidOperationException("Solid chart partition requires an owning pathing context."); |
| | | 339 | | |
| | | 340 | | private byte ComputeClearanceRadius() |
| | | 341 | | { |
| | | 342 | | // BFS from this voxel until we hit any blocked-or-missing neighbor |
| | 360 | 343 | | byte best = DefaultDegreeCap; |
| | 360 | 344 | | SwiftQueue<(SolidChartPartition v, byte dist)> q = ClearanceQueuePool.Rent(); |
| | 360 | 345 | | SwiftHashSet<SolidChartPartition> visited = PathManager.PartitionSetPool.Rent(); |
| | | 346 | | |
| | | 347 | | try |
| | | 348 | | { |
| | 360 | 349 | | q.Enqueue((this, 0)); |
| | 360 | 350 | | visited.Add(this); |
| | | 351 | | |
| | | 352 | | // stop BFS either when queue empty or we’ve already found best=1 |
| | 2881 | 353 | | while (q.Count > 0 && best > 1) |
| | | 354 | | { |
| | 2521 | 355 | | (SolidChartPartition part, byte dist) = q.Dequeue(); |
| | 2521 | 356 | | ExploreClearanceNeighbors(part, dist, visited, q, ref best); |
| | | 357 | | } |
| | | 358 | | |
| | | 359 | | // clamp to cap so you never return > DefaultDegreeCap |
| | 360 | 360 | | return Math.Min(best, DefaultDegreeCap); |
| | | 361 | | } |
| | | 362 | | finally |
| | | 363 | | { |
| | 360 | 364 | | ClearanceQueuePool.Release(q); |
| | 360 | 365 | | PathManager.PartitionSetPool.Release(visited); |
| | 360 | 366 | | } |
| | 360 | 367 | | } |
| | | 368 | | |
| | | 369 | | private static void ExploreClearanceNeighbors( |
| | | 370 | | SolidChartPartition part, |
| | | 371 | | byte dist, |
| | | 372 | | SwiftHashSet<SolidChartPartition> visited, |
| | | 373 | | SwiftQueue<(SolidChartPartition v, byte dist)> queue, |
| | | 374 | | ref byte best) |
| | | 375 | | { |
| | 2521 | 376 | | SolidChartPartition?[]? neighbors = part.Neighbors; |
| | 2521 | 377 | | if (neighbors == null) |
| | 0 | 378 | | return; |
| | | 379 | | |
| | 136134 | 380 | | for (int i = 0; i < neighbors.Length; i++) |
| | | 381 | | { |
| | 65546 | 382 | | byte nextDist = (byte)(dist + 1); |
| | 65546 | 383 | | SolidChartPartition? neighbor = neighbors[i]; |
| | | 384 | | |
| | 65546 | 385 | | if (IsClearanceBoundary(i, neighbor)) |
| | | 386 | | { |
| | 3364 | 387 | | best = Math.Min(best, nextDist); |
| | 3364 | 388 | | continue; |
| | | 389 | | } |
| | | 390 | | |
| | 62182 | 391 | | if (neighbor != null && ShouldExpandClearanceSearch(nextDist, best, neighbor, visited)) |
| | 2550 | 392 | | queue.Enqueue((neighbor, nextDist)); |
| | | 393 | | } |
| | 2521 | 394 | | } |
| | | 395 | | |
| | | 396 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 397 | | private static bool IsClearanceBoundary(int neighborIndex, SolidChartPartition? neighbor) |
| | | 398 | | { |
| | 65546 | 399 | | if (neighbor != null && neighbor.IsWalkable) |
| | 16810 | 400 | | return false; |
| | | 401 | | |
| | | 402 | | // skip above, below, or any above/below diagonals |
| | 48736 | 403 | | return neighborIndex != 4 && neighborIndex != 5 && neighborIndex < 10; |
| | | 404 | | } |
| | | 405 | | |
| | | 406 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | | 407 | | private static bool ShouldExpandClearanceSearch( |
| | | 408 | | byte nextDist, |
| | | 409 | | byte best, |
| | | 410 | | SolidChartPartition? neighbor, |
| | | 411 | | SwiftHashSet<SolidChartPartition> visited) |
| | | 412 | | { |
| | 16810 | 413 | | return neighbor != null |
| | 16810 | 414 | | && nextDist < best |
| | 16810 | 415 | | && nextDist < DefaultDegreeCap |
| | 16810 | 416 | | && visited.Add(neighbor); |
| | | 417 | | } |
| | | 418 | | |
| | | 419 | | #region NavigationChart Management |
| | | 420 | | |
| | | 421 | | /// <summary> |
| | | 422 | | /// Applies the resolved overlap state for this voxel to the active solid partition. |
| | | 423 | | /// </summary> |
| | | 424 | | internal void ApplyAuthoredState( |
| | | 425 | | ResolvedChartVoxelState? state, |
| | | 426 | | string? effectiveChartOwner, |
| | | 427 | | NavigationChartCell effectiveCell) |
| | | 428 | | { |
| | 2457 | 429 | | ChartOwners ??= new SwiftHashSet<string>(); |
| | 2457 | 430 | | ChartOwners.Clear(); |
| | 2457 | 431 | | state?.AddChartOwnersTo(ChartOwners); |
| | | 432 | | |
| | 2457 | 433 | | EffectiveChartOwner = effectiveChartOwner; |
| | 2457 | 434 | | _chartPathCostModifier = effectiveCell.PathCostModifier; |
| | 2457 | 435 | | ChartFlags = effectiveCell.Flags; |
| | 2457 | 436 | | } |
| | | 437 | | |
| | | 438 | | /// <summary> |
| | | 439 | | /// Returns true if the partition is claimed by the given map name. |
| | | 440 | | /// </summary> |
| | | 441 | | [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| | 11 | 442 | | public bool BelongsTo(string mapName) => ChartOwners?.Contains(mapName) == true; |
| | | 443 | | |
| | | 444 | | #endregion |
| | | 445 | | |
| | | 446 | | /// <summary> |
| | | 447 | | /// Records the reachability component assigned by the latest matching solid-partition snapshot. |
| | | 448 | | /// </summary> |
| | | 449 | | internal void SetReachabilityComponent(int snapshotKey, int version, int componentId) |
| | | 450 | | { |
| | 822 | 451 | | _reachabilitySnapshotKey = snapshotKey; |
| | 822 | 452 | | _reachabilityVersion = version; |
| | 822 | 453 | | _reachabilityComponentId = componentId; |
| | 822 | 454 | | } |
| | | 455 | | |
| | | 456 | | /// <summary> |
| | | 457 | | /// Tries to read the component recorded for the requested solid-partition reachability snapshot. |
| | | 458 | | /// </summary> |
| | | 459 | | internal bool TryGetReachabilityComponent(int snapshotKey, int version, out int componentId) |
| | | 460 | | { |
| | 3392 | 461 | | if (_reachabilitySnapshotKey == snapshotKey && _reachabilityVersion == version) |
| | | 462 | | { |
| | 3368 | 463 | | componentId = _reachabilityComponentId; |
| | 3368 | 464 | | return true; |
| | | 465 | | } |
| | | 466 | | |
| | 24 | 467 | | componentId = 0; |
| | 24 | 468 | | return false; |
| | | 469 | | } |
| | | 470 | | |
| | | 471 | | /// <inheritdoc/> |
| | | 472 | | public override int GetHashCode() |
| | | 473 | | { |
| | | 474 | | unchecked |
| | | 475 | | { |
| | 133978 | 476 | | VoxelIndex voxelIndex = WorldIndex.VoxelIndex; |
| | 133978 | 477 | | int hash = 17; |
| | 133978 | 478 | | hash = (hash * 31) + WorldIndex.WorldSpawnToken; |
| | 133978 | 479 | | hash = (hash * 31) + WorldIndex.GridIndex; |
| | 133978 | 480 | | hash = (hash * 31) + WorldIndex.GridSpawnToken; |
| | 133978 | 481 | | hash = (hash * 31) + voxelIndex.x; |
| | 133978 | 482 | | hash = (hash * 31) + voxelIndex.y; |
| | 133978 | 483 | | hash = (hash * 31) + voxelIndex.z; |
| | 133978 | 484 | | return hash; |
| | | 485 | | } |
| | | 486 | | } |
| | | 487 | | } |