Protfolio-Emanuel-Polsky / Assets / _Project / Code / PathFinding / Systems / CalculatePathSystem.cs
CalculatePathSystem.cs
Raw
using GarmentButton.FlatenArray;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;


namespace GarmentButton.PathFinding
{
    [BurstCompile]
    [UpdateAfter(typeof(DebugMouseHitSystem))]
    public partial struct CalculatePathSystem : ISystem
    {


        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<EndSimulationEntityCommandBufferSystem.Singleton>();
            state.RequireForUpdate<GridAdditionalData>();
            var queryGrid = SystemAPI.QueryBuilder().WithAll<GridData, Grid>().WithNone<UpdateCheck, UpdatePoints, CastTag>().Build();

            var query = SystemAPI.QueryBuilder().WithAll<PathElement, EndPointPath, LocalTransform>().Build();
            state.RequireForUpdate(query);
            state.RequireForUpdate(queryGrid);
        }




        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var gridData = SystemAPI.GetSingleton<GridData>();
            var gridAdditional = SystemAPI.GetSingleton<GridAdditionalData>();
            BlobAssetReference<PointPathBlobAsset> grid = SystemAPI.GetSingleton<Grid>().Value;
            var converter = new FlatenFloatArray2D(gridData.GridSize, gridData.CellSize, gridData.Center);
            var lenghtGrid = grid.Value.Array.Length;
            var ecb = SystemAPI.GetSingleton<EndSimulationEntityCommandBufferSystem.Singleton>().CreateCommandBuffer(state.WorldUnmanaged);

            state.Dependency = new PathCalculationJob
            {
                Converter = converter,
                ECB = ecb.AsParallelWriter(),
                GroundTypes = gridAdditional.Types.AsReadOnly(),
                Grid = grid,
                LenghtGrid = lenghtGrid,
            }.ScheduleParallel(state.Dependency);
        }

    }
    [WithAll(typeof(EndPointPath))]
    [BurstCompile]
    public partial struct PathCalculationJob : IJobEntity
    {

        private const int MOVE_DISTANCE_STRIGHT = 10;
        private const int MOVE_DISTANCE_DAIGNOLE = 14;
        private const int MOVE_DISTANCE_HEIGHT = 10;
        private const int LIMIT_DISTANCE_HEIGHT = 35;
        private const int HOW_FAR_CAN_JUMP = 10;
        private const int START_SEARCH_DISTANCE = 10;
        private const ushort INVALID_END_POINT_INDEX = ushort.MaxValue;


        [ReadOnly] public BlobAssetReference<PointPathBlobAsset> Grid;
        [ReadOnly] public NativeArray<GroundType>.ReadOnly GroundTypes;
        [ReadOnly] public int LenghtGrid;
        [ReadOnly] public FlatenFloatArray2D Converter;
        public EntityCommandBuffer.ParallelWriter ECB;
        public void Execute(Entity entity, [ChunkIndexInQuery] int sortKey, ref DynamicBuffer<PathElement> buffer, ref EndPointIndexPath endPointIndex, in EndPointPath target, in LocalTransform starPosition)
        {

            buffer.Clear();
            var gridPath = new NativeArray<PathNode>(LenghtGrid, Allocator.Temp);
            for (int i = 0; i < LenghtGrid; i++)
            {
                var point = Grid.Value.Array[i];
                var data = new PathNode
                {
                    Position = new int2(point.X, point.Z),
                    Height = point.Y,
                    CurrentIndex = i,
                    GCost = int.MaxValue,
                    CameFromIndex = -1
                };
                data.CalculateFCost();

                gridPath[i] = data;
            }

            var destination = target.Position;
            var starIndex = Converter.GetCellAtPosition(starPosition.Position.xz);
            var destinationIndex = Converter.GetCellAtPosition(destination.xz);
            endPointIndex.Value = INVALID_END_POINT_INDEX;
            starIndex = GetNeighbourNodesForStart(starIndex, Converter, ((int)(starPosition.Position.y * 10)) + 1);
            var starPoint = gridPath[starIndex];
            var endPoint = gridPath[destinationIndex];
            starPoint.GCost = 0;
            starPoint.HCost = CalculateDistance(starPoint, endPoint);
            starPoint.CalculateFCost();
            gridPath[starIndex] = starPoint;

            //Wanted To search
            var initialSearchCapacity = math.min(LenghtGrid, 128);
            var requestToSearch = new NativeList<PathNode>(initialSearchCapacity, Allocator.Temp);

            //Already Searched
            var searched = new NativeList<PathNode>(initialSearchCapacity, Allocator.Temp);
            var closed = new NativeArray<byte>(LenghtGrid, Allocator.Temp, NativeArrayOptions.ClearMemory);
            var openIndexes = new NativeArray<int>(LenghtGrid, Allocator.Temp, NativeArrayOptions.ClearMemory);
            AddToOpen(ref requestToSearch, openIndexes, starPoint);
            var gridWidth = Converter.GridSize.x;
            var gridHeight = Converter.GridSize.y;
            var foundUsablePath = false;

            while (requestToSearch.Length > 0)
            {
                var currentNode = PopOpen(ref requestToSearch, openIndexes);
                if (closed[currentNode.CurrentIndex] != 0)
                    continue;

                if (currentNode.CurrentIndex == endPoint.CurrentIndex)
                {
                    CalculatePath(buffer, gridPath, currentNode, entity, sortKey);
                    foundUsablePath = true;
                    break;
                }

                AddToClosed(currentNode.CurrentIndex, closed, ref searched, currentNode);

                var currentGroundType = GroundTypes[currentNode.CurrentIndex];
                var currentX = currentNode.CurrentIndex % gridWidth;
                var currentY = currentNode.CurrentIndex / gridWidth;
                for (var yOffset = -1; yOffset <= 1; yOffset++)
                {
                    for (var xOffset = -1; xOffset <= 1; xOffset++)
                    {
                        if ((xOffset | yOffset) == 0)
                            continue;

                        var neighbourX = currentX + xOffset;
                        var neighbourY = currentY + yOffset;
                        if ((uint)neighbourX >= (uint)gridWidth || (uint)neighbourY >= (uint)gridHeight)
                            continue;

                        var index = neighbourY * gridWidth + neighbourX;
                        var neighbourNode = gridPath[index];
                        var neighbourGroundType = GroundTypes[index];

                        if ((int)neighbourGroundType >= (int)GroundType.Blocked)
                        {
                            AddToClosed(index, closed, ref searched, neighbourNode);
                            continue;
                        }
                        if (closed[index] != 0)
                            continue;

                        int layerBase = 0;
                        switch (neighbourGroundType)
                        {
                            case GroundType.Air:
                                if (currentGroundType == GroundType.Air && currentNode.CameFromIndex != -1)
                                    if (!IsSameDirection(gridPath[currentNode.CameFromIndex], currentNode, neighbourNode))
                                        layerBase = 20;
                                break;
                            case GroundType.MoveAble:
                            case GroundType.NearGround:
                                layerBase = 15;
                                break;

                        }

                        if (!CanJumpIt(currentNode, neighbourNode))
                        {
                            continue;
                        }

                        int tentativeGCost = currentNode.GCost + CalculateDistance(currentNode, neighbourNode) + layerBase;
                        if (tentativeGCost < neighbourNode.GCost)
                        {


                            neighbourNode.CameFromIndex = currentNode.CurrentIndex;



                            neighbourNode.GCost = tentativeGCost;
                            neighbourNode.HCost = CalculateDistance(neighbourNode, endPoint);
                            neighbourNode.CalculateFCost();
                            gridPath[index] = neighbourNode;

                            if (!CheckCanJumpThrough(index, gridPath))
                            {
                                RemoveNodeFromOpen(ref requestToSearch, openIndexes, index);
                                AddToClosed(index, closed, ref searched, neighbourNode);
                                continue;
                            }


                            var indexOpenList = openIndexes[index] - 1;
                            if (indexOpenList >= 0)
                            {
                                UpdateOpen(ref requestToSearch, openIndexes, indexOpenList, neighbourNode);
                                continue;
                            }
                            AddToOpen(ref requestToSearch, openIndexes, neighbourNode);
                        }

                    }
                }
            }

            if (buffer.Length == 0)
            {
                foreach (var node in searched)
                {
                    var closesAirPoint = GroundTypes[node.CurrentIndex] == GroundType.Air && node.CameFromIndex != -1;
                    if (!closesAirPoint)
                        continue;

                    CalculatePath(buffer, gridPath, node, entity, sortKey);
                    foundUsablePath = buffer.Length > 0;
                    break;
                }
            }

            if (foundUsablePath)
                endPointIndex.Value = (ushort)destinationIndex;

            ECB.SetComponentEnabled<EndPointPath>(sortKey, entity, false);
            gridPath.Dispose();
            requestToSearch.Dispose();
            searched.Dispose();
            closed.Dispose();
            openIndexes.Dispose();
        }



        private bool IsSameDirection(PathNode back, PathNode currentNode, PathNode neighbourNode)
        {
            var backDirection = back.Position - currentNode.Position;
            var frontDirection = currentNode.Position - neighbourNode.Position;
            return backDirection.x == frontDirection.x && backDirection.y == frontDirection.y;
        }
        private void AddToClosed(int index, NativeArray<byte> closed, ref NativeList<PathNode> searched, PathNode node)
        {
            if (closed[index] != 0)
                return;

            closed[index] = 1;
            searched.Add(node);
        }

        private void RemoveNodeFromOpen(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, int nodeIndex)
        {
            var openListIndex = openIndexes[nodeIndex] - 1;
            if (openListIndex < 0)
                return;

            RemoveFromOpen(ref openList, openIndexes, openListIndex);
        }

        private PathNode PopOpen(ref NativeList<PathNode> openList, NativeArray<int> openIndexes)
        {
            var node = openList[0];
            RemoveFromOpen(ref openList, openIndexes, 0);
            return node;
        }

        private void AddToOpen(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, PathNode node)
        {
            openList.Add(node);
            var openListIndex = openList.Length - 1;
            openIndexes[node.CurrentIndex] = openListIndex + 1;
            SiftOpenUp(ref openList, openIndexes, openListIndex);
        }

        private void UpdateOpen(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, int openListIndex, PathNode node)
        {
            openList[openListIndex] = node;
            openIndexes[node.CurrentIndex] = openListIndex + 1;
            SiftOpenUp(ref openList, openIndexes, openListIndex);
        }

        private void RemoveFromOpen(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, int openListIndex)
        {
            var removedNodeIndex = openList[openListIndex].CurrentIndex;
            var lastOpenIndex = openList.Length - 1;
            var movedNodeIndex = openList[lastOpenIndex].CurrentIndex;

            openIndexes[removedNodeIndex] = 0;
            openList[openListIndex] = openList[lastOpenIndex];
            openList.RemoveAtSwapBack(lastOpenIndex);

            if (openListIndex < openList.Length)
            {
                openIndexes[movedNodeIndex] = openListIndex + 1;
                SiftOpenDown(ref openList, openIndexes, openListIndex);
                SiftOpenUp(ref openList, openIndexes, openListIndex);
            }
        }

        private void SiftOpenUp(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, int openListIndex)
        {
            while (openListIndex > 0)
            {
                var parentIndex = (openListIndex - 1) >> 1;
                if (HasHigherPriority(openList[parentIndex], openList[openListIndex]))
                    break;

                SwapOpen(ref openList, openIndexes, parentIndex, openListIndex);
                openListIndex = parentIndex;
            }
        }

        private void SiftOpenDown(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, int openListIndex)
        {
            while (true)
            {
                var leftIndex = (openListIndex << 1) + 1;
                if (leftIndex >= openList.Length)
                    break;

                var rightIndex = leftIndex + 1;
                var bestIndex = leftIndex;
                if (rightIndex < openList.Length && HasHigherPriority(openList[rightIndex], openList[leftIndex]))
                    bestIndex = rightIndex;

                if (HasHigherPriority(openList[openListIndex], openList[bestIndex]))
                    break;

                SwapOpen(ref openList, openIndexes, openListIndex, bestIndex);
                openListIndex = bestIndex;
            }
        }

        private void SwapOpen(ref NativeList<PathNode> openList, NativeArray<int> openIndexes, int leftIndex, int rightIndex)
        {
            var temp = openList[leftIndex];
            openList[leftIndex] = openList[rightIndex];
            openList[rightIndex] = temp;
            openIndexes[openList[leftIndex].CurrentIndex] = leftIndex + 1;
            openIndexes[openList[rightIndex].CurrentIndex] = rightIndex + 1;
        }

        private bool HasHigherPriority(PathNode left, PathNode right)
        {
            if (left.FCost != right.FCost)
                return left.FCost < right.FCost;

            return left.HCost < right.HCost;
        }

        public void CalculatePath(DynamicBuffer<PathElement> buffer, NativeArray<PathNode> pathNodes, PathNode endNodes, Entity entity, int index)
        {
            var reversePath = new NativeList<int>(40, Allocator.Temp);
            var currentNode = endNodes;
            var lastGroundType = GroundType.Ground;
            var currentDirection = int2.zero;
            var lastHeightDirection = 0f;
            while (currentNode.CameFromIndex != -1)
            {
                var direction = pathNodes[currentNode.CameFromIndex].Position - pathNodes[currentNode.CurrentIndex].Position;
                var heightDirection = pathNodes[currentNode.CameFromIndex].Height - pathNodes[currentNode.CurrentIndex].Height;
                var currentGround = GroundTypes[currentNode.CurrentIndex];
                var nextGround = GroundTypes[currentNode.CameFromIndex];

                var sameDirection = (currentDirection.x == direction.x && currentDirection.y == direction.y);
                var sameType = lastGroundType == currentGround;
                var isDifferentTypeNext = currentGround != nextGround;
                var sameHeightDirection = lastHeightDirection == heightDirection;

                if (sameDirection && sameType && !isDifferentTypeNext && sameHeightDirection)
                {

                    // reversePath[reversePath.Length - 1] = currentNode.CurrentIndex;
                }
                else
                {
                    reversePath.Add(currentNode.CurrentIndex);
                }
                lastHeightDirection = heightDirection;
                lastGroundType = currentGround;
                currentDirection = direction;
                currentNode = pathNodes[currentNode.CameFromIndex];
            }
            for (int i = reversePath.Length - 1; i >= 0; i--)
            {
                buffer.Add(new PathElement
                {
                    IndexGrid = (ushort)reversePath[i]
                });

            }
            reversePath.Dispose();

#if UNITY_EDITOR && PATHFINDING_DEBUG
            var path = ECB.AddBuffer<PointsData>(index, entity);

            for (int i = 0; i < pathNodes.Length; i++)
                path.Add(new PointsData { Cost = pathNodes[i].GCost });
            
            ECB.AddComponent<DebugAll>(index, entity);
#endif
        }
        private bool CheckCanJumpThrough(int index, NativeArray<PathNode> pathPoint)
        {
            var currentNode = pathPoint[index];
            int count = 0;
            while (currentNode.CameFromIndex != -1 && GroundTypes[currentNode.CurrentIndex] == GroundType.Air)
            {
                if (count > HOW_FAR_CAN_JUMP)
                {
                    return false;
                }

                currentNode = pathPoint[currentNode.CameFromIndex];
                count++;
            }
            return true;
        }
        private bool IsJumpingToAir(PathNode current, PathNode next) => (GroundTypes[current.CurrentIndex] != GroundType.Air && GroundTypes[next.CurrentIndex] == GroundType.Air);
        private bool IsJumpingToGround(PathNode current, PathNode next) => (GroundTypes[current.CurrentIndex] == GroundType.Ground && GroundTypes[next.CurrentIndex] == GroundType.Ground);
        private bool CanJumpIt(PathNode current, PathNode next)
        {
            var isNeededJump = next.Height > current.Height;
            if (!isNeededJump)
                return true;

            var differenceHeight = next.Height - current.Height;
            var canReachHeight = differenceHeight < LIMIT_DISTANCE_HEIGHT;
            if (!canReachHeight)
                return false;

            var isHeightMoreThanOneBox = differenceHeight > 15f;
            if (!isHeightMoreThanOneBox)
                return true;

            return IsJumpingToGround(current, next);
        }

        private int GetNeighbourNodesForStart(int currentNodeIndex, FlatenFloatArray2D converter, int height)
        {
            if (Grid.Value.Array[currentNodeIndex].Y < height + 2)
                return currentNodeIndex;

            var gridWidth = converter.GridSize.x;
            var gridHeight = converter.GridSize.y;
            var originX = currentNodeIndex % gridWidth;
            var originY = currentNodeIndex / gridWidth;
            for (var distance = 1; distance <= START_SEARCH_DISTANCE; distance++)
            {
                for (var direction = 0; direction < 8; direction++)
                {
                    var neighbourX = originX;
                    var neighbourY = originY;
                    switch ((Direction)direction)
                    {
                        case Direction.Up_Left:
                            neighbourX -= distance;
                            neighbourY += distance;
                            break;
                        case Direction.Up:
                            neighbourY += distance;
                            break;
                        case Direction.Up_Right:
                            neighbourX += distance;
                            neighbourY += distance;
                            break;
                        case Direction.Down_Left:
                            neighbourX -= distance;
                            neighbourY -= distance;
                            break;
                        case Direction.Down:
                            neighbourY -= distance;
                            break;
                        case Direction.Down_Right:
                            neighbourX += distance;
                            neighbourY -= distance;
                            break;
                        case Direction.Left:
                            neighbourX -= distance;
                            break;
                        case Direction.Right:
                            neighbourX += distance;
                            break;
                    }

                    if ((uint)neighbourX >= (uint)gridWidth || (uint)neighbourY >= (uint)gridHeight)
                        continue;

                    var index = neighbourY * gridWidth + neighbourX;
                    if (Grid.Value.Array[index].Y > height)
                        continue;

                    return index;
                }
            }
            return currentNodeIndex;
        }
        private int CalculateDistance(PathNode a, PathNode b)
        {
            int xDistance = math.abs(a.Position.x - b.Position.x);
            int yDistance = math.abs(a.Position.y - b.Position.y);
            int heightDistance = a.Height - b.Height;
            int remaining = math.abs(xDistance - yDistance);
            return MOVE_DISTANCE_DAIGNOLE * math.min(xDistance, yDistance) + MOVE_DISTANCE_STRIGHT * remaining + MOVE_DISTANCE_HEIGHT + heightDistance;
        }
    }
}