diff options
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs')
-rw-r--r-- | Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs new file mode 100644 index 0000000..fa2e2cd --- /dev/null +++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Graphs/Navmesh/Voxels/LinkedVoxelField.cs @@ -0,0 +1,295 @@ +using Pathfinding.Jobs; +using Unity.Collections; +using Unity.Mathematics; + +namespace Pathfinding.Graphs.Navmesh.Voxelization.Burst { + struct CellMinMax { + public int objectID; + public int min; + public int max; + } + + public struct LinkedVoxelField : IArenaDisposable { + public const uint MaxHeight = 65536; + public const int MaxHeightInt = 65536; + /// <summary> + /// Constant for default LinkedVoxelSpan top and bottom values. + /// It is important with the U since ~0 != ~0U + /// This can be used to check if a LinkedVoxelSpan is valid and not just the default span + /// </summary> + public const uint InvalidSpanValue = ~0U; + + /// <summary>Initial estimate on the average number of spans (layers) in the voxel representation. Should be greater or equal to 1</summary> + public const float AvgSpanLayerCountEstimate = 8; + + /// <summary>The width of the field along the x-axis. [Limit: >= 0] [Units: voxels]</summary> + public int width; + + /// <summary>The depth of the field along the z-axis. [Limit: >= 0] [Units: voxels]</summary> + public int depth; + /// <summary>The maximum height coordinate. [Limit: >= 0, <= MaxHeight] [Units: voxels]</summary> + public int height; + public bool flatten; + + public NativeList<LinkedVoxelSpan> linkedSpans; + private NativeList<int> removedStack; + private NativeList<CellMinMax> linkedCellMinMax; + + public LinkedVoxelField (int width, int depth, int height) { + this.width = width; + this.depth = depth; + this.height = height; + this.flatten = true; + linkedSpans = new NativeList<LinkedVoxelSpan>(0, Allocator.Persistent); + removedStack = new NativeList<int>(128, Allocator.Persistent); + linkedCellMinMax = new NativeList<CellMinMax>(0, Allocator.Persistent); + } + + void IArenaDisposable.DisposeWith (DisposeArena arena) { + arena.Add(linkedSpans); + arena.Add(removedStack); + arena.Add(linkedCellMinMax); + } + + public void ResetLinkedVoxelSpans () { + int len = width * depth; + + LinkedVoxelSpan df = new LinkedVoxelSpan(InvalidSpanValue, InvalidSpanValue, -1, -1); + + linkedSpans.ResizeUninitialized(len); + linkedCellMinMax.Resize(len, NativeArrayOptions.UninitializedMemory); + for (int i = 0; i < len; i++) { + linkedSpans[i] = df; + linkedCellMinMax[i] = new CellMinMax { + objectID = -1, + min = 0, + max = 0, + }; + } + removedStack.Clear(); + } + + void PushToSpanRemovedStack (int index) { + removedStack.Add(index); + } + + public int GetSpanCount () { + int count = 0; + + int wd = width*depth; + + for (int x = 0; x < wd; x++) { + for (int s = x; s != -1 && linkedSpans[s].bottom != InvalidSpanValue; s = linkedSpans[s].next) { + count += linkedSpans[s].area != 0 ? 1 : 0; + } + } + return count; + } + + public void ResolveSolid (int index, int objectID, int voxelWalkableClimb) { + var minmax = linkedCellMinMax[index]; + + if (minmax.objectID != objectID) return; + + if (minmax.min < minmax.max - 1) { + // Add a span for the solid part of the object. + // + // This span ends at max-1 (where max is the top of the original object). + // This is to avoid issues when merging spans with different areas. + // Assume we had 3 spans like: + // y=0..5 walkable span from another object, area=2 + // y=9..10 walkable span, area=3 + // and min=0, max=10 for the current object. + // If we added a span for the whole solid range (0..10), then it will first get merged with the 0..5 span, receiving its area (assuming walkable climb was high enough), + // and then get merged with the 9..10 span, replacing its area. This would make the final area be 2, instead of 3 like it should be. + // If we instead add a solid span for the range 0..9, then the tie breaking will ensure that the final area is 3. + // Spans are always at least 1 voxel tall, so the solid span will always get merged with the original span. + AddLinkedSpan(index, minmax.min, minmax.max-1, CompactVoxelField.UnwalkableArea, voxelWalkableClimb, objectID); + } + } + + public void SetWalkableBackground () { + int wd = width*depth; + + for (int i = 0; i < wd; i++) { + linkedSpans[i] = new LinkedVoxelSpan(0, 1, 1); + } + } + + public void AddFlattenedSpan (int index, int area) { + if (linkedSpans[index].bottom == InvalidSpanValue) { + linkedSpans[index] = new LinkedVoxelSpan(0, 1, area); + } else { + // The prioritized area is (in order): + // - the unwalkable area (area=0) + // - the higher valued area + linkedSpans[index] = new LinkedVoxelSpan(0, 1, linkedSpans[index].area == 0 || area == 0 ? 0 : math.max(linkedSpans[index].area, area)); + } + } + + public void AddLinkedSpan (int index, int bottom, int top, int area, int voxelWalkableClimb, int objectID) { + var minmax = linkedCellMinMax[index]; + + if (minmax.objectID != objectID) { + linkedCellMinMax[index] = new CellMinMax { + objectID = objectID, + min = bottom, + max = top, + }; + } else { + minmax.min = math.min(minmax.min, bottom); + minmax.max = math.max(minmax.max, top); + linkedCellMinMax[index] = minmax; + } + + // Clamp to bounding box. If the span was outside the bbox, then bottom will become greater than top. + top = math.min(top, height); + bottom = math.max(bottom, 0); + + // Skip span if below or above the bounding box or if the span is zero voxels tall + if (bottom >= top) return; + + var utop = (uint)top; + var ubottom = (uint)bottom; + + // linkedSpans[index] is the span with the lowest y-coordinate at the position x,z such that index=x+z*width + // i.e linkedSpans is a 2D array laid out in a 1D array (for performance and simplicity) + + // Check if there is a root span, otherwise we can just add a new (valid) span and exit + if (linkedSpans[index].bottom == InvalidSpanValue) { + linkedSpans[index] = new LinkedVoxelSpan(ubottom, utop, area); + return; + } + + int prev = -1; + + // Original index, the first span we visited + int oindex = index; + + while (index != -1) { + var current = linkedSpans[index]; + if (current.bottom > utop) { + // If the current span's bottom higher up than the span we want to insert's top, then they do not intersect + // and we should just insert a new span here + break; + } else if (current.top < ubottom) { + // The current span and the span we want to insert do not intersect + // so just skip to the next span (it might intersect) + prev = index; + index = current.next; + } else { + // Intersection! Merge the spans + + // If two spans have almost the same upper y coordinate then + // we don't just pick the area from the topmost span. + // Instead we pick the maximum of the two areas. + // This ensures that unwalkable spans that end up at the same y coordinate + // as a walkable span (very common for vertical surfaces that meet a walkable surface at a ledge) + // do not end up making the surface unwalkable. + // This is also important for larger distances when there are very small obstacles on the ground. + // For example if a small rock happened to have a surface that was greater than the max slope angle, + // then its surface would be unwalkable. Without this check, even if the rock was tiny, it would + // create a hole in the navmesh. + + // voxelWalkableClimb is flagMergeDistance, when a walkable flag is favored before an unwalkable one + // So if a walkable span intersects an unwalkable span, the walkable span can be up to voxelWalkableClimb + // below the unwalkable span and the merged span will still be walkable. + // If both spans are walkable we use the area from the topmost span. + if (math.abs((int)utop - (int)current.top) < voxelWalkableClimb && (area == CompactVoxelField.UnwalkableArea || current.area == CompactVoxelField.UnwalkableArea)) { + // linkedSpans[index] is the lowest span, but we might use that span's area anyway if it is walkable + area = math.max(area, current.area); + } else { + // Pick the area from the topmost span + if (utop < current.top) area = current.area; + } + + // Find the new bottom and top for the merged span + ubottom = math.min(current.bottom, ubottom); + utop = math.max(current.top, utop); + + // Find the next span in the linked list + int next = current.next; + if (prev != -1) { + // There is a previous span + // Remove this span from the linked list + // TODO: Kinda slow. Check what asm is generated. + var p = linkedSpans[prev]; + p.next = next; + linkedSpans[prev] = p; + + // Add this span index to a list for recycling + PushToSpanRemovedStack(index); + + // Move to the next span in the list + index = next; + } else if (next != -1) { + // This was the root span and there is a span left in the linked list + // Remove this span from the linked list by assigning the next span as the root span + linkedSpans[oindex] = linkedSpans[next]; + + // Recycle the old span index + PushToSpanRemovedStack(next); + + // Move to the next span in the list + // NOP since we just removed the current span, the next span + // we want to visit will have the same index as we are on now (i.e oindex) + } else { + // This was the root span and there are no other spans in the linked list + // Just replace the root span with the merged span and exit + linkedSpans[oindex] = new LinkedVoxelSpan(ubottom, utop, area); + return; + } + } + } + + // We now have a merged span that needs to be inserted + // and connected with the existing spans + + // The new merged span will be inserted right after 'prev' (if it exists, otherwise before index) + + // Take a node from the recycling stack if possible + // Otherwise create a new node (well, just a new index really) + int nextIndex; + if (removedStack.Length > 0) { + // Pop + nextIndex = removedStack[removedStack.Length - 1]; + removedStack.RemoveAtSwapBack(removedStack.Length - 1); + } else { + nextIndex = linkedSpans.Length; + linkedSpans.Resize(linkedSpans.Length + 1, NativeArrayOptions.UninitializedMemory); + } + + if (prev != -1) { + linkedSpans[nextIndex] = new LinkedVoxelSpan(ubottom, utop, area, linkedSpans[prev].next); + // TODO: Check asm + var p = linkedSpans[prev]; + p.next = nextIndex; + linkedSpans[prev] = p; + } else { + linkedSpans[nextIndex] = linkedSpans[oindex]; + linkedSpans[oindex] = new LinkedVoxelSpan(ubottom, utop, area, nextIndex); + } + } + } + + public struct LinkedVoxelSpan { + public uint bottom; + public uint top; + + public int next; + + /*Area + * 0 is an unwalkable span (triangle face down) + * 1 is a walkable span (triangle face up) + */ + public int area; + + public LinkedVoxelSpan (uint bottom, uint top, int area) { + this.bottom = bottom; this.top = top; this.area = area; this.next = -1; + } + + public LinkedVoxelSpan (uint bottom, uint top, int area, int next) { + this.bottom = bottom; this.top = top; this.area = area; this.next = next; + } + } +} |